From 1d193dec0fd61938ec3edcff40161aa6814c8f87 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Tue, 11 Feb 2025 00:34:32 -0500 Subject: [PATCH] Toast after posting status Closes #561 --- .../Sources/ComposeUI/API/PostService.swift | 9 +++-- .../ComposeUI/ComposeMastodonContext.swift | 2 -- .../Sources/ComposeUI/Model/DismissMode.swift | 5 ++- .../Sources/ComposeUI/Views/ComposeView.swift | 8 +++-- ShareExtension/ShareHostingController.swift | 2 +- ShareExtension/ShareMastodonContext.swift | 3 -- Tusker/Scenes/ComposeSceneDelegate.swift | 2 +- .../Compose/ComposeHostingController.swift | 35 ++++++++++++++++--- .../Main/BaseMainTabBarViewController.swift | 5 ++- .../Profile/ProfileViewController.swift | 3 ++ .../Timeline/TimelineViewController.swift | 4 +-- .../Views/Toast/ToastableViewController.swift | 20 +++++++++++ 12 files changed, 76 insertions(+), 22 deletions(-) diff --git a/Packages/ComposeUI/Sources/ComposeUI/API/PostService.swift b/Packages/ComposeUI/Sources/ComposeUI/API/PostService.swift index ae84c7c528..39e68fbb8d 100644 --- a/Packages/ComposeUI/Sources/ComposeUI/API/PostService.swift +++ b/Packages/ComposeUI/Sources/ComposeUI/API/PostService.swift @@ -25,9 +25,9 @@ final class PostService: ObservableObject { self.draft = draft } - func post() async throws(Error) { + func post() async throws(Error) -> Status { guard draft.hasContent || draft.editedStatusID != nil else { - return + throw .noContent } // save before posting, so if a crash occurs during network request, the status won't be lost @@ -105,7 +105,7 @@ final class PostService: ObservableObject { do { let (status, _) = try await mastodonController.run(request) currentStep += 1 - mastodonController.storeCreatedStatus(status) + return status } catch { throw Error.posting(error) } @@ -197,6 +197,7 @@ final class PostService: ObservableObject { } enum Error: Swift.Error, LocalizedError { + case noContent case attachmentData(index: Int, cause: DraftAttachment.ExportError) case attachmentMissingMimeType(index: Int, type: UTType) case attachmentUpload(index: Int, cause: Client.Error) @@ -204,6 +205,8 @@ final class PostService: ObservableObject { var localizedDescription: String { switch self { + case .noContent: + return "No content" case let .attachmentData(index: index, cause: cause): return "Attachment \(index + 1): \(cause.localizedDescription)" case let .attachmentMissingMimeType(index: index, type: type): diff --git a/Packages/ComposeUI/Sources/ComposeUI/ComposeMastodonContext.swift b/Packages/ComposeUI/Sources/ComposeUI/ComposeMastodonContext.swift index 2febcb26ee..5cf0be280e 100644 --- a/Packages/ComposeUI/Sources/ComposeUI/ComposeMastodonContext.swift +++ b/Packages/ComposeUI/Sources/ComposeUI/ComposeMastodonContext.swift @@ -26,7 +26,5 @@ public protocol ComposeMastodonContext { @MainActor func searchCachedHashtags(query: String) -> [Hashtag] - func storeCreatedStatus(_ status: Status) - func fetchStatus(id: String) -> (any StatusProtocol)? } diff --git a/Packages/ComposeUI/Sources/ComposeUI/Model/DismissMode.swift b/Packages/ComposeUI/Sources/ComposeUI/Model/DismissMode.swift index cbbeec6921..1419b45404 100644 --- a/Packages/ComposeUI/Sources/ComposeUI/Model/DismissMode.swift +++ b/Packages/ComposeUI/Sources/ComposeUI/Model/DismissMode.swift @@ -6,7 +6,10 @@ // import Foundation +import Pachyderm public enum DismissMode { - case cancel, post + case cancel + case edit(Status) + case post(Status) } diff --git a/Packages/ComposeUI/Sources/ComposeUI/Views/ComposeView.swift b/Packages/ComposeUI/Sources/ComposeUI/Views/ComposeView.swift index 56d01accee..b70b4a7d83 100644 --- a/Packages/ComposeUI/Sources/ComposeUI/Views/ComposeView.swift +++ b/Packages/ComposeUI/Sources/ComposeUI/Views/ComposeView.swift @@ -223,7 +223,7 @@ private struct ComposeViewBody: View { state.poster = poster do { - try await poster.post() + let status = try await poster.post() isDismissing = true state.didPostSuccessfully = true @@ -233,7 +233,11 @@ private struct ComposeViewBody: View { // don't unset the poster, so the ui remains disabled while dismissing - config.dismiss(.post) + if draft.editedStatusID != nil { + config.dismiss(.edit(status)) + } else { + config.dismiss(.post(status)) + } } catch { self.postError = error state.poster = nil diff --git a/ShareExtension/ShareHostingController.swift b/ShareExtension/ShareHostingController.swift index ffff7c1f27..4aaf841c99 100644 --- a/ShareExtension/ShareHostingController.swift +++ b/ShareExtension/ShareHostingController.swift @@ -74,7 +74,7 @@ class ShareHostingController: UIHostingController { switch mode { case .cancel: extensionContext.cancelRequest(withError: Error.cancelled) - case .post: + case .post(_), .edit(_): extensionContext.completeRequest(returningItems: nil) } } diff --git a/ShareExtension/ShareMastodonContext.swift b/ShareExtension/ShareMastodonContext.swift index 6b7a7d5017..857d437a7a 100644 --- a/ShareExtension/ShareMastodonContext.swift +++ b/ShareExtension/ShareMastodonContext.swift @@ -72,9 +72,6 @@ final class ShareMastodonContext: ComposeMastodonContext, ObservableObject, Send return [] } - func storeCreatedStatus(_ status: Status) { - } - func fetchStatus(id: String) -> (any StatusProtocol)? { return nil } diff --git a/Tusker/Scenes/ComposeSceneDelegate.swift b/Tusker/Scenes/ComposeSceneDelegate.swift index 8664bee115..c772a52aaf 100644 --- a/Tusker/Scenes/ComposeSceneDelegate.swift +++ b/Tusker/Scenes/ComposeSceneDelegate.swift @@ -121,7 +121,7 @@ extension ComposeSceneDelegate: ComposeHostingControllerDelegate { switch mode { case .cancel: animation = .decline - case .post: + case .post(_), .edit(_): animation = .commit } closeWindow(animation: animation) diff --git a/Tusker/Screens/Compose/ComposeHostingController.swift b/Tusker/Screens/Compose/ComposeHostingController.swift index 67bce479d6..f63e60b7f4 100644 --- a/Tusker/Screens/Compose/ComposeHostingController.swift +++ b/Tusker/Screens/Compose/ComposeHostingController.swift @@ -145,10 +145,41 @@ class ComposeHostingController: UIHostingController (any TuskerNavigationDelegate)? { + if let toastable = vc as? any TuskerNavigationDelegate { + return toastable + } else { + for child in vc.children { + if let navDelegate = findNavDelegate(in: child) { + return navDelegate + } + } + return nil + } + } + private func presentAssetPicker(completion: @MainActor @escaping ([PHPickerResult]) -> Void) { self.assetPickerCompletion = completion @@ -280,10 +311,6 @@ extension MastodonController: ComposeMastodonContext { return results } - func storeCreatedStatus(_ status: Status) { - persistentContainer.addOrUpdate(status: status) - } - func fetchStatus(id: String) -> (any StatusProtocol)? { return persistentContainer.status(for: id) } diff --git a/Tusker/Screens/Main/BaseMainTabBarViewController.swift b/Tusker/Screens/Main/BaseMainTabBarViewController.swift index 006b8a8ef1..b5cd951c00 100644 --- a/Tusker/Screens/Main/BaseMainTabBarViewController.swift +++ b/Tusker/Screens/Main/BaseMainTabBarViewController.swift @@ -178,9 +178,8 @@ extension BaseMainTabBarViewController: StateRestorableViewController { var activity: NSUserActivity? if let presentedNav = presentedViewController as? UINavigationController, let compose = presentedNav.viewControllers.first as? ComposeHostingController { - // TODO: this -// let draft = compose.controller.draft -// activity = UserActivityManager.editDraftActivity(id: draft.id, accountID: draft.accountID) + let draft = compose.state.draft + activity = UserActivityManager.editDraftActivity(id: draft.id, accountID: draft.accountID) } else if let vc = (selectedViewController as? any NavigationControllerProtocol)?.topViewController as? StateRestorableViewController { activity = vc.stateRestorationActivity() } diff --git a/Tusker/Screens/Profile/ProfileViewController.swift b/Tusker/Screens/Profile/ProfileViewController.swift index b6617dc1c3..cc5c45fb05 100644 --- a/Tusker/Screens/Profile/ProfileViewController.swift +++ b/Tusker/Screens/Profile/ProfileViewController.swift @@ -354,6 +354,9 @@ extension ProfileViewController: TuskerNavigationDelegate { } extension ProfileViewController: ToastableViewController { + var toastScrollView: UIScrollView? { + currentViewController.collectionView + } } extension ProfileViewController: ProfileHeaderViewDelegate { diff --git a/Tusker/Screens/Timeline/TimelineViewController.swift b/Tusker/Screens/Timeline/TimelineViewController.swift index 663a4c49e8..4e4f34c0e6 100644 --- a/Tusker/Screens/Timeline/TimelineViewController.swift +++ b/Tusker/Screens/Timeline/TimelineViewController.swift @@ -723,7 +723,7 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro } else { var config = ToastConfiguration(title: "Sync Position") config.edge = .top - config.dismissAutomaticallyAfter = 5 + config.dismissAutomaticallyAfter = 2 config.systemImageName = "arrow.triangle.2.circlepath" config.action = { [unowned self] toast in toast.isUserInteractionEnabled = false @@ -861,7 +861,7 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro var config = ToastConfiguration(title: "Jump to Present") config.edge = .top config.systemImageName = "arrow.up" - config.dismissAutomaticallyAfter = 4 + config.dismissAutomaticallyAfter = 2 config.action = { [unowned self] toast in toast.dismissToast(animated: true) diff --git a/Tusker/Views/Toast/ToastableViewController.swift b/Tusker/Views/Toast/ToastableViewController.swift index 8445e4541b..38cda6e3e9 100644 --- a/Tusker/Views/Toast/ToastableViewController.swift +++ b/Tusker/Views/Toast/ToastableViewController.swift @@ -106,3 +106,23 @@ extension ToastableViewController { } } + +extension UITabBarController: ToastableViewController { + var toastParentView: UIView { + (selectedViewController as? ToastableViewController)?.toastParentView ?? self.view + } + + var toastScrollView: UIScrollView? { + (selectedViewController as? ToastableViewController)?.toastScrollView + } +} + +extension UINavigationController: ToastableViewController { + var toastParentView: UIView { + (topViewController as? ToastableViewController)?.toastParentView ?? self.view + } + + var toastScrollView: UIScrollView? { + (topViewController as? ToastableViewController)?.toastScrollView + } +}