diff --git a/Localizations/Localizable.strings b/Localizations/Localizable.strings index b7dcdd2..b406bd3 100644 --- a/Localizations/Localizable.strings +++ b/Localizations/Localizable.strings @@ -87,6 +87,7 @@ "registration.password-confirmation-mismatch" = "Password and password confirmation do not match"; "secondary-navigation.manage-accounts" = "Manage Accounts"; "secondary-navigation.lists" = "Lists"; +"secondary-navigation.my-profile" = "My Profile"; "secondary-navigation.preferences" = "Preferences"; "identities.accounts" = "Accounts"; "identities.browsing" = "Browsing"; diff --git a/ServiceLayer/Sources/ServiceLayer/Services/IdentityService.swift b/ServiceLayer/Sources/ServiceLayer/Services/IdentityService.swift index c90390f..1f846be 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/IdentityService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/IdentityService.swift @@ -9,6 +9,8 @@ import MastodonAPI import Secrets public struct IdentityService { + public let navigationService: NavigationService + private let id: Identity.Id private let identityDatabase: IdentityDatabase private let contentDatabase: ContentDatabase @@ -36,6 +38,10 @@ public struct IdentityService { inMemory: environment.inMemoryContent, appGroup: AppEnvironment.appGroup, keychain: environment.keychain) + + navigationService = NavigationService( + mastodonAPIClient: mastodonAPIClient, + contentDatabase: contentDatabase) } } @@ -238,10 +244,6 @@ public extension IdentityService { 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 { AccountListService( endpoint: accountList, diff --git a/ServiceLayer/Sources/ServiceLayer/Services/TimelineService.swift b/ServiceLayer/Sources/ServiceLayer/Services/TimelineService.swift index 2f5b862..085461b 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/TimelineService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/TimelineService.swift @@ -12,6 +12,7 @@ public struct TimelineService { public let nextPageMaxId: AnyPublisher public let preferLastPresentIdOverNextPageMaxId = true public let title: AnyPublisher + public let titleLocalizationComponents: AnyPublisher<[String], Never> private let timeline: Timeline private let mastodonAPIClient: MastodonAPIClient @@ -26,10 +27,22 @@ public struct TimelineService { navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase) 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() - } else { + titleLocalizationComponents = Empty().eraseToAnyPublisher() + case .favorites: title = Empty().eraseToAnyPublisher() + titleLocalizationComponents = Just(["favorites"]).eraseToAnyPublisher() + case .bookmarks: + title = Empty().eraseToAnyPublisher() + titleLocalizationComponents = Just(["bookmarks"]).eraseToAnyPublisher() + default: + title = Empty().eraseToAnyPublisher() + titleLocalizationComponents = Empty().eraseToAnyPublisher() } } } diff --git a/View Controllers/MainNavigationViewController.swift b/View Controllers/MainNavigationViewController.swift index 05bf7b1..9dd8b34 100644 --- a/View Controllers/MainNavigationViewController.swift +++ b/View Controllers/MainNavigationViewController.swift @@ -40,9 +40,8 @@ final class MainNavigationViewController: UITabBarController { .sink { [weak self] in self?.setupViewControllers(pending: $0) } .store(in: &cancellables) - viewModel.timelineNavigations.map { _ in } - .merge(with: viewModel.followRequestNavigations.map { _ in }) - .sink { [weak self] in self?.selectedIndex = 0 } + viewModel.navigations + .sink { [weak self] in self?.handle(navigation: $0) } .store(in: &cancellables) NotificationCenter.default.publisher(for: UIScene.willEnterForegroundNotification) @@ -142,4 +141,29 @@ private extension MainNavigationViewController { 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) + } } diff --git a/View Controllers/TimelinesViewController.swift b/View Controllers/TimelinesViewController.swift index 407a6a4..c34a88b 100644 --- a/View Controllers/TimelinesViewController.swift +++ b/View Controllers/TimelinesViewController.swift @@ -69,28 +69,6 @@ final class TimelinesViewController: UIPageViewController { animated: !UIAccessibility.isReduceMotionEnabled) }, 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) } } diff --git a/ViewModels/Sources/ViewModels/View Models/NavigationViewModel.swift b/ViewModels/Sources/ViewModels/View Models/NavigationViewModel.swift index 9461bc0..0b56d0a 100644 --- a/ViewModels/Sources/ViewModels/View Models/NavigationViewModel.swift +++ b/ViewModels/Sources/ViewModels/View Models/NavigationViewModel.swift @@ -7,21 +7,18 @@ import ServiceLayer public final class NavigationViewModel: ObservableObject { public let identityContext: IdentityContext - public let timelineNavigations: AnyPublisher - public let followRequestNavigations: AnyPublisher + public let navigations: AnyPublisher @Published public private(set) var recentIdentities = [Identity]() @Published public var presentingSecondaryNavigation = false @Published public var alertItem: AlertItem? - private let timelineNavigationsSubject = PassthroughSubject() - private let followRequestNavigationsSubject = PassthroughSubject() + private let navigationsSubject = PassthroughSubject() private var cancellables = Set() public init(identityContext: IdentityContext) { self.identityContext = identityContext - timelineNavigations = timelineNavigationsSubject.eraseToAnyPublisher() - followRequestNavigations = followRequestNavigationsSubject.eraseToAnyPublisher() + navigations = navigationsSubject.eraseToAnyPublisher() identityContext.$identity .sink { [weak self] _ in self?.objectWillChange.send() } @@ -34,7 +31,7 @@ public final class NavigationViewModel: ObservableObject { } public extension NavigationViewModel { - enum Tab: CaseIterable { + enum Tab: Int, CaseIterable { case timelines case explore case notifications @@ -95,25 +92,27 @@ public extension NavigationViewModel { .store(in: &cancellables) } + func navigateToProfile(id: Account.Id) { + presentingSecondaryNavigation = false + navigationsSubject.send(.profile(identityContext.service.navigationService.profileService(id: id))) + } + func navigate(timeline: Timeline) { presentingSecondaryNavigation = false - timelineNavigationsSubject.send(timeline) + navigationsSubject.send( + .collection(identityContext.service.navigationService.timelineService(timeline: timeline))) } func navigateToFollowerRequests() { - let followRequestsViewModel = CollectionItemsViewModel( - collectionService: identityContext.service.service( - accountList: .followRequests, - titleComponents: ["follow-requests"]), - identityContext: identityContext) - presentingSecondaryNavigation = false - followRequestNavigationsSubject.send(followRequestsViewModel) + navigationsSubject.send(.collection(identityContext.service.service( + accountList: .followRequests, + titleComponents: ["follow-requests"]))) } func viewModel(timeline: Timeline) -> CollectionItemsViewModel { CollectionItemsViewModel( - collectionService: identityContext.service.service(timeline: timeline), + collectionService: identityContext.service.navigationService.timelineService(timeline: timeline), identityContext: identityContext) } diff --git a/Views/SecondaryNavigationView.swift b/Views/SecondaryNavigationView.swift index 611d634..914b3b9 100644 --- a/Views/SecondaryNavigationView.swift +++ b/Views/SecondaryNavigationView.swift @@ -12,6 +12,17 @@ struct SecondaryNavigationView: View { var body: some View { Form { 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( destination: IdentitiesView(viewModel: .init(identityContext: viewModel.identityContext)) .environmentObject(rootViewModel),