Refactored SessionShareExtension code to Swift

This commit is contained in:
Morgan Pretty 2022-01-11 12:38:19 +11:00
parent 7a22c9d329
commit 4f3faa28bc
15 changed files with 748 additions and 657 deletions

View file

@ -21,10 +21,8 @@
34330AA31E79686200DF2FB9 /* OWSProgressView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34330AA21E79686200DF2FB9 /* OWSProgressView.m */; };
34386A54207D271D009F5D9C /* NeverClearView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34386A53207D271C009F5D9C /* NeverClearView.swift */; };
3441FD9F21A3604F00BB9542 /* BackupRestoreViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3441FD9E21A3604F00BB9542 /* BackupRestoreViewController.swift */; };
34480B361FD0929200BC14EF /* ShareAppExtensionContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 34480B351FD0929200BC14EF /* ShareAppExtensionContext.m */; };
344825C6211390C800DB4BD8 /* OWSOrphanDataCleaner.m in Sources */ = {isa = PBXBuildFile; fileRef = 344825C5211390C800DB4BD8 /* OWSOrphanDataCleaner.m */; };
346129991FD1E4DA00532771 /* SignalApp.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129971FD1E4D900532771 /* SignalApp.m */; };
34641E1F2088DA6D00E2EDE5 /* SAEScreenLockViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34641E1E2088DA6D00E2EDE5 /* SAEScreenLockViewController.m */; };
34661FB820C1C0D60056EDD6 /* message_sent.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 34661FB720C1C0D60056EDD6 /* message_sent.aiff */; };
346B66311F4E29B200E5122F /* CropScaleImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346B66301F4E29B200E5122F /* CropScaleImageViewController.swift */; };
3478504C1FD7496D007B8332 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B66DBF4919D5BBC8006EA940 /* Images.xcassets */; };
@ -775,6 +773,11 @@
FAD6392E0205566D11AA9E48 /* Pods_Session.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB3724C70247A916D43271FE /* Pods_Session.framework */; };
FC3BD9881A30A790005B96BB /* Social.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC3BD9871A30A790005B96BB /* Social.framework */; };
FCB11D8C1A129A76002F93FB /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FCB11D8B1A129A76002F93FB /* CoreMedia.framework */; };
FD705A8C278CDB5600F16121 /* SAEScreenLockViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD705A8B278CDB5600F16121 /* SAEScreenLockViewController.swift */; };
FD705A8E278CE29800F16121 /* String+Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD705A8D278CE29800F16121 /* String+Localization.swift */; };
FD705A90278CEBBC00F16121 /* ShareAppExtensionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD705A8F278CEBBC00F16121 /* ShareAppExtensionContext.swift */; };
FD705A92278D051200F16121 /* ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD705A91278D051200F16121 /* ReusableView.swift */; };
FD705A94278D052B00F16121 /* UITableView+ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD705A93278D052B00F16121 /* UITableView+ReusableView.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -956,16 +959,12 @@
34330AA21E79686200DF2FB9 /* OWSProgressView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSProgressView.m; sourceTree = "<group>"; };
34386A53207D271C009F5D9C /* NeverClearView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NeverClearView.swift; sourceTree = "<group>"; };
3441FD9E21A3604F00BB9542 /* BackupRestoreViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackupRestoreViewController.swift; sourceTree = "<group>"; };
34480B341FD0929200BC14EF /* ShareAppExtensionContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShareAppExtensionContext.h; sourceTree = "<group>"; };
34480B351FD0929200BC14EF /* ShareAppExtensionContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ShareAppExtensionContext.m; sourceTree = "<group>"; };
34480B371FD092A900BC14EF /* SignalShareExtension-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SignalShareExtension-Bridging-Header.h"; sourceTree = "<group>"; };
34480B381FD092E300BC14EF /* SessionShareExtension-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SessionShareExtension-Prefix.pch"; sourceTree = "<group>"; };
344825C4211390C700DB4BD8 /* OWSOrphanDataCleaner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSOrphanDataCleaner.h; sourceTree = "<group>"; };
344825C5211390C800DB4BD8 /* OWSOrphanDataCleaner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSOrphanDataCleaner.m; sourceTree = "<group>"; };
346129971FD1E4D900532771 /* SignalApp.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SignalApp.m; sourceTree = "<group>"; };
346129981FD1E4DA00532771 /* SignalApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SignalApp.h; sourceTree = "<group>"; };
34641E1D2088DA6C00E2EDE5 /* SAEScreenLockViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SAEScreenLockViewController.h; sourceTree = "<group>"; };
34641E1E2088DA6D00E2EDE5 /* SAEScreenLockViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SAEScreenLockViewController.m; sourceTree = "<group>"; };
34661FB720C1C0D60056EDD6 /* message_sent.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; name = message_sent.aiff; path = Session/Meta/AudioFiles/message_sent.aiff; sourceTree = SOURCE_ROOT; };
346B66301F4E29B200E5122F /* CropScaleImageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CropScaleImageViewController.swift; sourceTree = "<group>"; };
3488F9352191CC4000E524CC /* MediaView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaView.swift; sourceTree = "<group>"; };
@ -1780,6 +1779,11 @@
FB523C549815DE935E98151E /* Pods_SessionMessagingKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SessionMessagingKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
FC3BD9871A30A790005B96BB /* Social.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Social.framework; path = System/Library/Frameworks/Social.framework; sourceTree = SDKROOT; };
FCB11D8B1A129A76002F93FB /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; };
FD705A8B278CDB5600F16121 /* SAEScreenLockViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SAEScreenLockViewController.swift; sourceTree = "<group>"; };
FD705A8D278CE29800F16121 /* String+Localization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Localization.swift"; sourceTree = "<group>"; };
FD705A8F278CEBBC00F16121 /* ShareAppExtensionContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareAppExtensionContext.swift; sourceTree = "<group>"; };
FD705A91278D051200F16121 /* ReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReusableView.swift; sourceTree = "<group>"; };
FD705A93278D052B00F16121 /* UITableView+ReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+ReusableView.swift"; sourceTree = "<group>"; };
FF9BA33D021B115B1F5B4E46 /* Pods-SessionMessagingKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionMessagingKit.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SessionMessagingKit/Pods-SessionMessagingKit.app store release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -1971,10 +1975,8 @@
children = (
C31C21A4255BCA4800EC2D66 /* Meta */,
4535186C1FC635DD00210559 /* MainInterface.storyboard */,
34641E1D2088DA6C00E2EDE5 /* SAEScreenLockViewController.h */,
34641E1E2088DA6D00E2EDE5 /* SAEScreenLockViewController.m */,
34480B341FD0929200BC14EF /* ShareAppExtensionContext.h */,
34480B351FD0929200BC14EF /* ShareAppExtensionContext.m */,
FD705A8B278CDB5600F16121 /* SAEScreenLockViewController.swift */,
FD705A8F278CEBBC00F16121 /* ShareAppExtensionContext.swift */,
C3ADC66026426688005F1414 /* ShareVC.swift */,
B817AD9B26436F73009DF825 /* ThreadPickerVC.swift */,
B817AD9926436593009DF825 /* SimplifiedConversationCell.swift */,
@ -2282,8 +2284,11 @@
C33FDB6B255A580F00E217F9 /* SNUserDefaults.swift */,
C33FDB3F255A580C00E217F9 /* String+SSK.swift */,
C3C2AC2D2553CBEB00C340D1 /* String+Trimming.swift */,
FD705A8D278CE29800F16121 /* String+Localization.swift */,
C38EF237255B6D65007E1867 /* UIDevice+featureSupport.swift */,
C35D0DB425AE5F1200B6BF49 /* UIEdgeInsets.swift */,
FD705A91278D051200F16121 /* ReusableView.swift */,
FD705A93278D052B00F16121 /* UITableView+ReusableView.swift */,
C38EF23D255B6D66007E1867 /* UIView+OWS.h */,
C38EF23E255B6D66007E1867 /* UIView+OWS.m */,
C38EF2EF255B6DBB007E1867 /* Weak.swift */,
@ -4330,9 +4335,9 @@
buildActionMask = 2147483647;
files = (
B817AD9A26436593009DF825 /* SimplifiedConversationCell.swift in Sources */,
34480B361FD0929200BC14EF /* ShareAppExtensionContext.m in Sources */,
C3ADC66126426688005F1414 /* ShareVC.swift in Sources */,
34641E1F2088DA6D00E2EDE5 /* SAEScreenLockViewController.m in Sources */,
FD705A90278CEBBC00F16121 /* ShareAppExtensionContext.swift in Sources */,
FD705A8C278CDB5600F16121 /* SAEScreenLockViewController.swift in Sources */,
B817AD9C26436F73009DF825 /* ThreadPickerVC.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -4549,6 +4554,7 @@
C3D9E4C02567767F0040E4F3 /* DataSource.m in Sources */,
C3D9E43125676D3D0040E4F3 /* Configuration.swift in Sources */,
C32C5DD2256DD9E5003C73A2 /* LRUCache.swift in Sources */,
FD705A94278D052B00F16121 /* UITableView+ReusableView.swift in Sources */,
C3A7211A2558BCA10043A11F /* DiffieHellman.swift in Sources */,
C32C5FA1256DFED5003C73A2 /* NSArray+Functional.m in Sources */,
C3A7225E2558C38D0043A11F /* Promise+Retaining.swift in Sources */,
@ -4561,6 +4567,7 @@
C32C5B48256DC211003C73A2 /* NSNotificationCenter+OWS.m in Sources */,
C3D9E3C925676AF30040E4F3 /* TSYapDatabaseObject.m in Sources */,
C352A3A62557B60D00338F3E /* TSRequest.m in Sources */,
FD705A92278D051200F16121 /* ReusableView.swift in Sources */,
B8AE75A425A6C6A6001A84D2 /* Data+Trimming.swift in Sources */,
B8856DE6256F15F2001CE70E /* String+SSK.swift in Sources */,
C3471ED42555386B00297E91 /* AESGCM.swift in Sources */,
@ -4584,6 +4591,7 @@
C300A60D2554B31900555489 /* Logging.swift in Sources */,
B8FF8EA625C11FEF004D1F22 /* IPv4.swift in Sources */,
C3D9E35525675EE10040E4F3 /* MIMETypeUtil.m in Sources */,
FD705A8E278CE29800F16121 /* String+Localization.swift in Sources */,
C3A7219A2558C1660043A11F /* AnyPromise+Conversion.swift in Sources */,
C300A6322554B6D100555489 /* NSDate+Timestamp.mm in Sources */,
);

