Rework "export backup" UI.

This commit is contained in:
Matthew Chen 2018-01-05 14:42:50 -05:00
parent cb4b44b8f4
commit 980b3d25a7
12 changed files with 526 additions and 126 deletions

View File

@ -42,6 +42,7 @@
344F248720069ECB00CFB4F4 /* ModalActivityIndicatorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 344F248620069ECB00CFB4F4 /* ModalActivityIndicatorViewController.swift */; };
344F248A20069F0600CFB4F4 /* ViewControllerUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 344F248820069F0600CFB4F4 /* ViewControllerUtils.h */; };
344F248B20069F0600CFB4F4 /* ViewControllerUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 344F248920069F0600CFB4F4 /* ViewControllerUtils.m */; };
3456D2C41FFFCC70001EA55D /* OWSBackupViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3456D2C21FFFCC6F001EA55D /* OWSBackupViewController.m */; };
3461284B1FD0B94000532771 /* SAELoadViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3461284A1FD0B93F00532771 /* SAELoadViewController.swift */; };
346129341FD1A88700532771 /* OWSSwiftUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346129331FD1A88700532771 /* OWSSwiftUtils.swift */; };
346129391FD1B47300532771 /* OWSPreferences.h in Headers */ = {isa = PBXBuildFile; fileRef = 346129371FD1B47200532771 /* OWSPreferences.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -523,6 +524,8 @@
344F248920069F0600CFB4F4 /* ViewControllerUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ViewControllerUtils.m; path = SignalMessaging/contacts/ViewControllerUtils.m; sourceTree = SOURCE_ROOT; };
34533F161EA8D2070006114F /* OWSAudioAttachmentPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSAudioAttachmentPlayer.h; sourceTree = "<group>"; };
34533F171EA8D2070006114F /* OWSAudioAttachmentPlayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSAudioAttachmentPlayer.m; sourceTree = "<group>"; };
3456D2C21FFFCC6F001EA55D /* OWSBackupViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSBackupViewController.m; sourceTree = "<group>"; };
3456D2C31FFFCC6F001EA55D /* OWSBackupViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBackupViewController.h; sourceTree = "<group>"; };
3461284A1FD0B93F00532771 /* SAELoadViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SAELoadViewController.swift; sourceTree = "<group>"; };
346129331FD1A88700532771 /* OWSSwiftUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSSwiftUtils.swift; sourceTree = "<group>"; };
346129371FD1B47200532771 /* OWSPreferences.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSPreferences.h; sourceTree = "<group>"; };
@ -1323,6 +1326,8 @@
34B3F8591E8DF1700035BE1A /* NotificationSettingsViewController.m */,
34CCAF391F0C2748004084F4 /* OWSAddToContactViewController.h */,
34CCAF3A1F0C2748004084F4 /* OWSAddToContactViewController.m */,
3456D2C31FFFCC6F001EA55D /* OWSBackupViewController.h */,
3456D2C21FFFCC6F001EA55D /* OWSBackupViewController.m */,
34B3F85B1E8DF1700035BE1A /* OWSConversationSettingsViewController.h */,
34B3F85C1E8DF1700035BE1A /* OWSConversationSettingsViewController.m */,
34D5CCAB1EAE7136005515DB /* OWSConversationSettingsViewDelegate.h */,
@ -2923,6 +2928,7 @@
45F170BB1E2FC5D3003FC1F2 /* CallAudioService.swift in Sources */,
34B3F8711E8DF1700035BE1A /* AboutTableViewController.m in Sources */,
34B3F88D1E8DF1700035BE1A /* OWSQRCodeScanningViewController.m in Sources */,
3456D2C41FFFCC70001EA55D /* OWSBackupViewController.m in Sources */,
34BECE2E1F7ABCE000D7438D /* GifPickerViewController.swift in Sources */,
34CCAF3B1F0C2748004084F4 /* OWSAddToContactViewController.m in Sources */,
34D1F0C01F8EC1760066283D /* MessageRecipientStatusUtils.swift in Sources */,
@ -3114,7 +3120,11 @@
"DEBUG=1",
"$(inherited)",
);
"GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = "DEBUG=1 $(inherited) SSK_BUILDING_FOR_TESTS=1";
"GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = (
"DEBUG=1",
"$(inherited)",
"SSK_BUILDING_FOR_TESTS=1",
);
GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;

