Merge branch 'charlesmchen/profileView2'

pull/1/head
Matthew Chen 8 years ago
commit 83a02536a4

@ -65,7 +65,7 @@
34B3F88D1E8DF1700035BE1A /* OWSQRCodeScanningViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F8631E8DF1700035BE1A /* OWSQRCodeScanningViewController.m */; };
34B3F88E1E8DF1700035BE1A /* PrivacySettingsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F8651E8DF1700035BE1A /* PrivacySettingsTableViewController.m */; };
34B3F88F1E8DF1710035BE1A /* RegistrationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F8671E8DF1700035BE1A /* RegistrationViewController.m */; };
34B3F8901E8DF1710035BE1A /* SettingsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F8691E8DF1700035BE1A /* SettingsTableViewController.m */; };
34B3F8901E8DF1710035BE1A /* AppSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F8691E8DF1700035BE1A /* AppSettingsViewController.m */; };
34B3F8911E8DF1710035BE1A /* ShowGroupMembersViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F86B1E8DF1700035BE1A /* ShowGroupMembersViewController.m */; };
34B3F8921E8DF1710035BE1A /* SignalAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F86C1E8DF1700035BE1A /* SignalAttachment.swift */; };
34B3F8931E8DF1710035BE1A /* SignalsNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F86E1E8DF1700035BE1A /* SignalsNavigationController.m */; };
@ -76,8 +76,9 @@
34B3F8A21E8EA6040035BE1A /* ViewControllerUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F8A11E8EA6040035BE1A /* ViewControllerUtils.m */; };
34CCAF381F0C0599004084F4 /* AppUpdateNag.m in Sources */ = {isa = PBXBuildFile; fileRef = 34CCAF371F0C0599004084F4 /* AppUpdateNag.m */; };
34CCAF3B1F0C2748004084F4 /* OWSAddToContactViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34CCAF3A1F0C2748004084F4 /* OWSAddToContactViewController.m */; };
34CE88E71F2FB9A10098030F /* ProfileViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34CE88E61F2FB9A10098030F /* ProfileViewController.m */; };
34D5CC961EA6AFAD005515DB /* OWSContactsSyncing.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5CC951EA6AFAD005515DB /* OWSContactsSyncing.m */; };
34D5CCA91EAE3D30005515DB /* GroupViewHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5CCA81EAE3D30005515DB /* GroupViewHelper.m */; };
34D5CCA91EAE3D30005515DB /* AvatarViewHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5CCA81EAE3D30005515DB /* AvatarViewHelper.m */; };
34D5CCB11EAE7E7F005515DB /* SelectRecipientViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5CCB01EAE7E7F005515DB /* SelectRecipientViewController.m */; };
34D8C0271ED3673300188D7C /* DebugUIMessages.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D8C0241ED3673300188D7C /* DebugUIMessages.m */; };
34D8C0281ED3673300188D7C /* DebugUITableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D8C0261ED3673300188D7C /* DebugUITableViewController.m */; };
@ -483,8 +484,8 @@
34B3F8651E8DF1700035BE1A /* PrivacySettingsTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PrivacySettingsTableViewController.m; sourceTree = "<group>"; };
34B3F8661E8DF1700035BE1A /* RegistrationViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RegistrationViewController.h; sourceTree = "<group>"; };
34B3F8671E8DF1700035BE1A /* RegistrationViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RegistrationViewController.m; sourceTree = "<group>"; };
34B3F8681E8DF1700035BE1A /* SettingsTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingsTableViewController.h; sourceTree = "<group>"; };
34B3F8691E8DF1700035BE1A /* SettingsTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsTableViewController.m; sourceTree = "<group>"; };
34B3F8681E8DF1700035BE1A /* AppSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppSettingsViewController.h; sourceTree = "<group>"; };
34B3F8691E8DF1700035BE1A /* AppSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppSettingsViewController.m; sourceTree = "<group>"; };
34B3F86A1E8DF1700035BE1A /* ShowGroupMembersViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShowGroupMembersViewController.h; sourceTree = "<group>"; };
34B3F86B1E8DF1700035BE1A /* ShowGroupMembersViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ShowGroupMembersViewController.m; sourceTree = "<group>"; };
34B3F86C1E8DF1700035BE1A /* SignalAttachment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalAttachment.swift; sourceTree = "<group>"; };
@ -503,12 +504,14 @@
34CCAF371F0C0599004084F4 /* AppUpdateNag.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppUpdateNag.m; sourceTree = "<group>"; };
34CCAF391F0C2748004084F4 /* OWSAddToContactViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSAddToContactViewController.h; sourceTree = "<group>"; };
34CCAF3A1F0C2748004084F4 /* OWSAddToContactViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSAddToContactViewController.m; sourceTree = "<group>"; };
34CE88E51F2FB9A10098030F /* ProfileViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ProfileViewController.h; sourceTree = "<group>"; };
34CE88E61F2FB9A10098030F /* ProfileViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ProfileViewController.m; sourceTree = "<group>"; };
34D5CC941EA6AFAD005515DB /* OWSContactsSyncing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSContactsSyncing.h; sourceTree = "<group>"; };
34D5CC951EA6AFAD005515DB /* OWSContactsSyncing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactsSyncing.m; sourceTree = "<group>"; };
34D5CC981EA6EB79005515DB /* OWSMessageCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageCollectionViewCell.h; sourceTree = "<group>"; };
34D5CC9B1EA6ED17005515DB /* OWSMessageMediaAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageMediaAdapter.h; sourceTree = "<group>"; };
34D5CCA71EAE3D30005515DB /* GroupViewHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GroupViewHelper.h; sourceTree = "<group>"; };
34D5CCA81EAE3D30005515DB /* GroupViewHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GroupViewHelper.m; sourceTree = "<group>"; };
34D5CCA71EAE3D30005515DB /* AvatarViewHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AvatarViewHelper.h; sourceTree = "<group>"; };
34D5CCA81EAE3D30005515DB /* AvatarViewHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AvatarViewHelper.m; sourceTree = "<group>"; };
34D5CCAB1EAE7136005515DB /* OWSConversationSettingsViewDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSConversationSettingsViewDelegate.h; sourceTree = "<group>"; };
34D5CCAF1EAE7E7F005515DB /* SelectRecipientViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SelectRecipientViewController.h; sourceTree = "<group>"; };
34D5CCB01EAE7E7F005515DB /* SelectRecipientViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SelectRecipientViewController.m; sourceTree = "<group>"; };
@ -950,9 +953,13 @@
3472229E1EB22FFE00E53955 /* AddToGroupViewController.m */,
34B3F8361E8DF1700035BE1A /* AdvancedSettingsTableViewController.h */,
34B3F8371E8DF1700035BE1A /* AdvancedSettingsTableViewController.m */,
34B3F8681E8DF1700035BE1A /* AppSettingsViewController.h */,
34B3F8691E8DF1700035BE1A /* AppSettingsViewController.m */,
34B3F8381E8DF1700035BE1A /* AttachmentApprovalViewController.swift */,
34B3F8391E8DF1700035BE1A /* AttachmentSharing.h */,
34B3F83A1E8DF1700035BE1A /* AttachmentSharing.m */,
34D5CCA71EAE3D30005515DB /* AvatarViewHelper.h */,
34D5CCA81EAE3D30005515DB /* AvatarViewHelper.m */,
343D3D991E9283F100165CA4 /* BlockListUIUtils.h */,
343D3D9A1E9283F100165CA4 /* BlockListUIUtils.m */,
34B3F89A1E8DF3270035BE1A /* BlockListViewController.h */,
@ -977,8 +984,6 @@
34E8BF371EE9E2FD00F5F4CA /* FingerprintViewScanController.m */,
34B3F8471E8DF1700035BE1A /* FullImageViewController.h */,
34B3F8481E8DF1700035BE1A /* FullImageViewController.m */,
34D5CCA71EAE3D30005515DB /* GroupViewHelper.h */,
34D5CCA81EAE3D30005515DB /* GroupViewHelper.m */,
34B3F8491E8DF1700035BE1A /* InboxTableViewCell.h */,
34B3F84A1E8DF1700035BE1A /* InboxTableViewCell.m */,
34B3F84C1E8DF1700035BE1A /* InviteFlow.swift */,
@ -1017,6 +1022,8 @@
34D99C8B1F27B13B00D284D6 /* OWSViewController.m */,
34B3F8641E8DF1700035BE1A /* PrivacySettingsTableViewController.h */,
34B3F8651E8DF1700035BE1A /* PrivacySettingsTableViewController.m */,
34CE88E51F2FB9A10098030F /* ProfileViewController.h */,
34CE88E61F2FB9A10098030F /* ProfileViewController.m */,
34B3F8661E8DF1700035BE1A /* RegistrationViewController.h */,
34B3F8671E8DF1700035BE1A /* RegistrationViewController.m */,
4585C4671ED8F8D200896AEA /* SafetyNumberConfirmationAlert.swift */,
@ -1026,8 +1033,6 @@
3400C7951EAF99F4008A8584 /* SelectThreadViewController.m */,
3400C7901EAF89CD008A8584 /* SendExternalFileViewController.h */,
3400C7911EAF89CD008A8584 /* SendExternalFileViewController.m */,
34B3F8681E8DF1700035BE1A /* SettingsTableViewController.h */,
34B3F8691E8DF1700035BE1A /* SettingsTableViewController.m */,
34B3F86A1E8DF1700035BE1A /* ShowGroupMembersViewController.h */,
34B3F86B1E8DF1700035BE1A /* ShowGroupMembersViewController.m */,
34B3F86C1E8DF1700035BE1A /* SignalAttachment.swift */,
@ -2161,12 +2166,13 @@
34D5CCB11EAE7E7F005515DB /* SelectRecipientViewController.m in Sources */,
34B3F88F1E8DF1710035BE1A /* RegistrationViewController.m in Sources */,
3448BFCD1EDF0EA7005B2D69 /* OWSMessagesInputToolbar.m in Sources */,
34B3F8901E8DF1710035BE1A /* SettingsTableViewController.m in Sources */,
34B3F8901E8DF1710035BE1A /* AppSettingsViewController.m in Sources */,
34FD93701E3BD43A00109093 /* OWSAnyTouchGestureRecognizer.m in Sources */,
343D3D9B1E9283F100165CA4 /* BlockListUIUtils.m in Sources */,
34B3F8931E8DF1710035BE1A /* SignalsNavigationController.m in Sources */,
76EB063A18170B33006006FC /* FunctionalUtil.m in Sources */,
34F308A21ECB469700BB7697 /* OWSBezierPathView.m in Sources */,
34CE88E71F2FB9A10098030F /* ProfileViewController.m in Sources */,
348F2EAE1F0D21BC00D4ECE0 /* DeviceSleepManager.swift in Sources */,
34E3EF101EFC2684007F6822 /* DebugUIPage.m in Sources */,
76EB058A18170B33006006FC /* Release.m in Sources */,
@ -2216,7 +2222,7 @@
34B3F8881E8DF1700035BE1A /* OversizeTextMessageViewController.swift in Sources */,
34330AA31E79686200DF2FB9 /* OWSProgressView.m in Sources */,
34B3F8A21E8EA6040035BE1A /* ViewControllerUtils.m in Sources */,
34D5CCA91EAE3D30005515DB /* GroupViewHelper.m in Sources */,
34D5CCA91EAE3D30005515DB /* AvatarViewHelper.m in Sources */,
453D28BA1D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.m in Sources */,
45F170AC1E2F0351003FC1F2 /* CallAudioSession.swift in Sources */,
34B3F8801E8DF1700035BE1A /* InviteFlow.swift in Sources */,

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "profile_avatar_default@1x.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "profile_avatar_default@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "profile_avatar_default@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

