diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj index 2355a5e5c..b4ee2358c 100644 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -68,7 +68,7 @@ 8C1587042C76524600AB5036 /* AppVariantExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C1587032C76524600AB5036 /* AppVariantExtension.swift */; }; 8C2106F52CA6159600228287 /* ErrorBannerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C2106F42CA6159600228287 /* ErrorBannerTests.swift */; }; 8C2752EF2C6D5CA900F0B0A5 /* TripDetailsAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C2752EE2C6D5CA900F0B0A5 /* TripDetailsAnalytics.swift */; }; - 8C3D64102BE19DBA0026DD53 /* SettingsPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C3D640F2BE19DBA0026DD53 /* SettingsPage.swift */; }; + 8C3D64102BE19DBA0026DD53 /* MorePage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C3D640F2BE19DBA0026DD53 /* MorePage.swift */; }; 8C5054582BB5EB6C00C6A51C /* StopDetailsPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C5054572BB5EB6C00C6A51C /* StopDetailsPage.swift */; }; 8C5054A02CA47C3C00137CFE /* ErrorBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C50549F2CA47C3C00137CFE /* ErrorBanner.swift */; }; 8C5F47662C40842200FB71DA /* TripDetailsStopViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C5F47652C40842200FB71DA /* TripDetailsStopViewTests.swift */; }; @@ -94,7 +94,7 @@ 8CCBF2CD2CC162E900B29806 /* Shimmer in Frameworks */ = {isa = PBXBuildFile; productRef = 8CCBF2CC2CC162E900B29806 /* Shimmer */; }; 8CD58ECD2BEC115B0004E031 /* TripDetailsPageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CD58ECC2BEC115B0004E031 /* TripDetailsPageTests.swift */; }; 8CD79F112C46F6E200AFF17D /* MapboxBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CD79F102C46F6E200AFF17D /* MapboxBridge.swift */; }; - 8CD81A242BE55B2C0090585F /* SettingsPageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CD81A232BE55B2C0090585F /* SettingsPageTests.swift */; }; + 8CD81A242BE55B2C0090585F /* MorePageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CD81A232BE55B2C0090585F /* MorePageTests.swift */; }; 8CDF2C342BE9357E007FC912 /* OptionalNavigationLinkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CDF2C332BE9357E007FC912 /* OptionalNavigationLinkTests.swift */; }; 8CE0140E2BBDB7C300918FAE /* RoutePillSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CE0140D2BBDB7C300918FAE /* RoutePillSection.swift */; }; 8CE014102BBDB8DC00918FAE /* StopDetailsRoutesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CE0140F2BBDB8DC00918FAE /* StopDetailsRoutesView.swift */; }; @@ -136,6 +136,8 @@ 9A60E8E72B8501BD008A8D5C /* RoutePillTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A60E8E62B8501BD008A8D5C /* RoutePillTests.swift */; }; 9A635D1F2B99103200A43C51 /* EmptyWhenModifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A635D1E2B99103200A43C51 /* EmptyWhenModifierTests.swift */; }; 9A6A51EF2C652BB100E3AC13 /* AlertActivePeriodFormattingExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A6A51EE2C652BB100E3AC13 /* AlertActivePeriodFormattingExtension.swift */; }; + 9A6ACA2B2CD0096A00299AF5 /* MoreSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A6ACA2A2CD0096A00299AF5 /* MoreSectionView.swift */; }; + 9A6ACA2D2CD00F8200299AF5 /* MoreItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A6ACA2C2CD00F8200299AF5 /* MoreItem.swift */; }; 9A6DDF912B976FDF004D141A /* EmptyWhenModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A6DDF902B976FDF004D141A /* EmptyWhenModifier.swift */; }; 9A6FA0232BC70D0B0067769C /* InspectionEmissary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A6FA0222BC70D0A0067769C /* InspectionEmissary.swift */; }; 9A6FA0252BC714360067769C /* HomeMapViewTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A6FA0242BC714360067769C /* HomeMapViewTest.swift */; }; @@ -145,6 +147,9 @@ 9A76022F2C546CE300B69A35 /* StopDetailsAlertHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A76022E2C546CE300B69A35 /* StopDetailsAlertHeader.swift */; }; 9A7AC5C22BE139FB0036126F /* AnnotatedMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7AC5C12BE139FB0036126F /* AnnotatedMap.swift */; }; 9A7B7CA92B98E41B0045214F /* NonNilModifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7B7CA82B98E41B0045214F /* NonNilModifierTests.swift */; }; + 9A7F12132CCB185D0042B0F1 /* TabLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7F12122CCB185D0042B0F1 /* TabLabel.swift */; }; + 9A7F12172CCFEFAA0042B0F1 /* MoreLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7F12162CCFEFAA0042B0F1 /* MoreLink.swift */; }; + 9A7F12192CCFF2D20042B0F1 /* MorePhone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7F12182CCFF2D20042B0F1 /* MorePhone.swift */; }; 9A887D572B683103006F5B80 /* SearchResultsContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A887D562B683103006F5B80 /* SearchResultsContainer.swift */; }; 9A887D592B698EF1006F5B80 /* SearchResultViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A887D582B698EF1006F5B80 /* SearchResultViewTests.swift */; }; 9A88AAC12BD0680C00A5BF88 /* StopDetailsFilterPills.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A88AAC02BD0680C00A5BF88 /* StopDetailsFilterPills.swift */; }; @@ -198,7 +203,7 @@ EDDE72EF2CBF19EF0000180D /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDDE72EE2CBF19EF0000180D /* ArrayExtension.swift */; }; EDE92FA62C3DD675007AD2F6 /* ScreenTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDE92FA52C3DD675007AD2F6 /* ScreenTracker.swift */; }; EDE92FA82C408A32007AD2F6 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDE92FA72C408A32007AD2F6 /* SettingsViewModel.swift */; }; - EDE92FAA2C408A50007AD2F6 /* SettingsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDE92FA92C408A50007AD2F6 /* SettingsSection.swift */; }; + EDE92FAA2C408A50007AD2F6 /* MoreSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDE92FA92C408A50007AD2F6 /* MoreSection.swift */; }; EDE92FAC2C408A6A007AD2F6 /* Setting+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDE92FAB2C408A6A007AD2F6 /* Setting+Convenience.swift */; }; /* End PBXBuildFile section */ @@ -313,7 +318,7 @@ 8C2752EE2C6D5CA900F0B0A5 /* TripDetailsAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TripDetailsAnalytics.swift; sourceTree = ""; }; 8C304C652B69C0C300263886 /* .swift-version */ = {isa = PBXFileReference; lastKnownFileType = text; path = ".swift-version"; sourceTree = ""; }; 8C349BB72B754F2600AC7FFB /* 10 Park Plaza.gpx */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = "10 Park Plaza.gpx"; sourceTree = ""; }; - 8C3D640F2BE19DBA0026DD53 /* SettingsPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsPage.swift; sourceTree = ""; }; + 8C3D640F2BE19DBA0026DD53 /* MorePage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MorePage.swift; sourceTree = ""; }; 8C42F06F2B890BC800F9A77B /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = ""; }; 8C5054572BB5EB6C00C6A51C /* StopDetailsPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StopDetailsPage.swift; sourceTree = ""; }; 8C50549F2CA47C3C00137CFE /* ErrorBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorBanner.swift; sourceTree = ""; }; @@ -339,7 +344,7 @@ 8CC1BB3F2B59D1F6005386FE /* LocationDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationDataManager.swift; sourceTree = ""; }; 8CD58ECC2BEC115B0004E031 /* TripDetailsPageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TripDetailsPageTests.swift; sourceTree = ""; }; 8CD79F102C46F6E200AFF17D /* MapboxBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapboxBridge.swift; sourceTree = ""; }; - 8CD81A232BE55B2C0090585F /* SettingsPageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsPageTests.swift; sourceTree = ""; }; + 8CD81A232BE55B2C0090585F /* MorePageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MorePageTests.swift; sourceTree = ""; }; 8CDF2C332BE9357E007FC912 /* OptionalNavigationLinkTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionalNavigationLinkTests.swift; sourceTree = ""; }; 8CE0140D2BBDB7C300918FAE /* RoutePillSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoutePillSection.swift; sourceTree = ""; }; 8CE0140F2BBDB8DC00918FAE /* StopDetailsRoutesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StopDetailsRoutesView.swift; sourceTree = ""; }; @@ -382,6 +387,8 @@ 9A60E8E62B8501BD008A8D5C /* RoutePillTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoutePillTests.swift; sourceTree = ""; }; 9A635D1E2B99103200A43C51 /* EmptyWhenModifierTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyWhenModifierTests.swift; sourceTree = ""; }; 9A6A51EE2C652BB100E3AC13 /* AlertActivePeriodFormattingExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertActivePeriodFormattingExtension.swift; sourceTree = ""; }; + 9A6ACA2A2CD0096A00299AF5 /* MoreSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoreSectionView.swift; sourceTree = ""; }; + 9A6ACA2C2CD00F8200299AF5 /* MoreItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoreItem.swift; sourceTree = ""; }; 9A6DDF902B976FDF004D141A /* EmptyWhenModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyWhenModifier.swift; sourceTree = ""; }; 9A6FA0222BC70D0A0067769C /* InspectionEmissary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectionEmissary.swift; sourceTree = ""; }; 9A6FA0242BC714360067769C /* HomeMapViewTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeMapViewTest.swift; sourceTree = ""; }; @@ -391,6 +398,9 @@ 9A76022E2C546CE300B69A35 /* StopDetailsAlertHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StopDetailsAlertHeader.swift; sourceTree = ""; }; 9A7AC5C12BE139FB0036126F /* AnnotatedMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnnotatedMap.swift; sourceTree = ""; }; 9A7B7CA82B98E41B0045214F /* NonNilModifierTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonNilModifierTests.swift; sourceTree = ""; }; + 9A7F12122CCB185D0042B0F1 /* TabLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabLabel.swift; sourceTree = ""; }; + 9A7F12162CCFEFAA0042B0F1 /* MoreLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoreLink.swift; sourceTree = ""; }; + 9A7F12182CCFF2D20042B0F1 /* MorePhone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MorePhone.swift; sourceTree = ""; }; 9A887D562B683103006F5B80 /* SearchResultsContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultsContainer.swift; sourceTree = ""; }; 9A887D582B698EF1006F5B80 /* SearchResultViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultViewTests.swift; sourceTree = ""; }; 9A88AAC02BD0680C00A5BF88 /* StopDetailsFilterPills.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StopDetailsFilterPills.swift; sourceTree = ""; }; @@ -446,7 +456,7 @@ EDDE72EE2CBF19EF0000180D /* ArrayExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayExtension.swift; sourceTree = ""; }; EDE92FA52C3DD675007AD2F6 /* ScreenTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenTracker.swift; sourceTree = ""; }; EDE92FA72C408A32007AD2F6 /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; - EDE92FA92C408A50007AD2F6 /* SettingsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSection.swift; sourceTree = ""; }; + EDE92FA92C408A50007AD2F6 /* MoreSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoreSection.swift; sourceTree = ""; }; EDE92FAB2C408A6A007AD2F6 /* Setting+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Setting+Convenience.swift"; sourceTree = ""; }; F35CF19A11F0511AA7B2E080 /* Pods-iosApp.stagingdebug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.stagingdebug.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.stagingdebug.xcconfig"; sourceTree = ""; }; F71252D2B68FF131F8E6BDE2 /* Pods-iosAppTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosAppTests.release.xcconfig"; path = "Target Support Files/Pods-iosAppTests/Pods-iosAppTests.release.xcconfig"; sourceTree = ""; }; @@ -696,14 +706,18 @@ path = Pods; sourceTree = ""; }; - 8C3D640E2BE19DA60026DD53 /* Settings */ = { + 8C3D640E2BE19DA60026DD53 /* More */ = { isa = PBXGroup; children = ( + 9A6ACA2C2CD00F8200299AF5 /* MoreItem.swift */, + 9A7F12162CCFEFAA0042B0F1 /* MoreLink.swift */, + 8C3D640F2BE19DBA0026DD53 /* MorePage.swift */, + 9A7F12182CCFF2D20042B0F1 /* MorePhone.swift */, + EDE92FA92C408A50007AD2F6 /* MoreSection.swift */, + 9A6ACA2A2CD0096A00299AF5 /* MoreSectionView.swift */, EDE92FAB2C408A6A007AD2F6 /* Setting+Convenience.swift */, - 8C3D640F2BE19DBA0026DD53 /* SettingsPage.swift */, - EDE92FA92C408A50007AD2F6 /* SettingsSection.swift */, ); - path = Settings; + path = More; sourceTree = ""; }; 8C5054562BB5EB5000C6A51C /* StopDetails */ = { @@ -763,7 +777,7 @@ 8CD81A222BE55B150090585F /* Settings */ = { isa = PBXGroup; children = ( - 8CD81A232BE55B2C0090585F /* SettingsPageTests.swift */, + 8CD81A232BE55B2C0090585F /* MorePageTests.swift */, ); path = Settings; sourceTree = ""; @@ -773,9 +787,9 @@ children = ( 9A9E3DC02C629190004AADC7 /* AlertDetails */, 9A2005C22B97B57E00F562E1 /* Map */, + 8C3D640E2BE19DA60026DD53 /* More */, 9A2005C12B97B56C00F562E1 /* NearbyTransit */, 9A2005C32B97B5A000F562E1 /* Search */, - 8C3D640E2BE19DA60026DD53 /* Settings */, 8C5054562BB5EB5000C6A51C /* StopDetails */, 8C7726132BE58FC50088D423 /* TripDetails */, ); @@ -864,6 +878,7 @@ 9A9E7DD02C2200F4000DA1FD /* TransitHeader.swift */, 9A2005CA2B97B68700F562E1 /* UpcomingTripView.swift */, 9A52A2B22CC3035F00CC01D6 /* WithRealtimeIndicator.swift */, + 9A7F12122CCB185D0042B0F1 /* TabLabel.swift */, ); path = ComponentViews; sourceTree = ""; @@ -1265,7 +1280,7 @@ 9A4F3B9F2C580C820040D6C9 /* StopDetailsAlertHeaderTests.swift in Sources */, 6EA231752C21CF2100789173 /* LoadingCardTests.swift in Sources */, 9A56407B2C6B83F2004761C0 /* PredictionTextTests.swift in Sources */, - 8CD81A242BE55B2C0090585F /* SettingsPageTests.swift in Sources */, + 8CD81A242BE55B2C0090585F /* MorePageTests.swift in Sources */, 6EF64C052C1B7FC6005F365D /* RouteHeaderTests.swift in Sources */, 6EF64C032C1B7C2E005F365D /* RouteCardTests.swift in Sources */, 8CB823D62BC5E85C002C87E0 /* SheetNavigationStackEntryTests.swift in Sources */, @@ -1380,6 +1395,7 @@ 9A74A2112BE2D71400E57102 /* AnnotationLabel.swift in Sources */, 6EEF219E2BF2927E0023A3E9 /* VehicleCardView.swift in Sources */, 9A9E7DCF2C2200C9000DA1FD /* LineHeader.swift in Sources */, + 9A7F12172CCFEFAA0042B0F1 /* MoreLink.swift in Sources */, 9A52B33A2C6E7C7D0028EEAB /* AlertDetailsAnalytics.swift in Sources */, 6E04E32F2BE95A8D006F8131 /* NearbyViewModel.swift in Sources */, 9A88AAC12BD0680C00A5BF88 /* StopDetailsFilterPills.swift in Sources */, @@ -1395,11 +1411,11 @@ 8CE1D6762CC9690400E3BDB1 /* LoadingPlaceholderModifier.swift in Sources */, 2152FB042600AC8F00CF470E /* IOSApp.swift in Sources */, 9A887D572B683103006F5B80 /* SearchResultsContainer.swift in Sources */, - EDE92FAA2C408A50007AD2F6 /* SettingsSection.swift in Sources */, + EDE92FAA2C408A50007AD2F6 /* MoreSection.swift in Sources */, 9A76022D2C5468C300B69A35 /* AlertIcon.swift in Sources */, 9A4DB7812CA5A23200E8755B /* TextFieldObserver.swift in Sources */, 8C815AF32BF5257B009BB51C /* TripDetailsStopListView.swift in Sources */, - 8C3D64102BE19DBA0026DD53 /* SettingsPage.swift in Sources */, + 8C3D64102BE19DBA0026DD53 /* MorePage.swift in Sources */, EDE92FA62C3DD675007AD2F6 /* ScreenTracker.swift in Sources */, 9A4850572CB56A1600F50EE7 /* RouteTypeExtension.swift in Sources */, EDE92FA82C408A32007AD2F6 /* SettingsViewModel.swift in Sources */, @@ -1411,6 +1427,7 @@ EDE92FAC2C408A6A007AD2F6 /* Setting+Convenience.swift in Sources */, 9A4DB77F2CA4A32800E8755B /* SearchOverlay.swift in Sources */, 8CC1BB402B59D1F6005386FE /* LocationDataManager.swift in Sources */, + 9A6ACA2B2CD0096A00299AF5 /* MoreSectionView.swift in Sources */, 6E3C8D7E2C11FDA80059C28C /* ActionButton.swift in Sources */, 9A3BAB742BEABADF00DAFDE2 /* ColorAssetExtension.swift in Sources */, 6E20278E2BD989630037554F /* DummyTestAppView.swift in Sources */, @@ -1427,9 +1444,11 @@ 9AB44A112B8FC43E00E8FFB3 /* ErrorCard.swift in Sources */, ED87C88D2C10E754009D398F /* PhoenixChannelError.swift in Sources */, 9A37F3072BACCCA5001714FE /* CoordinateExtension.swift in Sources */, + 9A7F12132CCB185D0042B0F1 /* TabLabel.swift in Sources */, 9A392C972CC0497C00DE1FBE /* ErrorBannerViewModel.swift in Sources */, 6EA2316F2C21BEB100789173 /* LoadingCard.swift in Sources */, 8C84D33E2B5AEE0200192C0A /* NearbyTransitView.swift in Sources */, + 9A7F12192CCFF2D20042B0F1 /* MorePhone.swift in Sources */, 6E55C17F2C247D480086A424 /* StopDetailsView.swift in Sources */, 9A15DA3D2BF51BA7003A861C /* HomeMapViewLayerExtension.swift in Sources */, ED93D6022CB6C09B003B1C12 /* LoadingResultsView.swift in Sources */, @@ -1450,6 +1469,7 @@ 6E973DA52C17384C00CBF341 /* SheetHeader.swift in Sources */, 9A9E7DD32C2203BE000DA1FD /* TransitCard.swift in Sources */, 8C6A48402BC09A2E0032A554 /* StopDetailsFilteredRouteView.swift in Sources */, + 9A6ACA2D2CD00F8200299AF5 /* MoreItem.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/iosApp/iosApp/ComponentViews/TabLabel.swift b/iosApp/iosApp/ComponentViews/TabLabel.swift new file mode 100644 index 000000000..675189c06 --- /dev/null +++ b/iosApp/iosApp/ComponentViews/TabLabel.swift @@ -0,0 +1,23 @@ +// +// TabLabel.swift +// iosApp +// +// Created by esimon on 10/24/24. +// Copyright © 2024 MBTA. All rights reserved. +// + +import SwiftUI + +struct TabLabel: View { + var title: String + var image: ImageResource + + init(_ title: String, image: ImageResource) { + self.title = title + self.image = image + } + + var body: some View { + Label(title: { Text(title) }, icon: { Image(image) }) + } +} diff --git a/iosApp/iosApp/ContentView.swift b/iosApp/iosApp/ContentView.swift index 6baa5c0f7..195ba3611 100644 --- a/iosApp/iosApp/ContentView.swift +++ b/iosApp/iosApp/ContentView.swift @@ -20,6 +20,7 @@ struct ContentView: View { @StateObject var nearbyVM = NearbyViewModel() @StateObject var mapVM = MapViewModel() @StateObject var searchVM = SearchViewModel() + @StateObject var settingsVM = SettingsViewModel() let transition: AnyTransition = .asymmetric(insertion: .push(from: .bottom), removal: .opacity) var screenTracker: ScreenTracker = AnalyticsProvider.shared @@ -28,7 +29,7 @@ struct ContentView: View { private enum SelectedTab: Hashable { case nearby - case settings + case more } @State private var selectedTab = SelectedTab.nearby @@ -47,14 +48,14 @@ struct ContentView: View { @ViewBuilder var contents: some View { - if selectedTab == .settings { + if selectedTab == .more { TabView(selection: $selectedTab) { nearbyTab .tag(SelectedTab.nearby) - .tabItem { Label("Nearby", systemImage: "mappin") } - SettingsPage() - .tag(SelectedTab.settings) - .tabItem { Label("Settings", systemImage: "gear") } + .tabItem { TabLabel("Nearby", image: .tabIconNearby) } + MorePage(viewModel: settingsVM) + .tag(SelectedTab.more) + .tabItem { TabLabel("More", image: .tabIconMore) } .onAppear { screenTracker.track(screen: .settings) } @@ -78,12 +79,12 @@ struct ContentView: View { viewportProvider: viewportProvider ) .tag(SelectedTab.nearby) - .tabItem { Label("Nearby", systemImage: "mappin") } + .tabItem { TabLabel("Nearby", image: .tabIconNearby) } // we want to show nothing in the sheet when the settings tab is open, // but an EmptyView here causes the tab to not be listed VStack {} - .tag(SelectedTab.settings) - .tabItem { Label("Settings", systemImage: "gear") } + .tag(SelectedTab.more) + .tabItem { TabLabel("More", image: .tabIconMore) } } } } @@ -134,6 +135,7 @@ struct ContentView: View { Task { await errorBannerVM.activate() } Task { await contentVM.loadConfig() } Task { await contentVM.loadHideMaps() } + Task { await settingsVM.getSections() } } .onChange(of: scenePhase) { newPhase in if newPhase == .active { diff --git a/iosApp/iosApp/Icons.xcassets/fa-phone.imageset/Contents.json b/iosApp/iosApp/Icons.xcassets/fa-phone.imageset/Contents.json new file mode 100644 index 000000000..e29c4ced6 --- /dev/null +++ b/iosApp/iosApp/Icons.xcassets/fa-phone.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images": [ + { + "filename": "fa-phone.svg", + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + }, + "properties": { + "template-rendering-intent": "template" + } +} diff --git a/iosApp/iosApp/Icons.xcassets/fa-phone.imageset/fa-phone.svg b/iosApp/iosApp/Icons.xcassets/fa-phone.imageset/fa-phone.svg new file mode 100644 index 000000000..6545b375a --- /dev/null +++ b/iosApp/iosApp/Icons.xcassets/fa-phone.imageset/fa-phone.svg @@ -0,0 +1 @@ + diff --git a/iosApp/iosApp/Icons.xcassets/mbta-logo.imageset/Contents.json b/iosApp/iosApp/Icons.xcassets/mbta-logo.imageset/Contents.json new file mode 100644 index 000000000..569b58682 --- /dev/null +++ b/iosApp/iosApp/Icons.xcassets/mbta-logo.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images": [ + { + "filename": "mbta-logo.svg", + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/iosApp/iosApp/Icons.xcassets/mbta-logo.imageset/mbta-logo.svg b/iosApp/iosApp/Icons.xcassets/mbta-logo.imageset/mbta-logo.svg new file mode 100644 index 000000000..41e1ee53f --- /dev/null +++ b/iosApp/iosApp/Icons.xcassets/mbta-logo.imageset/mbta-logo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/iosApp/iosApp/Icons.xcassets/tab-icon-more.imageset/Contents.json b/iosApp/iosApp/Icons.xcassets/tab-icon-more.imageset/Contents.json new file mode 100644 index 000000000..d634eeae8 --- /dev/null +++ b/iosApp/iosApp/Icons.xcassets/tab-icon-more.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images": [ + { + "filename": "tab-icon-more.svg", + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + }, + "properties": { + "template-rendering-intent": "template" + } +} diff --git a/iosApp/iosApp/Icons.xcassets/tab-icon-more.imageset/tab-icon-more.svg b/iosApp/iosApp/Icons.xcassets/tab-icon-more.imageset/tab-icon-more.svg new file mode 100644 index 000000000..d07d9ad63 --- /dev/null +++ b/iosApp/iosApp/Icons.xcassets/tab-icon-more.imageset/tab-icon-more.svg @@ -0,0 +1,3 @@ + + + diff --git a/iosApp/iosApp/Icons.xcassets/tab-icon-nearby.imageset/Contents.json b/iosApp/iosApp/Icons.xcassets/tab-icon-nearby.imageset/Contents.json new file mode 100644 index 000000000..ec892320a --- /dev/null +++ b/iosApp/iosApp/Icons.xcassets/tab-icon-nearby.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images": [ + { + "filename": "tab-icon-nearby.svg", + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + }, + "properties": { + "template-rendering-intent": "template" + } +} diff --git a/iosApp/iosApp/Icons.xcassets/tab-icon-nearby.imageset/tab-icon-nearby.svg b/iosApp/iosApp/Icons.xcassets/tab-icon-nearby.imageset/tab-icon-nearby.svg new file mode 100644 index 000000000..ee274c3d1 --- /dev/null +++ b/iosApp/iosApp/Icons.xcassets/tab-icon-nearby.imageset/tab-icon-nearby.svg @@ -0,0 +1,3 @@ + + + diff --git a/iosApp/iosApp/Localizable.xcstrings b/iosApp/iosApp/Localizable.xcstrings index 28dfe4b34..6a8f304ea 100644 --- a/iosApp/iosApp/Localizable.xcstrings +++ b/iosApp/iosApp/Localizable.xcstrings @@ -186,6 +186,9 @@ "Coast Guard Restriction" : { "comment" : "Possible alert cause" }, + "Commuter Rail and Ferry tickets" : { + "comment" : "Label for a More page link to the MBTA mTicket app" + }, "Congestion" : { "comment" : "Possible alert cause" }, @@ -221,6 +224,12 @@ }, "Error loading data" : { + }, + "Fare Information" : { + "comment" : "Label for a More page link to fare information on MBTA.com" + }, + "Feature Flags" : { + "comment" : "More page section header, only displayed in the developer app for enabling in-progress features" }, "ferries" : { "comment" : "ferries" @@ -246,12 +255,18 @@ "Full Description" : { "comment" : "Header for the details of a disruption" }, + "General MBTA Information & Support" : { + "comment" : "More page section header, includes user support resources" + }, "Hazmat Condition" : { "comment" : "Possible alert cause" }, "Heavy Ridership" : { "comment" : "Possible alert cause" }, + "Hide Maps" : { + "comment" : "A setting on the More page to remove the app component from the app" + }, "High Winds" : { "comment" : "Possible alert cause" }, @@ -300,9 +315,18 @@ }, "Location access state unknown" : { + }, + "Made with ♥ in Boston, for Boston" : { + }, "Maintenance" : { "comment" : "Possible alert cause" + }, + "Map Debug" : { + "comment" : "A setting on the More page to display map debug information (only visible for developers)" + }, + "MBTA Go" : { + }, "Mechanical Problem" : { "comment" : "Possible alert cause" @@ -310,6 +334,12 @@ "Medical Emergency" : { "comment" : "Possible alert cause" }, + "Monday through Friday: 6:30 AM - 8 PM" : { + "comment" : "Footnote under the More page support header, these are the hours for the support call center" + }, + "mTicket App" : { + "comment" : "Footnote underneath the \"Commuter Rail and Ferry tickets\" label on the More page link to the MBTA mTicket app" + }, "Next stop" : { "comment" : "Label for a vehicle's next stop. For example: Next stop Alewife" }, @@ -348,9 +378,18 @@ }, "Predictions unavailable" : { + }, + "Privacy Policy" : { + "comment" : "Label for a More page link to the MBTA.com privacy policy" }, "Recently Viewed" : { + }, + "Resources" : { + "comment" : "More page section header, includes links to MBTA.com and mTicket app" + }, + "Route Search" : { + "comment" : "A setting on the More page to display routes in search (only visible for developers)" }, "Routes" : { @@ -368,7 +407,7 @@ }, "Settings" : { - + "comment" : "More page section header, includes settings that the user can configure" }, "Severe Weather" : { "comment" : "Possible alert cause" @@ -421,6 +460,9 @@ "Technical Problem" : { "comment" : "Possible alert cause" }, + "Terms of Use" : { + "comment" : "Label for a More page link to the MBTA.com terms of use" + }, "This vehicle is completing another trip." : { }, @@ -445,6 +487,9 @@ "trains" : { "comment" : "trains" }, + "Trip Planner" : { + "comment" : "Label for a More page link to the MBTA.com trip planner" + }, "Unable to connect" : { }, @@ -479,6 +524,9 @@ "Updated: %@" : { "comment" : "Interpolated value is a timestamp for when displayed alert details were last changed" }, + "version %@" : { + "comment" : "Version number label on the More page" + }, "Weather" : { "comment" : "Possible alert cause" }, diff --git a/iosApp/iosApp/Pages/More/MoreItem.swift b/iosApp/iosApp/Pages/More/MoreItem.swift new file mode 100644 index 000000000..233113cc7 --- /dev/null +++ b/iosApp/iosApp/Pages/More/MoreItem.swift @@ -0,0 +1,48 @@ +// +// MoreItem.swift +// iosApp +// +// Created by esimon on 10/28/24. +// Copyright © 2024 MBTA. All rights reserved. +// + +import Foundation +import shared + +enum MoreItem: Identifiable, Equatable { + case toggle(setting: Setting) + case link(label: String, url: String, note: String? = nil) + case phone(label: String, phoneNumber: String) + + var id: String { + switch self { + case let .toggle(setting: setting): setting.key.name + case let .link(label: _, url: url, note: _): url + case let .phone(label: _, phoneNumber: number): number + } + } + + var label: String { + switch self { + case let .toggle(setting: setting): + switch setting.key { + case .hideMaps: NSLocalizedString( + "Hide Maps", + comment: "A setting on the More page to remove the app component from the app" + ) + case .searchRouteResults: NSLocalizedString( + "Route Search", + comment: "A setting on the More page to display routes in search (only visible for developers)" + ) + case .map: NSLocalizedString( + "Map Debug", + comment: "A setting on the More page to display map debug information (only visible for developers)" + ) + } + + case let .link(label: label, url: _, note: _): label + + case let .phone(label: label, phoneNumber: _): label + } + } +} diff --git a/iosApp/iosApp/Pages/More/MoreLink.swift b/iosApp/iosApp/Pages/More/MoreLink.swift new file mode 100644 index 000000000..b5ab0c9fe --- /dev/null +++ b/iosApp/iosApp/Pages/More/MoreLink.swift @@ -0,0 +1,46 @@ +// +// MoreLink.swift +// iosApp +// +// Created by esimon on 10/28/24. +// Copyright © 2024 MBTA. All rights reserved. +// + +import SwiftUI + +struct MoreLink: View { + var label: String + var url: String + var note: String? = nil + + @ScaledMetric + private var iconSize: CGFloat = 10.5 + + var body: some View { + Link(destination: URL(string: url)!) { + HStack(alignment: .center, spacing: 0) { + VStack(alignment: .leading, spacing: 0) { + Text(label) + .foregroundStyle(Color.text) + .font(Typography.body) + if let note { + Text(note) + .foregroundStyle(Color.text) + .font(Typography.footnote) + .opacity(0.6) + .padding(.top, 2) + } + } + Spacer() + Image(systemName: "arrow.up.right") + .resizable() + .frame(width: iconSize, height: iconSize, alignment: .center) + .foregroundStyle(Color.deemphasized) + .fontWeight(.bold) + } + } + .padding(.vertical, 10) + .padding(.horizontal, 16) + .frame(minHeight: 44) + } +} diff --git a/iosApp/iosApp/Pages/More/MorePage.swift b/iosApp/iosApp/Pages/More/MorePage.swift new file mode 100644 index 000000000..17bf1df98 --- /dev/null +++ b/iosApp/iosApp/Pages/More/MorePage.swift @@ -0,0 +1,65 @@ +// +// MorePage.swift +// iosApp +// +// Created by Horn, Melody on 2024-04-30. +// Copyright © 2024 MBTA. All rights reserved. +// + +import shared +import SwiftUI + +struct MorePage: View { + let inspection = Inspection() + + var viewModel = SettingsViewModel() + + var body: some View { + VStack(spacing: 0) { + HStack(alignment: .bottom) { + Text("MBTA Go") + .font(Typography.title1Bold) + Spacer() + let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String + if let version { + Text( + "version \(version)", + comment: "Version number label on the More page" + ) + .font(Typography.footnote) + } + } + .padding(.horizontal, 16) + .padding(.vertical, 10) + .background(Color.fill3) + Rectangle() + .fill(Color.halo) + .frame(height: 2) + .frame(maxWidth: .infinity) + + ScrollView { + VStack(alignment: .leading, spacing: 16) { + ForEach(viewModel.sections) { section in + MoreSectionView( + section: section, + toggleSetting: { key in viewModel.toggleSetting(key: key) } + ) + } + HStack(alignment: .center, spacing: 16) { + Image(.mbtaLogo).resizable().frame(width: 64, height: 64) + Text("Made with ♥ in Boston, for Boston").font(Typography.callout) + } + } + .padding(.horizontal, 16) + .padding(.vertical, 24) + } + } + .background(Color.fill1) + .onReceive(inspection.notice) { inspection.visit(self, $0) } + } +} + +#Preview { + MorePage() + .font(Typography.body) +} diff --git a/iosApp/iosApp/Pages/More/MorePhone.swift b/iosApp/iosApp/Pages/More/MorePhone.swift new file mode 100644 index 000000000..f49dd8a53 --- /dev/null +++ b/iosApp/iosApp/Pages/More/MorePhone.swift @@ -0,0 +1,42 @@ +// +// MorePhone.swift +// iosApp +// +// Created by esimon on 10/28/24. +// Copyright © 2024 MBTA. All rights reserved. +// + +import SwiftUI + +struct MorePhone: View { + var label: String + var phoneNumber: String + + @ScaledMetric + private var iconSize: CGFloat = 16 + + var body: some View { + HStack(alignment: .center, spacing: 0) { + VStack(alignment: .leading, spacing: 0) { + Text(label) + .foregroundStyle(Color.text) + .font(Typography.body) + } + Spacer() + Image(.faPhone) + .resizable() + .frame(width: iconSize, height: iconSize, alignment: .center) + .foregroundStyle(Color.deemphasized) + .fontWeight(.semibold) + } + .contentShape(Rectangle()) + .padding(.vertical, 10) + .padding(.horizontal, 16) + .frame(minHeight: 44) + .onTapGesture { + if let url = URL(string: "tel://\(phoneNumber)"), UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url) + } + } + } +} diff --git a/iosApp/iosApp/Pages/More/MoreSection.swift b/iosApp/iosApp/Pages/More/MoreSection.swift new file mode 100644 index 000000000..1f4a287e9 --- /dev/null +++ b/iosApp/iosApp/Pages/More/MoreSection.swift @@ -0,0 +1,64 @@ +// +// MoreSection.swift +// iosApp +// +// Created by Brandon Rodriguez on 7/11/24. +// Copyright © 2024 MBTA. All rights reserved. +// + +import Foundation +import shared + +struct MoreSection: Identifiable, Equatable { + enum Category: String { + case feedback + case resources + case settings + case featureFlags + case other + case support + } + + var id: Category + var items: [MoreItem] + + var name: String? { + switch id { + case .feedback: nil + case .resources: NSLocalizedString( + "Resources", + comment: "More page section header, includes links to MBTA.com and mTicket app" + ) + case .settings: NSLocalizedString( + "Settings", + comment: "More page section header, includes settings that the user can configure" + ) + case .featureFlags: NSLocalizedString( + "Feature Flags", + comment: "More page section header, only displayed in the developer app for enabling in-progress features" + ) + case .other: nil + case .support: NSLocalizedString( + "General MBTA Information & Support", + comment: "More page section header, includes user support resources" + ) + } + } + + var note: String? { + switch id { + case .support: NSLocalizedString( + "Monday through Friday: 6:30 AM - 8 PM", + comment: "Footnote under the More page support header, these are the hours for the support call center" + ) + default: nil + } + } + + var requiresStaging: Bool { + switch id { + case .featureFlags: true + default: false + } + } +} diff --git a/iosApp/iosApp/Pages/More/MoreSectionView.swift b/iosApp/iosApp/Pages/More/MoreSectionView.swift new file mode 100644 index 000000000..996c0c835 --- /dev/null +++ b/iosApp/iosApp/Pages/More/MoreSectionView.swift @@ -0,0 +1,69 @@ +// +// MoreSectionView.swift +// iosApp +// +// Created by esimon on 10/28/24. +// Copyright © 2024 MBTA. All rights reserved. +// + +import shared +import SwiftUI + +struct MoreSectionView: View { + var section: MoreSection + var toggleSetting: (Settings) -> Void + + var body: some View { + if !(section.requiresStaging && !(appVariant == .staging)) { + VStack(alignment: .leading, spacing: 8) { + if let name = section.name { + VStack(alignment: .leading, spacing: 0) { + Text(name) + .foregroundStyle(Color.text) + .font(Typography.subheadlineSemibold) + .listRowInsets(EdgeInsets()) + if let note = section.note { + Text(note) + .foregroundStyle(Color.text) + .font(Typography.footnote) + .padding(.top, 2) + } + } + .padding(.bottom, 2) + } + VStack(alignment: .leading, spacing: 0) { + ForEach(Array(section.items.enumerated()), id: \.element.id) { index, row in + VStack(alignment: .leading, spacing: 0) { + switch row { + case let .toggle(setting: toggle): + Toggle(isOn: Binding( + get: { toggle.isOn }, + set: { _ in toggleSetting(toggle.key) } + )) { Text(row.label) } + .padding(.vertical, 6) + .padding(.horizontal, 16) + .frame(minHeight: 44) + case let .link(label: label, url: url, note: note): + MoreLink(label: label, url: url, note: note) + case let .phone(label: label, phoneNumber: phoneNumber): + MorePhone(label: label, phoneNumber: phoneNumber) + } + if index < section.items.count { + Rectangle() + .fill(Color.halo) + .frame(height: 1) + .frame(maxWidth: .infinity) + } + } + } + } + .background(Color.fill3) + .clipShape(.rect(cornerRadius: 8.0)) + .overlay( + RoundedRectangle(cornerRadius: 8.0) + .stroke(Color.halo, lineWidth: 1.0) + ) + } + } + } +} diff --git a/iosApp/iosApp/Pages/More/Setting+Convenience.swift b/iosApp/iosApp/Pages/More/Setting+Convenience.swift new file mode 100644 index 000000000..4960e235b --- /dev/null +++ b/iosApp/iosApp/Pages/More/Setting+Convenience.swift @@ -0,0 +1,20 @@ +// +// Setting+Convenience.swift +// iosApp +// +// Created by Brandon Rodriguez on 7/11/24. +// Copyright © 2024 MBTA. All rights reserved. +// + +import Foundation +import shared + +extension Setting: Identifiable { + var category: MoreSection.Category { + switch key { + case .hideMaps: .settings + case .searchRouteResults: .featureFlags + case .map: .featureFlags + } + } +} diff --git a/iosApp/iosApp/Pages/Settings/Setting+Convenience.swift b/iosApp/iosApp/Pages/Settings/Setting+Convenience.swift deleted file mode 100644 index e9b63644d..000000000 --- a/iosApp/iosApp/Pages/Settings/Setting+Convenience.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// Setting+Convenience.swift -// iosApp -// -// Created by Brandon Rodriguez on 7/11/24. -// Copyright © 2024 MBTA. All rights reserved. -// - -import Foundation -import shared - -extension Setting: Identifiable { - public var id: UUID { - UUID() - } - - var name: String { - switch key { - case .hideMaps: - "Hide Maps" - case .searchRouteResults: - "Search - Route Results" - case .map: - "Map Debug" - } - } - - var icon: String { - switch key { - case .hideMaps: - "map" - case .searchRouteResults: - "point.topleft.down.to.point.bottomright.curvepath.fill" - case .map: - "location.magnifyingglass" - } - } - - var category: SettingsSection.Category { - switch key { - case .hideMaps: - .settings - case .searchRouteResults: - .featureFlags - case .map: - .debug - } - } -} diff --git a/iosApp/iosApp/Pages/Settings/SettingsPage.swift b/iosApp/iosApp/Pages/Settings/SettingsPage.swift deleted file mode 100644 index a1cd4f73d..000000000 --- a/iosApp/iosApp/Pages/Settings/SettingsPage.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// SettingsPage.swift -// iosApp -// -// Created by Horn, Melody on 2024-04-30. -// Copyright © 2024 MBTA. All rights reserved. -// - -import shared -import SwiftUI - -struct SettingsPage: View { - let inspection = Inspection() - - @StateObject var viewModel = SettingsViewModel() - - var body: some View { - VStack { - Text("Settings") - .font(Typography.title1) - - List($viewModel.settings) { $section in - if !(section.requiresStaging && !(appVariant == .staging)) { - Section(section.name) { - ForEach($section.settings) { $row in - Toggle(isOn: $row.isOn) { Label(row.name, systemImage: row.icon) } - } - } - } - } - } - .onReceive(inspection.notice) { inspection.visit(self, $0) } - .task { await viewModel.getSettings() } - } -} - -#Preview { - SettingsPage() - .font(Typography.body) -} diff --git a/iosApp/iosApp/Pages/Settings/SettingsSection.swift b/iosApp/iosApp/Pages/Settings/SettingsSection.swift deleted file mode 100644 index b14e857a9..000000000 --- a/iosApp/iosApp/Pages/Settings/SettingsSection.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// SettingsSection.swift -// iosApp -// -// Created by Brandon Rodriguez on 7/11/24. -// Copyright © 2024 MBTA. All rights reserved. -// - -import shared - -struct SettingsSection: Identifiable, Equatable { - enum Category: String { - case settings - case debug - case featureFlags - } - - var id: Category - var name: String { - switch id { - case .settings: - "Settings" - case .debug: - "Debug" - case .featureFlags: - "Feature Flags" - } - } - - var settings: [Setting] - var requiresStaging: Bool { - switch id { - case .settings: - false - case .debug: - false - case .featureFlags: - true - } - } -} diff --git a/iosApp/iosApp/ViewModels/SettingsViewModel.swift b/iosApp/iosApp/ViewModels/SettingsViewModel.swift index 85199aa54..c75cd41ee 100644 --- a/iosApp/iosApp/ViewModels/SettingsViewModel.swift +++ b/iosApp/iosApp/ViewModels/SettingsViewModel.swift @@ -7,50 +7,87 @@ // import Combine +import Foundation import shared class SettingsViewModel: ObservableObject { - @Published var settings = [SettingsSection]() + @Published var sections = [MoreSection]() private let settingsRepository: ISettingsRepository + private var settings: Set = Set() private var cancellables = Set() init(settingsRepository: ISettingsRepository = RepositoryDI().settings) { self.settingsRepository = settingsRepository - setUpSubscriptions() } - func setUpSubscriptions() { - $settings - .dropFirst(2) - .map { sections in - sections.reduce([Setting]()) { partialResult, section in - partialResult + section.settings - } + func toggleSetting(key: Settings) { + setSettings(settings.map { setting in + if key == setting.key { + setting.isOn = !setting.isOn } - .sink { [weak self] settingsToCommit in - guard let self else { return } - setSettings(settingsToCommit) - } - .store(in: &cancellables) + return setting + }) } - @MainActor func getSettings() async { + @MainActor func getSections() async { do { - let storedSettings = try await settingsRepository.getSettings() - settings = [ - SettingsSection( + settings = try await settingsRepository.getSettings() + sections = [ + MoreSection(id: .resources, items: [ + .link( + label: NSLocalizedString( + "Trip Planner", + comment: "Label for a More page link to the MBTA.com trip planner" + ), + url: "https://www.mbta.com/trip-planner" + ), + .link( + label: NSLocalizedString( + "Fare Information", + comment: "Label for a More page link to fare information on MBTA.com" + ), + url: "https://www.mbta.com/fares" + ), + .link( + label: NSLocalizedString( + "Commuter Rail and Ferry tickets", + comment: "Label for a More page link to the MBTA mTicket app" + ), + url: "https://apps.apple.com/us/app/mbta-mticket/id560487958", + note: NSLocalizedString( + "mTicket App", + comment: "Footnote underneath the \"Commuter Rail and Ferry tickets\" label on the More page link to the MBTA mTicket app" + ) + ), + ]), + MoreSection( id: .settings, - settings: storedSettings.filter { $0.category == .settings } - ), - SettingsSection( - id: .debug, - settings: storedSettings.filter { $0.category == .debug } + items: settings.filter { $0.category == .settings }.map { MoreItem.toggle(setting: $0) } ), - SettingsSection( + MoreSection( id: .featureFlags, - settings: storedSettings.filter { $0.category == .featureFlags } + items: settings.filter { $0.category == .featureFlags }.map { MoreItem.toggle(setting: $0) } ), + MoreSection(id: .other, items: [ + .link( + label: NSLocalizedString( + "Terms of Use", + comment: "Label for a More page link to the MBTA.com terms of use" + ), + url: "https://www.mbta.com/policies/terms-use" + ), + .link( + label: NSLocalizedString( + "Privacy Policy", + comment: "Label for a More page link to the MBTA.com privacy policy" + ), + url: "https://www.mbta.com/policies/privacy-policy" + ), + ]), + MoreSection(id: .support, items: [ + .phone(label: "617-222-3200", phoneNumber: "6172223200"), + ]), ] } catch { debugPrint("failed to load settings") diff --git a/iosApp/iosAppTests/Pages/Settings/SettingsPageTests.swift b/iosApp/iosAppTests/Pages/Settings/MorePageTests.swift similarity index 50% rename from iosApp/iosAppTests/Pages/Settings/SettingsPageTests.swift rename to iosApp/iosAppTests/Pages/Settings/MorePageTests.swift index 5bf73fdc1..3b76b98ef 100644 --- a/iosApp/iosAppTests/Pages/Settings/SettingsPageTests.swift +++ b/iosApp/iosAppTests/Pages/Settings/MorePageTests.swift @@ -1,5 +1,5 @@ // -// SettingsPageTests.swift +// MorePageTests.swift // iosAppTests // // Created by Horn, Melody on 2024-05-03. @@ -12,7 +12,7 @@ import shared import ViewInspector import XCTest -final class SettingsPageTests: XCTestCase { +final class MorePageTests: XCTestCase { class FakeSettingsRepository: ISettingsRepository { var settings: Set let onGet: (() -> Void)? @@ -43,54 +43,77 @@ final class SettingsPageTests: XCTestCase { } } - @MainActor func testLoadsState() throws { + @MainActor func testLoadsState() async throws { let loadedPublisher = PassthroughSubject() let settingsRepository = FakeSettingsRepository( mapDebug: true, searchRouteResults: false, - onGet: { - loadedPublisher.send(()) - } - ) - let sut = SettingsPage( - viewModel: SettingsViewModel(settingsRepository: settingsRepository) + onGet: { loadedPublisher.send(()) } ) + let viewModel = SettingsViewModel(settingsRepository: settingsRepository) + + let sut = MorePage(viewModel: viewModel) let exp = sut.inspection.inspect(onReceive: loadedPublisher, after: 1) { view in - XCTAssertTrue(try view.find(ViewType.Toggle.self).isOn()) + XCTAssertTrue(try view.find(text: "Map Debug").parent().parent().find(ViewType.Toggle.self).isOn()) } ViewHosting.host(view: sut) + await viewModel.getSections() - wait(for: [exp], timeout: 2) + await fulfillment(of: [exp], timeout: 2) } - @MainActor func testSavesState() throws { + @MainActor func testSavesState() async throws { let loadedPublisher = PassthroughSubject() let savedExp = expectation(description: "saved state") let settingsRepository = FakeSettingsRepository( mapDebug: false, searchRouteResults: false, - onGet: { - loadedPublisher.send(()) - }, + onGet: { loadedPublisher.send(()) }, onSet: { let mapSetting = $0.first(where: { $0.key == .map }) XCTAssertTrue(mapSetting?.isOn == true) savedExp.fulfill() } ) - let sut = SettingsPage( - viewModel: SettingsViewModel(settingsRepository: settingsRepository) - ) + let viewModel = SettingsViewModel(settingsRepository: settingsRepository) + + let sut = MorePage(viewModel: viewModel) + let tapExp = sut.inspection.inspect(onReceive: loadedPublisher, after: 1) { view in + try view.find(text: "Map Debug").parent().parent().find(ViewType.Toggle.self).tap() + } ViewHosting.host(view: sut) + await viewModel.getSections() - let tapExp = sut.inspection.inspect(onReceive: loadedPublisher, after: 1) { view in - try view.find(ViewType.Toggle.self).tap() + await fulfillment(of: [tapExp, savedExp], timeout: 5) + } + + @MainActor func testLinksExist() async throws { + let loadedPublisher = PassthroughSubject() + + let settingsRepository = FakeSettingsRepository( + mapDebug: false, + searchRouteResults: false, + onGet: { loadedPublisher.send(()) } + ) + let viewModel = SettingsViewModel(settingsRepository: settingsRepository) + + let sut = MorePage(viewModel: viewModel) + let exp = sut.inspection.inspect(onReceive: loadedPublisher, after: 1) { view in + try XCTAssertNotNil(view.find(text: "Trip Planner")) + try XCTAssertNotNil(view.find(text: "Fare Information")) + try XCTAssertNotNil(view.find(text: "Commuter Rail and Ferry tickets")) + try XCTAssertNotNil(view.find(text: "Terms of Use")) + try XCTAssertNotNil(view.find(text: "Privacy Policy")) + try XCTAssertNotNil(view.find(text: "617-222-3200")) } - wait(for: [tapExp, savedExp], timeout: 2) + ViewHosting.host(view: sut) + await viewModel.getSections() + + await fulfillment(of: [exp], timeout: 5) } }