diff --git a/Mlem.xcodeproj/project.pbxproj b/Mlem.xcodeproj/project.pbxproj index 4d237ffe9..e8a0d2951 100644 --- a/Mlem.xcodeproj/project.pbxproj +++ b/Mlem.xcodeproj/project.pbxproj @@ -416,8 +416,13 @@ CDF842682A49FB9000723DA0 /* Inbox View Logic.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDF842672A49FB9000723DA0 /* Inbox View Logic.swift */; }; 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 */; }; + 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 */; }; 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 */; }; E49F0E762A90395400BC4EE3 /* NavigationPath+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = E49F0E752A90395400BC4EE3 /* NavigationPath+Helpers.swift */; }; @@ -852,8 +857,13 @@ CDF842672A49FB9000723DA0 /* Inbox View Logic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Inbox View Logic.swift"; sourceTree = ""; }; 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 = ""; }; + 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 = ""; }; 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 = ""; }; E49F0E752A90395400BC4EE3 /* NavigationPath+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NavigationPath+Helpers.swift"; sourceTree = ""; }; @@ -2214,6 +2224,7 @@ E47B2B742A902DB400629AF7 /* Route */ = { isa = PBXGroup; children = ( + E40E018B2AABF85500410B2C /* NavigationRoutes.swift */, E47B2B752A902DE200629AF7 /* SettingsRoutes.swift */, ); path = Route; @@ -2222,6 +2233,7 @@ E47B2B772A902E3C00629AF7 /* Router */ = { isa = PBXGroup; children = ( + E40E018D2AABFBDE00410B2C /* NavigationRouter.swift */, E4902BAA2A9024BF0054FB36 /* SettingsRouter.swift */, ); path = Router; @@ -2230,6 +2242,9 @@ E4902BA92A90245E0054FB36 /* Navigation */ = { isa = PBXGroup; children = ( + E40E018F2AABFC9300410B2C /* AnyNavigationPath.swift */, + E47478142AAC3C19001CB1AC /* NavigationContext.swift */, + E47478122AAC350E001CB1AC /* NavigationLink+Helpers.swift */, E49F0E752A90395400BC4EE3 /* NavigationPath+Helpers.swift */, E47B2B772A902E3C00629AF7 /* Router */, E47B2B742A902DB400629AF7 /* Route */, @@ -2433,8 +2448,10 @@ 637218472A3A2AAD008C4816 /* APICommentView.swift in Sources */, CDDCF6492A6641F0003DA3AC /* FancyTabItemLabelBuilder.swift in Sources */, 63344C542A07D193001BC616 /* FiltersSettingsView.swift in Sources */, + E40E01902AABFC9300410B2C /* AnyNavigationPath.swift in Sources */, 6372185C2A3A2AAD008C4816 /* APICommunity.swift in Sources */, 6372185D2A3A2AAD008C4816 /* APICommunityAggregates.swift in Sources */, + E47478152AAC3C19001CB1AC /* NavigationContext.swift in Sources */, CD04D5DB2A36154F008EF95B /* SaveButtonView.swift in Sources */, CDE6A80D2A45EAB30062D161 /* Embedded Post.swift in Sources */, B1955A212A6145C00056CF99 /* Environment - EasterFlagSetter.swift in Sources */, @@ -2450,6 +2467,7 @@ 034C724F2A82B61200B8A4B8 /* LayoutWidgetTracker.swift in Sources */, 637218682A3A2AAD008C4816 /* CreateComment.swift in Sources */, 637218722A3A2AAD008C4816 /* HideCommunity.swift in Sources */, + E47478132AAC350E001CB1AC /* NavigationLink+Helpers.swift in Sources */, 63344C6E2A097FA1001BC616 /* View - Hide View.swift in Sources */, 5064D0432A6E645D00B22EE3 /* Notifiable.swift in Sources */, 6318EDCB27EE4E2200BFCAE8 /* User.swift in Sources */, @@ -2496,6 +2514,7 @@ CD1446272A5B36DA00610EF1 /* EULA.swift in Sources */, 500C168E2A66FAAB006F243B /* HapticManager+Dependency.swift in Sources */, 038A16E52A7A97380087987E /* LayoutWidgetView.swift in Sources */, + E40E018E2AABFBDE00410B2C /* NavigationRouter.swift in Sources */, CD1446252A5B357900610EF1 /* Document.swift in Sources */, CDEBC32C2A9A582500518D9D /* Votes Model.swift in Sources */, CDEBC3282A9A57F200518D9D /* Content Model Identifier.swift in Sources */, @@ -2785,6 +2804,7 @@ 50BC1ABB2A8D6A5A00E3C48B /* ScoringOperation.swift in Sources */, CD18DC692A51ECB6002C56BC /* InteractionSwipeAndMenuHelpers.swift in Sources */, 6386E0362A042C59006B3C1D /* Contributor.swift in Sources */, + E40E018C2AABF85500410B2C /* NavigationRoutes.swift in Sources */, CD18DC6F2A5209C3002C56BC /* MarkPrivateMessageAsReadRequest.swift in Sources */, CD82A2552A716C7C00111034 /* APIPersonUnreadCounts.swift in Sources */, CD04D5E72A3636FB008EF95B /* Headline Post.swift in Sources */, diff --git a/Mlem/Extensions/Navigation getter.swift b/Mlem/Extensions/Navigation getter.swift index 78fb3339c..b0b9189f9 100644 --- a/Mlem/Extensions/Navigation getter.swift +++ b/Mlem/Extensions/Navigation getter.swift @@ -8,6 +8,8 @@ import Foundation import SwiftUI +// MARK: - SwiftUI.NavigationPath + private struct NavigationPathGetter: EnvironmentKey { static let defaultValue: Binding = .constant(NavigationPath()) } @@ -18,3 +20,16 @@ extension EnvironmentValues { set { self[NavigationPathGetter.self] = newValue } } } + +// MARK: - Mlem NavigationRoute + +private struct NavigationPathWithRoutes: EnvironmentKey { + static let defaultValue: Binding<[NavigationRoute]> = .constant([]) +} + +extension EnvironmentValues { + var navigationPathWithRoutes: Binding<[NavigationRoute]> { + get { self[NavigationPathWithRoutes.self] } + set { self[NavigationPathWithRoutes.self] = newValue } + } +} diff --git a/Mlem/Extensions/View - Handle Lemmy Links.swift b/Mlem/Extensions/View - Handle Lemmy Links.swift index a4df55512..16e095acb 100644 --- a/Mlem/Extensions/View - Handle Lemmy Links.swift +++ b/Mlem/Extensions/View - Handle Lemmy Links.swift @@ -15,63 +15,73 @@ struct HandleLemmyLinksDisplay: ViewModifier { @AppStorage("internetSpeed") var internetSpeed: InternetSpeed = .fast @AppStorage("defaultPostSorting") var defaultPostSorting: PostSortType = .hot - + @AppStorage("upvoteOnSave") var upvoteOnSave = false + + // swiftlint:disable function_body_length func body(content: Content) -> some View { content - .navigationDestination(for: APICommunityView.self) { context in - FeedView(community: context.community, feedType: .all, sortType: defaultPostSorting) - .environmentObject(appState) - .environmentObject(filtersTracker) - .environmentObject(CommunitySearchResultsTracker()) - } - .navigationDestination(for: APICommunity.self) { community in - FeedView(community: community, feedType: .all, sortType: defaultPostSorting) - .environmentObject(appState) - .environmentObject(filtersTracker) - .environmentObject(CommunitySearchResultsTracker()) - } - .navigationDestination(for: CommunityLinkWithContext.self) { context in - FeedView(community: context.community, feedType: context.feedType, sortType: defaultPostSorting) - .environmentObject(appState) + .navigationDestination(for: NavigationRoute.self) { route in + switch route { + case .apiCommunity(let community): + FeedView(community: community, feedType: .all, sortType: defaultPostSorting) + .environmentObject(appState) + .environmentObject(filtersTracker) + .environmentObject(CommunitySearchResultsTracker()) + case .apiCommunityView(let context): + FeedView(community: context.community, feedType: .all, sortType: defaultPostSorting) + .environmentObject(appState) + .environmentObject(filtersTracker) + .environmentObject(CommunitySearchResultsTracker()) + case .communityLinkWithContext(let context): + FeedView(community: context.community, feedType: context.feedType, sortType: defaultPostSorting) + .environmentObject(appState) + .environmentObject(filtersTracker) + .environmentObject(CommunitySearchResultsTracker()) + case .communitySidebarLinkWithContext(let context): + CommunitySidebarView( + community: context.community, + communityDetails: context.communityDetails + ) .environmentObject(filtersTracker) .environmentObject(CommunitySearchResultsTracker()) - } - .navigationDestination(for: CommunitySidebarLinkWithContext.self) { context in - CommunitySidebarView( - community: context.community, - communityDetails: context.communityDetails - ) - .environmentObject(filtersTracker) - .environmentObject(CommunitySearchResultsTracker()) - } - .navigationDestination(for: APIPost.self) { post in - LazyLoadExpandedPost(post: post) - } - .navigationDestination(for: PostLinkWithContext.self) { post in - ExpandedPost(post: post.post, scrollTarget: post.scrollTarget) - .environmentObject(post.postTracker) - .environmentObject(appState) - } - .navigationDestination(for: LazyLoadPostLinkWithContext.self) { post in - LazyLoadExpandedPost(post: post.post, scrollTarget: post.scrollTarget) - } - .navigationDestination(for: APIPerson.self) { user in - UserView(userID: user.id) - .environmentObject(appState) - } - .navigationDestination(for: UserModeratorLink.self) { user in - UserModeratorView(userDetails: user.user, moderatedCommunities: user.moderatedCommunities) - .environmentObject(appState) + case .apiPostView(let post): + let postModel = PostModel(from: post) + ExpandedPost(post: postModel) + .environmentObject( + PostTracker( + shouldPerformMergeSorting: false, + internetSpeed: internetSpeed, + initialItems: [postModel], + upvoteOnSave: upvoteOnSave + ) + ) + .environmentObject(appState) + case .apiPost(let post): + LazyLoadExpandedPost(post: post) + case .apiPerson(let user): + UserView(userID: user.id) + .environmentObject(appState) + case .postLinkWithContext(let post): + ExpandedPost(post: post.post, scrollTarget: post.scrollTarget) + .environmentObject(post.postTracker) + .environmentObject(appState) + case .lazyLoadPostLinkWithContext(let post): + LazyLoadExpandedPost(post: post.post, scrollTarget: post.scrollTarget) + case .userModeratorLink(let user): + UserModeratorView(userDetails: user.user, moderatedCommunities: user.moderatedCommunities) + .environmentObject(appState) + } } } + // swiftlint:enable function_body_length } -struct HandleLemmyLinkResolution: ViewModifier { +struct HandleLemmyLinkResolution: ViewModifier { @Dependency(\.apiClient) var apiClient @Dependency(\.errorHandler) var errorHandler @Dependency(\.notifier) var notifier - let navigationPath: Binding + let navigationPath: Binding func body(content: Content) -> some View { content @@ -165,7 +175,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/AnyNavigationPath.swift b/Mlem/Navigation/AnyNavigationPath.swift new file mode 100644 index 000000000..89fa0feb4 --- /dev/null +++ b/Mlem/Navigation/AnyNavigationPath.swift @@ -0,0 +1,28 @@ +// +// AnyNavigationPath.swift +// Mlem +// +// Created by Bosco Ho on 2023-09-08. +// + +import Foundation +import SwiftUI + +protocol AnyNavigationPath { + + /// 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: Hashable + + // swiftlint:disable identifier_name + /// Removes values from the end of this path. + mutating func removeLast(_ k: Int) + // swiftlint:enable identifier_name +} + +extension NavigationPath: AnyNavigationPath {} diff --git a/Mlem/Navigation/NavigationContext.swift b/Mlem/Navigation/NavigationContext.swift new file mode 100644 index 000000000..1fdb8651e --- /dev/null +++ b/Mlem/Navigation/NavigationContext.swift @@ -0,0 +1,16 @@ +// +// NavigationContext.swift +// Mlem +// +// Created by Bosco Ho on 2023-09-08. +// + +import Foundation + +/// You may wish to vary how to construct a `NavigationLink` or any other SwiftUI Navigation construct for a variety of reasons. +enum NavigationContext { + /// When presented inside a `NavigationSplitView` sidebar. + case sidebar + /// When presented in any view. + case view +} diff --git a/Mlem/Navigation/NavigationLink+Helpers.swift b/Mlem/Navigation/NavigationLink+Helpers.swift new file mode 100644 index 000000000..a18aab310 --- /dev/null +++ b/Mlem/Navigation/NavigationLink+Helpers.swift @@ -0,0 +1,16 @@ +// +// NavigationLink+Helpers.swift +// Mlem +// +// Created by Bosco Ho on 2023-09-08. +// + +import SwiftUI + +extension NavigationLink where Destination == Never { + + /// Convenience initializer. + init(_ route: NavigationRoute, @ViewBuilder label: () -> Label) { + self = .init(value: route, label: label) + } +} diff --git a/Mlem/Navigation/Route/NavigationRoutes.swift b/Mlem/Navigation/Route/NavigationRoutes.swift new file mode 100644 index 000000000..1063dfd63 --- /dev/null +++ b/Mlem/Navigation/Route/NavigationRoutes.swift @@ -0,0 +1,28 @@ +// +// NavigationRoutes.swift +// Mlem +// +// Created by Bosco Ho on 2023-09-08. +// + +import Foundation + +/// Possible routes for navigation links in `Mlem.app`. +/// +/// See `SettingsRoutes` for settings-related routes. +enum NavigationRoute: Hashable { + case apiCommunityView(APICommunityView) + case apiCommunity(APICommunity) + + case communityLinkWithContext(CommunityLinkWithContext) + case communitySidebarLinkWithContext(CommunitySidebarLinkWithContext) + + case apiPostView(APIPostView) + case apiPost(APIPost) + + case apiPerson(APIPerson) + + case postLinkWithContext(PostLinkWithContext) + case lazyLoadPostLinkWithContext(LazyLoadPostLinkWithContext) + case userModeratorLink(UserModeratorLink) +} diff --git a/Mlem/Navigation/Route/SettingsRoutes.swift b/Mlem/Navigation/Route/SettingsRoutes.swift index 048057322..b4dc96a02 100644 --- a/Mlem/Navigation/Route/SettingsRoutes.swift +++ b/Mlem/Navigation/Route/SettingsRoutes.swift @@ -7,7 +7,7 @@ import Foundation -enum SettingsRoute: Hashable, Codable { +enum SettingsRoute: Hashable { case accountsPage case general case accessibility @@ -15,6 +15,12 @@ enum SettingsRoute: Hashable, Codable { case contentFilters case about case advanced + + case aboutPage(AboutSettingsRoute) + case appearancePage(AppearanceSettingsRoute) + case commentPage(CommentSettingsRoute) + case postPage(PostSettingsRoute) + case licensesPage(LicensesSettingsRoute) } enum AppearanceSettingsRoute: Hashable, Codable { diff --git a/Mlem/Navigation/Router/NavigationRouter.swift b/Mlem/Navigation/Router/NavigationRouter.swift new file mode 100644 index 000000000..4642bdc30 --- /dev/null +++ b/Mlem/Navigation/Router/NavigationRouter.swift @@ -0,0 +1,36 @@ +// +// NavigationRouter.swift +// Mlem +// +// Created by Bosco Ho on 2023-09-08. +// + +import Foundation + +final class NavigationRouter: ObservableObject { + @Published var path: [Route] = [] +} + +extension NavigationRouter: AnyNavigationPath { + var count: Int { + path.count + } + + var isEmpty: Bool { + path.isEmpty + } + + func append(_ value: V) where V: Hashable { + assert(value is Route) + guard let route = value as? Route else { + 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/Router/SettingsRouter.swift b/Mlem/Navigation/Router/SettingsRouter.swift index 7362dd010..e3db731e5 100644 --- a/Mlem/Navigation/Router/SettingsRouter.swift +++ b/Mlem/Navigation/Router/SettingsRouter.swift @@ -10,11 +10,6 @@ import SwiftUI extension View { func useSettingsNavigationRouter() -> some View { modifier(SettingsRouter()) - .modifier(AppearanceSettingsRouter()) - .modifier(CommentSettingsRouter()) - .modifier(PostSettingsRouter()) - .modifier(AboutSettingsRouter()) - .modifier(LicensesSettingsRouter()) } } @@ -22,6 +17,7 @@ struct SettingsRouter: ViewModifier { @Environment(\.navigationPath) private var navigationPath @EnvironmentObject private var layoutWidgetTracker: LayoutWidgetTracker + // swiftlint:disable cyclomatic_complexity func body(content: Content) -> some View { content .navigationDestination(for: SettingsRoute.self) { route in @@ -40,96 +36,83 @@ struct SettingsRouter: ViewModifier { AboutView(navigationPath: navigationPath) case .advanced: AdvancedSettingsView() + case .aboutPage(let path): + aboutPageDestination(for: path) + case .appearancePage(let path): + appearancePageDestination(for: path) + case .commentPage(let path): + commentPageDestination(for: path) + case .postPage(let path): + postPageDestination(for: path) + case .licensesPage(let path): + licensesPageDestination(for: path) } } } -} - -private struct AppearanceSettingsRouter: ViewModifier { - func body(content: Content) -> some View { - content - .navigationDestination(for: AppearanceSettingsRoute.self) { route in - switch route { - case .theme: - ThemeSettingsView() - case .appIcon: - IconSettingsView() - case .posts: - PostSettingsView() - case .comments: - CommentSettingsView() - case .communities: - CommunitySettingsView() - case .users: - UserSettingsView() - case .tabBar: - TabBarSettingsView() - } - } + // swiftlint:enable cyclomatic_complexity + + @ViewBuilder + private func aboutPageDestination(for path: AboutSettingsRoute) -> some View { + switch path { + case .contributors: + ContributorsView() + case let .eula(doc): + DocumentView(text: doc.body) + case let .privacyPolicy(doc): + DocumentView(text: doc.body) + case .licenses: + LicensesView() + } } -} - -private struct CommentSettingsRouter: ViewModifier { - @EnvironmentObject private var layoutWidgetTracker: LayoutWidgetTracker - func body(content: Content) -> some View { - content - .navigationDestination(for: CommentSettingsRoute.self) { route in - switch route { - case .layoutWidget: - LayoutWidgetEditView(widgets: layoutWidgetTracker.groups.comment, onSave: { widgets in - layoutWidgetTracker.groups.comment = widgets - layoutWidgetTracker.saveLayoutWidgets() - }) - } - } + @ViewBuilder + private func appearancePageDestination(for path: AppearanceSettingsRoute) -> some View { + switch path { + case .theme: + ThemeSettingsView() + case .appIcon: + IconSettingsView() + case .posts: + PostSettingsView() + case .comments: + CommentSettingsView() + case .communities: + CommunitySettingsView() + case .users: + UserSettingsView() + case .tabBar: + TabBarSettingsView() + } } -} - -private struct PostSettingsRouter: ViewModifier { - @EnvironmentObject private var layoutWidgetTracker: LayoutWidgetTracker - - func body(content: Content) -> some View { - content - .navigationDestination(for: PostSettingsRoute.self) { route in - switch route { - 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 commentPageDestination(for path: CommentSettingsRoute) -> some View { + switch path { + case .layoutWidget: + LayoutWidgetEditView(widgets: layoutWidgetTracker.groups.comment, onSave: { widgets in + layoutWidgetTracker.groups.comment = widgets + layoutWidgetTracker.saveLayoutWidgets() + }) + } } -} - -private struct AboutSettingsRouter: ViewModifier { - func body(content: Content) -> some View { - content - .navigationDestination(for: AboutSettingsRoute.self) { route in - switch route { - case .contributors: - ContributorsView() - case let .eula(doc): - DocumentView(text: doc.body) - case let .privacyPolicy(doc): - DocumentView(text: doc.body) - case .licenses: - LicensesView() - } - } + + @ViewBuilder + private func postPageDestination(for path: PostSettingsRoute) -> some View { + switch path { + 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() + }) + } } -} - -private struct LicensesSettingsRouter: ViewModifier { - func body(content: Content) -> some View { - content - .navigationDestination(for: LicensesSettingsRoute.self) { route in - switch route { - case let .licenseDocument(doc): - DocumentView(text: doc.body) - } - } + + @ViewBuilder + private func licensesPageDestination(for path: LicensesSettingsRoute) -> some View { + switch path { + case let .licenseDocument(doc): + DocumentView(text: doc.body) + } } } diff --git a/Mlem/Views/Shared/Comments/Components/Embedded Post.swift b/Mlem/Views/Shared/Comments/Components/Embedded Post.swift index 6a4c17ade..d62d137d0 100644 --- a/Mlem/Views/Shared/Comments/Components/Embedded Post.swift +++ b/Mlem/Views/Shared/Comments/Components/Embedded Post.swift @@ -31,10 +31,10 @@ struct EmbeddedPost: View { // - enrich info // - navigation link to post var body: some View { - NavigationLink(value: LazyLoadPostLinkWithContext( + NavigationLink(.lazyLoadPostLinkWithContext(.init( post: post, scrollTarget: comment.id - )) { + ))) { postLinkButton() } } diff --git a/Mlem/Views/Shared/Links/Community Link View.swift b/Mlem/Views/Shared/Links/Community Link View.swift new file mode 100644 index 000000000..4d63f92d8 --- /dev/null +++ b/Mlem/Views/Shared/Links/Community Link View.swift @@ -0,0 +1,183 @@ +// +// Community Link.swift +// Mlem +// +// Created by Eric Andrews on 2023-06-27. +// + +import Foundation +import SwiftUI + +private let clipOptOut = ["beehaw.org"] + +func shouldClipAvatar(community: APICommunity) -> Bool { + guard let hostString = community.actorId.host else { + return true + } + + return !clipOptOut.contains(hostString) +} + +func shouldClipAvatar(url: URL?) -> Bool { + guard let hostString = url?.host else { + return true + } + + return !clipOptOut.contains(hostString) +} + +struct CommunityLinkView: View { + let community: APICommunity + let serverInstanceLocation: ServerInstanceLocation + let extraText: String? + let overrideShowAvatar: Bool? // if present, shows or hides avatar according to value; otherwise uses system setting + + init( + community: APICommunity, + serverInstanceLocation: ServerInstanceLocation = .bottom, + overrideShowAvatar: Bool? = nil, + extraText: String? = nil + ) { + self.community = community + self.serverInstanceLocation = serverInstanceLocation + self.extraText = extraText + self.overrideShowAvatar = overrideShowAvatar + } + + var body: some View { + NavigationLink(.apiCommunity(community)) { + HStack { + CommunityLabel( + community: community, + serverInstanceLocation: serverInstanceLocation, + overrideShowAvatar: overrideShowAvatar + ) + Spacer() + if let text = extraText { + Text(text) + } + } + } + } +} + +struct CommunityLabel: View { + // settings + @AppStorage("shouldShowCommunityIcons") var shouldShowCommunityIcons: Bool = true + @AppStorage("shouldBlurNsfw") var shouldBlurNsfw: Bool = true + + // parameters + let community: APICommunity + let serverInstanceLocation: ServerInstanceLocation + let overrideShowAvatar: Bool? // if present, shows or hides the avatar according to value; otherwise uses system setting + + // computed + var blurAvatar: Bool { shouldBlurNsfw && community.nsfw } + var avatarSize: CGSize { serverInstanceLocation == .bottom + ? CGSize(width: AppConstants.largeAvatarSize, height: AppConstants.largeAvatarSize) + : CGSize(width: AppConstants.smallAvatarSize, height: AppConstants.smallAvatarSize) + } + + var showAvatar: Bool { + if let overrideShowAvatar { + return overrideShowAvatar + } else { + return shouldShowCommunityIcons + } + } + + init( + community: APICommunity, + serverInstanceLocation: ServerInstanceLocation, + overrideShowAvatar: Bool? = nil + ) { + self.community = community + self.serverInstanceLocation = serverInstanceLocation + self.overrideShowAvatar = overrideShowAvatar + } + + var body: some View { + Group { + HStack(alignment: .bottom, spacing: AppConstants.largeAvatarSpacing) { + if showAvatar { + communityAvatar + .blur(radius: blurAvatar ? 4 : 0) + .clipShape(Circle()) + .overlay(Circle() + .stroke( + Color(UIColor.secondarySystemBackground), + lineWidth: shouldClipAvatar(community: community) ? 1 : 0 + )) + } + + switch serverInstanceLocation { + case .disabled: + communityName + case .trailing: + HStack(spacing: 0) { + communityName + communityInstance + } + .foregroundColor(.secondary) + case .bottom: + VStack(alignment: .leading) { + communityName + communityInstance + } + } + } + .accessibilityElement(children: .combine) + } + } + + @ViewBuilder + private var communityName: some View { + Text(community.name) + .dynamicTypeSize(.small ... .accessibility1) + .font(.footnote) + .bold() + .foregroundColor(.secondary) + } + + @ViewBuilder + private var communityInstance: some View { + if let host = community.actorId.host() { + Text("@\(host)") + .dynamicTypeSize(.small ... .accessibility2) + .lineLimit(1) + .foregroundColor(Color(uiColor: .tertiaryLabel)) + .font(.caption) + .allowsHitTesting(false) + } else { + EmptyView() + } + } + + @ViewBuilder + private var communityAvatar: some View { + Group { + if let url = community.icon { + CachedImage( + url: url.withIcon64Parameters, + shouldExpand: false, + fixedSize: avatarSize, + imageNotFound: defaultCommunityAvatar, + contentMode: .fill + ) + } else { + defaultCommunityAvatar() + } + } + .frame(width: avatarSize.width, height: avatarSize.height) + .accessibilityHidden(true) + } + + private func defaultCommunityAvatar() -> AnyView { + AnyView(Image(systemName: "building.2.crop.circle.fill") + .resizable() + .scaledToFit() + .frame(width: avatarSize.width, height: avatarSize.height) + .foregroundColor(.secondary) + ) + } +} diff --git a/Mlem/Views/Shared/Links/User/UserLinkView.swift b/Mlem/Views/Shared/Links/User/UserLinkView.swift index 7223ed01f..1d3b00eb7 100644 --- a/Mlem/Views/Shared/Links/User/UserLinkView.swift +++ b/Mlem/Views/Shared/Links/User/UserLinkView.swift @@ -32,7 +32,7 @@ struct UserLinkView: View { } var body: some View { - NavigationLink(value: user) { + NavigationLink(.apiPerson(user)) { UserLabelView( user: user, serverInstanceLocation: serverInstanceLocation, diff --git a/Mlem/Views/Tabs/Feeds/Community List/Community List View.swift b/Mlem/Views/Tabs/Feeds/Community List/Community List View.swift index 2841d2238..d04398d7f 100644 --- a/Mlem/Views/Tabs/Feeds/Community List/Community List View.swift +++ b/Mlem/Views/Tabs/Feeds/Community List/Community List View.swift @@ -36,20 +36,23 @@ struct CommunityListView: View { feedType: .subscribed, iconName: AppConstants.subscribedFeedSymbolNameFill, iconColor: .red, - description: "Subscribed communities from all servers" + description: "Subscribed communities from all servers", + navigationContext: .sidebar ) .id("top") // For "scroll to top" sidebar item HomepageFeedRowView( feedType: .local, iconName: AppConstants.localFeedSymbolNameFill, iconColor: .green, - description: "Local communities from your server" + description: "Local communities from your server", + navigationContext: .sidebar ) HomepageFeedRowView( feedType: .all, iconName: AppConstants.federatedFeedSymbolNameFill, iconColor: .blue, - description: "All communities that federate with your server" + description: "All communities that federate with your server", + navigationContext: .sidebar ) ForEach(model.visibleSections) { section in @@ -58,7 +61,8 @@ struct CommunityListView: View { CommuntiyFeedRowView( community: community, subscribed: model.isSubscribed(to: community), - communitySubscriptionChanged: model.updateSubscriptionStatus + communitySubscriptionChanged: model.updateSubscriptionStatus, + navigationContext: .sidebar ) } } diff --git a/Mlem/Views/Tabs/Feeds/Community List/Components/CommunityListRowViews.swift b/Mlem/Views/Tabs/Feeds/Community List/Components/CommunityListRowViews.swift index 66b411898..5f1b76f53 100644 --- a/Mlem/Views/Tabs/Feeds/Community List/Components/CommunityListRowViews.swift +++ b/Mlem/Views/Tabs/Feeds/Community List/Components/CommunityListRowViews.swift @@ -28,9 +28,10 @@ struct CommuntiyFeedRowView: View { let community: APICommunity let subscribed: Bool let communitySubscriptionChanged: (APICommunity, Bool) -> Void + let navigationContext: NavigationContext var body: some View { - NavigationLink(value: CommunityLinkWithContext(community: community, feedType: .subscribed)) { + NavigationLink(value: pathValue) { HStack { // NavigationLink with invisible array communityNameLabel @@ -66,6 +67,15 @@ struct CommuntiyFeedRowView: View { .accessibilityLabel(communityLabel) } + private var pathValue: AnyHashable { + if navigationContext == .sidebar { + 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)) + } + } + private var communityNameText: Text { Text(community.name) } @@ -126,9 +136,10 @@ struct HomepageFeedRowView: View { let iconName: String let iconColor: Color let description: String + let navigationContext: NavigationContext var body: some View { - NavigationLink(value: CommunityLinkWithContext(community: nil, feedType: feedType)) { + NavigationLink(value: pathValue) { HStack { Image(systemName: iconName).resizable() .frame(width: 36, height: 36).foregroundColor(iconColor) @@ -141,4 +152,13 @@ struct HomepageFeedRowView: View { .accessibilityElement(children: .combine) } } + + private var pathValue: AnyHashable { + if navigationContext == .sidebar { + 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)) + } + } } diff --git a/Mlem/Views/Tabs/Feeds/Components/Sidebar View.swift b/Mlem/Views/Tabs/Feeds/Components/Sidebar View.swift index b96cd98d8..9c97a51f2 100644 --- a/Mlem/Views/Tabs/Feeds/Components/Sidebar View.swift +++ b/Mlem/Views/Tabs/Feeds/Components/Sidebar View.swift @@ -91,7 +91,7 @@ struct CommunitySidebarView: View { Divider() ForEach(communityDetails.moderators) { moderatorView in - NavigationLink(value: moderatorView.moderator) { + NavigationLink(.apiPerson(moderatorView.moderator)) { HStack { UserLabelView( user: moderatorView.moderator, diff --git a/Mlem/Views/Tabs/Feeds/Feed Root.swift b/Mlem/Views/Tabs/Feeds/Feed Root.swift index 12453477e..d4cb7f7d4 100644 --- a/Mlem/Views/Tabs/Feeds/Feed Root.swift +++ b/Mlem/Views/Tabs/Feeds/Feed Root.swift @@ -16,7 +16,7 @@ struct FeedRoot: View { @AppStorage("defaultFeed") var defaultFeed: FeedType = .subscribed @AppStorage("defaultPostSorting") var defaultPostSorting: PostSortType = .hot - @State var navigationPath = NavigationPath() + @StateObject private var feedRouter: NavigationRouter = .init() @State var rootDetails: CommunityLinkWithContext? @@ -27,7 +27,7 @@ struct FeedRoot: View { CommunityListView(selectedCommunity: $rootDetails) } detail: { if let rootDetails { - NavigationStack(path: $navigationPath) { + NavigationStack(path: $feedRouter.path) { FeedView( community: rootDetails.community, feedType: rootDetails.feedType, @@ -43,9 +43,9 @@ struct FeedRoot: View { } } .handleLemmyLinkResolution( - navigationPath: $navigationPath + navigationPath: .constant(feedRouter) ) - .environment(\.navigationPath, $navigationPath) + .environmentObject(feedRouter) .environmentObject(appState) .onAppear { if rootDetails == nil || shortcutItemToProcess != nil { @@ -63,7 +63,7 @@ struct FeedRoot: View { rootDetails = CommunityLinkWithContext(community: nil, feedType: defaultFeed) } - _ = HandleLemmyLinkResolution(navigationPath: $navigationPath) + _ = HandleLemmyLinkResolution(navigationPath: .constant(feedRouter)) .didReceiveURL(url) } } diff --git a/Mlem/Views/Tabs/Feeds/Feed View.swift b/Mlem/Views/Tabs/Feeds/Feed View.swift index de84f23cf..d092d3e37 100644 --- a/Mlem/Views/Tabs/Feeds/Feed View.swift +++ b/Mlem/Views/Tabs/Feeds/Feed View.swift @@ -166,7 +166,7 @@ struct FeedView: View { private func feedPost(for post: PostModel) -> some View { VStack(spacing: 0) { // TODO: reenable nav - NavigationLink(value: PostLinkWithContext(post: post, postTracker: postTracker)) { + NavigationLink(.postLinkWithContext(.init(post: post, postTracker: postTracker))) { FeedPost( post: post, showPostCreator: shouldShowPostCreator, @@ -189,13 +189,14 @@ struct FeedView: View { Menu { if let community, let communityDetails { // until we find a nice way to put nav stuff in MenuFunction, this'll have to do :( - NavigationLink(value: - CommunitySidebarLinkWithContext( + NavigationLink(.communitySidebarLinkWithContext( + .init( community: community, communityDetails: communityDetails - )) { - Label("Sidebar", systemImage: "sidebar.right") - } + ) + )) { + Label("Sidebar", systemImage: "sidebar.right") + } ForEach(genCommunitySpecificMenuFunctions(for: community)) { menuFunction in MenuButton(menuFunction: menuFunction, confirmDestructive: confirmDestructive) @@ -251,16 +252,15 @@ struct FeedView: View { @ViewBuilder private var toolbarHeader: some View { if let community { - NavigationLink(value: - CommunitySidebarLinkWithContext( - community: community, - communityDetails: communityDetails - )) { - Text(community.name) - .font(.headline) - .foregroundColor(.primary) - .accessibilityHint("Activate to view sidebar.") - } + NavigationLink(.communitySidebarLinkWithContext(.init( + community: community, + communityDetails: communityDetails + ))) { + Text(community.name) + .font(.headline) + .foregroundColor(.primary) + .accessibilityHint("Activate to view sidebar.") + } } else { Menu { ForEach(genFeedSwitchingFunctions()) { menuFunction in diff --git a/Mlem/Views/Tabs/Inbox/Feed/Mentions Feed View.swift b/Mlem/Views/Tabs/Inbox/Feed/Mentions Feed View.swift index 0d32c4a1e..bab253911 100644 --- a/Mlem/Views/Tabs/Inbox/Feed/Mentions Feed View.swift +++ b/Mlem/Views/Tabs/Inbox/Feed/Mentions Feed View.swift @@ -51,10 +51,10 @@ extension InboxView { } func inboxMentionViewWithInteraction(mention: APIPersonMentionView) -> some View { - NavigationLink(value: LazyLoadPostLinkWithContext( + NavigationLink(.lazyLoadPostLinkWithContext(.init( post: mention.post, scrollTarget: mention.comment.id - )) { + ))) { InboxMentionView(mention: mention, menuFunctions: genMentionMenuGroup(mention: mention)) .padding(.vertical, AppConstants.postAndCommentSpacing) .padding(.horizontal) diff --git a/Mlem/Views/Tabs/Inbox/Feed/Replies Feed View.swift b/Mlem/Views/Tabs/Inbox/Feed/Replies Feed View.swift index f3e33dba7..6f2ac5a3a 100644 --- a/Mlem/Views/Tabs/Inbox/Feed/Replies Feed View.swift +++ b/Mlem/Views/Tabs/Inbox/Feed/Replies Feed View.swift @@ -56,10 +56,10 @@ extension InboxView { } func inboxReplyViewWithInteraction(reply: APICommentReplyView) -> some View { - NavigationLink(value: LazyLoadPostLinkWithContext( + NavigationLink(.lazyLoadPostLinkWithContext(.init( post: reply.post, scrollTarget: reply.comment.id - )) { + ))) { InboxReplyView(reply: reply, menuFunctions: genCommentReplyMenuGroup(commentReply: reply)) .padding(.vertical, AppConstants.postAndCommentSpacing) .padding(.horizontal) diff --git a/Mlem/Views/Tabs/Inbox/Inbox View.swift b/Mlem/Views/Tabs/Inbox/Inbox View.swift index 023c4710f..0adae107f 100644 --- a/Mlem/Views/Tabs/Inbox/Inbox View.swift +++ b/Mlem/Views/Tabs/Inbox/Inbox View.swift @@ -84,11 +84,11 @@ struct InboxView: View { @State var curTab: InboxTab = .all // utility - @State private var navigationPath = NavigationPath() + @StateObject private var inboxRouter: NavigationRouter = .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: $navigationPath) { + NavigationStack(path: $inboxRouter.path) { contentView .navigationTitle("Inbox") .navigationBarTitleDisplayMode(.inline) diff --git a/Mlem/Views/Tabs/Profile/Profile View.swift b/Mlem/Views/Tabs/Profile/Profile View.swift index 09b9fabd5..e7dec5d58 100644 --- a/Mlem/Views/Tabs/Profile/Profile View.swift +++ b/Mlem/Views/Tabs/Profile/Profile View.swift @@ -17,14 +17,14 @@ struct ProfileView: View { @Environment(\.tabSelectionHashValue) private var selectedTagHashValue @Environment(\.tabNavigationSelectionHashValue) private var selectedNavigationTabHashValue - @State private var navigationPath = NavigationPath() + @StateObject private var profileRouter: NavigationRouter = .init() var body: some View { - NavigationStack(path: $navigationPath) { + NavigationStack(path: $profileRouter.path) { UserView(userID: userID) .handleLemmyViews() } - .handleLemmyLinkResolution(navigationPath: $navigationPath) + .handleLemmyLinkResolution(navigationPath: .constant(profileRouter)) .onChange(of: selectedTagHashValue) { newValue in if newValue == TabSelection.profile.hashValue { print("switched to Profile tab") diff --git a/Mlem/Views/Tabs/Profile/User View.swift b/Mlem/Views/Tabs/Profile/User View.swift index 5d7013bf8..9417fa337 100644 --- a/Mlem/Views/Tabs/Profile/User View.swift +++ b/Mlem/Views/Tabs/Profile/User View.swift @@ -78,7 +78,7 @@ struct UserView: View { @ViewBuilder private var moderatorButton: some View { if let user = userDetails, !moderatedCommunities.isEmpty { - NavigationLink(value: UserModeratorLink(user: user, moderatedCommunities: moderatedCommunities)) { + NavigationLink(.userModeratorLink(.init(user: user, moderatedCommunities: moderatedCommunities))) { Image(systemName: "shield") } } diff --git a/Mlem/Views/Tabs/Profile/UserFeedView.swift b/Mlem/Views/Tabs/Profile/UserFeedView.swift index 4fce46bbb..f7d06917c 100644 --- a/Mlem/Views/Tabs/Profile/UserFeedView.swift +++ b/Mlem/Views/Tabs/Profile/UserFeedView.swift @@ -70,7 +70,7 @@ struct UserFeedView: View { } private func postEntry(for post: PostModel) -> some View { - NavigationLink(value: PostLinkWithContext(post: post, postTracker: privatePostTracker)) { + NavigationLink(.postLinkWithContext(.init(post: post, postTracker: privatePostTracker))) { VStack(spacing: 0) { FeedPost( post: post, diff --git a/Mlem/Views/Tabs/Search/Search View.swift b/Mlem/Views/Tabs/Search/Search View.swift index 801f35cfa..135e3c7a3 100644 --- a/Mlem/Views/Tabs/Search/Search View.swift +++ b/Mlem/Views/Tabs/Search/Search View.swift @@ -30,20 +30,20 @@ struct SearchView: View { @State private var searchPage: Int = 1 @State private var hasMorePages: Bool = true - @State private var navigationPath = NavigationPath() + @StateObject private var searchRouter: NavigationRouter = .init() // constants private let searchPageSize = 50 var body: some View { - NavigationStack(path: $navigationPath) { + NavigationStack(path: $searchRouter.path) { content .handleLemmyViews() .navigationBarTitleDisplayMode(.inline) .navigationBarColor() .navigationTitle("Search") } - .handleLemmyLinkResolution(navigationPath: $navigationPath) + .handleLemmyLinkResolution(navigationPath: .constant(searchRouter)) .searchable(text: getSearchTextBinding(), prompt: "Search for communities") .autocorrectionDisabled(true) .textInputAutocapitalization(.never) diff --git a/Mlem/Views/Tabs/Settings/Components/Settings View.swift b/Mlem/Views/Tabs/Settings/Components/Settings View.swift index 7beeed45e..f78f75334 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 - @State var navigationPath = NavigationPath() + @StateObject private var settingsRouter: NavigationRouter = .init() @Environment(\.openURL) private var openURL @Environment(\.tabSelectionHashValue) private var selectedTagHashValue @@ -19,8 +19,8 @@ struct SettingsView: View { @Namespace var scrollToTop var body: some View { - NavigationStack(path: $navigationPath) { - ScrollViewReader { proxy in + NavigationStack(path: $settingsRouter.path) { + ScrollViewReader { _ in List { Section { NavigationLink(value: SettingsRoute.accountsPage) { @@ -63,19 +63,10 @@ struct SettingsView: View { .onChange(of: selectedNavigationTabHashValue) { newValue in if newValue == TabSelection.settings.hashValue { print("re-selected \(TabSelection.settings) tab") - #if DEBUG - if navigationPath.isEmpty { - withAnimation { - proxy.scrollTo(scrollToTop, anchor: .bottom) - } - } else { - navigationPath.goBack() - } - #endif } } } - .environment(\.navigationPath, $navigationPath) + .environmentObject(settingsRouter) .fancyTabScrollCompatible() .handleLemmyViews() .navigationTitle("Settings") @@ -83,7 +74,7 @@ struct SettingsView: View { .navigationBarTitleDisplayMode(.inline) .useSettingsNavigationRouter() } - .handleLemmyLinkResolution(navigationPath: $navigationPath) + .handleLemmyLinkResolution(navigationPath: .constant(settingsRouter)) .onChange(of: selectedTagHashValue) { newValue in if newValue == TabSelection.settings.hashValue { print("switched to Settings tab") diff --git a/Mlem/Views/Tabs/Settings/Components/Views/About/AboutView.swift b/Mlem/Views/Tabs/Settings/Components/Views/About/AboutView.swift index 95ee274ae..3b09722db 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: AboutSettingsRoute.contributors) { + NavigationLink(value: SettingsRoute.aboutPage(.contributors)) { Label("Contributors", systemImage: "person.2.fill").labelStyle(SquircleLabelStyle(color: .teal)) } } Section { - NavigationLink(value: AboutSettingsRoute.privacyPolicy(privacyPolicy)) { + NavigationLink(value: SettingsRoute.aboutPage(.privacyPolicy(privacyPolicy))) { Label("Privacy Policy", systemImage: "hand.raised.fill").labelStyle(SquircleLabelStyle(color: .blue)) } - NavigationLink(value: AboutSettingsRoute.eula(eula)) { + NavigationLink(value: SettingsRoute.aboutPage(.eula(eula))) { Label("EULA", systemImage: "doc.plaintext.fill").labelStyle(SquircleLabelStyle(color: .purple)) } - NavigationLink(value: AboutSettingsRoute.licenses) { + NavigationLink(value: SettingsRoute.aboutPage(.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 b2b9e478c..9d1d33074 100644 --- a/Mlem/Views/Tabs/Settings/Components/Views/About/LicensesView.swift +++ b/Mlem/Views/Tabs/Settings/Components/Views/About/LicensesView.swift @@ -28,15 +28,17 @@ struct LicensesView: View { } Section("Open Source Licenses") { - NavigationLink("KeychainAccess", value: LicensesSettingsRoute.licenseDocument(keychainAccessLicense)) + NavigationLink("KeychainAccess", value: SettingsRoute.licensesPage(.licenseDocument(keychainAccessLicense))) - NavigationLink("Nuke", value: LicensesSettingsRoute.licenseDocument(nukeLicense)) + NavigationLink("Nuke", value: SettingsRoute.licensesPage(.licenseDocument(nukeLicense))) - NavigationLink("Swift Dependencies", value: LicensesSettingsRoute.licenseDocument(swiftDependenciesLicense)) + NavigationLink("Swift Dependencies", value: SettingsRoute.licensesPage(.licenseDocument(swiftDependenciesLicense))) - NavigationLink("Swift Markdown UI", value: LicensesSettingsRoute.licenseDocument(swiftMarkdownUILIcense)) + NavigationLink("Swift Markdown UI", value: SettingsRoute.licensesPage(.licenseDocument(swiftMarkdownUILIcense))) - NavigationLink("Awesome Lemmy Instances", value: LicensesSettingsRoute.licenseDocument(awesomeLemmyInstancesLicense)) + NavigationLink( + "Awesome Lemmy Instances", + value: SettingsRoute.licensesPage(.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 7b472a9e4..9bd166de1 100644 --- a/Mlem/Views/Tabs/Settings/Components/Views/Appearance/AppearanceSettingsView.swift +++ b/Mlem/Views/Tabs/Settings/Components/Views/Appearance/AppearanceSettingsView.swift @@ -13,7 +13,7 @@ struct AppearanceSettingsView: View { var body: some View { List { Section { - NavigationLink(value: AppearanceSettingsRoute.theme) { + NavigationLink(value: SettingsRoute.appearancePage(.theme)) { switch lightOrDarkMode { case .unspecified: ThemeLabel(title: "Theme", color1: .white, color2: .black) @@ -26,7 +26,7 @@ struct AppearanceSettingsView: View { } } #if !os(macOS) && !targetEnvironment(macCatalyst) - NavigationLink(value: AppearanceSettingsRoute.appIcon) { + NavigationLink(value: SettingsRoute.appearancePage(.appIcon)) { Label { Text("App Icon") } icon: { @@ -41,24 +41,24 @@ struct AppearanceSettingsView: View { } Section { - NavigationLink(value: AppearanceSettingsRoute.posts) { + NavigationLink(value: SettingsRoute.appearancePage(.posts)) { Label("Posts", systemImage: "doc.plaintext.fill").labelStyle(SquircleLabelStyle(color: .pink)) } - NavigationLink(value: AppearanceSettingsRoute.comments) { + NavigationLink(value: SettingsRoute.appearancePage(.comments)) { Label("Comments", systemImage: "bubble.left.fill").labelStyle(SquircleLabelStyle(color: .orange)) } - NavigationLink(value: AppearanceSettingsRoute.communities) { + NavigationLink(value: SettingsRoute.appearancePage(.communities)) { Label("Communities", systemImage: "house.fill").labelStyle(SquircleLabelStyle(color: .green, fontSize: 15)) } - NavigationLink(value: AppearanceSettingsRoute.users) { + NavigationLink(value: SettingsRoute.appearancePage(.users)) { Label("Users", systemImage: "person.fill").labelStyle(SquircleLabelStyle(color: .blue)) } } Section { - NavigationLink(value: AppearanceSettingsRoute.tabBar) { + NavigationLink(value: SettingsRoute.appearancePage(.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 08e7de673..734fe38ea 100644 --- a/Mlem/Views/Tabs/Settings/Components/Views/Appearance/Comment/CommentSettingsView.swift +++ b/Mlem/Views/Tabs/Settings/Components/Views/Appearance/Comment/CommentSettingsView.swift @@ -49,7 +49,7 @@ struct CommentSettingsView: View { isTicked: $compactComments ) - NavigationLink(value: CommentSettingsRoute.layoutWidget) { + NavigationLink(value: SettingsRoute.commentPage(.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 d32ab15ce..f50db834e 100644 --- a/Mlem/Views/Tabs/Settings/Components/Views/Appearance/Post/PostSettingsView.swift +++ b/Mlem/Views/Tabs/Settings/Components/Views/Appearance/Post/PostSettingsView.swift @@ -49,7 +49,7 @@ struct PostSettingsView: View { options: PostSize.allCases ) - NavigationLink(value: PostSettingsRoute.customizeWidgets) { + NavigationLink(value: SettingsRoute.postPage(.customizeWidgets)) { Label { Text("Customize Widgets") } icon: {