mirror of
https://github.com/signalapp/Signal-iOS.git
synced 2025-12-05 01:10:41 +00:00
Remove invisible view from share extension
This commit is contained in:
@@ -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 = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
4535186D1FC635DD00210559 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
|
||||
4535186F1FC635DD00210559 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
4539B5851F79348F007141FF /* PushRegistrationManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushRegistrationManager.swift; sourceTree = "<group>"; };
|
||||
4542DF53208D40AC007B4E76 /* LoadingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingViewController.swift; sourceTree = "<group>"; };
|
||||
@@ -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 = "<group>";
|
||||
};
|
||||
4535186C1FC635DD00210559 /* MainInterface.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
4535186D1FC635DD00210559 /* Base */,
|
||||
);
|
||||
name = MainInterface.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A5E7C673248C5442007C949A /* InfoPlist.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="j1y-V4-xli">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13527"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Share View Controller-->
|
||||
<scene sceneID="ceB-am-kn3">
|
||||
<objects>
|
||||
<viewController id="j1y-V4-xli" customClass="ShareViewController" customModule="SignalShareExtension" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" opaque="NO" contentMode="scaleToFill" id="wbc-yd-nQP">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<viewLayoutGuide key="safeArea" id="1Xd-am-t49"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="CEy-Cv-SGf" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -80,10 +80,10 @@
|
||||
)
|
||||
</string>
|
||||
</dict>
|
||||
<key>NSExtensionMainStoryboard</key>
|
||||
<string>MainInterface</string>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.share-services</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).ShareViewController</string>
|
||||
</dict>
|
||||
<key>OWSBundleIDPrefix</key>
|
||||
<string>$(SIGNAL_BUNDLEID_PREFIX)</string>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,5 +12,5 @@ public protocol ShareViewDelegate: AnyObject {
|
||||
func shareViewWasCompleted()
|
||||
func shareViewWasCancelled()
|
||||
func shareViewFailed(error: Error)
|
||||
var shareViewNavigationController: OWSNavigationController? { get }
|
||||
var shareViewNavigationController: OWSNavigationController { get }
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ class SharingThreadPickerViewController: ConversationPickerViewController {
|
||||
if let navigationController = shareViewDelegate?.shareViewNavigationController {
|
||||
navigationController.presentActionSheet(alert)
|
||||
} else {
|
||||
super.presentActionSheet(alert)
|
||||
self.presentActionSheet(alert)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user