diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..2132af5d --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,50 @@ + +# Pull Request (PR) Checklist + +Thank you for your contribution! Please confirm that you've checked all the boxes below before submitting your PR. Use `[x]` to check a box, e.g., `[x]`, and make sure there's no space around the brackets. + +## PR Context + +- **Type**: (Bug Fix / Feature / Refactor / Regression Fix) _Choose one and delete the rest._ +- **Issue Link**: [Redmine Issue](https://redmine.buzzhives.com/issues/[XXXXX]) +- **Risk Factor**: (Low / Medium / High) - _Provide a brief explanation of the risk._ + +## Changes + +_Describe your changes in detail, highlighting the problem it solves or the feature it adds._ + +- +- +- + +## Checklist for Reviewers + +### Documentation and Code Quality +- [ ] **KDocs Documentation**: Are all changes, new functionalities, and classes documented with KDocs? +- [ ] **Architectural Patterns**: Is there consistent and proper use of architectural patterns (e.g., MVVM, MVP)? + +### Testing and Reliability +- [ ] **Unit Testing**: Are there unit tests for all new functionalities and classes? +- [ ] **Emulator and Real Device Testing**: Has the application been tested on both emulators and real devices to ensure compatibility? + +### Error Handling and Logging +- [ ] **Error Handling**: Are errors and exceptions caught and handled gracefully, ensuring the app remains stable? +- [ ] **Logging**: Is there proper logging in place for critical errors and information, aiding in debugging and monitoring? + + +## Testing Procedure + +_If applicable, provide steps or commands for testing your changes. This can help reviewers and testers._ + +- +- +- + +## Work-in-Progress (WIP) + +_List any remaining work or areas that need additional focus. This section can be updated as the work progresses._ + +- [ ] +- [ ] + +_Remember to keep this template updated based on the feedback and evolving project standards._ diff --git a/.github/workflows/android_ci.yml b/.github/workflows/android_ci.yml new file mode 100644 index 00000000..dd9208e9 --- /dev/null +++ b/.github/workflows/android_ci.yml @@ -0,0 +1,37 @@ +name: Android CI + +on: + push: + branches: [ disable-temporarily ] + pull_request: + branches: [ disable-temporarily ] + +jobs: + build: + name: Build and Test + runs-on: ubuntu-20.04 + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '17' + + - name: Grant execute permission for Gradle wrapper + run: chmod +x gradlew + + - name: Cache Gradle dependencies + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + ~/.android/build-cache + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} + restore-keys: | + ${{ runner.os }}-gradle- + - name: Run tests + run: ./gradlew test \ No newline at end of file diff --git a/.github/workflows/auto_stale_pr.yml b/.github/workflows/auto_stale_pr.yml new file mode 100644 index 00000000..f2fd6d08 --- /dev/null +++ b/.github/workflows/auto_stale_pr.yml @@ -0,0 +1,14 @@ +name: 'Close stale issues and PRs' +on: + schedule: + - cron: '30 1 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v9 + with: + stale-issue-message: 'This issue is stale because it has been open 5 days with no activity. Remove stale label or comment or this will be closed in 2 days.' + days-before-stale: 5 + days-before-close: 2 \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a2f8816a..00000000 --- a/.travis.yml +++ /dev/null @@ -1,32 +0,0 @@ -language: android -dist: trusty - -# To avoid following similar issues: -# * https://github.com/travis-ci/travis-ci/issues/5582 -# * https://github.com/OneBusAway/onebusaway-android/pull/476 -sudo: true - -android: - components: - # Use the latest revision of Android SDK Tools - - tools - - platform-tools - - # The BuildTools version used by your project - - build-tools-28.0.3 - - # The SDK version used to compile your project - - android-28 - - # Additional components - - extra-google-google_play_services - - extra-android-m2repository - - extra-google-m2repository - -before_install: - - yes | sdkmanager "platforms;android-29" - -script: - # It should be `test` not `testDebugUnitTest` to - # execute tests for pure Java/Kotlin modules. - ./gradlew test \ No newline at end of file diff --git a/CommonCoreLegacy/build.gradle b/CommonCoreLegacy/build.gradle index 72f71b47..199fda28 100644 --- a/CommonCoreLegacy/build.gradle +++ b/CommonCoreLegacy/build.gradle @@ -28,6 +28,9 @@ android { release { consumerProguardFile 'proguard-rules.pro' } + staging { + debuggable true + } } lintOptions { @@ -71,6 +74,7 @@ dependencies { implementation 'com.github.skedgo:commons-collections:v1.0' debugImplementation project(':TripKitDomain') + stagingImplementation project(':TripKitDomain') releaseImplementation project(':TripKitDomain') implementation libs.kotlin diff --git a/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/common/model/Location.java b/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/common/model/Location.java index 6d2a12d5..9ba3d76e 100644 --- a/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/common/model/Location.java +++ b/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/common/model/Location.java @@ -68,6 +68,8 @@ public class Location implements Parcelable { */ public static final int TYPE_W3W = 9; + public static final int TYPE_SCHOOL = 11; + public static final int NO_BEARING = Integer.MAX_VALUE; public static final double ZERO_LAT = 0.0; public static final double ZERO_LON = 0.0; @@ -114,6 +116,10 @@ public Location createFromParcel(Parcel in) { List routes = new ArrayList<>(); in.readTypedList(routes, RouteDetails.CREATOR); + List modeIdentifiers = new ArrayList<>(); + in.readList(modeIdentifiers, String.class.getClassLoader()); + location.modeIdentifiers = modeIdentifiers; + return location; } @@ -176,6 +182,8 @@ public Location[] newArray(int size) { private List operators; private List routes; + private List modeIdentifiers; + public Location() { lat = ZERO_LAT; lon = ZERO_LON; @@ -483,6 +491,14 @@ public void setOperators( List operators) { this.operators = operators; } + public List getModeIdentifiers() { + return modeIdentifiers; + } + + public void setModeIdentifiers(List modeIdentifiers) { + this.modeIdentifiers = modeIdentifiers; + } + /** * Get the distance between this and another point *