@ -28,6 +28,7 @@
#import <SignalServiceKit/OWSFailedMessagesJob.h>
#import <SignalServiceKit/OWSIncomingMessageReadObserver.h>
#import <SignalServiceKit/OWSMessageSender.h>
#import <SignalServiceKit/OWSProfilesManager.h>
#import <SignalServiceKit/TSAccountManager.h>
#import <SignalServiceKit/TSDatabaseView.h>
#import <SignalServiceKit/TSMessagesManager.h>
@ -160,6 +161,7 @@ static NSString *const kURLHostVerifyPrefix = @"verify";
DDLogInfo(@"%@ application: didFinishLaunchingWithOptions completed.", self.tag);
[OWSAnalytics appLaunchDidBegin];
[OWSProfilesManager.sharedManager appLaunchDidBegin];
return YES;
}

@ -4,6 +4,7 @@
#import <Foundation/Foundation.h>
#import "AppSettingsViewController.h"
#import "AttachmentSharing.h"
#import "Environment.h"
#import "FLAnimatedImage.h"
@ -23,7 +24,6 @@
#import "PrivacySettingsTableViewController.h"
#import "PropertyListPreferences.h"
#import "PushManager.h"
#import "SettingsTableViewController.h"
#import "SignalsViewController.h"
#import "TSMessageAdapter.h"
#import "UIColor+OWS.h"

@ -4,6 +4,6 @@
#import "OWSTableViewController.h"
@interface SettingsTableViewController : OWSTableViewController
@interface AppSettingsViewController : OWSTableViewController
@end

