From de204b3d90118b57bcc4a0d9a0bea2048aa974ef Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 2 Mar 2020 10:23:34 +1100 Subject: [PATCH 1/9] Fix closed group screen title on iPhone 5s --- Signal/Signal-Info.plist | 2 +- Signal/src/Loki/View Controllers/NewClosedGroupVC.swift | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Signal/Signal-Info.plist b/Signal/Signal-Info.plist index 378a5f3ca..342d37adb 100644 --- a/Signal/Signal-Info.plist +++ b/Signal/Signal-Info.plist @@ -5,7 +5,7 @@ BuildDetails CarthageVersion - 0.33.0 + 0.34.0 OSXVersion 10.15.3 WebRTCCommit diff --git a/Signal/src/Loki/View Controllers/NewClosedGroupVC.swift b/Signal/src/Loki/View Controllers/NewClosedGroupVC.swift index 4a7008b48..3fcf7cace 100644 --- a/Signal/src/Loki/View Controllers/NewClosedGroupVC.swift +++ b/Signal/src/Loki/View Controllers/NewClosedGroupVC.swift @@ -65,7 +65,8 @@ final class NewClosedGroupVC : UIViewController, UITableViewDataSource, UITableV let titleLabel = UILabel() titleLabel.text = NSLocalizedString("New Closed Group", comment: "") titleLabel.textColor = Colors.text - titleLabel.font = .boldSystemFont(ofSize: Values.veryLargeFontSize) + let titleLabelFontSize = isSmallScreen ? Values.largeFontSize : Values.veryLargeFontSize + titleLabel.font = .boldSystemFont(ofSize: titleLabelFontSize) navigationItem.titleView = titleLabel // Set up content if !contacts.isEmpty { From ab687a9cc6b57461bcede24a17fab3c7f3b90f20 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 2 Mar 2020 16:27:33 +1100 Subject: [PATCH 2/9] Implement new conversation button redesign --- Signal.xcodeproj/project.pbxproj | 8 + .../Loki V2/Group.imageset/Contents.json | 12 + .../Loki V2/Group.imageset/Group.pdf | Bin 0 -> 30357 bytes .../Loki V2/Message.imageset/Contents.json | 12 + .../Loki V2/Message.imageset/Message.pdf | Bin 0 -> 5742 bytes .../Loki V2/Plus.imageset/Contents.json | 12 + .../Loki V2/Plus.imageset/Plus.pdf | Bin 0 -> 4018 bytes Signal/src/AppDelegate.m | 2 +- .../Components/NewConversationButtonSet.swift | 271 ++++++++++++++++++ Signal/src/Loki/Style Guide/Colors.swift | 1 + Signal/src/Loki/Style Guide/Values.swift | 3 +- .../src/Loki/Utilities/UIImage+Scaling.swift | 17 ++ Signal/src/Loki/View Controllers/HomeVC.swift | 56 +--- .../View Controllers/NewClosedGroupVC.swift | 6 +- 14 files changed, 354 insertions(+), 46 deletions(-) create mode 100644 Signal/Images.xcassets/Loki V2/Group.imageset/Contents.json create mode 100644 Signal/Images.xcassets/Loki V2/Group.imageset/Group.pdf create mode 100644 Signal/Images.xcassets/Loki V2/Message.imageset/Contents.json create mode 100644 Signal/Images.xcassets/Loki V2/Message.imageset/Message.pdf create mode 100644 Signal/Images.xcassets/Loki V2/Plus.imageset/Contents.json create mode 100644 Signal/Images.xcassets/Loki V2/Plus.imageset/Plus.pdf create mode 100644 Signal/src/Loki/Components/NewConversationButtonSet.swift create mode 100644 Signal/src/Loki/Utilities/UIImage+Scaling.swift diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 297b2117f..98b1893ba 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -573,6 +573,8 @@ B82B408E239DC00D00A248E7 /* DisplayNameVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82B408D239DC00D00A248E7 /* DisplayNameVC.swift */; }; B82B4090239DD75000A248E7 /* RestoreVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82B408F239DD75000A248E7 /* RestoreVC.swift */; }; B82B4094239DF15900A248E7 /* ConversationTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82B4093239DF15900A248E7 /* ConversationTitleView.swift */; }; + B83F2B86240C7B8F000A54AB /* NewConversationButtonSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = B83F2B85240C7B8F000A54AB /* NewConversationButtonSet.swift */; }; + B83F2B88240CB75A000A54AB /* UIImage+Scaling.swift in Sources */ = {isa = PBXBuildFile; fileRef = B83F2B87240CB75A000A54AB /* UIImage+Scaling.swift */; }; B846365B22B7418B00AF1514 /* Identicon+ObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B846365A22B7418B00AF1514 /* Identicon+ObjC.swift */; }; B84664F5235022F30083A1CD /* MentionUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B84664F4235022F30083A1CD /* MentionUtilities.swift */; }; B847570323D5698100759540 /* LokiPushNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B847570223D5698100759540 /* LokiPushNotificationManager.swift */; }; @@ -1416,6 +1418,8 @@ B82B408D239DC00D00A248E7 /* DisplayNameVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayNameVC.swift; sourceTree = ""; }; B82B408F239DD75000A248E7 /* RestoreVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestoreVC.swift; sourceTree = ""; }; B82B4093239DF15900A248E7 /* ConversationTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationTitleView.swift; sourceTree = ""; }; + B83F2B85240C7B8F000A54AB /* NewConversationButtonSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewConversationButtonSet.swift; sourceTree = ""; }; + B83F2B87240CB75A000A54AB /* UIImage+Scaling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Scaling.swift"; sourceTree = ""; }; B846365A22B7418B00AF1514 /* Identicon+ObjC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Identicon+ObjC.swift"; sourceTree = ""; }; B84664F4235022F30083A1CD /* MentionUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionUtilities.swift; sourceTree = ""; }; B847570023D568EB00759540 /* SignalServiceKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SignalServiceKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -2786,6 +2790,7 @@ B8162F0422892C5F00D46544 /* FriendRequestViewDelegate.swift */, B8B26C8E234D629C004ED98C /* MentionCandidateSelectionView.swift */, B8B26C90234D8CBD004ED98C /* MentionCandidateSelectionViewDelegate.swift */, + B83F2B85240C7B8F000A54AB /* NewConversationButtonSet.swift */, B8BB82B02390C37000BA5194 /* SearchBar.swift */, B85357BE23A1AE0800AAF6CD /* SeedReminderView.swift */, B85357C023A1B81900AAF6CD /* SeedReminderViewDelegate.swift */, @@ -2807,6 +2812,7 @@ B84664F4235022F30083A1CD /* MentionUtilities.swift */, B886B4A82398BA1500211ABE /* QRCode.swift */, B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */, + B83F2B87240CB75A000A54AB /* UIImage+Scaling.swift */, ); path = Utilities; sourceTree = ""; @@ -3846,6 +3852,7 @@ 340FC8B8204DAC8D007AEB0F /* AddToGroupViewController.m in Sources */, 341F2C0F1F2B8AE700D07D6B /* DebugUIMisc.m in Sources */, B893063F2383961A005EAA8E /* ScanQRCodeWrapperVC.swift in Sources */, + B83F2B86240C7B8F000A54AB /* NewConversationButtonSet.swift in Sources */, 340FC8AF204DAC8D007AEB0F /* OWSLinkDeviceViewController.m in Sources */, 34E3EF0D1EFC235B007F6822 /* DebugUIDiskUsage.m in Sources */, 454A84042059C787008B8C75 /* MediaTileViewController.swift in Sources */, @@ -3883,6 +3890,7 @@ B885D5F4233491AB00EE0D8E /* DeviceLinkingModal.swift in Sources */, 45DF5DF21DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift in Sources */, 451166C01FD86B98000739BA /* AccountManager.swift in Sources */, + B83F2B88240CB75A000A54AB /* UIImage+Scaling.swift in Sources */, 3430FE181F7751D4000EC51B /* GiphyAPI.swift in Sources */, 4C2F454F214C00E1004871FF /* AvatarTableViewCell.swift in Sources */, 346E9D5421B040B700562252 /* RegistrationController.swift in Sources */, diff --git a/Signal/Images.xcassets/Loki V2/Group.imageset/Contents.json b/Signal/Images.xcassets/Loki V2/Group.imageset/Contents.json new file mode 100644 index 000000000..10de2f9a6 --- /dev/null +++ b/Signal/Images.xcassets/Loki V2/Group.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Group.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Signal/Images.xcassets/Loki V2/Group.imageset/Group.pdf b/Signal/Images.xcassets/Loki V2/Group.imageset/Group.pdf new file mode 100644 index 0000000000000000000000000000000000000000..be41ce92df6735b7f431981cdb1d5fbb96bb73e5 GIT binary patch literal 30357 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 literal 0 HcmV?d00001 diff --git a/Signal/Images.xcassets/Loki V2/Message.imageset/Contents.json b/Signal/Images.xcassets/Loki V2/Message.imageset/Contents.json new file mode 100644 index 000000000..22681b32e --- /dev/null +++ b/Signal/Images.xcassets/Loki V2/Message.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Message.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Signal/Images.xcassets/Loki V2/Message.imageset/Message.pdf b/Signal/Images.xcassets/Loki V2/Message.imageset/Message.pdf new file mode 100644 index 0000000000000000000000000000000000000000..46a40dafdb84ec47b20c20bac5bf85d2321aa0de GIT binary patch literal 5742 zcmbtYc{r5a`?riRgi7|9WbDjhh9=p!EH##tof*c?XpB8svSiDir6fxUk)nvkl07lW zo{%JE&GOpiJ5=vmZ{Oeb{p0ss=X##!x}W>p=bX=dpZmGb2duBIDFu_30fL(*mnLTl zmhZf7Y5^iZP!JyL3_N`r1kuB}I}jZ~NQ%f11krSICE^H_rz?htQ^(oj?QkGPMW82< zfWx=}eW`H@~}FtGJ~77;!jQ&DHr*eVGx+Ph(EHQWGi5pxiY_dIdjhxT#XxEB+@E<$8eR<`@%<#eSrGo?3WOoN4AkN+H z2MCn6J?!=X+k3*1(7liQufM>0-p>OEg6Lx$_U{CoI}s%F9b!YAC*F%-i}M7@eix|W z-HDX%o}fLq81BLS!`|NapKj78;BAd?M36bfsJbS|5(F_pW1VrfM9`iSHQit!xgWiM zF|a54Y2b&SPws2|S3g6(&-tfah>;hTxW5WbJi(1J2Ian|0fd1ds;YQjkhv5RL}@ZG zN~6r@pLL($nP9-MXJ-??4;!gt4E&DB_1`V)~s5!vHm~g$R$8xkqtEwf=7H-By zR|BBy5#tbHyA}bSQV9Q_j^0CT4@4LY@zXJT3;c=epN>((yW$B(9vEBP{^Hd<;UE}n zACm8D`{7*}{1+=fyaW3YBj+f=0#Vu9itdzs2!g2Nyq#=uhFYrsdxwS_n_uHK=K7Y5 zY6sSI^}Owq%2y*1#cFh)9v!(`k!hY>;llHHsEO;f`sud}3-vGf%m|7qR`{xju4q&)$|u_in>ydbkuNXrjBVhjvbl3Zo~V*a zw~pq58)ZG)?=QXUwtZQDYU9gso_qry$@>=+k~+H%Us07P-KjHINY+ErSrY~C=#;v-kU|g9-YMduJ^pgil9rsPG;9JwSZ@l&$RsHs`BOZ%a?~k zyxNMojXmu^(>>*?eh1PA-gRmulT;qS{jvb`D@EJjFo<^_>8}H#tzK)vb-auK z<@2%Z3@az+iu=Z8UhjO7>6J2)AZK7|c`7-K{~%Ve-8pX0chZRfK%~2OEb$#Q`6Vfa;@Bm)$`+!{m-@ZO2Av zoa?UfxQW~B&_(l@mPXc=O2*5!obKRWraMboY8g`?y(uf778l+;<^R!2fpwdsir=KU znt#wEP&6c)hIidu3;*2A`8KHscgc|FTt$Duus?^TF(7ris|!q2QR{qO@F~hlAnjbx z#vM+#)U1GS)35LLfNlMHZ8bR8i!;o*Psz+b8W(5`in!Rg5U#|$D@~FL6b}Svr75OK zCkt98Nb6YZj_C;KT32n>bS4C>3c)!&!Al%HeI;d;1L-CxaeSJ#4OO1#LChnXZOn2N;*w^jjJX)HNnY zVqF*)N|4K=nLDkfV?$Gw149lAfdkc4`rDy^7eo{4U__1y`}|CU#o;3zO=~n<9< z#R76H(!uW?@-EZkNawXPsV3J%dwSMy=}|%1Gpt`t3Fo**!8O>q=cATrs2Eb3P|pc0 zF&iky+f=^y96m+^qO`I14h^I`O@3~(-&`y=+Q7mjQX0>WU-Q)?uT(H!hjIo##NW`M z2AjHNI3~`O@XgJc_J4BRs2dvNzhbnb(B{8oyeK?NzO52_+jPuz_5p;%LA5GmcJo+y zZK^RNWZFmJLwG*^L?n?eN zfivc8FkoBlbH|dzho13p|8G;90!$Xs?&EqYf<~WKnd9vU;9N=Wys z=y~lHt}bk{OiS7-+jSO&E9=)m0Nrgs2Z4cS#h? zrzzCPOkm09eHM2n@zO`y1CcqHbS{n-?ukg#*m3LyOg~l!+LvydenF4(ldL_POvpy) zU2NZ@dnO_el)M>b58aHDYPw((Z)0AT_K0>6Gb_)>(pt~`fzRVg6_*9v9a##fWF3gD z37%;@I4->C`&@jKeIb;!!SgHj^6^?=5ML1gSiC?>ok#+s8Nel!_4o~tkgL#nHXep4 zhMllOFAxSH_t8Yb2sXba3#{0q>w_xKwfTh4C9`Ws2yq$;iDguToY!_1v=mklCLK?1 zKV_#y;4)RVLYE3rsmXUcxcerk6O58ik0H7QE3&N4%j z)B|+_AzH~YY0GI7?Ip>@i7u%#X#rq#%0QY=nzeMci1qOxop1NHt7r@wlpBmf^2#pc zXpUFBPzcx7$jnW}Uc+W$qp(*$Ae#-q&yzxuDi5OzmA{m|0pC5&quO)V?@d+3*@{zd zac`l=wB+))dXm31uLyi5tuW%iIP*!-Nxn&*$?}(m2VOvJ)@2O2@5Uy_*2KmyBR)MN zPmqUYA39m>J{L>f5O){9oGO@VpGut?U!-S@?_=-FDsn4A_d=b8oiWZs&Rc!b#c|{U zvd?XDmNjDc<96h9S#^|u)u!`bjnfL#$vl}nwLIKB#yn|=79@A3duDs)Wabp2wZhSi z=?TDW(roI96*M(t{?&PVaCrj-o+!Np^mnR-Jj!%lg-#w>B{7I%y1 zndQm1oV0y^nqso z9aQ7#w##{bH#a1h6k<$bvN%RKtR?3pjihEI1x&?E=}g=zrpq=P-!^5sSlR}*MmSH{ zmUrD7`ShfuP-@J(ZZ1$h)3R)Yqvl)0joph|VuSZU=@o?&d z+05pvvjcHc-c4>zBZCS*84uHW0U8^q z8)`xYAaAUNEP6R;RJYZa7!zG4VJV{^LV`3R@}O$fFP-}4jgRCicPHwJdYanTq+kE0_t%+Np_|~VrcC9| zk4GJ}aM>>nULE?h@-g$Hd&jZ%<@T*BNPDD@mqV@hw}|Zq)S~)K)nJ??)n(4&+U$DNb#BDI zyu^7pP0*_QFKow$=nrV{yQG;h)4+=xBd=D&=16x}?{4|L_h#^UH}a)=*(3vNYBlt_ z^u1-tFK{-BXOlRI5q=MZEaq&|0MF_Eklu64Kd zMZ8y^eD8qVeYq6b7ja1~k0vg);&XTS53TD03%8k15BWt0 z7JU)f$zFZ>+AP{Er~h4l)YE)frvSZxu`j_rS;xALNh#gjy}S~;nvq%l(m|J;*md;Q zO;onel_k}d@Wq%Y&d5_2iHg+~AKzq^mX(fngrq&RT6{(*942jT8>baJl%g!<1G552 za}hgn{rokzZ&=S>ar*LP!Hx9AqkQMaM9KA9y1=PT%Pq+0YIWe`_1!F9+L!~I^U}2e zyH@WMz66f$bki5IS}DEqKe^+$V>>&MD*{A|-!4)r+38y!urDO7%-$KWA(vlsh>xgJ zgY2emJVZCrkqgNZniZPUS|wWK*p1i`|3@p$Upr#cdM3i_#J)XUOYJVszT&e*a=^AX zeRb)c$xzZ6y|b@8vitn8$H!^mUP1I|ajnv7$WG3d?MiFnms#{WIyE~^$>%cT z_L8r#oBgM;wXo%_|5DWcP1?x9;D6VvduQ`MirVj7%-==r-nsneiT@vJ#}foOi?egW z?6VC7S%9Bj4R&ZJ8R_m zlPYC?yxpD(=>JyvhrjYA;Ov1=5KImT{rdvRB9SskkUi*!3<*6+!GG@px&M?wWn?Li z`dx-V$xsmcT?U21DZu_qCWH7>4}ti@9u&EEnE6-Vla!hLAwyC^;debajN<2i$q*+| z6jtbWJtzuJiJRYLa2WKDG2k%SKlBhMz$jzb_(a&{;=91M;{VBm5vq`fQ* fg@h^s|F_EbF!m%;VrbvNNH_us1cNoun!x`6`ed+U literal 0 HcmV?d00001 diff --git a/Signal/Images.xcassets/Loki V2/Plus.imageset/Contents.json b/Signal/Images.xcassets/Loki V2/Plus.imageset/Contents.json new file mode 100644 index 000000000..e34919cc9 --- /dev/null +++ b/Signal/Images.xcassets/Loki V2/Plus.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Plus.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Signal/Images.xcassets/Loki V2/Plus.imageset/Plus.pdf b/Signal/Images.xcassets/Loki V2/Plus.imageset/Plus.pdf new file mode 100644 index 0000000000000000000000000000000000000000..7d6855b3b49d5c0db0301c027b0ab4fd9fc61632 GIT binary patch literal 4018 zcmai%c{o)4`^PPlWk@PZq@2WvFf(S5WFN+qj4jzU!wh3Bdn8Mi2P4_Clw^tQib^zO zUmp`fDl{=ODmWWI$6|r9)*K0rFb`%q(!}wJ5 zl7Z&i%TD)%oEJO5i17(Oh@fvKf!MnK&K*p!_gaK;#v zHB4L*^sMQSwufSMf+D)t4Xf-RF(lFle?Q1DvQW9SO-uFt$T54`ZgDBOB#V}P57f)j zMCMW13y1vIG}J8fSkLcit2JsJm+dry#34l{TguD}H)Tz_I|u9^_ld5gh{z6rpJK)T zTr08uGbsU(A2_K77yjU`Fj`lNsS^xsj|!bMO*Ta+JU{LlHp>R4d<#tQboyCE#)?^dX2qFr1YC)^$y_S_=8O5|iv$2_jC20}^d@-v0*D_T znG$@w{Jb3rK7jHMLD$REm$B~yFnu*;`t>s(bN`pm#@=3zW&~frhLIGd57+@vT`zYp zZ?lUyM*_fX1YI9Epz<@pZ#hoe?_sq{6VUcL5FAMBH-&X#Ic-6o>A1L|H)lTnO@D+=Xu=aorJwpO~Y6-^(n@5)Gr zOy!DN^@%(~-XYf;k7r5I^2wlF+YG69&iPk32^4co77Km-*x=yT+lDMKflPTrc2Udx!B^O)$p{-M&szj;j3ob>YYJb=awN0)H~Yb zJ603!3uRD>5X+jR^{qqI4Qc0CYCJf!=(IBI62P*ui=}4#_UFUV?)J&8?|G$p;ujX& zeOPnc6pnvT*~*FV>TZlyUC-?KxMiVMD6AQN6eEZr=+O&{3eTW=dwKcNsZQJL{5zA+ zRY`w#waDGhc`m#h_2Hb!hg9y!SeN>!Q{k336r8}3p+mIL1Ip5zB3HBB1YGaLgsDw;o#1OXD;PQl|G4zL4VC`iu2gUdD0BECCsMRcoUtx zrSjxN@+TtQo7nc<;PkxAEd7_gDFKo7MwUa^3+ z;jdWzuen}ic^Mw`0%R1Se38QzbpHA7i#)txXf1Gb(-|1+csPkG;uF7Mo_5I%p@Kb` zI>Y=2t+_7iY8#&rU~kiny}_TXwebRS|A6eZl3ZP5_0^-kqIFz$*Ea8Rz5tLM+pHmx zdo$Pual2o!JPsSJQWN7sH}||l1q$Bg6n_4OcDhzbgYQYRFzw(c7P%;g<`gciPA>L- zeM_!`%P5^2mXfS{kGLgC6tETRW!>N_IQTTdAnDQ+J4Z||F5`euyU1jW6`6*&zzyS1 z!-g^(Gb{{+r8KuV$YGAfTX*mIlm~^4A*w#-e#mp_RE`Sf5ZZ0bmQK)QoMj*CqLPTE!LW? z8j!koL{w++-sOSXnzJ=0UlU%#4xv>FwgyvX+g8OtP*%AKQUn|NF}fIClwRF!%FztF z@D*Vya*v!st|upcMovGaPEy|}SCZ^^p2?(b$a>0NNs~w;rm?0amKdJ%8WI@FF7YVA zzJ$3#Tyd_Wu3JM2r3us`YQP<8wgYl!>RZfQRb6aQ?WXIOmO1sg6wxfv22l~wbE4_U zb|sN4&#bO2de#iGqsGOW=Ml)7ZawqJ9+sB*vDcg^RUNOITVAnPB;T)5FjlHw?s&i2 z(mCI#x^KT-vVx6@_7mGDW&t|bGf+#YL#4@5u7*iYc}_tiy3r=I`}qst^@VutTTSQraVr_CU6vj- zb5)xyuUoU+>>NWnqFpB)tNRMarXQ6T%TM?>E{3RO*;S1R)f?4Q*Z9^Xwjf(@PW70; z7z*F|D_{R(^^qY%d*?sUYWtSSlbq7Q(hmkPMHAUpeKLK!`|^2sg+B?ult19ze~DHr z=AESAq(F8EyqNp)+?%vX>-o*zvm*&J{;eLZW3SXlw`;eK?>Vz42_4^&T9h&)O69Tt zBfau`()r3K$?Nzf*QFwjlNzxa2^#ww{2NL`pkHy@Y}@{uW1mMp&1{ZrVL>NAW!xXY z1t5Kp1*Zm&BKN*sexQ~n)~5Ohap-`9r0q)~Hl1&}{!yp6m3Zw0H+>E$$RW!h3zuup z1zrsd0LkO^P`a)428o>mo#^-kp)*1O`d#QYy&?2jy zeo!!2(%-vyePHZN%;ds*@Z?7Ygze4hh1cGX=*2zMiW?41R+nTC9~3_{!M_u`zqy!h zSh4ExG=L5oNvV(7N}g#yK9jI)_m%K136viBFtQ(&-f9=?h^fE`VkX(J+K=A(J#<(J zpDIqhk(w`qDDE%*lN4F=wPsL93Kv&(+zz2GO@X!`^I^Jm-K3$9=l17m^(Pq~^RXh< zXI%fw@QVRi@|M&!E1qiChvUv@LQc0y@80RvsjMl_o5tRb`oE!ZAivHH5 z^wEv4JZiYY_RUkH0K29-+Lwm;L=V8jVDwXueoq47;Dl$~_)C+!CRGg=T&kC*+jXAm zn9wNleKvgVZSI2Zi(|ys^anq}*G(w-xZt zpDW<)*lgWr%S^nL{pgE|cXs9U-j%$SC-vtS8Z2-&moDrfB|0V6DBgcaIbG?B9x9qE zm)ML`6rT?{zdM`VFbQ98tNilB(RCklg-Y@=}M__PZ~-!TU>RjS=}zzk*I2 zQC)uD@l|~34)3Ya%W)wkv(no+YmZ-8$64nNza5T!T%b$}HVmGa4I9ip)OSc;^VZIl zRq|S9R&}@Y8ER6W;O$$gIRRH!blRhq<70(mPM-JGsH>S8$gZfW81D&Bue4u&>RtSX zvi0p;dZ}}Ts-0R$b_iuLdOKnGaQ&T|4hvUFvyYZMD6OY(lqic1cJ82u0*p^+?V(M}I8vQwRIhsn|Addw-SZ({%Lrx!@jB1qm z>+yP8e`(IufGvtMzN__8nrq?Pxm%8_ z9Z9nb*ss{MoCM8)E8O2!0wEs6>528o&;Nm+nH2gRnw8;-zkrv?uS|^rxfoqt9h?ur z319-ODPZ?2gbC5VnD}qT_5q-02~H%Oj#nUH17n0JA{h5~NcLlpHynVTAvyUl;F*c& zjBLLFGMpLy&y2b_U!1#_^ACLY`N{2nV>$e{4;WbPJRjlVURmxIP+zEgkoSg_!#P> zB1{nmLogq3xDwJD29svojJUI2PRtPC{}1{5jDg++A{Yk1;b7SRUVyTa5<&?e0zWkb zQiV})<^_2E(qKpgqaOd%5C|k=68WbFgDEmf`7aF#XO!k&ni8|V|I`qO{}iXfF#2C{ zimLx79|EcP+nIg6aU^$w_xC~Aj1)v*{4W4C_3~mk&UBSA5Mw-vUJUzxtWk^xIHimu zDk-ZFVFUyYP9QoF@ybdkh(sh355vI}k*ZD_;Qt=-qk(*U8D4%bG(s5(2TMumVfDfP E1K~p7P5=M^ literal 0 HcmV?d00001 diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 25a8de660..64721d20c 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -884,7 +884,7 @@ static NSTimeInterval launchStartedAt; return; } - [SignalApp.sharedApp.homeViewController createPrivateChat]; + [SignalApp.sharedApp.homeViewController createNewPrivateChat]; completionHandler(YES); }]; diff --git a/Signal/src/Loki/Components/NewConversationButtonSet.swift b/Signal/src/Loki/Components/NewConversationButtonSet.swift new file mode 100644 index 000000000..39fcd0e69 --- /dev/null +++ b/Signal/src/Loki/Components/NewConversationButtonSet.swift @@ -0,0 +1,271 @@ + +final class NewConversationButtonSet : UIView { + private var isUserDragging = false + private var horizontalButtonConstraints: [NewConversationButton:NSLayoutConstraint] = [:] + private var verticalButtonConstraints: [NewConversationButton:NSLayoutConstraint] = [:] + private var expandedButton: NewConversationButton? + var delegate: NewConversationButtonSetDelegate? + + // MARK: Settings + private let spacing = Values.largeSpacing + private let iconSize = CGFloat(24) + private let maxDragDistance = CGFloat(56) + + // MARK: Components + private lazy var mainButton = NewConversationButton(isMainButton: true, icon: #imageLiteral(resourceName: "Plus").scaled(to: CGSize(width: iconSize, height: iconSize))) + private lazy var newPrivateChatButton = NewConversationButton(isMainButton: false, icon: #imageLiteral(resourceName: "Message").scaled(to: CGSize(width: iconSize, height: iconSize))) + private lazy var newClosedGroupButton = NewConversationButton(isMainButton: false, icon: #imageLiteral(resourceName: "Group").scaled(to: CGSize(width: iconSize, height: iconSize))) + private lazy var joinOpenGroupButton = NewConversationButton(isMainButton: false, icon: #imageLiteral(resourceName: "Globe").scaled(to: CGSize(width: iconSize, height: iconSize))) + + // MARK: Initialization + override init(frame: CGRect) { + super.init(frame: frame) + setUpViewHierarchy() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setUpViewHierarchy() + } + + private func setUpViewHierarchy() { + let inset = (Values.newConversationButtonExpandedSize - Values.newConversationButtonCollapsedSize) / 2 + addSubview(joinOpenGroupButton) + horizontalButtonConstraints[joinOpenGroupButton] = joinOpenGroupButton.pin(.left, to: .left, of: self, withInset: inset) + verticalButtonConstraints[joinOpenGroupButton] = joinOpenGroupButton.pin(.bottom, to: .bottom, of: self, withInset: -inset) + addSubview(newPrivateChatButton) + newPrivateChatButton.center(.horizontal, in: self) + verticalButtonConstraints[newPrivateChatButton] = newPrivateChatButton.pin(.top, to: .top, of: self, withInset: inset) + addSubview(newClosedGroupButton) + horizontalButtonConstraints[newClosedGroupButton] = newClosedGroupButton.pin(.right, to: .right, of: self, withInset: -inset) + verticalButtonConstraints[newClosedGroupButton] = newClosedGroupButton.pin(.bottom, to: .bottom, of: self, withInset: -inset) + addSubview(mainButton) + mainButton.center(.horizontal, in: self) + mainButton.pin(.bottom, to: .bottom, of: self) + let width = 3 * Values.newConversationButtonExpandedSize + 2 * spacing + set(.width, to: width) + let height = 2 * Values.newConversationButtonExpandedSize + spacing + set(.height, to: height) + collapse(withAnimation: false) + isUserInteractionEnabled = true + let mainButtonTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleMainButtonTapped)) + mainButton.addGestureRecognizer(mainButtonTapGestureRecognizer) + let joinOpenGroupButtonTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleJoinOpenGroupButtonTapped)) + joinOpenGroupButton.addGestureRecognizer(joinOpenGroupButtonTapGestureRecognizer) + let createNewPrivateChatButtonTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleCreateNewPrivateChatButtonTapped)) + newPrivateChatButton.addGestureRecognizer(createNewPrivateChatButtonTapGestureRecognizer) + let createNewClosedGroupButtonTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleCreateNewClosedGroupButtonTapped)) + newClosedGroupButton.addGestureRecognizer(createNewClosedGroupButtonTapGestureRecognizer) + } + + // MARK: Interaction + @objc private func handleMainButtonTapped() { expand(isUserDragging: false) } + @objc private func handleJoinOpenGroupButtonTapped() { delegate?.joinOpenGroup() } + @objc private func handleCreateNewPrivateChatButtonTapped() { delegate?.createNewPrivateChat() } + @objc private func handleCreateNewClosedGroupButtonTapped() { delegate?.createNewClosedGroup() } + + private func expand(isUserDragging: Bool) { + let buttons = [ joinOpenGroupButton, newPrivateChatButton, newClosedGroupButton ] + UIView.animate(withDuration: 0.25, animations: { + buttons.forEach { $0.alpha = 1 } + let inset = (Values.newConversationButtonExpandedSize - Values.newConversationButtonCollapsedSize) / 2 + let size = Values.newConversationButtonCollapsedSize + self.joinOpenGroupButton.frame = CGRect(origin: CGPoint(x: inset, y: self.height() - size - inset), size: CGSize(width: size, height: size)) + self.newPrivateChatButton.frame = CGRect(center: CGPoint(x: self.bounds.center.x, y: inset + size / 2), size: CGSize(width: size, height: size)) + self.newClosedGroupButton.frame = CGRect(origin: CGPoint(x: self.width() - size - inset, y: self.height() - size - inset), size: CGSize(width: size, height: size)) + }, completion: { _ in + self.isUserDragging = isUserDragging + }) + } + + private func collapse(withAnimation isAnimated: Bool) { + isUserDragging = false + let buttons = [ joinOpenGroupButton, newPrivateChatButton, newClosedGroupButton ] + UIView.animate(withDuration: isAnimated ? 0.25 : 0) { + buttons.forEach { button in + button.alpha = 0 + let size = Values.newConversationButtonCollapsedSize + button.frame = CGRect(center: self.mainButton.center, size: CGSize(width: size, height: size)) + } + } + } + + private func reset() { + let mainButtonLocationInSelfCoordinates = CGPoint(x: width() / 2, y: height() - Values.newConversationButtonExpandedSize / 2) + let mainButtonSize = mainButton.frame.size + UIView.animate(withDuration: 0.25) { + self.mainButton.frame = CGRect(center: mainButtonLocationInSelfCoordinates, size: mainButtonSize) + self.mainButton.alpha = 1 + } + if let expandedButton = expandedButton { collapse(expandedButton) } + expandedButton = nil + Timer.scheduledTimer(withTimeInterval: 0.25, repeats: false) { _ in + self.collapse(withAnimation: true) + } + } + + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + guard let touch = touches.first, mainButton.contains(touch), !isUserDragging else { return } + UIImpactFeedbackGenerator(style: .medium).impactOccurred() + expand(isUserDragging: true) + } + + override func touchesMoved(_ touches: Set, with event: UIEvent?) { + guard let touch = touches.first, isUserDragging else { return } + let mainButtonSize = mainButton.frame.size + let mainButtonLocationInSelfCoordinates = CGPoint(x: width() / 2, y: height() - Values.newConversationButtonExpandedSize / 2) + let touchLocationInSelfCoordinates = touch.location(in: self) + mainButton.frame = CGRect(center: touchLocationInSelfCoordinates, size: mainButtonSize) + mainButton.alpha = 1 - (touchLocationInSelfCoordinates.distance(to: mainButtonLocationInSelfCoordinates) / maxDragDistance) + let buttons = [ joinOpenGroupButton, newPrivateChatButton, newClosedGroupButton ] + let buttonToExpand = buttons.first { $0.contains(touch) } + if let buttonToExpand = buttonToExpand { + guard buttonToExpand != expandedButton else { return } + if let expandedButton = expandedButton { collapse(expandedButton) } + expand(buttonToExpand) + expandedButton = buttonToExpand + } else { + if let expandedButton = expandedButton { collapse(expandedButton) } + expandedButton = nil + } + } + + override func touchesEnded(_ touches: Set, with event: UIEvent?) { + guard let touch = touches.first, isUserDragging else { return } + if joinOpenGroupButton.contains(touch) { delegate?.joinOpenGroup() } + else if newPrivateChatButton.contains(touch) { delegate?.createNewPrivateChat() } + else if newClosedGroupButton.contains(touch) { delegate?.createNewClosedGroup() } + reset() + } + + override func touchesCancelled(_ touches: Set, with event: UIEvent?) { + guard isUserDragging else { return } + reset() + } + + private func expand(_ button: NewConversationButton) { + if let horizontalConstraint = horizontalButtonConstraints[button] { horizontalConstraint.constant = 0 } + if let verticalConstraint = verticalButtonConstraints[button] { verticalConstraint.constant = 0 } + let size = Values.newConversationButtonExpandedSize + let frame = CGRect(center: button.center, size: CGSize(width: size, height: size)) + button.widthConstraint.constant = size + button.heightConstraint.constant = size + UIView.animate(withDuration: 0.25) { + self.layoutIfNeeded() + button.frame = frame + button.layer.cornerRadius = size / 2 + button.addGlow(ofSize: size) + button.backgroundColor = Colors.accent + } + } + + private func collapse(_ button: NewConversationButton) { + let inset = (Values.newConversationButtonExpandedSize - Values.newConversationButtonCollapsedSize) / 2 + if joinOpenGroupButton == expandedButton { + horizontalButtonConstraints[joinOpenGroupButton]!.constant = inset + verticalButtonConstraints[joinOpenGroupButton]!.constant = -inset + } else if newPrivateChatButton == expandedButton { + verticalButtonConstraints[newPrivateChatButton]!.constant = inset + } else if newClosedGroupButton == expandedButton { + horizontalButtonConstraints[newClosedGroupButton]!.constant = -inset + verticalButtonConstraints[newClosedGroupButton]!.constant = -inset + } + let size = Values.newConversationButtonCollapsedSize + let frame = CGRect(center: button.center, size: CGSize(width: size, height: size)) + button.widthConstraint.constant = size + button.heightConstraint.constant = size + UIView.animate(withDuration: 0.25) { + self.layoutIfNeeded() + button.frame = frame + button.layer.cornerRadius = size / 2 + button.removeGlow() + button.backgroundColor = Colors.newConversationButtonCollapsedBackground + } + } +} + +// MARK: Delegate +protocol NewConversationButtonSetDelegate { + + func joinOpenGroup() + func createNewPrivateChat() + func createNewClosedGroup() +} + +// MARK: Button +private final class NewConversationButton : UIImageView { + private let isMainButton: Bool + private let icon: UIImage + var widthConstraint: NSLayoutConstraint! + var heightConstraint: NSLayoutConstraint! + + // Initialization + init(isMainButton: Bool, icon: UIImage) { + self.isMainButton = isMainButton + self.icon = icon + super.init(frame: CGRect.zero) + setUpViewHierarchy() + } + + override init(frame: CGRect) { + preconditionFailure("Use init(isMainButton:) instead.") + } + + required init?(coder: NSCoder) { + preconditionFailure("Use init(isMainButton:) instead.") + } + + private func setUpViewHierarchy() { + backgroundColor = isMainButton ? Colors.accent : Colors.newConversationButtonCollapsedBackground + let size = isMainButton ? Values.newConversationButtonExpandedSize : Values.newConversationButtonCollapsedSize + layer.cornerRadius = size / 2 + if isMainButton { addGlow(ofSize: size) } + layer.masksToBounds = false + image = icon + contentMode = .center + widthConstraint = set(.width, to: size) + heightConstraint = set(.height, to: size) + } + + // General + func addGlow(ofSize size: CGFloat) { + layer.shadowPath = UIBezierPath(ovalIn: CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: size, height: size))).cgPath + layer.shadowColor = Colors.newConversationButtonShadow.cgColor + layer.shadowOffset = CGSize(width: 0, height: 0.8) + layer.shadowOpacity = 1 + layer.shadowRadius = 6 + } + + func removeGlow() { + layer.shadowPath = nil + layer.shadowColor = nil + layer.shadowOffset = CGSize.zero + layer.shadowOpacity = 0 + layer.shadowRadius = 0 + } +} + +// MARK: Convenience +private extension UIView { + + func contains(_ touch: UITouch) -> Bool { + return bounds.contains(touch.location(in: self)) + } +} + +private extension CGPoint { + + func distance(to otherPoint: CGPoint) -> CGFloat { + return sqrt(pow(self.x - otherPoint.x, 2) + pow(self.y - otherPoint.y, 2)) + } +} + +private extension CGRect { + + init(center: CGPoint, size: CGSize) { + let originX = center.x - size.width / 2 + let originY = center.y - size.height / 2 + let origin = CGPoint(x: originX, y: originY) + self.init(origin: origin, size: size) + } +} diff --git a/Signal/src/Loki/Style Guide/Colors.swift b/Signal/src/Loki/Style Guide/Colors.swift index ee7d5c8e8..eba314018 100644 --- a/Signal/src/Loki/Style Guide/Colors.swift +++ b/Signal/src/Loki/Style Guide/Colors.swift @@ -35,4 +35,5 @@ final class Colors : NSObject { @objc static let composeViewTextFieldBackground = UIColor(hex: 0x141414) @objc static let receivedMessageBackground = UIColor(hex: 0x222325) @objc static let sentMessageBackground = UIColor(hex: 0x3F4146) + @objc static let newConversationButtonCollapsedBackground = UIColor(hex: 0x1F1F1F) } diff --git a/Signal/src/Loki/Style Guide/Values.swift b/Signal/src/Loki/Style Guide/Values.swift index 587f55a10..257e98d5e 100644 --- a/Signal/src/Loki/Style Guide/Values.swift +++ b/Signal/src/Loki/Style Guide/Values.swift @@ -30,7 +30,8 @@ final class Values : NSObject { @objc static let borderThickness = CGFloat(1) @objc static let conversationCellStatusIndicatorSize = CGFloat(14) @objc static let searchBarHeight = CGFloat(36) - @objc static let newConversationButtonSize = CGFloat(45) + @objc static let newConversationButtonCollapsedSize = CGFloat(48) + @objc static let newConversationButtonExpandedSize = CGFloat(60) @objc static let textFieldHeight = isSmallScreen ? CGFloat(48) : CGFloat(80) @objc static let textFieldCornerRadius = CGFloat(8) @objc static let separatorLabelHeight = CGFloat(24) diff --git a/Signal/src/Loki/Utilities/UIImage+Scaling.swift b/Signal/src/Loki/Utilities/UIImage+Scaling.swift new file mode 100644 index 000000000..2645c3d43 --- /dev/null +++ b/Signal/src/Loki/Utilities/UIImage+Scaling.swift @@ -0,0 +1,17 @@ + +extension UIImage { + + func scaled(to size: CGSize) -> UIImage { + var rect = CGRect.zero + let aspectRatio = min(size.width / self.size.width, size.height / self.size.height) + rect.size.width = self.size.width * aspectRatio + rect.size.height = self.size.height * aspectRatio + rect.origin.x = (size.width - rect.size.width) / 2 + rect.origin.y = (size.height - rect.size.height) / 2 + UIGraphicsBeginImageContextWithOptions(size, false, 0) + draw(in: rect) + let result = UIGraphicsGetImageFromCurrentImageContext()! + UIGraphicsEndImageContext() + return result + } +} diff --git a/Signal/src/Loki/View Controllers/HomeVC.swift b/Signal/src/Loki/View Controllers/HomeVC.swift index 554f65498..c9a40faef 100644 --- a/Signal/src/Loki/View Controllers/HomeVC.swift +++ b/Signal/src/Loki/View Controllers/HomeVC.swift @@ -1,5 +1,5 @@ -final class HomeVC : UIViewController, UITableViewDataSource, UITableViewDelegate, UIScrollViewDelegate, UIViewControllerPreviewingDelegate, SeedReminderViewDelegate { +final class HomeVC : UIViewController, UITableViewDataSource, UITableViewDelegate, UIScrollViewDelegate, UIViewControllerPreviewingDelegate, NewConversationButtonSetDelegate, SeedReminderViewDelegate { private var threadViewModelCache: [String:ThreadViewModel] = [:] private var isObservingDatabase = true private var isViewVisible = false { didSet { updateIsObservingDatabase() } } @@ -45,23 +45,9 @@ final class HomeVC : UIViewController, UITableViewDataSource, UITableViewDelegat return result }() - private lazy var newConversationButton: UIButton = { - let result = UIButton() - result.setTitle("+", for: UIControl.State.normal) - result.titleLabel!.font = .systemFont(ofSize: 35) - result.setTitleColor(UIColor(hex: 0x121212), for: UIControl.State.normal) - result.titleEdgeInsets = UIEdgeInsets(top: 0, left: 1, bottom: 4, right: 0) // Slight adjustment to make the plus exactly centered - result.backgroundColor = Colors.accent - let size = Values.newConversationButtonSize - result.layer.cornerRadius = size / 2 - result.layer.shadowPath = UIBezierPath(ovalIn: CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: size, height: size))).cgPath - result.layer.shadowColor = Colors.newConversationButtonShadow.cgColor - result.layer.shadowOffset = CGSize(width: 0, height: 0.8) - result.layer.shadowOpacity = 1 - result.layer.shadowRadius = 6 - result.layer.masksToBounds = false - result.set(.width, to: size) - result.set(.height, to: size) + private lazy var newConversationButtonSet: NewConversationButtonSet = { + let result = NewConversationButtonSet() + result.delegate = self return result }() @@ -113,11 +99,10 @@ final class HomeVC : UIViewController, UITableViewDataSource, UITableViewDelegat // tableView.tableHeaderView = searchBar // searchBar.sizeToFit() // tableView.contentOffset = CGPoint(x: 0, y: searchBar.frame.height) - // Set up new conversation button - newConversationButton.addTarget(self, action: #selector(createPrivateChat), for: UIControl.Event.touchUpInside) - view.addSubview(newConversationButton) - newConversationButton.center(.horizontal, in: view) - newConversationButton.pin(.bottom, to: .bottom, of: view, withInset: -Values.newConversationButtonBottomOffset) // Negative due to how the constraint is set up + // Set up new conversation button set + view.addSubview(newConversationButtonSet) + newConversationButtonSet.center(.horizontal, in: view) + newConversationButtonSet.pin(.bottom, to: .bottom, of: view, withInset: -Values.newConversationButtonBottomOffset) // Negative due to how the constraint is set up // Set up previewing if (traitCollection.forceTouchCapability == .available) { registerForPreviewing(with: self, sourceView: tableView) @@ -261,17 +246,6 @@ final class HomeVC : UIViewController, UITableViewDataSource, UITableViewDelegat let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(openSettings)) profilePictureView.addGestureRecognizer(tapGestureRecognizer) navigationItem.leftBarButtonItem = UIBarButtonItem(customView: profilePictureView) - let newClosedGroupButton = UIButton(type: .custom) - newClosedGroupButton.setImage(#imageLiteral(resourceName: "btnGroup--white"), for: UIControl.State.normal) - newClosedGroupButton.addTarget(self, action: #selector(createClosedGroup), for: UIControl.Event.touchUpInside) - newClosedGroupButton.tintColor = Colors.text - let joinPublicChatButton = UIButton(type: .custom) - joinPublicChatButton.setImage(#imageLiteral(resourceName: "Globe"), for: UIControl.State.normal) - joinPublicChatButton.addTarget(self, action: #selector(joinPublicChat), for: UIControl.Event.touchUpInside) - joinPublicChatButton.tintColor = Colors.text - let buttonStackView = UIStackView(arrangedSubviews: [ newClosedGroupButton, joinPublicChatButton ]) - buttonStackView.axis = .horizontal - navigationItem.rightBarButtonItem = UIBarButtonItem(customView: buttonStackView) } // MARK: Interaction @@ -371,21 +345,21 @@ final class HomeVC : UIViewController, UITableViewDataSource, UITableViewDelegat present(navigationController, animated: true, completion: nil) } - @objc private func joinPublicChat() { + @objc func joinOpenGroup() { let joinPublicChatVC = JoinPublicChatVC() let navigationController = OWSNavigationController(rootViewController: joinPublicChatVC) present(navigationController, animated: true, completion: nil) } - @objc private func createClosedGroup() { - let newClosedGroupVC = NewClosedGroupVC() - let navigationController = OWSNavigationController(rootViewController: newClosedGroupVC) + @objc func createNewPrivateChat() { + let newPrivateChatVC = NewPrivateChatVC() + let navigationController = OWSNavigationController(rootViewController: newPrivateChatVC) present(navigationController, animated: true, completion: nil) } - @objc func createPrivateChat() { - let newPrivateChatVC = NewPrivateChatVC() - let navigationController = OWSNavigationController(rootViewController: newPrivateChatVC) + @objc func createNewClosedGroup() { + let newClosedGroupVC = NewClosedGroupVC() + let navigationController = OWSNavigationController(rootViewController: newClosedGroupVC) present(navigationController, animated: true, completion: nil) } diff --git a/Signal/src/Loki/View Controllers/NewClosedGroupVC.swift b/Signal/src/Loki/View Controllers/NewClosedGroupVC.swift index 3fcf7cace..cb46342ab 100644 --- a/Signal/src/Loki/View Controllers/NewClosedGroupVC.swift +++ b/Signal/src/Loki/View Controllers/NewClosedGroupVC.swift @@ -107,7 +107,7 @@ final class NewClosedGroupVC : UIViewController, UITableViewDataSource, UITableV explanationLabel.text = NSLocalizedString("You don't have any contacts yet", comment: "") let createNewPrivateChatButton = Button(style: .prominentOutline, size: .medium) createNewPrivateChatButton.setTitle(NSLocalizedString("Start a Session", comment: ""), for: UIControl.State.normal) - createNewPrivateChatButton.addTarget(self, action: #selector(createPrivateChat), for: UIControl.Event.touchUpInside) + createNewPrivateChatButton.addTarget(self, action: #selector(createNewPrivateChat), for: UIControl.Event.touchUpInside) createNewPrivateChatButton.set(.width, to: 160) let stackView = UIStackView(arrangedSubviews: [ explanationLabel, createNewPrivateChatButton ]) stackView.axis = .vertical @@ -201,9 +201,9 @@ final class NewClosedGroupVC : UIViewController, UITableViewDataSource, UITableV } } - @objc private func createPrivateChat() { + @objc private func createNewPrivateChat() { presentingViewController?.dismiss(animated: true, completion: nil) - SignalApp.shared().homeViewController!.createPrivateChat() + SignalApp.shared().homeViewController!.createNewPrivateChat() } } From 64188f74dcb2cf121796e0374e3cb23d2d9231f8 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 2 Mar 2020 16:45:42 +1100 Subject: [PATCH 3/9] Adjust fonts --- Signal/src/Loki/Components/Button.swift | 2 +- Signal/src/Loki/View Controllers/DeviceLinksVC.swift | 2 +- Signal/src/Loki/View Controllers/NewClosedGroupVC.swift | 2 +- Signal/src/Loki/View Controllers/SeedVC.swift | 5 +++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Signal/src/Loki/Components/Button.swift b/Signal/src/Loki/Components/Button.swift index d94d18b89..f8b0ceb1d 100644 --- a/Signal/src/Loki/Components/Button.swift +++ b/Signal/src/Loki/Components/Button.swift @@ -63,7 +63,7 @@ final class Button : UIButton { layer.borderColor = borderColor.cgColor layer.borderWidth = Values.borderThickness let fontSize = (size == .small) ? Values.smallFontSize : Values.mediumFontSize - titleLabel!.font = Fonts.spaceMono(ofSize: fontSize) + titleLabel!.font = .boldSystemFont(ofSize: fontSize) setTitleColor(textColor, for: UIControl.State.normal) } } diff --git a/Signal/src/Loki/View Controllers/DeviceLinksVC.swift b/Signal/src/Loki/View Controllers/DeviceLinksVC.swift index 3b54c62bc..10a232e70 100644 --- a/Signal/src/Loki/View Controllers/DeviceLinksVC.swift +++ b/Signal/src/Loki/View Controllers/DeviceLinksVC.swift @@ -24,7 +24,7 @@ final class DeviceLinksVC : UIViewController, UITableViewDataSource, UITableView explanationLabel.lineBreakMode = .byWordWrapping explanationLabel.textAlignment = .center explanationLabel.text = NSLocalizedString("You haven't linked any devices yet", comment: "") - let linkNewDeviceButton = Button(style: .prominentOutline, size: .medium) + let linkNewDeviceButton = Button(style: .prominentOutline, size: .large) linkNewDeviceButton.setTitle(NSLocalizedString("Link a Device", comment: ""), for: UIControl.State.normal) linkNewDeviceButton.addTarget(self, action: #selector(linkNewDevice), for: UIControl.Event.touchUpInside) linkNewDeviceButton.set(.width, to: 160) diff --git a/Signal/src/Loki/View Controllers/NewClosedGroupVC.swift b/Signal/src/Loki/View Controllers/NewClosedGroupVC.swift index cb46342ab..5d849b8ca 100644 --- a/Signal/src/Loki/View Controllers/NewClosedGroupVC.swift +++ b/Signal/src/Loki/View Controllers/NewClosedGroupVC.swift @@ -105,7 +105,7 @@ final class NewClosedGroupVC : UIViewController, UITableViewDataSource, UITableV explanationLabel.lineBreakMode = .byWordWrapping explanationLabel.textAlignment = .center explanationLabel.text = NSLocalizedString("You don't have any contacts yet", comment: "") - let createNewPrivateChatButton = Button(style: .prominentOutline, size: .medium) + let createNewPrivateChatButton = Button(style: .prominentOutline, size: .large) createNewPrivateChatButton.setTitle(NSLocalizedString("Start a Session", comment: ""), for: UIControl.State.normal) createNewPrivateChatButton.addTarget(self, action: #selector(createNewPrivateChat), for: UIControl.Event.touchUpInside) createNewPrivateChatButton.set(.width, to: 160) diff --git a/Signal/src/Loki/View Controllers/SeedVC.swift b/Signal/src/Loki/View Controllers/SeedVC.swift index e0fd439a0..0dd6d4cc2 100644 --- a/Signal/src/Loki/View Controllers/SeedVC.swift +++ b/Signal/src/Loki/View Controllers/SeedVC.swift @@ -71,7 +71,8 @@ final class SeedVC : UIViewController { let navigationBarTitleLabel = UILabel() navigationBarTitleLabel.text = NSLocalizedString("Your Recovery Phrase", comment: "") navigationBarTitleLabel.textColor = Colors.text - navigationBarTitleLabel.font = .boldSystemFont(ofSize: Values.veryLargeFontSize) + let titleLabelFontSize = isSmallScreen ? Values.largeFontSize : Values.veryLargeFontSize + navigationBarTitleLabel.font = .boldSystemFont(ofSize: titleLabelFontSize) navigationItem.titleView = navigationBarTitleLabel // Set up navigation bar buttons let closeButton = UIBarButtonItem(image: #imageLiteral(resourceName: "X"), style: .plain, target: self, action: #selector(close)) @@ -145,7 +146,7 @@ final class SeedVC : UIViewController { let mainStackView = UIStackView(arrangedSubviews: [ topSpacer, topStackViewContainer, bottomSpacer, copyButtonContainer ]) mainStackView.axis = .vertical mainStackView.alignment = .fill - mainStackView.layoutMargins = UIEdgeInsets(top: 0, leading: 0, bottom: isSmallScreen ? Values.smallSpacing : Values.mediumSpacing, trailing: 0) + mainStackView.layoutMargins = UIEdgeInsets(top: 0, leading: 0, bottom: Values.mediumSpacing, trailing: 0) mainStackView.isLayoutMarginsRelativeArrangement = true view.addSubview(mainStackView) mainStackView.pin(.leading, to: .leading, of: view) From e079f7ba13d834aad3ed35e84e5b9ec39789a81f Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 3 Mar 2020 10:00:21 +1100 Subject: [PATCH 4/9] Allow user to drag beyond new conversation buttons --- .../Components/NewConversationButtonSet.swift | 86 +++++++++++++------ 1 file changed, 62 insertions(+), 24 deletions(-) diff --git a/Signal/src/Loki/Components/NewConversationButtonSet.swift b/Signal/src/Loki/Components/NewConversationButtonSet.swift index 39fcd0e69..fc5db863a 100644 --- a/Signal/src/Loki/Components/NewConversationButtonSet.swift +++ b/Signal/src/Loki/Components/NewConversationButtonSet.swift @@ -13,8 +13,8 @@ final class NewConversationButtonSet : UIView { // MARK: Components private lazy var mainButton = NewConversationButton(isMainButton: true, icon: #imageLiteral(resourceName: "Plus").scaled(to: CGSize(width: iconSize, height: iconSize))) - private lazy var newPrivateChatButton = NewConversationButton(isMainButton: false, icon: #imageLiteral(resourceName: "Message").scaled(to: CGSize(width: iconSize, height: iconSize))) - private lazy var newClosedGroupButton = NewConversationButton(isMainButton: false, icon: #imageLiteral(resourceName: "Group").scaled(to: CGSize(width: iconSize, height: iconSize))) + private lazy var createNewPrivateChatButton = NewConversationButton(isMainButton: false, icon: #imageLiteral(resourceName: "Message").scaled(to: CGSize(width: iconSize, height: iconSize))) + private lazy var createNewClosedGroupButton = NewConversationButton(isMainButton: false, icon: #imageLiteral(resourceName: "Group").scaled(to: CGSize(width: iconSize, height: iconSize))) private lazy var joinOpenGroupButton = NewConversationButton(isMainButton: false, icon: #imageLiteral(resourceName: "Globe").scaled(to: CGSize(width: iconSize, height: iconSize))) // MARK: Initialization @@ -33,12 +33,12 @@ final class NewConversationButtonSet : UIView { addSubview(joinOpenGroupButton) horizontalButtonConstraints[joinOpenGroupButton] = joinOpenGroupButton.pin(.left, to: .left, of: self, withInset: inset) verticalButtonConstraints[joinOpenGroupButton] = joinOpenGroupButton.pin(.bottom, to: .bottom, of: self, withInset: -inset) - addSubview(newPrivateChatButton) - newPrivateChatButton.center(.horizontal, in: self) - verticalButtonConstraints[newPrivateChatButton] = newPrivateChatButton.pin(.top, to: .top, of: self, withInset: inset) - addSubview(newClosedGroupButton) - horizontalButtonConstraints[newClosedGroupButton] = newClosedGroupButton.pin(.right, to: .right, of: self, withInset: -inset) - verticalButtonConstraints[newClosedGroupButton] = newClosedGroupButton.pin(.bottom, to: .bottom, of: self, withInset: -inset) + addSubview(createNewPrivateChatButton) + createNewPrivateChatButton.center(.horizontal, in: self) + verticalButtonConstraints[createNewPrivateChatButton] = createNewPrivateChatButton.pin(.top, to: .top, of: self, withInset: inset) + addSubview(createNewClosedGroupButton) + horizontalButtonConstraints[createNewClosedGroupButton] = createNewClosedGroupButton.pin(.right, to: .right, of: self, withInset: -inset) + verticalButtonConstraints[createNewClosedGroupButton] = createNewClosedGroupButton.pin(.bottom, to: .bottom, of: self, withInset: -inset) addSubview(mainButton) mainButton.center(.horizontal, in: self) mainButton.pin(.bottom, to: .bottom, of: self) @@ -53,9 +53,9 @@ final class NewConversationButtonSet : UIView { let joinOpenGroupButtonTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleJoinOpenGroupButtonTapped)) joinOpenGroupButton.addGestureRecognizer(joinOpenGroupButtonTapGestureRecognizer) let createNewPrivateChatButtonTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleCreateNewPrivateChatButtonTapped)) - newPrivateChatButton.addGestureRecognizer(createNewPrivateChatButtonTapGestureRecognizer) + createNewPrivateChatButton.addGestureRecognizer(createNewPrivateChatButtonTapGestureRecognizer) let createNewClosedGroupButtonTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleCreateNewClosedGroupButtonTapped)) - newClosedGroupButton.addGestureRecognizer(createNewClosedGroupButtonTapGestureRecognizer) + createNewClosedGroupButton.addGestureRecognizer(createNewClosedGroupButtonTapGestureRecognizer) } // MARK: Interaction @@ -65,14 +65,14 @@ final class NewConversationButtonSet : UIView { @objc private func handleCreateNewClosedGroupButtonTapped() { delegate?.createNewClosedGroup() } private func expand(isUserDragging: Bool) { - let buttons = [ joinOpenGroupButton, newPrivateChatButton, newClosedGroupButton ] + let buttons = [ joinOpenGroupButton, createNewPrivateChatButton, createNewClosedGroupButton ] UIView.animate(withDuration: 0.25, animations: { buttons.forEach { $0.alpha = 1 } let inset = (Values.newConversationButtonExpandedSize - Values.newConversationButtonCollapsedSize) / 2 let size = Values.newConversationButtonCollapsedSize self.joinOpenGroupButton.frame = CGRect(origin: CGPoint(x: inset, y: self.height() - size - inset), size: CGSize(width: size, height: size)) - self.newPrivateChatButton.frame = CGRect(center: CGPoint(x: self.bounds.center.x, y: inset + size / 2), size: CGSize(width: size, height: size)) - self.newClosedGroupButton.frame = CGRect(origin: CGPoint(x: self.width() - size - inset, y: self.height() - size - inset), size: CGSize(width: size, height: size)) + self.createNewPrivateChatButton.frame = CGRect(center: CGPoint(x: self.bounds.center.x, y: inset + size / 2), size: CGSize(width: size, height: size)) + self.createNewClosedGroupButton.frame = CGRect(origin: CGPoint(x: self.width() - size - inset, y: self.height() - size - inset), size: CGSize(width: size, height: size)) }, completion: { _ in self.isUserDragging = isUserDragging }) @@ -80,7 +80,7 @@ final class NewConversationButtonSet : UIView { private func collapse(withAnimation isAnimated: Bool) { isUserDragging = false - let buttons = [ joinOpenGroupButton, newPrivateChatButton, newClosedGroupButton ] + let buttons = [ joinOpenGroupButton, createNewPrivateChatButton, createNewClosedGroupButton ] UIView.animate(withDuration: isAnimated ? 0.25 : 0) { buttons.forEach { button in button.alpha = 0 @@ -117,8 +117,14 @@ final class NewConversationButtonSet : UIView { let touchLocationInSelfCoordinates = touch.location(in: self) mainButton.frame = CGRect(center: touchLocationInSelfCoordinates, size: mainButtonSize) mainButton.alpha = 1 - (touchLocationInSelfCoordinates.distance(to: mainButtonLocationInSelfCoordinates) / maxDragDistance) - let buttons = [ joinOpenGroupButton, newPrivateChatButton, newClosedGroupButton ] - let buttonToExpand = buttons.first { $0.contains(touch) } + let buttons = [ joinOpenGroupButton, createNewPrivateChatButton, createNewClosedGroupButton ] + let buttonToExpand = buttons.first { button in + var hasUserDraggedBeyondButton = false + if button == joinOpenGroupButton && touch.isLeft(of: joinOpenGroupButton) { hasUserDraggedBeyondButton = true } + if button == createNewPrivateChatButton && touch.isAbove(createNewPrivateChatButton) { hasUserDraggedBeyondButton = true } + if button == createNewClosedGroupButton && touch.isRight(of: createNewClosedGroupButton) { hasUserDraggedBeyondButton = true } + return button.contains(touch) || hasUserDraggedBeyondButton + } if let buttonToExpand = buttonToExpand { guard buttonToExpand != expandedButton else { return } if let expandedButton = expandedButton { collapse(expandedButton) } @@ -132,9 +138,9 @@ final class NewConversationButtonSet : UIView { override func touchesEnded(_ touches: Set, with event: UIEvent?) { guard let touch = touches.first, isUserDragging else { return } - if joinOpenGroupButton.contains(touch) { delegate?.joinOpenGroup() } - else if newPrivateChatButton.contains(touch) { delegate?.createNewPrivateChat() } - else if newClosedGroupButton.contains(touch) { delegate?.createNewClosedGroup() } + if joinOpenGroupButton.contains(touch) || touch.isLeft(of: joinOpenGroupButton) { delegate?.joinOpenGroup() } + else if createNewPrivateChatButton.contains(touch) || touch.isAbove(createNewPrivateChatButton) { delegate?.createNewPrivateChat() } + else if createNewClosedGroupButton.contains(touch) || touch.isRight(of: createNewClosedGroupButton) { delegate?.createNewClosedGroup() } reset() } @@ -164,11 +170,11 @@ final class NewConversationButtonSet : UIView { if joinOpenGroupButton == expandedButton { horizontalButtonConstraints[joinOpenGroupButton]!.constant = inset verticalButtonConstraints[joinOpenGroupButton]!.constant = -inset - } else if newPrivateChatButton == expandedButton { - verticalButtonConstraints[newPrivateChatButton]!.constant = inset - } else if newClosedGroupButton == expandedButton { - horizontalButtonConstraints[newClosedGroupButton]!.constant = -inset - verticalButtonConstraints[newClosedGroupButton]!.constant = -inset + } else if createNewPrivateChatButton == expandedButton { + verticalButtonConstraints[createNewPrivateChatButton]!.constant = inset + } else if createNewClosedGroupButton == expandedButton { + horizontalButtonConstraints[createNewClosedGroupButton]!.constant = -inset + verticalButtonConstraints[createNewClosedGroupButton]!.constant = -inset } let size = Values.newConversationButtonCollapsedSize let frame = CGRect(center: button.center, size: CGSize(width: size, height: size)) @@ -182,6 +188,11 @@ final class NewConversationButtonSet : UIView { button.backgroundColor = Colors.newConversationButtonCollapsedBackground } } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if !bounds.contains(point), isUserDragging { collapse(withAnimation: true) } + return super.hitTest(point, with: event) + } } // MARK: Delegate @@ -253,6 +264,33 @@ private extension UIView { } } +private extension UITouch { + + func isLeft(of view: UIView) -> Bool { + return isContainedVertically(in: view) && location(in: view).x < view.bounds.minX + } + + func isAbove(_ view: UIView) -> Bool { + return isContainedHorizontally(in: view) && location(in: view).y < view.bounds.minY + } + + func isRight(of view: UIView) -> Bool { + return isContainedVertically(in: view) && location(in: view).x > view.bounds.maxX + } + + func isBelow(_ view: UIView) -> Bool { + return isContainedHorizontally(in: view) && location(in: view).y > view.bounds.maxY + } + + private func isContainedHorizontally(in view: UIView) -> Bool { + return (view.bounds.minX...view.bounds.maxX) ~= location(in: view).x + } + + private func isContainedVertically(in view: UIView) -> Bool { + return (view.bounds.minY...view.bounds.maxY) ~= location(in: view).y + } +} + private extension CGPoint { func distance(to otherPoint: CGPoint) -> CGFloat { From 115ad5d852b6fe50247a01cc1a9e6068c7c51837 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 3 Mar 2020 11:02:41 +1100 Subject: [PATCH 5/9] Add home screen fade, conversation button drag margin & fix crunchy icon --- .../Loki/Components/ConversationCell.swift | 9 ++++- .../Components/NewConversationButtonSet.swift | 37 ++++++++++--------- Signal/src/Loki/Style Guide/Gradients.swift | 3 +- Signal/src/Loki/View Controllers/HomeVC.swift | 10 +++++ 4 files changed, 38 insertions(+), 21 deletions(-) diff --git a/Signal/src/Loki/Components/ConversationCell.swift b/Signal/src/Loki/Components/ConversationCell.swift index cb9ce6629..f66dc38dc 100644 --- a/Signal/src/Loki/Components/ConversationCell.swift +++ b/Signal/src/Loki/Components/ConversationCell.swift @@ -42,7 +42,9 @@ final class ConversationCell : UITableViewCell { private lazy var statusIndicatorView: UIImageView = { let result = UIImageView() - result.contentMode = .center + result.contentMode = .scaleAspectFit + result.layer.cornerRadius = Values.conversationCellStatusIndicatorSize / 2 + result.layer.masksToBounds = true return result }() @@ -167,6 +169,7 @@ final class ConversationCell : UITableViewCell { typingIndicatorView.isHidden = true typingIndicatorView.stopAnimation() } + statusIndicatorView.backgroundColor = nil let lastMessage = threadViewModel.lastMessageForInbox if let lastMessage = lastMessage as? TSOutgoingMessage { let image: UIImage @@ -174,7 +177,9 @@ final class ConversationCell : UITableViewCell { switch status { case .calculatingPoW, .uploading, .sending: image = #imageLiteral(resourceName: "CircleDotDotDot") case .sent, .skipped, .delivered: image = #imageLiteral(resourceName: "CircleCheck") - case .read: image = #imageLiteral(resourceName: "FilledCircleCheck") + case .read: + statusIndicatorView.backgroundColor = .white + image = #imageLiteral(resourceName: "FilledCircleCheck") case .failed: image = #imageLiteral(resourceName: "message_status_failed").asTintedImage(color: Colors.text)! } statusIndicatorView.image = image diff --git a/Signal/src/Loki/Components/NewConversationButtonSet.swift b/Signal/src/Loki/Components/NewConversationButtonSet.swift index fc5db863a..8510a2a51 100644 --- a/Signal/src/Loki/Components/NewConversationButtonSet.swift +++ b/Signal/src/Loki/Components/NewConversationButtonSet.swift @@ -10,6 +10,7 @@ final class NewConversationButtonSet : UIView { private let spacing = Values.largeSpacing private let iconSize = CGFloat(24) private let maxDragDistance = CGFloat(56) + private let dragMargin = CGFloat(16) // MARK: Components private lazy var mainButton = NewConversationButton(isMainButton: true, icon: #imageLiteral(resourceName: "Plus").scaled(to: CGSize(width: iconSize, height: iconSize))) @@ -120,9 +121,9 @@ final class NewConversationButtonSet : UIView { let buttons = [ joinOpenGroupButton, createNewPrivateChatButton, createNewClosedGroupButton ] let buttonToExpand = buttons.first { button in var hasUserDraggedBeyondButton = false - if button == joinOpenGroupButton && touch.isLeft(of: joinOpenGroupButton) { hasUserDraggedBeyondButton = true } - if button == createNewPrivateChatButton && touch.isAbove(createNewPrivateChatButton) { hasUserDraggedBeyondButton = true } - if button == createNewClosedGroupButton && touch.isRight(of: createNewClosedGroupButton) { hasUserDraggedBeyondButton = true } + if button == joinOpenGroupButton && touch.isLeft(of: joinOpenGroupButton, with: dragMargin) { hasUserDraggedBeyondButton = true } + if button == createNewPrivateChatButton && touch.isAbove(createNewPrivateChatButton, with: dragMargin) { hasUserDraggedBeyondButton = true } + if button == createNewClosedGroupButton && touch.isRight(of: createNewClosedGroupButton, with: dragMargin) { hasUserDraggedBeyondButton = true } return button.contains(touch) || hasUserDraggedBeyondButton } if let buttonToExpand = buttonToExpand { @@ -138,9 +139,9 @@ final class NewConversationButtonSet : UIView { override func touchesEnded(_ touches: Set, with event: UIEvent?) { guard let touch = touches.first, isUserDragging else { return } - if joinOpenGroupButton.contains(touch) || touch.isLeft(of: joinOpenGroupButton) { delegate?.joinOpenGroup() } - else if createNewPrivateChatButton.contains(touch) || touch.isAbove(createNewPrivateChatButton) { delegate?.createNewPrivateChat() } - else if createNewClosedGroupButton.contains(touch) || touch.isRight(of: createNewClosedGroupButton) { delegate?.createNewClosedGroup() } + if joinOpenGroupButton.contains(touch) || touch.isLeft(of: joinOpenGroupButton, with: dragMargin) { delegate?.joinOpenGroup() } + else if createNewPrivateChatButton.contains(touch) || touch.isAbove(createNewPrivateChatButton, with: dragMargin) { delegate?.createNewPrivateChat() } + else if createNewClosedGroupButton.contains(touch) || touch.isRight(of: createNewClosedGroupButton, with: dragMargin) { delegate?.createNewClosedGroup() } reset() } @@ -266,28 +267,28 @@ private extension UIView { private extension UITouch { - func isLeft(of view: UIView) -> Bool { - return isContainedVertically(in: view) && location(in: view).x < view.bounds.minX + func isLeft(of view: UIView, with margin: CGFloat = 0) -> Bool { + return isContainedVertically(in: view, with: margin) && location(in: view).x < view.bounds.minX } - func isAbove(_ view: UIView) -> Bool { - return isContainedHorizontally(in: view) && location(in: view).y < view.bounds.minY + func isAbove(_ view: UIView, with margin: CGFloat = 0) -> Bool { + return isContainedHorizontally(in: view, with: margin) && location(in: view).y < view.bounds.minY } - func isRight(of view: UIView) -> Bool { - return isContainedVertically(in: view) && location(in: view).x > view.bounds.maxX + func isRight(of view: UIView, with margin: CGFloat = 0) -> Bool { + return isContainedVertically(in: view, with: margin) && location(in: view).x > view.bounds.maxX } - func isBelow(_ view: UIView) -> Bool { - return isContainedHorizontally(in: view) && location(in: view).y > view.bounds.maxY + func isBelow(_ view: UIView, with margin: CGFloat = 0) -> Bool { + return isContainedHorizontally(in: view, with: margin) && location(in: view).y > view.bounds.maxY } - private func isContainedHorizontally(in view: UIView) -> Bool { - return (view.bounds.minX...view.bounds.maxX) ~= location(in: view).x + private func isContainedHorizontally(in view: UIView, with margin: CGFloat = 0) -> Bool { + return ((view.bounds.minX - margin)...(view.bounds.maxX + margin)) ~= location(in: view).x } - private func isContainedVertically(in view: UIView) -> Bool { - return (view.bounds.minY...view.bounds.maxY) ~= location(in: view).y + private func isContainedVertically(in view: UIView, with margin: CGFloat = 0) -> Bool { + return ((view.bounds.minY - margin)...(view.bounds.maxY + margin)) ~= location(in: view).y } } diff --git a/Signal/src/Loki/Style Guide/Gradients.swift b/Signal/src/Loki/Style Guide/Gradients.swift index c65be7b36..0060bf9e6 100644 --- a/Signal/src/Loki/Style Guide/Gradients.swift +++ b/Signal/src/Loki/Style Guide/Gradients.swift @@ -26,5 +26,6 @@ final class Gradient : NSObject { @objc(LKGradients) final class Gradients : NSObject { - @objc static let defaultLokiBackground = Gradient(start: UIColor(hex: 0x171717), end: UIColor(hex:0x121212)) + @objc static let defaultLokiBackground = Gradient(start: UIColor(hex: 0x171717), end: UIColor(hex: 0x121212)) + @objc static let transparentToBlack75 = Gradient(start: UIColor(red: 0, green: 0, blue: 0, alpha: 0), end: UIColor(red: 0, green: 0, blue: 0, alpha: 0.75)) } diff --git a/Signal/src/Loki/View Controllers/HomeVC.swift b/Signal/src/Loki/View Controllers/HomeVC.swift index c9a40faef..22fbffedc 100644 --- a/Signal/src/Loki/View Controllers/HomeVC.swift +++ b/Signal/src/Loki/View Controllers/HomeVC.swift @@ -51,6 +51,14 @@ final class HomeVC : UIViewController, UITableViewDataSource, UITableViewDelegat return result }() + private lazy var fadeView: UIView = { + let result = UIView() + let gradient = Gradients.transparentToBlack75 + result.setGradient(gradient) + result.isUserInteractionEnabled = false + return result + }() + // MARK: Lifecycle override func viewDidLoad() { SignalApp.shared().homeViewController = self @@ -95,6 +103,8 @@ final class HomeVC : UIViewController, UITableViewDataSource, UITableViewDelegat } tableView.pin(.trailing, to: .trailing, of: view) tableView.pin(.bottom, to: .bottom, of: view) + view.addSubview(fadeView) + fadeView.pin(to: view) // Set up search bar // tableView.tableHeaderView = searchBar // searchBar.sizeToFit() From 142867abf9c7151c78803ffe43befc497423e07d Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 3 Mar 2020 11:07:25 +1100 Subject: [PATCH 6/9] Remove Session public chat notice --- Signal.xcodeproj/project.pbxproj | 4 --- .../src/Loki/Utilities/GeneralUtilities.swift | 29 ------------------- .../ConversationViewController.m | 8 ----- 3 files changed, 41 deletions(-) delete mode 100644 Signal/src/Loki/Utilities/GeneralUtilities.swift diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 98b1893ba..fccbb059f 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -589,7 +589,6 @@ B8544E3523D5201400299F14 /* UIView+Constraints.swift in Sources */ = {isa = PBXBuildFile; fileRef = B885D5F52334A32100EE0D8E /* UIView+Constraints.swift */; }; B86BD08423399ACF000F5AE3 /* Modal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08323399ACF000F5AE3 /* Modal.swift */; }; B86BD08623399CEF000F5AE3 /* SeedModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08523399CEF000F5AE3 /* SeedModal.swift */; }; - B8783E9C23EB8DDE00404FB8 /* GeneralUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8783E9B23EB8DDE00404FB8 /* GeneralUtilities.swift */; }; B8783E9E23EB948D00404FB8 /* UILabel+Interaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */; }; B885D5F4233491AB00EE0D8E /* DeviceLinkingModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B885D5F3233491AB00EE0D8E /* DeviceLinkingModal.swift */; }; B886B4A72398B23E00211ABE /* QRCodeVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B886B4A62398B23E00211ABE /* QRCodeVC.swift */; }; @@ -1433,7 +1432,6 @@ B8544E3223D50E4900299F14 /* AppearanceUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceUtilities.swift; sourceTree = ""; }; B86BD08323399ACF000F5AE3 /* Modal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modal.swift; sourceTree = ""; }; B86BD08523399CEF000F5AE3 /* SeedModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedModal.swift; sourceTree = ""; }; - B8783E9B23EB8DDE00404FB8 /* GeneralUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralUtilities.swift; sourceTree = ""; }; B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+Interaction.swift"; sourceTree = ""; }; B885D5F3233491AB00EE0D8E /* DeviceLinkingModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceLinkingModal.swift; sourceTree = ""; }; B885D5F52334A32100EE0D8E /* UIView+Constraints.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Constraints.swift"; sourceTree = ""; }; @@ -2807,7 +2805,6 @@ children = ( B8544E3223D50E4900299F14 /* AppearanceUtilities.swift */, B8544E3023D16CA500299F14 /* DeviceUtilities.swift */, - B8783E9B23EB8DDE00404FB8 /* GeneralUtilities.swift */, B847570223D5698100759540 /* LokiPushNotificationManager.swift */, B84664F4235022F30083A1CD /* MentionUtilities.swift */, B886B4A82398BA1500211ABE /* QRCode.swift */, @@ -3908,7 +3905,6 @@ 3403B95D20EA9527001A1F44 /* OWSContactShareButtonsView.m in Sources */, 34B0796D1FCF46B100E248C2 /* MainAppContext.m in Sources */, 34E3EF101EFC2684007F6822 /* DebugUIPage.m in Sources */, - B8783E9C23EB8DDE00404FB8 /* GeneralUtilities.swift in Sources */, B80C6B572384A56D00FDBC8B /* DeviceLinksVC.swift in Sources */, 34A8B3512190A40E00218A25 /* MediaAlbumCellView.swift in Sources */, 34D1F0AE1F867BFC0066283D /* OWSMessageCell.m in Sources */, diff --git a/Signal/src/Loki/Utilities/GeneralUtilities.swift b/Signal/src/Loki/Utilities/GeneralUtilities.swift deleted file mode 100644 index 12d761798..000000000 --- a/Signal/src/Loki/Utilities/GeneralUtilities.swift +++ /dev/null @@ -1,29 +0,0 @@ - -@objc(LKGeneralUtilities) -final class GeneralUtilities : NSObject { - - private override init() { } - - @objc static func getSessionPublicChatNotice() -> String { - return """ - Welcome to the Session public chat! In order for this forum to be a fun environment, full of robust and constructive discussion and inclusive of everyone, please read and follow the rules below. - - 1. Please Keep Talk Relevant to Topic and Add Value to the Discussion. - (No Referral Links, Spamming, Off Topic Discussion) - - 2. You don't have to love everyone, but be civil. - (No Baiting, Excessively Partisan Arguments, Threats, and so on. Use common sense.) - - 3. Do not be a shill. - Comparison and criticism is reasonable, but blatant shilling of anything you work for, work on, or own is not. - - 4. Don't post explicit content - be it excessively offensive language, sexual, or violent. Any form of bigotry including racism, sexism, transphobia, homophobia, ableism, fatphobia, classism will NOT be tolerated. - - If you break these rules, you’ll be warned by an admin. If your behaviour doesn’t improve, you will be removed from the public chat. Admins reserve the right to remove anyone violating these rules. - - We want to keep this group a pleasant and supportive space for everyone. - - If you experience any anti-social behaviour or have an issue with these rules, please contact an admin. - """ - } -} diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 68eace1e0..ee47a7191 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -1440,14 +1440,6 @@ typedef enum : NSUInteger { [self updateInputToolbarLayout]; [self ensureScrollDownButton]; - - NSUserDefaults *userDefaults = NSUserDefaults.standardUserDefaults; - if ([@"Session Public Chat" isEqual:self.thread.name] && ![userDefaults boolForKey:@"hasSeenSessionPublicChatNotice"]) { - UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"The Rules" message:[LKGeneralUtilities getSessionPublicChatNotice] preferredStyle:UIAlertControllerStyleAlert]; - [alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]]; - [self presentViewController:alert animated:YES completion:nil]; - [userDefaults setBool:YES forKey:@"hasSeenSessionPublicChatNotice"]; - } } // `viewWillDisappear` is called whenever the view *starts* to disappear, From 33172b3ad71fb83a6c8ef798e4482eb164292e30 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 3 Mar 2020 14:33:22 +1100 Subject: [PATCH 7/9] Fix fade --- Signal/src/Loki/Style Guide/Gradients.swift | 2 +- Signal/src/Loki/View Controllers/HomeVC.swift | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Signal/src/Loki/Style Guide/Gradients.swift b/Signal/src/Loki/Style Guide/Gradients.swift index 0060bf9e6..7690c0bd4 100644 --- a/Signal/src/Loki/Style Guide/Gradients.swift +++ b/Signal/src/Loki/Style Guide/Gradients.swift @@ -27,5 +27,5 @@ final class Gradient : NSObject { final class Gradients : NSObject { @objc static let defaultLokiBackground = Gradient(start: UIColor(hex: 0x171717), end: UIColor(hex: 0x121212)) - @objc static let transparentToBlack75 = Gradient(start: UIColor(red: 0, green: 0, blue: 0, alpha: 0), end: UIColor(red: 0, green: 0, blue: 0, alpha: 0.75)) + @objc static let transparentToBlack75 = Gradient(start: UIColor(red: 0, green: 0, blue: 0, alpha: 0), end: UIColor(red: 0, green: 0, blue: 0, alpha: 1)) } diff --git a/Signal/src/Loki/View Controllers/HomeVC.swift b/Signal/src/Loki/View Controllers/HomeVC.swift index 22fbffedc..a509a24a0 100644 --- a/Signal/src/Loki/View Controllers/HomeVC.swift +++ b/Signal/src/Loki/View Controllers/HomeVC.swift @@ -53,7 +53,7 @@ final class HomeVC : UIViewController, UITableViewDataSource, UITableViewDelegat private lazy var fadeView: UIView = { let result = UIView() - let gradient = Gradients.transparentToBlack75 + let gradient = Gradients.transparentToBlack result.setGradient(gradient) result.isUserInteractionEnabled = false return result @@ -104,7 +104,11 @@ final class HomeVC : UIViewController, UITableViewDataSource, UITableViewDelegat tableView.pin(.trailing, to: .trailing, of: view) tableView.pin(.bottom, to: .bottom, of: view) view.addSubview(fadeView) - fadeView.pin(to: view) + fadeView.pin(.leading, to: .leading, of: view) + let topInset = 0.15 * view.height() + fadeView.pin(.top, to: .top, of: view, withInset: topInset) + fadeView.pin(.trailing, to: .trailing, of: view) + fadeView.pin(.bottom, to: .bottom, of: view) // Set up search bar // tableView.tableHeaderView = searchBar // searchBar.sizeToFit() From 2ef3eead3afd99a8d4fd5cc3a4c633d6834600d3 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 3 Mar 2020 14:34:40 +1100 Subject: [PATCH 8/9] Fix build --- Signal/src/Loki/Style Guide/Gradients.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Signal/src/Loki/Style Guide/Gradients.swift b/Signal/src/Loki/Style Guide/Gradients.swift index 7690c0bd4..0f2ac23bb 100644 --- a/Signal/src/Loki/Style Guide/Gradients.swift +++ b/Signal/src/Loki/Style Guide/Gradients.swift @@ -27,5 +27,5 @@ final class Gradient : NSObject { final class Gradients : NSObject { @objc static let defaultLokiBackground = Gradient(start: UIColor(hex: 0x171717), end: UIColor(hex: 0x121212)) - @objc static let transparentToBlack75 = Gradient(start: UIColor(red: 0, green: 0, blue: 0, alpha: 0), end: UIColor(red: 0, green: 0, blue: 0, alpha: 1)) + @objc static let transparentToBlack = Gradient(start: UIColor(red: 0, green: 0, blue: 0, alpha: 0), end: UIColor(red: 0, green: 0, blue: 0, alpha: 1)) } From b56fc0d01031a276b79be70736da8a48d720ebb6 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 3 Mar 2020 15:52:18 +1100 Subject: [PATCH 9/9] Update build number --- Signal.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index fccbb059f..74a74c662 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -4234,7 +4234,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 48; + CURRENT_PROJECT_VERSION = 49; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -4296,7 +4296,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 48; + CURRENT_PROJECT_VERSION = 49; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -4350,7 +4350,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 48; + CURRENT_PROJECT_VERSION = 49; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = SUQ8J2PCT7; @@ -4419,7 +4419,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 48; + CURRENT_PROJECT_VERSION = 49; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = SUQ8J2PCT7; @@ -4628,7 +4628,7 @@ CODE_SIGN_ENTITLEMENTS = Signal/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 48; + CURRENT_PROJECT_VERSION = 49; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -4695,7 +4695,7 @@ CODE_SIGN_ENTITLEMENTS = Signal/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 48; + CURRENT_PROJECT_VERSION = 49; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)",