Skip to content

Commit

Permalink
Fix router lemmy link resolution (#644)
Browse files Browse the repository at this point in the history
  • Loading branch information
boscojwho authored Sep 27, 2023
1 parent 67a3c3c commit 6348989
Show file tree
Hide file tree
Showing 8 changed files with 270 additions and 26 deletions.
16 changes: 16 additions & 0 deletions Mlem.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -430,11 +430,13 @@
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 */; };
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 */; };
E4DDB4322A81819300B3A7E0 /* Double.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4DDB4312A81819300B3A7E0 /* Double.swift */; };
E4DDB4342A819C8000B3A7E0 /* QuickLookView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4DDB4332A819C8000B3A7E0 /* QuickLookView.swift */; };
E4F0B5722AC2581800BC3E4A /* RoutableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F0B5712AC2581800BC3E4A /* RoutableTests.swift */; };
E4F0B56F2ABD00A000BC3E4A /* PresentationBackgroundInteraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F0B56E2ABD00A000BC3E4A /* PresentationBackgroundInteraction.swift */; };
/* End PBXBuildFile section */

Expand Down Expand Up @@ -877,11 +879,13 @@
E47478142AAC3C19001CB1AC /* NavigationContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationContext.swift; sourceTree = "<group>"; };
E47B2B752A902DE200629AF7 /* SettingsRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsRoutes.swift; sourceTree = "<group>"; };
E4902BAA2A9024BF0054FB36 /* SettingsRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsRouter.swift; sourceTree = "<group>"; };
E49E01F32ABD99D300E42BB3 /* Routable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Routable.swift; sourceTree = "<group>"; };
E49F0E752A90395400BC4EE3 /* NavigationPath+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NavigationPath+Helpers.swift"; sourceTree = "<group>"; };
E4D4DB9F2A7C7B9D00C4F3DE /* Comments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Comments.swift; sourceTree = "<group>"; };
E4D4DBA12A7F233200C4F3DE /* FancyTabNavigationSelectionHashValueEnvironmentKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FancyTabNavigationSelectionHashValueEnvironmentKey.swift; sourceTree = "<group>"; };
E4DDB4312A81819300B3A7E0 /* Double.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Double.swift; sourceTree = "<group>"; };
E4DDB4332A819C8000B3A7E0 /* QuickLookView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickLookView.swift; sourceTree = "<group>"; };
E4F0B5712AC2581800BC3E4A /* RoutableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoutableTests.swift; sourceTree = "<group>"; };
E4F0B56E2ABD00A000BC3E4A /* PresentationBackgroundInteraction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentationBackgroundInteraction.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -1511,6 +1515,7 @@
isa = PBXGroup;
children = (
504ECBAF2AB4B0DF006C0B96 /* Model */,
E4F0B5702AC257FD00BC3E4A /* Navigation */,
50CC4A802AA0D5F90074C845 /* Parsers */,
50CC4A7B2A9CFF840074C845 /* Supporting Files */,
50BC1AB72A89741000E3C48B /* Community List */,
Expand Down Expand Up @@ -2280,6 +2285,7 @@
E47478142AAC3C19001CB1AC /* NavigationContext.swift */,
E47478122AAC350E001CB1AC /* NavigationLink+Helpers.swift */,
E49F0E752A90395400BC4EE3 /* NavigationPath+Helpers.swift */,
E49E01F32ABD99D300E42BB3 /* Routable.swift */,
E47B2B772A902E3C00629AF7 /* Router */,
E47B2B742A902DB400629AF7 /* Route */,
);
Expand All @@ -2294,6 +2300,14 @@
path = Animations;
sourceTree = "<group>";
};
E4F0B5702AC257FD00BC3E4A /* Navigation */ = {
isa = PBXGroup;
children = (
E4F0B5712AC2581800BC3E4A /* RoutableTests.swift */,
);
path = Navigation;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
Expand Down Expand Up @@ -2859,6 +2873,7 @@
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 */,
Expand Down Expand Up @@ -2886,6 +2901,7 @@
031BF9552AB25AFB00F4517F /* SiteVersionTests.swift in Sources */,
50CC4A782A9CBDF70074C845 /* TimestampedValueTests.swift in Sources */,
50CC4A822AA0D61F0074C845 /* InstanceMetadataParserTests.swift in Sources */,
E4F0B5722AC2581800BC3E4A /* RoutableTests.swift in Sources */,
50C86ABC2A7E50E200277519 /* PersistenceRepositoryTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
27 changes: 16 additions & 11 deletions Mlem/Extensions/View - Handle Lemmy Links.swift
Original file line number Diff line number Diff line change
Expand Up @@ -153,17 +153,22 @@ struct HandleLemmyLinkResolution<Path: AnyNavigationPath>: ViewModifier {
}

return await MainActor.run {
switch resolution {
case let .post(object):
navigationPath.wrappedValue.append(object)
return true
case let .person(object):
navigationPath.wrappedValue.append(object.person)
return true
case let .community(object):
navigationPath.wrappedValue.append(object)
return true
case .comment:
do {
switch resolution {
case let .post(object):
navigationPath.wrappedValue.append(try Path.makeRoute(object))
return true
case let .person(object):
navigationPath.wrappedValue.append(try Path.makeRoute(object.person))
return true
case let .community(object):
navigationPath.wrappedValue.append(try Path.makeRoute(object))
return true
case .comment:
return false
}
} catch {
errorHandler.handle(error)
return false
}
}
Expand Down
9 changes: 6 additions & 3 deletions Mlem/Navigation/AnyNavigationPath.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,22 @@ import SwiftUI

protocol AnyNavigationPath {

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: Hashable
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
}

extension NavigationPath: AnyNavigationPath {}
40 changes: 40 additions & 0 deletions Mlem/Navigation/Routable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// Routable.swift
// Mlem
//
// Created by Bosco Ho on 2023-09-22.
//

import Foundation

/// Conforming types can be added to a `NavigationRouter`'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).
/// - Returns: `nil` if data value cannot be mapped to a navigation route.
static func makeRoute<V>(_ value: V) throws -> Self where V: Hashable

/// Generic error string
static var makeRouteErrorString: String { get }
}

enum RoutableError<V: Hashable>: LocalizedError {
case routeNotConfigured(value: V)
}

extension Routable {

/// Default implementation.
static func makeRoute<V>(_ value: V) throws -> Self where V: Hashable {
switch value {
case let value as Self:
return value
default:
throw RoutableError.routeNotConfigured(value: value)
}
}

static var makeRouteErrorString: String {
"`makeRoute(...) implementation must return a valid route for all valid data values."
}
}
34 changes: 33 additions & 1 deletion Mlem/Navigation/Route/NavigationRoutes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Foundation
/// Possible routes for navigation links in `Mlem.app`.
///
/// See `SettingsRoutes` for settings-related routes.
enum NavigationRoute: Hashable {
enum NavigationRoute: Routable {
case apiCommunityView(APICommunityView)
case apiCommunity(APICommunity)

Expand All @@ -25,4 +25,36 @@ enum NavigationRoute: Hashable {
case postLinkWithContext(PostLinkWithContext)
case lazyLoadPostLinkWithContext(LazyLoadPostLinkWithContext)
case userModeratorLink(UserModeratorLink)

// swiftlint:disable cyclomatic_complexity
static func makeRoute<V>(_ value: V) throws -> NavigationRoute where V: Hashable {
switch value {
case let value as APICommunityView:
return .apiCommunityView(value)
case let value as APICommunity:
return .apiCommunity(value)
case let value as CommunityLinkWithContext:
return .communityLinkWithContext(value)
case let value as CommunitySidebarLinkWithContext:
return .communitySidebarLinkWithContext(value)
case let value as APIPostView:
return .apiPostView(value)
case let value as APIPost:
return .apiPost(value)
case let value as APIPerson:
return .apiPerson(value)
case let value as PostLinkWithContext:
return .postLinkWithContext(value)
case let value as LazyLoadPostLinkWithContext:
return .lazyLoadPostLinkWithContext(value)
case let value as UserModeratorLink:
return .userModeratorLink(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)
}
}
// swiftlint:enable cyclomatic_complexity
}
54 changes: 48 additions & 6 deletions Mlem/Navigation/Route/SettingsRoutes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

enum SettingsRoute: Hashable {
enum SettingsRoute: Routable {
case accountsPage
case general
case accessibility
Expand All @@ -21,9 +21,29 @@ enum SettingsRoute: Hashable {
case commentPage(CommentSettingsRoute)
case postPage(PostSettingsRoute)
case licensesPage(LicensesSettingsRoute)

static func makeRoute<V>(_ 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: Hashable, Codable {
enum AppearanceSettingsRoute: Routable, Codable {
case theme
case appIcon
case posts
Expand All @@ -33,21 +53,43 @@ enum AppearanceSettingsRoute: Hashable, Codable {
case tabBar
}

enum CommentSettingsRoute: Hashable, Codable {
enum CommentSettingsRoute: Routable, Codable {
case layoutWidget
}

enum PostSettingsRoute: Hashable, Codable {
enum PostSettingsRoute: Routable, Codable {
case customizeWidgets
}

enum AboutSettingsRoute: Hashable {
enum AboutSettingsRoute: Routable {
case contributors
case privacyPolicy(Document)
case eula(Document)
case licenses

static func makeRoute<V>(_ 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: Hashable {
enum LicensesSettingsRoute: Routable {
case licenseDocument(Document)

static func makeRoute<V>(_ value: V) throws -> LicensesSettingsRoute where V: Hashable {
switch value {
case let value as Document:
return .licenseDocument(value)
default:
throw RoutableError.routeNotConfigured(value: value)
}
}
}
20 changes: 15 additions & 5 deletions Mlem/Navigation/Router/NavigationRouter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,21 @@

import Foundation

final class NavigationRouter<Route: Hashable>: ObservableObject {
@Published var path: [Route] = []
final class NavigationRouter<RouteValue: Routable>: ObservableObject {

/// - Avoid directly manipulating this value, if alternate methods are provided.
@Published var path: [RouteValue] = []

}

extension NavigationRouter: AnyNavigationPath {

typealias Route = RouteValue

static func makeRoute<V>(_ value: V) throws -> Route where V: Hashable {
try RouteValue.makeRoute(value)
}

var count: Int {
path.count
}
Expand All @@ -20,9 +30,9 @@ extension NavigationRouter: AnyNavigationPath {
path.isEmpty
}

func append<V>(_ value: V) where V: Hashable {
assert(value is Route)
func append<V>(_ value: V) where V: Routable {
guard let route = value as? Route else {
assert(value is Route)
return
}
path.append(route)
Expand Down
Loading

0 comments on commit 6348989

Please sign in to comment.