forked from shadowfacts/Tusker
Add multi-window support and auxiliary windows
This commit is contained in:
parent
67a029180e
commit
522c9b2b03
|
@ -186,6 +186,7 @@
|
|||
D681E4D9246E346E0053414F /* AccountActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D681E4D8246E346E0053414F /* AccountActivityItemSource.swift */; };
|
||||
D68232F72464F4FD00325FB8 /* ComposeDrawingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68232F62464F4FD00325FB8 /* ComposeDrawingViewController.swift */; };
|
||||
D686BBE324FBF8110068E6AA /* WrappedProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D686BBE224FBF8110068E6AA /* WrappedProgressView.swift */; };
|
||||
D68C2AE325869BAB00548EFF /* AuxiliarySceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68C2AE225869BAB00548EFF /* AuxiliarySceneDelegate.swift */; };
|
||||
D68E525B24A3D77E0054355A /* TuskerRootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68E525A24A3D77E0054355A /* TuskerRootViewController.swift */; };
|
||||
D68E525D24A3E8F00054355A /* SearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68E525C24A3E8F00054355A /* SearchViewController.swift */; };
|
||||
D68E6F59253C9969001A1B4C /* MultiSourceEmojiLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68E6F58253C9969001A1B4C /* MultiSourceEmojiLabel.swift */; };
|
||||
|
@ -199,6 +200,8 @@
|
|||
D6945C3423AC6431005C403C /* AddSavedHashtagViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C3323AC6431005C403C /* AddSavedHashtagViewController.swift */; };
|
||||
D6945C3823AC739F005C403C /* InstanceTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C3723AC739F005C403C /* InstanceTimelineViewController.swift */; };
|
||||
D6945C3A23AC75E2005C403C /* FindInstanceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6945C3923AC75E2005C403C /* FindInstanceViewController.swift */; };
|
||||
D69693F42585941A00F4E116 /* UIWindowSceneDelegate+Close.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69693F32585941A00F4E116 /* UIWindowSceneDelegate+Close.swift */; };
|
||||
D69693FA25859A8000F4E116 /* ComposeSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69693F925859A8000F4E116 /* ComposeSceneDelegate.swift */; };
|
||||
D6969E9E240C81B9002843CE /* NSTextAttachment+Emoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6969E9D240C81B9002843CE /* NSTextAttachment+Emoji.swift */; };
|
||||
D6969EA0240C8384002843CE /* EmojiLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6969E9F240C8384002843CE /* EmojiLabel.swift */; };
|
||||
D6969EA4240DD28D002843CE /* UnknownNotificationTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6969EA2240DD28D002843CE /* UnknownNotificationTableViewCell.xib */; };
|
||||
|
@ -221,7 +224,7 @@
|
|||
D6A5BB2D23BBA9C4003BF21D /* MyProfileTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A5BB2C23BBA9C4003BF21D /* MyProfileTests.swift */; };
|
||||
D6A5BB2F23BBAC97003BF21D /* DelegatingResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A5BB2E23BBAC97003BF21D /* DelegatingResponse.swift */; };
|
||||
D6A5BB3123BBAD87003BF21D /* JSONResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A5BB3023BBAD87003BF21D /* JSONResponse.swift */; };
|
||||
D6AC956723C4347E008C9946 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AC956623C4347E008C9946 /* SceneDelegate.swift */; };
|
||||
D6AC956723C4347E008C9946 /* MainSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AC956623C4347E008C9946 /* MainSceneDelegate.swift */; };
|
||||
D6ACE1AC240C3BAD004EA8E2 /* Ambassador.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D65F613023AE99E000F3CFD3 /* Ambassador.framework */; };
|
||||
D6ACE1AD240C3BAD004EA8E2 /* Embassy.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D65F612D23AE990C00F3CFD3 /* Embassy.framework */; };
|
||||
D6AEBB3E2321638100E5038B /* UIActivity+Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB3D2321638100E5038B /* UIActivity+Types.swift */; };
|
||||
|
@ -541,6 +544,7 @@
|
|||
D681E4D8246E346E0053414F /* AccountActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountActivityItemSource.swift; sourceTree = "<group>"; };
|
||||
D68232F62464F4FD00325FB8 /* ComposeDrawingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeDrawingViewController.swift; sourceTree = "<group>"; };
|
||||
D686BBE224FBF8110068E6AA /* WrappedProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WrappedProgressView.swift; sourceTree = "<group>"; };
|
||||
D68C2AE225869BAB00548EFF /* AuxiliarySceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuxiliarySceneDelegate.swift; sourceTree = "<group>"; };
|
||||
D68E525A24A3D77E0054355A /* TuskerRootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TuskerRootViewController.swift; sourceTree = "<group>"; };
|
||||
D68E525C24A3E8F00054355A /* SearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewController.swift; sourceTree = "<group>"; };
|
||||
D68E6F58253C9969001A1B4C /* MultiSourceEmojiLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiSourceEmojiLabel.swift; sourceTree = "<group>"; };
|
||||
|
@ -554,6 +558,8 @@
|
|||
D6945C3323AC6431005C403C /* AddSavedHashtagViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddSavedHashtagViewController.swift; sourceTree = "<group>"; };
|
||||
D6945C3723AC739F005C403C /* InstanceTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceTimelineViewController.swift; sourceTree = "<group>"; };
|
||||
D6945C3923AC75E2005C403C /* FindInstanceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = FindInstanceViewController.swift; path = Tusker/Screens/FindInstanceViewController.swift; sourceTree = SOURCE_ROOT; };
|
||||
D69693F32585941A00F4E116 /* UIWindowSceneDelegate+Close.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIWindowSceneDelegate+Close.swift"; sourceTree = "<group>"; };
|
||||
D69693F925859A8000F4E116 /* ComposeSceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeSceneDelegate.swift; sourceTree = "<group>"; };
|
||||
D6969E9D240C81B9002843CE /* NSTextAttachment+Emoji.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTextAttachment+Emoji.swift"; sourceTree = "<group>"; };
|
||||
D6969E9F240C8384002843CE /* EmojiLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiLabel.swift; sourceTree = "<group>"; };
|
||||
D6969EA2240DD28D002843CE /* UnknownNotificationTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = UnknownNotificationTableViewCell.xib; sourceTree = "<group>"; };
|
||||
|
@ -575,7 +581,7 @@
|
|||
D6A5BB2C23BBA9C4003BF21D /* MyProfileTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyProfileTests.swift; sourceTree = "<group>"; };
|
||||
D6A5BB2E23BBAC97003BF21D /* DelegatingResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DelegatingResponse.swift; sourceTree = "<group>"; };
|
||||
D6A5BB3023BBAD87003BF21D /* JSONResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONResponse.swift; sourceTree = "<group>"; };
|
||||
D6AC956623C4347E008C9946 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||
D6AC956623C4347E008C9946 /* MainSceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSceneDelegate.swift; sourceTree = "<group>"; };
|
||||
D6AEBB3D2321638100E5038B /* UIActivity+Types.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIActivity+Types.swift"; sourceTree = "<group>"; };
|
||||
D6AEBB402321642700E5038B /* SendMesasgeActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMesasgeActivity.swift; sourceTree = "<group>"; };
|
||||
D6AEBB422321685E00E5038B /* OpenInSafariActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenInSafariActivity.swift; sourceTree = "<group>"; };
|
||||
|
@ -1177,6 +1183,7 @@
|
|||
D6D3F4C324FDB6B700EC4A6A /* View+ConditionalModifier.swift */,
|
||||
D6D4CC90250D2C3100FCCF8D /* UIAccessibility.swift */,
|
||||
D6620ACD2511A0ED00312CA0 /* StatusStateResolver.swift */,
|
||||
D69693F32585941A00F4E116 /* UIWindowSceneDelegate+Close.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1421,7 +1428,9 @@
|
|||
children = (
|
||||
D6E4885C24A2890C0011C13E /* Tusker.entitlements */,
|
||||
D6D4DDCF212518A000E1C4BB /* AppDelegate.swift */,
|
||||
D6AC956623C4347E008C9946 /* SceneDelegate.swift */,
|
||||
D6AC956623C4347E008C9946 /* MainSceneDelegate.swift */,
|
||||
D68C2AE225869BAB00548EFF /* AuxiliarySceneDelegate.swift */,
|
||||
D69693F925859A8000F4E116 /* ComposeSceneDelegate.swift */,
|
||||
D64D0AAC2128D88B005A6F37 /* LocalData.swift */,
|
||||
D6945C2E23AC47C3005C403C /* SavedDataManager.swift */,
|
||||
D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */,
|
||||
|
@ -1854,6 +1863,7 @@
|
|||
D6BC9DB3232D4C07002CA326 /* WellnessPrefsView.swift in Sources */,
|
||||
D64BC18F23C18B9D000D0238 /* FollowRequestNotificationTableViewCell.swift in Sources */,
|
||||
D693DE5923FE24310061E07D /* InteractivePushTransition.swift in Sources */,
|
||||
D69693FA25859A8000F4E116 /* ComposeSceneDelegate.swift in Sources */,
|
||||
D65234C9256189D0001AF9CF /* TimelineLikeTableViewController.swift in Sources */,
|
||||
D6A3BC8A2321F79B00FD64D5 /* AccountTableViewCell.swift in Sources */,
|
||||
D66A77BB233838DC0058F1EC /* UIFont+Traits.swift in Sources */,
|
||||
|
@ -1875,7 +1885,7 @@
|
|||
D6945C3423AC6431005C403C /* AddSavedHashtagViewController.swift in Sources */,
|
||||
D627943723A552C200D38C68 /* BookmarkStatusActivity.swift in Sources */,
|
||||
D62D2426217ABF63005076CC /* UserActivityType.swift in Sources */,
|
||||
D6AC956723C4347E008C9946 /* SceneDelegate.swift in Sources */,
|
||||
D6AC956723C4347E008C9946 /* MainSceneDelegate.swift in Sources */,
|
||||
D6BC9DD7232D7811002CA326 /* TimelinesPageViewController.swift in Sources */,
|
||||
D60E2F2E244248BF005F8713 /* MastodonCachePersistentStore.swift in Sources */,
|
||||
D620483623D38075008A63EF /* ContentTextView.swift in Sources */,
|
||||
|
@ -1946,6 +1956,7 @@
|
|||
D622757424EDF1CD00B82A16 /* ComposeAttachmentsList.swift in Sources */,
|
||||
D6E0DC8E216EDF1E00369478 /* Previewing.swift in Sources */,
|
||||
D6BED174212667E900F02DA0 /* TimelineStatusTableViewCell.swift in Sources */,
|
||||
D69693F42585941A00F4E116 /* UIWindowSceneDelegate+Close.swift in Sources */,
|
||||
D6C143DA253510F4007DC240 /* ComposeContentWarningTextField.swift in Sources */,
|
||||
0427033822B30F5F000D31B6 /* BehaviorPrefsView.swift in Sources */,
|
||||
D627943923A553B600D38C68 /* UnbookmarkStatusActivity.swift in Sources */,
|
||||
|
@ -1983,6 +1994,7 @@
|
|||
D6D3F4C424FDB6B700EC4A6A /* View+ConditionalModifier.swift in Sources */,
|
||||
D6B8DB342182A59300424AF7 /* UIAlertController+Visibility.swift in Sources */,
|
||||
D67C57AD21E265FC00C3118B /* LargeAccountDetailView.swift in Sources */,
|
||||
D68C2AE325869BAB00548EFF /* AuxiliarySceneDelegate.swift in Sources */,
|
||||
D6AEBB432321685E00E5038B /* OpenInSafariActivity.swift in Sources */,
|
||||
D6C693FE2162FEEA007D6A6D /* UIViewController+Children.swift in Sources */,
|
||||
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */,
|
||||
|
|
|
@ -52,4 +52,33 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
MenuController.buildMainMenu(builder: builder)
|
||||
}
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
|
||||
let name = getSceneNameForActivity(options.userActivities.first)
|
||||
return UISceneConfiguration(name: name, sessionRole: connectingSceneSession.role)
|
||||
}
|
||||
|
||||
private func getSceneNameForActivity(_ activity: NSUserActivity?) -> String {
|
||||
guard let activity = activity,
|
||||
let type = UserActivityType(rawValue: activity.activityType) else {
|
||||
return "main-scene"
|
||||
}
|
||||
|
||||
switch type {
|
||||
case .showConversation,
|
||||
.showTimeline,
|
||||
.checkNotifications,
|
||||
.search,
|
||||
.bookmarks,
|
||||
.myProfile,
|
||||
.showProfile:
|
||||
return "auxiliary"
|
||||
|
||||
case .newPost:
|
||||
return "compose"
|
||||
|
||||
default:
|
||||
fatalError("no scene for activity type \(type)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
//
|
||||
// AuxiliarySceneDelegate.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 12/13/20.
|
||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
|
||||
class AuxiliarySceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
private var launchActivity: NSUserActivity?
|
||||
|
||||
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
|
||||
guard let windowScene = scene as? UIWindowScene else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let activity = connectionOptions.userActivities.first ?? session.stateRestorationActivity else {
|
||||
// without an account, we don't know what type of auxiliary scene this is and can't do anything
|
||||
UIApplication.shared.requestSceneSessionDestruction(session, options: nil, errorHandler: nil)
|
||||
return
|
||||
}
|
||||
launchActivity = activity
|
||||
|
||||
let account: LocalData.UserAccountInfo
|
||||
|
||||
if let activityAccount = UserActivityManager.getAccount(from: activity) {
|
||||
account = activityAccount
|
||||
} else if let mostRecent = LocalData.shared.getMostRecentAccount() {
|
||||
account = mostRecent
|
||||
} else {
|
||||
// without an account, we can't do anything so we just destroy the scene
|
||||
// todo: this isn't really true for instance public timelines, how much do we care about that?
|
||||
UIApplication.shared.requestSceneSessionDestruction(session, options: nil, errorHandler: nil)
|
||||
return
|
||||
}
|
||||
|
||||
let controller = MastodonController.getForAccount(account)
|
||||
session.mastodonController = controller
|
||||
|
||||
guard let rootVC = viewController(for: activity, mastodonController: controller) else {
|
||||
UIApplication.shared.requestSceneSessionDestruction(session, options: nil, errorHandler: nil)
|
||||
return
|
||||
}
|
||||
rootVC.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(close))
|
||||
let nav = EnhancedNavigationViewController(rootViewController: rootVC)
|
||||
|
||||
window = UIWindow(windowScene: windowScene)
|
||||
window!.rootViewController = nav
|
||||
window!.makeKeyAndVisible()
|
||||
}
|
||||
|
||||
func sceneWillResignActive(_ scene: UIScene) {
|
||||
scene.userActivity = launchActivity
|
||||
}
|
||||
|
||||
func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? {
|
||||
return scene.userActivity
|
||||
}
|
||||
|
||||
private func viewController(for activity: NSUserActivity, mastodonController: MastodonController) -> UIViewController? {
|
||||
switch UserActivityType(rawValue: activity.activityType) {
|
||||
case .showTimeline:
|
||||
guard let timeline = UserActivityManager.getTimeline(from: activity) else { return nil }
|
||||
return timelineViewController(for: timeline, mastodonController: mastodonController)
|
||||
|
||||
case .showConversation:
|
||||
guard let id = UserActivityManager.getConversationStatus(from: activity) else { return nil }
|
||||
return ConversationTableViewController(for: id, mastodonController: mastodonController)
|
||||
|
||||
case .checkNotifications:
|
||||
guard let mode = UserActivityManager.getNotificationsMode(from: activity) else { return nil }
|
||||
return NotificationsPageViewController(initialMode: mode, mastodonController: mastodonController)
|
||||
|
||||
case .search:
|
||||
return SearchViewController(mastodonController: mastodonController)
|
||||
|
||||
case .bookmarks:
|
||||
return BookmarksTableViewController(mastodonController: mastodonController)
|
||||
|
||||
case .myProfile:
|
||||
return MyProfileViewController(mastodonController: mastodonController)
|
||||
|
||||
case .showProfile:
|
||||
guard let id = UserActivityManager.getProfile(from: activity) else { return nil }
|
||||
return ProfileViewController(accountID: id, mastodonController: mastodonController)
|
||||
|
||||
default:
|
||||
fatalError("invalid activity type for auxiliary scene: \(activity.activityType)")
|
||||
}
|
||||
}
|
||||
|
||||
private func timelineViewController(for timeline: Timeline, mastodonController: MastodonController) -> UIViewController {
|
||||
switch timeline {
|
||||
// todo: list/hashtag controllers need whole objects which must be fetched asynchronously
|
||||
default:
|
||||
return TimelineTableViewController(for: timeline, mastodonController: mastodonController)
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func close() {
|
||||
closeWindow()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
//
|
||||
// ComposeSceneDelegate.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 12/12/20.
|
||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class ComposeSceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
|
||||
guard let windowScene = scene as? UIWindowScene else {
|
||||
return
|
||||
}
|
||||
|
||||
let account: LocalData.UserAccountInfo
|
||||
let draft: Draft?
|
||||
|
||||
if let activity = connectionOptions.userActivities.first ?? session.stateRestorationActivity,
|
||||
let activityAccount = UserActivityManager.getAccount(from: activity) {
|
||||
account = activityAccount
|
||||
draft = UserActivityManager.getDraft(from: activity)
|
||||
} else {
|
||||
account = LocalData.shared.getMostRecentAccount()!
|
||||
draft = nil
|
||||
}
|
||||
|
||||
let controller = MastodonController.getForAccount(account)
|
||||
session.mastodonController = controller
|
||||
|
||||
let composeVC = ComposeHostingController(draft: draft, mastodonController: controller)
|
||||
composeVC.delegate = self
|
||||
let nav = EnhancedNavigationViewController(rootViewController: composeVC)
|
||||
|
||||
window = UIWindow(windowScene: windowScene)
|
||||
window!.rootViewController = nav
|
||||
window!.makeKeyAndVisible()
|
||||
}
|
||||
|
||||
func sceneWillResignActive(_ scene: UIScene) {
|
||||
DraftsManager.save()
|
||||
|
||||
if let window = window,
|
||||
let nav = window.rootViewController as? UINavigationController,
|
||||
let compose = nav.topViewController as? ComposeHostingController {
|
||||
scene.userActivity = UserActivityManager.editDraftActivity(id: compose.draft.id, accountID: scene.session.mastodonController!.accountInfo!.id)
|
||||
}
|
||||
}
|
||||
|
||||
func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? {
|
||||
return scene.userActivity
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ComposeSceneDelegate: ComposeHostingControllerDelegate {
|
||||
func dismissCompose(mode: ComposeUIState.DismissMode) -> Bool {
|
||||
let animation: UIWindowScene.DismissalAnimation
|
||||
switch mode {
|
||||
case .cancel:
|
||||
animation = .decline
|
||||
case .post:
|
||||
animation = .commit
|
||||
}
|
||||
closeWindow(animation: animation)
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// UIWindowSceneDelegate+Close.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 12/12/20.
|
||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIWindowSceneDelegate {
|
||||
|
||||
func closeWindow(animation: UIWindowScene.DismissalAnimation = .standard, errorHandler: ((Error) -> Void)? = nil) {
|
||||
guard let session = self.window??.windowScene?.session else { return }
|
||||
let options = UIWindowSceneDestructionRequestOptions()
|
||||
options.windowDismissalAnimation = animation
|
||||
UIApplication.shared.requestSceneSessionDestruction(session, options: options, errorHandler: errorHandler)
|
||||
}
|
||||
|
||||
}
|
|
@ -51,21 +51,24 @@
|
|||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Post videos from the camera.</string>
|
||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||
<string>Save photos directly from other people's posts.</string>
|
||||
<string>Save photos directly from other people's posts.</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>Post photos from the photo library.</string>
|
||||
<key>NSUserActivityTypes</key>
|
||||
<array>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.show-conversation</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.show-timeline</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.check-notifications</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.check-mentions</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.new-post</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.search</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.bookmarks</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.my-profile</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.show-profile</string>
|
||||
</array>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<false/>
|
||||
<true/>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict>
|
||||
<key>UIWindowSceneSessionRoleApplication</key>
|
||||
|
@ -76,7 +79,23 @@
|
|||
<key>UISceneConfigurationName</key>
|
||||
<string>main-scene</string>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
||||
<string>$(PRODUCT_MODULE_NAME).MainSceneDelegate</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UISceneClassName</key>
|
||||
<string>UIWindowScene</string>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).AuxiliarySceneDelegate</string>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>auxiliary</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UISceneClassName</key>
|
||||
<string>UIWindowScene</string>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).ComposeSceneDelegate</string>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>compose</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// SceneDelegate.swift
|
||||
// MainSceneDelegate.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 1/6/20.
|
||||
|
@ -11,7 +11,7 @@ import Pachyderm
|
|||
import CrashReporter
|
||||
import MessageUI
|
||||
|
||||
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
class MainSceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
|
@ -195,13 +195,13 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||
|
||||
}
|
||||
|
||||
extension SceneDelegate: OnboardingViewControllerDelegate {
|
||||
extension MainSceneDelegate: OnboardingViewControllerDelegate {
|
||||
func didFinishOnboarding(account: LocalData.UserAccountInfo) {
|
||||
activateAccount(account, animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
extension SceneDelegate: MFMailComposeViewControllerDelegate {
|
||||
extension MainSceneDelegate: MFMailComposeViewControllerDelegate {
|
||||
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
|
||||
controller.dismiss(animated: true) {
|
||||
self.showAppOrOnboardingUI()
|
|
@ -47,4 +47,8 @@ class DraftsManager: Codable {
|
|||
drafts.removeAll { $0 == draft }
|
||||
}
|
||||
|
||||
func getBy(id: UUID) -> Draft? {
|
||||
return drafts.first { $0.id == id }
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,8 +11,14 @@ import Combine
|
|||
import Pachyderm
|
||||
import PencilKit
|
||||
|
||||
protocol ComposeHostingControllerDelegate: class {
|
||||
func dismissCompose(mode: ComposeUIState.DismissMode) -> Bool
|
||||
}
|
||||
|
||||
class ComposeHostingController: UIHostingController<ComposeContainerView> {
|
||||
|
||||
weak var delegate: ComposeHostingControllerDelegate?
|
||||
|
||||
let mastodonController: MastodonController
|
||||
|
||||
let uiState: ComposeUIState
|
||||
|
@ -61,7 +67,7 @@ class ComposeHostingController: UIHostingController<ComposeContainerView> {
|
|||
|
||||
pasteConfiguration = UIPasteConfiguration(forAccepting: CompositionAttachment.self)
|
||||
|
||||
userActivity = UserActivityManager.newPostActivity()
|
||||
userActivity = UserActivityManager.newPostActivity(accountID: mastodonController.accountInfo!.id)
|
||||
|
||||
self.uiState.$draft
|
||||
.flatMap(\.$visibility)
|
||||
|
@ -81,21 +87,6 @@ class ComposeHostingController: UIHostingController<ComposeContainerView> {
|
|||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
// can't do this in viewDidLoad because viewDidLoad isn't called for UIHostingController
|
||||
// if mainToolbar.superview == nil {
|
||||
// view.addSubview(mainToolbar)
|
||||
// NSLayoutConstraint.activate([
|
||||
// mainToolbar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
// mainToolbar.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
// // use the top anchor of the toolbar so our additionalSafeAreaInsets (which has the bottom as the toolbar height) don't affect it
|
||||
// mainToolbar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
|
||||
// ])
|
||||
// }
|
||||
}
|
||||
|
||||
override func didMove(toParent parent: UIViewController?) {
|
||||
super.didMove(toParent: parent)
|
||||
|
||||
|
@ -287,8 +278,11 @@ class ComposeHostingController: UIHostingController<ComposeContainerView> {
|
|||
extension ComposeHostingController: ComposeUIStateDelegate {
|
||||
var assetPickerDelegate: AssetPickerViewControllerDelegate? { self }
|
||||
|
||||
func dismissCompose() {
|
||||
self.dismiss(animated: true)
|
||||
func dismissCompose(mode: ComposeUIState.DismissMode) {
|
||||
let dismissed = delegate?.dismissCompose(mode: mode) ?? false
|
||||
if !dismissed {
|
||||
self.dismiss(animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
func presentAssetPickerSheet() {
|
||||
|
|
|
@ -11,7 +11,7 @@ import SwiftUI
|
|||
protocol ComposeUIStateDelegate: class {
|
||||
var assetPickerDelegate: AssetPickerViewControllerDelegate? { get }
|
||||
|
||||
func dismissCompose()
|
||||
func dismissCompose(mode: ComposeUIState.DismissMode)
|
||||
func presentAssetPickerSheet()
|
||||
func presentComposeDrawing()
|
||||
|
||||
|
@ -54,6 +54,12 @@ extension ComposeUIState {
|
|||
}
|
||||
}
|
||||
|
||||
extension ComposeUIState {
|
||||
enum DismissMode {
|
||||
case cancel, post
|
||||
}
|
||||
}
|
||||
|
||||
protocol ComposeAutocompleteHandler: class {
|
||||
func autocomplete(with string: String)
|
||||
}
|
||||
|
|
|
@ -168,14 +168,14 @@ struct ComposeView: View {
|
|||
private func cancel() {
|
||||
if Preferences.shared.automaticallySaveDrafts {
|
||||
// draft is already stored in drafts manager, drafts manager is saved by ComposeHostingController.viewWillDisappear
|
||||
uiState.delegate?.dismissCompose()
|
||||
uiState.delegate?.dismissCompose(mode: .cancel)
|
||||
} else {
|
||||
// if the draft doesn't have content, it doesn't need to be saved
|
||||
if draft.hasContent {
|
||||
uiState.isShowingSaveDraftSheet = true
|
||||
} else {
|
||||
DraftsManager.shared.remove(draft)
|
||||
uiState.delegate?.dismissCompose()
|
||||
uiState.delegate?.dismissCompose(mode: .cancel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -185,12 +185,12 @@ struct ComposeView: View {
|
|||
.default(Text("Save Draft"), action: {
|
||||
// draft is already stored in drafts manager, drafts manager is saved by ComposeHostingController.viewWillDisappear
|
||||
uiState.isShowingSaveDraftSheet = false
|
||||
uiState.delegate?.dismissCompose()
|
||||
uiState.delegate?.dismissCompose(mode: .cancel)
|
||||
}),
|
||||
.destructive(Text("Delete Draft"), action: {
|
||||
DraftsManager.shared.remove(draft)
|
||||
uiState.isShowingSaveDraftSheet = false
|
||||
uiState.delegate?.dismissCompose()
|
||||
uiState.delegate?.dismissCompose(mode: .cancel)
|
||||
}),
|
||||
.cancel(),
|
||||
])
|
||||
|
@ -242,7 +242,7 @@ struct ComposeView: View {
|
|||
|
||||
// wait .25 seconds so the user can see the progress bar has completed
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(250)) {
|
||||
self.uiState.delegate?.dismissCompose()
|
||||
self.uiState.delegate?.dismissCompose(mode: .post)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -119,7 +119,7 @@ class CrashReporterViewController: UIViewController {
|
|||
}
|
||||
|
||||
@IBAction func cancelPressed(_ sender: Any) {
|
||||
(view.window!.windowScene!.delegate as! SceneDelegate).showAppOrOnboardingUI()
|
||||
(view.window!.windowScene!.delegate as! MainSceneDelegate).showAppOrOnboardingUI()
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -127,7 +127,7 @@ class CrashReporterViewController: UIViewController {
|
|||
extension CrashReporterViewController: MFMailComposeViewControllerDelegate {
|
||||
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
|
||||
controller.dismiss(animated: true) {
|
||||
(self.view.window!.windowScene!.delegate as! SceneDelegate).showAppOrOnboardingUI()
|
||||
(self.view.window!.windowScene!.delegate as! MainSceneDelegate).showAppOrOnboardingUI()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -131,7 +131,7 @@ class FastAccountSwitcherViewController: UIViewController {
|
|||
selectionChangedFeedbackGenerator = nil
|
||||
|
||||
hide() {
|
||||
(self.view.window!.windowScene!.delegate as! SceneDelegate).activateAccount(account, animated: true)
|
||||
(self.view.window!.windowScene!.delegate as! MainSceneDelegate).activateAccount(account, animated: true)
|
||||
}
|
||||
} else {
|
||||
hide()
|
||||
|
|
|
@ -80,6 +80,7 @@ class MainSidebarViewController: UIViewController {
|
|||
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
collectionView.backgroundColor = .clear
|
||||
collectionView.delegate = self
|
||||
collectionView.dragDelegate = self
|
||||
view.addSubview(collectionView)
|
||||
|
||||
dataSource = createDataSource()
|
||||
|
@ -220,6 +221,32 @@ class MainSidebarViewController: UIViewController {
|
|||
present(navController, animated: true)
|
||||
}
|
||||
|
||||
private func userActivityForItem(_ item: Item) -> NSUserActivity? {
|
||||
guard let id = mastodonController.accountInfo?.id else { return nil }
|
||||
|
||||
switch item {
|
||||
case .tab(.notifications):
|
||||
return UserActivityManager.checkNotificationsActivity(mode: Preferences.shared.defaultNotificationsMode)
|
||||
case .tab(.compose):
|
||||
return UserActivityManager.newPostActivity(accountID: id)
|
||||
case .search:
|
||||
return UserActivityManager.searchActivity()
|
||||
case .bookmarks:
|
||||
return UserActivityManager.bookmarksActivity()
|
||||
case .tab(.myProfile):
|
||||
return UserActivityManager.myProfileActivity()
|
||||
case let .list(list):
|
||||
return UserActivityManager.showTimelineActivity(timeline: .list(id: list.id), accountID: id)
|
||||
case let .savedHashtag(tag):
|
||||
return UserActivityManager.showTimelineActivity(timeline: .tag(hashtag: tag.name), accountID: id)
|
||||
case .savedInstance(_):
|
||||
// todo: show timeline activity doesn't work for public timelines
|
||||
return nil
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
|
@ -367,6 +394,19 @@ extension MainSidebarViewController: UICollectionViewDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
extension MainSidebarViewController: UICollectionViewDragDelegate {
|
||||
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
|
||||
guard let item = dataSource.itemIdentifier(for: indexPath),
|
||||
let activity = userActivityForItem(item) else {
|
||||
return []
|
||||
}
|
||||
|
||||
let provider = NSItemProvider(object: activity)
|
||||
return [UIDragItem(itemProvider: provider)]
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
extension MainSidebarViewController: InstanceTimelineViewControllerDelegate {
|
||||
func didSaveInstance(url: URL) {
|
||||
|
|
|
@ -16,16 +16,19 @@ class NotificationsPageViewController: SegmentedPageViewController {
|
|||
|
||||
weak var mastodonController: MastodonController!
|
||||
|
||||
init(mastodonController: MastodonController) {
|
||||
var initialMode: NotificationsMode?
|
||||
|
||||
init(initialMode: NotificationsMode? = nil, mastodonController: MastodonController) {
|
||||
self.initialMode = initialMode
|
||||
self.mastodonController = mastodonController
|
||||
|
||||
let notifications = NotificationsTableViewController(allowedTypes: Pachyderm.Notification.Kind.allCases, mastodonController: mastodonController)
|
||||
notifications.title = notificationsTitle
|
||||
notifications.userActivity = UserActivityManager.checkNotificationsActivity()
|
||||
notifications.userActivity = UserActivityManager.checkNotificationsActivity(mode: .allNotifications)
|
||||
|
||||
let mentions = NotificationsTableViewController(allowedTypes: [.mention], mastodonController: mastodonController)
|
||||
mentions.title = mentionsTitle
|
||||
mentions.userActivity = UserActivityManager.checkMentionsActivity()
|
||||
mentions.userActivity = UserActivityManager.checkNotificationsActivity(mode: .mentionsOnly)
|
||||
|
||||
super.init(titles: [
|
||||
notificationsTitle,
|
||||
|
@ -46,7 +49,7 @@ class NotificationsPageViewController: SegmentedPageViewController {
|
|||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
selectMode(Preferences.shared.defaultNotificationsMode)
|
||||
selectMode(initialMode ?? Preferences.shared.defaultNotificationsMode)
|
||||
}
|
||||
|
||||
func selectMode(_ mode: NotificationsMode) {
|
||||
|
|
|
@ -63,7 +63,8 @@ class PreferencesNavigationController: UINavigationController {
|
|||
// the propper fix would be to figure out what's leaking instances of this class
|
||||
guard let window = self.view.window,
|
||||
let windowScene = window.windowScene,
|
||||
let sceneDelegate = windowScene.delegate as? SceneDelegate else {
|
||||
// todo: my profile can be torn off into a separate window, this doesn't work
|
||||
let sceneDelegate = windowScene.delegate as? MainSceneDelegate else {
|
||||
return
|
||||
}
|
||||
let account = notification.userInfo!["account"] as! LocalData.UserAccountInfo
|
||||
|
@ -76,7 +77,8 @@ class PreferencesNavigationController: UINavigationController {
|
|||
@objc func userLoggedOut() {
|
||||
guard let window = self.view.window,
|
||||
let windowScene = window.windowScene,
|
||||
let sceneDelegate = windowScene.delegate as? SceneDelegate else {
|
||||
// todo: my profile can be torn off into a separate window, this doesn't work
|
||||
let sceneDelegate = windowScene.delegate as? MainSceneDelegate else {
|
||||
return
|
||||
}
|
||||
isSwitchingAccounts = true
|
||||
|
@ -90,7 +92,8 @@ class PreferencesNavigationController: UINavigationController {
|
|||
extension PreferencesNavigationController: OnboardingViewControllerDelegate {
|
||||
func didFinishOnboarding(account: LocalData.UserAccountInfo) {
|
||||
DispatchQueue.main.async {
|
||||
let sceneDelegate = self.view.window!.windowScene!.delegate as! SceneDelegate
|
||||
// todo: my profile can be torn off into a separate window, this will crash
|
||||
let sceneDelegate = self.view.window!.windowScene!.delegate as! MainSceneDelegate
|
||||
self.dismiss(animated: true) { // dismiss instance selector
|
||||
self.dismiss(animated: true) { // dismiss preferences
|
||||
sceneDelegate.activateAccount(account, animated: false)
|
||||
|
|
|
@ -37,6 +37,8 @@ class MyProfileViewController: ProfileViewController {
|
|||
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Preferences", style: .plain, target: self, action: #selector(preferencesPressed))
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(preferencesChanged), name: .preferencesChanged, object: nil)
|
||||
|
||||
userActivity = UserActivityManager.myProfileActivity()
|
||||
}
|
||||
|
||||
private func setAvatarTabBarImage<Account: AccountProtocol>(account: Account) {
|
||||
|
|
|
@ -78,6 +78,8 @@ class ProfileViewController: UIPageViewController {
|
|||
}
|
||||
navigationItem.rightBarButtonItem = composeButton
|
||||
|
||||
userActivity = UserActivityManager.showProfileActivity(id: accountID!, accountID: mastodonController.accountInfo!.id)
|
||||
|
||||
headerView = ProfileHeaderView.create()
|
||||
headerView.delegate = self
|
||||
|
||||
|
|
|
@ -32,6 +32,8 @@ class SearchViewController: UIViewController {
|
|||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
view.backgroundColor = .systemBackground
|
||||
|
||||
resultsController = SearchResultsViewController(mastodonController: mastodonController)
|
||||
resultsController.exploreNavigationController = self.navigationController
|
||||
searchController = UISearchController(searchResultsController: resultsController)
|
||||
|
|
|
@ -28,7 +28,9 @@ class TimelineTableViewController: TimelineLikeTableViewController<TimelineEntry
|
|||
title = timeline.title
|
||||
tabBarItem.image = timeline.tabBarImage
|
||||
|
||||
userActivity = UserActivityManager.showTimelineActivity(timeline: timeline)
|
||||
if let id = mastodonController.accountInfo?.id {
|
||||
userActivity = UserActivityManager.showTimelineActivity(timeline: timeline, accountID: id)
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
|
@ -51,6 +53,8 @@ class TimelineTableViewController: TimelineLikeTableViewController<TimelineEntry
|
|||
super.viewDidLoad()
|
||||
|
||||
tableView.register(UINib(nibName: "TimelineStatusTableViewCell", bundle: .main), forCellReuseIdentifier: "statusCell")
|
||||
|
||||
tableView.dragDelegate = self
|
||||
}
|
||||
|
||||
override class func refreshCommandTitle() -> String {
|
||||
|
@ -171,3 +175,18 @@ extension TimelineTableViewController: UITableViewDataSourcePrefetching {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TimelineTableViewController: UITableViewDragDelegate {
|
||||
func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
|
||||
let id = item(for: indexPath).id
|
||||
guard let status = mastodonController.persistentContainer.status(for: id),
|
||||
let accountId = mastodonController.accountInfo?.id else {
|
||||
return []
|
||||
}
|
||||
let activity = UserActivityManager.showConversationActivity(mainStatusID: id, accountID: accountId)
|
||||
let itemProvider = NSItemProvider(object: status.url! as NSURL)
|
||||
itemProvider.registerObject(activity, visibility: .all)
|
||||
let dragItem = UIDragItem(itemProvider: itemProvider)
|
||||
return [dragItem]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -189,7 +189,7 @@ extension MenuPreviewProvider {
|
|||
}))
|
||||
}
|
||||
|
||||
let shareSection = [
|
||||
var shareSection = [
|
||||
openInSafariAction(url: status.url!),
|
||||
createAction(identifier: "share", title: "Share...", systemImageName: "square.and.arrow.up", handler: { [weak self] (_) in
|
||||
guard let self = self else { return }
|
||||
|
@ -197,14 +197,30 @@ extension MenuPreviewProvider {
|
|||
}),
|
||||
]
|
||||
|
||||
#if targetEnvironment(macCatalyst)
|
||||
shareSection.append(createAction(identifier: "new_window", title: "Open in New Window", systemImageName: "", handler: { (_) in
|
||||
guard let id = mastodonController.accountInfo?.id else {
|
||||
return
|
||||
}
|
||||
// todo: this should try to find an existing session
|
||||
UIApplication.shared.requestSceneSessionActivation(nil, userActivity: UserActivityManager.showConversationActivity(mainStatusID: statusID, accountID: id), options: nil, errorHandler: nil)
|
||||
}))
|
||||
#endif
|
||||
|
||||
return [
|
||||
UIMenu(title: "", image: nil, identifier: nil, options: [.displayInline], children: shareSection),
|
||||
UIMenu(title: "", image: nil, identifier: nil, options: [.displayInline], children: actionsSection),
|
||||
]
|
||||
}
|
||||
|
||||
private func createAction(identifier: String, title: String, systemImageName: String, handler: @escaping UIActionHandler) -> UIAction {
|
||||
return UIAction(title: title, image: UIImage(systemName: systemImageName), identifier: UIAction.Identifier(identifier), discoverabilityTitle: nil, attributes: [], state: .off, handler: handler)
|
||||
private func createAction(identifier: String, title: String, systemImageName: String?, handler: @escaping UIActionHandler) -> UIAction {
|
||||
let image: UIImage?
|
||||
if let name = systemImageName {
|
||||
image = UIImage(systemName: name)
|
||||
} else {
|
||||
image = nil
|
||||
}
|
||||
return UIAction(title: title, image: image, identifier: UIAction.Identifier(identifier), discoverabilityTitle: nil, attributes: [], state: .off, handler: handler)
|
||||
}
|
||||
|
||||
private func openInSafariAction(url: URL) -> UIAction {
|
||||
|
|
|
@ -30,18 +30,27 @@ class UserActivityManager {
|
|||
private static func present(_ vc: UIViewController, animated: Bool = true) {
|
||||
getMainViewController().present(vc, animated: animated)
|
||||
}
|
||||
|
||||
static func getAccount(from activity: NSUserActivity) -> LocalData.UserAccountInfo? {
|
||||
guard let id = activity.userInfo?["accountID"] as? String else {
|
||||
return nil
|
||||
}
|
||||
return LocalData.shared.getAccount(id: id)
|
||||
}
|
||||
|
||||
// MARK: - New Post
|
||||
static func newPostActivity(mentioning: Account? = nil) -> NSUserActivity {
|
||||
static func newPostActivity(mentioning: Account? = nil, accountID: String) -> NSUserActivity {
|
||||
// todo: update to use managed objects
|
||||
let activity = NSUserActivity(type: .newPost)
|
||||
activity.isEligibleForPrediction = true
|
||||
activity.userInfo = [
|
||||
"accountID": accountID,
|
||||
]
|
||||
if let mentioning = mentioning {
|
||||
activity.userInfo = ["mentioning": mentioning.acct]
|
||||
activity.userInfo!["mentioning"] = mentioning.acct
|
||||
activity.title = "Send a message to \(mentioning.displayName)"
|
||||
activity.suggestedInvocationPhrase = "Send a message to \(mentioning.displayName)"
|
||||
} else {
|
||||
activity.userInfo = [:]
|
||||
activity.title = "New Post"
|
||||
activity.suggestedInvocationPhrase = "Post in Tusker"
|
||||
}
|
||||
|
@ -56,12 +65,39 @@ class UserActivityManager {
|
|||
present(UINavigationController(rootViewController: composeVC))
|
||||
}
|
||||
|
||||
static func editDraftActivity(id: UUID, accountID: String) -> NSUserActivity {
|
||||
let activity = NSUserActivity(type: .newPost)
|
||||
activity.userInfo = [
|
||||
"accountID": accountID,
|
||||
"draftID": id.uuidString,
|
||||
]
|
||||
return activity
|
||||
}
|
||||
|
||||
static func getDraft(from activity: NSUserActivity) -> Draft? {
|
||||
guard activity.activityType == UserActivityType.newPost.rawValue,
|
||||
let str = activity.userInfo?["draftID"] as? String,
|
||||
let uuid = UUID(uuidString: str) else {
|
||||
return nil
|
||||
}
|
||||
return DraftsManager.shared.getBy(id: uuid)
|
||||
}
|
||||
|
||||
// MARK: - Check Notifications
|
||||
static func checkNotificationsActivity() -> NSUserActivity {
|
||||
static func checkNotificationsActivity(mode: NotificationsMode = .allNotifications) -> NSUserActivity {
|
||||
let activity = NSUserActivity(type: .checkNotifications)
|
||||
activity.isEligibleForPrediction = true
|
||||
activity.title = NSLocalizedString("Check Notifications", comment: "check notifications shortcut title")
|
||||
activity.suggestedInvocationPhrase = NSLocalizedString("Check my Tusker notifications", comment: "check notifications shortcut invocation phrase")
|
||||
activity.addUserInfoEntries(from: [
|
||||
"notificationsMode": mode.rawValue
|
||||
])
|
||||
switch mode {
|
||||
case .allNotifications:
|
||||
activity.title = NSLocalizedString("Check Notifications", comment: "check notifications shortcut title")
|
||||
activity.suggestedInvocationPhrase = NSLocalizedString("Check my Tusker notifications", comment: "check notifications shortcut invocation phrase")
|
||||
case .mentionsOnly:
|
||||
activity.title = NSLocalizedString("Check Mentions", comment: "check mentions shortcut title")
|
||||
activity.suggestedInvocationPhrase = NSLocalizedString("Check my mentions", comment: "check mentions shortcut invocation phrase")
|
||||
}
|
||||
return activity
|
||||
}
|
||||
|
||||
|
@ -72,37 +108,27 @@ class UserActivityManager {
|
|||
let notificationsPageController = navigationController.viewControllers.first as? NotificationsPageViewController {
|
||||
navigationController.popToRootViewController(animated: false)
|
||||
notificationsPageController.loadViewIfNeeded()
|
||||
notificationsPageController.selectMode(.allNotifications)
|
||||
notificationsPageController.selectMode(getNotificationsMode(from: activity) ?? Preferences.shared.defaultNotificationsMode)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Check Mentions
|
||||
static func checkMentionsActivity() -> NSUserActivity {
|
||||
let activity = NSUserActivity(type: .checkMentions)
|
||||
activity.isEligibleForPrediction = true
|
||||
activity.title = NSLocalizedString("Check Mentions", comment: "check mentions shortcut title")
|
||||
activity.suggestedInvocationPhrase = NSLocalizedString("Check my mentions", comment: "check mentions shortcut invocation phrase")
|
||||
return activity
|
||||
}
|
||||
|
||||
static func handleCheckMentions(activity: NSUserActivity) {
|
||||
let mainViewController = getMainViewController()
|
||||
mainViewController.select(tab: .notifications)
|
||||
if let navController = mainViewController.getTabController(tab: .notifications) as? UINavigationController,
|
||||
let notificationsPageController = navController.viewControllers.first as? NotificationsPageViewController {
|
||||
navController.popToRootViewController(animated: false)
|
||||
notificationsPageController.loadViewIfNeeded()
|
||||
notificationsPageController.selectMode(.mentionsOnly)
|
||||
static func getNotificationsMode(from activity: NSUserActivity) -> NotificationsMode? {
|
||||
guard let str = activity.userInfo?["notificationsMode"] as? String else {
|
||||
return nil
|
||||
}
|
||||
return NotificationsMode(rawValue: str)
|
||||
}
|
||||
|
||||
// MARK: - Show Timeline
|
||||
static func showTimelineActivity(timeline: Timeline) -> NSUserActivity? {
|
||||
static func showTimelineActivity(timeline: Timeline, accountID: String) -> NSUserActivity? {
|
||||
guard let timelineData = try? encoder.encode(timeline) else { return nil }
|
||||
|
||||
let activity = NSUserActivity(type: .showTimeline)
|
||||
activity.isEligibleForPrediction = true
|
||||
activity.userInfo = ["timelineData": timelineData]
|
||||
activity.userInfo = [
|
||||
"timelineData": timelineData,
|
||||
"accountID": accountID,
|
||||
]
|
||||
switch timeline {
|
||||
case .home:
|
||||
activity.title = NSLocalizedString("Show Home Timeline", comment: "home timeline shortcut title")
|
||||
|
@ -127,11 +153,16 @@ class UserActivityManager {
|
|||
return activity
|
||||
}
|
||||
|
||||
static func handleShowTimeline(activity: NSUserActivity) {
|
||||
guard let timelineData = activity.userInfo?["timelineData"] as? Data,
|
||||
let timeline = try? decoder.decode(Timeline.self, from: timelineData) else {
|
||||
return
|
||||
static func getTimeline(from activity: NSUserActivity) -> Timeline? {
|
||||
guard activity.activityType == UserActivityType.showTimeline.rawValue,
|
||||
let data = activity.userInfo?["timelineData"] as? Data else {
|
||||
return nil
|
||||
}
|
||||
return try? decoder.decode(Timeline.self, from: data)
|
||||
}
|
||||
|
||||
static func handleShowTimeline(activity: NSUserActivity) {
|
||||
guard let timeline = getTimeline(from: activity) else { return }
|
||||
|
||||
let mainViewController = getMainViewController()
|
||||
mainViewController.select(tab: .timelines)
|
||||
|
@ -162,6 +193,21 @@ class UserActivityManager {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - Show Conversation
|
||||
static func showConversationActivity(mainStatusID: String, accountID: String, isEligibleForPrediction: Bool = false) -> NSUserActivity {
|
||||
let activity = NSUserActivity(type: .showConversation)
|
||||
activity.userInfo = [
|
||||
"mainStatusID": mainStatusID,
|
||||
"accountID": accountID,
|
||||
]
|
||||
activity.isEligibleForPrediction = isEligibleForPrediction
|
||||
return activity
|
||||
}
|
||||
|
||||
static func getConversationStatus(from activity: NSUserActivity) -> String? {
|
||||
return activity.userInfo?["mainStatusID"] as? String
|
||||
}
|
||||
|
||||
// MARK: - Explore
|
||||
|
||||
static func searchActivity() -> NSUserActivity {
|
||||
|
@ -200,4 +246,33 @@ class UserActivityManager {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - My Profile
|
||||
static func myProfileActivity() -> NSUserActivity {
|
||||
let activity = NSUserActivity(type: .myProfile)
|
||||
activity.isEligibleForPrediction = true
|
||||
activity.title = NSLocalizedString("My Profile", comment: "my profile shortcut title")
|
||||
activity.suggestedInvocationPhrase = NSLocalizedString("Show my Mastodon profile", comment: "my profile shortuct invocation phrase")
|
||||
return activity
|
||||
}
|
||||
|
||||
static func handleMyProfile(activity: NSUserActivity) {
|
||||
let mainViewController = getMainViewController()
|
||||
mainViewController.select(tab: .myProfile)
|
||||
}
|
||||
|
||||
// MARK: - Show Profile
|
||||
static func showProfileActivity(id profileID: String, accountID: String) -> NSUserActivity {
|
||||
let activity = NSUserActivity(type: .showProfile)
|
||||
activity.userInfo = [
|
||||
"profileID": profileID,
|
||||
"accountID": accountID,
|
||||
]
|
||||
// todo: should this be eligible for prediction?
|
||||
return activity
|
||||
}
|
||||
|
||||
static func getProfile(from activity: NSUserActivity) -> String? {
|
||||
return activity.userInfo?["profileID"] as? String
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,12 +9,14 @@
|
|||
import Foundation
|
||||
|
||||
enum UserActivityType: String {
|
||||
case newPost = "net.shadowfacts.Tusker.activity.new-post"
|
||||
case checkNotifications = "net.shadowfacts.Tusker.activity.check-notifications"
|
||||
case checkMentions = "net.shadowfacts.Tusker.activity.check-mentions"
|
||||
case showTimeline = "net.shadowfacts.Tusker.activity.show-timeline"
|
||||
case search = "net.shadowfacts.Tusker.activity.search"
|
||||
case bookmarks = "net.shadowfacts.Tusker.activity.bookmarks"
|
||||
case newPost = "space.vaccor.Tusker.activity.new-post"
|
||||
case checkNotifications = "space.vaccor.Tusker.activity.check-notifications"
|
||||
case showTimeline = "space.vaccor.Tusker.activity.show-timeline"
|
||||
case search = "space.vaccor.Tusker.activity.search"
|
||||
case bookmarks = "space.vaccor.Tusker.activity.bookmarks"
|
||||
case showConversation = "space.vaccor.Tusker.activity.show-conversation"
|
||||
case myProfile = "space.vaccor.Tusker.activity.my-profile"
|
||||
case showProfile = "space.vaccor.Tusker.activity.show-profile"
|
||||
}
|
||||
|
||||
extension UserActivityType {
|
||||
|
@ -24,14 +26,18 @@ extension UserActivityType {
|
|||
return UserActivityManager.handleNewPost
|
||||
case .checkNotifications:
|
||||
return UserActivityManager.handleCheckNotifications
|
||||
case .checkMentions:
|
||||
return UserActivityManager.handleCheckMentions
|
||||
case .showTimeline:
|
||||
return UserActivityManager.handleShowTimeline
|
||||
case .search:
|
||||
return UserActivityManager.handleSearch
|
||||
case .bookmarks:
|
||||
return UserActivityManager.handleBookmarks
|
||||
case .showConversation:
|
||||
fatalError("cannot handle show conversation activity")
|
||||
case .myProfile:
|
||||
return UserActivityManager.handleMyProfile
|
||||
case .showProfile:
|
||||
fatalError("cannot handle show profile activity")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,9 +81,10 @@ class BaseStatusTableViewCell: UITableViewCell {
|
|||
|
||||
displayNameLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(accountPressed)))
|
||||
usernameLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(accountPressed)))
|
||||
|
||||
avatarImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(accountPressed)))
|
||||
|
||||
avatarImageView.layer.masksToBounds = true
|
||||
avatarImageView.addInteraction(UIDragInteraction(delegate: self))
|
||||
|
||||
attachmentsView.delegate = self
|
||||
|
||||
|
@ -479,3 +480,13 @@ extension BaseStatusTableViewCell: MenuPreviewProvider {
|
|||
return self.getStatusCellPreviewProviders(for: location, sourceViewController: sourceViewController)
|
||||
}
|
||||
}
|
||||
|
||||
extension BaseStatusTableViewCell: UIDragInteractionDelegate {
|
||||
func dragInteraction(_ interaction: UIDragInteraction, itemsForBeginning session: UIDragSession) -> [UIDragItem] {
|
||||
guard let currentAccountID = mastodonController.accountInfo?.id else {
|
||||
return []
|
||||
}
|
||||
let provider = NSItemProvider(object: UserActivityManager.showProfileActivity(id: accountID, accountID: currentAccountID))
|
||||
return [UIDragItem(itemProvider: provider)]
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue