diff --git a/Mlem.xcodeproj/project.pbxproj b/Mlem.xcodeproj/project.pbxproj index 2b2bfe306..02567eb20 100644 --- a/Mlem.xcodeproj/project.pbxproj +++ b/Mlem.xcodeproj/project.pbxproj @@ -26,8 +26,6 @@ 032109472AA7C3FC00912DFC /* CommunityLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032109462AA7C3FC00912DFC /* CommunityLabelView.swift */; }; 032109492AA7C41800912DFC /* AvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032109482AA7C41800912DFC /* AvatarView.swift */; }; 032DD2FD2AC3594B00F1B33D /* ImageUploadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032DD2FC2AC3594B00F1B33D /* ImageUploadView.swift */; }; - 032109472AA7C3FC00912DFC /* CommunityLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032109462AA7C3FC00912DFC /* CommunityLabelView.swift */; }; - 032109492AA7C41800912DFC /* AvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032109482AA7C41800912DFC /* AvatarView.swift */; }; 034C724F2A82B61200B8A4B8 /* LayoutWidgetTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 034C724E2A82B61200B8A4B8 /* LayoutWidgetTracker.swift */; }; 035EB0CA2A8687C200227859 /* JumpButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035EB0C92A8687C200227859 /* JumpButtonView.swift */; }; 036ED3BC2ABF1058009664BC /* SearchModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036ED3BB2ABF1058009664BC /* SearchModel.swift */; }; @@ -72,7 +70,6 @@ 503BA26F2A2C94540052516C /* URL+Identifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 503BA26E2A2C94540052516C /* URL+Identifiable.swift */; }; 504106CD2A744D7F000AAEF8 /* CommentRepository+Dependency.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504106CC2A744D7F000AAEF8 /* CommentRepository+Dependency.swift */; }; 504ECBAA2AB27C73006C0B96 /* LandingPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504ECBA92AB27C73006C0B96 /* LandingPage.swift */; }; - 504ECBAC2AB27CB1006C0B96 /* OnboardingRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504ECBAB2AB27CB1006C0B96 /* OnboardingRoute.swift */; }; 504ECBAE2AB45B2A006C0B96 /* LemmyURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504ECBAD2AB45B2A006C0B96 /* LemmyURL.swift */; }; 504ECBB12AB4B101006C0B96 /* LemmyURLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504ECBB02AB4B101006C0B96 /* LemmyURLTests.swift */; }; 505240E32A86916500EA4558 /* FavoriteCommunitiesTracker+Dependency.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505240E22A86916500EA4558 /* FavoriteCommunitiesTracker+Dependency.swift */; }; @@ -454,22 +451,20 @@ CDF8426B2A4A2AB600723DA0 /* Inbox Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDF8426A2A4A2AB600723DA0 /* Inbox Item.swift */; }; CDF8426F2A4A385A00723DA0 /* Inbox Item Type.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDF8426E2A4A385A00723DA0 /* Inbox Item Type.swift */; }; CDF9EF332AB2845C003F885B /* Icons.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDF9EF322AB2845C003F885B /* Icons.swift */; }; - E40E018C2AABF85500410B2C /* NavigationRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E40E018B2AABF85500410B2C /* NavigationRoutes.swift */; }; - E40E018E2AABFBDE00410B2C /* NavigationRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E40E018D2AABFBDE00410B2C /* NavigationRouter.swift */; }; - E40E01902AABFC9300410B2C /* AnyNavigationPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = E40E018F2AABFC9300410B2C /* AnyNavigationPath.swift */; }; - E40E018C2AABF85500410B2C /* NavigationRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E40E018B2AABF85500410B2C /* NavigationRoutes.swift */; }; - E40E018E2AABFBDE00410B2C /* NavigationRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E40E018D2AABFBDE00410B2C /* NavigationRouter.swift */; }; - E40E01902AABFC9300410B2C /* AnyNavigationPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = E40E018F2AABFC9300410B2C /* AnyNavigationPath.swift */; }; + E408C7882B0EE60000BE0A4A /* Routable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E408C7872B0EE60000BE0A4A /* Routable.swift */; }; + E40E018C2AABF85500410B2C /* AppRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E40E018B2AABF85500410B2C /* AppRoutes.swift */; }; + E40E018E2AABFBDE00410B2C /* AnyNavigationPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = E40E018D2AABFBDE00410B2C /* AnyNavigationPath.swift */; }; + E40E01902AABFC9300410B2C /* AnyNavigablePath.swift in Sources */ = {isa = PBXBuildFile; fileRef = E40E018F2AABFC9300410B2C /* AnyNavigablePath.swift */; }; E41FAD792AB12C2500557719 /* Environment+ScrollViewReaderProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41FAD782AB12C2500557719 /* Environment+ScrollViewReaderProxy.swift */; }; E41FAD7B2AB12D5900557719 /* ScrollToView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41FAD7A2AB12D5900557719 /* ScrollToView.swift */; }; + E42D9B5A2AD6802B0087693C /* OnboardingRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E42D9B592AD6802B0087693C /* OnboardingRoutes.swift */; }; E4516E472AAC4B3500F496BE /* DismissAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4516E462AAC4B3500F496BE /* DismissAction.swift */; }; E453477E2A9DE37300D1B46F /* Array+SafeIndexing.swift in Sources */ = {isa = PBXBuildFile; fileRef = E453477D2A9DE37300D1B46F /* Array+SafeIndexing.swift */; }; E453A1D02A81C2140004BB8A /* QuickLookPreviewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E453A1CF2A81C2140004BB8A /* QuickLookPreviewController.swift */; }; E47478132AAC350E001CB1AC /* NavigationLink+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47478122AAC350E001CB1AC /* NavigationLink+Helpers.swift */; }; E47478152AAC3C19001CB1AC /* NavigationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47478142AAC3C19001CB1AC /* NavigationContext.swift */; }; - E47B2B762A902DE200629AF7 /* SettingsRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47B2B752A902DE200629AF7 /* SettingsRoutes.swift */; }; - E4902BAB2A9024BF0054FB36 /* SettingsRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4902BAA2A9024BF0054FB36 /* SettingsRouter.swift */; }; - E49E01F42ABD99D300E42BB3 /* Routable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E49E01F32ABD99D300E42BB3 /* Routable.swift */; }; + E47B2B762A902DE200629AF7 /* SettingsValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47B2B752A902DE200629AF7 /* SettingsValues.swift */; }; + E48DE4A22AC3F23F004E6291 /* DestinationValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = E48DE4A12AC3F23F004E6291 /* DestinationValue.swift */; }; E49F0E762A90395400BC4EE3 /* NavigationPath+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = E49F0E752A90395400BC4EE3 /* NavigationPath+Helpers.swift */; }; E4D4DBA02A7C7B9D00C4F3DE /* Comments.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D4DB9F2A7C7B9D00C4F3DE /* Comments.swift */; }; E4D4DBA22A7F233200C4F3DE /* FancyTabNavigationSelectionHashValueEnvironmentKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D4DBA12A7F233200C4F3DE /* FancyTabNavigationSelectionHashValueEnvironmentKey.swift */; }; @@ -516,8 +511,6 @@ 032109462AA7C3FC00912DFC /* CommunityLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommunityLabelView.swift; sourceTree = ""; }; 032109482AA7C41800912DFC /* AvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarView.swift; sourceTree = ""; }; 032DD2FC2AC3594B00F1B33D /* ImageUploadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageUploadView.swift; sourceTree = ""; }; - 032109462AA7C3FC00912DFC /* CommunityLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommunityLabelView.swift; sourceTree = ""; }; - 032109482AA7C41800912DFC /* AvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarView.swift; sourceTree = ""; }; 034C724E2A82B61200B8A4B8 /* LayoutWidgetTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutWidgetTracker.swift; sourceTree = ""; }; 035EB0C92A8687C200227859 /* JumpButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JumpButtonView.swift; sourceTree = ""; }; 036ED3BB2ABF1058009664BC /* SearchModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchModel.swift; sourceTree = ""; }; @@ -562,7 +555,6 @@ 503BA26E2A2C94540052516C /* URL+Identifiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Identifiable.swift"; sourceTree = ""; }; 504106CC2A744D7F000AAEF8 /* CommentRepository+Dependency.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CommentRepository+Dependency.swift"; sourceTree = ""; }; 504ECBA92AB27C73006C0B96 /* LandingPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LandingPage.swift; sourceTree = ""; }; - 504ECBAB2AB27CB1006C0B96 /* OnboardingRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingRoute.swift; sourceTree = ""; }; 504ECBAD2AB45B2A006C0B96 /* LemmyURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LemmyURL.swift; sourceTree = ""; }; 504ECBB02AB4B101006C0B96 /* LemmyURLTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LemmyURLTests.swift; sourceTree = ""; }; 505240E22A86916500EA4558 /* FavoriteCommunitiesTracker+Dependency.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FavoriteCommunitiesTracker+Dependency.swift"; sourceTree = ""; }; @@ -942,22 +934,20 @@ CDF8426A2A4A2AB600723DA0 /* Inbox Item.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Inbox Item.swift"; sourceTree = ""; }; CDF8426E2A4A385A00723DA0 /* Inbox Item Type.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Inbox Item Type.swift"; sourceTree = ""; }; CDF9EF322AB2845C003F885B /* Icons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Icons.swift; sourceTree = ""; }; - E40E018B2AABF85500410B2C /* NavigationRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationRoutes.swift; sourceTree = ""; }; - E40E018D2AABFBDE00410B2C /* NavigationRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationRouter.swift; sourceTree = ""; }; - E40E018F2AABFC9300410B2C /* AnyNavigationPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyNavigationPath.swift; sourceTree = ""; }; - E40E018B2AABF85500410B2C /* NavigationRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationRoutes.swift; sourceTree = ""; }; - E40E018D2AABFBDE00410B2C /* NavigationRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationRouter.swift; sourceTree = ""; }; - E40E018F2AABFC9300410B2C /* AnyNavigationPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyNavigationPath.swift; sourceTree = ""; }; + E408C7872B0EE60000BE0A4A /* Routable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Routable.swift; path = Routes/Routable.swift; sourceTree = ""; }; + E40E018B2AABF85500410B2C /* AppRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRoutes.swift; sourceTree = ""; }; + E40E018D2AABFBDE00410B2C /* AnyNavigationPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyNavigationPath.swift; sourceTree = ""; }; + E40E018F2AABFC9300410B2C /* AnyNavigablePath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyNavigablePath.swift; sourceTree = ""; }; E41FAD782AB12C2500557719 /* Environment+ScrollViewReaderProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Environment+ScrollViewReaderProxy.swift"; sourceTree = ""; }; E41FAD7A2AB12D5900557719 /* ScrollToView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollToView.swift; sourceTree = ""; }; + E42D9B592AD6802B0087693C /* OnboardingRoutes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingRoutes.swift; sourceTree = ""; }; E4516E462AAC4B3500F496BE /* DismissAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DismissAction.swift; sourceTree = ""; }; E453477D2A9DE37300D1B46F /* Array+SafeIndexing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+SafeIndexing.swift"; sourceTree = ""; }; E453A1CF2A81C2140004BB8A /* QuickLookPreviewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickLookPreviewController.swift; sourceTree = ""; }; E47478122AAC350E001CB1AC /* NavigationLink+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NavigationLink+Helpers.swift"; sourceTree = ""; }; E47478142AAC3C19001CB1AC /* NavigationContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationContext.swift; sourceTree = ""; }; - E47B2B752A902DE200629AF7 /* SettingsRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsRoutes.swift; sourceTree = ""; }; - E4902BAA2A9024BF0054FB36 /* SettingsRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsRouter.swift; sourceTree = ""; }; - E49E01F32ABD99D300E42BB3 /* Routable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Routable.swift; sourceTree = ""; }; + E47B2B752A902DE200629AF7 /* SettingsValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsValues.swift; sourceTree = ""; }; + E48DE4A12AC3F23F004E6291 /* DestinationValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DestinationValue.swift; sourceTree = ""; }; E49F0E752A90395400BC4EE3 /* NavigationPath+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NavigationPath+Helpers.swift"; sourceTree = ""; }; E4D4DB9F2A7C7B9D00C4F3DE /* Comments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Comments.swift; sourceTree = ""; }; E4D4DBA12A7F233200C4F3DE /* FancyTabNavigationSelectionHashValueEnvironmentKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FancyTabNavigationSelectionHashValueEnvironmentKey.swift; sourceTree = ""; }; @@ -1195,7 +1185,6 @@ children = ( 504ECBA92AB27C73006C0B96 /* LandingPage.swift */, CDE3BA862A8C25B000B972E2 /* OnboardingView.swift */, - 504ECBAB2AB27CB1006C0B96 /* OnboardingRoute.swift */, CD2053112ACB72190000AA38 /* AccountTransitionView.swift */, ); path = Onboarding; @@ -1472,7 +1461,6 @@ E4DDB4312A81819300B3A7E0 /* Double.swift */, B1955A202A6145C00056CF99 /* Environment - EasterFlagSetter.swift */, E41FAD782AB12C2500557719 /* Environment+ScrollViewReaderProxy.swift */, - CDE8F2382A68DA7D00E0AE68 /* Environment - Force Onboard.swift */, 507573902A5AD53C00AA7ABD /* Error+Equatable.swift */, 6D8601ED2A43C0B1002A56FC /* Image.swift */, 6DA61F842A568F99001EA633 /* Int.swift */, @@ -2404,35 +2392,36 @@ path = "Quick Look"; sourceTree = ""; }; - E47B2B742A902DB400629AF7 /* Route */ = { + E47B2B742A902DB400629AF7 /* Routes */ = { isa = PBXGroup; children = ( - E40E018B2AABF85500410B2C /* NavigationRoutes.swift */, - E47B2B752A902DE200629AF7 /* SettingsRoutes.swift */, + E40E018B2AABF85500410B2C /* AppRoutes.swift */, + E42D9B592AD6802B0087693C /* OnboardingRoutes.swift */, ); - path = Route; + path = Routes; sourceTree = ""; }; - E47B2B772A902E3C00629AF7 /* Router */ = { + E48DE4A02AC3F208004E6291 /* Destination Values */ = { isa = PBXGroup; children = ( - E40E018D2AABFBDE00410B2C /* NavigationRouter.swift */, - E4902BAA2A9024BF0054FB36 /* SettingsRouter.swift */, + E48DE4A12AC3F23F004E6291 /* DestinationValue.swift */, + E47B2B752A902DE200629AF7 /* SettingsValues.swift */, ); - path = Router; + path = "Destination Values"; sourceTree = ""; }; E4902BA92A90245E0054FB36 /* Navigation */ = { isa = PBXGroup; children = ( - E40E018F2AABFC9300410B2C /* AnyNavigationPath.swift */, + E40E018F2AABFC9300410B2C /* AnyNavigablePath.swift */, + E40E018D2AABFBDE00410B2C /* AnyNavigationPath.swift */, E47478142AAC3C19001CB1AC /* NavigationContext.swift */, E47478122AAC350E001CB1AC /* NavigationLink+Helpers.swift */, E49F0E752A90395400BC4EE3 /* NavigationPath+Helpers.swift */, - E49E01F32ABD99D300E42BB3 /* Routable.swift */, - E47B2B772A902E3C00629AF7 /* Router */, - E47B2B742A902DB400629AF7 /* Route */, + E408C7872B0EE60000BE0A4A /* Routable.swift */, E4516E462AAC4B3500F496BE /* DismissAction.swift */, + E47B2B742A902DB400629AF7 /* Routes */, + E48DE4A02AC3F208004E6291 /* Destination Values */, ); path = Navigation; sourceTree = ""; @@ -2642,7 +2631,7 @@ 637218472A3A2AAD008C4816 /* APICommentView.swift in Sources */, CDDCF6492A6641F0003DA3AC /* FancyTabItemLabelBuilder.swift in Sources */, 63344C542A07D193001BC616 /* FiltersSettingsView.swift in Sources */, - E40E01902AABFC9300410B2C /* AnyNavigationPath.swift in Sources */, + E40E01902AABFC9300410B2C /* AnyNavigablePath.swift in Sources */, 6372185C2A3A2AAD008C4816 /* APICommunity.swift in Sources */, 6372185D2A3A2AAD008C4816 /* APICommunityAggregates.swift in Sources */, 03B7AAF52ABEFA7A00068B23 /* UserResultView.swift in Sources */, @@ -2704,9 +2693,11 @@ B1DD00BD2A62DDEC002A7B39 /* RecognizedLemmyInstances.swift in Sources */, 6DA61F892A575DF1001EA633 /* URL - Lemmy Image Parameters.swift in Sources */, 50811B3E2A9205BA006BA3F2 /* GetCommunityResponse+Mock.swift in Sources */, + E48DE4A22AC3F23F004E6291 /* DestinationValue.swift in Sources */, E4DDB4322A81819300B3A7E0 /* Double.swift in Sources */, CD3FBCD92A4A6BD100B2063F /* Replies Tracker.swift in Sources */, 5016A2B32A67EC0700B257E8 /* NotificationDisplayer.swift in Sources */, + E42D9B5A2AD6802B0087693C /* OnboardingRoutes.swift in Sources */, CD1446212A5B328E00610EF1 /* Privacy Policy.swift in Sources */, 507573942A5AD59E00AA7ABD /* EquatableError.swift in Sources */, CDE6A81A2A490B970062D161 /* Inbox Reply View.swift in Sources */, @@ -2715,7 +2706,7 @@ CD1446272A5B36DA00610EF1 /* EULA.swift in Sources */, 500C168E2A66FAAB006F243B /* HapticManager+Dependency.swift in Sources */, 038A16E52A7A97380087987E /* LayoutWidgetView.swift in Sources */, - E40E018E2AABFBDE00410B2C /* NavigationRouter.swift in Sources */, + E40E018E2AABFBDE00410B2C /* AnyNavigationPath.swift in Sources */, CD1446252A5B357900610EF1 /* Document.swift in Sources */, CDEBC32C2A9A582500518D9D /* Votes Model.swift in Sources */, CDEBC3282A9A57F200518D9D /* Content Model Identifier.swift in Sources */, @@ -2736,7 +2727,7 @@ 50C99B592A61D889005D57DD /* APIClient+Dependency.swift in Sources */, CDDCF6572A678298003DA3AC /* FancyTabBarSelection.swift in Sources */, CD3FBCE92A4B482700B2063F /* Generic Merge.swift in Sources */, - E47B2B762A902DE200629AF7 /* SettingsRoutes.swift in Sources */, + E47B2B762A902DE200629AF7 /* SettingsValues.swift in Sources */, CDA145ED2A510AC100DDAFC9 /* MarkCommentReplyAsReadRequest.swift in Sources */, CD391F982A537E8E00E213B5 /* ReplyToComment.swift in Sources */, 5064D03D2A6DE0AA00B22EE3 /* Notifier.swift in Sources */, @@ -2772,6 +2763,7 @@ CDEBC32A2A9A580B00518D9D /* Post Model.swift in Sources */, CDC6A8CA2A6F1C8D00CC11AC /* AssociatedIconProtocol.swift in Sources */, 6D405B032A43E7DB00C65F9C /* Sidebar Header Label.swift in Sources */, + E408C7882B0EE60000BE0A4A /* Routable.swift in Sources */, 030E86412AC6F692000283A6 /* SearchBar.swift in Sources */, 50785F762A9A684300117245 /* SavedAccountTracker+Dependency.swift in Sources */, 632578182A29F83C00446A66 /* PostSortMenu.swift in Sources */, @@ -2929,7 +2921,6 @@ 637218432A3A2AAD008C4816 /* APIClient.swift in Sources */, CD82A2572A716D7C00111034 /* PersonRepository+Dependency.swift in Sources */, 63DF71F12A02999C002AC14E /* App Constants.swift in Sources */, - 504ECBAC2AB27CB1006C0B96 /* OnboardingRoute.swift in Sources */, CD82A2532A716B8100111034 /* PersonRepository.swift in Sources */, CD69F55F2A40121D0028D4F7 /* Ellipsis Menu.swift in Sources */, 638535712A1779BC00815781 /* GeneralSettingsView.swift in Sources */, @@ -2938,7 +2929,6 @@ 50811B322A9204C1006BA3F2 /* APICommunity+Mock.swift in Sources */, 6D693A482A51B904009E2D76 /* CreateCommentReport.swift in Sources */, CD2E182B2A3B708500224F8A /* Settings Options.swift in Sources */, - E4902BAB2A9024BF0054FB36 /* SettingsRouter.swift in Sources */, 50A881282A71D66B003E3661 /* APIClient+Community.swift in Sources */, 6307378D2A1CEB7C00039852 /* My Vote.swift in Sources */, 50F830FA2A4C935C00D67099 /* FeedTracker.swift in Sources */, @@ -3037,7 +3027,7 @@ 50BC1ABB2A8D6A5A00E3C48B /* ScoringOperation.swift in Sources */, CD18DC692A51ECB6002C56BC /* InteractionSwipeAndMenuHelpers.swift in Sources */, 6386E0362A042C59006B3C1D /* Contributor.swift in Sources */, - E40E018C2AABF85500410B2C /* NavigationRoutes.swift in Sources */, + E40E018C2AABF85500410B2C /* AppRoutes.swift in Sources */, CD18DC6F2A5209C3002C56BC /* MarkPrivateMessageAsReadRequest.swift in Sources */, CD82A2552A716C7C00111034 /* APIPersonUnreadCounts.swift in Sources */, CD04D5E72A3636FB008EF95B /* Headline Post.swift in Sources */, @@ -3053,7 +3043,6 @@ CDE6A8182A490AF20062D161 /* Inbox Mention View.swift in Sources */, CD3FBCDD2A4A6F0600B2063F /* GetReplies.swift in Sources */, 6332FDCF27EFDD2E0009A98A /* Accounts Page.swift in Sources */, - E49E01F42ABD99D300E42BB3 /* Routable.swift in Sources */, CDF842682A49FB9000723DA0 /* Inbox View Logic.swift in Sources */, 6D15D74C2A44DC240061B5CB /* Date.swift in Sources */, CDF1EF122A6B672C003594B6 /* Feed View.swift in Sources */, diff --git a/Mlem/Extensions/Navigation getter.swift b/Mlem/Extensions/Navigation getter.swift index 741996134..a1938e7ce 100644 --- a/Mlem/Extensions/Navigation getter.swift +++ b/Mlem/Extensions/Navigation getter.swift @@ -24,25 +24,12 @@ extension EnvironmentValues { // MARK: - Mlem NavigationRoute private struct NavigationPathWithRoutes: EnvironmentKey { - static let defaultValue: Binding<[NavigationRoute]> = .constant([]) + static let defaultValue: Binding<[AppRoute]> = .constant([]) } extension EnvironmentValues { - var navigationPathWithRoutes: Binding<[NavigationRoute]> { + var navigationPathWithRoutes: Binding<[AppRoute]> { get { self[NavigationPathWithRoutes.self] } set { self[NavigationPathWithRoutes.self] = newValue } } } - -// MARK: - Mlem SettingsRoute - -struct NavigationPathWithSettingsRoutes: EnvironmentKey { - static let defaultValue: Binding<[SettingsRoute]> = .constant([]) -} - -extension EnvironmentValues { - var settingsRoutesNavigationPath: Binding<[SettingsRoute]> { - get { self[NavigationPathWithSettingsRoutes.self] } - set { self[NavigationPathWithSettingsRoutes.self] = newValue } - } -} diff --git a/Mlem/Extensions/View - Handle Lemmy Links.swift b/Mlem/Extensions/View - Handle Lemmy Links.swift index 6cf3bac7c..4916d5686 100644 --- a/Mlem/Extensions/View - Handle Lemmy Links.swift +++ b/Mlem/Extensions/View - Handle Lemmy Links.swift @@ -10,6 +10,8 @@ import Foundation import SwiftUI struct HandleLemmyLinksDisplay: ViewModifier { + @Environment(\.navigationPath) private var navigationPath + @EnvironmentObject private var layoutWidgetTracker: LayoutWidgetTracker @EnvironmentObject var appState: AppState @EnvironmentObject var filtersTracker: FiltersTracker @@ -18,9 +20,10 @@ struct HandleLemmyLinksDisplay: ViewModifier { @AppStorage("upvoteOnSave") var upvoteOnSave = false // swiftlint:disable function_body_length + // swiftlint:disable:next cyclomatic_complexity func body(content: Content) -> some View { content - .navigationDestination(for: NavigationRoute.self) { route in + .navigationDestination(for: AppRoute.self) { route in switch route { case .apiCommunity(let community): FeedView(community: community, feedType: .all, sortType: defaultPostSorting) @@ -71,13 +74,108 @@ struct HandleLemmyLinksDisplay: ViewModifier { case .userModeratorLink(let user): UserModeratorView(userDetails: user.user, moderatedCommunities: user.moderatedCommunities) .environmentObject(appState) + case .settings(let page): + settingsDestination(for: page) + case .aboutSettings(let page): + aboutSettingsDestination(for: page) + case .appearanceSettings(let page): + appearanceSettingsDestination(for: page) + case .commentSettings(let page): + commentSettingsDestination(for: page) + case .postSettings(let page): + postSettingsDestination(for: page) + case .licenseSettings(let page): + licensesSettingsDestination(for: page) } } } // swiftlint:enable function_body_length + + @ViewBuilder + private func settingsDestination(for page: SettingsPage) -> some View { + switch page { + case .accounts: + AccountsPage() + case .general: + GeneralSettingsView() + case .accessibility: + AccessibilitySettingsView() + case .appearance: + AppearanceSettingsView() + case .contentFilters: + FiltersSettingsView() + case .about: + AboutView() + case .advanced: + AdvancedSettingsView() + } + } + + @ViewBuilder + private func aboutSettingsDestination(for page: AboutSettingsPage) -> some View { + switch page { + case .contributors: + ContributorsView() + case let .document(doc): + DocumentView(text: doc.body) + case .licenses: + LicensesView() + } + } + + @ViewBuilder + private func appearanceSettingsDestination(for page: AppearanceSettingsPage) -> some View { + switch page { + case .theme: + ThemeSettingsView() + case .appIcon: + IconSettingsView() + case .posts: + PostSettingsView() + case .comments: + CommentSettingsView() + case .communities: + CommunitySettingsView() + case .users: + UserSettingsView() + case .tabBar: + TabBarSettingsView() + } + } + + @ViewBuilder + private func commentSettingsDestination(for page: CommentSettingsPage) -> some View { + switch page { + case .layoutWidget: + LayoutWidgetEditView(widgets: layoutWidgetTracker.groups.comment, onSave: { widgets in + layoutWidgetTracker.groups.comment = widgets + layoutWidgetTracker.saveLayoutWidgets() + }) + } + } + + @ViewBuilder + private func postSettingsDestination(for page: PostSettingsPage) -> some View { + switch page { + case .customizeWidgets: + /// We really should be passing in the layout widget through the route enum value, but that would involve making layout widget tracker hashable and codable. + LayoutWidgetEditView(widgets: layoutWidgetTracker.groups.post, onSave: { widgets in + layoutWidgetTracker.groups.post = widgets + layoutWidgetTracker.saveLayoutWidgets() + }) + } + } + + @ViewBuilder + private func licensesSettingsDestination(for page: LicensesSettingsPage) -> some View { + switch page { + case let .licenseDocument(doc): + DocumentView(text: doc.body) + } + } } -struct HandleLemmyLinkResolution: ViewModifier { +struct HandleLemmyLinkResolution: ViewModifier { @Dependency(\.apiClient) var apiClient @Dependency(\.errorHandler) var errorHandler @Dependency(\.notifier) var notifier @@ -181,7 +279,7 @@ extension View { modifier(HandleLemmyLinksDisplay()) } - func handleLemmyLinkResolution(navigationPath: Binding

) -> some View { + func handleLemmyLinkResolution(navigationPath: Binding

) -> some View { modifier(HandleLemmyLinkResolution(navigationPath: navigationPath)) } } diff --git a/Mlem/Navigation/AnyNavigablePath.swift b/Mlem/Navigation/AnyNavigablePath.swift new file mode 100644 index 000000000..8b2d23df4 --- /dev/null +++ b/Mlem/Navigation/AnyNavigablePath.swift @@ -0,0 +1,31 @@ +// +// AnyNavigablePath.swift +// Mlem +// +// Created by Bosco Ho on 2023-09-08. +// + +import Foundation +import SwiftUI + +protocol AnyNavigablePath { + + associatedtype Route: Routable + + /// Implementation should make a route that makes sense for the passed-in data value and can be appended to the navigation path. + static func makeRoute(_ value: V) throws -> Route where V: Hashable + + /// The number of elements in this path. + var count: Int { get } + + /// A Boolean that indicates whether this path is empty. + var isEmpty: Bool { get } + + /// Appends a new value to the end of this path. + mutating func append(_ value: V) where V: Routable + + // swiftlint:disable identifier_name + /// Removes values from the end of this path. + mutating func removeLast(_ k: Int) + // swiftlint:enable identifier_name +} diff --git a/Mlem/Navigation/AnyNavigationPath.swift b/Mlem/Navigation/AnyNavigationPath.swift index 3c5d9fe60..e2c72e5f1 100644 --- a/Mlem/Navigation/AnyNavigationPath.swift +++ b/Mlem/Navigation/AnyNavigationPath.swift @@ -6,26 +6,45 @@ // import Foundation -import SwiftUI -protocol AnyNavigationPath { +/// For when the system `NavigationPath` doesn't meet your needs. +/// +/// Technical Note: +/// - [2023.09] Initially, enum-based navigation routes were added during the development of tab-bar navigation. When using the system `NavigationPath`, the UI would exhibit a bug where views would randomly push onto view without any animations, after which the navigation path became corrupt, making programmatic navigation unreliable. Using enum-based navigation routes with custom navigation paths resulted in this issue disappearing on both iOS 16/17. +final class AnyNavigationPath: ObservableObject { - associatedtype Route: Routable + /// - Avoid directly manipulating this value, if alternate methods are provided. + @Published var path: [RouteValue] = [] - /// Implementation should make a route that makes sense for the passed-in data value and can be appended to the navigation path. - static func makeRoute(_ value: V) throws -> Route where V: Hashable +} + +extension AnyNavigationPath: AnyNavigablePath { + + typealias Route = RouteValue + + static func makeRoute(_ value: V) throws -> Route where V: Hashable { + try RouteValue.makeRoute(value) + } - /// The number of elements in this path. - var count: Int { get } + var count: Int { + path.count + } - /// A Boolean that indicates whether this path is empty. - var isEmpty: Bool { get } + var isEmpty: Bool { + path.isEmpty + } - /// Appends a new value to the end of this path. - mutating func append(_ value: V) where V: Routable + func append(_ value: V) where V: Routable { + guard let route = value as? Route else { + assert(value is Route) + return + } + path.append(route) + } // swiftlint:disable identifier_name - /// Removes values from the end of this path. - mutating func removeLast(_ k: Int) + func removeLast(_ k: Int = 1) { + path.removeLast(k) + } // swiftlint:enable identifier_name } diff --git a/Mlem/Navigation/Destination Values/DestinationValue.swift b/Mlem/Navigation/Destination Values/DestinationValue.swift new file mode 100644 index 000000000..65d4176c6 --- /dev/null +++ b/Mlem/Navigation/Destination Values/DestinationValue.swift @@ -0,0 +1,20 @@ +// +// DestinationValue.swift +// Mlem +// +// Created by Bosco Ho on 2023-09-26. +// + +import Foundation + +/// Essentially a cheap "view-model", wrap `DestinationValue` inside a `Routable` value, then use that value as the data to define navigation destinations. +/// +/// Conforming types can be used to drive value-based navigation for destinations that are defined semantically or are not (yet) mapped to a particular data-type or view model. +/// +/// For example: +/// - Many `Settings` views are presented based on their purpose and not the data they present. +/// - In this scenario, we can define a set of semantically named enum cases (i.e. `.general` or `.about`), and treat these enum cases as values that drive navigation. +/// - See `AppRoute` settings routes for an example implementation. +/// +/// - Warning: Avoid directly adding `DestinationValue` to a navigation path or using them as data to define `navigationDestination(...)`. +protocol DestinationValue: Hashable {} diff --git a/Mlem/Navigation/Destination Values/SettingsValues.swift b/Mlem/Navigation/Destination Values/SettingsValues.swift new file mode 100644 index 000000000..993d7c283 --- /dev/null +++ b/Mlem/Navigation/Destination Values/SettingsValues.swift @@ -0,0 +1,47 @@ +// +// SettingsRoutes.swift +// Mlem +// +// Created by Bosco Ho on 2023-08-18. +// + +import Foundation + +enum SettingsPage: DestinationValue { + case accounts + case general + case accessibility + case appearance + case contentFilters + case about + case advanced +} + +enum AboutSettingsPage: DestinationValue { + case contributors + /// e.g. `Privacy Policy` or `EULA`. + case document(Document) + case licenses +} + +enum AppearanceSettingsPage: DestinationValue { + case theme + case appIcon + case posts + case comments + case communities + case users + case tabBar +} + +enum CommentSettingsPage: DestinationValue { + case layoutWidget +} + +enum PostSettingsPage: DestinationValue { + case customizeWidgets +} + +enum LicensesSettingsPage: DestinationValue { + case licenseDocument(Document) +} diff --git a/Mlem/Navigation/DismissAction.swift b/Mlem/Navigation/DismissAction.swift index 00551fbc9..c231c11e9 100644 --- a/Mlem/Navigation/DismissAction.swift +++ b/Mlem/Navigation/DismissAction.swift @@ -50,7 +50,6 @@ struct NavigationDismissHoisting: ViewModifier { @EnvironmentObject private var navigation: Navigation @Environment(\.navigationPathWithRoutes) private var routesNavigationPath - @Environment(\.settingsRoutesNavigationPath) private var settingsNavigationPath @Environment(\.tabSelectionHashValue) private var selectedTabHashValue @@ -62,11 +61,7 @@ struct NavigationDismissHoisting: ViewModifier { assertionFailure() return [] } - if selectedTabHashValue == TabSelection.settings.hashValue { - return settingsNavigationPath.wrappedValue - } else { - return routesNavigationPath.wrappedValue - } + return routesNavigationPath.wrappedValue } /// - Note: Unfortunately, we can't access the dismiss action via View.environment...doing so causes SwiftUI to enter into infinite loop. [2023.09] @@ -123,7 +118,6 @@ struct PerformTabBarNavigation: ViewModifier { @Dependency(\.hapticManager) private var hapticManager @Environment(\.navigationPathWithRoutes) private var routesNavigationPath - @Environment(\.settingsRoutesNavigationPath) private var settingsNavigationPath @Environment(\.tabSelectionHashValue) private var selectedTabHashValue @Environment(\.tabNavigationSelectionHashValue) private var selectedNavigationTabHashValue @@ -136,11 +130,7 @@ struct PerformTabBarNavigation: ViewModifier { assertionFailure() return [] } - if selectedTabHashValue == TabSelection.settings.hashValue { - return settingsNavigationPath.wrappedValue - } else { - return routesNavigationPath.wrappedValue - } + return routesNavigationPath.wrappedValue } let tab: TabSelection diff --git a/Mlem/Navigation/NavigationLink+Helpers.swift b/Mlem/Navigation/NavigationLink+Helpers.swift index a18aab310..d05ee82db 100644 --- a/Mlem/Navigation/NavigationLink+Helpers.swift +++ b/Mlem/Navigation/NavigationLink+Helpers.swift @@ -10,7 +10,7 @@ import SwiftUI extension NavigationLink where Destination == Never { /// Convenience initializer. - init(_ route: NavigationRoute, @ViewBuilder label: () -> Label) { + init(_ route: AppRoute, @ViewBuilder label: () -> Label) { self = .init(value: route, label: label) } } diff --git a/Mlem/Navigation/Route/SettingsRoutes.swift b/Mlem/Navigation/Route/SettingsRoutes.swift deleted file mode 100644 index f52a8fff2..000000000 --- a/Mlem/Navigation/Route/SettingsRoutes.swift +++ /dev/null @@ -1,95 +0,0 @@ -// -// SettingsRoutes.swift -// Mlem -// -// Created by Bosco Ho on 2023-08-18. -// - -import Foundation - -enum SettingsRoute: Routable { - case accountsPage - case general - case accessibility - case appearance - case contentFilters - case about - case advanced - - case aboutPage(AboutSettingsRoute) - case appearancePage(AppearanceSettingsRoute) - case commentPage(CommentSettingsRoute) - case postPage(PostSettingsRoute) - case licensesPage(LicensesSettingsRoute) - - static func makeRoute(_ value: V) throws -> SettingsRoute where V: Hashable { - switch value { - case let value as AboutSettingsRoute: - return try .aboutPage(AboutSettingsRoute.makeRoute(value)) - case let value as AppearanceSettingsRoute: - return try .appearancePage(AppearanceSettingsRoute.makeRoute(value)) - case let value as CommentSettingsRoute: - return try .commentPage(CommentSettingsRoute.makeRoute(value)) - case let value as PostSettingsRoute: - return try .postPage(PostSettingsRoute.makeRoute(value)) - case let value as LicensesSettingsRoute: - return try .licensesPage(LicensesSettingsRoute.makeRoute(value)) - case let value as Self: - /// Value is an enum case of type `Self` with either no associated value or pre-populated associated value. - return value - default: - throw RoutableError.routeNotConfigured(value: value) - } - } -} - -enum AppearanceSettingsRoute: Routable, Codable { - case theme - case appIcon - case posts - case comments - case communities - case users - case tabBar -} - -enum CommentSettingsRoute: Routable, Codable { - case layoutWidget -} - -enum PostSettingsRoute: Routable, Codable { - case customizeWidgets -} - -enum AboutSettingsRoute: Routable { - case contributors - case privacyPolicy(Document) - case eula(Document) - case licenses - - static func makeRoute(_ value: V) throws -> AboutSettingsRoute where V: Hashable { - switch value { - case let value as Document: - // return .privacyPolicy(value) - return .eula(value) - case let value as Self: - /// Value is an enum case of type `Self` with either no associated value or pre-populated associated value. - return value - default: - throw RoutableError.routeNotConfigured(value: value) - } - } -} - -enum LicensesSettingsRoute: Routable { - case licenseDocument(Document) - - static func makeRoute(_ value: V) throws -> LicensesSettingsRoute where V: Hashable { - switch value { - case let value as Document: - return .licenseDocument(value) - default: - throw RoutableError.routeNotConfigured(value: value) - } - } -} diff --git a/Mlem/Navigation/Router/NavigationRouter.swift b/Mlem/Navigation/Router/NavigationRouter.swift deleted file mode 100644 index a25b066d0..000000000 --- a/Mlem/Navigation/Router/NavigationRouter.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// NavigationRouter.swift -// Mlem -// -// Created by Bosco Ho on 2023-09-08. -// - -import Foundation - -final class NavigationRouter: ObservableObject { - - /// - Avoid directly manipulating this value, if alternate methods are provided. - @Published var path: [RouteValue] = [] - -} - -extension NavigationRouter: AnyNavigationPath { - - typealias Route = RouteValue - - static func makeRoute(_ value: V) throws -> Route where V: Hashable { - try RouteValue.makeRoute(value) - } - - var count: Int { - path.count - } - - var isEmpty: Bool { - path.isEmpty - } - - func append(_ value: V) where V: Routable { - guard let route = value as? Route else { - assert(value is Route) - return - } - path.append(route) - } - - // swiftlint:disable identifier_name - func removeLast(_ k: Int = 1) { - path.removeLast(k) - } - // swiftlint:enable identifier_name -} diff --git a/Mlem/Navigation/Route/NavigationRoutes.swift b/Mlem/Navigation/Routes/AppRoutes.swift similarity index 64% rename from Mlem/Navigation/Route/NavigationRoutes.swift rename to Mlem/Navigation/Routes/AppRoutes.swift index fb53effab..1ac472e8b 100644 --- a/Mlem/Navigation/Route/NavigationRoutes.swift +++ b/Mlem/Navigation/Routes/AppRoutes.swift @@ -1,5 +1,5 @@ // -// NavigationRoutes.swift +// AppRoutes.swift // Mlem // // Created by Bosco Ho on 2023-09-08. @@ -9,8 +9,8 @@ import Foundation /// Possible routes for navigation links in `Mlem.app`. /// -/// See `SettingsRoutes` for settings-related routes. -enum NavigationRoute: Routable { +/// For simple (i.e. linear) navigation flows, you may wish to define a separate set of routes. For example, see `OnboardingRoutes`. +enum AppRoute: Routable { case apiCommunityView(APICommunityView) case apiCommunity(APICommunity) @@ -26,8 +26,16 @@ enum NavigationRoute: Routable { case lazyLoadPostLinkWithContext(LazyLoadPostLinkWithContext) case userModeratorLink(UserModeratorLink) + // MARK: - Settings + case settings(SettingsPage) + case aboutSettings(AboutSettingsPage) + case appearanceSettings(AppearanceSettingsPage) + case commentSettings(CommentSettingsPage) + case postSettings(PostSettingsPage) + case licenseSettings(LicensesSettingsPage) + // swiftlint:disable cyclomatic_complexity - static func makeRoute(_ value: V) throws -> NavigationRoute where V: Hashable { + static func makeRoute(_ value: V) throws -> AppRoute where V: Hashable { switch value { case let value as APICommunityView: return .apiCommunityView(value) @@ -49,6 +57,18 @@ enum NavigationRoute: Routable { return .lazyLoadPostLinkWithContext(value) case let value as UserModeratorLink: return .userModeratorLink(value) + case let value as SettingsPage: + return .settings(value) + case let value as AboutSettingsPage: + return .aboutSettings(value) + case let value as AppearanceSettingsPage: + return .appearanceSettings(value) + case let value as CommentSettingsPage: + return .commentSettings(value) + case let value as PostSettingsPage: + return .postSettings(value) + case let value as LicensesSettingsPage: + return .licenseSettings(value) case let value as Self: /// Value is an enum case of type `Self` with either no associated value or pre-populated associated value. return value diff --git a/Mlem/Views/Onboarding/OnboardingRoute.swift b/Mlem/Navigation/Routes/OnboardingRoutes.swift similarity index 65% rename from Mlem/Views/Onboarding/OnboardingRoute.swift rename to Mlem/Navigation/Routes/OnboardingRoutes.swift index 24fd3f39d..e719139c6 100644 --- a/Mlem/Views/Onboarding/OnboardingRoute.swift +++ b/Mlem/Navigation/Routes/OnboardingRoutes.swift @@ -8,7 +8,8 @@ import Foundation -enum OnboardingRoute: Hashable { +/// Routes for Onboarding navigation flow. +enum OnboardingRoute: Routable { case onboard case login(URL?) } diff --git a/Mlem/Navigation/Routable.swift b/Mlem/Navigation/Routes/Routable.swift similarity index 94% rename from Mlem/Navigation/Routable.swift rename to Mlem/Navigation/Routes/Routable.swift index 34a6a1e80..0aa2cbc3b 100644 --- a/Mlem/Navigation/Routable.swift +++ b/Mlem/Navigation/Routes/Routable.swift @@ -7,7 +7,7 @@ import Foundation -/// Conforming types can be added to a `NavigationRouter`'s path. +/// Conforming types can be added to `AnyNavigablePath`'s path. protocol Routable: Hashable { /// - Parameter value: A data type for a given navigation destination. This value could be (but not limited to) some raw data, a view model, or an enum case (representing a route on a navigation path). diff --git a/Mlem/Views/Tabs/Feeds/Community List/Components/CommunityListRowViews.swift b/Mlem/Views/Tabs/Feeds/Community List/Components/CommunityListRowViews.swift index 8717a4064..01db2de3b 100644 --- a/Mlem/Views/Tabs/Feeds/Community List/Components/CommunityListRowViews.swift +++ b/Mlem/Views/Tabs/Feeds/Community List/Components/CommunityListRowViews.swift @@ -71,7 +71,7 @@ struct CommuntiyFeedRowView: View { return CommunityLinkWithContext(community: community, feedType: .subscribed) } else { // Do not use enum route path in sidebar: It doesn't work, and I have no idea why =/ [2023.09] - return NavigationRoute.communityLinkWithContext(.init(community: community, feedType: .subscribed)) + return AppRoute.communityLinkWithContext(.init(community: community, feedType: .subscribed)) } } @@ -157,7 +157,7 @@ struct HomepageFeedRowView: View { return CommunityLinkWithContext(community: nil, feedType: feedType) } else { // Do not use enum route path in sidebar: It doesn't work, and I have no idea why =/ [2023.09] - return NavigationRoute.communityLinkWithContext(.init(community: nil, feedType: feedType)) + return AppRoute.communityLinkWithContext(.init(community: nil, feedType: feedType)) } } } diff --git a/Mlem/Views/Tabs/Feeds/Feed Root.swift b/Mlem/Views/Tabs/Feeds/Feed Root.swift index 4d18fe979..cea5623da 100644 --- a/Mlem/Views/Tabs/Feeds/Feed Root.swift +++ b/Mlem/Views/Tabs/Feeds/Feed Root.swift @@ -17,7 +17,7 @@ struct FeedRoot: View { @AppStorage("defaultFeed") var defaultFeed: FeedType = .subscribed @AppStorage("defaultPostSorting") var defaultPostSorting: PostSortType = .hot - @StateObject private var feedRouter: NavigationRouter = .init() + @StateObject private var feedTabNavigation: AnyNavigationPath = .init() @StateObject private var navigation: Navigation = .init() @State var rootDetails: CommunityLinkWithContext? @@ -36,7 +36,7 @@ struct FeedRoot: View { NavigationSplitView(columnVisibility: $columnVisibility) { CommunityListView(selectedCommunity: $rootDetails) } detail: { - NavigationStack(path: $feedRouter.path) { + NavigationStack(path: $feedTabNavigation.path) { if let rootDetails { FeedView( community: rootDetails.community, @@ -57,14 +57,13 @@ struct FeedRoot: View { } .environment(\.scrollViewProxy, proxy) } - .environment(\.navigationPathWithRoutes, $feedRouter.path) .environmentObject(navigation) .handleLemmyLinkResolution( - navigationPath: .constant(feedRouter) + navigationPath: .constant(feedTabNavigation) ) - .environmentObject(feedRouter) + .environmentObject(feedTabNavigation) .environmentObject(appState) - .environment(\.navigationPathWithRoutes, $feedRouter.path) + .environment(\.navigationPathWithRoutes, $feedTabNavigation.path) .onAppear { if rootDetails == nil || shortcutItemToProcess != nil { let feedType = FeedType(rawValue: @@ -81,7 +80,7 @@ struct FeedRoot: View { rootDetails = CommunityLinkWithContext(community: nil, feedType: defaultFeed) } - _ = HandleLemmyLinkResolution(navigationPath: .constant(feedRouter)) + _ = HandleLemmyLinkResolution(navigationPath: .constant(feedTabNavigation)) .didReceiveURL(url) } } diff --git a/Mlem/Views/Tabs/Inbox/Inbox View.swift b/Mlem/Views/Tabs/Inbox/Inbox View.swift index a15479c03..30b66d41d 100644 --- a/Mlem/Views/Tabs/Inbox/Inbox View.swift +++ b/Mlem/Views/Tabs/Inbox/Inbox View.swift @@ -88,12 +88,12 @@ struct InboxView: View { @State var curTab: InboxTab = .all // utility - @StateObject private var inboxRouter: NavigationRouter = .init() @StateObject private var navigation: Navigation = .init() + @StateObject private var inboxTabNavigation: AnyNavigationPath = .init() var body: some View { // NOTE: there appears to be a SwiftUI issue with segmented pickers stacked on top of ScrollViews which causes the tab bar to appear fully transparent. The internet suggests that this may be a bug that only manifests in dev mode, so, unless this pops up in a build, don't worry about it. If it does manifest, we can either put the Picker *in* the ScrollView (bad because then you can't access it without scrolling to the top) or put a Divider() at the bottom of the VStack (bad because then the material tab bar doesn't show) - NavigationStack(path: $inboxRouter.path) { + NavigationStack(path: $inboxTabNavigation.path) { contentView .navigationTitle("Inbox") .navigationBarTitleDisplayMode(.inline) @@ -115,7 +115,7 @@ struct InboxView: View { } } } - .environment(\.navigationPathWithRoutes, $inboxRouter.path) + .environment(\.navigationPathWithRoutes, $inboxTabNavigation.path) .environmentObject(navigation) } diff --git a/Mlem/Views/Tabs/Profile/Profile View.swift b/Mlem/Views/Tabs/Profile/Profile View.swift index 779f95251..4d7f59e9d 100644 --- a/Mlem/Views/Tabs/Profile/Profile View.swift +++ b/Mlem/Views/Tabs/Profile/Profile View.swift @@ -17,20 +17,21 @@ struct ProfileView: View { @Environment(\.tabSelectionHashValue) private var selectedTagHashValue @Environment(\.tabNavigationSelectionHashValue) private var selectedNavigationTabHashValue - @StateObject private var profileRouter: NavigationRouter = .init() + @StateObject private var profileTabNavigation: AnyNavigationPath = .init() @StateObject private var navigation: Navigation = .init() var body: some View { ScrollViewReader { proxy in - NavigationStack(path: $profileRouter.path) { + NavigationStack(path: $profileTabNavigation.path) { UserView(userID: userID) .handleLemmyViews() .tabBarNavigationEnabled(.profile, navigation) } - .environment(\.navigationPathWithRoutes, $profileRouter.path) + .handleLemmyLinkResolution(navigationPath: .constant(profileTabNavigation)) + .environment(\.navigationPathWithRoutes, $profileTabNavigation.path) .environment(\.scrollViewProxy, proxy) .environmentObject(navigation) - .handleLemmyLinkResolution(navigationPath: .constant(profileRouter)) + .handleLemmyLinkResolution(navigationPath: .constant(profileTabNavigation)) .onChange(of: selectedTagHashValue) { newValue in if newValue == TabSelection.profile.hashValue { print("switched to Profile tab") diff --git a/Mlem/Views/Tabs/Search/Results/CommunityResultView.swift b/Mlem/Views/Tabs/Search/Results/CommunityResultView.swift index fa8cf3c7f..751e3cdb4 100644 --- a/Mlem/Views/Tabs/Search/Results/CommunityResultView.swift +++ b/Mlem/Views/Tabs/Search/Results/CommunityResultView.swift @@ -52,7 +52,7 @@ struct CommunityResultView: View { } var body: some View { - NavigationLink(value: NavigationRoute.apiCommunity(community.community)) { + NavigationLink(value: AppRoute.apiCommunity(community.community)) { HStack(spacing: 10) { AvatarView(community: community.community, avatarSize: 48) VStack(alignment: .leading, spacing: 4) { diff --git a/Mlem/Views/Tabs/Search/Results/UserResultView.swift b/Mlem/Views/Tabs/Search/Results/UserResultView.swift index 346889b29..c7bdedc3a 100644 --- a/Mlem/Views/Tabs/Search/Results/UserResultView.swift +++ b/Mlem/Views/Tabs/Search/Results/UserResultView.swift @@ -32,7 +32,7 @@ struct UserResultView: View { } var body: some View { - NavigationLink(value: NavigationRoute.apiPerson(user.user)) { + NavigationLink(value: AppRoute.apiPerson(user.user)) { HStack(spacing: 10) { AvatarView(user: user.user, avatarSize: 48) VStack(alignment: .leading, spacing: 4) { diff --git a/Mlem/Views/Tabs/Search/Search View.swift b/Mlem/Views/Tabs/Search/Search View.swift index 71296ca4e..3e27e6bcc 100644 --- a/Mlem/Views/Tabs/Search/Search View.swift +++ b/Mlem/Views/Tabs/Search/Search View.swift @@ -35,14 +35,14 @@ struct SearchView: View { @State private var scrollToTopAppeared = false - @StateObject private var searchRouter: NavigationRouter = .init() + @StateObject private var searchTabNavigation: AnyNavigationPath = .init() @StateObject private var navigation: Navigation = .init() // constants private let searchPageSize = 50 var body: some View { - NavigationStack(path: $searchRouter.path) { + NavigationStack(path: $searchTabNavigation.path) { content .tabBarNavigationEnabled(.search, navigation) .handleLemmyViews() @@ -50,9 +50,9 @@ struct SearchView: View { .navigationBarColor() .navigationTitle("Search") } - .environment(\.navigationPathWithRoutes, $searchRouter.path) + .environment(\.navigationPathWithRoutes, $searchTabNavigation.path) .environmentObject(navigation) - .handleLemmyLinkResolution(navigationPath: .constant(searchRouter)) + .handleLemmyLinkResolution(navigationPath: .constant(searchTabNavigation)) .searchable(text: getSearchTextBinding(), prompt: "Search for communities") .autocorrectionDisabled(true) .textInputAutocapitalization(.never) diff --git a/Mlem/Views/Tabs/Search/SearchRoot.swift b/Mlem/Views/Tabs/Search/SearchRoot.swift index b02ca2da1..06bd18f45 100644 --- a/Mlem/Views/Tabs/Search/SearchRoot.swift +++ b/Mlem/Views/Tabs/Search/SearchRoot.swift @@ -11,7 +11,7 @@ struct SearchRoot: View { @Environment(\.tabSelectionHashValue) private var selectedTagHashValue @Environment(\.tabNavigationSelectionHashValue) private var selectedNavigationTabHashValue - @StateObject private var searchRouter: NavigationRouter = .init() + @StateObject private var searchRouter: AnyNavigationPath = .init() var body: some View { NavigationStack(path: $searchRouter.path) { diff --git a/Mlem/Views/Tabs/Settings/Components/Settings View.swift b/Mlem/Views/Tabs/Settings/Components/Settings View.swift index e34632375..e71319b21 100644 --- a/Mlem/Views/Tabs/Settings/Components/Settings View.swift +++ b/Mlem/Views/Tabs/Settings/Components/Settings View.swift @@ -10,7 +10,7 @@ import SwiftUI struct SettingsView: View { @EnvironmentObject var layoutWidgetTracker: LayoutWidgetTracker - @StateObject private var settingsRouter: NavigationRouter = .init() + @StateObject private var settingsTabNavigation: AnyNavigationPath = .init() @StateObject private var navigation: Navigation = .init() @Environment(\.openURL) private var openURL @@ -22,54 +22,53 @@ struct SettingsView: View { var body: some View { ScrollViewReader { proxy in - NavigationStack(path: $settingsRouter.path) { + NavigationStack(path: $settingsTabNavigation.path) { List { Section { - NavigationLink(value: SettingsRoute.accountsPage) { + NavigationLink(.settings(.accounts)) { Label("Accounts", systemImage: "person.fill").labelStyle(SquircleLabelStyle(color: .teal)) } .id(scrollToTop) } Section { - NavigationLink(value: SettingsRoute.general) { + NavigationLink(.settings(.general)) { Label("General", systemImage: "gear").labelStyle(SquircleLabelStyle(color: .gray)) } - NavigationLink(value: SettingsRoute.accessibility) { + NavigationLink(.settings(.accessibility)) { // apparently the Apple a11y symbol isn't an SFSymbol Label("Accessibility", systemImage: "hand.point.up.braille.fill").labelStyle(SquircleLabelStyle(color: .blue)) } - NavigationLink(value: SettingsRoute.appearance) { + NavigationLink(.settings(.appearance)) { Label("Appearance", systemImage: "paintbrush.fill").labelStyle(SquircleLabelStyle(color: .pink)) } - NavigationLink(value: SettingsRoute.contentFilters) { + NavigationLink(.settings(.contentFilters)) { Label("Content Filters", systemImage: "line.3.horizontal.decrease") .labelStyle(SquircleLabelStyle(color: .orange)) } } Section { - NavigationLink(value: SettingsRoute.about) { + NavigationLink(.settings(.about)) { Label("About Mlem", systemImage: "info").labelStyle(SquircleLabelStyle(color: .blue)) } } Section { - NavigationLink(value: SettingsRoute.advanced) { + NavigationLink(.settings(.advanced)) { Label("Advanced", systemImage: "gearshape.2.fill").labelStyle(SquircleLabelStyle(color: .gray)) } } } .tabBarNavigationEnabled(.settings, navigation) - .environmentObject(settingsRouter) + .environmentObject(settingsTabNavigation) .fancyTabScrollCompatible() .handleLemmyViews() .navigationTitle("Settings") .navigationBarColor() .navigationBarTitleDisplayMode(.inline) - .useSettingsNavigationRouter() .hoistNavigation( dismiss: dismiss, auxiliaryAction: { @@ -80,14 +79,18 @@ struct SettingsView: View { } ) } - .environment(\.settingsRoutesNavigationPath, $settingsRouter.path) .environmentObject(navigation) - .handleLemmyLinkResolution(navigationPath: .constant(settingsRouter)) + .handleLemmyLinkResolution(navigationPath: .constant(settingsTabNavigation)) .onChange(of: selectedTagHashValue) { newValue in if newValue == TabSelection.settings.hashValue { print("switched to Settings tab") } } + .fancyTabScrollCompatible() + .handleLemmyViews() + .navigationTitle("Settings") + .navigationBarColor() + .navigationBarTitleDisplayMode(.inline) } } } diff --git a/Mlem/Views/Tabs/Settings/Components/Views/About/AboutView.swift b/Mlem/Views/Tabs/Settings/Components/Views/About/AboutView.swift index 0a8dd30d2..aa6d7ed76 100644 --- a/Mlem/Views/Tabs/Settings/Components/Views/About/AboutView.swift +++ b/Mlem/Views/Tabs/Settings/Components/Views/About/AboutView.swift @@ -41,19 +41,19 @@ struct AboutView: View { } .buttonStyle(SettingsButtonStyle()) - NavigationLink(value: SettingsRoute.aboutPage(.contributors)) { + NavigationLink(.aboutSettings(.contributors)) { Label("Contributors", systemImage: "person.2.fill").labelStyle(SquircleLabelStyle(color: .teal)) } } Section { - NavigationLink(value: SettingsRoute.aboutPage(.privacyPolicy(privacyPolicy))) { + NavigationLink(.aboutSettings(.document(privacyPolicy))) { Label("Privacy Policy", systemImage: "hand.raised.fill").labelStyle(SquircleLabelStyle(color: .blue)) } - NavigationLink(value: SettingsRoute.aboutPage(.eula(eula))) { + NavigationLink(.aboutSettings(.document(eula))) { Label("EULA", systemImage: "doc.plaintext.fill").labelStyle(SquircleLabelStyle(color: .purple)) } - NavigationLink(value: SettingsRoute.aboutPage(.licenses)) { + NavigationLink(.aboutSettings(.licenses)) { Label("Licenses", systemImage: "doc.fill").labelStyle(SquircleLabelStyle(color: .orange)) } } diff --git a/Mlem/Views/Tabs/Settings/Components/Views/About/LicensesView.swift b/Mlem/Views/Tabs/Settings/Components/Views/About/LicensesView.swift index ccb738e96..b71d4196b 100644 --- a/Mlem/Views/Tabs/Settings/Components/Views/About/LicensesView.swift +++ b/Mlem/Views/Tabs/Settings/Components/Views/About/LicensesView.swift @@ -29,19 +29,19 @@ struct LicensesView: View { } Section("Open Source Licenses") { - NavigationLink("KeychainAccess", value: SettingsRoute.licensesPage(.licenseDocument(keychainAccessLicense))) + NavigationLink("KeychainAccess", value: AppRoute.licenseSettings(.licenseDocument(keychainAccessLicense))) - NavigationLink("Nuke", value: SettingsRoute.licensesPage(.licenseDocument(nukeLicense))) + NavigationLink("Nuke", value: AppRoute.licenseSettings(.licenseDocument(nukeLicense))) - NavigationLink("Swift Dependencies", value: SettingsRoute.licensesPage(.licenseDocument(swiftDependenciesLicense))) + NavigationLink("Swift Dependencies", value: AppRoute.licenseSettings(.licenseDocument(swiftDependenciesLicense))) - NavigationLink("Swift Markdown UI", value: SettingsRoute.licensesPage(.licenseDocument(swiftMarkdownUILicense))) + NavigationLink("Swift Markdown UI", value: AppRoute.licenseSettings(.licenseDocument(swiftMarkdownUILicense))) - NavigationLink("SwiftUIX", value: SettingsRoute.licensesPage(.licenseDocument(swiftUIXLicense))) + NavigationLink("SwiftUIX", value: AppRoute.licenseSettings(.licenseDocument(swiftUIXLicense))) NavigationLink( "Awesome Lemmy Instances", - value: SettingsRoute.licensesPage(.licenseDocument(awesomeLemmyInstancesLicense))) + value: AppRoute.licenseSettings(.licenseDocument(awesomeLemmyInstancesLicense))) } } .fancyTabScrollCompatible() diff --git a/Mlem/Views/Tabs/Settings/Components/Views/Appearance/AppearanceSettingsView.swift b/Mlem/Views/Tabs/Settings/Components/Views/Appearance/AppearanceSettingsView.swift index 140c047b7..66eaf3023 100644 --- a/Mlem/Views/Tabs/Settings/Components/Views/Appearance/AppearanceSettingsView.swift +++ b/Mlem/Views/Tabs/Settings/Components/Views/Appearance/AppearanceSettingsView.swift @@ -15,7 +15,7 @@ struct AppearanceSettingsView: View { var body: some View { List { Section { - NavigationLink(value: SettingsRoute.appearancePage(.theme)) { + NavigationLink(.appearanceSettings(.theme)) { switch lightOrDarkMode { case .unspecified: ThemeLabel(title: "Theme", color1: .white, color2: .black) @@ -28,7 +28,7 @@ struct AppearanceSettingsView: View { } } #if !os(macOS) && !targetEnvironment(macCatalyst) - NavigationLink(value: SettingsRoute.appearancePage(.appIcon)) { + NavigationLink(.appearanceSettings(.appIcon)) { Label { Text("App Icon") } icon: { @@ -47,24 +47,24 @@ struct AppearanceSettingsView: View { } Section { - NavigationLink(value: SettingsRoute.appearancePage(.posts)) { + NavigationLink(.appearanceSettings(.posts)) { Label("Posts", systemImage: "doc.plaintext.fill").labelStyle(SquircleLabelStyle(color: .pink)) } - NavigationLink(value: SettingsRoute.appearancePage(.comments)) { + NavigationLink(.appearanceSettings(.comments)) { Label("Comments", systemImage: "bubble.left.fill").labelStyle(SquircleLabelStyle(color: .orange)) } - NavigationLink(value: SettingsRoute.appearancePage(.communities)) { + NavigationLink(.appearanceSettings(.communities)) { Label("Communities", systemImage: "house.fill").labelStyle(SquircleLabelStyle(color: .green, fontSize: 15)) } - NavigationLink(value: SettingsRoute.appearancePage(.users)) { + NavigationLink(.appearanceSettings(.users)) { Label("Users", systemImage: "person.fill").labelStyle(SquircleLabelStyle(color: .blue)) } } Section { - NavigationLink(value: SettingsRoute.appearancePage(.tabBar)) { + NavigationLink(.appearanceSettings(.tabBar)) { Label("Tab Bar", systemImage: "square").labelStyle(SquircleLabelStyle(color: .purple)) } } diff --git a/Mlem/Views/Tabs/Settings/Components/Views/Appearance/Comment/CommentSettingsView.swift b/Mlem/Views/Tabs/Settings/Components/Views/Appearance/Comment/CommentSettingsView.swift index df8fa7837..fe2dc7912 100644 --- a/Mlem/Views/Tabs/Settings/Components/Views/Appearance/Comment/CommentSettingsView.swift +++ b/Mlem/Views/Tabs/Settings/Components/Views/Appearance/Comment/CommentSettingsView.swift @@ -52,7 +52,7 @@ struct CommentSettingsView: View { isTicked: $compactComments ) - NavigationLink(value: SettingsRoute.commentPage(.layoutWidget)) { + NavigationLink(.commentSettings(.layoutWidget)) { Label { Text("Customize Widgets") } icon: { diff --git a/Mlem/Views/Tabs/Settings/Components/Views/Appearance/Post/PostSettingsView.swift b/Mlem/Views/Tabs/Settings/Components/Views/Appearance/Post/PostSettingsView.swift index 952eadd5d..19bde82d2 100644 --- a/Mlem/Views/Tabs/Settings/Components/Views/Appearance/Post/PostSettingsView.swift +++ b/Mlem/Views/Tabs/Settings/Components/Views/Appearance/Post/PostSettingsView.swift @@ -51,7 +51,7 @@ struct PostSettingsView: View { options: PostSize.allCases ) - NavigationLink(value: SettingsRoute.postPage(.customizeWidgets)) { + NavigationLink(.postSettings(.customizeWidgets)) { Label { Text("Customize Widgets") } icon: { diff --git a/MlemTests/Navigation/RoutableTests.swift b/MlemTests/Navigation/RoutableTests.swift index b4effc13a..7b7224043 100644 --- a/MlemTests/Navigation/RoutableTests.swift +++ b/MlemTests/Navigation/RoutableTests.swift @@ -52,45 +52,21 @@ final class RoutableTests: XCTestCase { XCTAssertThrowsError(try MockRoute.routeC(.makeRoute(data))) } - // MARK: - NavigationRoutes + // MARK: - AppRoutes /// Passing in raw data value should return a valid route. /// Assert `(Data) –> Route`. func testNavigationRouteHandlesDataValue() throws { let value = CommunityLinkWithContext(community: nil, feedType: .all) - let route = try NavigationRoute.makeRoute(value) + let route = try AppRoute.makeRoute(value) XCTAssert(route == .communityLinkWithContext(value)) } /// Passing in a route enum with an associated value should return the passed in value. func testNavigationRouteHandlesNonNestedAssociatedValueEnumCase() throws { let data = CommunityLinkWithContext(community: nil, feedType: .all) - let value = NavigationRoute.communityLinkWithContext(data) - let route = try NavigationRoute.makeRoute(value) - XCTAssert(route == value) - } - - // MARK: - SettingsRoutes - - /// Passing in a route enum with no associated value should return the passed in value. - func testSettingsRouteHandlesNoAssociatedValueEnumCase() throws { - let value = SettingsRoute.general - let route = try SettingsRoute.makeRoute(value) - XCTAssert(route == value) - } - - /// Passing in a route enum with an associated value should return the passed in value. - func testSettingsRouteHandlesNonNestedAssociatedValueEnumCase() throws { - let value = SettingsRoute.aboutPage(.contributors) - let route = try SettingsRoute.makeRoute(value) - XCTAssert(route == value) - } - - /// Passing in a route enum with an associated value that also has an associated value should return the passed in value. - func testSettingsRouteHandlesNestedAssociatedValueEnumCase() throws { - let nestedValue = Document(body: "Mock EULA") - let value = SettingsRoute.aboutPage(.eula(nestedValue)) - let route = try SettingsRoute.makeRoute(value) + let value = AppRoute.communityLinkWithContext(data) + let route = try AppRoute.makeRoute(value) XCTAssert(route == value) } }