From 72ea096970c1cd391d6da82ba6b6d504115a23de Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 31 Jul 2017 16:45:06 -0400 Subject: [PATCH 1/7] Sketch out the profile view. // FREEBIE --- Signal.xcodeproj/project.pbxproj | 18 +- .../Contents.json | 23 ++ .../profile_avatar_default@1x.png | Bin 0 -> 2835 bytes .../profile_avatar_default@2x.png | Bin 0 -> 5032 bytes .../profile_avatar_default@3x.png | Bin 0 -> 7785 bytes Signal/src/Signal-Bridging-Header.h | 2 +- ...ntroller.h => AppSettingsViewController.h} | 2 +- ...ntroller.m => AppSettingsViewController.m} | 39 ++- .../ViewControllers/CallViewController.swift | 2 +- ...ExperienceUpgradesPageViewController.swift | 2 +- .../OWSLinkDeviceViewController.m | 1 - .../ViewControllers/ProfileViewController.h | 28 ++ .../ViewControllers/ProfileViewController.m | 248 ++++++++++++++++++ .../ViewControllers/SignalsViewController.m | 4 +- .../translations/en.lproj/Localizable.strings | 15 ++ .../src/Account/TSAccountManager.m | 2 +- 16 files changed, 361 insertions(+), 25 deletions(-) create mode 100644 Signal/Images.xcassets/profile_avatar_default.imageset/Contents.json create mode 100644 Signal/Images.xcassets/profile_avatar_default.imageset/profile_avatar_default@1x.png create mode 100644 Signal/Images.xcassets/profile_avatar_default.imageset/profile_avatar_default@2x.png create mode 100644 Signal/Images.xcassets/profile_avatar_default.imageset/profile_avatar_default@3x.png rename Signal/src/ViewControllers/{SettingsTableViewController.h => AppSettingsViewController.h} (64%) rename Signal/src/ViewControllers/{SettingsTableViewController.m => AppSettingsViewController.m} (93%) create mode 100644 Signal/src/ViewControllers/ProfileViewController.h create mode 100644 Signal/src/ViewControllers/ProfileViewController.m diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 37a8f3f76..20f36a041 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -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,6 +76,7 @@ 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 */; }; 34D5CCB11EAE7E7F005515DB /* SelectRecipientViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5CCB01EAE7E7F005515DB /* SelectRecipientViewController.m */; }; @@ -483,8 +484,8 @@ 34B3F8651E8DF1700035BE1A /* PrivacySettingsTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PrivacySettingsTableViewController.m; sourceTree = ""; }; 34B3F8661E8DF1700035BE1A /* RegistrationViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RegistrationViewController.h; sourceTree = ""; }; 34B3F8671E8DF1700035BE1A /* RegistrationViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RegistrationViewController.m; sourceTree = ""; }; - 34B3F8681E8DF1700035BE1A /* SettingsTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingsTableViewController.h; sourceTree = ""; }; - 34B3F8691E8DF1700035BE1A /* SettingsTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsTableViewController.m; sourceTree = ""; }; + 34B3F8681E8DF1700035BE1A /* AppSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppSettingsViewController.h; sourceTree = ""; }; + 34B3F8691E8DF1700035BE1A /* AppSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppSettingsViewController.m; sourceTree = ""; }; 34B3F86A1E8DF1700035BE1A /* ShowGroupMembersViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShowGroupMembersViewController.h; sourceTree = ""; }; 34B3F86B1E8DF1700035BE1A /* ShowGroupMembersViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ShowGroupMembersViewController.m; sourceTree = ""; }; 34B3F86C1E8DF1700035BE1A /* SignalAttachment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalAttachment.swift; sourceTree = ""; }; @@ -503,6 +504,8 @@ 34CCAF371F0C0599004084F4 /* AppUpdateNag.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppUpdateNag.m; sourceTree = ""; }; 34CCAF391F0C2748004084F4 /* OWSAddToContactViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSAddToContactViewController.h; sourceTree = ""; }; 34CCAF3A1F0C2748004084F4 /* OWSAddToContactViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSAddToContactViewController.m; sourceTree = ""; }; + 34CE88E51F2FB9A10098030F /* ProfileViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ProfileViewController.h; sourceTree = ""; }; + 34CE88E61F2FB9A10098030F /* ProfileViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ProfileViewController.m; sourceTree = ""; }; 34D5CC941EA6AFAD005515DB /* OWSContactsSyncing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSContactsSyncing.h; sourceTree = ""; }; 34D5CC951EA6AFAD005515DB /* OWSContactsSyncing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactsSyncing.m; sourceTree = ""; }; 34D5CC981EA6EB79005515DB /* OWSMessageCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageCollectionViewCell.h; sourceTree = ""; }; @@ -950,6 +953,8 @@ 3472229E1EB22FFE00E53955 /* AddToGroupViewController.m */, 34B3F8361E8DF1700035BE1A /* AdvancedSettingsTableViewController.h */, 34B3F8371E8DF1700035BE1A /* AdvancedSettingsTableViewController.m */, + 34B3F8681E8DF1700035BE1A /* AppSettingsViewController.h */, + 34B3F8691E8DF1700035BE1A /* AppSettingsViewController.m */, 34B3F8381E8DF1700035BE1A /* AttachmentApprovalViewController.swift */, 34B3F8391E8DF1700035BE1A /* AttachmentSharing.h */, 34B3F83A1E8DF1700035BE1A /* AttachmentSharing.m */, @@ -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 */, diff --git a/Signal/Images.xcassets/profile_avatar_default.imageset/Contents.json b/Signal/Images.xcassets/profile_avatar_default.imageset/Contents.json new file mode 100644 index 000000000..f7587e86c --- /dev/null +++ b/Signal/Images.xcassets/profile_avatar_default.imageset/Contents.json @@ -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" + } +} \ No newline at end of file diff --git a/Signal/Images.xcassets/profile_avatar_default.imageset/profile_avatar_default@1x.png b/Signal/Images.xcassets/profile_avatar_default.imageset/profile_avatar_default@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..850526de9e3372848c8e2c0acbd4b3e5b1f0a645 GIT binary patch literal 2835 zcmb7G2{@E%8y;&NnuCUjmNCvz7_(*@V-6YHpMA@O-;6P)u?;h$nfXx)b!-*c3J0Zd zIF?Zel|p2Rq9nA8{^+DEQ%a$KhL-tVbzT4eUDx-$-+Mpz{oc>>eD6EgBsx3V0Oi!> z0002c&KB<~90Nr6dRgH!oS*3~93+^oHoF0px3|0yPJRlvB`^U1d64Lq0G!EF0|2Cd zp}Bjpyd3N?WO^u!6hQZJ9aa&QJ)(is%60c;lx0l~?E!C)*SAQ0nS`T2*)?BV?tf05Y5sLJ6gWv6w;~ z^1F7z-hLJSSCSl3_^R1T6IW})iZaKzQke9RNKp$s!e}fU3cFI!TKHQ>R#let&~OHY z$rQ?P2A?J3*jm2VHyfPM7Y}i8t-y&v6IMzR<%L6jHW0_w@(JHF5C_EsqSj-aX&g$3 z7oH|;=t>nx1Of~HZe*>}k{&{5xP_C*6j5d?5;15kd&P(FLm#n-m@MyO~CM#BtOmE!TwQY>6_j>OK@>Y-ghg{KxLCscm<1;ELD zva2@$0Azo+!&|s>BwpnaNoWQ4x?c=7_Gr^-Sp>pq;DxuJd~cs^KUVNgW8Q>a@bRzT z7N-dl(NR@b<_{IrmM|O6SYi=t7Y zU(&~R^F+Bk?Z<-lOn%txW9?`49uV3skZnZ^Y0k)LRM#*#K5(dBg{v@xj2bNJ#(3z$ zHh$C?94!(6`GFDC*%8jxK2S+wiSp1?wfoI2vr$co9rG$Vh0t=VL+`5YEkM(b2FV`O z0@7k08DKi7g;FxdUH@R?L9D8&b!|xha`3Aq2)DPdi8s4-znet-E~QWDF*dmiF>@o4 zM-&7$t+EfDV&d$GtJ3JXp2`P7h>fHs&2){P%ru2tChbF#b|&)(uB)%Z{rO z6w0OZ>E3bGM2%$E8`~m=3Nz>X;0g3*S5H%Uhq;WTQT6z;G9bUFnizyQ{-6QR)wwWu z%fII@3&pNRkotv`*U|;ghm0IiE=>^`-f@d%UDZjlHpv|d`)Hq@fF8&4menqz7EXD` zg?EJMU3~hJNr`m+3~dZTA-wb0?eeNgxKQc!9hK_Y#-^DsVm|O(7pA+Mjsxe4%I~m- zb;!>WMk8-ugLnJ>JfPRoeSW)Dos*i|g`~1rcr-s3#iwNA_shU_(_T?)1i5lfooNd( zI++9ioN29u1}nCaHH`>UnmJ5u@o?VbL8w;Z+8H@Hdbrebwc5>PKWko zjRm4%+kB!ySDddq_pqR~ar`bU`Yr2zRQ^d8n$0eh<#q-Gqx{z4AbW}c^2V}$pi|?u z{(7}qccDR@P56nrof(@a@e&6D{8VGkZJ5j}p(b_yTqBViylAanux@+ZDJZ%>RdW4O z!(`L8wm^p(yTwAV;ZU?C5k2-6STbNPCli)_>O`CM_1yeZS=hrHRh}f=wVzc<3%hj) zaBL}ZR)fa-*FrJpXxpTw+q3*9#tH03j=$3LJ`69eTN0rt7cW_4zEe@|O@?Ho#^4E$ zmxx%w61*bvv?HRVGfJWIo}`IUr;_W8!o4-Q3?3Uv;~zF>&;R*8qZ6cjQnL9SXAic2>own@ z$BNTRRjPd|n{D=8PUGlA9;PxFWw9`UuL% zF1b0(F!qjidX@|Io2O!~SxlLvxTF?LwZJ9ltlCe}37YHxv;s=Y^HoBfm&-?(PJ!PbB% zIPs4dT~uwD~}e z%eiq+)$*$3ro7t~XDt?45qlf^^#pBh{yUeLgSzVRDvd>k({=j9t(#3Z+<*I!J4aRl z^0zY|{Wb{11%5mxb^cyak}RQOa-mt^9l@m;==34c&{k=57ba_Yz3z+wza2tf|I)a$ zb~@0DYE9_yeB_Yralm(x*NgTZ-7%Lnz$RgUpZDhfKBPFyEK5!7&p6=h{=1v#pQ@de LBffGs@yLGw=+lTR literal 0 HcmV?d00001 diff --git a/Signal/Images.xcassets/profile_avatar_default.imageset/profile_avatar_default@2x.png b/Signal/Images.xcassets/profile_avatar_default.imageset/profile_avatar_default@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..22695ce7fae27c624f2bbfa2a5fb19d1452a2f4e GIT binary patch literal 5032 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 literal 0 HcmV?d00001 diff --git a/Signal/src/Signal-Bridging-Header.h b/Signal/src/Signal-Bridging-Header.h index 3b0467780..00d707a04 100644 --- a/Signal/src/Signal-Bridging-Header.h +++ b/Signal/src/Signal-Bridging-Header.h @@ -4,6 +4,7 @@ #import +#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" diff --git a/Signal/src/ViewControllers/SettingsTableViewController.h b/Signal/src/ViewControllers/AppSettingsViewController.h similarity index 64% rename from Signal/src/ViewControllers/SettingsTableViewController.h rename to Signal/src/ViewControllers/AppSettingsViewController.h index f9bb8bf3c..b2550cee6 100644 --- a/Signal/src/ViewControllers/SettingsTableViewController.h +++ b/Signal/src/ViewControllers/AppSettingsViewController.h @@ -4,6 +4,6 @@ #import "OWSTableViewController.h" -@interface SettingsTableViewController : OWSTableViewController +@interface AppSettingsViewController : OWSTableViewController @end diff --git a/Signal/src/ViewControllers/SettingsTableViewController.m b/Signal/src/ViewControllers/AppSettingsViewController.m similarity index 93% rename from Signal/src/ViewControllers/SettingsTableViewController.m rename to Signal/src/ViewControllers/AppSettingsViewController.m index 113e10ee0..5eeae8abc 100644 --- a/Signal/src/ViewControllers/SettingsTableViewController.m +++ b/Signal/src/ViewControllers/AppSettingsViewController.m @@ -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 #import -@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]; } diff --git a/Signal/src/ViewControllers/CallViewController.swift b/Signal/src/ViewControllers/CallViewController.swift index 1f1adcd73..f5541c3fe 100644 --- a/Signal/src/ViewControllers/CallViewController.swift +++ b/Signal/src/ViewControllers/CallViewController.swift @@ -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) diff --git a/Signal/src/ViewControllers/ExperienceUpgradesPageViewController.swift b/Signal/src/ViewControllers/ExperienceUpgradesPageViewController.swift index b7c7d99ac..309d38cde 100644 --- a/Signal/src/ViewControllers/ExperienceUpgradesPageViewController.swift +++ b/Signal/src/ViewControllers/ExperienceUpgradesPageViewController.swift @@ -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) diff --git a/Signal/src/ViewControllers/OWSLinkDeviceViewController.m b/Signal/src/ViewControllers/OWSLinkDeviceViewController.m index 73cd9812a..650bc6679 100644 --- a/Signal/src/ViewControllers/OWSLinkDeviceViewController.m +++ b/Signal/src/ViewControllers/OWSLinkDeviceViewController.m @@ -5,7 +5,6 @@ #import "OWSLinkDeviceViewController.h" #import "OWSDeviceProvisioningURLParser.h" #import "OWSLinkedDevicesTableViewController.h" -#import "SettingsTableViewController.h" #import #import #import diff --git a/Signal/src/ViewControllers/ProfileViewController.h b/Signal/src/ViewControllers/ProfileViewController.h new file mode 100644 index 000000000..a786eb689 --- /dev/null +++ b/Signal/src/ViewControllers/ProfileViewController.h @@ -0,0 +1,28 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import "OWSTableViewController.h" + +NS_ASSUME_NONNULL_BEGIN + +//@class ProfileViewController; +// +//@protocol ProfileViewControllerDelegate +// +//- (void)ProfileViewController:(ProfileViewController *)vc +// didSelectCountryCode:(NSString *)countryCode +// countryName:(NSString *)countryName +// callingCode:(NSString *)callingCode; +// +//@end +// +//#pragma mark - + +@interface ProfileViewController : OWSTableViewController + +//@property (nonatomic, weak) id countryCodeDelegate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/ProfileViewController.m b/Signal/src/ViewControllers/ProfileViewController.m new file mode 100644 index 000000000..4adb39ec6 --- /dev/null +++ b/Signal/src/ViewControllers/ProfileViewController.m @@ -0,0 +1,248 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import "ProfileViewController.h" +#import "Signal-Swift.h" +#import "UIColor+OWS.h" +#import "UIFont+OWS.h" +#import "UIView+OWS.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface ProfileViewController () +//< +// OWSTableViewControllerDelegate +//// , UISearchBarDelegate +//> + +//@property (nonatomic, readonly) UISearchBar *searchBar; +// +//@property (nonatomic) NSArray *countryCodes; + +@property (nonatomic) UITextField *nameTextField; + +@property (nonatomic) AvatarImageView *avatarView; + +@property (nonatomic) UILabel *avatarLabel; + +@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.countryCodes = [PhoneNumberUtil countryCodesForSearchTerm:nil]; + + // self.navigationItem.leftBarButtonItem = + // [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemStop + // target:self + // action:@selector(dismissWasPressed:)]; + + [self createViews]; +} + +- (void)createViews +{ + _nameTextField = [UITextField new]; + _nameTextField.font = [UIFont ows_mediumFontWithSize:18.f]; + // _nameTextField.textAlignment = _nameTextField.textAlignmentUnnatural; + _nameTextField.textColor = [UIColor ows_materialBlueColor]; + // TODO: Copy. + _nameTextField.placeholder = NSLocalizedString( + @"PROFILE_VIEW_NAME_DEFAULT_TEXT", @"Default text for the profile name field of the profile view."); + _nameTextField.delegate = self; + [_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; + + // 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 kAvatarHeightPoints = 100.f; + const CGFloat kAvatarTopMargin = 10.f; + const CGFloat kAvatarBottomMargin = 10.f; + const CGFloat kAvatarVSpacing = 10.f; + CGFloat avatarCellHeight = round( + kAvatarHeightPoints + kAvatarTopMargin + kAvatarBottomMargin + kAvatarVSpacing + self.avatarLabel.height); + // const CGFloat kCountryRowHeight = 50; + // const CGFloat kPhoneNumberRowHeight = 50; + // const CGFloat examplePhoneNumberRowHeight = self.examplePhoneNumberFont.lineHeight + 3.f; + // const CGFloat kButtonRowHeight = 60; + [avatarSection addItem:[OWSTableItem itemWithCustomCellBlock:^{ + // SelectRecipientViewController *strongSelf = weakSelf; + // OWSCAssert(strongSelf); + + UITableViewCell *cell = [UITableViewCell new]; + cell.preservesSuperviewLayoutMargins = YES; + cell.contentView.preservesSuperviewLayoutMargins = YES; + + // TODO: Use the current avatar. + UIImage *defaultAvatarImage = [UIImage imageNamed:@"profile_avatar_default"]; + OWSAssert(defaultAvatarImage.size.width == kAvatarHeightPoints); + OWSAssert(defaultAvatarImage.size.height == kAvatarHeightPoints); + AvatarImageView *avatarView = weakSelf.avatarView; + avatarView.image = defaultAvatarImage; + + [cell.contentView addSubview:avatarView]; + [avatarView autoHCenterInSuperview]; + [avatarView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:kAvatarTopMargin]; + + 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 + OWSTableSection *nameSection = [OWSTableSection new]; + nameSection.headerTitle = NSLocalizedString( + @"PROFILE_VIEW_NAME_SECTION_HEADER", @"Label for the profile name field of the profile view."); + // const CGFloat kCountryRowHeight = 50; + // const CGFloat kPhoneNumberRowHeight = 50; + // const CGFloat examplePhoneNumberRowHeight = self.examplePhoneNumberFont.lineHeight + 3.f; + // const CGFloat kButtonRowHeight = 60; + [nameSection + addItem: + [OWSTableItem + itemWithCustomCellBlock:^{ + // SelectRecipientViewController *strongSelf = weakSelf; + // OWSCAssert(strongSelf); + + 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; + } + // customRowHeight:kCountryRowHeight + + // kPhoneNumberRowHeight + // + examplePhoneNumberRowHeight + // + kButtonRowHeight + actionBlock:nil]]; + [contents addSection:nameSection]; + + self.contents = contents; +} + +//- (void)countryCodeWasSelected:(NSString *)countryCode +//{ +// OWSAssert(countryCode.length > 0); +// +// NSString *callingCodeSelected = [PhoneNumberUtil callingCodeFromCountryCode:countryCode]; +// NSString *countryNameSelected = [PhoneNumberUtil countryNameFromCountryCode:countryCode]; +// NSString *countryCodeSelected = countryCode; +// [self.countryCodeDelegate ProfileViewController:self +// didSelectCountryCode:countryCodeSelected +// countryName:countryNameSelected +// callingCode:callingCodeSelected]; +// [self.searchBar resignFirstResponder]; +// [self dismissViewControllerAnimated:YES completion:nil]; +//} +// +//- (void)dismissWasPressed:(id)sender { +// [self dismissViewControllerAnimated:YES completion:nil]; +//} + +- (void)avatarTapped:(UIGestureRecognizer *)sender +{ + if (sender.state == UIGestureRecognizerStateRecognized) { + } +} + +#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 +{ + return YES; + + // [ViewControllerUtils phoneNumberTextField:textField + // shouldChangeCharactersInRange:range + // replacementString:insertionText + // countryCode:_callingCode]; + // + // [self updatePhoneNumberButtonEnabling]; + // + // return NO; // inform our caller that we took care of performing the change +} + +- (BOOL)textFieldShouldReturn:(UITextField *)textField +{ + return YES; + + // [textField resignFirstResponder]; + // if ([self hasValidPhoneNumber]) { + // [self tryToSelectPhoneNumber]; + // } + // return NO; +} + +- (void)textFieldDidChange:(id)sender +{ + // [self updatePhoneNumberButtonEnabling]; +} + +#pragma mark - Logging + ++ (NSString *)tag +{ + return [NSString stringWithFormat:@"[%@]", self.class]; +} + +- (NSString *)tag +{ + return self.class.tag; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/SignalsViewController.m b/Signal/src/ViewControllers/SignalsViewController.m index 8758a42ed..6dba98273 100644 --- a/Signal/src/ViewControllers/SignalsViewController.m +++ b/Signal/src/ViewControllers/SignalsViewController.m @@ -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]; } diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 0de7d0989..af0216c73 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -1036,6 +1036,21 @@ /* No comment provided by engineer. */ "PROCEED_BUTTON" = "Proceed"; +/* 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"; diff --git a/SignalServiceKit/src/Account/TSAccountManager.m b/SignalServiceKit/src/Account/TSAccountManager.m index 894e492ff..560516067 100644 --- a/SignalServiceKit/src/Account/TSAccountManager.m +++ b/SignalServiceKit/src/Account/TSAccountManager.m @@ -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 From 873f5208c41bfca2998531c1934db961579d07af Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 31 Jul 2017 17:12:09 -0400 Subject: [PATCH 2/7] Sketch out the profile view. // FREEBIE --- Signal.xcodeproj/project.pbxproj | 12 +- Signal/src/ViewControllers/AvatarViewHelper.h | 34 ++++ .../{GroupViewHelper.m => AvatarViewHelper.m} | 14 +- Signal/src/ViewControllers/GroupViewHelper.h | 34 ---- .../ViewControllers/NewGroupViewController.m | 20 +- .../ViewControllers/ProfileViewController.h | 15 -- .../ViewControllers/ProfileViewController.m | 175 ++++++++++++------ .../UpdateGroupViewController.m | 22 +-- 8 files changed, 185 insertions(+), 141 deletions(-) create mode 100644 Signal/src/ViewControllers/AvatarViewHelper.h rename Signal/src/ViewControllers/{GroupViewHelper.m => AvatarViewHelper.m} (92%) delete mode 100644 Signal/src/ViewControllers/GroupViewHelper.h diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 20f36a041..21e816027 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -78,7 +78,7 @@ 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 */; }; @@ -510,8 +510,8 @@ 34D5CC951EA6AFAD005515DB /* OWSContactsSyncing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactsSyncing.m; sourceTree = ""; }; 34D5CC981EA6EB79005515DB /* OWSMessageCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageCollectionViewCell.h; sourceTree = ""; }; 34D5CC9B1EA6ED17005515DB /* OWSMessageMediaAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageMediaAdapter.h; sourceTree = ""; }; - 34D5CCA71EAE3D30005515DB /* GroupViewHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GroupViewHelper.h; sourceTree = ""; }; - 34D5CCA81EAE3D30005515DB /* GroupViewHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GroupViewHelper.m; sourceTree = ""; }; + 34D5CCA71EAE3D30005515DB /* AvatarViewHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AvatarViewHelper.h; sourceTree = ""; }; + 34D5CCA81EAE3D30005515DB /* AvatarViewHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AvatarViewHelper.m; sourceTree = ""; }; 34D5CCAB1EAE7136005515DB /* OWSConversationSettingsViewDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSConversationSettingsViewDelegate.h; sourceTree = ""; }; 34D5CCAF1EAE7E7F005515DB /* SelectRecipientViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SelectRecipientViewController.h; sourceTree = ""; }; 34D5CCB01EAE7E7F005515DB /* SelectRecipientViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SelectRecipientViewController.m; sourceTree = ""; }; @@ -958,6 +958,8 @@ 34B3F8381E8DF1700035BE1A /* AttachmentApprovalViewController.swift */, 34B3F8391E8DF1700035BE1A /* AttachmentSharing.h */, 34B3F83A1E8DF1700035BE1A /* AttachmentSharing.m */, + 34D5CCA71EAE3D30005515DB /* AvatarViewHelper.h */, + 34D5CCA81EAE3D30005515DB /* AvatarViewHelper.m */, 343D3D991E9283F100165CA4 /* BlockListUIUtils.h */, 343D3D9A1E9283F100165CA4 /* BlockListUIUtils.m */, 34B3F89A1E8DF3270035BE1A /* BlockListViewController.h */, @@ -982,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 */, @@ -2222,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 */, diff --git a/Signal/src/ViewControllers/AvatarViewHelper.h b/Signal/src/ViewControllers/AvatarViewHelper.h new file mode 100644 index 000000000..38dda3f92 --- /dev/null +++ b/Signal/src/ViewControllers/AvatarViewHelper.h @@ -0,0 +1,34 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class SignalAccount; +@class AvatarViewHelper; +@class OWSContactsManager; +@class TSThread; + +@protocol AvatarViewHelperDelegate + +- (void)avatarDidChange:(UIImage *)image; + +- (UIViewController *)fromViewController; + +@end + +#pragma mark - + +typedef void (^AvatarViewSuccessBlock)(); + +@interface AvatarViewHelper : NSObject + +@property (nonatomic, weak) id delegate; + +- (void)showChangeAvatarUI; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/GroupViewHelper.m b/Signal/src/ViewControllers/AvatarViewHelper.m similarity index 92% rename from Signal/src/ViewControllers/GroupViewHelper.m rename to Signal/src/ViewControllers/AvatarViewHelper.m index d980e1f70..5ea311301 100644 --- a/Signal/src/ViewControllers/GroupViewHelper.m +++ b/Signal/src/ViewControllers/AvatarViewHelper.m @@ -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 @@ -13,17 +13,17 @@ NS_ASSUME_NONNULL_BEGIN -@interface GroupViewHelper () +@interface AvatarViewHelper () @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); @@ -117,9 +117,9 @@ NS_ASSUME_NONNULL_BEGIN if (rawAvatar) { // We resize the avatar to fill a 210x210 square. // - // See: GroupCreateActivity.java in Signal-Android.java. + // See: AvatarCreateActivity.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]; diff --git a/Signal/src/ViewControllers/GroupViewHelper.h b/Signal/src/ViewControllers/GroupViewHelper.h deleted file mode 100644 index 932f6d543..000000000 --- a/Signal/src/ViewControllers/GroupViewHelper.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@class SignalAccount; -@class GroupViewHelper; -@class OWSContactsManager; -@class TSThread; - -@protocol GroupViewHelperDelegate - -- (void)groupAvatarDidChange:(UIImage *)image; - -- (UIViewController *)fromViewController; - -@end - -#pragma mark - - -typedef void (^GroupViewSuccessBlock)(); - -@interface GroupViewHelper : NSObject - -@property (nonatomic, weak) id delegate; - -- (void)showChangeGroupAvatarUI; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/NewGroupViewController.m b/Signal/src/ViewControllers/NewGroupViewController.m index ab6236dab..395a3654b 100644 --- a/Signal/src/ViewControllers/NewGroupViewController.m +++ b/Signal/src/ViewControllers/NewGroupViewController.m @@ -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 () @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,9 @@ const NSUInteger kNewGroupViewControllerAvatarWidth = 68; return YES; } -#pragma mark - GroupViewHelperDelegate +#pragma mark - AvatarViewHelperDelegate -- (void)groupAvatarDidChange:(UIImage *)image +- (void)avatarDidChange:(UIImage *)image { OWSAssert(image); diff --git a/Signal/src/ViewControllers/ProfileViewController.h b/Signal/src/ViewControllers/ProfileViewController.h index a786eb689..16471c80f 100644 --- a/Signal/src/ViewControllers/ProfileViewController.h +++ b/Signal/src/ViewControllers/ProfileViewController.h @@ -6,23 +6,8 @@ NS_ASSUME_NONNULL_BEGIN -//@class ProfileViewController; -// -//@protocol ProfileViewControllerDelegate -// -//- (void)ProfileViewController:(ProfileViewController *)vc -// didSelectCountryCode:(NSString *)countryCode -// countryName:(NSString *)countryName -// callingCode:(NSString *)callingCode; -// -//@end -// -//#pragma mark - - @interface ProfileViewController : OWSTableViewController -//@property (nonatomic, weak) id countryCodeDelegate; - @end NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/ProfileViewController.m b/Signal/src/ViewControllers/ProfileViewController.m index 4adb39ec6..12d822bd1 100644 --- a/Signal/src/ViewControllers/ProfileViewController.m +++ b/Signal/src/ViewControllers/ProfileViewController.m @@ -3,22 +3,18 @@ // #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" NS_ASSUME_NONNULL_BEGIN -@interface ProfileViewController () -//< -// OWSTableViewControllerDelegate -//// , UISearchBarDelegate -//> +@interface ProfileViewController () -//@property (nonatomic, readonly) UISearchBar *searchBar; -// -//@property (nonatomic) NSArray *countryCodes; +@property (nonatomic, readonly) AvatarViewHelper *avatarViewHelper; @property (nonatomic) UITextField *nameTextField; @@ -26,6 +22,10 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic) UILabel *avatarLabel; +@property (nonatomic, nullable) UIImage *avatar; + +@property (nonatomic) BOOL hasUnsavedChanges; + @end #pragma mark - @@ -39,13 +39,11 @@ NS_ASSUME_NONNULL_BEGIN 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:)]; - // self.countryCodes = [PhoneNumberUtil countryCodesForSearchTerm:nil]; - - // self.navigationItem.leftBarButtonItem = - // [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemStop - // target:self - // action:@selector(dismissWasPressed:)]; + _avatarViewHelper = [AvatarViewHelper new]; + _avatarViewHelper.delegate = self; [self createViews]; } @@ -54,7 +52,6 @@ NS_ASSUME_NONNULL_BEGIN { _nameTextField = [UITextField new]; _nameTextField.font = [UIFont ows_mediumFontWithSize:18.f]; - // _nameTextField.textAlignment = _nameTextField.textAlignmentUnnatural; _nameTextField.textColor = [UIColor ows_materialBlueColor]; // TODO: Copy. _nameTextField.placeholder = NSLocalizedString( @@ -87,34 +84,26 @@ NS_ASSUME_NONNULL_BEGIN 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 kAvatarHeightPoints = 100.f; + const CGFloat kAvatarSizePoints = 100.f; const CGFloat kAvatarTopMargin = 10.f; const CGFloat kAvatarBottomMargin = 10.f; const CGFloat kAvatarVSpacing = 10.f; - CGFloat avatarCellHeight = round( - kAvatarHeightPoints + kAvatarTopMargin + kAvatarBottomMargin + kAvatarVSpacing + self.avatarLabel.height); - // const CGFloat kCountryRowHeight = 50; - // const CGFloat kPhoneNumberRowHeight = 50; - // const CGFloat examplePhoneNumberRowHeight = self.examplePhoneNumberFont.lineHeight + 3.f; - // const CGFloat kButtonRowHeight = 60; + CGFloat avatarCellHeight + = round(kAvatarSizePoints + kAvatarTopMargin + kAvatarBottomMargin + kAvatarVSpacing + self.avatarLabel.height); [avatarSection addItem:[OWSTableItem itemWithCustomCellBlock:^{ - // SelectRecipientViewController *strongSelf = weakSelf; - // OWSCAssert(strongSelf); - UITableViewCell *cell = [UITableViewCell new]; cell.preservesSuperviewLayoutMargins = YES; cell.contentView.preservesSuperviewLayoutMargins = YES; // TODO: Use the current avatar. - UIImage *defaultAvatarImage = [UIImage imageNamed:@"profile_avatar_default"]; - OWSAssert(defaultAvatarImage.size.width == kAvatarHeightPoints); - OWSAssert(defaultAvatarImage.size.height == kAvatarHeightPoints); AvatarImageView *avatarView = weakSelf.avatarView; - avatarView.image = defaultAvatarImage; + [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]; @@ -136,22 +125,16 @@ NS_ASSUME_NONNULL_BEGIN OWSTableSection *nameSection = [OWSTableSection new]; nameSection.headerTitle = NSLocalizedString( @"PROFILE_VIEW_NAME_SECTION_HEADER", @"Label for the profile name field of the profile view."); - // const CGFloat kCountryRowHeight = 50; - // const CGFloat kPhoneNumberRowHeight = 50; - // const CGFloat examplePhoneNumberRowHeight = self.examplePhoneNumberFont.lineHeight + 3.f; - // const CGFloat kButtonRowHeight = 60; [nameSection addItem: [OWSTableItem itemWithCustomCellBlock:^{ - // SelectRecipientViewController *strongSelf = weakSelf; - // OWSCAssert(strongSelf); - UITableViewCell *cell = [UITableViewCell new]; cell.preservesSuperviewLayoutMargins = YES; cell.contentView.preservesSuperviewLayoutMargins = YES; UITextField *nameTextField = weakSelf.nameTextField; + // TODO: Use the current profile name. [cell.contentView addSubview:nameTextField]; [nameTextField autoPinLeadingToSuperView]; [nameTextField autoPinTrailingToSuperView]; @@ -160,41 +143,82 @@ NS_ASSUME_NONNULL_BEGIN cell.selectionStyle = UITableViewCellSelectionStyleNone; return cell; } - // customRowHeight:kCountryRowHeight + - // kPhoneNumberRowHeight - // + examplePhoneNumberRowHeight - // + kButtonRowHeight actionBlock:nil]]; [contents addSection:nameSection]; self.contents = contents; } -//- (void)countryCodeWasSelected:(NSString *)countryCode -//{ -// OWSAssert(countryCode.length > 0); -// -// NSString *callingCodeSelected = [PhoneNumberUtil callingCodeFromCountryCode:countryCode]; -// NSString *countryNameSelected = [PhoneNumberUtil countryNameFromCountryCode:countryCode]; -// NSString *countryCodeSelected = countryCode; -// [self.countryCodeDelegate ProfileViewController:self -// didSelectCountryCode:countryCodeSelected -// countryName:countryNameSelected -// callingCode:callingCodeSelected]; -// [self.searchBar resignFirstResponder]; -// [self dismissViewControllerAnimated:YES completion:nil]; -//} -// -//- (void)dismissWasPressed:(id)sender { -// [self dismissViewControllerAnimated:YES completion:nil]; -//} +#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 +{ + // OWSAssert(self.conversationSettingsViewDelegate); + // + // [self updateGroup]; + // + // [self.conversationSettingsViewDelegate popAllConversationSettingsViews]; + // [self.navigationController popViewControllerAnimated:YES]; +} + #pragma mark - UITextFieldDelegate // TODO: This logic resides in both RegistrationViewController and here. @@ -228,9 +252,44 @@ NS_ASSUME_NONNULL_BEGIN - (void)textFieldDidChange:(id)sender { + self.hasUnsavedChanges = YES; // [self updatePhoneNumberButtonEnabling]; } +#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 + +- (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 diff --git a/Signal/src/ViewControllers/UpdateGroupViewController.m b/Signal/src/ViewControllers/UpdateGroupViewController.m index 0c4d67ced..ea6f6ea15 100644 --- a/Signal/src/ViewControllers/UpdateGroupViewController.m +++ b/Signal/src/ViewControllers/UpdateGroupViewController.m @@ -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 () @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,9 @@ NS_ASSUME_NONNULL_BEGIN return YES; } -#pragma mark - GroupViewHelperDelegate +#pragma mark - AvatarViewHelperDelegate -- (void)groupAvatarDidChange:(UIImage *)image +- (void)avatarDidChange:(UIImage *)image { OWSAssert(image); From 0bd23345a15be77bfd8f835b300fad5715156497 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 31 Jul 2017 17:49:52 -0400 Subject: [PATCH 3/7] Sketch out the profile view. // FREEBIE --- .../Messages/Attachments/TSAttachmentStream.m | 2 +- .../src/Profiles/OWSProfilesManager.h | 6 ++ .../src/Profiles/OWSProfilesManager.m | 80 ++++++++++++++++++- .../src/Storage/TSStorageManager.m | 4 +- SignalServiceKit/src/Util/MIMETypeUtil.m | 27 ++++--- 5 files changed, 101 insertions(+), 18 deletions(-) diff --git a/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m b/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m index 27c5db7bb..74bc26eda 100644 --- a/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m +++ b/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m @@ -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]; diff --git a/SignalServiceKit/src/Profiles/OWSProfilesManager.h b/SignalServiceKit/src/Profiles/OWSProfilesManager.h index 139bb2728..21f4b1446 100644 --- a/SignalServiceKit/src/Profiles/OWSProfilesManager.h +++ b/SignalServiceKit/src/Profiles/OWSProfilesManager.h @@ -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,10 @@ NS_ASSUME_NONNULL_BEGIN + (instancetype)sharedManager; +- (nullable NSString *)localProfileName; + +- (nullable UIImage *)localProfileAvatarImage; + @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Profiles/OWSProfilesManager.m b/SignalServiceKit/src/Profiles/OWSProfilesManager.m index a46c7f437..720844624 100644 --- a/SignalServiceKit/src/Profiles/OWSProfilesManager.m +++ b/SignalServiceKit/src/Profiles/OWSProfilesManager.m @@ -10,9 +10,14 @@ NS_ASSUME_NONNULL_BEGIN +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_LocalProfileNameKey = @"kOWSProfilesManager_LocalProfileNameKey"; +NSString *const kOWSProfilesManager_LocalProfileAvatarFilenameKey + = @"kOWSProfilesManager_LocalProfileAvatarFilenameKey"; // TODO: static const NSInteger kProfileKeyLength = 16; @@ -22,7 +27,11 @@ 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; + +@property (atomic, nullable) NSString *localProfileName; +@property (atomic, nullable) UIImage *localProfileAvatarImage; +@property (atomic) BOOL hasLoadedLocalProfile; @end @@ -84,6 +93,8 @@ static const NSInteger kProfileKeyLength = 16; } OWSAssert(_localProfileKey.length == kProfileKeyLength); + [self loadLocalProfileAsync]; + return self; } @@ -109,10 +120,71 @@ static const NSInteger kProfileKeyLength = 16; return [SecurityUtils generateRandomBytes:kProfileKeyLength]; } -- (nullable NSData *)localProfileKey +#pragma mark - Local Profile + +- (void)loadLocalProfileAsync { - OWSAssert(_localProfileKey.length == kProfileKeyLength); - return _localProfileKey; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSString *_Nullable localProfileName = [self.storageManager objectForKey:kOWSProfilesManager_LocalProfileNameKey + inCollection:kOWSProfilesManager_Collection]; + NSString *_Nullable localProfileAvatarFilename = + [self.storageManager objectForKey:kOWSProfilesManager_LocalProfileAvatarFilenameKey + inCollection:kOWSProfilesManager_Collection]; + UIImage *_Nullable localProfileAvatar = nil; + if (localProfileAvatarFilename) { + localProfileAvatar = [self loadProfileAvatarsWithFilename:localProfileAvatarFilename]; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + self.localProfileName = localProfileName; + self.localProfileAvatarImage = localProfileAvatar; + self.hasLoadedLocalProfile = YES; + + if (localProfileAvatar || localProfileName) { + [[NSNotificationCenter defaultCenter] postNotificationName:kNSNotificationName_LocalProfileDidChange + object:nil + userInfo:nil]; + } + }); + }); +} + +#pragma mark - Avatar Disk Cache + +- (nullable UIImage *)loadProfileAvatarsWithFilename:(NSString *)filename +{ + 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 diff --git a/SignalServiceKit/src/Storage/TSStorageManager.m b/SignalServiceKit/src/Storage/TSStorageManager.m index c52489fdd..f030b36b9 100644 --- a/SignalServiceKit/src/Storage/TSStorageManager.m +++ b/SignalServiceKit/src/Storage/TSStorageManager.m @@ -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; diff --git a/SignalServiceKit/src/Util/MIMETypeUtil.m b/SignalServiceKit/src/Util/MIMETypeUtil.m index 5c77a0002..dfde27b72 100644 --- a/SignalServiceKit/src/Util/MIMETypeUtil.m +++ b/SignalServiceKit/src/Util/MIMETypeUtil.m @@ -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 From 0f3a3d190335c1f99176faecb99844a9f949ad18 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 1 Aug 2017 10:51:01 -0400 Subject: [PATCH 4/7] Sketch out profile upload. // FREEBIE --- .../src/Profiles/OWSProfilesManager.h | 8 + .../src/Profiles/OWSProfilesManager.m | 276 ++++++++++++++++-- 2 files changed, 267 insertions(+), 17 deletions(-) diff --git a/SignalServiceKit/src/Profiles/OWSProfilesManager.h b/SignalServiceKit/src/Profiles/OWSProfilesManager.h index 21f4b1446..fe937cb0c 100644 --- a/SignalServiceKit/src/Profiles/OWSProfilesManager.h +++ b/SignalServiceKit/src/Profiles/OWSProfilesManager.h @@ -17,6 +17,14 @@ extern NSString *const kNSNotificationName_LocalProfileDidChange; - (nullable 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)setLocalProfileName:(nullable NSString *)localProfileName + localProfileAvatarImage:(nullable UIImage *)localProfileAvatarImage + success:(void (^)())successBlock + failure:(void (^)())failureBlock; + @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Profiles/OWSProfilesManager.m b/SignalServiceKit/src/Profiles/OWSProfilesManager.m index 720844624..7d2c44650 100644 --- a/SignalServiceKit/src/Profiles/OWSProfilesManager.m +++ b/SignalServiceKit/src/Profiles/OWSProfilesManager.m @@ -8,16 +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_LocalProfileNameKey = @"kOWSProfilesManager_LocalProfileNameKey"; -NSString *const kOWSProfilesManager_LocalProfileAvatarFilenameKey - = @"kOWSProfilesManager_LocalProfileAvatarFilenameKey"; +NSString *const kOWSProfilesManager_LocalProfileAvatarMetadataKey + = @"kOWSProfilesManager_LocalProfileAvatarMetadataKey"; // TODO: static const NSInteger kProfileKeyLength = 16; @@ -29,9 +92,11 @@ static const NSInteger kProfileKeyLength = 16; @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) AvatarMetadata *localProfileAvatarMetadata; @property (atomic, nullable) UIImage *localProfileAvatarImage; -@property (atomic) BOOL hasLoadedLocalProfile; @end @@ -122,38 +187,215 @@ static const NSInteger kProfileKeyLength = 16; #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([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)setLocalProfileName:(nullable NSString *)localProfileName + localProfileAvatarImage:(nullable UIImage *)localProfileAvatarImage + success:(void (^)())successBlock + failure:(void (^)())failureBlock +{ + OWSAssert([NSThread isMainThread]); + OWSAssert(successBlock); + OWSAssert(failureBlock); + + // 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:^{ + [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]; - NSString *_Nullable localProfileAvatarFilename = - [self.storageManager objectForKey:kOWSProfilesManager_LocalProfileAvatarFilenameKey + AvatarMetadata *_Nullable localProfileAvatarMetadata = + [self.storageManager objectForKey:kOWSProfilesManager_LocalProfileAvatarMetadataKey inCollection:kOWSProfilesManager_Collection]; - UIImage *_Nullable localProfileAvatar = nil; - if (localProfileAvatarFilename) { - localProfileAvatar = [self loadProfileAvatarsWithFilename:localProfileAvatarFilename]; + UIImage *_Nullable localProfileAvatarImage = nil; + if (localProfileAvatarMetadata) { + localProfileAvatarImage = [self loadProfileAvatarsWithFilename:localProfileAvatarMetadata.fileName]; + if (!localProfileAvatarImage) { + localProfileAvatarMetadata = nil; + } } dispatch_async(dispatch_get_main_queue(), ^{ self.localProfileName = localProfileName; - self.localProfileAvatarImage = localProfileAvatar; - self.hasLoadedLocalProfile = YES; + self.localProfileAvatarImage = localProfileAvatarImage; + self.localProfileAvatarMetadata = localProfileAvatarMetadata; - if (localProfileAvatar || localProfileName) { - [[NSNotificationCenter defaultCenter] postNotificationName:kNSNotificationName_LocalProfileDidChange - object:nil - userInfo:nil]; - } + [[NSNotificationCenter defaultCenter] postNotificationName:kNSNotificationName_LocalProfileDidChange + object:nil + userInfo:nil]; }); }); } #pragma mark - Avatar Disk Cache -- (nullable UIImage *)loadProfileAvatarsWithFilename:(NSString *)filename +- (nullable UIImage *)loadProfileAvatarsWithFilename:(NSString *)fileName { - NSString *filePath = [self.profileAvatarsDirPath stringByAppendingPathComponent:filename]; + OWSAssert(fileName.length > 0); + + NSString *filePath = [self.profileAvatarsDirPath stringByAppendingPathComponent:fileName]; UIImage *_Nullable image = [UIImage imageWithContentsOfFile:filePath]; return image; } From c331788c0659c9ebd1ffd4886a3b36b8b1d309a3 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 1 Aug 2017 11:31:00 -0400 Subject: [PATCH 5/7] Modify the profile view to update profile manager state. // FREEBIE --- Signal/src/AppDelegate.m | 2 ++ Signal/src/ViewControllers/AvatarViewHelper.h | 2 ++ Signal/src/ViewControllers/AvatarViewHelper.m | 3 +- .../ViewControllers/NewGroupViewController.m | 6 ++++ .../ViewControllers/ProfileViewController.m | 26 +++++++++++++--- .../UpdateGroupViewController.m | 6 ++++ .../translations/en.lproj/Localizable.strings | 3 ++ .../src/Profiles/OWSProfilesManager.h | 13 ++++---- .../src/Profiles/OWSProfilesManager.m | 31 ++++++++++++------- 9 files changed, 68 insertions(+), 24 deletions(-) diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 6748639f3..fd6af93c0 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -28,6 +28,7 @@ #import #import #import +#import #import #import #import @@ -160,6 +161,7 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; DDLogInfo(@"%@ application: didFinishLaunchingWithOptions completed.", self.tag); [OWSAnalytics appLaunchDidBegin]; + [OWSProfilesManager sharedManager]; return YES; } diff --git a/Signal/src/ViewControllers/AvatarViewHelper.h b/Signal/src/ViewControllers/AvatarViewHelper.h index 38dda3f92..928164279 100644 --- a/Signal/src/ViewControllers/AvatarViewHelper.h +++ b/Signal/src/ViewControllers/AvatarViewHelper.h @@ -13,6 +13,8 @@ NS_ASSUME_NONNULL_BEGIN @protocol AvatarViewHelperDelegate +- (NSString *)avatarActionSheetTitle; + - (void)avatarDidChange:(UIImage *)image; - (UIViewController *)fromViewController; diff --git a/Signal/src/ViewControllers/AvatarViewHelper.m b/Signal/src/ViewControllers/AvatarViewHelper.m index 5ea311301..3023a310b 100644 --- a/Signal/src/ViewControllers/AvatarViewHelper.m +++ b/Signal/src/ViewControllers/AvatarViewHelper.m @@ -29,8 +29,7 @@ NS_ASSUME_NONNULL_BEGIN 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", @"") diff --git a/Signal/src/ViewControllers/NewGroupViewController.m b/Signal/src/ViewControllers/NewGroupViewController.m index 395a3654b..83eb7f09f 100644 --- a/Signal/src/ViewControllers/NewGroupViewController.m +++ b/Signal/src/ViewControllers/NewGroupViewController.m @@ -608,6 +608,12 @@ const NSUInteger kNewGroupViewControllerAvatarWidth = 68; #pragma mark - AvatarViewHelperDelegate +- (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); diff --git a/Signal/src/ViewControllers/ProfileViewController.m b/Signal/src/ViewControllers/ProfileViewController.m index 12d822bd1..f427bf128 100644 --- a/Signal/src/ViewControllers/ProfileViewController.m +++ b/Signal/src/ViewControllers/ProfileViewController.m @@ -9,6 +9,7 @@ #import "UIFont+OWS.h" #import "UIView+OWS.h" #import "UIViewController+OWS.h" +#import NS_ASSUME_NONNULL_BEGIN @@ -45,6 +46,8 @@ NS_ASSUME_NONNULL_BEGIN _avatarViewHelper = [AvatarViewHelper new]; _avatarViewHelper.delegate = self; + _avatar = [OWSProfilesManager.sharedManager localProfileAvatarImage]; + [self createViews]; } @@ -53,10 +56,12 @@ NS_ASSUME_NONNULL_BEGIN _nameTextField = [UITextField new]; _nameTextField.font = [UIFont ows_mediumFontWithSize:18.f]; _nameTextField.textColor = [UIColor ows_materialBlueColor]; - // TODO: Copy. _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]; + DDLogError(@"_nameTextField.text: %@", _nameTextField.text); + [DDLog flushLog]; [_nameTextField addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; _avatarView = [AvatarImageView new]; @@ -95,10 +100,8 @@ NS_ASSUME_NONNULL_BEGIN cell.preservesSuperviewLayoutMargins = YES; cell.contentView.preservesSuperviewLayoutMargins = YES; - // TODO: Use the current avatar. AvatarImageView *avatarView = weakSelf.avatarView; [weakSelf updateAvatarView]; - [cell.contentView addSubview:avatarView]; [avatarView autoHCenterInSuperview]; [avatarView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:kAvatarTopMargin]; @@ -134,7 +137,6 @@ NS_ASSUME_NONNULL_BEGIN cell.contentView.preservesSuperviewLayoutMargins = YES; UITextField *nameTextField = weakSelf.nameTextField; - // TODO: Use the current profile name. [cell.contentView addSubview:nameTextField]; [nameTextField autoPinLeadingToSuperView]; [nameTextField autoPinTrailingToSuperView]; @@ -211,6 +213,16 @@ NS_ASSUME_NONNULL_BEGIN - (void)updateProfile { + __weak ProfileViewController *weakSelf = self; + [OWSProfilesManager.sharedManager + updateLocalProfileName:self.nameTextField.text + localProfileAvatarImage:self.avatar + success:^{ + [weakSelf.navigationController popViewControllerAnimated:YES]; + } + failure:^{ + // <#code#> + }]; // OWSAssert(self.conversationSettingsViewDelegate); // // [self updateGroup]; @@ -276,6 +288,12 @@ NS_ASSUME_NONNULL_BEGIN #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); diff --git a/Signal/src/ViewControllers/UpdateGroupViewController.m b/Signal/src/ViewControllers/UpdateGroupViewController.m index ea6f6ea15..6b3f1eb93 100644 --- a/Signal/src/ViewControllers/UpdateGroupViewController.m +++ b/Signal/src/ViewControllers/UpdateGroupViewController.m @@ -489,6 +489,12 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - AvatarViewHelperDelegate +- (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); diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index af0216c73..01fe53dc4 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -1036,6 +1036,9 @@ /* 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"; diff --git a/SignalServiceKit/src/Profiles/OWSProfilesManager.h b/SignalServiceKit/src/Profiles/OWSProfilesManager.h index fe937cb0c..5ce661f71 100644 --- a/SignalServiceKit/src/Profiles/OWSProfilesManager.h +++ b/SignalServiceKit/src/Profiles/OWSProfilesManager.h @@ -13,17 +13,16 @@ extern NSString *const kNSNotificationName_LocalProfileDidChange; + (instancetype)sharedManager; -- (nullable NSString *)localProfileName; - -- (nullable UIImage *)localProfileAvatarImage; +@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)setLocalProfileName:(nullable NSString *)localProfileName - localProfileAvatarImage:(nullable UIImage *)localProfileAvatarImage - success:(void (^)())successBlock - failure:(void (^)())failureBlock; +- (void)updateLocalProfileName:(nullable NSString *)localProfileName + localProfileAvatarImage:(nullable UIImage *)localProfileAvatarImage + success:(void (^)())successBlock + failure:(void (^)())failureBlock; @end diff --git a/SignalServiceKit/src/Profiles/OWSProfilesManager.m b/SignalServiceKit/src/Profiles/OWSProfilesManager.m index 7d2c44650..5558b5a9a 100644 --- a/SignalServiceKit/src/Profiles/OWSProfilesManager.m +++ b/SignalServiceKit/src/Profiles/OWSProfilesManager.m @@ -95,8 +95,8 @@ static const NSInteger kProfileKeyLength = 16; // 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) AvatarMetadata *localProfileAvatarMetadata; @property (atomic, nullable) UIImage *localProfileAvatarImage; +@property (atomic, nullable) AvatarMetadata *localProfileAvatarMetadata; @end @@ -181,7 +181,7 @@ static const NSInteger kProfileKeyLength = 16; + (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]; } @@ -230,14 +230,21 @@ static const NSInteger kProfileKeyLength = 16; userInfo:nil]; } -- (void)setLocalProfileName:(nullable NSString *)localProfileName - localProfileAvatarImage:(nullable UIImage *)localProfileAvatarImage - success:(void (^)())successBlock - failure:(void (^)())failureBlock +- (void)updateLocalProfileName:(nullable NSString *)localProfileName + localProfileAvatarImage:(nullable UIImage *)localProfileAvatarImage + success:(void (^)())successBlock + failure:(void (^)())failureBlockParameter { OWSAssert([NSThread isMainThread]); OWSAssert(successBlock); - OWSAssert(failureBlock); + 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: // @@ -247,10 +254,12 @@ static const NSInteger kProfileKeyLength = 16; [self updateProfileOnService:localProfileName avatarMetadata:avatarMetadata success:^{ - [self updateLocalProfileName:localProfileName - localProfileAvatarImage:localProfileAvatarImage - localProfileAvatarMetadata:avatarMetadata]; - successBlock(); + dispatch_async(dispatch_get_main_queue(), ^{ + [self updateLocalProfileName:localProfileName + localProfileAvatarImage:localProfileAvatarImage + localProfileAvatarMetadata:avatarMetadata]; + successBlock(); + }); } failure:^{ failureBlock(); From 8a8f3d81fc2c1ff3fb90d2c39cafeb8188c4a5ad Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 1 Aug 2017 11:34:23 -0400 Subject: [PATCH 6/7] Clean up ahead of PR. // FREEBIE --- .../ViewControllers/ProfileViewController.m | 29 ++++--------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/Signal/src/ViewControllers/ProfileViewController.m b/Signal/src/ViewControllers/ProfileViewController.m index f427bf128..552931320 100644 --- a/Signal/src/ViewControllers/ProfileViewController.m +++ b/Signal/src/ViewControllers/ProfileViewController.m @@ -223,12 +223,6 @@ NS_ASSUME_NONNULL_BEGIN failure:^{ // <#code#> }]; - // OWSAssert(self.conversationSettingsViewDelegate); - // - // [self updateGroup]; - // - // [self.conversationSettingsViewDelegate popAllConversationSettingsViews]; - // [self.navigationController popViewControllerAnimated:YES]; } #pragma mark - UITextFieldDelegate @@ -239,33 +233,22 @@ NS_ASSUME_NONNULL_BEGIN shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)insertionText { + // TODO: Possibly filter invalid input. + // TODO: Possibly prevent user from typing overlong name. return YES; - - // [ViewControllerUtils phoneNumberTextField:textField - // shouldChangeCharactersInRange:range - // replacementString:insertionText - // countryCode:_callingCode]; - // - // [self updatePhoneNumberButtonEnabling]; - // - // return NO; // inform our caller that we took care of performing the change } - (BOOL)textFieldShouldReturn:(UITextField *)textField { - return YES; - - // [textField resignFirstResponder]; - // if ([self hasValidPhoneNumber]) { - // [self tryToSelectPhoneNumber]; - // } - // return NO; + [self updateProfile]; + return NO; } - (void)textFieldDidChange:(id)sender { self.hasUnsavedChanges = YES; - // [self updatePhoneNumberButtonEnabling]; + + // TODO: Update length warning. } #pragma mark - Avatar From 03a4ebc4d8d10909236c2fb6caed0d5461d834e7 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 1 Aug 2017 16:30:24 -0400 Subject: [PATCH 7/7] Respond to CR. // FREEBIE --- Signal/src/AppDelegate.m | 2 +- Signal/src/ViewControllers/AvatarViewHelper.m | 2 +- .../src/ViewControllers/ProfileViewController.m | 6 ++---- .../src/Profiles/OWSProfilesManager.h | 2 ++ .../src/Profiles/OWSProfilesManager.m | 15 ++++++++++----- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index fd6af93c0..79438d5ee 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -161,7 +161,7 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; DDLogInfo(@"%@ application: didFinishLaunchingWithOptions completed.", self.tag); [OWSAnalytics appLaunchDidBegin]; - [OWSProfilesManager sharedManager]; + [OWSProfilesManager.sharedManager appLaunchDidBegin]; return YES; } diff --git a/Signal/src/ViewControllers/AvatarViewHelper.m b/Signal/src/ViewControllers/AvatarViewHelper.m index 3023a310b..b88b4a00e 100644 --- a/Signal/src/ViewControllers/AvatarViewHelper.m +++ b/Signal/src/ViewControllers/AvatarViewHelper.m @@ -116,7 +116,7 @@ NS_ASSUME_NONNULL_BEGIN if (rawAvatar) { // We resize the avatar to fill a 210x210 square. // - // See: AvatarCreateActivity.java in Signal-Android.java. + // See: GroupCreateActivity.java in Signal-Android.java. UIImage *resizedAvatar = [rawAvatar resizedImageToFillPixelSize:CGSizeMake(210, 210)]; [self.delegate avatarDidChange:resizedAvatar]; } diff --git a/Signal/src/ViewControllers/ProfileViewController.m b/Signal/src/ViewControllers/ProfileViewController.m index 552931320..18383a749 100644 --- a/Signal/src/ViewControllers/ProfileViewController.m +++ b/Signal/src/ViewControllers/ProfileViewController.m @@ -60,8 +60,6 @@ NS_ASSUME_NONNULL_BEGIN @"PROFILE_VIEW_NAME_DEFAULT_TEXT", @"Default text for the profile name field of the profile view."); _nameTextField.delegate = self; _nameTextField.text = [OWSProfilesManager.sharedManager localProfileName]; - DDLogError(@"_nameTextField.text: %@", _nameTextField.text); - [DDLog flushLog]; [_nameTextField addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; _avatarView = [AvatarImageView new]; @@ -85,7 +83,7 @@ NS_ASSUME_NONNULL_BEGIN __weak ProfileViewController *weakSelf = self; - // Avatar + // 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."); @@ -124,7 +122,7 @@ NS_ASSUME_NONNULL_BEGIN actionBlock:nil]]; [contents addSection:avatarSection]; - // Profile + // Profile Name OWSTableSection *nameSection = [OWSTableSection new]; nameSection.headerTitle = NSLocalizedString( @"PROFILE_VIEW_NAME_SECTION_HEADER", @"Label for the profile name field of the profile view."); diff --git a/SignalServiceKit/src/Profiles/OWSProfilesManager.h b/SignalServiceKit/src/Profiles/OWSProfilesManager.h index 5ce661f71..bddbcae48 100644 --- a/SignalServiceKit/src/Profiles/OWSProfilesManager.h +++ b/SignalServiceKit/src/Profiles/OWSProfilesManager.h @@ -24,6 +24,8 @@ extern NSString *const kNSNotificationName_LocalProfileDidChange; success:(void (^)())successBlock failure:(void (^)())failureBlock; +- (void)appLaunchDidBegin; + @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Profiles/OWSProfilesManager.m b/SignalServiceKit/src/Profiles/OWSProfilesManager.m index 5558b5a9a..7eda6dd54 100644 --- a/SignalServiceKit/src/Profiles/OWSProfilesManager.m +++ b/SignalServiceKit/src/Profiles/OWSProfilesManager.m @@ -77,7 +77,7 @@ NSString *const kNSNotificationName_LocalProfileDidChange = @"kNSNotificationNam 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"; @@ -146,14 +146,14 @@ 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); @@ -176,6 +176,11 @@ 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 @@ -380,7 +385,7 @@ static const NSInteger kProfileKeyLength = 16; inCollection:kOWSProfilesManager_Collection]; UIImage *_Nullable localProfileAvatarImage = nil; if (localProfileAvatarMetadata) { - localProfileAvatarImage = [self loadProfileAvatarsWithFilename:localProfileAvatarMetadata.fileName]; + localProfileAvatarImage = [self loadProfileAvatarWithFilename:localProfileAvatarMetadata.fileName]; if (!localProfileAvatarImage) { localProfileAvatarMetadata = nil; } @@ -400,7 +405,7 @@ static const NSInteger kProfileKeyLength = 16; #pragma mark - Avatar Disk Cache -- (nullable UIImage *)loadProfileAvatarsWithFilename:(NSString *)fileName +- (nullable UIImage *)loadProfileAvatarWithFilename:(NSString *)fileName { OWSAssert(fileName.length > 0);