@ -2,7 +2,7 @@
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "SettingsTableViewController.h"
#import "AppSettingsViewController.h"
#import "AboutTableViewController.h"
#import "AdvancedSettingsTableViewController.h"
#import "DebugUITableViewController.h"
@ -11,6 +11,7 @@
#import "OWSContactsManager.h"
#import "OWSLinkedDevicesTableViewController.h"
#import "PrivacySettingsTableViewController.h"
#import "ProfileViewController.h"
#import "PropertyListPreferences.h"
#import "PushManager.h"
#import "Signal-Swift.h"
@ -18,7 +19,7 @@
#import <SignalServiceKit/TSAccountManager.h>
#import <SignalServiceKit/TSSocketManager.h>
@interface SettingsTableViewController ()
@interface AppSettingsViewController ()
@property (nonatomic, readonly) OWSContactsManager *contactsManager;
@ -26,7 +27,7 @@
#pragma mark -
@implementation SettingsTableViewController
@implementation AppSettingsViewController
- (instancetype)init
{
@ -72,7 +73,7 @@
[self observeNotifications];
self.title = NSLocalizedString(@"SETTINGS_NAV_BAR_TITLE", @"Title for settings activity");
[self updateTableContents];
}
@ -83,7 +84,8 @@
[self updateTableContents];
}
- (void)dealloc {
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
@ -94,7 +96,7 @@
OWSTableContents *contents = [OWSTableContents new];
OWSTableSection *section = [OWSTableSection new];
__weak SettingsTableViewController *weakSelf = self;
__weak AppSettingsViewController *weakSelf = self;
[section addItem:[OWSTableItem itemWithCustomCellBlock:^{
UITableViewCell *cell = [UITableViewCell new];
cell.preservesSuperviewLayoutMargins = YES;
@ -168,6 +170,13 @@
}
actionBlock:nil]];
}
[section addItem:[OWSTableItem
disclosureItemWithText:NSLocalizedString(@"PROFILE_VIEW_TITLE", @"Title for the profile view.")
actionBlock:^{
[weakSelf showProfile];
}]];
[section addItem:[OWSTableItem disclosureItemWithText:NSLocalizedString(@"SETTINGS_INVITE_TITLE",
@"Settings table view cell label")
actionBlock:^{
@ -225,7 +234,7 @@
actionBlock:nil]];
[contents addSection:section];
self.contents = contents;
}
@ -255,6 +264,12 @@
[self.navigationController pushViewController:vc animated:YES];
}
- (void)showProfile
{
ProfileViewController *vc = [[ProfileViewController alloc] init];
[self.navigationController pushViewController:vc animated:YES];
}
- (void)showAdvanced
{
AdvancedSettingsTableViewController *vc = [[AdvancedSettingsTableViewController alloc] init];
@ -288,7 +303,7 @@
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"PROCEED_BUTTON", @"")
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction *action) {
[self proceedToUnregistration];
[self proceedToUnregistration];
}]];
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"TXT_CANCEL_TITLE", @"")
style:UIAlertActionStyleCancel
@ -297,7 +312,8 @@
[self presentViewController:alertController animated:YES completion:nil];
}
- (void)proceedToUnregistration {
- (void)proceedToUnregistration
{
[TSAccountManager unregisterTextSecureWithSuccess:^{
[Environment resetAppData];
}
@ -316,9 +332,10 @@
object:nil];
}
- (void)socketStateDidChange {
- (void)socketStateDidChange
{
OWSAssert([NSThread isMainThread]);
[self updateTableContents];
}

@ -0,0 +1,36 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@class SignalAccount;
@class AvatarViewHelper;
@class OWSContactsManager;
@class TSThread;
@protocol AvatarViewHelperDelegate <NSObject>
- (NSString *)avatarActionSheetTitle;
- (void)avatarDidChange:(UIImage *)image;
- (UIViewController *)fromViewController;
@end
#pragma mark -
typedef void (^AvatarViewSuccessBlock)();
@interface AvatarViewHelper : NSObject
@property (nonatomic, weak) id<AvatarViewHelperDelegate> delegate;
- (void)showChangeAvatarUI;
@end
NS_ASSUME_NONNULL_END

@ -2,7 +2,7 @@
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "GroupViewHelper.h"
#import "AvatarViewHelper.h"
#import "OWSContactsManager.h"
#import "UIUtil.h"
#import <MobileCoreServices/UTCoreTypes.h>
@ -13,24 +13,23 @@
NS_ASSUME_NONNULL_BEGIN
@interface GroupViewHelper () <UIImagePickerControllerDelegate, UINavigationControllerDelegate>
@interface AvatarViewHelper () <UIImagePickerControllerDelegate, UINavigationControllerDelegate>
@end
#pragma mark -
@implementation GroupViewHelper
@implementation AvatarViewHelper
#pragma mark - Group Avatar
#pragma mark - Avatar Avatar
- (void)showChangeGroupAvatarUI
- (void)showChangeAvatarUI
{
OWSAssert([NSThread isMainThread]);
OWSAssert(self.delegate);
UIAlertController *actionSheetController =
[UIAlertController alertControllerWithTitle:NSLocalizedString(@"NEW_GROUP_ADD_PHOTO_ACTION",
@"Action Sheet title prompting the user for a group avatar")
[UIAlertController alertControllerWithTitle:self.delegate.avatarActionSheetTitle
message:nil
preferredStyle:UIAlertControllerStyleActionSheet];
UIAlertAction *dismissAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"TXT_CANCEL_TITLE", @"")
@ -119,7 +118,7 @@ NS_ASSUME_NONNULL_BEGIN
//
// See: GroupCreateActivity.java in Signal-Android.java.
UIImage *resizedAvatar = [rawAvatar resizedImageToFillPixelSize:CGSizeMake(210, 210)];
[self.delegate groupAvatarDidChange:resizedAvatar];
[self.delegate avatarDidChange:resizedAvatar];
}
[self.delegate.fromViewController dismissViewControllerAnimated:YES completion:nil];

@ -900,7 +900,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
assert(fromViewController != nil)
// Construct the "settings" view & push the "privacy settings" view.
let navigationController = UINavigationController(rootViewController:SettingsTableViewController())
let navigationController = UINavigationController(rootViewController:AppSettingsViewController())
navigationController.pushViewController(PrivacySettingsTableViewController(), animated:false)
fromViewController?.present(navigationController, animated: true, completion: nil)

@ -38,7 +38,7 @@ private class CallKitExperienceUpgradeViewController: ExperienceUpgradeViewContr
assert(fromViewController != nil)
// Construct the "settings" view & push the "privacy settings" view.
let navigationController = UINavigationController(rootViewController:SettingsTableViewController())
let navigationController = UINavigationController(rootViewController:AppSettingsViewController())
navigationController.pushViewController(PrivacySettingsTableViewController(), animated:false)
fromViewController?.present(navigationController, animated: true, completion: nil)

@ -1,34 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@class SignalAccount;
@class GroupViewHelper;
@class OWSContactsManager;
@class TSThread;
@protocol GroupViewHelperDelegate <NSObject>
- (void)groupAvatarDidChange:(UIImage *)image;
- (UIViewController *)fromViewController;
@end
#pragma mark -
typedef void (^GroupViewSuccessBlock)();
@interface GroupViewHelper : NSObject
@property (nonatomic, weak) id<GroupViewHelperDelegate> delegate;
- (void)showChangeGroupAvatarUI;
@end
NS_ASSUME_NONNULL_END

@ -4,11 +4,11 @@
#import "NewGroupViewController.h"
#import "AddToGroupViewController.h"
#import "AvatarViewHelper.h"
#import "BlockListUIUtils.h"
#import "ContactTableViewCell.h"
#import "ContactsViewHelper.h"
#import "Environment.h"
#import "GroupViewHelper.h"
#import "OWSContactsManager.h"
#import "OWSTableViewController.h"
#import "Signal-Swift.h"
@ -31,14 +31,14 @@ const NSUInteger kNewGroupViewControllerAvatarWidth = 68;
@interface NewGroupViewController () <UIImagePickerControllerDelegate,
UITextFieldDelegate,
ContactsViewHelperDelegate,
GroupViewHelperDelegate,
AvatarViewHelperDelegate,
AddToGroupViewControllerDelegate,
OWSTableViewControllerDelegate,
UINavigationControllerDelegate>
@property (nonatomic, readonly) OWSMessageSender *messageSender;
@property (nonatomic, readonly) ContactsViewHelper *contactsViewHelper;
@property (nonatomic, readonly) GroupViewHelper *groupViewHelper;
@property (nonatomic, readonly) AvatarViewHelper *avatarViewHelper;
@property (nonatomic, readonly) OWSTableViewController *tableViewController;
@property (nonatomic, readonly) AvatarImageView *avatarView;
@ -84,8 +84,8 @@ const NSUInteger kNewGroupViewControllerAvatarWidth = 68;
{
_messageSender = [Environment getCurrent].messageSender;
_contactsViewHelper = [[ContactsViewHelper alloc] initWithDelegate:self];
_groupViewHelper = [GroupViewHelper new];
_groupViewHelper.delegate = self;
_avatarViewHelper = [AvatarViewHelper new];
_avatarViewHelper.delegate = self;
self.memberRecipientIds = [NSMutableSet new];
}
@ -181,7 +181,7 @@ const NSUInteger kNewGroupViewControllerAvatarWidth = 68;
- (void)avatarTouched:(UIGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateRecognized) {
[self showChangeGroupAvatarUI];
[self showChangeAvatarUI];
}
}
@ -520,9 +520,9 @@ const NSUInteger kNewGroupViewControllerAvatarWidth = 68;
#pragma mark - Group Avatar
- (void)showChangeGroupAvatarUI
- (void)showChangeAvatarUI
{
[self.groupViewHelper showChangeGroupAvatarUI];
[self.avatarViewHelper showChangeAvatarUI];
}
- (void)setGroupAvatar:(nullable UIImage *)groupAvatar
@ -606,9 +606,15 @@ const NSUInteger kNewGroupViewControllerAvatarWidth = 68;
return YES;
}
#pragma mark - GroupViewHelperDelegate
#pragma mark - AvatarViewHelperDelegate
- (void)groupAvatarDidChange:(UIImage *)image
- (NSString *)avatarActionSheetTitle
{
return NSLocalizedString(
@"NEW_GROUP_ADD_PHOTO_ACTION", @"Action Sheet title prompting the user for a group avatar");
}
- (void)avatarDidChange:(UIImage *)image
{
OWSAssert(image);

@ -5,7 +5,6 @@
#import "OWSLinkDeviceViewController.h"
#import "OWSDeviceProvisioningURLParser.h"
#import "OWSLinkedDevicesTableViewController.h"
#import "SettingsTableViewController.h"
#import <SignalServiceKit/ECKeyPair+OWSPrivateKey.h>
#import <SignalServiceKit/OWSDeviceProvisioner.h>
#import <SignalServiceKit/OWSIdentityManager.h>

@ -0,0 +1,13 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSTableViewController.h"
NS_ASSUME_NONNULL_BEGIN
@interface ProfileViewController : OWSTableViewController
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,306 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "ProfileViewController.h"
#import "AvatarViewHelper.h"
#import "Signal-Swift.h"
#import "UIColor+OWS.h"
#import "UIFont+OWS.h"
#import "UIView+OWS.h"
#import "UIViewController+OWS.h"
#import <SignalServiceKit/OWSProfilesManager.h>
NS_ASSUME_NONNULL_BEGIN
@interface ProfileViewController () <UITextFieldDelegate, AvatarViewHelperDelegate>
@property (nonatomic, readonly) AvatarViewHelper *avatarViewHelper;
@property (nonatomic) UITextField *nameTextField;
@property (nonatomic) AvatarImageView *avatarView;
@property (nonatomic) UILabel *avatarLabel;
@property (nonatomic, nullable) UIImage *avatar;
@property (nonatomic) BOOL hasUnsavedChanges;
@end
#pragma mark -
@implementation ProfileViewController
- (void)loadView
{
[super loadView];
self.view.backgroundColor = [UIColor whiteColor];
[self.navigationController.navigationBar setTranslucent:NO];
self.title = NSLocalizedString(@"PROFILE_VIEW_TITLE", @"Title for the profile view.");
self.navigationItem.leftBarButtonItem =
[self createOWSBackButtonWithTarget:self selector:@selector(backButtonPressed:)];
_avatarViewHelper = [AvatarViewHelper new];
_avatarViewHelper.delegate = self;
_avatar = [OWSProfilesManager.sharedManager localProfileAvatarImage];
[self createViews];
}
- (void)createViews
{
_nameTextField = [UITextField new];
_nameTextField.font = [UIFont ows_mediumFontWithSize:18.f];
_nameTextField.textColor = [UIColor ows_materialBlueColor];
_nameTextField.placeholder = NSLocalizedString(
@"PROFILE_VIEW_NAME_DEFAULT_TEXT", @"Default text for the profile name field of the profile view.");
_nameTextField.delegate = self;
_nameTextField.text = [OWSProfilesManager.sharedManager localProfileName];
[_nameTextField addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];
_avatarView = [AvatarImageView new];
_avatarLabel = [UILabel new];
_avatarLabel.font = [UIFont ows_regularFontWithSize:14.f];
_avatarLabel.textColor = [UIColor ows_materialBlueColor];
// TODO: Copy.
_avatarLabel.text
= NSLocalizedString(@"PROFILE_VIEW_AVATAR_INSTRUCTIONS", @"Instructions for how to change the profile avatar.");
[_avatarLabel sizeToFit];
[self updateTableContents];
}
#pragma mark - Table Contents
- (void)updateTableContents
{
OWSTableContents *contents = [OWSTableContents new];
__weak ProfileViewController *weakSelf = self;
// Profile Avatar
OWSTableSection *avatarSection = [OWSTableSection new];
avatarSection.headerTitle = NSLocalizedString(
@"PROFILE_VIEW_AVATAR_SECTION_HEADER", @"Header title for the profile avatar field of the profile view.");
const CGFloat kAvatarSizePoints = 100.f;
const CGFloat kAvatarTopMargin = 10.f;
const CGFloat kAvatarBottomMargin = 10.f;
const CGFloat kAvatarVSpacing = 10.f;
CGFloat avatarCellHeight
= round(kAvatarSizePoints + kAvatarTopMargin + kAvatarBottomMargin + kAvatarVSpacing + self.avatarLabel.height);
[avatarSection addItem:[OWSTableItem itemWithCustomCellBlock:^{
UITableViewCell *cell = [UITableViewCell new];
cell.preservesSuperviewLayoutMargins = YES;
cell.contentView.preservesSuperviewLayoutMargins = YES;
AvatarImageView *avatarView = weakSelf.avatarView;
[weakSelf updateAvatarView];
[cell.contentView addSubview:avatarView];
[avatarView autoHCenterInSuperview];
[avatarView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:kAvatarTopMargin];
[avatarView autoSetDimension:ALDimensionWidth toSize:kAvatarSizePoints];
[avatarView autoSetDimension:ALDimensionHeight toSize:kAvatarSizePoints];
UILabel *avatarLabel = weakSelf.avatarLabel;
[cell.contentView addSubview:avatarLabel];
[avatarLabel autoHCenterInSuperview];
[avatarLabel autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:kAvatarBottomMargin];
cell.userInteractionEnabled = YES;
[cell
addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(avatarTapped:)]];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
customRowHeight:avatarCellHeight
actionBlock:nil]];
[contents addSection:avatarSection];
// Profile Name
OWSTableSection *nameSection = [OWSTableSection new];
nameSection.headerTitle = NSLocalizedString(
@"PROFILE_VIEW_NAME_SECTION_HEADER", @"Label for the profile name field of the profile view.");
[nameSection
addItem:
[OWSTableItem
itemWithCustomCellBlock:^{
UITableViewCell *cell = [UITableViewCell new];
cell.preservesSuperviewLayoutMargins = YES;
cell.contentView.preservesSuperviewLayoutMargins = YES;
UITextField *nameTextField = weakSelf.nameTextField;
[cell.contentView addSubview:nameTextField];
[nameTextField autoPinLeadingToSuperView];
[nameTextField autoPinTrailingToSuperView];
[nameTextField autoVCenterInSuperview];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
actionBlock:nil]];
[contents addSection:nameSection];
self.contents = contents;
}
#pragma mark - Event Handling
- (void)backButtonPressed:(id)sender
{
[self.nameTextField resignFirstResponder];
if (!self.hasUnsavedChanges) {
// If user made no changes, return to conversation settings view.
[self.navigationController popViewControllerAnimated:YES];
return;
}
UIAlertController *controller = [UIAlertController
alertControllerWithTitle:
NSLocalizedString(@"NEW_GROUP_VIEW_UNSAVED_CHANGES_TITLE",
@"The alert title if user tries to exit the new group view without saving changes.")
message:
NSLocalizedString(@"NEW_GROUP_VIEW_UNSAVED_CHANGES_MESSAGE",
@"The alert message if user tries to exit the new group view without saving changes.")
preferredStyle:UIAlertControllerStyleAlert];
[controller
addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ALERT_DISCARD_BUTTON",
@"The label for the 'discard' button in alerts and action sheets.")
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction *action) {
[self.navigationController popViewControllerAnimated:YES];
}]];
[controller addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"TXT_CANCEL_TITLE", nil)
style:UIAlertActionStyleCancel
handler:nil]];
[self presentViewController:controller animated:YES completion:nil];
}
- (void)avatarTapped:(UIGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateRecognized) {
[self.avatarViewHelper showChangeAvatarUI];
}
}
- (void)setHasUnsavedChanges:(BOOL)hasUnsavedChanges
{
_hasUnsavedChanges = hasUnsavedChanges;
if (hasUnsavedChanges) {
self.navigationItem.rightBarButtonItem = (self.hasUnsavedChanges
? [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"EDIT_GROUP_UPDATE_BUTTON",
@"The title for the 'update group' button.")
style:UIBarButtonItemStylePlain
target:self
action:@selector(updatePressed)]
: nil);
}
}
- (void)updatePressed
{
[self updateProfile];
}
- (void)updateProfile
{
__weak ProfileViewController *weakSelf = self;
[OWSProfilesManager.sharedManager
updateLocalProfileName:self.nameTextField.text
localProfileAvatarImage:self.avatar
success:^{
[weakSelf.navigationController popViewControllerAnimated:YES];
}
failure:^{
// <#code#>
}];
}
#pragma mark - UITextFieldDelegate
// TODO: This logic resides in both RegistrationViewController and here.
// We should refactor it out into a utility function.
- (BOOL)textField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)insertionText
{
// TODO: Possibly filter invalid input.
// TODO: Possibly prevent user from typing overlong name.
return YES;
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
[self updateProfile];
return NO;
}
- (void)textFieldDidChange:(id)sender
{
self.hasUnsavedChanges = YES;
// TODO: Update length warning.
}
#pragma mark - Avatar
- (void)setAvatar:(nullable UIImage *)avatar
{
OWSAssert([NSThread isMainThread]);
_avatar = avatar;
self.hasUnsavedChanges = YES;
[self updateAvatarView];
}
- (void)updateAvatarView
{
self.avatarView.image = (self.avatar ?: [UIImage imageNamed:@"profile_avatar_default"]);
}
#pragma mark - AvatarViewHelperDelegate
- (NSString *)avatarActionSheetTitle
{
return NSLocalizedString(
@"PROFILE_AVATAR_ACTIONSHEET_TITLE", @"Action Sheet title prompting the user for a profile avatar");
}
- (void)avatarDidChange:(UIImage *)image
{
OWSAssert(image);
// TODO: Crop to square and possible resize.
self.avatar = image;
}
- (UIViewController *)fromViewController
{
return self;
}
#pragma mark - Logging
+ (NSString *)tag
{
return [NSString stringWithFormat:@"[%@]", self.class];
}
- (NSString *)tag
{
return self.class.tag;
}
@end
NS_ASSUME_NONNULL_END