View File

@ -4,6 +4,7 @@
#import "DebugUIMisc.h"
#import "OWSBackup.h"
#import "OWSBackupViewController.h"
#import "OWSCountryMetadata.h"
#import "OWSTableViewController.h"
#import "RegistrationViewController.h"
@ -94,10 +95,22 @@ NS_ASSUME_NONNULL_BEGIN
[DebugUIMisc sendUnencryptedDatabase:thread];
}]];
}
[items addObject:[OWSTableItem itemWithTitle:@"Export Backup"
actionBlock:^{
[OWSBackup exportBackup];
}]];
[items addObject:[OWSTableItem
subPageItemWithText:@"Export Backup w/ Password"
actionBlock:^(UIViewController *viewController) {
OWSBackupViewController *backupViewController = [OWSBackupViewController new];
[backupViewController exportBackup:thread skipPassword:NO];
[viewController.navigationController pushViewController:backupViewController
animated:YES];
}]];
[items addObject:[OWSTableItem
subPageItemWithText:@"Export Backup w/o Password"
actionBlock:^(UIViewController *viewController) {
OWSBackupViewController *backupViewController = [OWSBackupViewController new];
[backupViewController exportBackup:thread skipPassword:YES];
[viewController.navigationController pushViewController:backupViewController
animated:YES];
}]];
return [OWSTableSection sectionWithTitle:self.name items:items];
}

View File

