Secondary navigation refactor

This commit is contained in:
Justin Mazzocchi 2021-01-27 15:49:13 -08:00
parent db8be2f00b
commit 3211ebf719
No known key found for this signature in database
GPG key ID: E223E6937AAFB01C
7 changed files with 75 additions and 47 deletions

View file

@ -87,6 +87,7 @@
"registration.password-confirmation-mismatch" = "Password and password confirmation do not match"; "registration.password-confirmation-mismatch" = "Password and password confirmation do not match";
"secondary-navigation.manage-accounts" = "Manage Accounts"; "secondary-navigation.manage-accounts" = "Manage Accounts";
"secondary-navigation.lists" = "Lists"; "secondary-navigation.lists" = "Lists";
"secondary-navigation.my-profile" = "My Profile";
"secondary-navigation.preferences" = "Preferences"; "secondary-navigation.preferences" = "Preferences";
"identities.accounts" = "Accounts"; "identities.accounts" = "Accounts";
"identities.browsing" = "Browsing"; "identities.browsing" = "Browsing";

View file

@ -9,6 +9,8 @@ import MastodonAPI
import Secrets import Secrets
public struct IdentityService { public struct IdentityService {
public let navigationService: NavigationService
private let id: Identity.Id private let id: Identity.Id
private let identityDatabase: IdentityDatabase private let identityDatabase: IdentityDatabase
private let contentDatabase: ContentDatabase private let contentDatabase: ContentDatabase
@ -36,6 +38,10 @@ public struct IdentityService {
inMemory: environment.inMemoryContent, inMemory: environment.inMemoryContent,
appGroup: AppEnvironment.appGroup, appGroup: AppEnvironment.appGroup,
keychain: environment.keychain) keychain: environment.keychain)
navigationService = NavigationService(
mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase)
} }
} }
@ -238,10 +244,6 @@ public extension IdentityService {
mastodonAPIClient.request(StatusEndpoint.post(statusComponents)).map(\.id).eraseToAnyPublisher() mastodonAPIClient.request(StatusEndpoint.post(statusComponents)).map(\.id).eraseToAnyPublisher()
} }
func service(timeline: Timeline) -> TimelineService {
TimelineService(timeline: timeline, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
}
func service(accountList: AccountsEndpoint, titleComponents: [String]? = nil) -> AccountListService { func service(accountList: AccountsEndpoint, titleComponents: [String]? = nil) -> AccountListService {
AccountListService( AccountListService(
endpoint: accountList, endpoint: accountList,

View file

@ -12,6 +12,7 @@ public struct TimelineService {
public let nextPageMaxId: AnyPublisher<String, Never> public let nextPageMaxId: AnyPublisher<String, Never>
public let preferLastPresentIdOverNextPageMaxId = true public let preferLastPresentIdOverNextPageMaxId = true
public let title: AnyPublisher<String, Never> public let title: AnyPublisher<String, Never>
public let titleLocalizationComponents: AnyPublisher<[String], Never>
private let timeline: Timeline private let timeline: Timeline
private let mastodonAPIClient: MastodonAPIClient private let mastodonAPIClient: MastodonAPIClient
@ -26,10 +27,22 @@ public struct TimelineService {
navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase) navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
nextPageMaxId = nextPageMaxIdSubject.eraseToAnyPublisher() nextPageMaxId = nextPageMaxIdSubject.eraseToAnyPublisher()
if case let .tag(tag) = timeline { switch timeline {
case let .list(list):
title = Just(list.title).eraseToAnyPublisher()
titleLocalizationComponents = Empty().eraseToAnyPublisher()
case let .tag(tag):
title = Just("#".appending(tag)).eraseToAnyPublisher() title = Just("#".appending(tag)).eraseToAnyPublisher()
} else { titleLocalizationComponents = Empty().eraseToAnyPublisher()
case .favorites:
title = Empty().eraseToAnyPublisher() title = Empty().eraseToAnyPublisher()
titleLocalizationComponents = Just(["favorites"]).eraseToAnyPublisher()
case .bookmarks:
title = Empty().eraseToAnyPublisher()
titleLocalizationComponents = Just(["bookmarks"]).eraseToAnyPublisher()
default:
title = Empty().eraseToAnyPublisher()
titleLocalizationComponents = Empty().eraseToAnyPublisher()
} }
} }
} }

View file

@ -40,9 +40,8 @@ final class MainNavigationViewController: UITabBarController {
.sink { [weak self] in self?.setupViewControllers(pending: $0) } .sink { [weak self] in self?.setupViewControllers(pending: $0) }
.store(in: &cancellables) .store(in: &cancellables)
viewModel.timelineNavigations.map { _ in } viewModel.navigations
.merge(with: viewModel.followRequestNavigations.map { _ in }) .sink { [weak self] in self?.handle(navigation: $0) }
.sink { [weak self] in self?.selectedIndex = 0 }
.store(in: &cancellables) .store(in: &cancellables)
NotificationCenter.default.publisher(for: UIScene.willEnterForegroundNotification) NotificationCenter.default.publisher(for: UIScene.willEnterForegroundNotification)
@ -142,4 +141,29 @@ private extension MainNavigationViewController {
dismiss(animated: true) dismiss(animated: true)
} }
} }
func handle(navigation: Navigation) {
let vc: UIViewController
switch navigation {
case let .collection(collectionService):
vc = TableViewController(
viewModel: CollectionItemsViewModel(
collectionService: collectionService,
identityContext: viewModel.identityContext),
rootViewModel: rootViewModel)
case let .profile(profileService):
vc = ProfileViewController(
viewModel: ProfileViewModel(
profileService: profileService,
identityContext: viewModel.identityContext),
rootViewModel: rootViewModel,
identityContext: viewModel.identityContext,
parentNavigationController: nil)
default:
return
}
selectedViewController?.show(vc, sender: self)
}
} }

