From 3661b5ce90c0e210acd2ab201d693b6ac7eed19d Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Sat, 3 Dec 2022 13:25:07 -0500 Subject: [PATCH] Refactor compose intialization - split ComposeContentViewModel.Kind into Destination (top level/reply) and an initial content string - replies get the mentions prepended to the initial content string --- .../Provider/DataSourceFacade+Status.swift | 2 +- ...tatusTableViewControllerNavigateable.swift | 2 +- .../Scene/Compose/ComposeViewController.swift | 3 +- Mastodon/Scene/Compose/ComposeViewModel.swift | 15 +++-- .../HashtagTimelineViewController.swift | 5 +- .../Scene/Profile/ProfileViewController.swift | 5 +- .../Root/MainTab/MainTabBarController.swift | 4 +- .../Root/Sidebar/SidebarViewController.swift | 2 +- .../Scene/Thread/ThreadViewController.swift | 2 +- Mastodon/Supporting Files/SceneDelegate.swift | 2 +- .../ComposeContentViewModel+DataSource.swift | 11 +--- .../ComposeContentViewModel.swift | 60 +++++++------------ 12 files changed, 49 insertions(+), 64 deletions(-) diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift index 9865029a1..34cd4121e 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift @@ -117,7 +117,7 @@ extension DataSourceFacade { let composeViewModel = ComposeViewModel( context: provider.context, authContext: provider.authContext, - kind: .reply(status: status) + destination: .reply(parent: status) ) _ = provider.coordinator.present( scene: .compose(viewModel: composeViewModel), diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewControllerNavigateable.swift b/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewControllerNavigateable.swift index e7b55f91c..0e0a24c9b 100644 --- a/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewControllerNavigateable.swift +++ b/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewControllerNavigateable.swift @@ -100,7 +100,7 @@ extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvid let composeViewModel = ComposeViewModel( context: self.context, authContext: authContext, - kind: .reply(status: status) + destination: .reply(parent: status) ) _ = self.coordinator.present( scene: .compose(viewModel: composeViewModel), diff --git a/Mastodon/Scene/Compose/ComposeViewController.swift b/Mastodon/Scene/Compose/ComposeViewController.swift index fbdbc7d12..9f287dbf8 100644 --- a/Mastodon/Scene/Compose/ComposeViewController.swift +++ b/Mastodon/Scene/Compose/ComposeViewController.swift @@ -34,7 +34,8 @@ final class ComposeViewController: UIViewController, NeedsDependency { return ComposeContentViewModel( context: context, authContext: viewModel.authContext, - kind: viewModel.kind + destination: viewModel.destination, + initialContent: viewModel.initialContent ) }() private(set) lazy var composeContentViewController: ComposeContentViewController = { diff --git a/Mastodon/Scene/Compose/ComposeViewModel.swift b/Mastodon/Scene/Compose/ComposeViewModel.swift index bf234b095..0dcdd9a2d 100644 --- a/Mastodon/Scene/Compose/ComposeViewModel.swift +++ b/Mastodon/Scene/Compose/ComposeViewModel.swift @@ -29,7 +29,8 @@ final class ComposeViewModel { // input let context: AppContext let authContext: AuthContext - let kind: ComposeContentViewModel.Kind + let destination: ComposeContentViewModel.Destination + let initialContent: String let traitCollectionDidChangePublisher = CurrentValueSubject(Void()) // use CurrentValueSubject to make initial event emit @@ -41,17 +42,19 @@ final class ComposeViewModel { init( context: AppContext, authContext: AuthContext, - kind: ComposeContentViewModel.Kind + destination: ComposeContentViewModel.Destination, + initialContent: String = "" ) { self.context = context self.authContext = authContext - self.kind = kind + self.destination = destination + self.initialContent = initialContent // end init self.title = { - switch kind { - case .post, .hashtag, .mention: return L10n.Scene.Compose.Title.newPost - case .reply: return L10n.Scene.Compose.Title.newReply + switch destination { + case .topLevel: return L10n.Scene.Compose.Title.newPost + case .reply: return L10n.Scene.Compose.Title.newReply } }() } diff --git a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift index 4a0be3816..1867d2c64 100644 --- a/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift +++ b/Mastodon/Scene/HashtagTimeline/HashtagTimelineViewController.swift @@ -162,10 +162,13 @@ extension HashtagTimelineViewController { @objc private func composeBarButtonItemPressed(_ sender: UIBarButtonItem) { os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) + let hashtag = "#" + viewModel.hashtag + UITextChecker.learnWord(hashtag) let composeViewModel = ComposeViewModel( context: context, authContext: viewModel.authContext, - kind: .hashtag(hashtag: viewModel.hashtag) + destination: .topLevel, + initialContent: hashtag ) _ = coordinator.present(scene: .compose(viewModel: composeViewModel), from: self, transition: .modal(animated: true, completion: nil)) } diff --git a/Mastodon/Scene/Profile/ProfileViewController.swift b/Mastodon/Scene/Profile/ProfileViewController.swift index 1184fb3d7..3dbc03fe4 100644 --- a/Mastodon/Scene/Profile/ProfileViewController.swift +++ b/Mastodon/Scene/Profile/ProfileViewController.swift @@ -538,10 +538,13 @@ extension ProfileViewController { @objc private func replyBarButtonItemPressed(_ sender: UIBarButtonItem) { os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) guard let mastodonUser = viewModel.user else { return } + let mention = "@" + mastodonUser.acct + UITextChecker.learnWord(mention) let composeViewModel = ComposeViewModel( context: context, authContext: viewModel.authContext, - kind: .mention(user: mastodonUser.asRecord) + destination: .topLevel, + initialContent: mention ) _ = coordinator.present(scene: .compose(viewModel: composeViewModel), from: self, transition: .modal(animated: true, completion: nil)) } diff --git a/Mastodon/Scene/Root/MainTab/MainTabBarController.swift b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift index 2e5d5ae58..1b23b490f 100644 --- a/Mastodon/Scene/Root/MainTab/MainTabBarController.swift +++ b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift @@ -379,7 +379,7 @@ extension MainTabBarController { let composeViewModel = ComposeViewModel( context: context, authContext: authContext, - kind: .post + destination: .topLevel ) _ = coordinator.present(scene: .compose(viewModel: composeViewModel), from: nil, transition: .modal(animated: true, completion: nil)) } @@ -804,7 +804,7 @@ extension MainTabBarController { let composeViewModel = ComposeViewModel( context: context, authContext: authContext, - kind: .post + destination: .topLevel ) _ = coordinator.present(scene: .compose(viewModel: composeViewModel), from: nil, transition: .modal(animated: true, completion: nil)) } diff --git a/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift b/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift index 7c76585a6..0ffcd4c8b 100644 --- a/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift +++ b/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift @@ -227,7 +227,7 @@ extension SidebarViewController: UICollectionViewDelegate { let composeViewModel = ComposeViewModel( context: context, authContext: authContext, - kind: .post + destination: .topLevel ) _ = coordinator.present(scene: .compose(viewModel: composeViewModel), from: self, transition: .modal(animated: true, completion: nil)) default: diff --git a/Mastodon/Scene/Thread/ThreadViewController.swift b/Mastodon/Scene/Thread/ThreadViewController.swift index e8e6ce130..1b386aa6a 100644 --- a/Mastodon/Scene/Thread/ThreadViewController.swift +++ b/Mastodon/Scene/Thread/ThreadViewController.swift @@ -117,7 +117,7 @@ extension ThreadViewController { let composeViewModel = ComposeViewModel( context: context, authContext: viewModel.authContext, - kind: .reply(status: threadContext.status) + destination: .reply(parent: threadContext.status) ) _ = coordinator.present( scene: .compose(viewModel: composeViewModel), diff --git a/Mastodon/Supporting Files/SceneDelegate.swift b/Mastodon/Supporting Files/SceneDelegate.swift index 4476477fd..336a83f7f 100644 --- a/Mastodon/Supporting Files/SceneDelegate.swift +++ b/Mastodon/Supporting Files/SceneDelegate.swift @@ -185,7 +185,7 @@ extension SceneDelegate { let composeViewModel = ComposeViewModel( context: AppContext.shared, authContext: authContext, - kind: .post + destination: .topLevel ) _ = coordinator?.present(scene: .compose(viewModel: composeViewModel), from: nil, transition: .modal(animated: true, completion: nil)) logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): present compose scene") diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel+DataSource.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel+DataSource.swift index abbfe0e61..58ceeaa92 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel+DataSource.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel+DataSource.swift @@ -47,10 +47,7 @@ extension ComposeContentViewModel { } .store(in: &disposeBag) - switch kind { - case .post: - break - case .reply(let status): + if case .reply(let status) = destination { let cell = composeReplyToTableViewCell // bind frame publisher cell.$framePublisher @@ -66,10 +63,6 @@ extension ComposeContentViewModel { guard let replyTo = status.object(in: context.managedObjectContext) else { return } cell.statusView.configure(status: replyTo) } - case .hashtag: - break - case .mention: - break } } } @@ -83,7 +76,7 @@ extension ComposeContentViewModel: UITableViewDataSource { public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { switch Section.allCases[section] { case .replyTo: - switch kind { + switch destination { case .reply: return 1 default: return 0 } diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel.swift index a1ddb6101..066a7ff78 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel.swift @@ -32,7 +32,7 @@ public final class ComposeContentViewModel: NSObject, ObservableObject { // input let context: AppContext - let kind: Kind + let destination: Destination weak var delegate: ComposeContentViewModelDelegate? @Published var viewLayoutFrame = ViewLayoutFrame() @@ -59,8 +59,7 @@ public final class ComposeContentViewModel: NSObject, ObservableObject { customEmojiPickerInputViewModel.configure(textInput: textView) } } - // for hashtag: "# " - // for mention: "@ " + // allow dismissing the compose view without confirmation if content == intialContent @Published public var initialContent = "" @Published public var content = "" @Published public var contentWeightedLength = 0 @@ -138,11 +137,12 @@ public final class ComposeContentViewModel: NSObject, ObservableObject { public init( context: AppContext, authContext: AuthContext, - kind: Kind + destination: Destination, + initialContent: String ) { self.context = context self.authContext = authContext - self.kind = kind + self.destination = destination self.visibility = { // default private when user locked var visibility: Mastodon.Entity.Status.Visibility = { @@ -152,8 +152,7 @@ public final class ComposeContentViewModel: NSObject, ObservableObject { return author.locked ? .private : .public }() // set visibility for reply post - switch kind { - case .reply(let record): + if case .reply(let record) = destination { context.managedObjectContext.performAndWait { guard let status = record.object(in: context.managedObjectContext) else { assertionFailure() @@ -173,8 +172,6 @@ public final class ComposeContentViewModel: NSObject, ObservableObject { break } } - default: - break } return visibility }() @@ -185,7 +182,8 @@ public final class ComposeContentViewModel: NSObject, ObservableObject { // end init // setup initial value - switch kind { + let initialContentWithSpace = initialContent.isEmpty ? "" : initialContent + " " + switch destination { case .reply(let record): context.managedObjectContext.performAndWait { guard let status = record.object(in: context.managedObjectContext) else { @@ -214,29 +212,15 @@ public final class ComposeContentViewModel: NSObject, ObservableObject { } let initialComposeContent = mentionAccts.joined(separator: " ") - let preInsertedContent: String? = initialComposeContent.isEmpty ? nil : initialComposeContent + " " - self.initialContent = preInsertedContent ?? "" - self.content = preInsertedContent ?? "" + let preInsertedContent = initialComposeContent.isEmpty ? "" : initialComposeContent + " " + self.initialContent = preInsertedContent + initialContentWithSpace + self.content = preInsertedContent + initialContentWithSpace } - case .hashtag(let hashtag): - let initialComposeContent = "#" + hashtag - UITextChecker.learnWord(initialComposeContent) - let preInsertedContent = initialComposeContent + " " - self.initialContent = preInsertedContent - self.content = preInsertedContent - case .mention(let record): - context.managedObjectContext.performAndWait { - guard let user = record.object(in: context.managedObjectContext) else { return } - let initialComposeContent = "@" + user.acct - UITextChecker.learnWord(initialComposeContent) - let preInsertedContent = initialComposeContent + " " - self.initialContent = preInsertedContent - self.content = preInsertedContent - } - case .post: - break + case .topLevel: + self.initialContent = initialContentWithSpace + self.content = initialContentWithSpace } - + // set limit let _configuration: Mastodon.Entity.Instance.Configuration? = { var configuration: Mastodon.Entity.Instance.Configuration? = nil @@ -443,11 +427,9 @@ extension ComposeContentViewModel { } extension ComposeContentViewModel { - public enum Kind { - case post - case hashtag(hashtag: String) - case mention(user: ManagedObjectRecord) - case reply(status: ManagedObjectRecord) + public enum Destination { + case topLevel + case reply(parent: ManagedObjectRecord) } public enum ScrollViewState { @@ -530,10 +512,10 @@ extension ComposeContentViewModel { return MastodonStatusPublisher( author: author, replyTo: { - switch self.kind { - case .reply(let status): return status - default: return nil + if case .reply(let status) = destination { + return status } + return nil }(), isContentWarningComposing: isContentWarningActive, contentWarning: contentWarning,