diff --git a/SignalShareExtension/SAELoadViewController.swift b/SignalShareExtension/SAELoadViewController.swift index 9857d5e93..651b85733 100644 --- a/SignalShareExtension/SAELoadViewController.swift +++ b/SignalShareExtension/SAELoadViewController.swift @@ -10,7 +10,36 @@ class SAELoadViewController: UIViewController { weak var delegate: ShareViewDelegate? - var activityIndicator: UIActivityIndicatorView? + var activityIndicator: UIActivityIndicatorView! + var progressView: UIProgressView! + + var progress: Progress? { + didSet { + guard progressView != nil else { + return + } + + updateProgressViewVisability() + progressView.observedProgress = progress + } + } + + func updateProgressViewVisability() { + guard progressView != nil, activityIndicator != nil else { + return + } + + // Prefer to show progress view when progress is present + if self.progress == nil { + activityIndicator.startAnimating() + self.progressView.isHidden = true + self.activityIndicator.isHidden = false + } else { + activityIndicator.stopAnimating() + self.progressView.isHidden = false + self.activityIndicator.isHidden = true + } + } // MARK: Initializers and Factory Methods @@ -39,6 +68,16 @@ class SAELoadViewController: UIViewController { self.view.addSubview(activityIndicator) activityIndicator.autoCenterInSuperview() + progressView = UIProgressView(progressViewStyle: .default) + progressView.observedProgress = progress + + self.view.addSubview(progressView) + progressView.autoVCenterInSuperview() + progressView.autoPinWidthToSuperview(withMargin: ScaleFromIPhone5(30)) + progressView.progressTintColor = UIColor.white + + updateProgressViewVisability() + let label = UILabel() label.textColor = UIColor.white label.font = UIFont.ows_mediumFont(withSize: 18) @@ -53,20 +92,11 @@ class SAELoadViewController: UIViewController { super.viewWillAppear(animated) self.navigationController?.isNavigationBarHidden = false - - guard let activityIndicator = activityIndicator else { - return - } - activityIndicator.startAnimating() } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) - guard let activityIndicator = activityIndicator else { - return - } - activityIndicator.stopAnimating() } // MARK: - Event Handlers diff --git a/SignalShareExtension/ShareViewController.swift b/SignalShareExtension/ShareViewController.swift index f13aaf8b3..4584b7b06 100644 --- a/SignalShareExtension/ShareViewController.swift +++ b/SignalShareExtension/ShareViewController.swift @@ -15,6 +15,9 @@ public class ShareViewController: UINavigationController, ShareViewDelegate, SAE private var hasInitialRootViewController = false private var isReadyForAppExtensions = false + var progressPoller: ProgressPoller? + var loadViewController: SAELoadViewController? + override open func loadView() { super.loadView() @@ -65,6 +68,9 @@ public class ShareViewController: UINavigationController, ShareViewDelegate, SAE return } + let loadViewController = SAELoadViewController(delegate: self) + self.loadViewController = loadViewController + // Don't display load screen immediately, in hopes that we can avoid it altogether. after(seconds: 2).then { () -> Void in guard self.presentedViewController == nil else { @@ -74,7 +80,6 @@ public class ShareViewController: UINavigationController, ShareViewDelegate, SAE Logger.debug("\(self.logTag) setup is slow - showing loading screen") - let loadViewController = SAELoadViewController(delegate: self) let navigationController = UINavigationController(rootViewController: loadViewController) self.present(navigationController, animated: true) }.retainUntilComplete() @@ -465,10 +470,21 @@ public class ShareViewController: UINavigationController, ShareViewDelegate, SAE // TODO show progress with exportSession let (promise, exportSession) = SignalAttachment.compressVideoAsMp4(dataSource: dataSource, dataUTI: specificUTIType, imageQuality: .medium) - // TODO use `exportSession.progress` to show a more precise progress indicator in the loadView, maybe sharing the "sending" progress UI. - // TODO expose "Cancel" // Can we move this process to the end of the share flow rather than up front? // Currently we aren't able to generate a proper thumbnail or play the video in the app extension without first converting it. + if let exportSession = exportSession { + let progressPoller = ProgressPoller(timeInterval: 0.1, ratioCompleteBlock: { return exportSession.progress }) + self.progressPoller = progressPoller + progressPoller.startPolling() + + guard let loadViewController = self.loadViewController else { + owsFail("load view controller was unexpectedly nil") + return promise + } + + loadViewController.progress = progressPoller.progress + } + return promise } @@ -477,3 +493,47 @@ public class ShareViewController: UINavigationController, ShareViewDelegate, SAE } } } + +class ProgressPoller { + + let TAG = "[ProgressPoller]" + + let progress: Progress + private(set) var timer: Timer? + + // Higher number offers higher ganularity + let progressTotalUnitCount: Int64 = 10000 + private let timeInterval: Double + private let ratioCompleteBlock: () -> Float + + init(timeInterval: TimeInterval, ratioCompleteBlock: @escaping () -> Float) { + self.timeInterval = timeInterval + self.ratioCompleteBlock = ratioCompleteBlock + + self.progress = Progress() + + progress.totalUnitCount = progressTotalUnitCount + progress.completedUnitCount = Int64(ratioCompleteBlock() * Float(progressTotalUnitCount)) + } + + func startPolling() { + guard self.timer == nil else { + owsFail("already started timer") + return + } + + self.timer = WeakTimer.scheduledTimer(timeInterval: timeInterval, target: self, userInfo: nil, repeats: true) { [weak self] (timer) in + guard let strongSelf = self else { + return + } + + let completedUnitCount = Int64(strongSelf.ratioCompleteBlock() * Float(strongSelf.progressTotalUnitCount)) + strongSelf.progress.completedUnitCount = completedUnitCount + + if completedUnitCount == strongSelf.progressTotalUnitCount { + Logger.debug("\(strongSelf.TAG) progress complete") + timer.invalidate() + } + } + } +}