View file

@ -69,28 +69,6 @@ final class TimelinesViewController: UIPageViewController {
animated: !UIAccessibility.isReduceMotionEnabled) animated: !UIAccessibility.isReduceMotionEnabled)
}, },
for: .valueChanged) for: .valueChanged)
viewModel.timelineNavigations.sink { [weak self] in
guard let self = self else { return }
let vc = TableViewController(
viewModel: self.viewModel.viewModel(timeline: $0),
rootViewModel: self.rootViewModel)
vc.navigationItem.title = $0.title
self.show(vc, sender: self)
}
.store(in: &cancellables)
viewModel.followRequestNavigations.sink { [weak self] in
guard let self = self else { return }
let vc = TableViewController(viewModel: $0, rootViewModel: self.rootViewModel)
self.show(vc, sender: self)
}
.store(in: &cancellables)
} }
} }

View file

@ -7,21 +7,18 @@ import ServiceLayer
public final class NavigationViewModel: ObservableObject { public final class NavigationViewModel: ObservableObject {
public let identityContext: IdentityContext public let identityContext: IdentityContext
public let timelineNavigations: AnyPublisher<Timeline, Never> public let navigations: AnyPublisher<Navigation, Never>
public let followRequestNavigations: AnyPublisher<CollectionViewModel, Never>
@Published public private(set) var recentIdentities = [Identity]() @Published public private(set) var recentIdentities = [Identity]()
@Published public var presentingSecondaryNavigation = false @Published public var presentingSecondaryNavigation = false
@Published public var alertItem: AlertItem? @Published public var alertItem: AlertItem?
private let timelineNavigationsSubject = PassthroughSubject<Timeline, Never>() private let navigationsSubject = PassthroughSubject<Navigation, Never>()
private let followRequestNavigationsSubject = PassthroughSubject<CollectionViewModel, Never>()
private var cancellables = Set<AnyCancellable>() private var cancellables = Set<AnyCancellable>()
public init(identityContext: IdentityContext) { public init(identityContext: IdentityContext) {
self.identityContext = identityContext self.identityContext = identityContext
timelineNavigations = timelineNavigationsSubject.eraseToAnyPublisher() navigations = navigationsSubject.eraseToAnyPublisher()
followRequestNavigations = followRequestNavigationsSubject.eraseToAnyPublisher()
identityContext.$identity identityContext.$identity
.sink { [weak self] _ in self?.objectWillChange.send() } .sink { [weak self] _ in self?.objectWillChange.send() }
@ -34,7 +31,7 @@ public final class NavigationViewModel: ObservableObject {
} }
public extension NavigationViewModel { public extension NavigationViewModel {
enum Tab: CaseIterable { enum Tab: Int, CaseIterable {
case timelines case timelines
case explore case explore
case notifications case notifications
@ -95,25 +92,27 @@ public extension NavigationViewModel {
.store(in: &cancellables) .store(in: &cancellables)
} }
func navigateToProfile(id: Account.Id) {
presentingSecondaryNavigation = false
navigationsSubject.send(.profile(identityContext.service.navigationService.profileService(id: id)))
}
func navigate(timeline: Timeline) { func navigate(timeline: Timeline) {
presentingSecondaryNavigation = false presentingSecondaryNavigation = false
timelineNavigationsSubject.send(timeline) navigationsSubject.send(
.collection(identityContext.service.navigationService.timelineService(timeline: timeline)))
} }
func navigateToFollowerRequests() { func navigateToFollowerRequests() {
let followRequestsViewModel = CollectionItemsViewModel(
collectionService: identityContext.service.service(
accountList: .followRequests,
titleComponents: ["follow-requests"]),
identityContext: identityContext)
presentingSecondaryNavigation = false presentingSecondaryNavigation = false
followRequestNavigationsSubject.send(followRequestsViewModel) navigationsSubject.send(.collection(identityContext.service.service(
accountList: .followRequests,
titleComponents: ["follow-requests"])))
} }
func viewModel(timeline: Timeline) -> CollectionItemsViewModel { func viewModel(timeline: Timeline) -> CollectionItemsViewModel {
CollectionItemsViewModel( CollectionItemsViewModel(
collectionService: identityContext.service.service(timeline: timeline), collectionService: identityContext.service.navigationService.timelineService(timeline: timeline),
identityContext: identityContext) identityContext: identityContext)
} }

View file

@ -12,6 +12,17 @@ struct SecondaryNavigationView: View {
var body: some View { var body: some View {
Form { Form {
Section { Section {
if let id = viewModel.identityContext.identity.account?.id {
Button {
viewModel.navigateToProfile(id: id)
} label: {
Label {
Text("secondary-navigation.my-profile").foregroundColor(.primary)
} icon: {
Image(systemName: "person.crop.square")
}
}
}
NavigationLink( NavigationLink(
destination: IdentitiesView(viewModel: .init(identityContext: viewModel.identityContext)) destination: IdentitiesView(viewModel: .init(identityContext: viewModel.identityContext))
.environmentObject(rootViewModel), .environmentObject(rootViewModel),