@ -0,0 +1,19 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import <SignalMessaging/OWSViewController.h>
NS_ASSUME_NONNULL_BEGIN
@class TSThread;
@interface OWSBackupViewController : OWSViewController
// If currentThread is non-nil, we should offer to let users send the
// backup in that thread.
- (void)exportBackup:(TSThread *_Nullable)currentThread skipPassword:(BOOL)skipPassword;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,269 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSBackupViewController.h"
#import "OWSBackup.h"
#import "Signal-Swift.h"
#import "ThreadUtil.h"
#import <SignalMessaging/AttachmentSharing.h>
#import <SignalMessaging/Environment.h>
#import <SignalMessaging/SignalMessaging-Swift.h>
#import <SignalMessaging/UIColor+OWS.h>
#import <SignalMessaging/UIFont+OWS.h>
#import <SignalMessaging/UIView+OWS.h>
#import <SignalServiceKit/MIMETypeUtil.h>
NS_ASSUME_NONNULL_BEGIN
@interface OWSBackupViewController () <OWSBackupDelegate>
@property (nonatomic) OWSBackup *backup;
@end
#pragma mark -
@implementation OWSBackupViewController
- (void)loadView
{
[super loadView];
self.view.backgroundColor = [UIColor whiteColor];
self.navigationItem.title = NSLocalizedString(@"BACKUP_EXPORT_VIEW_TITLE", @"Title for the 'backup export' view.");
self.navigationItem.leftBarButtonItem =
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemStop
target:self
action:@selector(dismissWasPressed:)];
self.backup.delegate = self;
[self updateUI];
}
- (void)exportBackup:(TSThread *_Nullable)currentThread skipPassword:(BOOL)skipPassword
{
OWSAssertIsOnMainThread();
// We set ourselves as the delegate of the backup later,
// after we've loaded our view.
self.backup = [OWSBackup new];
[self.backup exportBackup:currentThread skipPassword:skipPassword];
}
- (void)updateUI
{
for (UIView *subview in self.view.subviews) {
[subview removeFromSuperview];
}
switch (self.backup.backupState) {
case OWSBackupState_InProgress:
[self showInProgressUI];
break;
case OWSBackupState_Cancelled:
[self showCancelledUI];
break;
case OWSBackupState_Complete:
[self showCompleteUI];
break;
}
}
- (void)showInProgressUI
{
UIView *container = [UIView new];
[self.view addSubview:container];
[container autoCenterInSuperview];
UIActivityIndicatorView *activityIndicatorView =
[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[container addSubview:activityIndicatorView];
[activityIndicatorView autoPinEdgeToSuperviewEdge:ALEdgeTop];
[activityIndicatorView autoHCenterInSuperview];
[activityIndicatorView startAnimating];
UILabel *label = [UILabel new];
label.text = NSLocalizedString(
@"BACKUP_EXPORT_IN_PROGRESS_MESSAGE", @"Message indicating that backup export is in progress.");
label.textColor = [UIColor blackColor];
label.font = [UIFont ows_regularFontWithSize:18.f];
[container addSubview:label];
[label autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:activityIndicatorView withOffset:20.f];
[label autoPinEdgeToSuperviewEdge:ALEdgeBottom];
[label autoPinWidthToSuperview];
}
- (void)showCancelledUI
{
// Show nothing.
}
- (void)showCompleteUI
{
NSMutableArray<UIView *> *subviews = [NSMutableArray new];
{
NSString *message = NSLocalizedString(
@"BACKUP_EXPORT_COMPLETE_MESSAGE", @"Message indicating that backup export without password is complete.");
UILabel *label = [UILabel new];
label.text = message;
label.textColor = [UIColor blackColor];
label.font = [UIFont ows_regularFontWithSize:18.f];
label.textAlignment = NSTextAlignmentCenter;
label.numberOfLines = 0;
label.lineBreakMode = NSLineBreakByWordWrapping;
[subviews addObject:label];
}
if (self.backup.backupPassword) {
NSString *message = [NSString stringWithFormat:NSLocalizedString(@"BACKUP_EXPORT_PASSWORD_MESSAGE_FORMAT",
@"Format for message indicating that backup export with "
@"password is complete. Embeds: {{the backup password}}."),
self.backup.backupPassword];
UILabel *label = [UILabel new];
label.text = message;
label.textColor = [UIColor blackColor];
label.font = [UIFont ows_regularFontWithSize:14.f];
label.textAlignment = NSTextAlignmentCenter;
label.numberOfLines = 0;
label.lineBreakMode = NSLineBreakByWordWrapping;
[subviews addObject:label];
}
[subviews addObject:[UIView new]];
if (self.backup.backupPassword) {
[subviews
addObject:[self makeButtonWithTitle:NSLocalizedString(@"BACKUP_EXPORT_COPY_PASSWORD_BUTTON",
@"Label for button that copies backup password to the pasteboard.")
selector:@selector(copyPassword)]];
}
[subviews addObject:[self makeButtonWithTitle:NSLocalizedString(@"BACKUP_EXPORT_SHARE_BACKUP_BUTTON",
@"Label for button that opens share UI for backup.")
selector:@selector(shareBackup)]];
if (self.backup.currentThread) {
[subviews
addObject:[self makeButtonWithTitle:NSLocalizedString(@"BACKUP_EXPORT_SEND_BACKUP_BUTTON",
@"Label for button that 'send backup' in the current conversation.")
selector:@selector(sendBackup)]];
}
// TODO: We should offer the option to save the backup to "Files", iCloud, Dropbox, etc.
UIView *container = [UIView verticalStackWithSubviews:subviews spacing:10];
[self.view addSubview:container];
[container autoVCenterInSuperview];
[container autoPinWidthToSuperviewWithMargin:25.f];
}
- (UIView *)makeButtonWithTitle:(NSString *)title selector:(SEL)selector
{
const CGFloat kButtonHeight = 40;
OWSFlatButton *button = [OWSFlatButton buttonWithTitle:title
font:[OWSFlatButton fontForHeight:kButtonHeight]
titleColor:[UIColor whiteColor]
backgroundColor:[UIColor ows_materialBlueColor]
target:self
selector:selector];
[button autoSetDimension:ALDimensionWidth toSize:140];
[button autoSetDimension:ALDimensionHeight toSize:kButtonHeight];
return button;
}
- (void)copyPassword
{
OWSAssert(self.backup.backupPassword.length > 0);
[UIPasteboard.generalPasteboard setString:self.backup.backupPassword];
}
- (void)shareBackup
{
OWSAssertIsOnMainThread();
OWSAssert(self.backup.backupZipPath.length > 0);
[AttachmentSharing showShareUIForURL:[NSURL fileURLWithPath:self.backup.backupZipPath]];
}
- (void)sendBackup
{
OWSAssertIsOnMainThread();
OWSAssert(self.backup.backupZipPath.length > 0);
OWSAssert(self.backup.currentThread);
[ModalActivityIndicatorViewController
presentFromViewController:self
canCancel:NO
backgroundBlock:^(ModalActivityIndicatorViewController *modalActivityIndicator) {
NSString *fileName = [self.backup.backupZipPath lastPathComponent];
OWSMessageSender *messageSender = [Environment current].messageSender;
NSString *utiType = [MIMETypeUtil utiTypeForFileExtension:fileName.pathExtension];
DataSource *_Nullable dataSource =
[DataSourcePath dataSourceWithFilePath:self.backup.backupZipPath];
[dataSource setSourceFilename:fileName];
SignalAttachment *attachment =
[SignalAttachment attachmentWithDataSource:dataSource dataUTI:utiType];
if (!attachment || [attachment hasError]) {
OWSFail(@"%@ attachment[%@]: %@",
self.logTag,
[attachment sourceFilename],
[attachment errorName]);
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[ThreadUtil
sendMessageWithAttachment:attachment
inThread:self.backup.currentThread
messageSender:messageSender
completion:^(NSError *_Nullable error) {
OWSAssertIsOnMainThread();
[modalActivityIndicator dismissWithCompletion:^{
if (error) {
DDLogError(@"%@ send backup failed: %@", self.logTag, error);
[OWSAlerts
showAlertWithTitle:NSLocalizedString(
@"BACKUP_EXPORT_SEND_BACKUP_FAILED",
@"Message indicating that sending "
@"the backup failed.")];
} else {
[OWSAlerts
showAlertWithTitle:
NSLocalizedString(@"BACKUP_EXPORT_SEND_BACKUP_SUCCESS",
@"Message indicating that sending the backup "
@"succeeded.")];
}
}];
}];
});
}];
}
- (void)dismissWasPressed:(id)sender
{
[self.backup cancel];
[self.navigationController popViewControllerAnimated:YES];
}
#pragma mark - OWSBackupDelegate
- (void)backupStateDidChange
{
DDLogInfo(@"%@ %s.", self.logTag, __PRETTY_FUNCTION__);
[self updateUI];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -4,9 +4,38 @@
NS_ASSUME_NONNULL_BEGIN
@protocol OWSBackupDelegate <NSObject>
- (void)backupStateDidChange;
@end
#pragma mark -
typedef NS_ENUM(NSUInteger, OWSBackupState) {
OWSBackupState_InProgress,
OWSBackupState_Cancelled,
OWSBackupState_Complete,
};
@class TSThread;
@interface OWSBackup : NSObject
+ (void)exportBackup;
@property (nonatomic, weak) id<OWSBackupDelegate> delegate;
@property (nonatomic) OWSBackupState backupState;
// If non-nil, backup is encrypted.
@property (nonatomic, nullable, readonly) NSString *backupPassword;
@property (nonatomic, nullable, readonly) TSThread *currentThread;
@property (nonatomic, readonly) NSString *backupZipPath;
- (void)exportBackup:(nullable TSThread *)currentThread skipPassword:(BOOL)skipPassword;
- (void)cancel;
@end

View File

@ -7,7 +7,6 @@
#import "Signal-Swift.h"
#import "zlib.h"
#import <SSZipArchive/SSZipArchive.h>
#import <SignalMessaging/AttachmentSharing.h>
#import <SignalMessaging/SignalMessaging-Swift.h>
#import <SignalServiceKit/OWSFileSystem.h>
#import <SignalServiceKit/TSStorageManager.h>
@ -24,10 +23,12 @@ NS_ASSUME_NONNULL_BEGIN
@interface OWSBackup ()
@property (nonatomic) NSString *password;
@property (nonatomic, nullable) TSThread *currentThread;
@property (nonatomic, nullable) NSString *backupPassword;
@property (nonatomic) NSString *backupDirPath;
@property (nonatomic) NSString *backupZipPath;
@property (atomic) BOOL cancelled;
@end
@ -43,107 +44,58 @@ NS_ASSUME_NONNULL_BEGIN
[OWSFileSystem deleteFileIfExists:self.backupDirPath];
}
+ (void)exportBackup
- (void)setBackupState:(OWSBackupState)backupState
{
dispatch_async(dispatch_get_main_queue(), ^{
[[OWSBackup new] exportBackup];
});
_backupState = backupState;
[self.delegate backupStateDidChange];
}
- (void)exportBackup
- (void)cancel
{
self.backupState = OWSBackupState_Cancelled;
}
- (BOOL)isCancelled
{
return self.backupState == OWSBackupState_Cancelled;
}
- (void)exportBackup:(nullable TSThread *)currentThread skipPassword:(BOOL)skipPassword
{
OWSAssertIsOnMainThread();
// TODO: Should the user pick a password?
// If not, should probably generate something more user-friendly,
// e.g. case-insensitive set of hexadecimal?
NSString *password = [NSUUID UUID].UUIDString;
self.password = password;
DDLogVerbose(@"%@ backup export complete; password: %@", self.logTag, password);
self.currentThread = currentThread;
self.backupState = OWSBackupState_InProgress;
[self showExportProgressUI:^(UIAlertController *exportProgressAlert) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self exportToFilesAndZip];
if (skipPassword) {
DDLogVerbose(@"%@ backup export without password", self.logTag);
} else {
// TODO: Should the user pick a password?
// If not, should probably generate something more user-friendly,
// e.g. case-insensitive set of hexadecimal?
NSString *backupPassword = [NSUUID UUID].UUIDString;
self.backupPassword = backupPassword;
DDLogVerbose(@"%@ backup export with password: %@", self.logTag, backupPassword);
}
dispatch_async(dispatch_get_main_queue(), ^{
[exportProgressAlert dismissViewControllerAnimated:YES
completion:^{
[self showExportCompleteUI:^{
[self showShareUI];
}];
}];
});
[self startExport];
}
- (void)startExport
{
OWSAssertIsOnMainThread();
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self exportToFilesAndZip];
dispatch_async(dispatch_get_main_queue(), ^{
if (!self.isCancelled) {
self.backupState = OWSBackupState_Complete;
}
[self.delegate backupStateDidChange];
});
}];
}
- (void)showExportProgressUI:(void (^_Nonnull)(UIAlertController *))completion
{
OWSAssertIsOnMainThread();
OWSAssert(completion);
NSString *title = NSLocalizedString(
@"BACKUP_EXPORT_IN_PROGRESS_ALERT_TITLE", @"Title for the 'backup export in progress' alert.");
UIAlertController *alert =
[UIAlertController alertControllerWithTitle:title message:nil preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:[CommonStrings cancelButton]
style:UIAlertActionStyleCancel
handler:^(UIAlertAction *_Nonnull action) {
self.cancelled = YES;
}];
[alert addAction:cancelAction];
__weak UIAlertController *weakAlert = alert;
UIViewController *fromViewController = [[UIApplication sharedApplication] frontmostViewController];
[fromViewController presentViewController:alert
animated:YES
completion:^(void) {
UIAlertController *strongAlert = weakAlert;
if (!strongAlert) {
OWSFail(@"%@ missing alert.", self.logTag);
return;
}
completion(strongAlert);
}];
}
- (void)showExportCompleteUI:(void (^_Nonnull)(void))completion
{
OWSAssertIsOnMainThread();
OWSAssert(completion);
OWSAssert(self.password.length > 0);
// TODO: We probably want to offer an option that lets users copy
// the password to the pasteboard.
NSString *title
= NSLocalizedString(@"BACKUP_EXPORT_COMPLETE_ALERT_TITLE", @"Title for the 'backup export complete' alert.");
NSString *message = [NSString
stringWithFormat:
NSLocalizedString(@"BACKUP_EXPORT_COMPLETE_ALERT_MESSAGE_FORMAT",
@"Format for message for the 'backup export complete' alert. Embeds: {{the backup password}}."),
self.password];
UIAlertController *alert =
[UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", comment
: nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *_Nonnull action) {
completion();
}];
[alert addAction:okAction];
UIViewController *fromViewController = [[UIApplication sharedApplication] frontmostViewController];
[fromViewController presentViewController:alert animated:YES completion:nil];
}
- (void)showShareUI
{
OWSAssertIsOnMainThread();
OWSAssert(self.backupZipPath.length > 0);
[AttachmentSharing showShareUIForURL:[NSURL fileURLWithPath:self.backupZipPath]];
});
}
- (void)exportToFilesAndZip
@ -173,21 +125,34 @@ NS_ASSUME_NONNULL_BEGIN
[OWSFileSystem protectFolderAtPath:rootDirPath];
[OWSFileSystem ensureDirectoryExists:backupDirPath];
if (self.isCancelled) {
return;
}
NSData *databasePassword = [TSStorageManager sharedManager].databasePassword;
if (![self writeData:databasePassword fileName:@"databasePassword" backupDirPath:backupDirPath]) {
return;
}
if (self.isCancelled) {
return;
}
if (![self writeUserDefaults:NSUserDefaults.standardUserDefaults
fileName:@"standardUserDefaults"
backupDirPath:backupDirPath]) {
return;
}
if (self.isCancelled) {
return;
}
if (![self writeUserDefaults:NSUserDefaults.appUserDefaults
fileName:@"appUserDefaults"
backupDirPath:backupDirPath]) {
return;
}
if (self.isCancelled) {
return;
}
// Use a read/write transaction to acquire a file lock on the database files.
//
// TODO: If we use multiple database files, lock them too.
@ -198,12 +163,18 @@ NS_ASSUME_NONNULL_BEGIN
backupDirPath:backupDirPath]) {
return;
}
if (self.isCancelled) {
return;
}
if (![self copyDirectory:OWSFileSystem.appSharedDataDirectoryPath
dstDirName:@"appSharedDataDirectoryPath"
backupDirPath:backupDirPath]) {
return;
}
}];
if (self.isCancelled) {
return;
}
if (![self zipDirectory:backupDirPath dstFilePath:backupZipPath]) {
return;
}
@ -297,14 +268,13 @@ NS_ASSUME_NONNULL_BEGIN
{
OWSAssert(srcDirPath.length > 0);
OWSAssert(dstFilePath.length > 0);
OWSAssert(self.password.length > 0);
BOOL success = [SSZipArchive createZipFileAtPath:dstFilePath
withContentsOfDirectory:srcDirPath
keepParentDirectory:NO
compressionLevel:Z_DEFAULT_COMPRESSION
password:self.password
AES:YES
password:self.backupPassword
AES:self.backupPassword != nil
progressHandler:^(NSUInteger entryNumber, NSUInteger total) {
DDLogVerbose(@"%@ Zip progress: %zd / %zd = %f",
self.logTag,

View File

@ -145,16 +145,34 @@
/* action sheet button title to enable built in speaker during a call */
"AUDIO_ROUTE_BUILT_IN_SPEAKER" = "Speaker";
/* Format for message for the 'backup export complete' alert. Embeds: {{the backup password}} */
"BACKUP_EXPORT_COMPLETE_ALERT_MESSAGE_FORMAT" = "Your backup password is: %@. Make sure to keep a copy of this password or you won't be able to restore from this backup.";
/* Message indicating that backup export without password is complete. */
"BACKUP_EXPORT_COMPLETE_MESSAGE" = "Backup complete.";
/* Title for the 'backup export complete' alert. */
"BACKUP_EXPORT_COMPLETE_ALERT_TITLE" = "Backup Complete";
/* Label for button that copies backup password to the pasteboard. */
"BACKUP_EXPORT_COPY_PASSWORD_BUTTON" = "Copy Password";
/* Title for the 'backup export in progress' alert. */
"BACKUP_EXPORT_IN_PROGRESS_ALERT_TITLE" = "Exporting Backup...";
/* Message indicating that backup export is in progress. */
"BACKUP_EXPORT_IN_PROGRESS_MESSAGE" = "Exporting Backup...";
/* Format for backup filenames. Embeds: {{the date and time of the backup}} */
/* Format for message indicating that backup export with password is complete. Embeds: {{the backup password}}. */
"BACKUP_EXPORT_PASSWORD_MESSAGE_FORMAT" = "Your backup password is: %@. Make sure to keep a copy of this password or you won't be able to restore from this backup.";
/* Label for button that 'send backup' in the current conversation. */
"BACKUP_EXPORT_SEND_BACKUP_BUTTON" = "Send Backup as Message";
/* Message indicating that sending the backup failed. */
"BACKUP_EXPORT_SEND_BACKUP_FAILED" = "Sending Backup Failed.";
/* Message indicating that sending the backup succeeded. */
"BACKUP_EXPORT_SEND_BACKUP_SUCCESS" = "Backup Sent.";
/* Label for button that opens share UI for backup. */
"BACKUP_EXPORT_SHARE_BACKUP_BUTTON" = "Share Backup";
/* Title for the 'backup export' view. */
"BACKUP_EXPORT_VIEW_TITLE" = "Backup";
/* Format for backup filenames. Embeds: {{the date and time of the backup}}. Should not include characters like slash (/ or \\) or colon (:). */
"BACKUP_FILENAME_FORMAT" = "Signal Backup %@";
/* An explanation of the consequences of blocking another user. */

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSViewController.h"
@ -50,10 +50,13 @@ typedef NS_ENUM(NSInteger, OWSTableItemType) {
};
typedef void (^OWSTableActionBlock)(void);
typedef void (^OWSTableSubPageBlock)(UIViewController *viewController);
typedef UITableViewCell *_Nonnull (^OWSTableCustomCellBlock)(void);
@interface OWSTableItem : NSObject
@property (nonatomic, weak) UIViewController *tableViewController;
+ (OWSTableItem *)itemWithTitle:(NSString *)title actionBlock:(nullable OWSTableActionBlock)actionBlock;
+ (OWSTableItem *)itemWithCustomCell:(UITableViewCell *)customCell
@ -73,6 +76,12 @@ typedef UITableViewCell *_Nonnull (^OWSTableCustomCellBlock)(void);
customRowHeight:(CGFloat)customRowHeight
actionBlock:(nullable OWSTableActionBlock)actionBlock;
+ (OWSTableItem *)subPageItemWithText:(NSString *)text actionBlock:(nullable OWSTableSubPageBlock)actionBlock;
+ (OWSTableItem *)subPageItemWithText:(NSString *)text
customRowHeight:(CGFloat)customRowHeight
actionBlock:(nullable OWSTableSubPageBlock)actionBlock;
+ (OWSTableItem *)actionItemWithText:(NSString *)text actionBlock:(nullable OWSTableActionBlock)actionBlock;
+ (OWSTableItem *)softCenterLabelItemWithText:(NSString *)text;

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSTableViewController.h"
@ -153,6 +153,7 @@ const CGFloat kOWSTable_DefaultCellHeight = 45.f;
OWSTableItem *item = [OWSTableItem new];
item.itemType = OWSTableItemTypeAction;
__weak OWSTableItem *weakItem = item;
item.actionBlock = actionBlock;
item.customCellBlock = ^{
UITableViewCell *cell = [UITableViewCell new];
@ -176,6 +177,45 @@ const CGFloat kOWSTable_DefaultCellHeight = 45.f;
return item;
}
+ (OWSTableItem *)subPageItemWithText:(NSString *)text actionBlock:(nullable OWSTableSubPageBlock)actionBlock
{
OWSAssert(text.length > 0);
OWSAssert(actionBlock);
OWSTableItem *item = [OWSTableItem new];
item.itemType = OWSTableItemTypeAction;
__weak OWSTableItem *weakItem = item;
item.actionBlock = ^{
OWSTableItem *strongItem = weakItem;
OWSAssert(strongItem);
OWSAssert(strongItem.tableViewController);
if (actionBlock) {
actionBlock(strongItem.tableViewController);
}
};
item.customCellBlock = ^{
UITableViewCell *cell = [UITableViewCell new];
cell.textLabel.text = text;
cell.textLabel.font = [UIFont ows_regularFontWithSize:18.f];
cell.textLabel.textColor = [UIColor blackColor];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
return cell;
};
return item;
}
+ (OWSTableItem *)subPageItemWithText:(NSString *)text
customRowHeight:(CGFloat)customRowHeight
actionBlock:(nullable OWSTableSubPageBlock)actionBlock
{
OWSAssert(customRowHeight > 0);
OWSTableItem *item = [self subPageItemWithText:text actionBlock:actionBlock];
item.customRowHeight = @(customRowHeight);
return item;
}
+ (OWSTableItem *)actionItemWithText:(NSString *)text actionBlock:(nullable OWSTableActionBlock)actionBlock
{
OWSAssert(text.length > 0);
@ -481,6 +521,8 @@ NSString *const kOWSTableCellIdentifier = @"kOWSTableCellIdentifier";
{
OWSTableItem *item = [self itemForIndexPath:indexPath];
item.tableViewController = self;
UITableViewCell *customCell = [item customCell];
if (customCell) {
return customCell;

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import <PureLayout/PureLayout.h>
@ -95,9 +95,14 @@ CGFloat ScaleFromIPhone5(CGFloat iPhone5Value);
- (NSTextAlignment)textAlignmentUnnatural;
// Leading and trailing anchors honor layout margins.
// When using a UIView as a "div" to structure layout, we don't want it to have margins.
+ (UIView *)containerView;
- (void)setHLayoutMargins:(CGFloat)value;
#pragma mark - Containers
+ (UIView *)containerView;
+ (UIView *)verticalStackWithSubviews:(NSArray<UIView *> *)subviews spacing:(int)spacing;
#pragma mark - Debugging
- (void)addBorderWithColor:(UIColor *)color;

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSMath.h"
@ -388,6 +388,16 @@ CGFloat ScaleFromIPhone5(CGFloat iPhone5Value)
return (self.isRTL ? NSTextAlignmentLeft : NSTextAlignmentRight);
}
- (void)setHLayoutMargins:(CGFloat)value
{
UIEdgeInsets layoutMargins = self.layoutMargins;
layoutMargins.left = value;
layoutMargins.right = value;
self.layoutMargins = layoutMargins;
}
#pragma mark - Containers
+ (UIView *)containerView
{
UIView *view = [UIView new];
@ -397,12 +407,22 @@ CGFloat ScaleFromIPhone5(CGFloat iPhone5Value)
return view;
}
- (void)setHLayoutMargins:(CGFloat)value
+ (UIView *)verticalStackWithSubviews:(NSArray<UIView *> *)subviews spacing:(int)spacing
{
UIEdgeInsets layoutMargins = self.layoutMargins;
layoutMargins.left = value;
layoutMargins.right = value;
self.layoutMargins = layoutMargins;
UIView *container = [UIView containerView];
UIView *_Nullable lastSubview = nil;
for (UIView *subview in subviews) {
[container addSubview:subview];
[subview autoPinWidthToSuperview];
if (lastSubview) {
[subview autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:lastSubview withOffset:spacing];
} else {
[subview autoPinEdgeToSuperviewEdge:ALEdgeTop];
}
lastSubview = subview;
}
[lastSubview autoPinEdgeToSuperviewEdge:ALEdgeBottom];
return container;
}
#pragma mark - Debugging

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "MIMETypeUtil.h"
@ -461,7 +461,6 @@ NSString *const kSyncMessageFileExtension = @"bin";
+ (NSSet<NSString *> *)supportedVideoUTITypes
{
OWSAssertIsOnMainThread();
static NSSet<NSString *> *result = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
@ -472,7 +471,6 @@ NSString *const kSyncMessageFileExtension = @"bin";
+ (NSSet<NSString *> *)supportedAudioUTITypes
{
OWSAssertIsOnMainThread();
static NSSet<NSString *> *result = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
@ -483,7 +481,6 @@ NSString *const kSyncMessageFileExtension = @"bin";
+ (NSSet<NSString *> *)supportedImageUTITypes
{
OWSAssertIsOnMainThread();
static NSSet<NSString *> *result = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
@ -494,7 +491,6 @@ NSString *const kSyncMessageFileExtension = @"bin";
+ (NSSet<NSString *> *)supportedAnimatedImageUTITypes
{
OWSAssertIsOnMainThread();
static NSSet<NSString *> *result = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{