@ -4,6 +4,7 @@
#import "SignalsViewController.h"
#import "AppDelegate.h"
#import "AppSettingsViewController.h"
#import "InboxTableViewCell.h"
#import "MessageComposeTableViewController.h"
#import "MessagesViewController.h"
@ -11,7 +12,6 @@
#import "OWSContactsManager.h"
#import "PropertyListPreferences.h"
#import "PushManager.h"
#import "SettingsTableViewController.h"
#import "Signal-Swift.h"
#import "TSAccountManager.h"
#import "TSDatabaseView.h"
@ -305,7 +305,7 @@ typedef NS_ENUM(NSInteger, CellState) { kArchiveState, kInboxState };
}
- (void)settingsButtonPressed:(id)sender {
SettingsTableViewController *vc = [SettingsTableViewController new];
AppSettingsViewController *vc = [AppSettingsViewController new];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:vc];
[self presentViewController:navigationController animated:YES completion:nil];
}

@ -4,11 +4,11 @@
#import "UpdateGroupViewController.h"
#import "AddToGroupViewController.h"
#import "AvatarViewHelper.h"
#import "BlockListUIUtils.h"
#import "ContactTableViewCell.h"
#import "ContactsViewHelper.h"
#import "Environment.h"
#import "GroupViewHelper.h"
#import "OWSContactsManager.h"
#import "OWSTableViewController.h"
#import "Signal-Swift.h"
@ -30,14 +30,14 @@ NS_ASSUME_NONNULL_BEGIN
@interface UpdateGroupViewController () <UIImagePickerControllerDelegate,
UITextFieldDelegate,
ContactsViewHelperDelegate,
GroupViewHelperDelegate,
AvatarViewHelperDelegate,
AddToGroupViewControllerDelegate,
OWSTableViewControllerDelegate,
UINavigationControllerDelegate>
@property (nonatomic, readonly) OWSMessageSender *messageSender;
@property (nonatomic, readonly) ContactsViewHelper *contactsViewHelper;
@property (nonatomic, readonly) GroupViewHelper *groupViewHelper;
@property (nonatomic, readonly) AvatarViewHelper *avatarViewHelper;
@property (nonatomic, readonly) OWSTableViewController *tableViewController;
@property (nonatomic, readonly) AvatarImageView *avatarView;
@ -83,8 +83,8 @@ NS_ASSUME_NONNULL_BEGIN
{
_messageSender = [Environment getCurrent].messageSender;
_contactsViewHelper = [[ContactsViewHelper alloc] initWithDelegate:self];
_groupViewHelper = [GroupViewHelper new];
_groupViewHelper.delegate = self;
_avatarViewHelper = [AvatarViewHelper new];
_avatarViewHelper.delegate = self;
self.memberRecipientIds = [NSMutableSet new];
}
@ -158,7 +158,7 @@ NS_ASSUME_NONNULL_BEGIN
[self.groupNameTextField becomeFirstResponder];
break;
case UpdateGroupMode_EditGroupAvatar:
[self showChangeGroupAvatarUI];
[self showChangeAvatarUI];
break;
default:
break;
@ -228,7 +228,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)avatarTouched:(UIGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateRecognized) {
[self showChangeGroupAvatarUI];
[self showChangeAvatarUI];
}
}
@ -384,11 +384,11 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - Group Avatar
- (void)showChangeGroupAvatarUI
- (void)showChangeAvatarUI
{
[self.groupNameTextField resignFirstResponder];
[self.groupViewHelper showChangeGroupAvatarUI];
[self.avatarViewHelper showChangeAvatarUI];
}
- (void)setGroupAvatar:(nullable UIImage *)groupAvatar
@ -487,9 +487,15 @@ NS_ASSUME_NONNULL_BEGIN
return YES;
}
#pragma mark - GroupViewHelperDelegate
#pragma mark - AvatarViewHelperDelegate
- (void)groupAvatarDidChange:(UIImage *)image
- (NSString *)avatarActionSheetTitle
{
return NSLocalizedString(
@"NEW_GROUP_ADD_PHOTO_ACTION", @"Action Sheet title prompting the user for a group avatar");
}
- (void)avatarDidChange:(UIImage *)image
{
OWSAssert(image);

@ -1036,6 +1036,24 @@
/* No comment provided by engineer. */
"PROCEED_BUTTON" = "Proceed";
/* Action Sheet title prompting the user for a profile avatar */
"PROFILE_AVATAR_ACTIONSHEET_TITLE" = "Set Profile Avatar";
/* Instructions for how to change the profile avatar. */
"PROFILE_VIEW_AVATAR_INSTRUCTIONS" = "Tap to Select Avatar";
/* Header title for the profile avatar field of the profile view. */
"PROFILE_VIEW_AVATAR_SECTION_HEADER" = "Avatar";
/* Default text for the profile name field of the profile view. */
"PROFILE_VIEW_NAME_DEFAULT_TEXT" = "Enter your name.";
/* Label for the profile name field of the profile view. */
"PROFILE_VIEW_NAME_SECTION_HEADER" = "Profile Name";
/* Title for the profile view. */
"PROFILE_VIEW_TITLE" = "Profile";
/* No comment provided by engineer. */
"PUSH_MANAGER_MARKREAD" = "Mark as Read";

@ -314,7 +314,7 @@ NSString *const kNSNotificationName_LocalNumberDidChange = @"kNSNotificationName
DDLogInfo(@"%@ Successfully unregistered", self.tag);
success();
// This is called from `[SettingsTableViewController proceedToUnregistration]` whose
// This is called from `[AppSettingsViewController proceedToUnregistration]` whose
// success handler calls `[Environment resetAppData]`.
// This method, after calling that success handler, fires
// `kNSNotificationName_RegistrationStateDidChange` which is only safe to fire after

@ -167,7 +167,7 @@ NS_ASSUME_NONNULL_BEGIN
dispatch_once(&onceToken, ^{
NSString *documentsPath =
[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
attachmentsFolder = [documentsPath stringByAppendingFormat:@"/Attachments"];
attachmentsFolder = [documentsPath stringByAppendingPathComponent:@"Attachments"];
BOOL isDirectory;
BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:attachmentsFolder isDirectory:&isDirectory];

@ -4,6 +4,8 @@
NS_ASSUME_NONNULL_BEGIN
extern NSString *const kNSNotificationName_LocalProfileDidChange;
// This class can be safely accessed and used from any thread.
@interface OWSProfilesManager : NSObject
@ -11,6 +13,19 @@ NS_ASSUME_NONNULL_BEGIN
+ (instancetype)sharedManager;
@property (atomic, nullable, readonly) NSString *localProfileName;
@property (atomic, nullable, readonly) UIImage *localProfileAvatarImage;
// This method is used to update the "local profile" state on the client
// and the service. Client state is only updated if service state is
// successfully updated.
- (void)updateLocalProfileName:(nullable NSString *)localProfileName
localProfileAvatarImage:(nullable UIImage *)localProfileAvatarImage
success:(void (^)())successBlock
failure:(void (^)())failureBlock;
- (void)appLaunchDidBegin;
@end
NS_ASSUME_NONNULL_END

@ -8,11 +8,79 @@
#import "TSStorageManager.h"
#import "TextSecureKitEnv.h"
#import "TSYapDatabaseObject.h"
NS_ASSUME_NONNULL_BEGIN
@class TSThread;
@interface AvatarMetadata : TSYapDatabaseObject
// This filename is relative to OWSProfilesManager.profileAvatarsDirPath.
@property (nonatomic, readonly) NSString *fileName;
@property (nonatomic, readonly) NSString *avatarUrl;
@property (nonatomic, readonly) NSString *avatarDigest;
- (instancetype)init NS_UNAVAILABLE;
@end
#pragma mark -
@implementation AvatarMetadata
+ (NSString *)collection
{
return @"AvatarMetadata";
}
- (instancetype)initWithFileName:(NSString *)fileName
avatarUrl:(NSString *)avatarUrl
avatarDigest:(NSString *)avatarDigest
{
// TODO: Local filenames for avatars are guaranteed to be unique.
self = [super initWithUniqueId:fileName];
if (!self) {
return self;
}
OWSAssert(fileName.length > 0);
OWSAssert(avatarUrl.length > 0);
OWSAssert(avatarDigest.length > 0);
_fileName = fileName;
_avatarUrl = avatarUrl;
_avatarDigest = avatarDigest;
return self;
}
#pragma mark - NSObject
- (BOOL)isEqual:(AvatarMetadata *)other
{
return ([other isKindOfClass:[AvatarMetadata class]] && [self.fileName isEqualToString:other.fileName] &&
[self.avatarUrl isEqualToString:other.avatarUrl] && [self.avatarDigest isEqualToString:other.avatarDigest]);
}
- (NSUInteger)hash
{
return self.fileName.hash ^ self.avatarUrl.hash ^ self.avatarDigest.hash;
}
@end
#pragma mark -
NSString *const kNSNotificationName_LocalProfileDidChange = @"kNSNotificationName_LocalProfileDidChange";
NSString *const kOWSProfilesManager_Collection = @"kOWSProfilesManager_Collection";
// This key is used to persist the local user's profile key.
NSString *const kOWSProfilesManager_LocalProfileKey = @"kOWSProfilesManager_LocalProfileKey";
NSString *const kOWSProfilesManager_LocalProfileSecretKey = @"kOWSProfilesManager_LocalProfileSecretKey";
NSString *const kOWSProfilesManager_LocalProfileNameKey = @"kOWSProfilesManager_LocalProfileNameKey";
NSString *const kOWSProfilesManager_LocalProfileAvatarMetadataKey
= @"kOWSProfilesManager_LocalProfileAvatarMetadataKey";
// TODO:
static const NSInteger kProfileKeyLength = 16;
@ -22,7 +90,13 @@ static const NSInteger kProfileKeyLength = 16;
@property (nonatomic, readonly) TSStorageManager *storageManager;
@property (nonatomic, readonly) OWSMessageSender *messageSender;
@property (nonatomic, readonly, nullable) NSData *localProfileKey;
@property (atomic, readonly, nullable) NSData *localProfileKey;
// These properties should only be mutated on the main thread,
// but they may be accessed on other threads.
@property (atomic, nullable) NSString *localProfileName;
@property (atomic, nullable) UIImage *localProfileAvatarImage;
@property (atomic, nullable) AvatarMetadata *localProfileAvatarMetadata;
@end
@ -72,18 +146,20 @@ static const NSInteger kProfileKeyLength = 16;
[messageSender setProfilesManager:self];
// Try to load.
_localProfileKey = [self.storageManager objectForKey:kOWSProfilesManager_LocalProfileKey
_localProfileKey = [self.storageManager objectForKey:kOWSProfilesManager_LocalProfileSecretKey
inCollection:kOWSProfilesManager_Collection];
if (!_localProfileKey) {
// Generate
_localProfileKey = [OWSProfilesManager generateLocalProfileKey];
// Persist
[self.storageManager setObject:_localProfileKey
forKey:kOWSProfilesManager_LocalProfileKey
forKey:kOWSProfilesManager_LocalProfileSecretKey
inCollection:kOWSProfilesManager_Collection];
}
OWSAssert(_localProfileKey.length == kProfileKeyLength);
[self loadLocalProfileAsync];
return self;
}
@ -100,19 +176,271 @@ static const NSInteger kProfileKeyLength = 16;
object:nil];
}
- (void)appLaunchDidBegin
{
// Do nothing; we only want to make sure this singleton is created on startup.
}
#pragma mark - Local Profile Key
+ (NSData *)generateLocalProfileKey
{
// TODO:
OWSFail(@"Profile key generation is not yet implemented.");
DDLogVerbose(@"%@ Profile key generation is not yet implemented.", self.tag);
return [SecurityUtils generateRandomBytes:kProfileKeyLength];
}
- (nullable NSData *)localProfileKey
#pragma mark - Local Profile
// This method is use to update client "local profile" state.
- (void)updateLocalProfileName:(nullable NSString *)localProfileName
localProfileAvatarImage:(nullable UIImage *)localProfileAvatarImage
localProfileAvatarMetadata:(nullable AvatarMetadata *)localProfileAvatarMetadata
{
OWSAssert(_localProfileKey.length == kProfileKeyLength);
return _localProfileKey;
OWSAssert([NSThread isMainThread]);
// The avatar image and filename should both be set, or neither should be set.
if (!localProfileAvatarMetadata && localProfileAvatarImage) {
OWSFail(@"Missing avatar metadata.");
localProfileAvatarImage = nil;
}
if (localProfileAvatarMetadata && !localProfileAvatarImage) {
OWSFail(@"Missing avatar image.");
localProfileAvatarMetadata = nil;
}
self.localProfileName = localProfileName;
self.localProfileAvatarImage = localProfileAvatarImage;
self.localProfileAvatarMetadata = localProfileAvatarMetadata;
if (localProfileName) {
[self.storageManager setObject:localProfileName
forKey:kOWSProfilesManager_LocalProfileNameKey
inCollection:kOWSProfilesManager_Collection];
} else {
[self.storageManager removeObjectForKey:kOWSProfilesManager_LocalProfileNameKey
inCollection:kOWSProfilesManager_Collection];
}
if (localProfileAvatarMetadata) {
[self.storageManager setObject:localProfileAvatarMetadata
forKey:kOWSProfilesManager_LocalProfileAvatarMetadataKey
inCollection:kOWSProfilesManager_Collection];
} else {
[self.storageManager removeObjectForKey:kOWSProfilesManager_LocalProfileAvatarMetadataKey
inCollection:kOWSProfilesManager_Collection];
}
[[NSNotificationCenter defaultCenter] postNotificationName:kNSNotificationName_LocalProfileDidChange
object:nil
userInfo:nil];
}
- (void)updateLocalProfileName:(nullable NSString *)localProfileName
localProfileAvatarImage:(nullable UIImage *)localProfileAvatarImage
success:(void (^)())successBlock
failure:(void (^)())failureBlockParameter
{
OWSAssert([NSThread isMainThread]);
OWSAssert(successBlock);
OWSAssert(failureBlockParameter);
// Ensure that the failure block is called on the main thread.
void (^failureBlock)() = ^{
dispatch_async(dispatch_get_main_queue(), ^{
failureBlockParameter();
});
};
// The final steps are to:
//
// * Try to update the service.
// * Update client state on success.
void (^tryToUpdateService)(AvatarMetadata *_Nullable) = ^(AvatarMetadata *_Nullable avatarMetadata) {
[self updateProfileOnService:localProfileName
avatarMetadata:avatarMetadata
success:^{
dispatch_async(dispatch_get_main_queue(), ^{
[self updateLocalProfileName:localProfileName
localProfileAvatarImage:localProfileAvatarImage
localProfileAvatarMetadata:avatarMetadata];
successBlock();
});
}
failure:^{
failureBlock();
}];
};
// If we have a new avatar image, we must first:
//
// * Encode it to JPEG.
// * Write it to disk.
// * Upload it to service.
if (localProfileAvatarImage) {
if (self.localProfileAvatarMetadata && self.localProfileAvatarImage == localProfileAvatarImage) {
DDLogVerbose(@"%@ Updating local profile on service with unchanged avatar.", self.tag);
// If the avatar hasn't changed, reuse the existing metadata.
tryToUpdateService(self.localProfileAvatarMetadata);
} else {
DDLogVerbose(@"%@ Updating local profile on service with new avatar.", self.tag);
[self writeAvatarToDisk:localProfileAvatarImage
success:^(NSData *data, NSString *fileName) {
[self uploadAvatarToService:data
fileName:fileName
success:^(AvatarMetadata *avatarMetadata) {
tryToUpdateService(avatarMetadata);
}
failure:^{
failureBlock();
}];
}
failure:^{
failureBlock();
}];
}
} else {
DDLogVerbose(@"%@ Updating local profile on service with no avatar.", self.tag);
tryToUpdateService(nil);
}
}
- (void)writeAvatarToDisk:(UIImage *)avatar
success:(void (^)(NSData *data, NSString *fileName))successBlock
failure:(void (^)())failureBlock
{
OWSAssert(avatar);
OWSAssert(successBlock);
OWSAssert(failureBlock);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (avatar) {
NSData *_Nullable data = UIImageJPEGRepresentation(avatar, 1.f);
OWSAssert(data);
if (data) {
NSString *fileName = [[NSUUID UUID].UUIDString stringByAppendingPathExtension:@"jpg"];
NSString *filePath = [self.profileAvatarsDirPath stringByAppendingPathComponent:fileName];
BOOL success = [data writeToFile:filePath atomically:YES];
OWSAssert(success);
if (success) {
successBlock(data, fileName);
return;
}
}
}
failureBlock();
});
}
// TODO: The exact API & encryption scheme for avatars is not yet settled.
- (void)uploadAvatarToService:(NSData *)data
fileName:(NSString *)fileName
success:(void (^)(AvatarMetadata *avatarMetadata))successBlock
failure:(void (^)())failureBlock
{
OWSAssert(data.length > 0);
OWSAssert(fileName.length > 0);
OWSAssert(successBlock);
OWSAssert(failureBlock);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// TODO:
NSString *avatarUrl = @"avatarUrl";
NSString *avatarDigest = @"digest";
AvatarMetadata *avatarMetadata =
[[AvatarMetadata alloc] initWithFileName:fileName avatarUrl:avatarUrl avatarDigest:avatarDigest];
if (YES) {
successBlock(avatarMetadata);
return;
}
failureBlock();
});
}
// TODO: The exact API & encryption scheme for profiles is not yet settled.
- (void)updateProfileOnService:(nullable NSString *)localProfileName
avatarMetadata:(nullable AvatarMetadata *)avatarMetadata
success:(void (^)())successBlock
failure:(void (^)())failureBlock
{
OWSAssert(successBlock);
OWSAssert(failureBlock);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// TODO:
if (YES) {
successBlock();
return;
}
failureBlock();
});
}
- (void)loadLocalProfileAsync
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *_Nullable localProfileName = [self.storageManager objectForKey:kOWSProfilesManager_LocalProfileNameKey
inCollection:kOWSProfilesManager_Collection];
AvatarMetadata *_Nullable localProfileAvatarMetadata =
[self.storageManager objectForKey:kOWSProfilesManager_LocalProfileAvatarMetadataKey
inCollection:kOWSProfilesManager_Collection];
UIImage *_Nullable localProfileAvatarImage = nil;
if (localProfileAvatarMetadata) {
localProfileAvatarImage = [self loadProfileAvatarWithFilename:localProfileAvatarMetadata.fileName];
if (!localProfileAvatarImage) {
localProfileAvatarMetadata = nil;
}
}
dispatch_async(dispatch_get_main_queue(), ^{
self.localProfileName = localProfileName;
self.localProfileAvatarImage = localProfileAvatarImage;
self.localProfileAvatarMetadata = localProfileAvatarMetadata;
[[NSNotificationCenter defaultCenter] postNotificationName:kNSNotificationName_LocalProfileDidChange
object:nil
userInfo:nil];
});
});
}
#pragma mark - Avatar Disk Cache
- (nullable UIImage *)loadProfileAvatarWithFilename:(NSString *)fileName
{
OWSAssert(fileName.length > 0);
NSString *filePath = [self.profileAvatarsDirPath stringByAppendingPathComponent:fileName];
UIImage *_Nullable image = [UIImage imageWithContentsOfFile:filePath];
return image;
}
- (NSString *)profileAvatarsDirPath
{
static NSString *profileAvatarsDirPath = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *documentsPath =
[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
profileAvatarsDirPath = [documentsPath stringByAppendingPathComponent:@"ProfileAvatars"];
BOOL isDirectory;
BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:profileAvatarsDirPath isDirectory:&isDirectory];
if (exists) {
OWSAssert(isDirectory);
DDLogInfo(@"Profile avatars directory already exists");
} else {
NSError *error = nil;
[[NSFileManager defaultManager] createDirectoryAtPath:profileAvatarsDirPath
withIntermediateDirectories:YES
attributes:nil
error:&error];
if (error) {
DDLogError(@"Failed to create profile avatars directory: %@", error);
}
}
});
return profileAvatarsDirPath;
}
#pragma mark - Notifications

