diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 982a9ff475..ff2547577f 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -454,7 +454,6 @@ 4520D8D51D417D8E00123472 /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4520D8D41D417D8E00123472 /* Photos.framework */; }; 452EC6DF205E9E30000E787C /* MediaGallery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452EC6DE205E9E30000E787C /* MediaGallery.swift */; }; 4535186B1FC635DD00210559 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4535186A1FC635DD00210559 /* ShareViewController.swift */; }; - 4535186E1FC635DD00210559 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4535186C1FC635DD00210559 /* MainInterface.storyboard */; }; 453518721FC635DD00210559 /* SignalShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 453518681FC635DD00210559 /* SignalShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 45360B911F952AA900FA666C /* MarqueeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E5A6981F61E6DD001E4A8A /* MarqueeLabel.swift */; }; 4539B5861F79348F007141FF /* PushRegistrationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4539B5851F79348F007141FF /* PushRegistrationManager.swift */; }; @@ -4352,7 +4351,6 @@ 452EC6DE205E9E30000E787C /* MediaGallery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaGallery.swift; sourceTree = ""; }; 453518681FC635DD00210559 /* SignalShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SignalShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 4535186A1FC635DD00210559 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = ""; }; - 4535186D1FC635DD00210559 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; 4535186F1FC635DD00210559 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4539B5851F79348F007141FF /* PushRegistrationManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushRegistrationManager.swift; sourceTree = ""; }; 4542DF53208D40AC007B4E76 /* LoadingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingViewController.swift; sourceTree = ""; }; @@ -8842,7 +8840,6 @@ isa = PBXGroup; children = ( 4535186F1FC635DD00210559 /* Info.plist */, - 4535186C1FC635DD00210559 /* MainInterface.storyboard */, 5010526A2BDB23F50097DDC5 /* PrivacyInfo.xcprivacy */, 347850561FD86544007B8332 /* SAEFailedViewController.swift */, 3461284A1FD0B93F00532771 /* SAELoadViewController.swift */, @@ -15085,7 +15082,6 @@ buildActionMask = 2147483647; files = ( 3478504C1FD7496D007B8332 /* Images.xcassets in Resources */, - 4535186E1FC635DD00210559 /* MainInterface.storyboard in Resources */, 5010526B2BDB23F50097DDC5 /* PrivacyInfo.xcprivacy in Resources */, 7665BC9E2A3A72910060279B /* Symbols.xcassets in Resources */, ); @@ -19142,14 +19138,6 @@ name = PluralAware.stringsdict; sourceTree = ""; }; - 4535186C1FC635DD00210559 /* MainInterface.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 4535186D1FC635DD00210559 /* Base */, - ); - name = MainInterface.storyboard; - sourceTree = ""; - }; A5E7C673248C5442007C949A /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( diff --git a/SignalShareExtension/Base.lproj/MainInterface.storyboard b/SignalShareExtension/Base.lproj/MainInterface.storyboard deleted file mode 100644 index 2e59abc171..0000000000 --- a/SignalShareExtension/Base.lproj/MainInterface.storyboard +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SignalShareExtension/Info.plist b/SignalShareExtension/Info.plist index 9092698caa..62370a237e 100644 --- a/SignalShareExtension/Info.plist +++ b/SignalShareExtension/Info.plist @@ -80,10 +80,10 @@ ) - NSExtensionMainStoryboard - MainInterface NSExtensionPointIdentifier com.apple.share-services + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).ShareViewController OWSBundleIDPrefix $(SIGNAL_BUNDLEID_PREFIX) diff --git a/SignalShareExtension/SAELoadViewController.swift b/SignalShareExtension/SAELoadViewController.swift index 82bfccc40c..9d284b1cd9 100644 --- a/SignalShareExtension/SAELoadViewController.swift +++ b/SignalShareExtension/SAELoadViewController.swift @@ -8,12 +8,13 @@ import PureLayout import SignalServiceKit import SignalUI -class SAELoadViewController: UIViewController { +class SAELoadViewController: UIViewController, OWSNavigationChildController { - weak var delegate: ShareViewDelegate? + private weak var delegate: ShareViewDelegate? + private let shouldMimicRecipientPicker: Bool - var activityIndicator: UIActivityIndicatorView! - var progressView: UIProgressView! + private var activityIndicator: UIActivityIndicatorView! + private var progressView: UIProgressView! var progress: Progress? { didSet { @@ -26,7 +27,7 @@ class SAELoadViewController: UIViewController { } } - func updateProgressViewVisibility() { + private func updateProgressViewVisibility() { guard progressView != nil, activityIndicator != nil else { return } @@ -45,8 +46,9 @@ class SAELoadViewController: UIViewController { // MARK: Initializers and Factory Methods - init(delegate: ShareViewDelegate) { + init(delegate: ShareViewDelegate, shouldMimicRecipientPicker: Bool = false) { self.delegate = delegate + self.shouldMimicRecipientPicker = shouldMimicRecipientPicker super.init(nibName: nil, bundle: nil) } @@ -58,7 +60,22 @@ class SAELoadViewController: UIViewController { override func loadView() { super.loadView() - self.view.backgroundColor = Theme.backgroundColor + // It's not (currently) safe to create a SharingThreadPickerViewController + // while the Share Extension is launching, so instead mimic the header of + // the picker on the loading view controller. + // + // TODO: Make it safe to do so and remove this hack. + if self.shouldMimicRecipientPicker { + self.title = ConversationPickerViewController.Strings.title + self.navigationItem.leftBarButtonItem = .cancelButton(action: {}) + self.navigationItem.leftBarButtonItem?.isEnabled = false + } + + self.view.backgroundColor = ( + self.shouldMimicRecipientPicker + ? Theme.tableView2PresentedBackgroundColor + : Theme.backgroundColor + ) let activityIndicator = UIActivityIndicatorView(style: .large) activityIndicator.color = Theme.primaryIconColor @@ -86,9 +103,15 @@ class SAELoadViewController: UIViewController { label.autoPinEdge(.top, to: .bottom, of: activityIndicator, withOffset: 12) } - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) + var preferredNavigationBarStyle: OWSNavigationBarStyle { + // The false case should be the default, but we can't access the + // extension's default implementation here. + return self.shouldMimicRecipientPicker ? .solid : .blur + } - self.navigationController?.isNavigationBarHidden = false + var navbarBackgroundColorOverride: UIColor? { + // The false case should be the default, but we can't access the + // extension's default implementation here. + return self.shouldMimicRecipientPicker ? Theme.tableView2PresentedBackgroundColor : nil } } diff --git a/SignalShareExtension/ShareViewController.swift b/SignalShareExtension/ShareViewController.swift index ab3f7e7794..ca88483198 100644 --- a/SignalShareExtension/ShareViewController.swift +++ b/SignalShareExtension/ShareViewController.swift @@ -5,12 +5,12 @@ import CoreServices import Intents -public import PureLayout +import PureLayout import SignalServiceKit public import SignalUI import UniformTypeIdentifiers -public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailedViewDelegate { +public class ShareViewController: OWSNavigationController, ShareViewDelegate, SAEFailedViewDelegate { enum ShareViewControllerError: Error { case obsoleteShare @@ -23,12 +23,14 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed case noAttachments } - public var shareViewNavigationController: OWSNavigationController? + public var shareViewNavigationController: OWSNavigationController { self } private lazy var appReadiness = AppReadinessImpl() private var connectionTokens = [OWSChatConnection.ConnectionToken]() + private var initialLoadViewController: SAELoadViewController? + override open func loadView() { super.loadView() @@ -43,6 +45,29 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed Logger.info("") + let initialLoadViewController = SAELoadViewController( + delegate: self, + shouldMimicRecipientPicker: self.extensionContext?.intent == nil, + ) + self.setViewControllers([initialLoadViewController], animated: false) + self.initialLoadViewController = initialLoadViewController + } + + public override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + if let initialLoadViewController = self.initialLoadViewController.take() { + // Wait one run loop to ensure the loading indicator is visible if setUp + // blocks the main thread. + DispatchQueue.main.async { + Task { try await self.setUp(initialLoadViewController: initialLoadViewController) } + } + } + } + + private func setUp(initialLoadViewController: SAELoadViewController) async throws { + let appContext = CurrentAppContext() + let keychainStorage = KeychainStorageImpl(isUsingProductionService: TSConstants.isUsingProductionService) let databaseStorage: SDSDatabaseStorage do { @@ -57,166 +82,146 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed } databaseStorage.grdbStorage.setUpDatabasePathKVO() - let shareViewNavigationController = OWSNavigationController() - shareViewNavigationController.presentationController?.delegate = self - shareViewNavigationController.delegate = self - self.shareViewNavigationController = shareViewNavigationController - - Task { - let initialLoadViewController = SAELoadViewController(delegate: self) - var didDisplaceInitialLoadViewController = false - async let _ = { @MainActor () async throws -> Void in - // Don't display load screen immediately because loading the database and - // preparing attachments (if the recipient is pre-selected) will usually be - // fast enough that we can avoid it altogether. If you haven't run GRDB - // migrations in a while or have selected a long video, though, you'll - // likely see the load screen after 800ms. - try await Task.sleep(nanoseconds: 0.8.clampedNanoseconds) - guard self.presentedViewController == nil else { - return - } - self.showPrimaryViewController(initialLoadViewController) - }() - - let databaseContinuation = await AppSetup() - .start( - appContext: appContext, - databaseStorage: databaseStorage, - ) - .migrateDatabaseSchema() - .initGlobals( - appReadiness: appReadiness, - backupArchiveErrorPresenterFactory: NoOpBackupArchiveErrorPresenterFactory(), - deviceBatteryLevelManager: nil, - deviceSleepManager: nil, - paymentsEvents: PaymentsEventsAppExtension(), - mobileCoinHelper: MobileCoinHelperMinimal(), - callMessageHandler: NoopCallMessageHandler(), - currentCallProvider: CurrentCallNoOpProvider(), - notificationPresenter: NoopNotificationPresenterImpl(), - incrementalMessageTSAttachmentMigratorFactory: NoOpIncrementalMessageTSAttachmentMigratorFactory(), - ) - - // Configure the rest of the globals before preparing the database. - SUIEnvironment.shared.setUp( + let databaseContinuation = await AppSetup() + .start( + appContext: appContext, + databaseStorage: databaseStorage, + ) + .migrateDatabaseSchema() + .initGlobals( appReadiness: appReadiness, - authCredentialManager: databaseContinuation.authCredentialManager + backupArchiveErrorPresenterFactory: NoOpBackupArchiveErrorPresenterFactory(), + deviceBatteryLevelManager: nil, + deviceSleepManager: nil, + paymentsEvents: PaymentsEventsAppExtension(), + mobileCoinHelper: MobileCoinHelperMinimal(), + callMessageHandler: NoopCallMessageHandler(), + currentCallProvider: CurrentCallNoOpProvider(), + notificationPresenter: NoopNotificationPresenterImpl(), + incrementalMessageTSAttachmentMigratorFactory: NoOpIncrementalMessageTSAttachmentMigratorFactory(), ) - let finalContinuation = await databaseContinuation.migrateDatabaseData() - finalContinuation.runLaunchTasksIfNeededAndReloadCaches() - switch finalContinuation.setUpLocalIdentifiers( - willResumeInProgressRegistration: false, - canInitiateRegistration: false - ) { - case .corruptRegistrationState: - self.showNotRegisteredView() - return - case nil: - self.setAppIsReady() - } + // Configure the rest of the globals before preparing the database. + SUIEnvironment.shared.setUp( + appReadiness: appReadiness, + authCredentialManager: databaseContinuation.authCredentialManager + ) - if ScreenLock.shared.isScreenLockEnabled() { - let didUnlock = await withCheckedContinuation { continuation in - let viewController = SAEScreenLockViewController { didUnlock in - continuation.resume(returning: didUnlock) - } - self.showPrimaryViewController(viewController) + let finalContinuation = await databaseContinuation.migrateDatabaseData() + finalContinuation.runLaunchTasksIfNeededAndReloadCaches() + switch finalContinuation.setUpLocalIdentifiers( + willResumeInProgressRegistration: false, + canInitiateRegistration: false + ) { + case .corruptRegistrationState: + self.showNotRegisteredView() + return + case nil: + self.setAppIsReady() + } + + var didDisplaceInitialLoadViewController = false + + if ScreenLock.shared.isScreenLockEnabled() { + let didUnlock = await withCheckedContinuation { continuation in + let viewController = SAEScreenLockViewController { didUnlock in + continuation.resume(returning: didUnlock) } - guard didUnlock else { - self.shareViewWasCancelled() + self.setViewControllers([viewController], animated: false) + } + guard didUnlock else { + self.shareViewWasCancelled() + return + } + // If we show the Screen Lock UI, that'll displace the loading view + // controller or prevent it from being shown. + didDisplaceInitialLoadViewController = true + } + + // Prepare the attachments. + + let typedItemProviders: [TypedItemProvider] + do { + typedItemProviders = try buildTypedItemProviders() + } catch { + self.presentAttachmentError(error) + return + } + + // We need the unidentified connection for bulk identity key lookups. + let chatConnectionManager = DependenciesBridge.shared.chatConnectionManager + self.connectionTokens.append(chatConnectionManager.requestUnidentifiedConnection()) + + let conversationPicker: SharingThreadPickerViewController + conversationPicker = SharingThreadPickerViewController( + areAttachmentStoriesCompatPrecheck: typedItemProviders.allSatisfy { $0.isStoriesCompatible }, + shareViewDelegate: self + ) + + let preSelectedThread = self.fetchPreSelectedThread() + + let loadViewControllerToDisplay: SAELoadViewController? + let loadViewControllerForProgress: SAELoadViewController? + + // If we have a pre-selected thread, we wait to show the approval view + // until the attachments have been built. Otherwise, we'll present it + // immediately and tell it what attachments we're sharing once we've + // finished building them. + if preSelectedThread == nil { + self.setViewControllers([conversationPicker], animated: false) + // We show a progress spinner on the recipient picker. + loadViewControllerToDisplay = nil + loadViewControllerForProgress = nil + } else if didDisplaceInitialLoadViewController { + // We hit this branch when isScreenLockEnabled() == true. In this case, we + // need a new instance because the initial one has already been + // shown/dismissed. + loadViewControllerToDisplay = SAELoadViewController(delegate: self) + loadViewControllerForProgress = loadViewControllerToDisplay + } else { + // We don't need to show anything (it'll be shown by the block at the + // beginning of this Task), but we do want to hook up progress reporting. + loadViewControllerToDisplay = nil + loadViewControllerForProgress = initialLoadViewController + } + + let attachments: [SignalAttachment] + do { + // If buildAndValidateAttachments takes longer than 200ms, we want to show + // the new load view. If it takes less than 200ms, we'll exit out of this + // `do` block, that will cancel the `async let`, and then we'll leave the + // primary view controller alone as a result. + async let _ = { @MainActor () async throws -> Void in + guard let loadViewControllerToDisplay else { return } - // If we show the Screen Lock UI, that'll displace the loading view - // controller or prevent it from being shown. - didDisplaceInitialLoadViewController = true - } - - // Prepare the attachments. - - let typedItemProviders: [TypedItemProvider] - do { - typedItemProviders = try buildTypedItemProviders() - } catch { - self.presentAttachmentError(error) - return - } - - // We need the unidentified connection for bulk identity key lookups. - let chatConnectionManager = DependenciesBridge.shared.chatConnectionManager - self.connectionTokens.append(chatConnectionManager.requestUnidentifiedConnection()) - - let conversationPicker: SharingThreadPickerViewController - conversationPicker = SharingThreadPickerViewController( - areAttachmentStoriesCompatPrecheck: typedItemProviders.allSatisfy { $0.isStoriesCompatible }, - shareViewDelegate: self + try await Task.sleep(nanoseconds: 0.2.clampedNanoseconds) + // Check for cancellation on the main thread to ensure mutual exclusion + // with the the code outside of this do block. + try Task.checkCancellation() + self.setViewControllers([loadViewControllerToDisplay], animated: false) + }() + attachments = try await buildAndValidateAttachments( + for: typedItemProviders, + setProgress: { loadViewControllerForProgress?.progress = $0 } ) + } catch { + self.presentAttachmentError(error) + return + } - let preSelectedThread = self.fetchPreSelectedThread() + Logger.info("Setting picker attachments: \(attachments)") + conversationPicker.attachments = attachments - let loadViewControllerToDisplay: SAELoadViewController? - let loadViewControllerForProgress: SAELoadViewController? + if let preSelectedThread { + let approvalViewController = try conversationPicker.buildApprovalViewController(for: preSelectedThread) + self.setViewControllers([approvalViewController], animated: false) - // If we have a pre-selected thread, we wait to show the approval view - // until the attachments have been built. Otherwise, we'll present it - // immediately and tell it what attachments we're sharing once we've - // finished building them. - if preSelectedThread == nil { - self.showPrimaryViewController(conversationPicker) - // We show a progress spinner on the recipient picker. - loadViewControllerToDisplay = nil - loadViewControllerForProgress = nil - } else if didDisplaceInitialLoadViewController { - // We hit this branch when isScreenLockEnabled() == true. In this case, we - // need a new instance because the initial one has already been - // shown/dismissed. - loadViewControllerToDisplay = SAELoadViewController(delegate: self) - loadViewControllerForProgress = loadViewControllerToDisplay - } else { - // We don't need to show anything (it'll be shown by the block at the - // beginning of this Task), but we do want to hook up progress reporting. - loadViewControllerToDisplay = nil - loadViewControllerForProgress = initialLoadViewController - } - - let attachments: [SignalAttachment] - do { - // If buildAndValidateAttachments takes longer than 200ms, we want to show - // the new load view. If it takes less than 200ms, we'll exit out of this - // `do` block, that will cancel the `async let`, and then we'll leave the - // primary view controller alone as a result. - async let _ = { @MainActor () async throws -> Void in - guard let loadViewControllerToDisplay else { - return - } - try await Task.sleep(nanoseconds: 0.2.clampedNanoseconds) - // Check for cancellation on the main thread to ensure mutual exclusion - // with the the code outside of this do block. - try Task.checkCancellation() - self.showPrimaryViewController(loadViewControllerToDisplay) - }() - attachments = try await buildAndValidateAttachments( - for: typedItemProviders, - setProgress: { loadViewControllerForProgress?.progress = $0 } - ) - } catch { - self.presentAttachmentError(error) - return - } - - Logger.info("Setting picker attachments: \(attachments)") - conversationPicker.attachments = attachments - - if let preSelectedThread { - let approvalViewController = try conversationPicker.buildApprovalViewController(for: preSelectedThread) - self.showPrimaryViewController(approvalViewController) - - // If you're sharing to a specific thread, the picker view controller isn't - // added to the view hierarchy, but it's the "brains" of the sending - // operation and must not be deallocated. Tie its lifetime to the lifetime - // of the view controller that's visible. - ObjectRetainer.retainObject(conversationPicker, forLifetimeOf: approvalViewController) - } + // If you're sharing to a specific thread, the picker view controller isn't + // added to the view hierarchy, but it's the "brains" of the sending + // operation and must not be deallocated. Tie its lifetime to the lifetime + // of the view controller that's visible. + ObjectRetainer.retainObject(conversationPicker, forLifetimeOf: approvalViewController) } NotificationCenter.default.addObserver( @@ -241,7 +246,7 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed if ScreenLock.shared.isScreenLockEnabled() { Logger.info("dismissing.") - dismissAndCompleteExtension(animated: false, error: ShareViewControllerError.screenLockEnabled) + dismissAndCompleteExtension(error: ShareViewControllerError.screenLockEnabled) } } @@ -277,15 +282,7 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed let viewController = SAEFailedViewController(delegate: self, title: title, message: message) - let navigationController = UINavigationController() - navigationController.presentationController?.delegate = self - navigationController.setViewControllers([viewController], animated: false) - if self.presentedViewController == nil { - self.present(navigationController, animated: true) - } else { - owsFailDebug("modal already presented. swapping modal content for: \(type(of: viewController))") - assert(self.presentedViewController == navigationController) - } + self.setViewControllers([viewController], animated: false) } // MARK: ShareViewDelegate, SAEFailedViewDelegate @@ -297,63 +294,39 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed public func shareViewWasCompleted() { Logger.info("") - dismissAndCompleteExtension(animated: true, error: nil) + dismissAndCompleteExtension(error: nil) } public func shareViewWasCancelled() { Logger.info("") - dismissAndCompleteExtension(animated: true, error: ShareViewControllerError.obsoleteShare) + dismissAndCompleteExtension(error: ShareViewControllerError.obsoleteShare) } public func shareViewFailed(error: Error) { owsFailDebug("Error: \(error)") - dismissAndCompleteExtension(animated: true, error: error) + dismissAndCompleteExtension(error: error) } - private func dismissAndCompleteExtension(animated: Bool, error: Error?) { + private func dismissAndCompleteExtension(error: Error?) { + AssertIsOnMainThread() + let extensionContext = self.extensionContext - dismiss(animated: animated) { - AssertIsOnMainThread() - - if let error { - extensionContext?.cancelRequest(withError: error) - } else { - extensionContext?.completeRequest(returningItems: [], completionHandler: nil) - } - - // Share extensions reside in a process that may be reused between usages. - // That isn't safe; the codebase is full of statics (e.g. singletons) which - // we can't easily clean up. - Logger.info("ExitShareExtension") - Logger.flush() - exit(0) + if let error { + extensionContext?.cancelRequest(withError: error) + } else { + extensionContext?.completeRequest(returningItems: [], completionHandler: nil) } + + // Share extensions reside in a process that may be reused between usages. + // That isn't safe; the codebase is full of statics (e.g. singletons) which + // we can't easily clean up. + Logger.info("ExitShareExtension") + Logger.flush() + exit(0) } // MARK: Helpers - // This view controller is not visible to the user. It exists to intercept touches, set up the - // extensions dependencies, and eventually present a visible view to the user. - // For speed of presentation, we only present a single modal, and if it's already been presented - // we swap out the contents. - // e.g. if loading is taking a while, the user will see the load screen presented with a modal - // animation. Next, when loading completes, the load view will be switched out for the contact - // picker view. - private func showPrimaryViewController(_ viewController: UIViewController) { - AssertIsOnMainThread() - - guard let shareViewNavigationController = shareViewNavigationController else { - owsFailDebug("Missing shareViewNavigationController") - return - } - shareViewNavigationController.setViewControllers([viewController], animated: true) - if self.presentedViewController == nil { - self.present(shareViewNavigationController, animated: true) - } else { - assert(self.presentedViewController == shareViewNavigationController) - } - } - private func fetchPreSelectedThread() -> TSThread? { let hasIntent = self.extensionContext?.intent != nil Logger.info("hasIntent? \(hasIntent)") @@ -488,34 +461,16 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed } return result } -} -extension ShareViewController: UIAdaptivePresentationControllerDelegate { - public func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { + public override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + + // If we're disappearing because we presented something else (e.g., image + // editing tools), don't cancel the share extension. + guard self.presentedViewController == nil else { + return + } + shareViewWasCancelled() } } - -// MARK: - - -extension ShareViewController: UINavigationControllerDelegate { - - public func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) { - updateNavigationBarVisibility(for: viewController, in: navigationController, animated: animated) - } - - public func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) { - updateNavigationBarVisibility(for: viewController, in: navigationController, animated: animated) - } - - private func updateNavigationBarVisibility(for viewController: UIViewController, - in navigationController: UINavigationController, - animated: Bool) { - switch viewController { - case is AttachmentApprovalViewController: - navigationController.setNavigationBarHidden(true, animated: animated) - default: - navigationController.setNavigationBarHidden(false, animated: animated) - } - } -} diff --git a/SignalShareExtension/ShareViewDelegate.swift b/SignalShareExtension/ShareViewDelegate.swift index 9a433b2ff2..1608c092f0 100644 --- a/SignalShareExtension/ShareViewDelegate.swift +++ b/SignalShareExtension/ShareViewDelegate.swift @@ -12,5 +12,5 @@ public protocol ShareViewDelegate: AnyObject { func shareViewWasCompleted() func shareViewWasCancelled() func shareViewFailed(error: Error) - var shareViewNavigationController: OWSNavigationController? { get } + var shareViewNavigationController: OWSNavigationController { get } } diff --git a/SignalShareExtension/SharingThreadPickerViewController.swift b/SignalShareExtension/SharingThreadPickerViewController.swift index 0fae42f375..570bb24c43 100644 --- a/SignalShareExtension/SharingThreadPickerViewController.swift +++ b/SignalShareExtension/SharingThreadPickerViewController.swift @@ -69,7 +69,7 @@ class SharingThreadPickerViewController: ConversationPickerViewController { if let navigationController = shareViewDelegate?.shareViewNavigationController { navigationController.presentActionSheet(alert) } else { - super.presentActionSheet(alert) + self.presentActionSheet(alert) } } diff --git a/SignalUI/AttachmentApproval/AttachmentApprovalViewController.swift b/SignalUI/AttachmentApproval/AttachmentApprovalViewController.swift index 8b0b46679f..d8ff76a638 100644 --- a/SignalUI/AttachmentApproval/AttachmentApprovalViewController.swift +++ b/SignalUI/AttachmentApproval/AttachmentApprovalViewController.swift @@ -228,6 +228,8 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC public override func viewDidLoad() { super.viewDidLoad() + self.definesPresentationContext = true + view.backgroundColor = .black // avoid an unpleasant "bounce" which doesn't make sense in the context of a single item. diff --git a/SignalUI/AttachmentApproval/AttachmentPrepViewController.swift b/SignalUI/AttachmentApproval/AttachmentPrepViewController.swift index b9b37dcf0e..bc8c49b030 100644 --- a/SignalUI/AttachmentApproval/AttachmentPrepViewController.swift +++ b/SignalUI/AttachmentApproval/AttachmentPrepViewController.swift @@ -218,17 +218,20 @@ public class AttachmentPrepViewController: OWSViewController { scrollView.transform = .scale(scale).translate(.init(x: 0, y: -offsetY)) } - private func presentFullScreen(viewController: UIViewController) { + private func _presentMediaTool(viewController: UIViewController) { if let presentedViewController = presentedViewController { owsAssertDebug(false, "Already has presented view controller. [\(presentedViewController)]") presentedViewController.dismiss(animated: false) { [weak self] in - self?.presentFullScreen(viewController: viewController) + self?._presentMediaTool(viewController: viewController) } return } zoomOut(animated: true) { [weak self] in - self?.presentFullScreen(viewController, animated: false) + // Cover the current context. This ensures that we stay within our + // containing frame in the share extension. + viewController.modalPresentationStyle = .currentContext + self?.present(viewController, animated: false) } } @@ -236,10 +239,10 @@ public class AttachmentPrepViewController: OWSViewController { if let prepDelegate = prepDelegate { isMediaToolViewControllerPresented = true prepDelegate.attachmentPrepViewControllerDidRequestUpdateControlsVisibility(self) { _ in - self.presentFullScreen(viewController: viewController) + self._presentMediaTool(viewController: viewController) } } else { - self.presentFullScreen(viewController: viewController) + self._presentMediaTool(viewController: viewController) } } diff --git a/SignalUI/RecipientPickers/ConversationPicker.swift b/SignalUI/RecipientPickers/ConversationPicker.swift index 1b4c1e5ce1..65cddb6fb7 100644 --- a/SignalUI/RecipientPickers/ConversationPicker.swift +++ b/SignalUI/RecipientPickers/ConversationPicker.swift @@ -1281,12 +1281,12 @@ extension ConversationPickerViewController: ApprovalFooterDelegate { // MARK: - extension ConversationPickerViewController { - private struct Strings { - static let title = OWSLocalizedString("CONVERSATION_PICKER_TITLE", comment: "navbar header") - static let recentsSection = OWSLocalizedString("CONVERSATION_PICKER_SECTION_RECENTS", comment: "table section header for section containing recent conversations") - static let signalContactsSection = OWSLocalizedString("CONVERSATION_PICKER_SECTION_SIGNAL_CONTACTS", comment: "table section header for section containing contacts") - static let groupsSection = OWSLocalizedString("CONVERSATION_PICKER_SECTION_GROUPS", comment: "table section header for section containing groups") - static let storiesSection = OWSLocalizedString("CONVERSATION_PICKER_SECTION_STORIES", comment: "table section header for section containing stories") + public struct Strings { + public static let title = OWSLocalizedString("CONVERSATION_PICKER_TITLE", comment: "navbar header") + fileprivate static let recentsSection = OWSLocalizedString("CONVERSATION_PICKER_SECTION_RECENTS", comment: "table section header for section containing recent conversations") + fileprivate static let signalContactsSection = OWSLocalizedString("CONVERSATION_PICKER_SECTION_SIGNAL_CONTACTS", comment: "table section header for section containing contacts") + fileprivate static let groupsSection = OWSLocalizedString("CONVERSATION_PICKER_SECTION_GROUPS", comment: "table section header for section containing groups") + fileprivate static let storiesSection = OWSLocalizedString("CONVERSATION_PICKER_SECTION_STORIES", comment: "table section header for section containing stories") } }