From 34b962e3ca49ed511a0e3416f4960f159f0f2c72 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Fri, 8 Dec 2023 15:15:36 +0100 Subject: [PATCH] Remove me/remote-profile-viewmodel (IOS-192) --- Mastodon.xcodeproj/project.pbxproj | 8 - Mastodon/Coordinator/SceneCoordinator.swift | 37 ++++- .../Scene/Profile/MeProfileViewModel.swift | 41 ----- Mastodon/Scene/Profile/ProfileViewModel.swift | 6 +- .../Profile/RemoteProfileViewModel.swift | 91 ----------- .../Root/MainTab/MainTabBarController.swift | 53 +++---- Mastodon/Supporting Files/SceneDelegate.swift | 142 ++++++++++-------- 7 files changed, 144 insertions(+), 234 deletions(-) delete mode 100644 Mastodon/Scene/Profile/MeProfileViewModel.swift delete mode 100644 Mastodon/Scene/Profile/RemoteProfileViewModel.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index e08f34198..82880d0e1 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -427,7 +427,6 @@ DBA94436265CBB7400C537E1 /* ProfileFieldItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA94435265CBB7400C537E1 /* ProfileFieldItem.swift */; }; DBA9443E265CFA6400C537E1 /* ProfileFieldCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA9443D265CFA6400C537E1 /* ProfileFieldCollectionViewCell.swift */; }; DBABE3EC25ECAC4B00879EE5 /* WelcomeIllustrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBABE3EB25ECAC4B00879EE5 /* WelcomeIllustrationView.swift */; }; - DBAE3FAF26172FC0004B8251 /* RemoteProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3FAE26172FC0004B8251 /* RemoteProfileViewModel.swift */; }; DBB3BA2A26A81C020004F2D4 /* FLAnimatedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB3BA2926A81C020004F2D4 /* FLAnimatedImageView.swift */; }; DBB3BA2B26A81D060004F2D4 /* FLAnimatedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB3BA2926A81C020004F2D4 /* FLAnimatedImageView.swift */; }; DBB45B5627B39FC9002DC5A7 /* MediaPreviewVideoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB45B5527B39FC9002DC5A7 /* MediaPreviewVideoViewController.swift */; }; @@ -441,7 +440,6 @@ DBB525502611ED6D002F1F29 /* ProfileHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB5254F2611ED6D002F1F29 /* ProfileHeaderView.swift */; }; DBB525562611EDCA002F1F29 /* UserTimelineViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB525552611EDCA002F1F29 /* UserTimelineViewModel.swift */; }; DBB5255E2611F07A002F1F29 /* ProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB5255D2611F07A002F1F29 /* ProfileViewModel.swift */; }; - DBB525642612C988002F1F29 /* MeProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB525632612C988002F1F29 /* MeProfileViewModel.swift */; }; DBB8AB4626AECDE200F6D281 /* SendPostIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB8AB4526AECDE200F6D281 /* SendPostIntentHandler.swift */; }; DBB8AB4F26AED13F00F6D281 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DB427DDE25BAA00100D1B89D /* Assets.xcassets */; }; DBB9759C262462E1004620BD /* ThreadMetaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB9759B262462E1004620BD /* ThreadMetaView.swift */; }; @@ -1167,7 +1165,6 @@ DBA94435265CBB7400C537E1 /* ProfileFieldItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileFieldItem.swift; sourceTree = ""; }; DBA9443D265CFA6400C537E1 /* ProfileFieldCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileFieldCollectionViewCell.swift; sourceTree = ""; }; DBABE3EB25ECAC4B00879EE5 /* WelcomeIllustrationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeIllustrationView.swift; sourceTree = ""; }; - DBAE3FAE26172FC0004B8251 /* RemoteProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteProfileViewModel.swift; sourceTree = ""; }; DBB3BA2926A81C020004F2D4 /* FLAnimatedImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FLAnimatedImageView.swift; sourceTree = ""; }; DBB45B5527B39FC9002DC5A7 /* MediaPreviewVideoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewVideoViewController.swift; sourceTree = ""; }; DBB45B5827B39FE4002DC5A7 /* MediaPreviewVideoViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewVideoViewModel.swift; sourceTree = ""; }; @@ -1180,7 +1177,6 @@ DBB5254F2611ED6D002F1F29 /* ProfileHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileHeaderView.swift; sourceTree = ""; }; DBB525552611EDCA002F1F29 /* UserTimelineViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserTimelineViewModel.swift; sourceTree = ""; }; DBB5255D2611F07A002F1F29 /* ProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewModel.swift; sourceTree = ""; }; - DBB525632612C988002F1F29 /* MeProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeProfileViewModel.swift; sourceTree = ""; }; DBB8AB4526AECDE200F6D281 /* SendPostIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendPostIntentHandler.swift; sourceTree = ""; }; DBB9759B262462E1004620BD /* ThreadMetaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadMetaView.swift; sourceTree = ""; }; DBBE1B4425F3474B0081417A /* MastodonPickServerAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPickServerAppearance.swift; sourceTree = ""; }; @@ -2782,8 +2778,6 @@ DBFEEC97279BDC6A004F81DD /* About */, DB9D6BFE25E4F5940051B173 /* ProfileViewController.swift */, DBB5255D2611F07A002F1F29 /* ProfileViewModel.swift */, - DBAE3FAE26172FC0004B8251 /* RemoteProfileViewModel.swift */, - DBB525632612C988002F1F29 /* MeProfileViewModel.swift */, ); path = Profile; sourceTree = ""; @@ -3748,7 +3742,6 @@ 0F202201261326E6000C64BF /* HashtagTimelineViewModel.swift in Sources */, D81A94172B07A1D30067A19D /* ProfileCardView+Configuration.swift in Sources */, DB63F7452799056400455B82 /* HashtagTableViewCell.swift in Sources */, - DBAE3FAF26172FC0004B8251 /* RemoteProfileViewModel.swift in Sources */, D82BD7552ABC73AF009A374A /* NotificationPolicyTableViewCell.swift in Sources */, DB3EA8EB281B7E0700598866 /* DiscoveryCommunityViewModel+State.swift in Sources */, DB0F8150264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift in Sources */, @@ -3850,7 +3843,6 @@ DB1E346825F518E20079D7DF /* CategoryPickerSection.swift in Sources */, DB7274F4273BB9B200577D95 /* ListBatchFetchViewModel.swift in Sources */, DB0618052785A73D0030EE79 /* RegisterItem.swift in Sources */, - DBB525642612C988002F1F29 /* MeProfileViewModel.swift in Sources */, DB3EA8EF281B837000598866 /* DiscoveryCommunityViewController+DataSourceProvider.swift in Sources */, DB6B74EF272FB55000C70B6E /* FollowerListViewController.swift in Sources */, DB4AA6B327BA34B6009EC082 /* CellFrameCacheContainer.swift in Sources */, diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index f85a7c96f..50f5dfb0f 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -49,7 +49,8 @@ final public class SceneCoordinator { appContext.notificationService.requestRevealNotificationPublisher .receive(on: DispatchQueue.main) - .sink(receiveValue: { [weak self] pushNotification in + .sink(receiveValue: { + [weak self] pushNotification in guard let self = self else { return } Task { guard let currentActiveAuthenticationBox = self.authContext?.mastodonAuthenticationBox else { return } @@ -101,20 +102,44 @@ final public class SceneCoordinator { switch type { case .follow: - let profileViewModel = RemoteProfileViewModel(context: appContext, authContext: authContext, notificationID: notificationID) - _ = self.present(scene: .profile(viewModel: profileViewModel), from: from, transition: .show) + Task { + let account = try await appContext.apiService.notification( + notificationID: notificationID, + authenticationBox: authContext.mastodonAuthenticationBox + ).value.account + + let profileViewModel = ProfileViewModel( + context: appContext, + authContext: authContext, + account: account + ) + _ = self.present( + scene: .profile(viewModel: profileViewModel), + from: from, + transition: .show + ) + } case .followRequest: // do nothing break case .mention, .reblog, .favourite, .poll, .status: - let threadViewModel = RemoteThreadViewModel(context: appContext, authContext: authContext, notificationID: notificationID) - _ = self.present(scene: .thread(viewModel: threadViewModel), from: from, transition: .show) + let threadViewModel = RemoteThreadViewModel( + context: appContext, + authContext: authContext, + notificationID: notificationID + ) + _ = self.present( + scene: .thread(viewModel: threadViewModel), + from: from, + transition: .show + ) + case ._other: assertionFailure() break } } // end DispatchQueue.main.async - + } catch { assertionFailure(error.localizedDescription) return diff --git a/Mastodon/Scene/Profile/MeProfileViewModel.swift b/Mastodon/Scene/Profile/MeProfileViewModel.swift deleted file mode 100644 index 0d3e3f383..000000000 --- a/Mastodon/Scene/Profile/MeProfileViewModel.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// MeProfileViewModel.swift -// Mastodon -// -// Created by MainasuK Cirno on 2021-3-30. -// - -import UIKit -import Combine -import CoreData -import CoreDataStack -import MastodonCore -import MastodonSDK - -final class MeProfileViewModel: ProfileViewModel { - - @MainActor - init(context: AppContext, authContext: AuthContext) { - let me = authContext.mastodonAuthenticationBox.authentication.account() - super.init( - context: context, - authContext: authContext, - account: me - ) - } - - override func viewDidLoad() { - - super.viewDidLoad() - - Task { - do { - let account = try await context.apiService.authenticatedUserInfo(authenticationBox: authContext.mastodonAuthenticationBox).value - self.account = account - self.me = account - } catch { - // do nothing? - } - } - } -} diff --git a/Mastodon/Scene/Profile/ProfileViewModel.swift b/Mastodon/Scene/Profile/ProfileViewModel.swift index 7a075f6ea..9ff87c669 100644 --- a/Mastodon/Scene/Profile/ProfileViewModel.swift +++ b/Mastodon/Scene/Profile/ProfileViewModel.swift @@ -56,10 +56,10 @@ class ProfileViewModel: NSObject { // let needsPagePinToTop = CurrentValueSubject(false) @MainActor - init(context: AppContext, authContext: AuthContext, account: Mastodon.Entity.Account?) { + init(context: AppContext, authContext: AuthContext, account: Mastodon.Entity.Account) { self.context = context self.authContext = authContext - self.account = account! + self.account = account self.postsUserTimelineViewModel = UserTimelineViewModel( context: context, authContext: authContext, @@ -87,7 +87,7 @@ class ProfileViewModel: NSObject { // bind user $account .map { user -> UserIdentifier? in - guard let account, let domain = account.domain else { return nil } + guard let domain = account.domain else { return nil } return MastodonUserIdentifier(domain: domain, userID: account.id) } .assign(to: &$userIdentifier) diff --git a/Mastodon/Scene/Profile/RemoteProfileViewModel.swift b/Mastodon/Scene/Profile/RemoteProfileViewModel.swift deleted file mode 100644 index 2849e1fe2..000000000 --- a/Mastodon/Scene/Profile/RemoteProfileViewModel.swift +++ /dev/null @@ -1,91 +0,0 @@ -// -// RemoteProfileViewModel.swift -// Mastodon -// -// Created by MainasuK Cirno on 2021-4-2. -// - -import Foundation -import Combine -import CoreDataStack -import MastodonSDK -import MastodonCore - -final class RemoteProfileViewModel: ProfileViewModel { - - @MainActor - init(context: AppContext, authContext: AuthContext, userID: Mastodon.Entity.Account.ID) { - super.init(context: context, authContext: authContext, account: nil) - - let domain = authContext.mastodonAuthenticationBox.domain - let authorization = authContext.mastodonAuthenticationBox.userAuthorization - Just(userID) - .asyncMap { userID in - try await context.apiService.accountInfo( - domain: domain, - userID: userID, - authorization: authorization - ) - } - .retry(3) - .receive(on: DispatchQueue.main) - .sink { completion in - switch completion { - case .failure(_): - // TODO: handle error - break - case .finished: - break - } - } receiveValue: { [weak self] response in - self?.account = response.value - } - .store(in: &disposeBag) - } - - @MainActor - init(context: AppContext, authContext: AuthContext, notificationID: Mastodon.Entity.Notification.ID) { - super.init(context: context, authContext: authContext, account: nil) - - Task { @MainActor in - let response = try await context.apiService.notification( - notificationID: notificationID, - authenticationBox: authContext.mastodonAuthenticationBox - ) - - self.account = response.value.account - } // end Task - } - - @MainActor - init(context: AppContext, authContext: AuthContext, acct: String){ - super.init(context: context, authContext: authContext, account: nil) - - let domain = authContext.mastodonAuthenticationBox.domain - let authenticationBox = authContext.mastodonAuthenticationBox - - Just(acct) - .asyncMap { acct -> Mastodon.Response.Content in - try await context.apiService.search( - query: .init(q: acct, type: .accounts, resolve: true), - authenticationBox: authenticationBox - ).map { $0.accounts.first } - } - .retry(3) - .receive(on: DispatchQueue.main) - .sink { completion in - switch completion { - case .failure(_): - // TODO: handle error - break - case .finished: - break - } - } receiveValue: { [weak self] response in - guard let account = response.value else { return } - - self?.account = account - } - .store(in: &disposeBag) - } -} diff --git a/Mastodon/Scene/Root/MainTab/MainTabBarController.swift b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift index 42faa61fc..9e1d082c5 100644 --- a/Mastodon/Scene/Root/MainTab/MainTabBarController.swift +++ b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift @@ -95,32 +95,33 @@ class MainTabBarController: UITabBarController { let viewController: UIViewController switch self { - case .home: - let _viewController = HomeTimelineViewController() - _viewController.context = context - _viewController.coordinator = coordinator - _viewController.viewModel = .init(context: context, authContext: authContext) - viewController = _viewController - case .search: - let _viewController = SearchViewController() - _viewController.context = context - _viewController.coordinator = coordinator - _viewController.viewModel = .init(context: context, authContext: authContext) - viewController = _viewController - case .compose: - viewController = UIViewController() - case .notifications: - let _viewController = NotificationViewController() - _viewController.context = context - _viewController.coordinator = coordinator - _viewController.viewModel = .init(context: context, authContext: authContext) - viewController = _viewController - case .me: - let _viewController = ProfileViewController() - _viewController.context = context - _viewController.coordinator = coordinator - _viewController.viewModel = MeProfileViewModel(context: context, authContext: authContext) - viewController = _viewController + case .home: + let _viewController = HomeTimelineViewController() + _viewController.context = context + _viewController.coordinator = coordinator + _viewController.viewModel = .init(context: context, authContext: authContext) + viewController = _viewController + case .search: + let _viewController = SearchViewController() + _viewController.context = context + _viewController.coordinator = coordinator + _viewController.viewModel = .init(context: context, authContext: authContext) + viewController = _viewController + case .compose: + viewController = UIViewController() + case .notifications: + let _viewController = NotificationViewController() + _viewController.context = context + _viewController.coordinator = coordinator + _viewController.viewModel = .init(context: context, authContext: authContext) + viewController = _viewController + case .me: + let me = authContext.mastodonAuthenticationBox.authentication.account() + let _viewController = ProfileViewController() + _viewController.context = context + _viewController.coordinator = coordinator + _viewController.viewModel = ProfileViewModel(context: context, authContext: authContext, account: me) + viewController = _viewController } viewController.title = self.title return AdaptiveStatusBarStyleNavigationController(rootViewController: viewController) diff --git a/Mastodon/Supporting Files/SceneDelegate.swift b/Mastodon/Supporting Files/SceneDelegate.swift index 7c6c33f8e..9ecbfe58a 100644 --- a/Mastodon/Supporting Files/SceneDelegate.swift +++ b/Mastodon/Supporting Files/SceneDelegate.swift @@ -137,16 +137,26 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { switch (profile, statusID) { case (profile, nil): - let profileViewModel = RemoteProfileViewModel( - context: AppContext.shared, - authContext: authContext, - acct: incomingURL.absoluteString - ) - self.coordinator?.present( - scene: .profile(viewModel: profileViewModel), - from: nil, - transition: .show - ) + Task { + let domain = authContext.mastodonAuthenticationBox.domain + let authenticationBox = authContext.mastodonAuthenticationBox + + guard let account = try await AppContext.shared.apiService.search( + query: .init(q: incomingURL.absoluteString, type: .accounts, resolve: true), + authenticationBox: authenticationBox + ).value.accounts.first else { return } + + let profileViewModel = ProfileViewModel( + context: AppContext.shared, + authContext: authContext, + account: account + ) + _ = self.coordinator?.present( + scene: .profile(viewModel: profileViewModel), + from: nil, + transition: .show + ) + } case (profile, statusID): Task { @@ -248,58 +258,72 @@ extension SceneDelegate { if !UIApplication.shared.canOpenURL(url) { return } - #if DEBUG +#if DEBUG print("source application = \(sendingAppID ?? "Unknown")") print("url = \(url)") - #endif - +#endif + switch url.host { - case "post": - showComposeViewController() - case "profile": - let components = url.pathComponents - guard - components.count == 2, - components[0] == "/", - let authContext = coordinator?.authContext - else { return } - - let profileViewModel = RemoteProfileViewModel( - context: AppContext.shared, - authContext: authContext, - acct: components[1] - ) - self.coordinator?.present( - scene: .profile(viewModel: profileViewModel), - from: nil, - transition: .show - ) - case "status": - let components = url.pathComponents - guard - components.count == 2, - components[0] == "/", - let authContext = coordinator?.authContext - else { return } - let statusId = components[1] - // View post from user - let threadViewModel = RemoteThreadViewModel( - context: AppContext.shared, - authContext: authContext, - statusID: statusId - ) - coordinator?.present(scene: .thread(viewModel: threadViewModel), from: nil, transition: .show) - case "search": - let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems - guard - let authContext = coordinator?.authContext, - let searchQuery = queryItems?.first(where: { $0.name == "query" })?.value - else { return } - - let viewModel = SearchDetailViewModel(authContext: authContext, initialSearchText: searchQuery) - coordinator?.present(scene: .searchDetail(viewModel: viewModel), from: nil, transition: .show) - default: - return + case "post": + showComposeViewController() + case "profile": + let components = url.pathComponents + guard + components.count == 2, + components[0] == "/", + let authContext = coordinator?.authContext + else { return } + + Task { + do { + let domain = authContext.mastodonAuthenticationBox.domain + let authenticationBox = authContext.mastodonAuthenticationBox + + guard let account = try await AppContext.shared.apiService.search( + query: .init(q: components[1], type: .accounts, resolve: true), + authenticationBox: authenticationBox + ).value.accounts.first else { return } + + let profileViewModel = ProfileViewModel( + context: AppContext.shared, + authContext: authContext, + account: account + ) + self.coordinator?.present( + scene: .profile(viewModel: profileViewModel), + from: nil, + transition: .show + ) + } catch { + // fail silently + } + } + case "status": + let components = url.pathComponents + guard + components.count == 2, + components[0] == "/", + let authContext = coordinator?.authContext + else { return } + let statusId = components[1] + // View post from user + let threadViewModel = RemoteThreadViewModel( + context: AppContext.shared, + authContext: authContext, + statusID: statusId + ) + coordinator?.present(scene: .thread(viewModel: threadViewModel), from: nil, transition: .show) + case "search": + let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems + guard + let authContext = coordinator?.authContext, + let searchQuery = queryItems?.first(where: { $0.name == "query" })?.value + else { return } + + let viewModel = SearchDetailViewModel(authContext: authContext, initialSearchText: searchQuery) + coordinator?.present(scene: .searchDetail(viewModel: viewModel), from: nil, transition: .show) + default: + return } } }