Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix "Official Community" link, Improve navigation routing pattern #656

Merged
merged 34 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
706261d
- Remove `SettingsRouter`.
boscojwho Sep 27, 2023
6ff9b6c
- Merge SettingsRoutes into NavigationRoutes.
boscojwho Sep 27, 2023
ec4f17e
Rename navigation types for clarity.
boscojwho Sep 27, 2023
1555465
Rename vars for clarity.
boscojwho Sep 27, 2023
a282bf4
- Add `DestinationValue`.
boscojwho Sep 27, 2023
78ca37c
- Move `OnboardingRoutes` to navigation directory to separate views f…
boscojwho Sep 27, 2023
d9e57a9
Merge branch 'dev' into bosco/improve-navigation-routing-pattern
boscojwho Oct 4, 2023
7fad834
Fix Xcode project directory file ref (OnboardingRoutes.swift).
boscojwho Oct 4, 2023
c8b5ce0
Revert "Fix Xcode project directory file ref (OnboardingRoutes.swift)."
boscojwho Oct 7, 2023
7b25276
Revert "Merge branch 'dev' into bosco/improve-navigation-routing-patt…
boscojwho Oct 7, 2023
e9d3864
resolved email verification crash (#659) [f643b138e5b371559a15e562462…
boscojwho Oct 11, 2023
ebdbdaa
Fix widget wizard rect bug (#668)
boscojwho Oct 11, 2023
dab40ec
Added updated time display (#678)
boscojwho Oct 11, 2023
5121d7c
Fixed scrolling regression (#677)
boscojwho Oct 11, 2023
7afb20f
Fix crash when post object originates from link resolver (#669)
boscojwho Oct 11, 2023
59b32b0
Image uploading (#652)
boscojwho Oct 11, 2023
86c7cdb
Improved Search (#646)
boscojwho Oct 11, 2023
60f039a
Fixed swipey action bad behavior with context menus (#680)
boscojwho Oct 11, 2023
2dcf7be
Show error when instance is private (#683)
boscojwho Oct 11, 2023
f322e89
Made recent searches be stored per-account (#684)
boscojwho Oct 11, 2023
8185f66
Fixed default sidebar avatars (#689)
boscojwho Oct 11, 2023
faa224d
Updated account transition (#685)
boscojwho Oct 11, 2023
5e91e98
FIx issue where swipey view actions publishes updates on background t…
boscojwho Oct 11, 2023
46339b0
Optimized search view rendering (#695)
boscojwho Oct 11, 2023
45bf53b
Fix info stack alignment (#697)
boscojwho Oct 11, 2023
4eb983f
Allow disabling swipe-up for account switcher (#698)
boscojwho Oct 11, 2023
b6dfa96
Fixed profile refresh problems (#687)
boscojwho Oct 11, 2023
222299e
Fixed profile refresh problems (#687)
boscojwho Oct 11, 2023
37324b1
Avoid search rate limit (#704)
boscojwho Oct 11, 2023
3189169
Fix search home page refresh bug (#706)
boscojwho Oct 11, 2023
16683a0
Swipe to delete recent search (#703)
boscojwho Oct 11, 2023
b19c9c8
fixed compact comments not appropriately displaying vote status (#712)
boscojwho Oct 11, 2023
0df4cf1
- Fix some build errors.
boscojwho Oct 11, 2023
d707ccd
Merge branch 'dev' into bosco/improve-navigation-routing-pattern
boscojwho Oct 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 33 additions & 33 deletions Mlem.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Mlem/Extensions/Navigation getter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ 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 }
}
Expand Down
104 changes: 101 additions & 3 deletions Mlem/Extensions/View - Handle Lemmy Links.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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)
Expand Down Expand Up @@ -70,13 +73,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(navigationPath: navigationPath)
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<Path: AnyNavigationPath>: ViewModifier {
struct HandleLemmyLinkResolution<Path: AnyNavigablePath>: ViewModifier {
@Dependency(\.apiClient) var apiClient
@Dependency(\.errorHandler) var errorHandler
@Dependency(\.notifier) var notifier
Expand Down Expand Up @@ -180,7 +278,7 @@ extension View {
modifier(HandleLemmyLinksDisplay())
}

func handleLemmyLinkResolution<P: AnyNavigationPath>(navigationPath: Binding<P>) -> some View {
func handleLemmyLinkResolution<P: AnyNavigablePath>(navigationPath: Binding<P>) -> some View {
modifier(HandleLemmyLinkResolution(navigationPath: navigationPath))
}
}
31 changes: 31 additions & 0 deletions Mlem/Navigation/AnyNavigablePath.swift
Original file line number Diff line number Diff line change
@@ -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<V>(_ 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<V>(_ 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
}
45 changes: 32 additions & 13 deletions Mlem/Navigation/AnyNavigationPath.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<RouteValue: Routable>: 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<V>(_ value: V) throws -> Route where V: Hashable
}

extension AnyNavigationPath: AnyNavigablePath {

typealias Route = RouteValue

static func makeRoute<V>(_ 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<V>(_ value: V) where V: Routable
func append<V>(_ 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
}
20 changes: 20 additions & 0 deletions Mlem/Navigation/Destination Values/DestinationValue.swift
Original file line number Diff line number Diff line change
@@ -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 {}
47 changes: 47 additions & 0 deletions Mlem/Navigation/Destination Values/SettingsValues.swift
Original file line number Diff line number Diff line change
@@ -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)
}
2 changes: 1 addition & 1 deletion Mlem/Navigation/NavigationLink+Helpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Loading