@@ -568,6 +584,7 @@ public void writeToParcel(Parcel out, int flags) { out.writeString(region); out.writeTypedList(operators); out.writeTypedList(routes); + out.writeList(modeIdentifiers); } /** diff --git a/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/common/model/LocationConstants.kt b/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/common/model/LocationConstants.kt new file mode 100644 index 00000000..31b7bf5c --- /dev/null +++ b/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/common/model/LocationConstants.kt @@ -0,0 +1,3 @@ +package com.skedgo.tripkit.common.model + +const val LOCATION_CLASS_SCHOOL = "SchoolLocation" \ No newline at end of file diff --git a/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/common/model/ScheduledStop.java b/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/common/model/ScheduledStop.java index 20682079..50592293 100644 --- a/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/common/model/ScheduledStop.java +++ b/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/common/model/ScheduledStop.java @@ -35,6 +35,7 @@ public ScheduledStop createFromParcel(Parcel in) { stop.mType = StopType.from(in.readString()); stop.modeInfo = in.readParcelable(ModeInfo.class.getClassLoader()); stop.wheelchairAccessible = (Boolean) in.readValue(Boolean.class.getClassLoader()); + stop.bicycleAccessible = (Boolean) in.readValue(Boolean.class.getClassLoader()); stop.alertHashCodes = in.readArrayList(Long.class.getClassLoader()); return stop; } @@ -63,6 +64,9 @@ public ScheduledStop[] newArray(int size) { @SerializedName("wheelchairAccessible") private @Nullable Boolean wheelchairAccessible; + @SerializedName("bicycleAccessible") + private @Nullable + Boolean bicycleAccessible; @SerializedName("alertHashCodes") private @Nullable ArrayList alertHashCodes; @@ -146,6 +150,7 @@ public void fillFrom(Location location) { mParentId = other.mParentId; mCurrentFilter = other.mCurrentFilter; wheelchairAccessible = other.wheelchairAccessible; + bicycleAccessible = other.bicycleAccessible; alertHashCodes = other.alertHashCodes; } } @@ -266,6 +271,15 @@ public void setWheelchairAccessible(@Nullable Boolean wheelchairAccessible) { this.wheelchairAccessible = wheelchairAccessible; } + @Nullable + public Boolean getBicycleAccessible() { + return bicycleAccessible; + } + + public void setBicycleAccessible(@Nullable Boolean bicycleAccessible) { + this.bicycleAccessible = bicycleAccessible; + } + public String getEndStopCode() { return mEndStopCode; } @@ -323,6 +337,7 @@ public void writeToParcel(Parcel out, int flags) { out.writeString(mType == null ? null : mType.toString()); out.writeParcelable(modeInfo, 0); out.writeValue(wheelchairAccessible); + out.writeValue(bicycleAccessible); out.writeList(alertHashCodes); } } diff --git a/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/common/model/ServiceStop.java b/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/common/model/ServiceStop.java index abdf592c..66c3b8f4 100644 --- a/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/common/model/ServiceStop.java +++ b/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/common/model/ServiceStop.java @@ -10,7 +10,7 @@ /** * Represents a future stop of a service in a trip. */ -public class ServiceStop extends Location implements WheelchairAccessible { +public class ServiceStop extends Location implements WheelchairAccessible, BicycleAccessible { public static final Creator CREATOR = new Creator() { public ServiceStop createFromParcel(Parcel in) { Location location = Location.CREATOR.createFromParcel(in); @@ -23,6 +23,7 @@ public ServiceStop createFromParcel(Parcel in) { stop.code = in.readString(); stop.shortName = in.readString(); stop.wheelchairAccessible = (Boolean) in.readValue(Boolean.class.getClassLoader()); + stop.bicycleAccessible = (Boolean) in.readValue(Boolean.class.getClassLoader()); stop.type = StopType.from(in.readString()); return stop; @@ -46,6 +47,7 @@ public ServiceStop[] newArray(int size) { @SerializedName("code") private String code; @SerializedName("shortName") private @Nullable String shortName; @SerializedName("wheelchairAccessible") private @Nullable Boolean wheelchairAccessible; + @SerializedName("bicycleAccessible") private @Nullable Boolean bicycleAccessible; public ServiceStop() {} @@ -101,6 +103,14 @@ public void setWheelchairAccessible(@Nullable Boolean wheelchairAccessible) { this.wheelchairAccessible = wheelchairAccessible; } + @Nullable public Boolean getBicycleAccessible() { + return bicycleAccessible; + } + + public void setBicycleAccessibleAccessible(@Nullable Boolean bicycleAccessible) { + this.bicycleAccessible = bicycleAccessible; + } + public StopType getType() { return type; } @@ -148,6 +158,7 @@ public void writeToParcel(Parcel out, int flags) { out.writeString(code); out.writeString(shortName); out.writeValue(wheelchairAccessible); + out.writeValue(bicycleAccessible); out.writeString(type == null ? null : type.toString()); } diff --git a/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/common/model/TransportMode.java b/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/common/model/TransportMode.java index 7f27706f..17be9768 100644 --- a/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/common/model/TransportMode.java +++ b/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/common/model/TransportMode.java @@ -22,7 +22,7 @@ public class TransportMode { public static final String ID_SHUFFLE = "ps_shu"; public static final String ID_TNC = "ps_tnc"; public static final String ID_BICYCLE = "cy_bic"; - public static final String ID_SCHOOL_BUS = "pt_sch"; + public static final String ID_SCHOOL_BUS = "pt_ltd_SCHOOLBUS"; public static final String ID_PUBLIC_TRANSPORT = "pt_pub"; public static final String ID_MOTORBIKE = "me_mot"; public static final String ID_CAR = "me_car"; @@ -62,7 +62,7 @@ public static int getLocalIconResId(@Nullable String identifier) { return R.drawable.ic_public_transport; } else if (ID_TAXI.equals(identifier)) { return R.drawable.ic_taxi; - } else if (ID_SHUTTLE_BUS.equals(identifier)) { + } else if (ID_SHUTTLE_BUS.equals(identifier) || ID_SCHOOL_BUS.equals(identifier)) { return R.drawable.ic_shuttlebus; } else if (ID_MOTORBIKE.equals(identifier)) { return R.drawable.ic_motorbike; diff --git a/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/common/model/WheelchairAccessible.kt b/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/common/model/WheelchairAccessible.kt index 21a19808..c3d54129 100644 --- a/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/common/model/WheelchairAccessible.kt +++ b/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/common/model/WheelchairAccessible.kt @@ -2,4 +2,8 @@ package com.skedgo.tripkit.common.model interface WheelchairAccessible { val wheelchairAccessible: Boolean? +} + +interface BicycleAccessible { + val bicycleAccessible: Boolean? } \ No newline at end of file diff --git a/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/common/util/TransportModeUtils.java b/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/common/util/TransportModeUtils.java index 0fcf6556..6d29cc13 100644 --- a/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/common/util/TransportModeUtils.java +++ b/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/common/util/TransportModeUtils.java @@ -7,12 +7,12 @@ import android.util.DisplayMetrics; -import com.skedgo.tripkit.configuration.Server; +import com.skedgo.tripkit.configuration.ServerManager; import com.skedgo.tripkit.routing.ModeInfo; import com.skedgo.tripkit.common.model.TransportMode; public final class TransportModeUtils { - public static final String ICON_URL_TEMPLATE = Server.ApiTripGo.getValue() + "modeicons/android/%s/ic_transport_%s.png"; + public static final String ICON_URL_TEMPLATE = ServerManager.INSTANCE.getConfiguration().getApiTripGoUrl() + "modeicons/android/%s/ic_transport_%s.png"; private TransportModeUtils() { } diff --git a/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/routing/Geofence.kt b/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/routing/Geofence.kt index 420debe6..d5f60863 100644 --- a/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/routing/Geofence.kt +++ b/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/routing/Geofence.kt @@ -12,9 +12,9 @@ data class Geofence( ) { var timeline: Long = -1L - fun computeAndSetTimeline(tripEndDateTimeInMillis: Long): Long { + fun computeAndSetTimeline(tripEndDateTimeInMillis: Long) { val currentTimeInMillis = System.currentTimeMillis() - return tripEndDateTimeInMillis - currentTimeInMillis + timeline = tripEndDateTimeInMillis - currentTimeInMillis } } diff --git a/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/routing/RemoteIcon.kt b/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/routing/RemoteIcon.kt new file mode 100644 index 00000000..611bc548 --- /dev/null +++ b/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/routing/RemoteIcon.kt @@ -0,0 +1,15 @@ +package com.skedgo.tripkit.routing + +import androidx.annotation.StringDef + +@Retention(AnnotationRetention.RUNTIME) +@StringDef( + RemoteIcon.NEURON, + RemoteIcon.LIME, +) +annotation class RemoteIcon { + companion object { + const val NEURON = "neuron" + const val LIME = "lime" + } +} \ No newline at end of file diff --git a/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/routing/RoadTag.kt b/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/routing/RoadTag.kt index ea3fca92..79579784 100644 --- a/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/routing/RoadTag.kt +++ b/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/routing/RoadTag.kt @@ -36,6 +36,8 @@ enum class RoadTag { */ @SerializedName("UNKNOWN") UNKNOWN, + @SerializedName("LIT-ROUTE") + LIT_ROUTE } fun String.parseRoadTag(): RoadTag = @@ -52,7 +54,8 @@ fun RoadTag.getRoadSafetyColorPair(): Pair { RoadTag.CYCLE_NETWORK, RoadTag.BICYCLE_DESIGNATED, RoadTag.BICYCLE_BOULEVARD, - RoadTag.CCTV_CAMERA -> android.R.color.holo_blue_dark to true + RoadTag.CCTV_CAMERA, + RoadTag.LIT_ROUTE -> android.R.color.holo_blue_dark to true RoadTag.SIDE_WALK, RoadTag.SIDE_ROAD, @@ -72,7 +75,8 @@ fun RoadTag.getRoadSafetyColor(): Int { RoadTag.CYCLE_NETWORK, RoadTag.BICYCLE_DESIGNATED, RoadTag.BICYCLE_BOULEVARD, - RoadTag.CCTV_CAMERA -> Color.parseColor("#0000b3") + RoadTag.CCTV_CAMERA, + RoadTag.LIT_ROUTE -> Color.parseColor("#0000b3") RoadTag.SIDE_WALK, RoadTag.SIDE_ROAD, @@ -92,7 +96,8 @@ fun RoadTag.getRoadSafetyIndex(): Int { RoadTag.CYCLE_NETWORK, RoadTag.BICYCLE_DESIGNATED, RoadTag.BICYCLE_BOULEVARD, - RoadTag.CCTV_CAMERA -> 1 + RoadTag.CCTV_CAMERA, + RoadTag.LIT_ROUTE -> 1 RoadTag.SIDE_WALK, RoadTag.SIDE_ROAD, @@ -113,6 +118,7 @@ fun RoadTag.getTextColor(): Int { RoadTag.BICYCLE_DESIGNATED, RoadTag.BICYCLE_BOULEVARD, RoadTag.CCTV_CAMERA, + RoadTag.LIT_ROUTE, RoadTag.SIDE_ROAD, /*RoadTag.SEGREGATED,*/ RoadTag.UNKNOWN, @@ -136,6 +142,7 @@ fun RoadTag.getRoadTagLabel(): String { /*RoadTag.SERVICE_ROAD -> "Service Road"*/ //Ignored RoadTag.STREET_LIGHT -> "Street Light" RoadTag.CCTV_CAMERA -> "CCTV Camera" + RoadTag.LIT_ROUTE -> "Lit Route" /*RoadTag.SEGREGATED -> "Segregated"*/ //Ignored else -> "Other" } diff --git a/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/routing/Ticket.kt b/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/routing/Ticket.kt index d6c07e67..9e0c8158 100644 --- a/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/routing/Ticket.kt +++ b/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/routing/Ticket.kt @@ -1,5 +1,6 @@ package com.skedgo.tripkit.routing +// TODO double check if it's something we can remove and re-use the Ticket model from QuickBooking.kt data class Ticket( val cost: Double = 0.0, val exchange: Double = 0.0, diff --git a/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/routing/Trip.java b/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/routing/Trip.java index bb190fad..81f75293 100644 --- a/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/routing/Trip.java +++ b/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/routing/Trip.java @@ -50,6 +50,8 @@ public class Trip implements ITimeRange { private float caloriesCost; @SerializedName("moneyCost") private float mMoneyCost; + @SerializedName("moneyUSDCost") + private float moneyUsdCost; @SerializedName("carbonCost") private float mCarbonCost; @SerializedName("hassleCost") @@ -81,6 +83,8 @@ public class Trip implements ITimeRange { @Nullable @SerializedName("availability") private String availability; + @Nullable + private String availabilityInfo; @SerializedName("mainSegmentHashCode") private long mainSegmentHashCode; @SerializedName("hideExactTimes") @@ -100,6 +104,7 @@ public Trip() { mStartTimeInSecs = 0; mEndTimeInSecs = 0; mMoneyCost = UNKNOWN_COST; + moneyUsdCost = UNKNOWN_COST; mCarbonCost = 0; mHassleCost = 0; } @@ -208,6 +213,14 @@ public void setMoneyCost(final float moneyCost) { this.mMoneyCost = moneyCost; } + public float getMoneyUsdCost() { + return moneyUsdCost; + } + + public void setMoneyUsdCost(float moneyUsdCost) { + this.moneyUsdCost = moneyUsdCost; + } + public float getCarbonCost() { return mCarbonCost; } @@ -337,6 +350,11 @@ public Availability getAvailability() { return com.skedgo.tripkit.routing.AvailabilityKt.toAvailability(availability); } + @Nullable + public String getAvailabilityString() { + return availability; + } + /** * Mutability is subject to deletion after we finish migrating to an immutable {@link Trip}. */ @@ -345,6 +363,10 @@ public void setAvailability(@NonNull Availability availability) { this.availability = availability.getValue(); } + public void setAvailability(String availability) { + this.availability = availability; + } + public boolean queryIsLeaveAfter() { return queryIsLeaveAfter; } @@ -508,6 +530,22 @@ public String getDisplayCost(String localizedFreeText) { } } + @Nullable + public String getDisplayCostUsd() { + if (moneyUsdCost == 0) { + return null; + } else if (moneyUsdCost == Trip.UNKNOWN_COST) { + return null; + } else { + // Use locale. + NumberFormat numberFormat = NumberFormat.getNumberInstance(Locale.getDefault()); + numberFormat.setRoundingMode(RoundingMode.CEILING); + numberFormat.setMaximumFractionDigits(0); + String value = numberFormat.format(moneyUsdCost); + return (currencySymbol != null ? currencySymbol : "$") + value; + } + } + @Nullable public String getDisplayCarbonCost() { if (mCarbonCost > 0) { @@ -595,6 +633,15 @@ public void setQueryTime(long queryTime) { this.queryTime = queryTime; } + @Nullable + public String getAvailabilityInfo() { + return availabilityInfo; + } + + public void setAvailabilityInfo(@Nullable String availabilityInfo) { + this.availabilityInfo = availabilityInfo; + } + public String getTripUuid() { if (saveURL != null) { Uri uri = Uri.parse(saveURL); diff --git a/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/routing/TripSegment.java b/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/routing/TripSegment.java index a9184298..6ea042d5 100644 --- a/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/routing/TripSegment.java +++ b/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/routing/TripSegment.java @@ -113,6 +113,8 @@ public class TripSegment implements IRealTimeElement, ITimeRange { private RealTimeVehicle mRealTimeVehicle; @SerializedName("wheelchairAccessible") private boolean wheelchairAccessible; + @SerializedName("bicycleAccessible") + private boolean bicycleAccessible; @SerializedName("localCost") @Nullable private LocalCost localCost; @@ -516,6 +518,14 @@ public void setWheelchairAccessible(boolean wheelchairAccessible) { this.wheelchairAccessible = wheelchairAccessible; } + public boolean getBicycleAccessible() { + return bicycleAccessible; + } + + public void setBicycleAccessible(boolean bicycleAccessible) { + this.bicycleAccessible = bicycleAccessible; + } + public int getWheelchairFriendliness() { return Math.round(metresSafe / (float) metres * 100); } diff --git a/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/routing/TripSegmentExtensions.kt b/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/routing/TripSegmentExtensions.kt index 48cb5dfa..0e2f4f76 100644 --- a/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/routing/TripSegmentExtensions.kt +++ b/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/routing/TripSegmentExtensions.kt @@ -41,3 +41,6 @@ val TripSegment.noActionAlerts val TripSegment.actionAlert get() = alerts?.firstOrNull { it.alertAction() != null } + +val TripSegment?.bookingHasConfirmation + get() = this?.booking?.confirmation?.status() != null \ No newline at end of file diff --git a/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/routing/VehicleMode.java b/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/routing/VehicleMode.java index 26640b8d..6b78593b 100644 --- a/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/routing/VehicleMode.java +++ b/CommonCoreLegacy/src/main/java/com/skedgo/tripkit/routing/VehicleMode.java @@ -1,7 +1,6 @@ package com.skedgo.tripkit.routing; import android.content.Context; -import android.content.res.Resources; import android.graphics.drawable.Drawable; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; diff --git a/TripKitAndroid/build.gradle b/TripKitAndroid/build.gradle index e501081f..bd407047 100644 --- a/TripKitAndroid/build.gradle +++ b/TripKitAndroid/build.gradle @@ -19,6 +19,12 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } + buildTypes { + staging { + debuggable true + } + } + packagingOptions { // To avoid conflicts with ASL. // We don't utilize ServiceLoader, so this is unneeded. @@ -113,19 +119,36 @@ dependencies { testImplementation libs.mockk debugApi project(':CommonCoreLegacy') + stagingApi project(':CommonCoreLegacy') releaseApi project(':CommonCoreLegacy') + debugApi project(':TripKitDomain') + stagingApi project(':TripKitDomain') releaseApi project(':TripKitDomain') + debugApi project(':TripKitDomainLegacy') + stagingApi project(':TripKitDomainLegacy') releaseApi project(':TripKitDomainLegacy') + debugApi project(':TripKitData') + stagingApi project(':TripKitData') releaseApi project(':TripKitData') + debugApi project(':sqliteutils') + stagingApi project(':sqliteutils') releaseApi project(':sqliteutils') //api project(':SnapshotTaker') Uncomment when using in TripGo-v5 implementation libs.javaxAnnotation compileOnly 'com.github.pengrad:jdk9-deps:1.0' + + testImplementation libs.mockk + testImplementation libs.junit + testImplementation libs.androidxJUnitExtTesting + testImplementation libs.archCoreTesting + testImplementation libs.kluent + testImplementation libs.androidxTesting + testImplementation libs.assertjCore } // build a jar with source files diff --git a/TripKitAndroid/src/main/java/com/skedgo/TripKit.java b/TripKitAndroid/src/main/java/com/skedgo/TripKit.java index 0bad826e..4cb9a9e7 100644 --- a/TripKitAndroid/src/main/java/com/skedgo/TripKit.java +++ b/TripKitAndroid/src/main/java/com/skedgo/TripKit.java @@ -10,6 +10,7 @@ import com.skedgo.tripkit.HttpClientModule; import com.skedgo.tripkit.LocationInfoService; import com.skedgo.tripkit.MainModule; +import com.skedgo.tripkit.data.TripKitPreferencesModule; import com.skedgo.tripkit.data.regions.RegionService; import com.skedgo.tripkit.TripUpdater; import com.skedgo.tripkit.bookingproviders.BookingResolver; @@ -45,7 +46,8 @@ HttpClientModule.class, A2bRoutingDataModule.class, TspModule.class, - MainModule.class + MainModule.class, + TripKitPreferencesModule.class }) public abstract class TripKit { private static TripKit instance; diff --git a/TripKitAndroid/src/main/java/com/skedgo/geocoding/GCSkedgoResult.java b/TripKitAndroid/src/main/java/com/skedgo/geocoding/GCSkedgoResult.java index 7442ad6c..c5aa455a 100644 --- a/TripKitAndroid/src/main/java/com/skedgo/geocoding/GCSkedgoResult.java +++ b/TripKitAndroid/src/main/java/com/skedgo/geocoding/GCSkedgoResult.java @@ -1,9 +1,13 @@ package com.skedgo.geocoding; +import androidx.annotation.Nullable; + import com.skedgo.geocoding.agregator.GCSkedGoResultInterface; import org.jetbrains.annotations.NotNull; +import java.util.List; + public class GCSkedgoResult extends GCResult implements GCSkedGoResultInterface { @NotNull @@ -12,10 +16,21 @@ public class GCSkedgoResult extends GCResult implements GCSkedGoResultInterface // popularity json field from skedgo's json private int popularity; - public GCSkedgoResult(String name, double lat, double lng, @NotNull String resultClass, Integer popularity ){ + @Nullable + private List modeIdentifiers; + + public GCSkedgoResult( + String name, + double lat, + double lng, + @NotNull String resultClass, + Integer popularity, + @Nullable List modeIdentifiers + ){ super(name, lat, lng); this.popularity = popularity; this.resultClass = resultClass; + this.modeIdentifiers = modeIdentifiers; } @NotNull @@ -40,4 +55,10 @@ public void setPopularity(int popularity) { public boolean isStopLocation(){ return this.resultClass.equalsIgnoreCase("StopLocation"); } + + @Nullable + @Override + public List getModeIdentifiers() { + return modeIdentifiers; + } } diff --git a/TripKitAndroid/src/main/java/com/skedgo/geocoding/agregator/GCSkedGoResultInterface.java b/TripKitAndroid/src/main/java/com/skedgo/geocoding/agregator/GCSkedGoResultInterface.java index c309d382..c54f43f4 100644 --- a/TripKitAndroid/src/main/java/com/skedgo/geocoding/agregator/GCSkedGoResultInterface.java +++ b/TripKitAndroid/src/main/java/com/skedgo/geocoding/agregator/GCSkedGoResultInterface.java @@ -1,5 +1,9 @@ package com.skedgo.geocoding.agregator; +import androidx.annotation.Nullable; + +import java.util.List; + public interface GCSkedGoResultInterface extends GCResultInterface { // skedgo result class (class json field) @@ -7,4 +11,6 @@ public interface GCSkedGoResultInterface extends GCResultInterface { // skedgo result popularity (popularity json field) int getPopularity(); + @Nullable + List getModeIdentifiers(); } diff --git a/TripKitAndroid/src/main/java/com/skedgo/geocoding/agregator/MultiSourceGeocodingAggregator.java b/TripKitAndroid/src/main/java/com/skedgo/geocoding/agregator/MultiSourceGeocodingAggregator.java index 5467c2d6..1f376484 100644 --- a/TripKitAndroid/src/main/java/com/skedgo/geocoding/agregator/MultiSourceGeocodingAggregator.java +++ b/TripKitAndroid/src/main/java/com/skedgo/geocoding/agregator/MultiSourceGeocodingAggregator.java @@ -194,7 +194,10 @@ private ScoringResult calculateSkedGoScoring(GCQuery query, GCSkedGoResultInt // int popularityScore = (Math.min(popularity, GOOD_SCORE)) / (GOOD_SCORE / 100) int popularityScore = ((Math.min(popularity, GOOD_SCORE)) / (GOOD_SCORE / 100)) * 2; - popularityScore = GeocodeUtilities.rangedScoreForScore(popularityScore, 30, 80); + popularityScore = (candidate.getModeIdentifiers() != null && !candidate.getModeIdentifiers().isEmpty()) ? + GeocodeUtilities.rangedScoreForScore(popularityScore, 50, 90) : + GeocodeUtilities.rangedScoreForScore(popularityScore, 30, 80); + if (popularity > GOOD_SCORE) { int moreThanGood = popularityScore / GOOD_SCORE; int bonus = GeocodeUtilities.rangedScoreForScore(moreThanGood, 0, 10); @@ -208,7 +211,9 @@ private ScoringResult calculateSkedGoScoring(GCQuery query, GCSkedGoResultInt if (!query.getQueryText().isEmpty()){ int nameScore = GeocodeUtilities.scoreBasedOnNameMatchBetweenSearchTerm(query.getQueryText(), candidate.getName()); - int totalScore = GeocodeUtilities.rangedScoreForScore(nameScore,0,50); + int totalScore = (candidate.getModeIdentifiers() != null && !candidate.getModeIdentifiers().isEmpty()) ? + GeocodeUtilities.rangedScoreForScore(nameScore,50,90): + GeocodeUtilities.rangedScoreForScore(nameScore,0,50); scoringResult.setScore(totalScore); scoringResult.setNameScore(nameScore); return scoringResult; @@ -284,71 +289,4 @@ private ScoringResult calculateRegionsScoring(GCQuery query, GCAppResultInter return scoringResult; } - -// private List> removeDuplicates(List> list) { -// int count = list.size(); -// List> withoutDuplicates = new ArrayList<>(); -// for (int i = 0; i < count; i++) { -// GroupScoringResult groupScoringResult; -// ScoringResult scoringResult; -// if (list.get(i) instanceof ScoringResult) { -// groupScoringResult = new GroupScoringResult(); -// groupScoringResult.addDuplicate((ScoringResult) list.get(i)); -// scoringResult = (ScoringResult) list.get(i); -// } -// else{ -// groupScoringResult = (GroupScoringResult) list.get(i); -// scoringResult = (ScoringResult) groupScoringResult.getDuplicates().get(0); -// } -// -// for (int j = i + 1; j < count; j++) { -// if (scoringResult.equals(list.get(j))) { -// if (list.get(j) instanceof ScoringResult) -// groupScoringResult.addDuplicate((ScoringResult) list.get(j)); -// else -// groupScoringResult.addDuplicates(list.get(j).getDuplicates()); -// list.remove(j--); -// count--; -// } -// } -// withoutDuplicates.add(i, groupScoringResult); -// } -// return GeocodeUtilities.sortByScore(withoutDuplicates); -// } - - -// private List> removeDuplicates(List> list) { -// -// Set> treeSet = new TreeSet<>(new Comparator>() { -// @Override -// public int compare(MGAResultInterface o1, MGAResultInterface o2) { -// ScoringResult scoringResult = (ScoringResult) o1.getClassRepresentative(); -// if (scoringResult.equals(o2)){ -// GroupScoringResult o1Group = (GroupScoringResult) o1; -// if (!o1.equals(o2)) { -// GroupScoringResult o2Group = (GroupScoringResult) o2; -// List> o1Duplicates = new ArrayList<>(o1Group.getDuplicates()); -// o1Group.addDuplicates(new ArrayList<>(o2Group.getDuplicates())); -// o2Group.addDuplicates(o1Duplicates); -// -// } -// } -// return scoringResult.equals(o2) ? 0 : 1; -// } -// }); -// -// for (MGAResultInterface o : list){ -// GroupScoringResult groupScoringResult; -// if (o instanceof ScoringResult) { -// groupScoringResult = new GroupScoringResult(); -// groupScoringResult.addDuplicate((ScoringResult) o); -// } -// else{ -// groupScoringResult = (GroupScoringResult) o; -// } -// treeSet.add(groupScoringResult); -// } -// -// return new ArrayList>(treeSet); -// } } diff --git a/TripKitAndroid/src/main/java/com/skedgo/tripkit/AddCustomHeaders.kt b/TripKitAndroid/src/main/java/com/skedgo/tripkit/AddCustomHeaders.kt index e3253efa..4b10dda6 100644 --- a/TripKitAndroid/src/main/java/com/skedgo/tripkit/AddCustomHeaders.kt +++ b/TripKitAndroid/src/main/java/com/skedgo/tripkit/AddCustomHeaders.kt @@ -45,8 +45,10 @@ class AddCustomHeaders constructor( is Key.RegionEligibility -> builder.addHeader(regionEligibilityHeader, key.value) } - if (getUserToken?.call() != null) { - builder.addHeader(userTokenHeader, getUserToken.call()) + getUserToken?.call()?.let { token -> + if (token.isNotEmpty()) { + builder.addHeader(userTokenHeader, token) + } } val hasTripSelection = preferences?.getBoolean("tripSelection", false) ?: false diff --git a/TripKitAndroid/src/main/java/com/skedgo/tripkit/Configs.java b/TripKitAndroid/src/main/java/com/skedgo/tripkit/Configs.java index 2c226e02..dd5deefe 100644 --- a/TripKitAndroid/src/main/java/com/skedgo/tripkit/Configs.java +++ b/TripKitAndroid/src/main/java/com/skedgo/tripkit/Configs.java @@ -71,4 +71,8 @@ public interface Configs { @Value.Default public default boolean hideFavorites() { return false;} + @Value.Default public default boolean showGeofences() { return false;} + + @Value.Default public default boolean hasInductionCards() { return false;} + } diff --git a/TripKitAndroid/src/main/java/com/skedgo/tripkit/HttpClientModule.kt b/TripKitAndroid/src/main/java/com/skedgo/tripkit/HttpClientModule.kt index 1fe74eb4..b910a85c 100644 --- a/TripKitAndroid/src/main/java/com/skedgo/tripkit/HttpClientModule.kt +++ b/TripKitAndroid/src/main/java/com/skedgo/tripkit/HttpClientModule.kt @@ -2,13 +2,11 @@ package com.skedgo.tripkit import android.content.Context import android.content.SharedPreferences -import android.webkit.URLUtil import com.google.gson.Gson import com.haroldadmin.cnradapter.NetworkResponseAdapterFactory import com.skedgo.tripkit.configuration.AppVersionNameRepository import com.skedgo.tripkit.configuration.GetAppVersion -import com.skedgo.tripkit.configuration.Server -import com.skedgo.tripkit.data.HttpClientCustomDataStore +import com.skedgo.tripkit.configuration.ServerManager import com.skedgo.tripkit.data.regions.RegionService import com.skedgo.tripkit.regionrouting.RegionRoutingRepository import com.skedgo.tripkit.regionrouting.RegionRoutingApi @@ -107,7 +105,7 @@ open class HttpClientModule( @Provides open fun retrofitBuilder(gson: Gson): Retrofit.Builder { return Retrofit.Builder() - .baseUrl(Server.ApiTripGo.value) + .baseUrl(ServerManager.configuration.apiTripGoUrl) .addCallAdapterFactory(NetworkResponseAdapterFactory()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addConverterFactory(GsonConverterFactory.create(gson)) diff --git a/TripKitAndroid/src/main/java/com/skedgo/tripkit/LocationUtils.kt b/TripKitAndroid/src/main/java/com/skedgo/tripkit/LocationUtils.kt new file mode 100644 index 00000000..cbbc139e --- /dev/null +++ b/TripKitAndroid/src/main/java/com/skedgo/tripkit/LocationUtils.kt @@ -0,0 +1,10 @@ +package com.skedgo.tripkit + +import android.content.Context +import android.location.LocationManager + +fun Context.checkIfLocationProviderIsEnabled(): Boolean { + val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager + return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || + locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) +} \ No newline at end of file diff --git a/TripKitAndroid/src/main/java/com/skedgo/tripkit/MainModule.java b/TripKitAndroid/src/main/java/com/skedgo/tripkit/MainModule.java index 43558d0e..60d5ba43 100644 --- a/TripKitAndroid/src/main/java/com/skedgo/tripkit/MainModule.java +++ b/TripKitAndroid/src/main/java/com/skedgo/tripkit/MainModule.java @@ -16,6 +16,7 @@ import com.skedgo.tripkit.common.util.LowercaseEnumTypeAdapterFactory; import com.skedgo.tripkit.bookingproviders.BookingResolver; import com.skedgo.tripkit.bookingproviders.BookingResolverImpl; +import com.skedgo.tripkit.configuration.ServerManager; import com.skedgo.tripkit.data.regions.RegionService; import com.skedgo.tripkit.data.tsp.GsonAdaptersRegionInfo; import com.skedgo.tripkit.tsp.GsonAdaptersRegionInfoBody; @@ -43,7 +44,6 @@ import com.skedgo.tripkit.a2brouting.RouteService; import com.skedgo.TripKit; import com.skedgo.tripkit.configuration.AppVersionNameRepository; -import com.skedgo.tripkit.configuration.Server; @Module public class MainModule { @@ -63,7 +63,7 @@ Configs configs() { @Provides RegionsApi getRegionsApi(OkHttpClient httpClient) { return new Retrofit.Builder() - .baseUrl(Server.ApiTripGo.getValue()) + .baseUrl(ServerManager.INSTANCE.getConfiguration().getApiTripGoUrl()) .addConverterFactory(GsonConverterFactory.create(Gsons.createForRegion())) .addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io())) .client(httpClient) @@ -167,7 +167,7 @@ BookingResolver getBookingResolver() { LocationInfoApi getLocationInfoApi(Gson gson, OkHttpClient httpClient) { return new Retrofit.Builder() /* This base url is ignored as the api relies on @Url. */ - .baseUrl(Server.ApiTripGo.getValue()) + .baseUrl(ServerManager.INSTANCE.getConfiguration().getApiTripGoUrl()) .addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io())) .addConverterFactory(GsonConverterFactory.create(gson)) .client(httpClient) diff --git a/TripKitAndroid/src/main/java/com/skedgo/tripkit/ModeCombinationStrategy.java b/TripKitAndroid/src/main/java/com/skedgo/tripkit/ModeCombinationStrategy.java index 8c4ec917..8df10176 100644 --- a/TripKitAndroid/src/main/java/com/skedgo/tripkit/ModeCombinationStrategy.java +++ b/TripKitAndroid/src/main/java/com/skedgo/tripkit/ModeCombinationStrategy.java @@ -15,7 +15,9 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.regex.Pattern; +// TODO convert to kotlin and add coroutine version. final class ModeCombinationStrategy implements BiFunction, List, List>> { @Override @@ -33,7 +35,13 @@ public List> apply( final Set newSet = new HashSet<>(); newSet.add(modeId); - final TransportMode foundMode = modeMap.get(modeId); + TransportMode foundMode; + // For modes from modeIdentifiers, e.g. `pt_ltd_SCHOOLBUS_2029`, `pt_ltd_SCHOOLBUS_2031`, etc. + // to remove numeric suffix to get base modeId for checking if exist on modeMap + String baseModeId = Pattern.compile("_\\d+$").matcher(modeId).replaceAll(""); + + // Check if the baseModeId is present in the modeMap + foundMode = modeMap.get(baseModeId); if (foundMode != null) { boolean shouldMerge = false; final List implies = foundMode.getImplies(); @@ -71,15 +79,6 @@ public List> apply( seenModeIds.addAll(newSet); } - // Create a union set which combines all given modeIds. - // There is one exception: wa_wal is implied in multi-modal, so we don't need to specify it. We also don't want - // to duplicate a query if there aren't enough modes to justify multi-modal, so do some final checking. - if (modeIds.size() == 2 && modeIds.contains(TransportMode.ID_WHEEL_CHAIR) && modeIds.contains(TransportMode.ID_PS_DRT)) { - - } else { - - } - HashSet multiModal = new HashSet<>(modeIds); multiModal.remove(TransportMode.ID_WALK); if (multiModal.size() > 1) { diff --git a/TripKitAndroid/src/main/java/com/skedgo/tripkit/QueryGeneratorImpl.kt b/TripKitAndroid/src/main/java/com/skedgo/tripkit/QueryGeneratorImpl.kt index bb296071..d22d03db 100644 --- a/TripKitAndroid/src/main/java/com/skedgo/tripkit/QueryGeneratorImpl.kt +++ b/TripKitAndroid/src/main/java/com/skedgo/tripkit/QueryGeneratorImpl.kt @@ -1,5 +1,6 @@ package com.skedgo.tripkit +import com.skedgo.tripkit.common.model.LOCATION_CLASS_SCHOOL import com.skedgo.tripkit.common.model.Query import com.skedgo.tripkit.common.model.TransportMode import com.skedgo.tripkit.data.regions.RegionService @@ -40,7 +41,18 @@ internal class QueryGeneratorImpl(private val regionService: RegionService) : Qu allTransportModes.add(TransportMode.ID_WHEEL_CHAIR) } - val filteredTransportModes = allTransportModes.filter { transportModeFilter.useTransportMode(it) } + val filteredTransportModes = allTransportModes + .filter { transportModeFilter.useTransportMode(it) }.toMutableList() + + if(departure.locationClass == LOCATION_CLASS_SCHOOL || + arrival.locationClass == LOCATION_CLASS_SCHOOL) { + val modeIdentifiers = (departure.modeIdentifiers.orEmpty() + arrival.modeIdentifiers.orEmpty()) + .toSet().toList() + + filteredTransportModes.remove(TransportMode.ID_SCHOOL_BUS) + filteredTransportModes.addAll(modeIdentifiers) + } + query.transportModeIds = filteredTransportModes regionService.getTransportModesAsync() } diff --git a/TripKitAndroid/src/main/java/com/skedgo/tripkit/TripKitConstants.kt b/TripKitAndroid/src/main/java/com/skedgo/tripkit/TripKitConstants.kt index b8bccfec..0aca975d 100644 --- a/TripKitAndroid/src/main/java/com/skedgo/tripkit/TripKitConstants.kt +++ b/TripKitAndroid/src/main/java/com/skedgo/tripkit/TripKitConstants.kt @@ -6,6 +6,9 @@ class TripKitConstants { const val PREF_NAME_TRIP_KIT = "TripKit" const val PREF_KEY_CLIENT_ID = "pref_key_client_id" const val PREF_NAME_APP = "app_preferences" + const val PREF_KEY_TRIP_KIT_LAT_LNG = "pref_key_trip_kit_lat_lng" + const val PREF_KEY_POLYGON = "pref_key_polygon" + const val PREF_KEY_CLIENT_FEATURES = "pref_key_client_features" } } \ No newline at end of file diff --git a/TripKitAndroid/src/main/java/com/skedgo/tripkit/a2brouting/A2bRoutingDataModule.kt b/TripKitAndroid/src/main/java/com/skedgo/tripkit/a2brouting/A2bRoutingDataModule.kt index 4969c836..1e8794b6 100644 --- a/TripKitAndroid/src/main/java/com/skedgo/tripkit/a2brouting/A2bRoutingDataModule.kt +++ b/TripKitAndroid/src/main/java/com/skedgo/tripkit/a2brouting/A2bRoutingDataModule.kt @@ -8,7 +8,7 @@ import retrofit2.Retrofit import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory import retrofit2.converter.gson.GsonConverterFactory import io.reactivex.schedulers.Schedulers -import com.skedgo.tripkit.configuration.Server +import com.skedgo.tripkit.configuration.ServerManager import java.util.concurrent.TimeUnit @Module @@ -22,7 +22,7 @@ class A2bRoutingDataModule { return Retrofit.Builder() .addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io())) .addConverterFactory(GsonConverterFactory.create(gson)) - .baseUrl(Server.ApiTripGo.value) + .baseUrl(ServerManager.configuration.apiTripGoUrl) .client(client) .build() .create(A2bRoutingApi::class.java) diff --git a/TripKitAndroid/src/main/java/com/skedgo/tripkit/a2brouting/GetNonTravelledLineForTrip.kt b/TripKitAndroid/src/main/java/com/skedgo/tripkit/a2brouting/GetNonTravelledLineForTrip.kt index 9bd94038..a1289089 100644 --- a/TripKitAndroid/src/main/java/com/skedgo/tripkit/a2brouting/GetNonTravelledLineForTrip.kt +++ b/TripKitAndroid/src/main/java/com/skedgo/tripkit/a2brouting/GetNonTravelledLineForTrip.kt @@ -1,49 +1,61 @@ package com.skedgo.tripkit.a2brouting import android.graphics.Color -import com.skedgo.tripkit.common.util.PolyUtil +import com.google.maps.android.PolyUtil +import com.google.maps.android.ktx.utils.simplify +import com.skedgo.tripkit.common.util.TripKitLatLng import io.reactivex.Observable import com.skedgo.tripkit.routing.TripSegment import javax.inject.Inject class GetNonTravelledLineForTrip @Inject constructor() { - fun execute(segments: List): Observable> { - return Observable.fromCallable { createNonTravelledLinesToDraw(segments) } - .flatMap { Observable.fromIterable(it) } - } + companion object { + const val LAT_LNG_SIMPLIFY_TOLERANCE = 5.0 + } - private fun createNonTravelledLinesToDraw(segments: List?): List> { - return segments.orEmpty() - .filterNot { - it.from == null || it.to == null - } - .map { - val color = if (it.serviceColor == null) - Color.BLACK - else - it.serviceColor!!.color + fun execute(segments: List): Observable> { + return Observable.fromCallable { createNonTravelledLinesToDraw(segments) } + .flatMap { Observable.fromIterable(it) } + } - val shapes = it.shapes ?: emptyList() - shapes to color - } - .flatMap { (shapes, defaultColor) -> - shapes.filterNot { it.isTravelled } - .filter { - it.encodedWaypoints.isNotEmpty() - } - .map { - val color = if (it.serviceColor == null || it.serviceColor.color == Color.BLACK) - defaultColor + private fun createNonTravelledLinesToDraw(segments: List?): List> { + return segments.orEmpty() + .filterNot { + it.from == null || it.to == null + } + .map { + val color = if (it.serviceColor == null) + Color.BLACK else - it.serviceColor.color - PolyUtil.decode(it.encodedWaypoints) - .zipWithNext() - .map { (start, end) -> - com.skedgo.tripkit.LineSegment(start, end, color, "") + it.serviceColor?.color ?: Color.BLACK + + val shapes = it.shapes ?: emptyList() + shapes to color + } + .flatMap { (shapes, defaultColor) -> + shapes.filterNot { it.isTravelled } + .filter { + it.encodedWaypoints.isNotEmpty() + } + .map { + val color = + if (it.serviceColor == null || it.serviceColor.color == Color.BLACK) + defaultColor + else + it.serviceColor.color + PolyUtil.decode(it.encodedWaypoints).simplify(LAT_LNG_SIMPLIFY_TOLERANCE) + .zipWithNext() + .map { (start, end) -> + com.skedgo.tripkit.LineSegment( + TripKitLatLng(start.latitude, start.longitude), + TripKitLatLng(end.latitude, end.longitude), + color, + "" + ) + } } - } - } - } + } + } } \ No newline at end of file diff --git a/TripKitAndroid/src/main/java/com/skedgo/tripkit/a2brouting/GetTravelledLineForTrip.kt b/TripKitAndroid/src/main/java/com/skedgo/tripkit/a2brouting/GetTravelledLineForTrip.kt index b5e55614..2455b2c5 100644 --- a/TripKitAndroid/src/main/java/com/skedgo/tripkit/a2brouting/GetTravelledLineForTrip.kt +++ b/TripKitAndroid/src/main/java/com/skedgo/tripkit/a2brouting/GetTravelledLineForTrip.kt @@ -2,9 +2,10 @@ package com.skedgo.tripkit.a2brouting import android.graphics.Color import androidx.annotation.ColorInt +import com.google.maps.android.PolyUtil +import com.google.maps.android.ktx.utils.simplify import com.skedgo.tripkit.common.model.Street import com.skedgo.tripkit.common.model.TransportMode -import com.skedgo.tripkit.common.util.PolyUtil import com.skedgo.tripkit.common.util.TripKitLatLng import com.skedgo.tripkit.routing.RoadTag import io.reactivex.Observable @@ -15,6 +16,10 @@ import javax.inject.Inject class GetTravelledLineForTrip @Inject constructor() { + companion object { + const val LAT_LNG_SIMPLIFY_TOLERANCE = 5.0 + } + fun execute(segments: List?): Observable> { return Observable .fromCallable { @@ -41,11 +46,14 @@ class GetTravelledLineForTrip @Inject constructor() { color else it.serviceColor.color - PolyUtil.decode(it.encodedWaypoints) - .orEmpty().zipWithNext() + val decodedWayPoints = PolyUtil.decode(it.encodedWaypoints) + val simplified = decodedWayPoints.simplify(LAT_LNG_SIMPLIFY_TOLERANCE) + simplified.zipWithNext() .map { (start, end) -> com.skedgo.tripkit.LineSegment( - start, end, color, + TripKitLatLng(start.latitude, start.longitude), + TripKitLatLng(end.latitude, end.longitude), + color, com.skedgo.tripkit.LineSegment.Tag.SHAPE.toString() ) } @@ -53,15 +61,22 @@ class GetTravelledLineForTrip @Inject constructor() { val lineSegmentsFromStreets = segment.streets.orEmpty() .filter { it.encodedWaypoints() != null } .flatMap { street -> - PolyUtil.decode(street.encodedWaypoints()) + PolyUtil.decode(street.encodedWaypoints()).simplify( + LAT_LNG_SIMPLIFY_TOLERANCE + ) .zipWithNext() .map { (start, end) -> - var lineColor = (getColorForWheelchairAndBicycle(street, modeId) ?: color) - if(!street.roadTags().isNullOrEmpty()) { - lineColor = Color.BLUE + var lineColor = + (getColorForWheelchairAndBicycle(street, modeId) ?: color) + street.roadTags()?.let { + it.firstOrNull()?.let { + lineColor = it.getRoadTagColor() + } } com.skedgo.tripkit.LineSegment( - start, end, lineColor, + TripKitLatLng(start.latitude, start.longitude), + TripKitLatLng(end.latitude, end.longitude), + lineColor, com.skedgo.tripkit.LineSegment.Tag.STREET.toString() ) } diff --git a/TripKitAndroid/src/main/java/com/skedgo/tripkit/analytics/AnalyticsDataModule.kt b/TripKitAndroid/src/main/java/com/skedgo/tripkit/analytics/AnalyticsDataModule.kt index 15043fee..fb139462 100644 --- a/TripKitAndroid/src/main/java/com/skedgo/tripkit/analytics/AnalyticsDataModule.kt +++ b/TripKitAndroid/src/main/java/com/skedgo/tripkit/analytics/AnalyticsDataModule.kt @@ -9,7 +9,7 @@ import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import io.reactivex.schedulers.Schedulers import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory -import com.skedgo.tripkit.configuration.Server +import com.skedgo.tripkit.configuration.ServerManager /** * @suppress @@ -27,7 +27,7 @@ class AnalyticsDataModule { private fun reportingApi(gson: Gson, httpClient: OkHttpClient): MarkTripAsPlannedApi = Retrofit.Builder() /* This base url is ignored as the api relies on @Url. */ - .baseUrl(Server.ApiTripGo.value) + .baseUrl(ServerManager.configuration.apiTripGoUrl) .addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io())) .addConverterFactory(GsonConverterFactory.create(gson)) .client(httpClient) diff --git a/TripKitAndroid/src/main/java/com/skedgo/tripkit/data/BaseSharedPreference.kt b/TripKitAndroid/src/main/java/com/skedgo/tripkit/data/BaseSharedPreference.kt new file mode 100644 index 00000000..1f9ea34d --- /dev/null +++ b/TripKitAndroid/src/main/java/com/skedgo/tripkit/data/BaseSharedPreference.kt @@ -0,0 +1,30 @@ +package com.skedgo.tripkit.data + +import android.content.Context +import android.content.SharedPreferences +import com.google.gson.Gson + +abstract class BaseSharedPreference(context: Context) { + + abstract val prefenceKey: String + + protected val sharedPreferences: SharedPreferences by lazy { + context.getSharedPreferences(prefenceKey, Context.MODE_PRIVATE) + } + + protected val gson: Gson by lazy { Gson() } + + fun saveData(data: Map) { + sharedPreferences.edit().apply { + data.forEach { (key, value) -> + when (value) { + is String -> putString(key, value) + is Boolean -> putBoolean(key, value) + is Int -> putInt(key, value) + is Long -> putLong(key, value) + is Float -> putFloat(key, value) + } + } + }.apply() + } +} \ No newline at end of file diff --git a/TripKitAndroid/src/main/java/com/skedgo/tripkit/data/TripKitPreferencesModule.kt b/TripKitAndroid/src/main/java/com/skedgo/tripkit/data/TripKitPreferencesModule.kt new file mode 100644 index 00000000..05e240da --- /dev/null +++ b/TripKitAndroid/src/main/java/com/skedgo/tripkit/data/TripKitPreferencesModule.kt @@ -0,0 +1,19 @@ +package com.skedgo.tripkit.data + +import android.content.Context +import dagger.Module +import dagger.Provides +import javax.inject.Named + +/** + * To make [TripKitSharedPreference] injectable instead of initializing with applicationContext + * to avoid memory leakage. + */ +@Module +class TripKitPreferencesModule { + @Provides + @Named("TripKitPreference") + internal fun tripGoSharedPreference(context: Context): TripKitSharedPreference { + return TripKitSharedPreference(context) + } +} \ No newline at end of file diff --git a/TripKitAndroid/src/main/java/com/skedgo/tripkit/data/TripKitSharedPreference.kt b/TripKitAndroid/src/main/java/com/skedgo/tripkit/data/TripKitSharedPreference.kt new file mode 100644 index 00000000..1f0eb8dc --- /dev/null +++ b/TripKitAndroid/src/main/java/com/skedgo/tripkit/data/TripKitSharedPreference.kt @@ -0,0 +1,57 @@ +package com.skedgo.tripkit.data + +import android.annotation.SuppressLint +import android.content.Context +import android.content.SharedPreferences +import com.google.gson.Gson +import com.skedgo.tripkit.TripKitConstants.Companion.PREF_KEY_CLIENT_FEATURES +import com.skedgo.tripkit.TripKitConstants.Companion.PREF_KEY_CLIENT_ID +import com.skedgo.tripkit.TripKitConstants.Companion.PREF_KEY_POLYGON +import com.skedgo.tripkit.TripKitConstants.Companion.PREF_KEY_TRIP_KIT_LAT_LNG +import com.skedgo.tripkit.TripKitConstants.Companion.PREF_NAME_TRIP_KIT +import com.skedgo.tripkit.account.data.Polygon +import com.skedgo.tripkit.common.util.TripKitLatLng +import com.skedgo.tripkit.extensions.fromJson +import javax.inject.Inject + +// TODO update codes to use this for accessing TripKit SharedPreference +/** + * Singleton class to centralize handling of TripKit local data using [SharedPreferences] + */ +@SuppressLint("StaticFieldLeak") +class TripKitSharedPreference @Inject constructor(context: Context) : BaseSharedPreference(context) { + + override val prefenceKey: String + get() = PREF_NAME_TRIP_KIT + + fun saveClientId(clientId: String) { + sharedPreferences.edit() + .putString(PREF_KEY_CLIENT_ID, clientId) + .apply() + } + + fun getClientId(): String? = + sharedPreferences.getString(PREF_KEY_CLIENT_ID, null) + + fun saveClientFeatures(features: List) { + sharedPreferences.edit() + .putString(PREF_KEY_CLIENT_FEATURES, Gson().toJson(features)) + .apply() + } + + fun getClientFeatures(): List = + Gson().fromJson(sharedPreferences.getString(PREF_KEY_CLIENT_FEATURES, null) ?: "") + + fun savePolygon(polygon: Polygon?) { + sharedPreferences.edit() + .putString(PREF_KEY_POLYGON, polygon?.let { + gson.toJson(it) + } ?: "") + .apply() + } + + fun getPolygon(): Polygon? { + val dataString = sharedPreferences.getString(PREF_KEY_POLYGON, "") ?: "" + return gson.fromJson(dataString) + } +} \ No newline at end of file diff --git a/TripKitAndroid/src/main/java/com/skedgo/tripkit/routing/GeoLocation.kt b/TripKitAndroid/src/main/java/com/skedgo/tripkit/routing/GeoLocation.kt index d6f1fe62..a02a24ab 100644 --- a/TripKitAndroid/src/main/java/com/skedgo/tripkit/routing/GeoLocation.kt +++ b/TripKitAndroid/src/main/java/com/skedgo/tripkit/routing/GeoLocation.kt @@ -22,37 +22,45 @@ object GeoLocation { } private val currentGeofenceList = mutableListOf() private var geofencePendingIntent: PendingIntent? = null + private val gson: Gson by lazy { + Gson() + } fun init(context: Context) { this.context = context } @SuppressLint("MissingPermission") - fun createGeoFences(trip: Trip, geofences: List) { + fun createGeoFences( + trip: Trip, + geofences: List, + addGeofenceListener: (Boolean) -> Unit + ) { val gsmGeofences = mutableListOf() - gsmGeofences.addAll( - geofences.map { it.toGsmGeofence() } - ) + gsmGeofences.addAll(geofences.map { it.toGsmGeofence() }) currentGeofenceList.clear() currentGeofenceList.addAll(gsmGeofences) - val bundle = Bundle() - bundle.putString(GeofenceBroadcastReceiver.EXTRA_GEOFENCES, Gson().toJson(geofences)) - bundle.putString(GeofenceBroadcastReceiver.EXTRA_TRIP, Gson().toJson(trip)) + val data = mutableMapOf( + GeofenceBroadcastReceiver.EXTRA_GEOFENCES to gson.toJson(geofences), + GeofenceBroadcastReceiver.EXTRA_TRIP to gson.toJson(trip) + ) trip.group?.let { - bundle.putString(GeofenceBroadcastReceiver.EXTRA_TRIP_GROUP_UUID, it.uuid()) + data.put(GeofenceBroadcastReceiver.EXTRA_TRIP_GROUP_UUID, it.uuid()) } - geofencePendingIntent = GeofenceBroadcastReceiver.getPendingIntent(context, bundle) + geofencePendingIntent = GeofenceBroadcastReceiver.getPendingIntent(context, data) createGeoFencingRequest()?.let { request -> if (currentGeofenceList.isNotEmpty()) { removeGeoFences(onRemoveCallback = { geofencingClient.addGeofences(request, geofencePendingIntent).run { addOnSuccessListener { + addGeofenceListener.invoke(true) Log.e(TAG, "Geofence added successfully") } addOnFailureListener { + addGeofenceListener.invoke(false) Log.e(TAG, "Geofence add failed: ${it.message}") currentGeofenceList.clear() if (BuildConfig.DEBUG) @@ -66,7 +74,6 @@ object GeoLocation { private fun createGeoFencingRequest(): GeofencingRequest? { if (currentGeofenceList.isNotEmpty()) { - Log.e("GeoLocation", "creating geofence with ${currentGeofenceList.size} features") return GeofencingRequest.Builder().apply { setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER) addGeofences(currentGeofenceList) diff --git a/TripKitAndroid/src/main/java/com/skedgo/tripkit/routing/GeofenceBroadcastReceiver.kt b/TripKitAndroid/src/main/java/com/skedgo/tripkit/routing/GeofenceBroadcastReceiver.kt index c42d4aae..6dc357e0 100644 --- a/TripKitAndroid/src/main/java/com/skedgo/tripkit/routing/GeofenceBroadcastReceiver.kt +++ b/TripKitAndroid/src/main/java/com/skedgo/tripkit/routing/GeofenceBroadcastReceiver.kt @@ -4,7 +4,6 @@ import android.app.PendingIntent import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import android.os.Bundle import android.util.Log import android.widget.Toast import androidx.core.app.NotificationCompat @@ -28,17 +27,18 @@ class GeofenceBroadcastReceiver : BroadcastReceiver() { const val EXTRA_TRIP_GROUP_UUID = "EXTRA_TRIP_GROUP_UUID" const val NOTIFICATION_VEHICLE_APPROACHING_NOTIFICATION_ID = 9002 - fun getPendingIntent(context: Context, bundle: Bundle? = null): PendingIntent { + fun getPendingIntent(context: Context, dataMap: Map? = null): PendingIntent { val intent = Intent(context, GeofenceBroadcastReceiver::class.java) - bundle?.let { intent.putExtras(bundle) } + dataMap?.forEach { + intent.putExtra(it.key, it.value) + } intent.action = ACTION_GEOFENCE_EVENT return PendingIntent.getBroadcast( context, 0, intent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT ) - } } @@ -48,8 +48,8 @@ class GeofenceBroadcastReceiver : BroadcastReceiver() { Toast.makeText(context, "Geofence update received", Toast.LENGTH_SHORT).show() Log.e("GFBroadcastReceiver", "geofence update receive") - val tripString = intent.getStringExtra(TripAlarmBroadcastReceiver.EXTRA_START_TRIP_EVENT_TRIP) - val tripGroupUuid = intent.getStringExtra(TripAlarmBroadcastReceiver.EXTRA_START_TRIP_EVENT_TRIP_GROUP_UUID) + val tripString = intent.getStringExtra(EXTRA_TRIP) + val tripGroupUuid = intent.getStringExtra(EXTRA_TRIP_GROUP_UUID) if (intent.action == ACTION_GEOFENCE_EVENT) { val geofences: List = diff --git a/TripKitAndroid/src/main/java/com/skedgo/tripkit/routing/GeofenceUtils.kt b/TripKitAndroid/src/main/java/com/skedgo/tripkit/routing/GeofenceUtils.kt index a9506cca..d8a7956b 100644 --- a/TripKitAndroid/src/main/java/com/skedgo/tripkit/routing/GeofenceUtils.kt +++ b/TripKitAndroid/src/main/java/com/skedgo/tripkit/routing/GeofenceUtils.kt @@ -2,6 +2,7 @@ package com.skedgo.tripkit.routing import com.google.android.gms.location.Geofence.GEOFENCE_TRANSITION_ENTER import com.google.android.gms.location.Geofence.GEOFENCE_TRANSITION_EXIT +import com.google.android.gms.location.Geofence.NEVER_EXPIRE fun Geofence.toGsmGeofence(): com.google.android.gms.location.Geofence { @@ -15,13 +16,10 @@ fun Geofence.toGsmGeofence(): com.google.android.gms.location.Geofence { null } - val geofenceBuilder = com.google.android.gms.location.Geofence.Builder() - .setRequestId(this.id) - .setCircularRegion(this.center.lat, this.center.lng, this.radius.toFloat()) - .setExpirationDuration(timeline) - if (transitionType != null) { - geofenceBuilder.setTransitionTypes(transitionType) - } - - return geofenceBuilder.build() + return com.google.android.gms.location.Geofence.Builder() + .setRequestId(this.id) + .setCircularRegion(this.center.lat, this.center.lng, this.radius.toFloat()) + .setExpirationDuration(if (this.timeline <= 0) NEVER_EXPIRE else this.timeline) + .setTransitionTypes(transitionType ?: GEOFENCE_TRANSITION_ENTER) + .build() } \ No newline at end of file diff --git a/TripKitAndroid/src/main/java/com/skedgo/tripkit/tsp/TspModule.java b/TripKitAndroid/src/main/java/com/skedgo/tripkit/tsp/TspModule.java index c5d3c26c..1a01de05 100644 --- a/TripKitAndroid/src/main/java/com/skedgo/tripkit/tsp/TspModule.java +++ b/TripKitAndroid/src/main/java/com/skedgo/tripkit/tsp/TspModule.java @@ -1,6 +1,7 @@ package com.skedgo.tripkit.tsp; import com.google.gson.Gson; +import com.skedgo.tripkit.configuration.ServerManager; import dagger.Module; import dagger.Provides; @@ -8,7 +9,6 @@ import retrofit2.Retrofit; import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; import retrofit2.converter.gson.GsonConverterFactory; -import com.skedgo.tripkit.configuration.Server; @Module public class TspModule { @@ -16,7 +16,7 @@ public class TspModule { Gson gson, okhttp3.OkHttpClient httpClient) { return new Retrofit.Builder() - .baseUrl(Server.ApiTripGo.getValue()) + .baseUrl(ServerManager.INSTANCE.getConfiguration().getApiTripGoUrl()) .client(httpClient) .addConverterFactory(GsonConverterFactory.create(gson)) .addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io())) diff --git a/TripKitAndroid/src/main/java/com/skedgo/tripkit/utils/async/Result.kt b/TripKitAndroid/src/main/java/com/skedgo/tripkit/utils/async/Result.kt new file mode 100644 index 00000000..a228bbc2 --- /dev/null +++ b/TripKitAndroid/src/main/java/com/skedgo/tripkit/utils/async/Result.kt @@ -0,0 +1,13 @@ +package com.skedgo.tripkit.utils.async + +sealed class Result { + data class Success(val data: T) : Result() + data class Error(val exception: String) : Result() + object Loading : Result() + + companion object { + fun success(data: T) = Success(data) + fun error(exception: String) = Error(exception) + fun loading() = Loading + } +} \ No newline at end of file diff --git a/TripKitAndroid/src/test/java/com/skedgo/tripkit/data/TripKitSharedPreferenceTest.kt b/TripKitAndroid/src/test/java/com/skedgo/tripkit/data/TripKitSharedPreferenceTest.kt new file mode 100644 index 00000000..25c1669a --- /dev/null +++ b/TripKitAndroid/src/test/java/com/skedgo/tripkit/data/TripKitSharedPreferenceTest.kt @@ -0,0 +1,104 @@ +package com.skedgo.tripkit.data + +import android.content.Context +import android.content.SharedPreferences +import com.google.gson.Gson +import com.skedgo.tripkit.TripKitConstants.Companion.PREF_KEY_CLIENT_ID +import com.skedgo.tripkit.TripKitConstants.Companion.PREF_KEY_POLYGON +import com.skedgo.tripkit.account.data.Polygon +import com.skedgo.tripkit.extensions.fromJson +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import junit.framework.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.junit.MockitoJUnitRunner + +@RunWith(MockitoJUnitRunner::class) +class TripKitSharedPreferenceTest { + + companion object { + const val TEST_CLIENT_ID = "test_client_id" + const val MOCK_POLYGON_DATE = + """ + { + "polygon": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [ + -82.15809360496644, + 43.95196447506092 + ], + [ + -81.86016289579419, + 41.72524481319121 + ], + [ + -85.9210396251596, + 41.06445418951597 + ], + [ + -82.15809360496644, + 43.95196447506092 + ] + ] + ] + ] + } + } + """ + } + + private lateinit var tripKitSharedPreference: TripKitSharedPreference + private val context = mockk() + private val sharedPreferences = mockk() + private val editor = mockk(relaxed = true) + private val gson = Gson() + + @Before + fun setUp() { + every { context.getSharedPreferences(any(), any()) } returns sharedPreferences + every { sharedPreferences.edit() } returns editor + tripKitSharedPreference = TripKitSharedPreference(context) + } + + @Test + fun `saveClientId - verify SharedPreference editor is called to save clientId `() { + tripKitSharedPreference.saveClientId(TEST_CLIENT_ID) + + verify { editor.putString(PREF_KEY_CLIENT_ID, TEST_CLIENT_ID).apply() } + } + + @Test + fun `getClientId - should return expected client id`() { + every { sharedPreferences.getString(PREF_KEY_CLIENT_ID, null) } returns TEST_CLIENT_ID + + val result = tripKitSharedPreference.getClientId() + + assertEquals(TEST_CLIENT_ID, result) + } + + @Test + fun `savePolygon - verify SharedPreference editor is called to save polygon data in string`() { + val polygon: Polygon = gson.fromJson(MOCK_POLYGON_DATE) + val polygonJson = gson.toJson(polygon) + tripKitSharedPreference.savePolygon(polygon) + + verify { editor.putString(PREF_KEY_POLYGON, polygonJson).apply() } + } + + @Test + fun `getPolygon - should return expected polygon data`() { + val polygon: Polygon = gson.fromJson(MOCK_POLYGON_DATE) + val polygonJson = gson.toJson(polygon) + every { sharedPreferences.getString(PREF_KEY_POLYGON, "") } returns polygonJson + + val result = tripKitSharedPreference.getPolygon() + + assertEquals(polygon, result) + } +} \ No newline at end of file diff --git a/TripKitData/build.gradle b/TripKitData/build.gradle index fa9c80f8..d782e58f 100644 --- a/TripKitData/build.gradle +++ b/TripKitData/build.gradle @@ -23,6 +23,12 @@ android { } } + buildTypes { + staging { + debuggable true + } + } + lintOptions { // Changing to warning because of https://github.com/square/okio/issues/58. warning "InvalidPackage" @@ -59,12 +65,19 @@ dependencies { implementation 'com.github.skedgo:commons-collections:v1.0' debugImplementation project(":CommonCoreLegacy") + stagingImplementation project(":CommonCoreLegacy") releaseImplementation project(":CommonCoreLegacy") + debugImplementation project(":TripKitDomain") + stagingImplementation project(":TripKitDomain") releaseImplementation project(":TripKitDomain") + debugImplementation project(":TripKitDomainLegacy") + stagingImplementation project(":TripKitDomainLegacy") releaseImplementation project(":TripKitDomainLegacy") + debugApi project(":route-persistence") + stagingApi project(":route-persistence") releaseApi project(":route-persistence") implementation libs.dagger @@ -82,6 +95,7 @@ dependencies { annotationProcessor libs.roomCompiler kapt libs.roomCompiler implementation libs.roomRxjava2 + implementation libs.roomKtx kapt libs.daggerCompiler implementation libs.daggerAndroidSupport @@ -115,6 +129,7 @@ dependencies { compileOnly 'com.github.pengrad:jdk9-deps:1.0' debugApi project(':sqliteutils') + stagingApi project(':sqliteutils') releaseApi project(':sqliteutils') } diff --git a/TripKitData/schemas/com.skedgo.tripkit.data.database.TripKitDatabase/5.json b/TripKitData/schemas/com.skedgo.tripkit.data.database.TripKitDatabase/5.json new file mode 100644 index 00000000..6bcb6f40 --- /dev/null +++ b/TripKitData/schemas/com.skedgo.tripkit.data.database.TripKitDatabase/5.json @@ -0,0 +1,1395 @@ +{ + "formatVersion": 1, + "database": { + "version": 5, + "identityHash": "49661b1d3f26137a509189769563f103", + "entities": [ + { + "tableName": "carParks", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`identifier` TEXT NOT NULL, `cellId` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `address` TEXT, `info` TEXT, `modeIdentifier` TEXT, `localIconName` TEXT NOT NULL, `remoteIconName` TEXT, `operator_name` TEXT NOT NULL, `phone` TEXT, `website` TEXT, PRIMARY KEY(`identifier`))", + "fields": [ + { + "fieldPath": "identifier", + "columnName": "identifier", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellId", + "columnName": "cellId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lat", + "columnName": "lat", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "lng", + "columnName": "lng", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "modeIdentifier", + "columnName": "modeIdentifier", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "localIconName", + "columnName": "localIconName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "remoteIconName", + "columnName": "remoteIconName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "operator.name", + "columnName": "operator_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "operator.phone", + "columnName": "phone", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "operator.website", + "columnName": "website", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "identifier" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "onStreetParkings", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`identifier` TEXT NOT NULL, `cellId` TEXT NOT NULL, `name` TEXT NOT NULL, `encodedPolyline` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `address` TEXT, `info` TEXT NOT NULL, `parkingVacancy` TEXT, `availableContent` TEXT NOT NULL, `modeIdentifier` TEXT, `localIconName` TEXT NOT NULL, `remoteIconName` TEXT, `operator_name` TEXT NOT NULL, `phone` TEXT, `website` TEXT, PRIMARY KEY(`identifier`))", + "fields": [ + { + "fieldPath": "identifier", + "columnName": "identifier", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellId", + "columnName": "cellId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "encodedPolyline", + "columnName": "encodedPolyline", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lat", + "columnName": "lat", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "lng", + "columnName": "lng", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parkingVacancy", + "columnName": "parkingVacancy", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "availableContent", + "columnName": "availableContent", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "modeIdentifier", + "columnName": "modeIdentifier", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "localIconName", + "columnName": "localIconName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "remoteIconName", + "columnName": "remoteIconName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "operator.name", + "columnName": "operator_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "operator.phone", + "columnName": "phone", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "operator.website", + "columnName": "website", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "identifier" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "bikepods", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`identifier` TEXT NOT NULL, `cellId` TEXT, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `address` TEXT, `timezone` TEXT, `inService` INTEGER, `totalSpaces` INTEGER, `availableBikes` INTEGER, `lastUpdate` INTEGER, `deepLink` TEXT, `operator_name` TEXT NOT NULL, `operator_website` TEXT, `operator_phone` TEXT, `operator_appURLAndroid` TEXT, `datasource_disclaimer` TEXT, `datasource_name` TEXT, `datasource_phone` TEXT, `datasource_website` TEXT, `modeinfo_identifier` TEXT, `modeinfo_alt` TEXT, `modeinfo_localIcon` TEXT, `modeinfo_remoteIcon` TEXT, `modeinfo_remoteDarkIcon` TEXT, `modeinfo_description` TEXT, `modeinfo_remoteIconIsTemplate` INTEGER, `modeinfo_remoteIconIsBranding` INTEGER, `modeinfo_serviceColor_red` INTEGER, `modeinfo_serviceColor_blue` INTEGER, `modeinfo_serviceColor_green` INTEGER, PRIMARY KEY(`identifier`))", + "fields": [ + { + "fieldPath": "identifier", + "columnName": "identifier", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellId", + "columnName": "cellId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lat", + "columnName": "lat", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "lng", + "columnName": "lng", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "timezone", + "columnName": "timezone", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "bikePod.inService", + "columnName": "inService", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "bikePod.totalSpaces", + "columnName": "totalSpaces", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "bikePod.availableBikes", + "columnName": "availableBikes", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "bikePod.lastUpdate", + "columnName": "lastUpdate", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "bikePod.deepLink", + "columnName": "deepLink", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "bikePod.operator.name", + "columnName": "operator_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "bikePod.operator.website", + "columnName": "operator_website", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "bikePod.operator.phone", + "columnName": "operator_phone", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "bikePod.operator.appInfo.appURLAndroid", + "columnName": "operator_appURLAndroid", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "bikePod.dataSource.disclaimer", + "columnName": "datasource_disclaimer", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "bikePod.dataSource.provider.name", + "columnName": "datasource_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "bikePod.dataSource.provider.phone", + "columnName": "datasource_phone", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "bikePod.dataSource.provider.website", + "columnName": "datasource_website", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "modeInfo.identifier", + "columnName": "modeinfo_identifier", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "modeInfo.alt", + "columnName": "modeinfo_alt", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "modeInfo.localIcon", + "columnName": "modeinfo_localIcon", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "modeInfo.remoteIcon", + "columnName": "modeinfo_remoteIcon", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "modeInfo.remoteDarkIcon", + "columnName": "modeinfo_remoteDarkIcon", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "modeInfo.description", + "columnName": "modeinfo_description", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "modeInfo.remoteIconIsTemplate", + "columnName": "modeinfo_remoteIconIsTemplate", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "modeInfo.remoteIconIsBranding", + "columnName": "modeinfo_remoteIconIsBranding", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "modeInfo.color.red", + "columnName": "modeinfo_serviceColor_red", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "modeInfo.color.blue", + "columnName": "modeinfo_serviceColor_blue", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "modeInfo.color.green", + "columnName": "modeinfo_serviceColor_green", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "identifier" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "freeFloatingLocations", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`identifier` TEXT NOT NULL, `cellId` TEXT, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `name` TEXT, `address` TEXT, `freefloating_vehicle_available` INTEGER NOT NULL, `freefloating_vehicle_batteryLevel` INTEGER NOT NULL, `freefloating_vehicle_lastUpdate` INTEGER NOT NULL, `freefloating_vehicle_qrCode` TEXT, `freefloating_vehicle_name` TEXT NOT NULL, `freefloating_vehicle_vehicleType` TEXT NOT NULL, `freefloating_vehicle_freefloating_vehicletypeinfo_formFactor` TEXT, `freefloating_vehicle_freefloating_vehicletypeinfo_maxRangeMeters` INTEGER, `freefloating_vehicle_freefloating_vehicletypeinfo_propulsionType` TEXT, `freefloating_vehicle_freefloating_operator_name` TEXT NOT NULL, `freefloating_vehicle_freefloating_operator_website` TEXT, `freefloating_vehicle_freefloating_operator_phone` TEXT, `freefloating_vehicle_freefloating_operator_freefloating_appinfo_name` TEXT, `freefloating_vehicle_freefloating_operator_freefloating_appinfo_appURLAndroid` TEXT, `freefloating_modeinfo_identifier` TEXT, `freefloating_modeinfo_alt` TEXT, `freefloating_modeinfo_localIcon` TEXT, `freefloating_modeinfo_remoteIcon` TEXT, `freefloating_modeinfo_remoteDarkIcon` TEXT, `freefloating_modeinfo_description` TEXT, `freefloating_modeinfo_remoteIconIsTemplate` INTEGER, `freefloating_modeinfo_remoteIconIsBranding` INTEGER, `freefloating_modeinfo_serviceColor_red` INTEGER, `freefloating_modeinfo_serviceColor_blue` INTEGER, `freefloating_modeinfo_serviceColor_green` INTEGER, PRIMARY KEY(`identifier`))", + "fields": [ + { + "fieldPath": "identifier", + "columnName": "identifier", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellId", + "columnName": "cellId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lat", + "columnName": "lat", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "lng", + "columnName": "lng", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "vehicle.available", + "columnName": "freefloating_vehicle_available", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "vehicle.batteryLevel", + "columnName": "freefloating_vehicle_batteryLevel", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "vehicle.lastUpdate", + "columnName": "freefloating_vehicle_lastUpdate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "vehicle.qrCode", + "columnName": "freefloating_vehicle_qrCode", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "vehicle.name", + "columnName": "freefloating_vehicle_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "vehicle.vehicleType", + "columnName": "freefloating_vehicle_vehicleType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "vehicle.vehicleTypeInfo.formFactor", + "columnName": "freefloating_vehicle_freefloating_vehicletypeinfo_formFactor", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "vehicle.vehicleTypeInfo.maxRangeMeters", + "columnName": "freefloating_vehicle_freefloating_vehicletypeinfo_maxRangeMeters", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "vehicle.vehicleTypeInfo.propulsionType", + "columnName": "freefloating_vehicle_freefloating_vehicletypeinfo_propulsionType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "vehicle.operator.name", + "columnName": "freefloating_vehicle_freefloating_operator_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "vehicle.operator.website", + "columnName": "freefloating_vehicle_freefloating_operator_website", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "vehicle.operator.phone", + "columnName": "freefloating_vehicle_freefloating_operator_phone", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "vehicle.operator.appInfo.name", + "columnName": "freefloating_vehicle_freefloating_operator_freefloating_appinfo_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "vehicle.operator.appInfo.appURLAndroid", + "columnName": "freefloating_vehicle_freefloating_operator_freefloating_appinfo_appURLAndroid", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "modeInfo.identifier", + "columnName": "freefloating_modeinfo_identifier", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "modeInfo.alt", + "columnName": "freefloating_modeinfo_alt", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "modeInfo.localIcon", + "columnName": "freefloating_modeinfo_localIcon", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "modeInfo.remoteIcon", + "columnName": "freefloating_modeinfo_remoteIcon", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "modeInfo.remoteDarkIcon", + "columnName": "freefloating_modeinfo_remoteDarkIcon", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "modeInfo.description", + "columnName": "freefloating_modeinfo_description", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "modeInfo.remoteIconIsTemplate", + "columnName": "freefloating_modeinfo_remoteIconIsTemplate", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "modeInfo.remoteIconIsBranding", + "columnName": "freefloating_modeinfo_remoteIconIsBranding", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "modeInfo.color.red", + "columnName": "freefloating_modeinfo_serviceColor_red", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "modeInfo.color.blue", + "columnName": "freefloating_modeinfo_serviceColor_blue", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "modeInfo.color.green", + "columnName": "freefloating_modeinfo_serviceColor_green", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "identifier" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "OpeningDayEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `carParkId` TEXT NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`carParkId`) REFERENCES `carParks`(`identifier`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "carParkId", + "columnName": "carParkId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_OpeningDayEntity_carParkId", + "unique": false, + "columnNames": [ + "carParkId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_OpeningDayEntity_carParkId` ON `${TABLE_NAME}` (`carParkId`)" + } + ], + "foreignKeys": [ + { + "table": "carParks", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "carParkId" + ], + "referencedColumns": [ + "identifier" + ] + } + ] + }, + { + "tableName": "OpeningTimeEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `openingDayId` TEXT NOT NULL, `opens` TEXT NOT NULL, `closes` TEXT NOT NULL, FOREIGN KEY(`openingDayId`) REFERENCES `OpeningDayEntity`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "openingDayId", + "columnName": "openingDayId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "opens", + "columnName": "opens", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "closes", + "columnName": "closes", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_OpeningTimeEntity_openingDayId", + "unique": false, + "columnNames": [ + "openingDayId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_OpeningTimeEntity_openingDayId` ON `${TABLE_NAME}` (`openingDayId`)" + } + ], + "foreignKeys": [ + { + "table": "OpeningDayEntity", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "openingDayId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "carPods", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `address` TEXT, `cellId` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `name` TEXT NOT NULL, `localIcon` TEXT NOT NULL, `remoteIcon` TEXT, `operatorName` TEXT NOT NULL, `operatorPhone` TEXT, `operatorWebsite` TEXT, `garageAddress` TEXT, `availableChargingSpaces` INTEGER, `availableVehicles` INTEGER, `totalSpaces` INTEGER, `lastUpdate` INTEGER, `inService` INTEGER, `deepLink` TEXT, `appURLAndroid` TEXT, `bookingURL` TEXT, `red` INTEGER NOT NULL, `green` INTEGER NOT NULL, `blue` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "cellId", + "columnName": "cellId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lat", + "columnName": "lat", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "lng", + "columnName": "lng", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "localIcon", + "columnName": "localIcon", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "remoteIcon", + "columnName": "remoteIcon", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "operatorName", + "columnName": "operatorName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "operatorPhone", + "columnName": "operatorPhone", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "operatorWebsite", + "columnName": "operatorWebsite", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "garageAddress", + "columnName": "garageAddress", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "availableChargingSpaces", + "columnName": "availableChargingSpaces", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "availableVehicles", + "columnName": "availableVehicles", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "totalSpaces", + "columnName": "totalSpaces", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "lastUpdate", + "columnName": "lastUpdate", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "inService", + "columnName": "inService", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "deepLink", + "columnName": "deepLink", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "appURLAndroid", + "columnName": "appURLAndroid", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "bookingURL", + "columnName": "bookingURL", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "red", + "columnName": "red", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "green", + "columnName": "green", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "blue", + "columnName": "blue", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "PricingEntryEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `price` REAL NOT NULL, `label` TEXT, `maxDurationInMinutes` INTEGER, `pricingTableId` TEXT NOT NULL, FOREIGN KEY(`pricingTableId`) REFERENCES `PricingTableEntity`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "price", + "columnName": "price", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "label", + "columnName": "label", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "maxDurationInMinutes", + "columnName": "maxDurationInMinutes", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "pricingTableId", + "columnName": "pricingTableId", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_PricingEntryEntity_pricingTableId", + "unique": false, + "columnNames": [ + "pricingTableId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_PricingEntryEntity_pricingTableId` ON `${TABLE_NAME}` (`pricingTableId`)" + } + ], + "foreignKeys": [ + { + "table": "PricingTableEntity", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "pricingTableId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "carPodVehicles", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `carPodId` TEXT NOT NULL, `name` TEXT NOT NULL, `fuelType` TEXT, `licensePlate` TEXT, FOREIGN KEY(`carPodId`) REFERENCES `carPods`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "carPodId", + "columnName": "carPodId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fuelType", + "columnName": "fuelType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "licensePlate", + "columnName": "licensePlate", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_carPodVehicles_carPodId", + "unique": false, + "columnNames": [ + "carPodId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_carPodVehicles_carPodId` ON `${TABLE_NAME}` (`carPodId`)" + } + ], + "foreignKeys": [ + { + "table": "carPods", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "carPodId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "PricingTableEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `title` TEXT NOT NULL, `subtitle` TEXT, `currencySymbol` TEXT NOT NULL, `currency` TEXT NOT NULL, `carParkId` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`carParkId`) REFERENCES `carParks`(`identifier`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subtitle", + "columnName": "subtitle", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "currencySymbol", + "columnName": "currencySymbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "currency", + "columnName": "currency", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "carParkId", + "columnName": "carParkId", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_PricingTableEntity_carParkId", + "unique": false, + "columnNames": [ + "carParkId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_PricingTableEntity_carParkId` ON `${TABLE_NAME}` (`carParkId`)" + } + ], + "foreignKeys": [ + { + "table": "carParks", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "carParkId" + ], + "referencedColumns": [ + "identifier" + ] + } + ] + }, + { + "tableName": "stopLocations", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`address` TEXT, `code` TEXT NOT NULL, `name` TEXT NOT NULL, `popularify` INTEGER NOT NULL, `services` TEXT NOT NULL, `stopType` TEXT NOT NULL, `timeZone` TEXT, `wheelchairAccessible` INTEGER NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `modeinfo_identifier` TEXT, `modeinfo_alt` TEXT, `modeinfo_localIcon` TEXT, `modeinfo_remoteIcon` TEXT, `modeinfo_remoteDarkIcon` TEXT, `modeinfo_description` TEXT, `modeinfo_remoteIconIsTemplate` INTEGER NOT NULL, `modeinfo_remoteIconIsBranding` INTEGER NOT NULL, `modeinfo_serviceColor_red` INTEGER, `modeinfo_serviceColor_blue` INTEGER, `modeinfo_serviceColor_green` INTEGER, PRIMARY KEY(`code`))", + "fields": [ + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "code", + "columnName": "code", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "popularify", + "columnName": "popularify", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "services", + "columnName": "services", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "stopType", + "columnName": "stopType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timeZone", + "columnName": "timeZone", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "wheelchairAccessible", + "columnName": "wheelchairAccessible", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lat", + "columnName": "lat", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "lng", + "columnName": "lng", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modeInfo.identifier", + "columnName": "modeinfo_identifier", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "modeInfo.alt", + "columnName": "modeinfo_alt", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "modeInfo.localIcon", + "columnName": "modeinfo_localIcon", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "modeInfo.remoteIcon", + "columnName": "modeinfo_remoteIcon", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "modeInfo.remoteDarkIcon", + "columnName": "modeinfo_remoteDarkIcon", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "modeInfo.description", + "columnName": "modeinfo_description", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "modeInfo.remoteIconIsTemplate", + "columnName": "modeinfo_remoteIconIsTemplate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "modeInfo.remoteIconIsBranding", + "columnName": "modeinfo_remoteIconIsBranding", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "modeInfo.color.red", + "columnName": "modeinfo_serviceColor_red", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "modeInfo.color.blue", + "columnName": "modeinfo_serviceColor_blue", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "modeInfo.color.green", + "columnName": "modeinfo_serviceColor_green", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "code" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "scheduledServiceRealtimeInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `realTimeDeparture` INTEGER NOT NULL, `realTimeArrival` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realTimeDeparture", + "columnName": "realTimeDeparture", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realTimeArrival", + "columnName": "realTimeArrival", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "parent_stops_to_children_stops", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`parentStopCode` TEXT NOT NULL, `childrenStopCode` TEXT NOT NULL, PRIMARY KEY(`parentStopCode`, `childrenStopCode`))", + "fields": [ + { + "fieldPath": "parentStopCode", + "columnName": "parentStopCode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "childrenStopCode", + "columnName": "childrenStopCode", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "parentStopCode", + "childrenStopCode" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "serviceAlerts", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `serviceTripId` TEXT NOT NULL, `title` TEXT NOT NULL, `remoteHashCode` INTEGER NOT NULL, `severity` TEXT NOT NULL, `text` TEXT, `url` TEXT, `remoteIcon` TEXT, `lastUpdated` INTEGER NOT NULL, `fromDate` INTEGER NOT NULL, `location_lat` REAL, `location_lng` REAL, `location_timezone` TEXT, `location_address` TEXT, `action_text` TEXT, `action_type` TEXT, `action_excludedStopCodes` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serviceTripId", + "columnName": "serviceTripId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "remoteHashCode", + "columnName": "remoteHashCode", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "severity", + "columnName": "severity", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "remoteIcon", + "columnName": "remoteIcon", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fromDate", + "columnName": "fromDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "location.lat", + "columnName": "location_lat", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "location.lng", + "columnName": "location_lng", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "location.timezone", + "columnName": "location_timezone", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "location.address", + "columnName": "location_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "action.text", + "columnName": "action_text", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "action.type", + "columnName": "action_type", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "action.excludedStopCodes", + "columnName": "action_excludedStopCodes", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '49661b1d3f26137a509189769563f103')" + ] + } +} \ No newline at end of file diff --git a/TripKitData/src/main/java/com/skedgo/tripkit/account/data/AccountDataModule.kt b/TripKitData/src/main/java/com/skedgo/tripkit/account/data/AccountDataModule.kt index b5cea2e3..fe8f63e9 100644 --- a/TripKitData/src/main/java/com/skedgo/tripkit/account/data/AccountDataModule.kt +++ b/TripKitData/src/main/java/com/skedgo/tripkit/account/data/AccountDataModule.kt @@ -4,7 +4,7 @@ import android.content.Context import android.content.SharedPreferences import com.skedgo.tripkit.account.domain.UserKeyRepository import com.skedgo.tripkit.account.domain.UserTokenRepository -import com.skedgo.tripkit.configuration.Server +import com.skedgo.tripkit.configuration.ServerManager import dagger.Module import dagger.Provides import io.reactivex.schedulers.Schedulers @@ -26,7 +26,7 @@ class AccountDataModule { httpClient: OkHttpClient, @Named(UserTokenPreferences) prefs: SharedPreferences ): UserTokenRepository { val silentLoginApi = Retrofit.Builder() - .baseUrl(Server.ApiTripGo.value) + .baseUrl(ServerManager.configuration.apiTripGoUrl) .addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io())) .addConverterFactory(GsonConverterFactory.create()) .client(httpClient) @@ -34,7 +34,7 @@ class AccountDataModule { .create(SilentLoginApi::class.java) val accountApi = Retrofit.Builder() - .baseUrl(Server.ApiTripGo.value) + .baseUrl(ServerManager.configuration.apiTripGoUrl) .addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io())) .addConverterFactory(GsonConverterFactory.create()) .client(httpClient) diff --git a/TripKitData/src/main/java/com/skedgo/tripkit/account/data/Client.kt b/TripKitData/src/main/java/com/skedgo/tripkit/account/data/Client.kt index b01106d2..5f94ef94 100644 --- a/TripKitData/src/main/java/com/skedgo/tripkit/account/data/Client.kt +++ b/TripKitData/src/main/java/com/skedgo/tripkit/account/data/Client.kt @@ -7,8 +7,11 @@ data class Client( var clientName: String, val polygon: Polygon? = null, val appColors: AppColors? = null, - var isBeta: Boolean = false -) + var isBeta: Boolean = false, + val features: List? = emptyList() +) { + fun hasWalletFeature(): Boolean = features?.any { it == ClientFeature.WALLET.feature } ?: false +} data class AppColors( val barBackground: BarBackground, @@ -45,4 +48,8 @@ data class TintColor( val blue: Int, val green: Int, val red: Int -) \ No newline at end of file +) + +enum class ClientFeature(val feature: String) { + WALLET("WALLET") +} \ No newline at end of file diff --git a/TripKitData/src/main/java/com/skedgo/tripkit/data/database/DbFields.java b/TripKitData/src/main/java/com/skedgo/tripkit/data/database/DbFields.java index 37e7866e..854125a1 100644 --- a/TripKitData/src/main/java/com/skedgo/tripkit/data/database/DbFields.java +++ b/TripKitData/src/main/java/com/skedgo/tripkit/data/database/DbFields.java @@ -98,6 +98,7 @@ public final class DbFields { public static final DatabaseField W3W_INFO_URL = new DatabaseField("w3w_info_url", "text"); public static final DatabaseField SERVICE_DIRECTION = new DatabaseField("service_direction", "text"); public static final DatabaseField WHEELCHAIR_ACCESSIBLE = new DatabaseField("wheelchair_accessible", "integer"); + public static final DatabaseField BICYCLE_ACCESSIBLE = new DatabaseField("bicycle_accessible", "integer"); public static final DatabaseField OCCUPANCY = new DatabaseField("occupancy", "text"); public static final DatabaseField START_STOP_SHORT_NAME = new DatabaseField("start_stop_short_name", "text"); public static final DatabaseField START_PLATFORM = new DatabaseField("start_platform", "text"); diff --git a/TripKitData/src/main/java/com/skedgo/tripkit/data/database/DbHelper.java b/TripKitData/src/main/java/com/skedgo/tripkit/data/database/DbHelper.java index 25052150..15038109 100644 --- a/TripKitData/src/main/java/com/skedgo/tripkit/data/database/DbHelper.java +++ b/TripKitData/src/main/java/com/skedgo/tripkit/data/database/DbHelper.java @@ -11,7 +11,7 @@ import io.reactivex.functions.Consumer; public final class DbHelper extends SQLiteOpenHelper { - private static final int DATABASE_VERSION = 3; + private static final int DATABASE_VERSION = 4; private final DatabaseMigrator databaseMigrator; public DbHelper( @@ -36,6 +36,13 @@ public void onUpgrade(SQLiteDatabase database, int oldVersion, int newVersion) { } catch (SQLiteException ex) { // ignored if the column exists } + } else if (newVersion > 3) { + try { + database.execSQL("ALTER TABLE services ADD COLUMN bicycle_accessible INTEGER"); + database.execSQL("ALTER TABLE service_stops ADD COLUMN bicycle_accessible INTEGER"); + } catch (SQLiteException ex) { + // ignored if the column exists + } } } else { databaseMigrator.onUpgrade(database, oldVersion, newVersion); diff --git a/TripKitData/src/main/java/com/skedgo/tripkit/data/database/DbTables.java b/TripKitData/src/main/java/com/skedgo/tripkit/data/database/DbTables.java index 356146ae..bfe60676 100644 --- a/TripKitData/src/main/java/com/skedgo/tripkit/data/database/DbTables.java +++ b/TripKitData/src/main/java/com/skedgo/tripkit/data/database/DbTables.java @@ -125,6 +125,7 @@ public final class DbTables { DbFields.MODE_INFO, DbFields.SERVICE_DIRECTION, DbFields.WHEELCHAIR_ACCESSIBLE, + DbFields.BICYCLE_ACCESSIBLE, DbFields.START_STOP_SHORT_NAME, DbFields.START_PLATFORM }, @@ -159,7 +160,8 @@ public final class DbTables { DbFields.DEPARTURE_TIME, DbFields.ARRIVAL_TIME, DbFields.JULIAN_DAY, - DbFields.WHEELCHAIR_ACCESSIBLE + DbFields.WHEELCHAIR_ACCESSIBLE, + DbFields.BICYCLE_ACCESSIBLE, }, "CREATE UNIQUE INDEX 'unique_stop_per_service_per_day' ON service_stops (" + DbFields.STOP_CODE + ", " + diff --git a/TripKitData/src/main/java/com/skedgo/tripkit/data/database/TripKitDatabase.kt b/TripKitData/src/main/java/com/skedgo/tripkit/data/database/TripKitDatabase.kt index 8070dc31..19d55c66 100644 --- a/TripKitData/src/main/java/com/skedgo/tripkit/data/database/TripKitDatabase.kt +++ b/TripKitData/src/main/java/com/skedgo/tripkit/data/database/TripKitDatabase.kt @@ -4,12 +4,16 @@ import android.content.Context import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase +import com.skedgo.tripkit.data.database.booking.ticket.TicketDao +import com.skedgo.tripkit.data.database.booking.ticket.TicketEntity import com.skedgo.tripkit.data.database.locations.bikepods.BikePodDao import com.skedgo.tripkit.data.database.locations.bikepods.BikePodLocationEntity import com.skedgo.tripkit.data.database.locations.carparks.* import com.skedgo.tripkit.data.database.locations.carpods.CarPodDao import com.skedgo.tripkit.data.database.locations.carpods.CarPodEntity import com.skedgo.tripkit.data.database.locations.carpods.CarPodVehicle +import com.skedgo.tripkit.data.database.locations.facility.FacilityDao +import com.skedgo.tripkit.data.database.locations.facility.FacilityLocationEntity import com.skedgo.tripkit.data.database.locations.freefloating.FreeFloatingLocationDao import com.skedgo.tripkit.data.database.locations.freefloating.FreeFloatingLocationEntity import com.skedgo.tripkit.data.database.locations.onstreetparking.OnStreetParkingDao @@ -36,17 +40,21 @@ import com.skedgo.tripkit.data.database.timetables.ServiceAlertsEntity StopLocationEntity::class, ScheduledServiceRealtimeInfoEntity::class, ParentStopEntity::class, - ServiceAlertsEntity::class], version = 5) + ServiceAlertsEntity::class, + TicketEntity::class, + FacilityLocationEntity::class,], version = 7) abstract class TripKitDatabase : RoomDatabase() { abstract fun carParkDao(): CarParkDao abstract fun carPodDao(): CarPodDao abstract fun bikePodDao(): BikePodDao + abstract fun facilityDao(): FacilityDao abstract fun freeFloatingLocationDao(): FreeFloatingLocationDao abstract fun stopLocationDao(): StopLocationDao abstract fun scheduledServiceRealtimeInfoDao(): ScheduledServiceRealtimeInfoDao abstract fun parentStopDao(): ParentStopDao abstract fun onStreetParkingDao(): OnStreetParkingDao abstract fun serviceAlertsDao(): ServiceAlertsDao + abstract fun ticketDao(): TicketDao companion object { fun getInstance(context: Context): TripKitDatabase { return Room.databaseBuilder(context.applicationContext, diff --git a/TripKitData/src/main/java/com/skedgo/tripkit/data/database/booking/ticket/TicketDao.kt b/TripKitData/src/main/java/com/skedgo/tripkit/data/database/booking/ticket/TicketDao.kt new file mode 100644 index 00000000..80cfdb28 --- /dev/null +++ b/TripKitData/src/main/java/com/skedgo/tripkit/data/database/booking/ticket/TicketDao.kt @@ -0,0 +1,37 @@ +package com.skedgo.tripkit.data.database.booking.ticket + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import io.reactivex.Completable +import io.reactivex.Maybe +import io.reactivex.Single + +@Dao +interface TicketDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertTicket(ticket: TicketEntity) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertTickets(tickets: List) + + @Query("SELECT * FROM tickets WHERE id = :id") + suspend fun getTicketById(id: String): TicketEntity? + + //Included RxJava version for repository/service that still using RxJava + @Query("SELECT * FROM tickets") + suspend fun getAllTickets(): List + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertTicketRx(ticket: TicketEntity): Completable + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertTicketsRx(tickets: List): Completable + + @Query("SELECT * FROM tickets WHERE id = :id") + fun getTicketByIdRx(id: String): Maybe + + @Query("SELECT * FROM tickets") + fun getAllTicketsRx(): Single> +} \ No newline at end of file diff --git a/TripKitData/src/main/java/com/skedgo/tripkit/data/database/booking/ticket/TicketEntity.kt b/TripKitData/src/main/java/com/skedgo/tripkit/data/database/booking/ticket/TicketEntity.kt new file mode 100644 index 00000000..39d38b48 --- /dev/null +++ b/TripKitData/src/main/java/com/skedgo/tripkit/data/database/booking/ticket/TicketEntity.kt @@ -0,0 +1,19 @@ +package com.skedgo.tripkit.data.database.booking.ticket + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "tickets") +data class TicketEntity( + @PrimaryKey val id: String, + val validFromTimestamp: String?, + val validUntilTimestamp: String?, + val ticketURL: String?, + val activateURL: String?, + val ticketExpirationTimestamp: String?, + val purchasedTimestamp: String, + val fareJson: String, // Serialized Fare + val status: String?, + val qrCode: String? = null, + val ticketActionsJson: String?, // Serialized TicketAction +) \ No newline at end of file diff --git a/TripKitData/src/main/java/com/skedgo/tripkit/data/database/locations/facility/FacilityDao.kt b/TripKitData/src/main/java/com/skedgo/tripkit/data/database/locations/facility/FacilityDao.kt new file mode 100644 index 00000000..e444e0d7 --- /dev/null +++ b/TripKitData/src/main/java/com/skedgo/tripkit/data/database/locations/facility/FacilityDao.kt @@ -0,0 +1,27 @@ +package com.skedgo.tripkit.data.database.locations.facility + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import io.reactivex.Flowable + +@Dao +abstract class FacilityDao { + @Query("SELECT * from facilities WHERE cellId IN (:cellIds)") + abstract fun getAllInCells(cellIds: List): Flowable> + + @Query("SELECT * from facilities WHERE cellId IN (:cellIds) AND :southWestLat < lat AND lat < :northEastLat AND :southWestLng < lng AND lng < :northEastLng") + abstract fun getAllByCellsAndLatLngBounds( + cellIds: List, + southWestLat: Double, + southWestLng: Double, + northEastLat: Double, + northEastLng: Double): Flowable> + + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract fun saveAll(facilities: List) + + @Query("SELECT * from facilities WHERE identifier == :id") + abstract fun getById(id: String): FacilityLocationEntity +} \ No newline at end of file diff --git a/TripKitData/src/main/java/com/skedgo/tripkit/data/database/locations/facility/FacilityLocationEntity.kt b/TripKitData/src/main/java/com/skedgo/tripkit/data/database/locations/facility/FacilityLocationEntity.kt new file mode 100644 index 00000000..6392b0ca --- /dev/null +++ b/TripKitData/src/main/java/com/skedgo/tripkit/data/database/locations/facility/FacilityLocationEntity.kt @@ -0,0 +1,22 @@ +package com.skedgo.tripkit.data.database.locations.facility + + +import androidx.annotation.Keep +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "facilities") +@Keep +class FacilityLocationEntity { + @PrimaryKey + var identifier: String = "" + var cellId: String? = null + var lat: Double = 0.0 + var lng: Double = 0.0 + var address: String? = null + var timezone: String? = null + var city: String? = null + var region: String? = null + var name: String? = null + var facilityType: String? = null +} \ No newline at end of file diff --git a/TripKitData/src/main/java/com/skedgo/tripkit/data/database/locations/facility/FacilityRepository.kt b/TripKitData/src/main/java/com/skedgo/tripkit/data/database/locations/facility/FacilityRepository.kt new file mode 100644 index 00000000..dab5c6fd --- /dev/null +++ b/TripKitData/src/main/java/com/skedgo/tripkit/data/database/locations/facility/FacilityRepository.kt @@ -0,0 +1,13 @@ +package com.skedgo.tripkit.data.database.locations.facility + +import io.reactivex.Completable +import io.reactivex.Observable +import io.reactivex.Single +import com.skedgo.tripkit.location.GeoPoint + +interface FacilityRepository { + fun saveFacilities(key: String, facilities: List): Completable + fun getFacilities(cellIds: List): Observable> + fun getFacility(id: String): Single + fun getFacilitiesWithinBounds(cellIds: List, southwest: GeoPoint, northEast: GeoPoint): Observable> +} \ No newline at end of file diff --git a/TripKitData/src/main/java/com/skedgo/tripkit/data/database/locations/facility/FacilityRepositoryImpl.kt b/TripKitData/src/main/java/com/skedgo/tripkit/data/database/locations/facility/FacilityRepositoryImpl.kt new file mode 100644 index 00000000..c3b0bed5 --- /dev/null +++ b/TripKitData/src/main/java/com/skedgo/tripkit/data/database/locations/facility/FacilityRepositoryImpl.kt @@ -0,0 +1,48 @@ +package com.skedgo.tripkit.data.database.locations.facility + +import com.skedgo.tripkit.data.database.TripKitDatabase +import io.reactivex.Completable +import io.reactivex.Observable +import io.reactivex.Single +import io.reactivex.schedulers.Schedulers +import com.skedgo.tripkit.location.GeoPoint + +class FacilityRepositoryImpl(val tripGoDatabase2: TripKitDatabase) : FacilityRepository { + override fun saveFacilities( + key: String, + facilities: List + ): Completable { + return Completable + .fromAction { + facilities.forEach { + it.cellId = key + it.identifier = it.identifier + } + tripGoDatabase2.facilityDao().saveAll(facilities) + } + .subscribeOn(Schedulers.io()) + } + + override fun getFacilities(cellIds: List): Observable> { + return tripGoDatabase2.facilityDao() + .getAllInCells(cellIds) + .subscribeOn(Schedulers.io()).toObservable() + } + + override fun getFacilitiesWithinBounds( + cellIds: List, + southwest: GeoPoint, + northEast: GeoPoint + ): Observable> { + return tripGoDatabase2.facilityDao().getAllInCells(cellIds) + .subscribeOn(Schedulers.io()).toObservable() + } + + override fun getFacility(id: String): Single { + return Single + .fromCallable { + tripGoDatabase2.facilityDao().getById(id) + } + .subscribeOn(Schedulers.io()) + } +} \ No newline at end of file diff --git a/TripKitData/src/main/java/com/skedgo/tripkit/data/database/stops/StopLocationEntity.kt b/TripKitData/src/main/java/com/skedgo/tripkit/data/database/stops/StopLocationEntity.kt index b6f2fec2..da8ca6ea 100644 --- a/TripKitData/src/main/java/com/skedgo/tripkit/data/database/stops/StopLocationEntity.kt +++ b/TripKitData/src/main/java/com/skedgo/tripkit/data/database/stops/StopLocationEntity.kt @@ -22,6 +22,7 @@ class StopLocationEntity { lateinit var stopType: String var timeZone: String? = null var wheelchairAccessible: Boolean = false + var bicycleAccessible: Boolean = false var lat: Double = 0.0 var lng: Double = 0.0 @@ -39,6 +40,7 @@ fun StopLocationEntity.toScheduledStop(): ScheduledStop { it.type = this.stopType.let { StopType.from(it) } it.timeZone = this.timeZone it.wheelchairAccessible = this.wheelchairAccessible + it.bicycleAccessible = this.bicycleAccessible it.lat = this.lat it.lon = this.lng it.modeInfo = this.modeInfo.toModeInfo() diff --git a/TripKitData/src/main/java/com/skedgo/tripkit/data/locations/LocationsResponse.java b/TripKitData/src/main/java/com/skedgo/tripkit/data/locations/LocationsResponse.java index c2c48db1..808c5605 100644 --- a/TripKitData/src/main/java/com/skedgo/tripkit/data/locations/LocationsResponse.java +++ b/TripKitData/src/main/java/com/skedgo/tripkit/data/locations/LocationsResponse.java @@ -16,6 +16,7 @@ import com.skedgo.tripkit.data.database.locations.carpods.CarPodLocation; import com.skedgo.tripkit.data.database.locations.carrentals.CarRentalCompany; import com.skedgo.tripkit.data.database.locations.carrentals.CarRentalLocation; +import com.skedgo.tripkit.data.database.locations.facility.FacilityLocationEntity; import com.skedgo.tripkit.data.database.locations.freefloating.FreeFloatingLocationEntity; import com.skedgo.tripkit.data.database.locations.onstreetparking.OnStreetParkingLocation; @@ -53,6 +54,8 @@ public static class Group { @SerializedName("carRentals") List carRentals; + List facilities; + @SerializedName("hashCode") private long hashCode; @@ -98,5 +101,6 @@ public String getKey() { public List getCarRentals() { return carRentals; } public List getCarParks() { return carParks; } public List getCarPods() { return carPods; } + public List getFacilities() { return facilities; } } } \ No newline at end of file diff --git a/TripKitData/src/main/java/com/skedgo/tripkit/data/locations/StopsFetcher.kt b/TripKitData/src/main/java/com/skedgo/tripkit/data/locations/StopsFetcher.kt index 37a16500..99b6c816 100644 --- a/TripKitData/src/main/java/com/skedgo/tripkit/data/locations/StopsFetcher.kt +++ b/TripKitData/src/main/java/com/skedgo/tripkit/data/locations/StopsFetcher.kt @@ -14,6 +14,7 @@ import io.reactivex.schedulers.Schedulers import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import org.apache.commons.collections4.CollectionUtils import com.skedgo.tripkit.agenda.ConfigRepository +import com.skedgo.tripkit.data.database.locations.facility.FacilityRepository import com.skedgo.tripkit.data.database.locations.freefloating.FreeFloatingRepository import java.util.* @@ -29,7 +30,9 @@ open class StopsFetcher(private val api: LocationsApi, private val carParkMapper: CarParkMapper, private val carPodMapper: CarPodMapper, private val onStreetParkingMapper: OnStreetParkingMapper, - private val carPodRepository: CarPodRepository) { + private val carPodRepository: CarPodRepository, + private val facilityRepository: FacilityRepository, + ) { open fun fetchAsync(cellIds: List, region: Region, @@ -162,6 +165,9 @@ open class StopsFetcher(private val api: LocationsApi, ).plus( cells.filter { it.carPods != null && it.carPods.isNotEmpty() } .map { carPodRepository.saveCarPods(carPodMapper.toEntity(it.key, it.carPods)) } + ).plus( + cells.filter { it.facilities != null && it.facilities.isNotEmpty() } + .map { facilityRepository.saveFacilities(it.key, it.facilities) } ) .toList() .let { diff --git a/TripKitDomain/src/main/java/com/skedgo/tripkit/configuration/Server.kt b/TripKitDomain/src/main/java/com/skedgo/tripkit/configuration/Server.kt index db302cd9..a42ce279 100644 --- a/TripKitDomain/src/main/java/com/skedgo/tripkit/configuration/Server.kt +++ b/TripKitDomain/src/main/java/com/skedgo/tripkit/configuration/Server.kt @@ -1,9 +1,46 @@ package com.skedgo.tripkit.configuration -enum class Server(val value: String) { +enum class DefaultServer(val value: String) { ApiTripGo("https://api.tripgo.com/v1/"), -// ApiTripGo("https://galaxies.skedgo.com/lab/beta/satapp/"), - // BigBang("https://bigbang.buzzhives.com/satapp/") -// BigBang("https://api-beta.tripgo.com/v1/") BigBang("https://galaxies.skedgo.com/lab/beta/satapp/") } + +open class ServerConfiguration( + var apiTripGoUrl: String = DefaultServer.ApiTripGo.value, + var bigBangUrl: String = DefaultServer.BigBang.value +) + +/** + * Singleton object that manages server configurations for the application. + * + * This manager allows for setting and retrieving server URLs throughout the application. + * It provides a centralized point of configuration, making it easy to adjust server URLs + * for different environments (e.g., development, staging, production) or build variants. + * + * Usage: + * - Access default server URLs via `ServerManager.configuration`. + * - Customize server URLs using `ServerManager.customizeConfiguration(...)`. + * + * Example: + * ``` + * // Accessing default URLs + * val defaultApiUrl = ServerManager.configuration.apiTripGoUrl + * val defaultBigBangUrl = ServerManager.configuration.bigBangUrl + * + * // Customizing URLs + * ServerManager.customizeConfiguration(apiTripGoUrl = "https://api.newtripgo.com/v2/", bigBangUrl = "https://new.bigbang.url/") + * ``` + * + * Note: Customizations to the server URLs should ideally be done during application initialization + * to ensure consistent use of the URLs throughout the application lifecycle. + */ +object ServerManager { + + var configuration: ServerConfiguration = ServerConfiguration() + + fun setCustomConfiguration(apiTripGoUrl: String? = null, bigBangUrl: String? = null) { + apiTripGoUrl?.let { configuration.apiTripGoUrl = it } + bigBangUrl?.let { configuration.bigBangUrl = it } + } + +} \ No newline at end of file diff --git a/TripKitDomainLegacy/build.gradle b/TripKitDomainLegacy/build.gradle index 654d8347..2070e508 100644 --- a/TripKitDomainLegacy/build.gradle +++ b/TripKitDomainLegacy/build.gradle @@ -16,6 +16,12 @@ android { targetSdkVersion versions.targetSdkVersion } + buildTypes { + staging { + debuggable true + } + } + lintOptions { // Changing to warning because of https://github.com/square/okio/issues/58. warning 'InvalidPackage' @@ -39,9 +45,13 @@ android { } dependencies { + debugImplementation project(":TripKitDomain") + stagingImplementation project(":TripKitDomain") releaseImplementation project(":TripKitDomain") + debugImplementation project(':CommonCoreLegacy') + stagingImplementation project(':CommonCoreLegacy') releaseImplementation project(':CommonCoreLegacy') implementation libs.dagger diff --git a/TripKitSamples/build.gradle b/TripKitSamples/build.gradle index 732f3356..22d99385 100644 --- a/TripKitSamples/build.gradle +++ b/TripKitSamples/build.gradle @@ -53,7 +53,10 @@ dependencies { implementation libs.bindingCollectionAdapterRecyclerView debugImplementation project(':TripKitAndroid') + stagingImplementation project(':TripKitAndroid') releaseImplementation project(':TripKitAndroid') + debugImplementation project(':rxlifecyclecomponents') + stagingImplementation project(':rxlifecyclecomponents') releaseImplementation project(':rxlifecyclecomponents') } \ No newline at end of file diff --git a/ValidBookingCountData/build.gradle b/ValidBookingCountData/build.gradle index 77ae01ea..75ce4ad9 100644 --- a/ValidBookingCountData/build.gradle +++ b/ValidBookingCountData/build.gradle @@ -14,6 +14,12 @@ android { targetSdkVersion versions.targetSdkVersion } + buildTypes { + staging { + debuggable true + } + } + lintOptions { // Changing to warning because of https://github.com/square/okio/issues/58. warning 'InvalidPackage' @@ -60,8 +66,11 @@ dependencies { implementation libs.kotlin debugImplementation project(':TripKitDomain') + stagingImplementation project(':TripKitDomain') releaseImplementation project(':TripKitDomain') + debugImplementation project(':ValidBookingCountDomain') + stagingImplementation project(':ValidBookingCountDomain') releaseImplementation project(':ValidBookingCountDomain') implementation libs.javaxAnnotation diff --git a/ValidBookingCountData/src/main/java/com/skedgo/tripkit/validbookingcount/data/ValidBookingCountDataModule.kt b/ValidBookingCountData/src/main/java/com/skedgo/tripkit/validbookingcount/data/ValidBookingCountDataModule.kt index 08393ba6..1442cff8 100644 --- a/ValidBookingCountData/src/main/java/com/skedgo/tripkit/validbookingcount/data/ValidBookingCountDataModule.kt +++ b/ValidBookingCountData/src/main/java/com/skedgo/tripkit/validbookingcount/data/ValidBookingCountDataModule.kt @@ -8,7 +8,7 @@ import retrofit2.Retrofit import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory import retrofit2.converter.gson.GsonConverterFactory import io.reactivex.schedulers.Schedulers -import com.skedgo.tripkit.configuration.Server +import com.skedgo.tripkit.configuration.ServerManager import com.skedgo.tripkit.validbookingcount.domain.ValidBookingCountRepository @Module @@ -19,7 +19,7 @@ class ValidBookingCountDataModule { ): ValidBookingCountRepository { val api = Retrofit.Builder() .client(httpClient) - .baseUrl(Server.ApiTripGo.value) + .baseUrl(ServerManager.configuration.apiTripGoUrl) .addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io())) .addConverterFactory(GsonConverterFactory.create()) .build() diff --git a/dependencies.gradle b/dependencies.gradle index bd1d1371..88f8766d 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -35,6 +35,10 @@ versions.paging = "3.0.0" versions.viewPager2 = "1.0.0" versions.mockk = "1.13.8" versions.archCore = "2.1.0" +versions.qrCodeGenerator = "1.2.0" +versions.amplifyCore = "2.14.11" +versions.amplifyAuth = "1.1.0" +versions.desugar = "2.0.4" ext.versions = versions @@ -143,6 +147,7 @@ libs.coroutinesCore = "org.jetbrains.kotlinx:kotlinx-coroutines-core:$versions.k libs.coroutinesAndroid = "org.jetbrains.kotlinx:kotlinx-coroutines-android:$versions.kotlinCoroutinesVersion" libs.coroutinesRx = "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$versions.kotlinCoroutinesVersion" libs.coroutinesPlayServices = "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:$versions.kotlinCoroutinesVersion" +libs.coroutinesTest = "org.jetbrains.kotlinx:kotlinx-coroutines-test:$versions.kotlinCoroutinesVersion" libs.networkResponse = "com.github.haroldadmin:NetworkResponseAdapter:4.0.1" libs.ktxActivity = "androidx.activity:activity-ktx:1.1.0" libs.picasso = "com.squareup.picasso:picasso:2.71828" @@ -153,10 +158,17 @@ libs.pdfViewer = "com.github.barteksc:android-pdf-viewer:2.8.2" libs.fragmentKtx = "androidx.fragment:fragment-ktx:1.3.2" libs.glide = "com.github.bumptech.glide:glide:$versions.glide" libs.viewPager2 = "androidx.viewpager2:viewpager2:$versions.viewPager2" +libs.qrCodeGenerator = "com.github.SumiMakito:AwesomeQRCode:$versions.qrCodeGenerator" libs.volley = "com.android.volley:volley:$versions.volley" libs.jetBrainsAnnotation = "org.jetbrains:annotations-java5:$versions.jetBrainsJava5" libs.archCoreTesting = "androidx.arch.core:core-testing:$versions.archCore" +libs.amplifyCore = "com.amplifyframework:core:$versions.ampifyCore" +libs.amplifyAuth = "com.amplifyframework.ui:authenticator:$versions.amplifyAuth" + +versions.amplifyCore = "2.14.11" +versions.amplifyAuth = "1.1.0" + ext.libs = libs diff --git a/route-persistence/build.gradle b/route-persistence/build.gradle index afe55eb8..a98a5bbd 100644 --- a/route-persistence/build.gradle +++ b/route-persistence/build.gradle @@ -30,6 +30,9 @@ android { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } + staging { + debuggable true + } } compileOptions { @@ -51,8 +54,11 @@ android { dependencies { debugImplementation project(':CommonCoreLegacy') + stagingImplementation project(':CommonCoreLegacy') releaseImplementation project(':CommonCoreLegacy') + debugImplementation project(':sqliteutils') + stagingImplementation project(':sqliteutils') releaseImplementation project(':sqliteutils') implementation libs.appCompat @@ -60,6 +66,7 @@ dependencies { implementation libs.supportAnnotations debugImplementation project(":TripKitDomain") + stagingImplementation project(":TripKitDomain") releaseImplementation project(":TripKitDomain") implementation libs.kotlin diff --git a/route-persistence/src/main/java/com/skedgo/routepersistence/RouteContract.java b/route-persistence/src/main/java/com/skedgo/routepersistence/RouteContract.java index f1aeb2d6..4da4352c 100644 --- a/route-persistence/src/main/java/com/skedgo/routepersistence/RouteContract.java +++ b/route-persistence/src/main/java/com/skedgo/routepersistence/RouteContract.java @@ -43,6 +43,9 @@ final class RouteContract { static final String COL_SOURCES = "sources"; static final String COL_SUBSCRIBE_URL = "subscribeURL"; static final String COL_UNSUBSCRIBE_URL = "unsubscribeURL"; + static final String COL_AVAILABILITY = "availability"; + static final String COL_AVAILABILITY_INFO = "availabilityInfo"; + static final String COL_MONEY_USD_COST = "moneyUSDCost"; private RouteContract() { } @@ -80,6 +83,9 @@ static void createTables(SQLiteDatabase database) { final DatabaseField sources = new DatabaseField(COL_SOURCES, "text"); final DatabaseField subscribeUrl = new DatabaseField(COL_SUBSCRIBE_URL, "text"); final DatabaseField unSubscribeUrl = new DatabaseField(COL_UNSUBSCRIBE_URL, "text"); + final DatabaseField availability = new DatabaseField(COL_AVAILABILITY, "text"); + final DatabaseField availabilityInfo = new DatabaseField(COL_AVAILABILITY_INFO, "text"); + final DatabaseField moneyUsdCost = new DatabaseField(COL_MONEY_USD_COST, "real"); final DatabaseTable tripGroups = new DatabaseTable( TABLE_TRIP_GROUPS, new DatabaseField[]{ @@ -100,7 +106,7 @@ static void createTables(SQLiteDatabase database) { caloriesCost, moneyCost, carbonCost, hassleCost, weightedScore, updateUrl, progressUrl, plannedUrl, tempUrl, queryIsLeaveAfter, logUrl, shareUrl, mainSegmentHashCode, isHideExactTimes, - queryTime, subscribeUrl, unSubscribeUrl + queryTime, subscribeUrl, unSubscribeUrl, availability, availabilityInfo, moneyUsdCost }, UniqueIndices.of(TABLE_TRIPS, id, groupId, uuid), "CREATE TRIGGER deleteSegments AFTER DELETE ON " + TABLE_TRIPS + " BEGIN " + diff --git a/route-persistence/src/main/java/com/skedgo/routepersistence/RouteDatabaseHelper.java b/route-persistence/src/main/java/com/skedgo/routepersistence/RouteDatabaseHelper.java index 59c6dbea..76821477 100644 --- a/route-persistence/src/main/java/com/skedgo/routepersistence/RouteDatabaseHelper.java +++ b/route-persistence/src/main/java/com/skedgo/routepersistence/RouteDatabaseHelper.java @@ -7,7 +7,7 @@ import android.widget.Toast; public class RouteDatabaseHelper extends SQLiteOpenHelper { - private static final int DATABASE_VERSION = 11; + private static final int DATABASE_VERSION = 12; public RouteDatabaseHelper(Context context, String name) { super(context, name, null, DATABASE_VERSION); @@ -83,6 +83,15 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // ignored if the column exists } } + if(newVersion > 11) { + try { + db.execSQL("ALTER TABLE trips ADD COLUMN availability TEXT"); + db.execSQL("ALTER TABLE trips ADD COLUMN availabilityInfo TEXT"); + db.execSQL("ALTER TABLE trips ADD COLUMN moneyUSDCost REAL"); + } catch (SQLiteException ex) { + // ignored if the column exists + } + } } else { RoutingStatusContract.INSTANCE.create(db); } diff --git a/route-persistence/src/main/java/com/skedgo/routepersistence/RouteStore.kt b/route-persistence/src/main/java/com/skedgo/routepersistence/RouteStore.kt index 726b84d2..e7172592 100644 --- a/route-persistence/src/main/java/com/skedgo/routepersistence/RouteStore.kt +++ b/route-persistence/src/main/java/com/skedgo/routepersistence/RouteStore.kt @@ -129,6 +129,9 @@ open class RouteStore(private val databaseHelper: SQLiteOpenHelper, private val values.put(COL_QUERY_IS_LEAVE_AFTER, if (trip.queryIsLeaveAfter()) 1 else 0) values.put(COL_SUBSCRIBE_URL, trip.subscribeURL) values.put(COL_UNSUBSCRIBE_URL, trip.unsubscribeURL) + values.put(COL_AVAILABILITY, trip.availabilityString) + values.put(COL_AVAILABILITY_INFO, trip.availabilityInfo) + values.put(COL_MONEY_USD_COST, trip.moneyUsdCost) database.beginTransaction() try { database.update(TABLE_TRIPS, values, "$COL_UUID = ?", arrayOf(oldTripUuid)) @@ -257,6 +260,9 @@ open class RouteStore(private val databaseHelper: SQLiteOpenHelper, private val val isNotifable = groupCursor.getInt(groupCursor.getColumnIndex(COL_IS_NOTIFIABLE)) == 1 val subscribeUrl = tripCursor.getString(tripCursor.getColumnIndex(COL_SUBSCRIBE_URL)) val unSubscribeUrl = tripCursor.getString(tripCursor.getColumnIndex(COL_UNSUBSCRIBE_URL)) + val availability = tripCursor.getString(tripCursor.getColumnIndex(COL_AVAILABILITY)) + val availabilityInfo = tripCursor.getString(tripCursor.getColumnIndex(COL_AVAILABILITY_INFO)) + val moneyUSDCost = tripCursor.getFloat(tripCursor.getColumnIndex(COL_MONEY_USD_COST)) val trip = Trip() trip.id = id @@ -283,6 +289,9 @@ open class RouteStore(private val databaseHelper: SQLiteOpenHelper, private val trip.isFavourite(isNotifable) trip.subscribeURL = subscribeUrl trip.unsubscribeURL = unSubscribeUrl + trip.setAvailability(availability) + trip.availabilityInfo = availabilityInfo + trip.moneyUsdCost = moneyUSDCost return trip } @@ -401,6 +410,9 @@ open class RouteStore(private val databaseHelper: SQLiteOpenHelper, private val values.put(COL_QUERY_TIME, trip.queryTime) values.put(COL_SUBSCRIBE_URL, trip.subscribeURL) values.put(COL_UNSUBSCRIBE_URL, trip.unsubscribeURL) + values.put(COL_AVAILABILITY, trip.availabilityString) + values.put(COL_AVAILABILITY_INFO, trip.availabilityInfo) + values.put(COL_MONEY_USD_COST, trip.moneyUsdCost) database.insertWithOnConflict(TABLE_TRIPS, null, values, SQLiteDatabase.CONFLICT_REPLACE) } diff --git a/rxlifecyclecomponents/build.gradle b/rxlifecyclecomponents/build.gradle index 65c1a676..9bf5f097 100644 --- a/rxlifecyclecomponents/build.gradle +++ b/rxlifecyclecomponents/build.gradle @@ -26,6 +26,9 @@ android { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } + staging { + debuggable true + } } compileOptions { diff --git a/sqliteutils/build.gradle b/sqliteutils/build.gradle index 14aa6c7f..767b1503 100644 --- a/sqliteutils/build.gradle +++ b/sqliteutils/build.gradle @@ -25,6 +25,9 @@ android { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } + staging { + debuggable true + } } publishing { diff --git a/trip-kit-booking-ui/build.gradle b/trip-kit-booking-ui/build.gradle index d39c4b98..d341716a 100644 --- a/trip-kit-booking-ui/build.gradle +++ b/trip-kit-booking-ui/build.gradle @@ -16,6 +16,12 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } + buildTypes { + staging { + debuggable true + } + } + buildFeatures { viewBinding true dataBinding true @@ -62,10 +68,15 @@ dependencies { } debugImplementation project(':TripKitAndroid') + stagingImplementation project(':TripKitAndroid') releaseImplementation project(':TripKitAndroid') + debugImplementation project(':CommonCoreLegacy') + stagingImplementation project(':CommonCoreLegacy') releaseImplementation project(':CommonCoreLegacy') + debugImplementation project(':trip-kit-booking') + stagingImplementation project(':trip-kit-booking') releaseImplementation project(':trip-kit-booking') implementation 'uk.co.chrisjenx:calligraphy:2.3.0' @@ -80,7 +91,9 @@ dependencies { implementation libs.bindingCollectionAdapterRecyclerView debugImplementation project(':rxlifecyclecomponents') + stagingImplementation project(':rxlifecyclecomponents') releaseImplementation project(':rxlifecyclecomponents') + implementation libs.kotlin implementation libs.gson implementation libs.rxjava2 diff --git a/trip-kit-booking/build.gradle b/trip-kit-booking/build.gradle index 7069ceba..62b9681c 100644 --- a/trip-kit-booking/build.gradle +++ b/trip-kit-booking/build.gradle @@ -15,6 +15,18 @@ android { minSdkVersion versions.proMinSdkVersion targetSdkVersion versions.targetSdkVersion testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + + javaCompileOptions { + annotationProcessorOptions { + arguments += ["room.schemaLocation": "$projectDir/schemas".toString()] + } + } + } + + buildTypes { + staging { + debuggable true + } } packagingOptions { @@ -54,13 +66,17 @@ dependencies { testImplementation 'com.github.thuytrinh:MockWebServerRule:v1.0' debugImplementation project(':TripKitAndroid') + stagingImplementation project(':TripKitAndroid') releaseImplementation project(':TripKitAndroid') + debugImplementation project(':TripKitDomain') + stagingImplementation project(':TripKitDomain') releaseImplementation project(':TripKitDomain') + debugImplementation project(':CommonCoreLegacy') + stagingImplementation project(':CommonCoreLegacy') releaseImplementation project(':CommonCoreLegacy') - implementation libs.dagger implementation libs.rxjava2 implementation libs.rxAndroid2 @@ -71,8 +87,17 @@ dependencies { implementation libs.retrofitConverterGson implementation libs.retrofitAdapterRxJava implementation libs.kotlin + implementation libs.coroutinesCore + implementation libs.coroutinesAndroid + implementation libs.coroutinesRx kapt libs.daggerCompiler + implementation libs.roomRuntime + annotationProcessor libs.roomCompiler + kapt libs.roomCompiler + implementation libs.roomRxjava2 + implementation libs.roomKtx + kapt libs.value compileOnly libs.valueAnnotations compileOnly libs.builderAnnotations diff --git a/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/BookingModule.java b/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/BookingModule.java index f777b291..f7e4e524 100644 --- a/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/BookingModule.java +++ b/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/BookingModule.java @@ -7,6 +7,7 @@ import com.skedgo.tripkit.booking.viewmodel.AuthenticationViewModelImpl; import com.skedgo.tripkit.booking.viewmodel.BookingViewModel; import com.skedgo.tripkit.booking.viewmodel.BookingViewModelImpl; +import com.skedgo.tripkit.configuration.ServerManager; import dagger.Module; import dagger.Provides; @@ -15,7 +16,6 @@ import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; import retrofit2.converter.gson.GsonConverterFactory; import io.reactivex.schedulers.Schedulers; -import com.skedgo.tripkit.configuration.Server; @Module public class BookingModule { @@ -25,7 +25,7 @@ public class BookingModule { .create(); return new Retrofit.Builder() /* This base url is ignored as the api relies on @Url. */ - .baseUrl(Server.ApiTripGo.getValue()) + .baseUrl(ServerManager.INSTANCE.getConfiguration().getApiTripGoUrl()) .addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io())) .addConverterFactory(GsonConverterFactory.create(gson)) .client(httpClient) @@ -39,7 +39,7 @@ public class BookingModule { .create(); return new Retrofit.Builder() /* This base url is ignored as the api relies on @Url. */ - .baseUrl(Server.ApiTripGo.getValue()) + .baseUrl(ServerManager.INSTANCE.getConfiguration().getApiTripGoUrl()) .addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io())) .addConverterFactory(GsonConverterFactory.create(gson)) .client(httpClient) @@ -55,7 +55,7 @@ public class BookingModule { .create(); return new Retrofit.Builder() /* This base url is ignored as the api relies on @Url. */ - .baseUrl(Server.ApiTripGo.getValue()) + .baseUrl(ServerManager.INSTANCE.getConfiguration().getApiTripGoUrl()) .addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io())) .addConverterFactory(GsonConverterFactory.create(gson)) .client(httpClient) diff --git a/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/ConfirmPaymentUpdateResponse.kt b/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/ConfirmPaymentUpdateResponse.kt index 2ec82c71..6f9ce171 100644 --- a/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/ConfirmPaymentUpdateResponse.kt +++ b/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/ConfirmPaymentUpdateResponse.kt @@ -1,8 +1,18 @@ package com.skedgo.tripkit.booking.quickbooking +import com.google.gson.annotations.SerializedName + data class ConfirmPaymentUpdateResponse( val updateURL: String? = null, val paymentIntentID: String? = null, val clientSecret: String? = null, val url: String? = null, + val warning: String? = null, +) + +data class ConfirmPaymentError( + val errorCode: Int, + val title: String, + val error: String, + @SerializedName("usererror") val userError: Boolean ) \ No newline at end of file diff --git a/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/QuickBookRequest.kt b/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/QuickBookRequest.kt index 784a44fa..da7fad92 100644 --- a/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/QuickBookRequest.kt +++ b/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/QuickBookRequest.kt @@ -2,5 +2,5 @@ package com.skedgo.tripkit.booking.quickbooking data class QuickBookRequest( val input: List, - val tickets: List + val fares: List ) \ No newline at end of file diff --git a/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/QuickBookResponse.kt b/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/QuickBookResponse.kt index 7a5543c2..058b5e9f 100644 --- a/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/QuickBookResponse.kt +++ b/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/QuickBookResponse.kt @@ -123,7 +123,7 @@ data class Review( val origin: Origin, val price: Double, val provider: Provider, - val tickets: List? + val fares: List? ) { fun getPriceWithCurrency(): String { val symbol = if (currency == "USD") "$" else currency diff --git a/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/QuickBooking.kt b/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/QuickBooking.kt index 931000eb..27d27524 100644 --- a/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/QuickBooking.kt +++ b/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/QuickBooking.kt @@ -1,9 +1,12 @@ package com.skedgo.tripkit.booking.quickbooking +import com.google.gson.Gson import com.google.gson.annotations.SerializedName import com.skedgo.tripkit.common.model.BookingConfirmationInputNew import com.skedgo.tripkit.common.model.BookingConfirmationInputOptions import com.skedgo.tripkit.common.model.BookingConfirmationNotes +import com.skedgo.tripkit.data.database.booking.ticket.TicketEntity +import com.skedgo.tripkit.extensions.fromJson data class QuickBooking( val bookingTitle: String, @@ -14,7 +17,7 @@ data class QuickBooking( val input: List, val title: String, val tripUpdateURL: String, - @SerializedName("fares") val tickets: List, + @SerializedName("fares") val fares: List? = emptyList(), val billingEnabled: Boolean, val riders: List ) @@ -46,7 +49,8 @@ data class Input( var value: String?, var values: List?, val minValue: Int, - val maxValue: Int + val maxValue: Int, + var urlValue: String? ) { companion object { fun parse(bookingConfirmationInput: BookingConfirmationInputNew): Input = @@ -62,7 +66,8 @@ data class Input( bookingConfirmationInput.value(), bookingConfirmationInput.values(), 0, - 0 + 0, + null ) } @@ -104,12 +109,14 @@ data class Input( } } -data class Ticket( +// Renamed from "Ticket" to "Fare" since as per checking in the response, it's +// identified as fare for the ticket. +data class Fare( val id: String, - val currency: String, + val currency: String = "", val description: String, val name: String, - val price: Double, + val price: Double = 0.0, var value: Long?, val max: Int? = null, val riders: List, @@ -123,27 +130,84 @@ data class Rider( val description: String ) -data class PurchasedTicket( +// Renamed from "PurchasedTicket" to "Ticket" since as per checking in the response, it's +// identified as a ticket. +data class Ticket( val id: String, val validFromTimestamp: String?, val validUntilTimestamp: String?, val ticketURL: String?, val activateURL: String?, - val ticketExpirationTimestamp: String, + val ticketExpirationTimestamp: String?, val purchasedTimestamp: String, - val fare: Ticket, - val status: String, - val actions: List -) + val fare: Fare, + val status: String?, + val actions: List? = emptyList(), + val qrCode: String? = null +) { + + companion object { + fun TicketEntity.toTicket(): Ticket { + val gson = Gson() + return Ticket( + id = id, + validFromTimestamp = validFromTimestamp, + validUntilTimestamp = validUntilTimestamp, + ticketURL = ticketURL, + activateURL = activateURL, + ticketExpirationTimestamp = ticketExpirationTimestamp, + purchasedTimestamp = purchasedTimestamp, + fare = gson.fromJson(fareJson), + status = status, + actions = ticketActionsJson?.let { gson.fromJson(it) } ?: emptyList() , + qrCode = qrCode + ) + } + + fun Ticket.toEntity(): TicketEntity { + val gson = Gson() + return TicketEntity( + id = id, + validFromTimestamp = validFromTimestamp, + validUntilTimestamp = validUntilTimestamp, + ticketURL = ticketURL, + activateURL = activateURL, + ticketExpirationTimestamp = ticketExpirationTimestamp, + purchasedTimestamp = purchasedTimestamp, + fareJson = gson.toJson(fare), + status = status, + ticketActionsJson = if(actions.isNullOrEmpty()) "" else gson.toJson(actions), + qrCode = qrCode + ) + } + } + + fun getTicketStatus(): String { + return qrCode?.let { "Show QR Code" } ?: status.orEmpty() + } + + fun isTypeQRCode() = !qrCode.isNullOrBlank() + + enum class Status(val type: String) { + INACTIVE("inactive"), + UNACTIVATED("unactivated"); + + companion object { + fun getStatus(type: String): Status? { + return values().firstOrNull { it.type.equals(type, ignoreCase = true) } + } + } + } +} -data class PurchasedTicketAction( +data class TicketAction( val title: String, val type: String, - val confirmation: PurchasedTicketConfirmation, + val confirmation: TicketConfirmation, val internalURL: String ) -data class PurchasedTicketConfirmation( +data class TicketConfirmation( val message: String, val confirmActionTitle: String, val abortActionTitle: String diff --git a/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/QuickBookingApi.kt b/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/QuickBookingApi.kt index 60fc9585..93e23968 100644 --- a/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/QuickBookingApi.kt +++ b/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/QuickBookingApi.kt @@ -7,7 +7,6 @@ import okhttp3.ResponseBody import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST -import retrofit2.http.Path import retrofit2.http.Query import retrofit2.http.Url @@ -39,7 +38,10 @@ interface QuickBookingApi { fun getTicketHTML(@Url url: String): Single @GET("ticket") - fun getTickets(@Query("valid") valid: Boolean = true): Single> + fun getTickets(@Query("valid") valid: Boolean = true): Single> + + @GET("ticket") + suspend fun getTicketsAsync(@Query("valid") valid: Boolean = true): List @POST fun activateTicket(@Url url: String): Single diff --git a/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/QuickBookingPaymentIntent.kt b/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/QuickBookingPaymentIntent.kt index a95a5a3a..8c2af108 100644 --- a/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/QuickBookingPaymentIntent.kt +++ b/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/QuickBookingPaymentIntent.kt @@ -6,5 +6,6 @@ data class QuickBookingPaymentIntent( val url: String, val stripeApiKey: String? = null, val updateURL: String? = null, - val type: String? = null + val type: String? = null, + val internalPaymentIntentFetched: Boolean = false ) \ No newline at end of file diff --git a/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/QuickBookingRepository.kt b/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/QuickBookingRepository.kt new file mode 100644 index 00000000..6af16652 --- /dev/null +++ b/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/QuickBookingRepository.kt @@ -0,0 +1,66 @@ +package com.skedgo.tripkit.booking.quickbooking + +import com.skedgo.tripkit.booking.quickbooking.Ticket.Companion.toEntity +import com.skedgo.tripkit.booking.quickbooking.Ticket.Companion.toTicket +import com.skedgo.tripkit.data.database.TripKitDatabase +import com.skedgo.tripkit.utils.async.Result +import io.reactivex.Single +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.onStart +import javax.inject.Inject + +class QuickBookingRepository @Inject constructor( + val quickBookingService: QuickBookingService, + tripKitDatabase: TripKitDatabase +) { + private val ticketDao = tripKitDatabase.ticketDao() + suspend fun getTickets(): Flow>> = flow { + emit(Result.loading()) + + try { + val ticketsFromApi = quickBookingService.getTicketsAsync(true) + val ticketEntities = ticketsFromApi.map { ticket -> + ticket.toEntity() + } + ticketDao.insertTickets(ticketEntities) + emit(Result.success(ticketsFromApi)) + } catch (e: Exception) { + // If the API call fails, fetch tickets from the local database + val tickets = ticketDao.getAllTickets().map { entity -> entity.toTicket() } + if (tickets.isNotEmpty()) { + emit(Result.success(tickets)) + } else { + throw e + } + } + }.onStart { + // Emit loading state at the start + emit(Result.loading()) + }.catch { e -> + e.printStackTrace() + // Emit error state in case of exceptions + emit(Result.error(e.message ?: "Unknown error")) + }.flowOn(Dispatchers.IO) + + fun getTicketsRx(): Single> { + return quickBookingService.getTickets(true) + .flatMap { tickets -> + if (tickets.isNotEmpty()) { + // insertTicketsRx() saves all tickets and returns Completable + ticketDao.insertTicketsRx(tickets.map { it.toEntity() }) + .andThen(Single.just(tickets)) + } else { + Single.just(tickets) + } + }.onErrorResumeNext { + // In case of an error, fetch the tickets from the database + ticketDao.getAllTicketsRx().map { tickets -> + tickets.map { it.toTicket() } + }.onErrorResumeNext { Single.just(emptyList()) } + } + } +} \ No newline at end of file diff --git a/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/QuickBookingService.kt b/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/QuickBookingService.kt index 2679baad..616f95c3 100644 --- a/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/QuickBookingService.kt +++ b/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/QuickBookingService.kt @@ -4,7 +4,6 @@ import com.skedgo.tripkit.booking.mybookings.PaymentRequest import com.skedgo.tripkit.routing.RoutingResponse import io.reactivex.Single import okhttp3.ResponseBody -import retrofit2.http.Body interface QuickBookingService { fun getQuickBooking(url: String): Single> @@ -23,7 +22,8 @@ interface QuickBookingService { request: ConfirmPaymentUpdateRequest ): Single fun getTicketHTML(url: String): Single - fun getTickets(valid: Boolean): Single> + fun getTickets(valid: Boolean): Single> + suspend fun getTicketsAsync(valid: Boolean): List fun activateTicket(url: String): Single class QuickBookingServiceImpl(private val api: QuickBookingApi) : QuickBookingService { @@ -58,9 +58,12 @@ interface QuickBookingService { override fun getTicketHTML(url: String): Single = api.getTicketHTML(url) - override fun getTickets(valid: Boolean): Single> = + override fun getTickets(valid: Boolean): Single> = api.getTickets(valid) + override suspend fun getTicketsAsync(valid: Boolean): List = + api.getTicketsAsync(valid) + override fun activateTicket(url: String): Single = api.activateTicket(url) diff --git a/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/QuickBookingType.kt b/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/QuickBookingType.kt index 0ad2e860..5779135d 100644 --- a/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/QuickBookingType.kt +++ b/trip-kit-booking/src/main/java/com/skedgo/tripkit/booking/quickbooking/QuickBookingType.kt @@ -4,11 +4,12 @@ import androidx.annotation.StringDef @Retention(AnnotationRetention.RUNTIME) @StringDef( - QuickBookingType.MULTIPLE_CHOICE, - QuickBookingType.SINGLE_CHOICE, - QuickBookingType.LONG_TEXT, - QuickBookingType.RETURN_TRIP, - QuickBookingType.NUMBER + QuickBookingType.MULTIPLE_CHOICE, + QuickBookingType.SINGLE_CHOICE, + QuickBookingType.LONG_TEXT, + QuickBookingType.RETURN_TRIP, + QuickBookingType.NUMBER, + QuickBookingType.TERMS ) annotation class QuickBookingType { companion object { @@ -17,6 +18,7 @@ annotation class QuickBookingType { const val LONG_TEXT = "LONG_TEXT" const val RETURN_TRIP = "RETURN_TRIP" const val NUMBER = "NUMBER" + const val TERMS = "TERMS" } } @@ -28,9 +30,11 @@ fun String.getDefaultValueByType( QuickBookingType.LONG_TEXT -> { String.format("Tap to %s", title) } + QuickBookingType.NUMBER -> { values?.firstOrNull() ?: "0" } + else -> { "Tap to make selections" }