@ -277,7 +277,7 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass";
#if TARGET_OS_IPHONE
NSURL *fileURL = [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSString *path = [fileURL path];
databasePath = [path stringByAppendingFormat:@"/%@", databaseName];
databasePath = [path stringByAppendingPathComponent:databaseName];
#elif TARGET_OS_MAC
NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier];
@ -289,7 +289,7 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass";
[fileManager createDirectoryAtURL:appDirectory withIntermediateDirectories:NO attributes:nil error:nil];
}
databasePath = [appDirectory.filePathURL.absoluteString stringByAppendingFormat:@"/%@", databaseName];
databasePath = [appDirectory.filePathURL.absoluteString stringByAppendingPathComponent:databaseName];
#endif
return databasePath;

@ -364,36 +364,41 @@ NSString *const OWSMimeTypeUnknownForTests = @"unknown/mimetype";
}
+ (NSString *)filePathForImage:(NSString *)uniqueId ofMIMEType:(NSString *)contentType inFolder:(NSString *)folder {
return [[folder stringByAppendingFormat:@"/%@", uniqueId]
stringByAppendingPathExtension:[self getSupportedExtensionFromImageMIMEType:contentType]];
return [self filePathForData:uniqueId
withFileExtension:[self getSupportedExtensionFromImageMIMEType:contentType]
inFolder:folder];
}
+ (NSString *)filePathForVideo:(NSString *)uniqueId ofMIMEType:(NSString *)contentType inFolder:(NSString *)folder {
return [[folder stringByAppendingFormat:@"/%@", uniqueId]
stringByAppendingPathExtension:[self getSupportedExtensionFromVideoMIMEType:contentType]];
return [self filePathForData:uniqueId
withFileExtension:[self getSupportedExtensionFromVideoMIMEType:contentType]
inFolder:folder];
}
+ (NSString *)filePathForAudio:(NSString *)uniqueId ofMIMEType:(NSString *)contentType inFolder:(NSString *)folder {
return [[folder stringByAppendingFormat:@"/%@", uniqueId]
stringByAppendingPathExtension:[self getSupportedExtensionFromAudioMIMEType:contentType]];
return [self filePathForData:uniqueId
withFileExtension:[self getSupportedExtensionFromAudioMIMEType:contentType]
inFolder:folder];
}
+ (NSString *)filePathForAnimated:(NSString *)uniqueId ofMIMEType:(NSString *)contentType inFolder:(NSString *)folder {
return [[folder stringByAppendingFormat:@"/%@", uniqueId]
stringByAppendingPathExtension:[self getSupportedExtensionFromAnimatedMIMEType:contentType]];
return [self filePathForData:uniqueId
withFileExtension:[self getSupportedExtensionFromAnimatedMIMEType:contentType]
inFolder:folder];
}
+ (NSString *)filePathForBinaryData:(NSString *)uniqueId ofMIMEType:(NSString *)contentType inFolder:(NSString *)folder
{
return [[folder stringByAppendingFormat:@"/%@", uniqueId]
stringByAppendingPathExtension:[self getSupportedExtensionFromBinaryDataMIMEType:contentType]];
return [self filePathForData:uniqueId
withFileExtension:[self getSupportedExtensionFromBinaryDataMIMEType:contentType]
inFolder:folder];
}
+ (NSString *)filePathForData:(NSString *)uniqueId
withFileExtension:(NSString *)fileExtension
inFolder:(NSString *)folder
{
return [[folder stringByAppendingFormat:@"/%@", uniqueId] stringByAppendingPathExtension:fileExtension];
return [folder stringByAppendingPathComponent:[uniqueId stringByAppendingPathExtension:fileExtension]];
}
+ (nullable NSString *)utiTypeForMIMEType:(NSString *)mimeType

Loading…
Cancel
Save