mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Refactored SessionShareExtension code to Swift
This commit is contained in:
parent
7a22c9d329
commit
4f3faa28bc
15 changed files with 748 additions and 657 deletions
|
@ -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 */,
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
|
@ -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
|
206
SessionShareExtension/SAEScreenLockViewController.swift
Normal file
206
SessionShareExtension/SAEScreenLockViewController.swift
Normal 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()
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
204
SessionShareExtension/ShareAppExtensionContext.swift
Normal file
204
SessionShareExtension/ShareAppExtensionContext.swift
Normal 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.")
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
16
SessionUtilitiesKit/General/ReusableView.swift
Normal file
16
SessionUtilitiesKit/General/ReusableView.swift
Normal 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 {}
|
13
SessionUtilitiesKit/General/String+Localization.swift
Normal file
13
SessionUtilitiesKit/General/String+Localization.swift
Normal 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
|
||||
}
|
||||
}
|
23
SessionUtilitiesKit/General/UITableView+ReusableView.swift
Normal file
23
SessionUtilitiesKit/General/UITableView+ReusableView.swift
Normal 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
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
|
|
Loading…
Reference in a new issue