View file

@ -6,8 +6,6 @@
#import <UIKit/UIKit.h>
// Separate iOS Frameworks from other imports.
#import "SAEScreenLockViewController.h"
#import "ShareAppExtensionContext.h"
#import <SignalCoreKit/NSObject+OWS.h>
#import <SignalCoreKit/OWSAsserts.h>
#import <SignalCoreKit/OWSLogs.h>

View file

@ -1,18 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import <SignalUtilitiesKit/OWSViewController.h>
#import <SignalUtilitiesKit/ScreenLockViewController.h>
NS_ASSUME_NONNULL_BEGIN
@protocol ShareViewDelegate;
@interface SAEScreenLockViewController : ScreenLockViewController
- (instancetype)initWithShareViewDelegate:(id<ShareViewDelegate>)shareViewDelegate;
@end
NS_ASSUME_NONNULL_END

View file

@ -1,206 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "SAEScreenLockViewController.h"
#import "UIColor+OWS.h"
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
#import <SessionUtilitiesKit/AppContext.h>
NS_ASSUME_NONNULL_BEGIN
@interface SAEScreenLockViewController () <ScreenLockViewDelegate>
@property (nonatomic, readonly, weak) id<ShareViewDelegate> shareViewDelegate;
@property (nonatomic) BOOL hasShownAuthUIOnce;
@property (nonatomic) BOOL isShowingAuthUI;
@end
#pragma mark -
@implementation SAEScreenLockViewController
- (instancetype)initWithShareViewDelegate:(id<ShareViewDelegate>)shareViewDelegate
{
self = [super init];
if (!self) {
return self;
}
_shareViewDelegate = shareViewDelegate;
self.delegate = self;
return self;
}
- (void)loadView
{
[super loadView];
UIView.appearance.tintColor = LKColors.text;
// Gradient background
self.view.backgroundColor = UIColor.clearColor;
CAGradientLayer *layer = [CAGradientLayer new];
layer.frame = UIScreen.mainScreen.bounds;
UIColor *gradientStartColor = LKAppModeUtilities.isLightMode ? [UIColor colorWithRGBHex:0xFCFCFC] : [UIColor colorWithRGBHex:0x171717];
UIColor *gradientEndColor = LKAppModeUtilities.isLightMode ? [UIColor colorWithRGBHex:0xFFFFFF] : [UIColor colorWithRGBHex:0x121212];
layer.colors = @[ (id)gradientStartColor.CGColor, (id)gradientEndColor.CGColor ];
[self.view.layer insertSublayer:layer atIndex:0];
// Navigation bar background color
UINavigationBar *navigationBar = self.navigationController.navigationBar;
[navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
navigationBar.shadowImage = [UIImage new];
[navigationBar setTranslucent:NO];
navigationBar.barTintColor = LKColors.navigationBarBackground;
// Title
UILabel *titleLabel = [UILabel new];
titleLabel.text = NSLocalizedString(@"vc_share_title", @"");
titleLabel.textColor = LKColors.text;
titleLabel.font = [UIFont boldSystemFontOfSize:LKValues.veryLargeFontSize];
self.navigationItem.titleView = titleLabel;
// Close button
UIBarButtonItem *closeButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"X"] style:UIBarButtonItemStylePlain target:self action:@selector(dismissPressed:)];
closeButton.tintColor = LKColors.text;
self.navigationItem.leftBarButtonItem = closeButton;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self ensureUI];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self ensureUI];
// Auto-show the auth UI f
if (!self.hasShownAuthUIOnce) {
self.hasShownAuthUIOnce = YES;
[self tryToPresentAuthUIToUnlockScreenLock];
}
}
- (void)dealloc
{
// Surface memory leaks by logging the deallocation of view controllers.
OWSLogVerbose(@"Dealloc: %@", self.class);
}
- (void)tryToPresentAuthUIToUnlockScreenLock
{
OWSAssertIsOnMainThread();
if (self.isShowingAuthUI) {
// We're already showing the auth UI; abort.
return;
}
OWSLogInfo(@"try to unlock screen lock");
self.isShowingAuthUI = YES;
[OWSScreenLock.sharedManager tryToUnlockScreenLockWithSuccess:^{
OWSAssertIsOnMainThread();
OWSLogInfo(@"unlock screen lock succeeded.");
self.isShowingAuthUI = NO;
[self.shareViewDelegate shareViewWasUnlocked];
}
failure:^(NSError *error) {
OWSAssertIsOnMainThread();
OWSLogInfo(@"unlock screen lock failed.");
self.isShowingAuthUI = NO;
[self ensureUI];
[self showScreenLockFailureAlertWithMessage:error.localizedDescription];
}
unexpectedFailure:^(NSError *error) {
OWSAssertIsOnMainThread();
OWSLogInfo(@"unlock screen lock unexpectedly failed.");
self.isShowingAuthUI = NO;
// Local Authentication isn't working properly.
// This isn't covered by the docs or the forums but in practice
// it appears to be effective to retry again after waiting a bit.
dispatch_async(dispatch_get_main_queue(), ^{
[self ensureUI];
});
}
cancel:^{
OWSAssertIsOnMainThread();
OWSLogInfo(@"unlock screen lock cancelled.");
self.isShowingAuthUI = NO;
[self ensureUI];
}];
[self ensureUI];
}
- (void)ensureUI
{
[self updateUIWithState:ScreenLockUIStateScreenLock isLogoAtTop:NO animated:NO];
}
- (void)showScreenLockFailureAlertWithMessage:(NSString *)message
{
OWSAssertIsOnMainThread();
[OWSAlerts showAlertWithTitle:NSLocalizedString(@"SCREEN_LOCK_UNLOCK_FAILED",
@"Title for alert indicating that screen lock could not be unlocked.")
message:message
buttonTitle:nil
buttonAction:^(UIAlertAction *action) {
// After the alert, update the UI.
[self ensureUI];
}
fromViewController:self];
}
- (void)dismissPressed:(id)sender
{
OWSLogDebug(@"tapped dismiss share button");
[self cancelShareExperience];
}
- (void)cancelShareExperience
{
[self.shareViewDelegate shareViewWasCancelled];
}
#pragma mark - ScreenLockViewDelegate
- (void)unlockButtonWasTapped
{
OWSAssertIsOnMainThread();
OWSLogInfo(@"unlockButtonWasTapped");
[self tryToPresentAuthUIToUnlockScreenLock];
}
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,206 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
import PromiseKit
import SignalCoreKit
import SignalUtilitiesKit
import SessionUIKit
import SessionUtilitiesKit
final class SAEScreenLockViewController: ScreenLockViewController, ScreenLockViewDelegate {
private var hasShownAuthUIOnce: Bool = false
private var isShowingAuthUI: Bool = false
private weak var shareViewDelegate: ShareViewDelegate?
// MARK: - Initialization
init(shareViewDelegate: ShareViewDelegate) {
super.init(nibName: nil, bundle: nil)
self.shareViewDelegate = shareViewDelegate
self.delegate = self
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
OWSLogger.verbose("Dealloc: \(type(of: self))")
}
// MARK: - UI
private lazy var gradientBackground: CAGradientLayer = {
let layer: CAGradientLayer = CAGradientLayer()
let gradientStartColor: UIColor = (LKAppModeUtilities.isLightMode ?
UIColor(rgbHex: 0xFCFCFC) :
UIColor(rgbHex: 0x171717)
)
let gradientEndColor: UIColor = (LKAppModeUtilities.isLightMode ?
UIColor(rgbHex: 0xFFFFFF) :
UIColor(rgbHex: 0x121212)
)
layer.colors = [gradientStartColor.cgColor, gradientEndColor.cgColor]
return layer
}()
private lazy var titleLabel: UILabel = {
let titleLabel: UILabel = UILabel()
titleLabel.font = UIFont.boldSystemFont(ofSize: Values.veryLargeFontSize)
titleLabel.text = "vc_share_title".localized()
titleLabel.textColor = Colors.text
return titleLabel
}()
private lazy var closeButton: UIBarButtonItem = {
let closeButton: UIBarButtonItem = UIBarButtonItem(image: UIImage(named: "X"), style: .plain, target: self, action: #selector(dismissPressed))
closeButton.tintColor = Colors.text
return closeButton
}()
// MARK: - Lifecycle
override func loadView() {
super.loadView()
UIView.appearance().tintColor = Colors.text
self.view.backgroundColor = UIColor.clear
self.view.layer.insertSublayer(gradientBackground, at: 0)
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
self.navigationController?.navigationBar.shadowImage = UIImage()
self.navigationController?.navigationBar.isTranslucent = false
self.navigationController?.navigationBar.tintColor = Colors.navigationBarBackground
self.navigationItem.titleView = titleLabel
self.navigationItem.leftBarButtonItem = closeButton
setupLayout()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.ensureUI()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.ensureUI()
// Auto-show the auth UI f
if !hasShownAuthUIOnce {
hasShownAuthUIOnce = true
self.tryToPresentAuthUIToUnlockScreenLock()
}
}
// MARK: - Layout
private func setupLayout() {
gradientBackground.frame = UIScreen.main.bounds
}
// MARK: - Functions
private func tryToPresentAuthUIToUnlockScreenLock() {
AssertIsOnMainThread()
// If we're already showing the auth UI; abort.
if self.isShowingAuthUI { return }
OWSLogger.info("try to unlock screen lock")
isShowingAuthUI = true
OWSScreenLock.shared.tryToUnlockScreenLock(
success: { [weak self] in
AssertIsOnMainThread()
OWSLogger.info("unlock screen lock succeeded.")
self?.isShowingAuthUI = false
self?.shareViewDelegate?.shareViewWasUnlocked()
},
failure: { [weak self] error in
AssertIsOnMainThread()
OWSLogger.info("unlock screen lock failed.")
self?.isShowingAuthUI = false
self?.ensureUI()
self?.showScreenLockFailureAlert(message: error.localizedDescription)
},
unexpectedFailure: { [weak self] error in
AssertIsOnMainThread()
OWSLogger.info("unlock screen lock unexpectedly failed.")
self?.isShowingAuthUI = false
// Local Authentication isn't working properly.
// This isn't covered by the docs or the forums but in practice
// it appears to be effective to retry again after waiting a bit.
DispatchQueue.main.async {
self?.ensureUI()
}
},
cancel: { [weak self] in
AssertIsOnMainThread()
OWSLogger.info("unlock screen lock cancelled.")
self?.isShowingAuthUI = false
self?.ensureUI()
}
)
self.ensureUI()
}
private func ensureUI() {
self.updateUI(with: .screenLock, isLogoAtTop: false, animated: false)
}
private func showScreenLockFailureAlert(message: String) {
AssertIsOnMainThread()
OWSAlerts.showAlert(
// Title for alert indicating that screen lock could not be unlocked.
title: "SCREEN_LOCK_UNLOCK_FAILED".localized(),
message: message,
buttonTitle: nil,
buttonAction: { [weak self] action in
// After the alert, update the UI
self?.ensureUI()
},
fromViewController: self
)
}
// MARK: - Transitions
@objc private func dismissPressed() {
OWSLogger.debug("unlock screen lock cancelled.")
self.cancelShareExperience()
}
private func cancelShareExperience() {
self.shareViewDelegate?.shareViewWasCancelled()
}
// MARK: - ScreenLockViewDelegate
func unlockButtonWasTapped() {
AssertIsOnMainThread()
OWSLogger.info("unlockButtonWasTapped")
self.tryToPresentAuthUIToUnlockScreenLock()
}
}

View file

@ -1,18 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import <SessionUtilitiesKit/AppContext.h>
NS_ASSUME_NONNULL_BEGIN
// This is _NOT_ a singleton and will be instantiated each time that the SAE is used.
@interface ShareAppExtensionContext : NSObject <AppContext>
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithRootViewController:(UIViewController *)rootViewController;
@end
NS_ASSUME_NONNULL_END

View file

@ -1,240 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "ShareAppExtensionContext.h"
#import <SignalUtilitiesKit/UIViewController+OWS.h>
#import <SessionMessagingKit/OWSStorage.h>
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
#import <SignalUtilitiesKit/TSConstants.h>
#import <SessionUtilitiesKit/SessionUtilitiesKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN
@interface ShareAppExtensionContext ()
@property (nonatomic) UIViewController *rootViewController;
@property (atomic) UIApplicationState reportedApplicationState;
@end
#pragma mark -
@implementation ShareAppExtensionContext
@synthesize mainWindow = _mainWindow;
@synthesize appLaunchTime = _appLaunchTime;
- (instancetype)initWithRootViewController:(UIViewController *)rootViewController
{
self = [super init];
if (!self) {
return self;
}
OWSAssertDebug(rootViewController);
_rootViewController = rootViewController;
self.reportedApplicationState = UIApplicationStateActive;
_appLaunchTime = [NSDate new];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(extensionHostDidBecomeActive:)
name:NSExtensionHostDidBecomeActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(extensionHostWillResignActive:)
name:NSExtensionHostWillResignActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(extensionHostDidEnterBackground:)
name:NSExtensionHostDidEnterBackgroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(extensionHostWillEnterForeground:)
name:NSExtensionHostWillEnterForegroundNotification
object:nil];
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - Notifications
- (void)extensionHostDidBecomeActive:(NSNotification *)notification
{
OWSAssertIsOnMainThread();
OWSLogInfo(@"");
self.reportedApplicationState = UIApplicationStateActive;
[NSNotificationCenter.defaultCenter postNotificationName:OWSApplicationDidBecomeActiveNotification object:nil];
}
- (void)extensionHostWillResignActive:(NSNotification *)notification
{
OWSAssertIsOnMainThread();
self.reportedApplicationState = UIApplicationStateInactive;
OWSLogInfo(@"");
[DDLog flushLog];
[NSNotificationCenter.defaultCenter postNotificationName:OWSApplicationWillResignActiveNotification object:nil];
}
- (void)extensionHostDidEnterBackground:(NSNotification *)notification
{
OWSAssertIsOnMainThread();
OWSLogInfo(@"");
[DDLog flushLog];
self.reportedApplicationState = UIApplicationStateBackground;
[NSNotificationCenter.defaultCenter postNotificationName:OWSApplicationDidEnterBackgroundNotification object:nil];
}
- (void)extensionHostWillEnterForeground:(NSNotification *)notification
{
OWSAssertIsOnMainThread();
OWSLogInfo(@"");
self.reportedApplicationState = UIApplicationStateInactive;
[NSNotificationCenter.defaultCenter postNotificationName:OWSApplicationWillEnterForegroundNotification object:nil];
}
#pragma mark -
- (BOOL)isMainApp
{
return NO;
}
- (BOOL)isMainAppAndActive
{
return NO;
}
- (BOOL)isRTL
{
static BOOL isRTL = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// Borrowed from PureLayout's AppExtension compatible RTL support.
// App Extensions may not access -[UIApplication sharedApplication]; fall back to checking the bundle's
// preferred localization character direction
isRTL = [NSLocale characterDirectionForLanguage:[[NSBundle mainBundle] preferredLocalizations][0]]
== NSLocaleLanguageDirectionRightToLeft;
});
return isRTL;
}
- (void)setStatusBarHidden:(BOOL)isHidden animated:(BOOL)isAnimated
{
OWSLogInfo(@"Ignoring request to show/hide status bar since we're in an app extension");
}
- (CGFloat)statusBarHeight
{
return 20;
}
- (BOOL)isInBackground
{
return self.reportedApplicationState == UIApplicationStateBackground;
}
- (BOOL)isAppForegroundAndActive
{
return self.reportedApplicationState == UIApplicationStateActive;
}
- (UIBackgroundTaskIdentifier)beginBackgroundTaskWithExpirationHandler:
(BackgroundTaskExpirationHandler)expirationHandler
{
return UIBackgroundTaskInvalid;
}
- (void)endBackgroundTask:(UIBackgroundTaskIdentifier)backgroundTaskIdentifier
{
OWSAssertDebug(backgroundTaskIdentifier == UIBackgroundTaskInvalid);
}
- (void)ensureSleepBlocking:(BOOL)shouldBeBlocking blockingObjects:(NSArray<id> *)blockingObjects
{
OWSLogDebug(@"Ignoring request to block sleep.");
}
- (void)setMainAppBadgeNumber:(NSInteger)value
{
OWSFailDebug(@"");
}
- (nullable UIViewController *)frontmostViewController
{
OWSAssertDebug(self.rootViewController);
return [self.rootViewController findFrontmostViewController:YES];
}
- (nullable UIAlertAction *)openSystemSettingsAction
{
return nil;
}
- (BOOL)isRunningTests
{
// We don't need to distinguish this in the SAE.
return NO;
}
- (void)setNetworkActivityIndicatorVisible:(BOOL)value
{
OWSFailDebug(@"");
}
- (void)runNowOrWhenMainAppIsActive:(AppActiveBlock)block
{
OWSFailDebug(@"cannot run main app active blocks in share extension.");
}
- (id<SSKKeychainStorage>)keychainStorage
{
return [SSKDefaultKeychainStorage shared];
}
- (NSString *)appDocumentDirectoryPath
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *documentDirectoryURL =
[[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
return [documentDirectoryURL path];
}
- (NSString *)appSharedDataDirectoryPath
{
NSURL *groupContainerDirectoryURL =
[[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:SignalApplicationGroup];
return [groupContainerDirectoryURL path];
}
- (NSUserDefaults *)appUserDefaults
{
return [[NSUserDefaults alloc] initWithSuiteName:SignalApplicationGroup];
}
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,204 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
import SignalUtilitiesKit
import SessionUtilitiesKit
import SessionMessagingKit
/// This is _NOT_ a singleton and will be instantiated each time that the SAE is used.
final class ShareAppExtensionContext: NSObject, AppContext {
var rootViewController: UIViewController
var reportedApplicationState: UIApplication.State
let appLaunchTime = Date()
let isMainApp = false
let isMainAppAndActive = false
var mainWindow: UIWindow?
var wasWokenUpByPushNotification: Bool = false
private static var _isRTL: Bool = {
// Borrowed from PureLayout's AppExtension compatible RTL support.
// App Extensions may not access -[UIApplication sharedApplication]; fall back
// to checking the bundle's preferred localization character direction
return (
Locale.characterDirection(
forLanguage: (Bundle.main.preferredLocalizations.first ?? "")
) == Locale.LanguageDirection.rightToLeft
)
}()
var isRTL: Bool { return ShareAppExtensionContext._isRTL }
var isRunningTests: Bool { return false } // We don't need to distinguish this in the SAE
var statusBarHeight: CGFloat { return 20 }
var openSystemSettingsAction: UIAlertAction?
// MARK: - Initialization
init(rootViewController: UIViewController) {
self.rootViewController = rootViewController
self.reportedApplicationState = .active
super.init()
NotificationCenter.default.addObserver(
self,
selector: #selector(extensionHostDidBecomeActive(notification:)),
name: .NSExtensionHostDidBecomeActive,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(extensionHostWillResignActive(notification:)),
name: .NSExtensionHostWillResignActive,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(extensionHostDidEnterBackground(notification:)),
name: .NSExtensionHostDidEnterBackground,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(extensionHostWillEnterForeground(notification:)),
name: .NSExtensionHostWillEnterForeground,
object: nil
)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
// MARK: - Notifications
@objc private func extensionHostDidBecomeActive(notification: NSNotification) {
AssertIsOnMainThread()
OWSLogger.info("")
self.reportedApplicationState = .active
NotificationCenter.default.post(
name: .OWSApplicationDidBecomeActive,
object: nil
)
}
@objc private func extensionHostWillResignActive(notification: NSNotification) {
AssertIsOnMainThread()
self.reportedApplicationState = .inactive
OWSLogger.info("")
DDLog.flushLog()
NotificationCenter.default.post(
name: .OWSApplicationWillResignActive,
object: nil
)
}
@objc private func extensionHostDidEnterBackground(notification: NSNotification) {
AssertIsOnMainThread()
OWSLogger.info("")
DDLog.flushLog()
self.reportedApplicationState = .background
NotificationCenter.default.post(
name: .OWSApplicationDidEnterBackground,
object: nil
)
}
@objc private func extensionHostWillEnterForeground(notification: NSNotification) {
AssertIsOnMainThread()
OWSLogger.info("")
self.reportedApplicationState = .inactive
NotificationCenter.default.post(
name: .OWSApplicationWillEnterForeground,
object: nil
)
}
// MARK: - AppContext Functions
func isAppForegroundAndActive() -> Bool {
return (reportedApplicationState == .active)
}
func isInBackground() -> Bool {
return (reportedApplicationState == .background)
}
func frontmostViewController() -> UIViewController? {
return rootViewController.findFrontmostViewController(true)
}
func keychainStorage() -> SSKKeychainStorage {
return SSKDefaultKeychainStorage.shared
}
func appDocumentDirectoryPath() -> String {
let targetPath: String? = FileManager.default
.urls(
for: .documentDirectory,
in: .userDomainMask
)
.last?
.path
owsAssertDebug(targetPath != nil)
return (targetPath ?? "")
}
func appSharedDataDirectoryPath() -> String {
let targetPath: String? = FileManager.default
.containerURL(forSecurityApplicationGroupIdentifier: SignalApplicationGroup)?
.path
owsAssertDebug(targetPath != nil)
return (targetPath ?? "")
}
func appUserDefaults() -> UserDefaults {
let targetUserDefaults: UserDefaults? = UserDefaults(suiteName: SignalApplicationGroup)
owsAssertDebug(targetUserDefaults != nil)
return (targetUserDefaults ?? UserDefaults.standard)
}
func setStatusBarHidden(_ isHidden: Bool, animated isAnimated: Bool) {
OWSLogger.info("Ignoring request to show/hide status bar since we're in an app extension")
}
func beginBackgroundTask(expirationHandler: @escaping BackgroundTaskExpirationHandler) -> UIBackgroundTaskIdentifier {
return .invalid
}
func endBackgroundTask(_ backgroundTaskIdentifier: UIBackgroundTaskIdentifier) {
owsAssertDebug(backgroundTaskIdentifier == .invalid)
}
func ensureSleepBlocking(_ shouldBeBlocking: Bool, blockingObjects: [Any]) {
OWSLogger.debug("Ignoring request to block sleep.")
}
func setMainAppBadgeNumber(_ value: Int) {
owsFailDebug("")
}
func setNetworkActivityIndicatorVisible(_ value: Bool) {
owsFailDebug("")
}
func runNowOr(whenMainAppIsActive block: @escaping AppActiveBlock) {
owsFailDebug("cannot run main app active blocks in share extension.")
}
}

View file

@ -1,12 +1,14 @@
import CoreServices
import PromiseKit
import SignalUtilitiesKit
import SessionUIKit
final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerDelegate {
private var areVersionMigrationsComplete = false
public static var attachmentPrepPromise: Promise<[SignalAttachment]>?
// MARK: Error
// MARK: - Error
enum ShareViewControllerError: Error {
case assertionError(description: String)
case unsupportedMedia
@ -14,7 +16,8 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
case obsoleteShare
}
// MARK: Lifecycle
// MARK: - Lifecycle
override func loadView() {
super.loadView()
@ -39,28 +42,35 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
return
}
AppSetup.setupEnvironment(appSpecificSingletonBlock: {
SSKEnvironment.shared.notificationsManager = NoopNotificationsManager()
}, migrationCompletion: { [weak self] in
AssertIsOnMainThread()
AppSetup.setupEnvironment(
appSpecificSingletonBlock: {
SSKEnvironment.shared.notificationsManager = NoopNotificationsManager()
},
migrationCompletion: { [weak self] in
AssertIsOnMainThread()
self?.versionMigrationsDidComplete()
guard let strongSelf = self else { return }
// performUpdateCheck must be invoked after Environment has been initialized because
// upgrade process may depend on Environment.
strongSelf.versionMigrationsDidComplete()
})
// performUpdateCheck must be invoked after Environment has been initialized because
// upgrade process may depend on Environment.
self?.versionMigrationsDidComplete()
}
)
// We don't need to use "screen protection" in the SAE.
NotificationCenter.default.addObserver(self,
selector: #selector(storageIsReady),
name: .StorageIsReady,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(applicationDidEnterBackground),
name: .OWSApplicationDidEnterBackground,
object: nil)
NotificationCenter.default.addObserver(
self,
selector: #selector(storageIsReady),
name: .StorageIsReady,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(applicationDidEnterBackground),
name: .OWSApplicationDidEnterBackground,
object: nil
)
}
@objc
@ -88,12 +98,8 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
AssertIsOnMainThread()
// App isn't ready until storage is ready AND all version migrations are complete.
guard areVersionMigrationsComplete else {
return
}
guard OWSStorage.isStorageReady() else {
return
}
guard areVersionMigrationsComplete else { return }
guard OWSStorage.isStorageReady() else { return }
guard !AppReadiness.isAppReady() else {
// Only mark the app as ready once.
return
@ -108,9 +114,7 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
AppReadiness.setAppIsReady()
// We don't need to use messageFetcherJob in the SAE.
// We don't need to use SyncPushTokensJob in the SAE.
// We don't need to use DeviceSleepManager in the SAE.
AppVersion.sharedInstance().saeLaunchDidComplete()
@ -119,9 +123,7 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
// We don't need to use OWSMessageReceiver in the SAE.
// We don't need to use OWSBatchMessageProcessor in the SAE.
// We don't need to use OWSOrphanDataCleaner in the SAE.
// We don't need to fetch the local profile in the SAE
OWSReadReceiptManager.shared().prepareCachedValues()
@ -129,10 +131,10 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
override func viewDidLoad() {
super.viewDidLoad()
AppReadiness.runNowOrWhenAppDidBecomeReady { [weak self] in
AssertIsOnMainThread()
guard let strongSelf = self else { return }
strongSelf.showLockScreenOrMainContent()
self?.showLockScreenOrMainContent()
}
}
@ -143,11 +145,9 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
Logger.info("")
if OWSScreenLock.shared.isScreenLockEnabled() {
self.dismiss(animated: false) { [weak self] in
AssertIsOnMainThread()
guard let strongSelf = self else { return }
strongSelf.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
self?.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
}
}
}
@ -161,12 +161,15 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
ExitShareExtension()
}
// MARK: App Mode
// MARK: - App Mode
public func getCurrentAppMode() -> AppMode {
guard let window = self.view.window else { return .light }
let userInterfaceStyle = window.traitCollection.userInterfaceStyle
let isLightMode = (userInterfaceStyle == .light || userInterfaceStyle == .unspecified)
return isLightMode ? .light : .dark
return (isLightMode ? .light : .dark)
}
public func setCurrentAppMode(to appMode: AppMode) {
@ -181,7 +184,8 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
private func showLockScreenOrMainContent() {
if OWSScreenLock.shared.isScreenLockEnabled() {
showLockScreen()
} else {
}
else {
showMainContent()
}
}
@ -192,16 +196,23 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
}
private func showMainContent() {
let threadPickerVC = ThreadPickerVC()
let threadPickerVC: ThreadPickerVC = ThreadPickerVC()
threadPickerVC.shareVC = self
setViewControllers([ threadPickerVC ], animated: false)
let promise = buildAttachments()
ModalActivityIndicatorViewController.present(fromViewController: self, canCancel: false, message: NSLocalizedString("vc_share_loading_message", comment: "")) { activityIndicator in
promise.done { _ in
activityIndicator.dismiss { }
}.catch { _ in
activityIndicator.dismiss { }
}
ModalActivityIndicatorViewController.present(
fromViewController: self,
canCancel: false,
message: "vc_share_loading_message".localized()) { activityIndicator in
promise
.done { _ in
activityIndicator.dismiss { }
}
.catch { _ in
activityIndicator.dismiss { }
}
}
ShareVC.attachmentPrepPromise = promise
}
@ -220,7 +231,7 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
func shareViewFailed(error: Error) {
let alert = UIAlertController(title: "Session", message: error.localizedDescription, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: { _ in
alert.addAction(UIAlertAction(title: "OK".localized(), style: .default, handler: { _ in
self.extensionContext!.cancelRequest(withError: error)
}))
present(alert, animated: true, completion: nil)
@ -236,22 +247,29 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
guard let firstUtiType = itemProvider.registeredTypeIdentifiers.first else {
return false
}
return firstUtiType == utiType
return (firstUtiType == utiType)
}
private class func isVisualMediaItem(itemProvider: NSItemProvider) -> Bool {
return (itemProvider.hasItemConformingToTypeIdentifier(kUTTypeImage as String) ||
itemProvider.hasItemConformingToTypeIdentifier(kUTTypeMovie as String))
return (
itemProvider.hasItemConformingToTypeIdentifier(kUTTypeImage as String) ||
itemProvider.hasItemConformingToTypeIdentifier(kUTTypeMovie as String)
)
}
private class func isUrlItem(itemProvider: NSItemProvider) -> Bool {
return itemMatchesSpecificUtiType(itemProvider: itemProvider,
utiType: kUTTypeURL as String)
return itemMatchesSpecificUtiType(
itemProvider: itemProvider,
utiType: kUTTypeURL as String
)
}
private class func isContactItem(itemProvider: NSItemProvider) -> Bool {
return itemMatchesSpecificUtiType(itemProvider: itemProvider,
utiType: kUTTypeContact as String)
return itemMatchesSpecificUtiType(
itemProvider: itemProvider,
utiType: kUTTypeContact as String
)
}
private class func utiType(itemProvider: NSItemProvider) -> String? {
@ -259,7 +277,8 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
if isUrlItem(itemProvider: itemProvider) {
return kUTTypeURL as String
} else if isContactItem(itemProvider: itemProvider) {
}
else if isContactItem(itemProvider: itemProvider) {
return kUTTypeContact as String
}
@ -278,43 +297,43 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
// and send them as normal text messages if possible.
let urlString = url.absoluteString
return DataSourceValue.dataSource(withOversizeText: urlString)
} else if UTTypeConformsTo(utiType as CFString, kUTTypeText) {
}
else if UTTypeConformsTo(utiType as CFString, kUTTypeText) {
// Share text as oversize text messages.
//
// NOTE: SharingThreadPickerViewController will try to unpack them
// and send them as normal text messages if possible.
return DataSourcePath.dataSource(with: url,
shouldDeleteOnDeallocation: false)
} else {
guard let dataSource = DataSourcePath.dataSource(with: url,
shouldDeleteOnDeallocation: false) else {
return nil
}
if let customFileName = customFileName {
dataSource.sourceFilename = customFileName
} else {
// Ignore the filename for URLs.
dataSource.sourceFilename = url.lastPathComponent
}
return dataSource
return DataSourcePath.dataSource(
with: url,
shouldDeleteOnDeallocation: false
)
}
}
private class func preferredItemProviders(inputItem: NSExtensionItem) -> [NSItemProvider]? {
guard let attachments = inputItem.attachments else {
guard let dataSource = DataSourcePath.dataSource(with: url, shouldDeleteOnDeallocation: false) else {
return nil
}
// Fallback to the last part of the URL
dataSource.sourceFilename = (customFileName ?? url.lastPathComponent)
return dataSource
}
private class func preferredItemProviders(inputItem: NSExtensionItem) -> [NSItemProvider]? {
guard let attachments = inputItem.attachments else { return nil }
var visualMediaItemProviders = [NSItemProvider]()
var hasNonVisualMedia = false
for attachment in attachments {
if isVisualMediaItem(itemProvider: attachment) {
visualMediaItemProviders.append(attachment)
} else {
}
else {
hasNonVisualMedia = true
}
}
// Only allow multiple-attachment sends if all attachments
// are visual media.
if visualMediaItemProviders.count > 0 && !hasNonVisualMedia {
@ -334,6 +353,7 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
guard let itemProvider = attachment as? NSItemProvider else {
return false
}
return isUrlItem(itemProvider: itemProvider)
}) {
return [preferredAttachment]
@ -342,9 +362,11 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
// else return whatever is available
if let itemProvider = inputItem.attachments?.first {
return [itemProvider]
} else {
}
else {
owsFailDebug("Missing attachment.")
}
return []
}
@ -359,6 +381,7 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
Logger.error("invalid inputItem \(inputItemRaw)")
continue
}
if let itemProviders = ShareVC.preferredItemProviders(inputItem: inputItem) {
return Promise.value(itemProviders)
}
@ -366,6 +389,8 @@ final class ShareVC : UINavigationController, ShareViewDelegate, AppModeManagerD
let error = ShareViewControllerError.assertionError(description: "no input item")
return Promise(error: error)
}
// MARK: - LoadedItem
private
struct LoadedItem {

View file

@ -4,26 +4,8 @@ import SessionUIKit
final class SimplifiedConversationCell : UITableViewCell {
var threadViewModel: ThreadViewModel! { didSet { update() } }
static let reuseIdentifier = "SimplifiedConversationCell"
// MARK: - Initialization
// MARK: UI Components
private lazy var accentLineView: UIView = {
let result = UIView()
result.backgroundColor = Colors.destructive
return result
}()
private lazy var profilePictureView = ProfilePictureView()
private lazy var displayNameLabel: UILabel = {
let result = UILabel()
result.font = .boldSystemFont(ofSize: Values.mediumFontSize)
result.textColor = Colors.text
result.lineBreakMode = .byTruncatingTail
return result
}()
// MARK: Initialization
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setUpViewHierarchy()
@ -34,41 +16,90 @@ final class SimplifiedConversationCell : UITableViewCell {
setUpViewHierarchy()
}
// MARK: - UI
private lazy var stackView: UIStackView = {
let stackView: UIStackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .horizontal
stackView.alignment = .center
stackView.spacing = Values.mediumSpacing
return stackView
}()
private lazy var accentLineView: UIView = {
let result = UIView()
result.translatesAutoresizingMaskIntoConstraints = false
result.backgroundColor = Colors.destructive
return result
}()
private lazy var profilePictureView: ProfilePictureView = {
let view: ProfilePictureView = ProfilePictureView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private lazy var displayNameLabel: UILabel = {
let result = UILabel()
result.font = .boldSystemFont(ofSize: Values.mediumFontSize)
result.textColor = Colors.text
result.lineBreakMode = .byTruncatingTail
return result
}()
// MARK: - Initialization
private func setUpViewHierarchy() {
// Background color
backgroundColor = Colors.cellBackground
// Highlight color
let selectedBackgroundView = UIView()
selectedBackgroundView.backgroundColor = Colors.cellSelected
self.selectedBackgroundView = selectedBackgroundView
// Accent line view
addSubview(stackView)
stackView.addArrangedSubview(accentLineView)
stackView.addArrangedSubview(profilePictureView)
stackView.addArrangedSubview(displayNameLabel)
stackView.addArrangedSubview(UIView.hSpacer(0))
setupLayout()
}
// MARK: - Layout
private func setupLayout() {
accentLineView.set(.width, to: Values.accentLineThickness)
accentLineView.set(.height, to: 68)
// Profile picture view
let profilePictureViewSize = Values.mediumProfilePictureSize
profilePictureView.set(.width, to: profilePictureViewSize)
profilePictureView.set(.height, to: profilePictureViewSize)
profilePictureView.size = profilePictureViewSize
// Main stack view
let stackView = UIStackView(arrangedSubviews: [ accentLineView, profilePictureView, displayNameLabel, UIView.hSpacer(0) ])
stackView.axis = .horizontal
stackView.alignment = .center
stackView.spacing = Values.mediumSpacing
addSubview(stackView)
stackView.pin(to: self)
}
// MARK: Updating
// MARK: - Content
private func update() {
AssertIsOnMainThread()
guard let thread = threadViewModel?.threadRecord else { return }
let isBlocked: Bool
if let thread = thread as? TSContactThread {
isBlocked = SSKEnvironment.shared.blockingManager.isRecipientIdBlocked(thread.contactSessionID())
} else {
isBlocked = false
}
accentLineView.alpha = isBlocked ? 1 : 0
accentLineView.alpha = (isBlocked ? 1 : 0)
profilePictureView.update(for: thread)
displayNameLabel.text = getDisplayName()
}
@ -76,17 +107,25 @@ final class SimplifiedConversationCell : UITableViewCell {
private func getDisplayName() -> String {
if threadViewModel.isGroupThread {
if threadViewModel.name.isEmpty {
// TODO: Localization
return "Unknown Group"
} else {
return threadViewModel.name
}
} else {
if threadViewModel.threadRecord.isNoteToSelf() {
return NSLocalizedString("NOTE_TO_SELF", comment: "")
} else {
let hexEncodedPublicKey = threadViewModel.contactSessionID!
return Storage.shared.getContact(with: hexEncodedPublicKey)?.displayName(for: .regular) ?? hexEncodedPublicKey
}
return threadViewModel.name
}
if threadViewModel.threadRecord.isNoteToSelf() {
return "NOTE_TO_SELF".localized()
}
guard let hexEncodedPublicKey: String = threadViewModel.contactSessionID else {
// TODO: Localization
return "Unknown"
}
return (
Storage.shared.getContact(with: hexEncodedPublicKey)?.displayName(for: .regular) ??
hexEncodedPublicKey
)
}
}

View file

@ -1,8 +1,12 @@
import UIKit
import SignalUtilitiesKit
import SessionUIKit
import SessionMessagingKit
import SessionUtilitiesKit
final class ThreadPickerVC : UIViewController, UITableViewDataSource, UITableViewDelegate, AttachmentApprovalViewControllerDelegate {
final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableViewDelegate, AttachmentApprovalViewControllerDelegate {
private var threads: YapDatabaseViewMappings!
private var threadViewModelCache: [String:ThreadViewModel] = [:] // Thread ID to ThreadViewModel
private var threadViewModelCache: [String: ThreadViewModel] = [:] // Thread ID to ThreadViewModel
private var selectedThread: TSThread?
var shareVC: ShareVC?
@ -15,32 +19,50 @@ final class ThreadPickerVC : UIViewController, UITableViewDataSource, UITableVie
result.objectCacheLimit = 500
return result
}()
// MARK: - UI
private lazy var titleLabel: UILabel = {
let titleLabel: UILabel = UILabel()
titleLabel.text = "vc_share_title".localized()
titleLabel.textColor = Colors.text
titleLabel.font = .boldSystemFont(ofSize: Values.veryLargeFontSize)
return titleLabel
}()
private lazy var tableView: UITableView = {
let result = UITableView()
result.backgroundColor = .clear
result.separatorStyle = .none
result.register(SimplifiedConversationCell.self, forCellReuseIdentifier: SimplifiedConversationCell.reuseIdentifier)
result.showsVerticalScrollIndicator = false
return result
let tableView: UITableView = UITableView()
tableView.backgroundColor = .clear
tableView.separatorStyle = .none
tableView.register(view: SimplifiedConversationCell.self)
tableView.showsVerticalScrollIndicator = false
tableView.dataSource = self
tableView.delegate = self
return tableView
}()
private lazy var fadeView: UIView = {
let result = UIView()
let view = UIView()
let gradient = Gradients.homeVCFade
result.setGradient(gradient)
result.isUserInteractionEnabled = false
return result
view.setGradient(gradient)
view.isUserInteractionEnabled = false
return view
}()
// MARK: Lifecycle
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupNavBar()
// Gradient
view.backgroundColor = .clear
let gradient = Gradients.defaultBackground
view.setGradient(gradient)
view.setGradient(Gradients.defaultBackground)
// Threads
dbConnection.beginLongLivedReadTransaction() // Freeze the connection for use on the main thread (this gives us a stable data source that doesn't change until we tell it to)
threads = YapDatabaseViewMappings(groups: [ TSInboxGroup ], view: TSThreadDatabaseViewExtensionName) // The extension should be registered at this point
@ -48,23 +70,16 @@ final class ThreadPickerVC : UIViewController, UITableViewDataSource, UITableVie
dbConnection.read { transaction in
self.threads.update(with: transaction) // Perform the initial update
}
// Title
let titleLabel = UILabel()
titleLabel.text = NSLocalizedString("vc_share_title", comment: "")
titleLabel.textColor = Colors.text
titleLabel.font = .boldSystemFont(ofSize: Values.veryLargeFontSize)
navigationItem.titleView = titleLabel
// Table view
tableView.dataSource = self
tableView.delegate = self
view.addSubview(tableView)
tableView.pin(to: view)
view.addSubview(fadeView)
fadeView.pin(.leading, to: .leading, of: view)
let topInset = 0.15 * view.height()
fadeView.pin(.top, to: .top, of: view, withInset: topInset)
fadeView.pin(.trailing, to: .trailing, of: view)
fadeView.pin(.bottom, to: .bottom, of: view)
setupLayout()
// Reload
reload()
}
@ -80,18 +95,32 @@ final class ThreadPickerVC : UIViewController, UITableViewDataSource, UITableVie
}
}
// MARK: Layout
private func setupLayout() {
let topInset = 0.15 * view.height()
tableView.pin(to: view)
fadeView.pin(.leading, to: .leading, of: view)
fadeView.pin(.top, to: .top, of: view, withInset: topInset)
fadeView.pin(.trailing, to: .trailing, of: view)
fadeView.pin(.bottom, to: .bottom, of: view)
}
// MARK: Table View Data Source
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Int(threadCount)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: SimplifiedConversationCell.reuseIdentifier) as! SimplifiedConversationCell
let cell: SimplifiedConversationCell = tableView.dequeue(type: SimplifiedConversationCell.self, for: indexPath)
cell.threadViewModel = threadViewModel(at: indexPath.row)
return cell
}
// MARK: Updating
// MARK: - Updating
private func reload() {
AssertIsOnMainThread()
dbConnection.beginLongLivedReadTransaction() // Jump to the latest commit
@ -102,11 +131,17 @@ final class ThreadPickerVC : UIViewController, UITableViewDataSource, UITableVie
tableView.reloadData()
}
// MARK: Interaction
// MARK: - Interaction
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let thread = self.thread(at: indexPath.row), let attachments = ShareVC.attachmentPrepPromise?.value else { return }
self.selectedThread = thread
tableView.deselectRow(at: indexPath, animated: true)
guard let thread = self.thread(at: indexPath.row), let attachments = ShareVC.attachmentPrepPromise?.value else {
return
}
self.selectedThread = thread
let approvalVC = AttachmentApprovalViewController.wrappedInNavController(attachments: attachments, approvalDelegate: self)
navigationController!.present(approvalVC, animated: true, completion: nil)
}
@ -115,21 +150,24 @@ final class ThreadPickerVC : UIViewController, UITableViewDataSource, UITableVie
let message = VisibleMessage()
message.sentTimestamp = NSDate.millisecondTimestamp()
message.text = messageText
let tsMessage = TSOutgoingMessage.from(message, associatedWith: selectedThread!)
Storage.write { transaction in
tsMessage.save(with: transaction)
}
shareVC!.dismiss(animated: true, completion: nil)
ModalActivityIndicatorViewController.present(fromViewController: shareVC!, canCancel: false, message: NSLocalizedString("vc_share_sending_message", comment: "")) { activityIndicator in
MessageSender.sendNonDurably(message, with: attachments, in: self.selectedThread!).done { [weak self] _ in
guard let self = self else { return }
activityIndicator.dismiss { }
self.shareVC!.shareViewWasCompleted()
}.catch { [weak self] error in
guard let self = self else { return }
activityIndicator.dismiss { }
self.shareVC!.shareViewFailed(error: error)
}
ModalActivityIndicatorViewController.present(fromViewController: shareVC!, canCancel: false, message: "vc_share_sending_message".localized()) { activityIndicator in
MessageSender.sendNonDurably(message, with: attachments, in: self.selectedThread!)
.done { [weak self] _ in
activityIndicator.dismiss { }
self?.shareVC?.shareViewWasCompleted()
}
.catch { [weak self] error in
activityIndicator.dismiss { }
self?.shareVC?.shareViewFailed(error: error)
}
}
}
@ -141,7 +179,8 @@ final class ThreadPickerVC : UIViewController, UITableViewDataSource, UITableVie
// Do nothing
}
// MARK: Convenience
// MARK: - Convenience
private func thread(at index: Int) -> TSThread? {
var thread: TSThread? = nil
dbConnection.read { transaction in
@ -153,9 +192,11 @@ final class ThreadPickerVC : UIViewController, UITableViewDataSource, UITableVie
private func threadViewModel(at index: Int) -> ThreadViewModel? {
guard let thread = thread(at: index) else { return nil }
if let cachedThreadViewModel = threadViewModelCache[thread.uniqueId!] {
return cachedThreadViewModel
} else {
}
else {
var threadViewModel: ThreadViewModel? = nil
dbConnection.read { transaction in
threadViewModel = ThreadViewModel(thread: thread, transaction: transaction)

View file

@ -0,0 +1,16 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
public protocol ReusableView: AnyObject {
static var defaultReuseIdentifier: String { get }
}
public extension ReusableView where Self: UIView {
static var defaultReuseIdentifier: String {
return String(describing: self.self)
}
}
extension UITableViewCell: ReusableView {}
extension UITableViewHeaderFooterView: ReusableView {}

View file

@ -0,0 +1,13 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import SignalCoreKit
public extension String {
func localized() -> String {
// If the localized string matches the key provided then the localisation failed
let localizedString = NSLocalizedString(self, comment: "")
owsAssertDebug(localizedString != self, "Key \"\(self)\" is not set in Localizable.strings")
return localizedString
}
}

View file

@ -0,0 +1,23 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
public extension UITableView {
func register<View>(view: View.Type) where View: UITableViewCell {
register(view.self, forCellReuseIdentifier: view.defaultReuseIdentifier)
}
func registerHeaderFooterView<View>(view: View.Type) where View: UITableViewHeaderFooterView {
register(view.self, forHeaderFooterViewReuseIdentifier: view.defaultReuseIdentifier)
}
func dequeue<T>(type: T.Type, for indexPath: IndexPath) -> T where T: UITableViewCell {
let reuseIdentifier = T.defaultReuseIdentifier
return dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as! T
}
func dequeueHeaderFooterView<T>(type: T.Type) -> T where T: UITableViewHeaderFooterView {
let reuseIdentifier = T.defaultReuseIdentifier
return dequeueReusableHeaderFooterView(withIdentifier: reuseIdentifier) as! T
}
}

View file

@ -5,7 +5,7 @@
import Foundation
// All Observer methods will be invoked from the main thread.
@objc
public protocol ShareViewDelegate: class {
public protocol ShareViewDelegate: AnyObject {
func shareViewWasUnlocked()
func shareViewWasCompleted()
func shareViewWasCancelled()