From c87ed345e4b5798ddb8a78a75ead03fd34f8964f Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 29 Nov 2019 16:30:01 +1100 Subject: [PATCH] Further implement home screen redesign --- Signal.xcodeproj/project.pbxproj | 12 +- .../Loki V2/Contacts.imageset/Contacts@1x.png | Bin 0 -> 589 bytes .../Loki V2/Contacts.imageset/Contacts@2x.png | Bin 0 -> 1060 bytes .../Loki V2/Contacts.imageset/Contents.json | 22 ++ Signal/Images.xcassets/Loki V2/Contents.json | 6 + .../Loki V2/Globe.imageset/Contents.json | 22 ++ .../Loki V2/Globe.imageset/Globe@1x.png | Bin 0 -> 1240 bytes .../Loki V2/Globe.imageset/Globe@2x.png | Bin 0 -> 2321 bytes .../Loki V2/TickFilled.imageset/Contents.json | 22 ++ .../TickFilled.imageset/TickFilled@1x.png | Bin 0 -> 257 bytes .../TickFilled.imageset/TickFilled@2x.png | Bin 0 -> 464 bytes .../TickOutline.imageset/Contents.json | 22 ++ .../TickOutline.imageset/TickOutline@1x.png | Bin 0 -> 320 bytes .../TickOutline.imageset/TickOutline@2x.png | Bin 0 -> 571 bytes Signal/src/Loki/ConversationCell.swift | 82 ++++-- Signal/src/Loki/HomeVC.swift | 246 +++++++++++++++++- ...licChatVC.swift => JoinPublicChatVC.swift} | 4 +- Signal/src/Loki/ProfilePictureView.swift | 38 ++- Signal/src/Loki/SearchBar.swift | 37 +++ .../Loki/Utilities/Style Guide/Colors.swift | 19 +- .../Utilities/Style Guide/Gradients.swift | 18 +- .../Loki/Utilities/Style Guide/Values.swift | 36 +-- .../HomeView/HomeViewController.m | 4 +- .../translations/en.lproj/Localizable.strings | 2 + 24 files changed, 511 insertions(+), 81 deletions(-) create mode 100644 Signal/Images.xcassets/Loki V2/Contacts.imageset/Contacts@1x.png create mode 100644 Signal/Images.xcassets/Loki V2/Contacts.imageset/Contacts@2x.png create mode 100644 Signal/Images.xcassets/Loki V2/Contacts.imageset/Contents.json create mode 100644 Signal/Images.xcassets/Loki V2/Contents.json create mode 100644 Signal/Images.xcassets/Loki V2/Globe.imageset/Contents.json create mode 100644 Signal/Images.xcassets/Loki V2/Globe.imageset/Globe@1x.png create mode 100644 Signal/Images.xcassets/Loki V2/Globe.imageset/Globe@2x.png create mode 100644 Signal/Images.xcassets/Loki V2/TickFilled.imageset/Contents.json create mode 100644 Signal/Images.xcassets/Loki V2/TickFilled.imageset/TickFilled@1x.png create mode 100644 Signal/Images.xcassets/Loki V2/TickFilled.imageset/TickFilled@2x.png create mode 100644 Signal/Images.xcassets/Loki V2/TickOutline.imageset/Contents.json create mode 100644 Signal/Images.xcassets/Loki V2/TickOutline.imageset/TickOutline@1x.png create mode 100644 Signal/Images.xcassets/Loki V2/TickOutline.imageset/TickOutline@2x.png rename Signal/src/Loki/Messaging/{NewPublicChatVC.swift => JoinPublicChatVC.swift} (98%) create mode 100644 Signal/src/Loki/SearchBar.swift diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index d05ceace5..1a695ea01 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -11,7 +11,7 @@ 241C6315231F64CE00B4198E /* CGFloat+Rounding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 241C6312231F5F1D00B4198E /* CGFloat+Rounding.swift */; }; 241C6316231F64CE00B4198E /* UIColor+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 241C6310231F5C4400B4198E /* UIColor+Helper.swift */; }; 24A830A22293CD0100F4CAC0 /* LokiP2PServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A830A12293CD0100F4CAC0 /* LokiP2PServer.swift */; }; - 24BD2609234DA2050008EB0A /* NewPublicChatVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24BD2608234DA2050008EB0A /* NewPublicChatVC.swift */; }; + 24BD2609234DA2050008EB0A /* JoinPublicChatVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24BD2608234DA2050008EB0A /* JoinPublicChatVC.swift */; }; 2AE2882E4C2B96BFFF9EE27C /* Pods_SignalShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0F94C85CB0B235DA37F68ED0 /* Pods_SignalShareExtension.framework */; }; 3403B95D20EA9527001A1F44 /* OWSContactShareButtonsView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3403B95B20EA9526001A1F44 /* OWSContactShareButtonsView.m */; }; 34074F61203D0CBE004596AE /* OWSSounds.m in Sources */ = {isa = PBXBuildFile; fileRef = 34074F5F203D0CBD004596AE /* OWSSounds.m */; }; @@ -590,6 +590,7 @@ B8BB82A9238F62FB00BA5194 /* Gradients.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82A8238F62FB00BA5194 /* Gradients.swift */; }; B8BB82AB238F669C00BA5194 /* ConversationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82AA238F669C00BA5194 /* ConversationCell.swift */; }; B8BB82AD238F734800BA5194 /* ProfilePictureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82AC238F734800BA5194 /* ProfilePictureView.swift */; }; + B8BB82B12390C37000BA5194 /* SearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82B02390C37000BA5194 /* SearchBar.swift */; }; B90418E6183E9DD40038554A /* DateUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B90418E5183E9DD40038554A /* DateUtil.m */; }; B9EB5ABD1884C002007CBB57 /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9EB5ABC1884C002007CBB57 /* MessageUI.framework */; }; BFF3FB9730634F37D25903F4 /* Pods_Signal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D17BB5C25D615AB49813100C /* Pods_Signal.framework */; }; @@ -691,7 +692,7 @@ 241C6310231F5C4400B4198E /* UIColor+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Helper.swift"; sourceTree = ""; }; 241C6312231F5F1D00B4198E /* CGFloat+Rounding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGFloat+Rounding.swift"; sourceTree = ""; }; 24A830A12293CD0100F4CAC0 /* LokiP2PServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LokiP2PServer.swift; sourceTree = ""; }; - 24BD2608234DA2050008EB0A /* NewPublicChatVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewPublicChatVC.swift; sourceTree = ""; }; + 24BD2608234DA2050008EB0A /* JoinPublicChatVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinPublicChatVC.swift; sourceTree = ""; }; 264242150E87D10A357DB07B /* Pods_SignalMessaging.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SignalMessaging.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3403B95B20EA9526001A1F44 /* OWSContactShareButtonsView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactShareButtonsView.m; sourceTree = ""; }; 3403B95C20EA9527001A1F44 /* OWSContactShareButtonsView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSContactShareButtonsView.h; sourceTree = ""; }; @@ -1408,6 +1409,7 @@ B8BB82A8238F62FB00BA5194 /* Gradients.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Gradients.swift; sourceTree = ""; }; B8BB82AA238F669C00BA5194 /* ConversationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationCell.swift; sourceTree = ""; }; B8BB82AC238F734800BA5194 /* ProfilePictureView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePictureView.swift; sourceTree = ""; }; + B8BB82B02390C37000BA5194 /* SearchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchBar.swift; sourceTree = ""; }; B90418E4183E9DD40038554A /* DateUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DateUtil.h; sourceTree = ""; }; B90418E5183E9DD40038554A /* DateUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DateUtil.m; sourceTree = ""; }; B97940251832BD2400BD66CB /* UIUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIUtil.h; sourceTree = ""; }; @@ -2670,6 +2672,7 @@ B86BD0882339A253000F5AE3 /* Utilities */, B8BB82A4238F627000BA5194 /* HomeVC.swift */, B8BB82AA238F669C00BA5194 /* ConversationCell.swift */, + B8BB82B02390C37000BA5194 /* SearchBar.swift */, B8BB82AC238F734800BA5194 /* ProfilePictureView.swift */, ); path = Loki; @@ -2741,7 +2744,7 @@ B8B26C8E234D629C004ED98C /* MentionCandidateSelectionView.swift */, B8B26C90234D8CBD004ED98C /* MentionCandidateSelectionViewDelegate.swift */, B89841E222B7579F00B1BDC6 /* NewConversationVC.swift */, - 24BD2608234DA2050008EB0A /* NewPublicChatVC.swift */, + 24BD2608234DA2050008EB0A /* JoinPublicChatVC.swift */, B893063E2383961A005EAA8E /* ScanQRCodeWrapperVC.swift */, ); path = Messaging; @@ -3867,6 +3870,7 @@ 3448E15C22133274004B052E /* OnboardingPermissionsViewController.swift in Sources */, 34D920E720E179C200D51158 /* OWSMessageFooterView.m in Sources */, 341341EF2187467A00192D59 /* ConversationViewModel.m in Sources */, + B8BB82B12390C37000BA5194 /* SearchBar.swift in Sources */, 348BB25D20A0C5530047AEC2 /* ContactShareViewHelper.swift in Sources */, 34B3F8801E8DF1700035BE1A /* InviteFlow.swift in Sources */, 457C87B82032645C008D52D6 /* DebugUINotifications.swift in Sources */, @@ -3896,7 +3900,7 @@ B821F2F82272CED3002C88C0 /* DisplayNameVC.swift in Sources */, 34D8C0271ED3673300188D7C /* DebugUIMessages.m in Sources */, B885D5F62334A32100EE0D8E /* UIView+Constraints.swift in Sources */, - 24BD2609234DA2050008EB0A /* NewPublicChatVC.swift in Sources */, + 24BD2609234DA2050008EB0A /* JoinPublicChatVC.swift in Sources */, 34DBF003206BD5A500025978 /* OWSMessageTextView.m in Sources */, 34D1F0B41F86D31D0066283D /* ConversationCollectionView.m in Sources */, 34B3F8821E8DF1700035BE1A /* NewContactThreadViewController.m in Sources */, diff --git a/Signal/Images.xcassets/Loki V2/Contacts.imageset/Contacts@1x.png b/Signal/Images.xcassets/Loki V2/Contacts.imageset/Contacts@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..c24b89c9e07a027912191ece3566222fd3c94ca1 GIT binary patch literal 589 zcmV-T0P000>X1^@s6#OZ}&0006MNkl3OCf;< zf=Ri!6c|Uia{GMWdj{c7zbiPWZ(#dc*tT`NxeNEH$R8?IBkl*iLM-6~fRo&J!&4v1W zKEI0r44D)Pg_}SiuwAd$KYTvl3NPji#i=2i*Xvy_7K^u{bW=>ISVp~EE7Jl~1Mv$SfU}kXNSRPm$Y8ZvH*7YWtK02f zVPC`@08p>jyA;o7V)B=n_Tx>ER~C!Khv{bl(TfoDz+KL40z%fErMc{lxXbt(jX}6W zu(ADq|IG}{Znv)@Mnt#qVF^g#4oRg_TSgBk+kTw$gg{RUQ@Bx~7?EX_86%opv@opz zVn(4@|1ApwmjxpX5Rb?2#(VOAK%gmdNsMQ+x1hg6mQW~kU9DCh#2+j4HjR~8fz0aW b@-NXh57(WA&A0^000000NkvXXu0mjfw-O4l literal 0 HcmV?d00001 diff --git a/Signal/Images.xcassets/Loki V2/Contacts.imageset/Contacts@2x.png b/Signal/Images.xcassets/Loki V2/Contacts.imageset/Contacts@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..7a9ddaeef61f8c79bc662026367bdbc56940ed84 GIT binary patch literal 1060 zcmV+<1l#+GP)N{gGRI%H{w z4o(dLEh=5wL6AbR1fg*#F=&aI6e3sn%IA6a?)zR|Z3*ev_uhT?yMN!^#p!5~Kc_`F zI@%z|&67=eWNvQmUT<&jjYuRC?dj<`wY$6fvs$fwE0@cw^?Lo&%*@PF2{@O_J@4%7 z?61{o+o@FQj`TkF+!F{Ko=7Bag~QddA zAZw8I_4V(=!^5X-4&s#KWPt(3$Hy<&oM8pBv$OL{@&b1_1+%!g_;zAqVwC!3V`Jk- zEEYRs3S$hiva<3ykx2Ao!R2!K_uAUp2Z(Wa77m9;srTU7{QUfV9A*$++&4C1W&}dT zGN+gVrL65{WiIbbVT?eiSauvo{FZ`QSXjt$)hUz7T%Meqe4~dic)YK#@1zuBqB2lo zr>Cc%=z`po!3Sa^NQq3rQz41Z5katEQwynsa9EmrkBf?l;jBSSV^)DcxTMqRce(|$ z+3c0s+1dA8xZuJfc@gA@AXum^2prOAG#WM_I0_C0#6&2z^sxZ(J4g1zK&W`QK$t{< z7+TaFVp2tvS8!-bH@4UFL=aF2R}o1Wu42Ler3&Qr&N2d_YP;0ga(xsRR#4r>#>QUb zhExJqMl%9|m)$y5m_I6&%F93?5DNx_7r7RnQVIZuhK62H{h6ufjmocnAK>;uY8&+D zRKjvS+<}!1+8}F>zz5K-#N+WthdqzL?Vr!*uen{C)-4KUb+5_<}Z~>A4{duLmMyXa*UJR zCLfOC-8RTkSl#W+*7pb`b9Z<5?PxT56D$D>I6u4qg#hHhC2F#eQ$*N<@vJiV3K2cdETI?rOUc-`wwD*?rxFmM+Z2yPn>KXZbT1u!EcBbVTZRg>S@)sLuMU0r9Pm1_M7@vuGMxBRw8 e{I+Pb^8NsEA3;rkg$ri@0000MeO(Ba4KzmZOdInYGsM@B^sBJZ$LI)ahK1y;%lchRONbHj^ZI0;<-B)uH!+$($@A&JTT=c?V@ALlO-}AiB@Ao_p zfzDYbld0D0_1?_O%X^x~^bp$UZ0a|j_DLP&(~?e6ZPt*xygCX?AKlgWOek?SIhM=ipG6&k(?*-khFmV!t;)*ER_q`KJ~uaaH9tSU z3sL}JEEem{qN1XDlgZQy&mI^UP+{I_0D176^!a=YE|=RL z3cDE4APC}lOwQSyWBcLZ zp)DyX2^_@)0A~$vY&L%mfGU;h{m#zLFGoj5 zuSaG2FSyle{b_%He-@nh{r(q#OaPCIi`&MLGZ>6FN=iz~fIJR|vw7A&G&J;8R#uh; z>;i${T6T8!g;?b947?ys^!@*_14aZq6bk(r3BLXHQ5-s7^^q zQ3L{kyGKVyQ}BIST3RJkZ3G;&yRoqmsYJC}eWk6f?Q5*v#}SIPtV`;23iP7 zlJ}q>V`;%U)#-GTMx)UHJcz~O%l{R;uCDG?l}belg~BcXhDG@O{yumXY;cEzYIQoD z4uL>GJ`UXNc2A|Jr$1vho1J2@I2+3v(nQdtb2AF`5!UJ2+S>fi&dviqpKoQcSQS_; z$;ruo2LgfW)YR0B($dl_TARHS3gnGB0YklsE-js z5hr-z3Z_+Z>FMcau~?T{T3T*EwGssJRs_7SukW%#q4>dSweB1o9K74x+Z)pB z^}j+N(d+fsB@)R6sZ?4A%B*z|chW-IW9l&avd<2*P0000XuEZVq}`3O*|tGlx7Z&fYekZ_QQNfJZEd9K z{&1z+=pWs#k!(@|HoMyt>}D4QN7k{Zqr!;F2n$$5MPnEdncF-!^WMDo%>9^Ae)J@h zxgY18`@HXY&-?Klp-fS`aN$BuG#bsCoSb|u8jUVeRW*Z8ErjTIxm+Cnt+$%$SirbLLEC-n@AV{_*4O>C>kQ{YFPem1obM4W*={ zoS!yrT5C~J(T6XBmeyp&t@N=uaw%`H$`Sa)9CfbH(p+1aVvIX5j5KnU?YIX0MrKYRAD=RDD=X>(1HaEOgAAOLb!G7)|FF-?(S~QM^jWK-_Jc0fMEmTpMc^z zEiLU^uh+Yf=4x+mZz?G%d6Vw`i zL;ALD+kQ}AU;odk0AE#AweiT2Bmbtk_V3@ntG2fGuWWxHLpAIR2r2gM+xPClg9ks7 zK*ZVev17-6TUl922^0d2ZBmzv*=*jtx%||rQ;me-`0?ZK?cBNZ_tL(I%SMHny_*4R zX=(Y(rcIl6(S%b8KBi$kRXBb6^hc$or9YEbRo>X5v$ON>`T6;8v5s*`7T%#4DK0MF zLz{}Hzh)SESgbDpnQ^`o&=eAE3@ z_1EVWQglPu-IbG*qxo3g9zTBk$(%WJ-cEGG+T}K8n&E{BYFY}6yl>ow?dj?H)a&*B zm^uZ6!GZDd@r`oH6$}PH$ji(7Ir>E+kvp?y&9bEulJz!`NJOO#>-YOjsgJk{POglw zS(=-hzu2;6%j;a~=H}*Kl$MtM8PCusH8pk0f)wWDsYgpo%P-5y%0A%VHz`dOA%|uR zprU~DCWj9n-m!c4Zlu3j8x&td$uKcMTOZVu}qd#ikxjma{hKgri z5Zt+Q=UirH=DMlS{PpYC{kgfhI=huXA*3KB*EgNy+NiukDr)Lg5Mi%fyVeBopr=7+ za9Ykz^ZX!UVq)S)X7;xyOgXcb!Ggx*70ahetcA5u*KrWYS2+uemJil_~ ziuODjjcSJwbP&N;Q(8q0G)MqBqor76aCcn$GDjlwh*su8+>F`D2*xD9>1%X2(T7;2 zw6wHt#slfKh5@`B!8k`kU=yLJtprQpxq+QS|JK&l!3`TWq|;cQ0mKCwCyLF4?n8A2 zl9h4}>=ESUL~qb{sS1NR&<}m|qQ0Y}!$9|eohv9PC}*o>5Y9A8%H}gPG-L?Zb_6LQ zee&c<>+IRHH^lBuX%aia7QmM*SyIl}xwtt@!PnQ6!^7X_c^0*+=(*)kh z&)d=DNCjkC(X8yW%uo9I`bsl0GMWjD%p-E`L%5NV5uMk>7$@*%TDM6BUg2yUVX{rG zR8=LL)OTUuHS{J9-?c@rv%Qt9{m^DPeSOo0QBR^nU)oJm;|p^F%KdtHl=i6XW)&@Je4DO<|6fz3M-e#O>6Pu#c%P`z}vTPH{jse zA)4x*$*dTWznd_43``_Yk!f){9IJxo2^>5-YMTp?6nM4@hEux)f{#-dJr!{3_|o7_ rgpdPLA{YP|sQ~g9o!3ufAockl%ABHJr=7Wk00000NkvXXu0mjfTBMCX literal 0 HcmV?d00001 diff --git a/Signal/Images.xcassets/Loki V2/TickFilled.imageset/Contents.json b/Signal/Images.xcassets/Loki V2/TickFilled.imageset/Contents.json new file mode 100644 index 000000000..9459f00f3 --- /dev/null +++ b/Signal/Images.xcassets/Loki V2/TickFilled.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "TickFilled@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "TickFilled@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Signal/Images.xcassets/Loki V2/TickFilled.imageset/TickFilled@1x.png b/Signal/Images.xcassets/Loki V2/TickFilled.imageset/TickFilled@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..fd0df2f65c366b8fb2b779e85e90fc76ef6a4f45 GIT binary patch literal 257 zcmV+c0sj7pP)*mZ9r9cUl{y zwGe5VqN*wsMd4=83Dh)A{D~hx01bMK?M0sF;jwMoMZlp+O$PRTKTj#HAk_5Ly0WbcEP)#kHVT8WK-C}5m*$ikPA%p-8ga*J5FNSk&q&c z=Xw78m=Q!mRi6O70eAtxXMPwr07n3KK8wh%FO`HAE5MhNICdt2P9dNdq^chP-eZ{q z>l+c_{zh~_s>&)}CqnEtR!1P^HPHY)2vWmDWYqzw>SxvT#9|uTYZ*;Ns!CT8^cS(B zK`iJIeF+_MM+rq$*?5AIV!z+xdc9)1-TLx86O>|QlXSga<8U}2%Q6&2;R9_5N|Mb& zX0sVCmrF>MBSGII-u_0Sfe`Mw5u{U^GXnDa!iGT0<+?Qfuub}9K%6`q~^C<>B zibxImpG&p6!IQ5dPr2qoHTHHFQ?H{I$Vg_tYlCPE&I*(=B^r=UMK*KH@{tn^8%`XU zo#lpB$3bM_4kYe@f^%I9NJ+D5I&ud{CO-jS)9Z=HSMd)M71o!^)witx0000u4^P)`wK zoh;OD&o}etO;0oZb8r(+L>`Igg@|q<=k-BE2WG~zGAe;00>GyNor!2oM0+ASGc*2^ z|L)#@HzFeZM%02!KoLO_UynZnyiMOeVJm z=v-@og_BZc%wfml@n<+3URTJ9inX%9eqKRRfNe1E5t!x_0593U7??Vj(hA&ew_ixG zd!qNOPR0sZLm&~AB;9N_`{{H#vPMW*S*@f*RCxw1G!^E7?m~z@7C9914F-d&SQ%|y zwLn)1>Kv+}Rn~8Ec3#OeFsOG8$&kiX{n)T|I?;2!Id1 zi^G2CHW7sx?nSqj_pf*lnEOsgOe3tpZOXQ`m4Dj5w9N+Ze*xDHTr6#D!3zKY002ov JPDHLkV1g*8_^JQ^ literal 0 HcmV?d00001 diff --git a/Signal/src/Loki/ConversationCell.swift b/Signal/src/Loki/ConversationCell.swift index 24e49f42c..1a022769b 100644 --- a/Signal/src/Loki/ConversationCell.swift +++ b/Signal/src/Loki/ConversationCell.swift @@ -1,11 +1,11 @@ final class ConversationCell : UITableViewCell { - public var threadViewModel: ThreadViewModel! { didSet { update() } } + var threadViewModel: ThreadViewModel! { didSet { update() } } - public static let reuseIdentifier = "ConversationCell" + static let reuseIdentifier = "ConversationCell" // MARK: Components - private lazy var unreadMessagesIndicator: UIView = { + private lazy var unreadMessagesIndicatorView: UIView = { let result = UIView() result.backgroundColor = Colors.accent return result @@ -21,6 +21,15 @@ final class ConversationCell : UITableViewCell { return result }() + private lazy var timestampLabel: UILabel = { + let result = UILabel() + result.font = .systemFont(ofSize: Values.smallFontSize) + result.textColor = Colors.text + result.lineBreakMode = .byTruncatingTail + result.alpha = Values.conversationCellTimestampOpacity + return result + }() + private lazy var snippetLabel: UILabel = { let result = UILabel() result.font = .systemFont(ofSize: Values.smallFontSize) @@ -31,6 +40,14 @@ final class ConversationCell : UITableViewCell { private lazy var typingIndicatorView = TypingIndicatorView() + private lazy var bottomLabelStackViewSpacer = UIView.hStretchingSpacer() + + private lazy var statusIndicatorView: UIImageView = { + let result = UIImageView() + result.contentMode = .center + return result + }() + // MARK: Initialization override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) @@ -43,31 +60,44 @@ final class ConversationCell : UITableViewCell { } private func setUpViewHierarchy() { - // Make the cell transparent - backgroundColor = .clear - // Set up the unread messages indicator - unreadMessagesIndicator.set(.width, to: Values.accentLineThickness) + // Set the cell background color + backgroundColor = Colors.conversationCellBackground + // Set up the highlight color + let selectedBackgroundView = UIView() + selectedBackgroundView.backgroundColor = Colors.conversationCellSelected + self.selectedBackgroundView = selectedBackgroundView + // Set up the unread messages indicator view + unreadMessagesIndicatorView.set(.width, to: Values.accentLineThickness) // Set up the profile picture view let profilePictureViewSize = Values.mediumProfilePictureSize profilePictureView.set(.width, to: profilePictureViewSize) profilePictureView.set(.height, to: profilePictureViewSize) profilePictureView.size = profilePictureViewSize // Set up the label stack view + let topLabelStackView = UIStackView(arrangedSubviews: [ displayNameLabel, UIView.hStretchingSpacer(), timestampLabel ]) + topLabelStackView.axis = .horizontal + topLabelStackView.spacing = Values.smallSpacing / 2 // Effectively Values.smallSpacing because there'll be spacing before and after the invisible spacer let snippetLabelContainer = UIView() snippetLabelContainer.addSubview(snippetLabel) snippetLabelContainer.addSubview(typingIndicatorView) - let labelStackView = UIStackView(arrangedSubviews: [ UIView.spacer(withHeight: Values.smallSpacing), displayNameLabel, snippetLabelContainer, UIView.spacer(withHeight: Values.smallSpacing) ]) + let bottomLabelStackView = UIStackView(arrangedSubviews: [ snippetLabelContainer, bottomLabelStackViewSpacer, statusIndicatorView ]) + bottomLabelStackView.axis = .horizontal + bottomLabelStackView.spacing = Values.smallSpacing / 2 // Effectively Values.smallSpacing because there'll be spacing before and after the invisible spacer + let labelStackView = UIStackView(arrangedSubviews: [ UIView.spacer(withHeight: Values.smallSpacing), topLabelStackView, bottomLabelStackView, UIView.spacer(withHeight: Values.smallSpacing) ]) labelStackView.axis = .vertical labelStackView.spacing = Values.smallSpacing // Set up the main stack view - let stackView = UIStackView(arrangedSubviews: [ unreadMessagesIndicator, profilePictureView, labelStackView ]) + let stackView = UIStackView(arrangedSubviews: [ unreadMessagesIndicatorView, profilePictureView, labelStackView ]) stackView.axis = .horizontal stackView.alignment = .center stackView.spacing = Values.mediumSpacing contentView.addSubview(stackView) // Set up the constraints - unreadMessagesIndicator.pin(.top, to: .top, of: stackView) - unreadMessagesIndicator.pin(.bottom, to: .bottom, of: stackView) + unreadMessagesIndicatorView.pin(.top, to: .top, of: stackView) + unreadMessagesIndicatorView.pin(.bottom, to: .bottom, of: stackView) + timestampLabel.setContentCompressionResistancePriority(.required, for: NSLayoutConstraint.Axis.horizontal) + statusIndicatorView.set(.width, to: Values.conversationCellStatusIndicatorSize) + statusIndicatorView.set(.height, to: Values.conversationCellStatusIndicatorSize) snippetLabel.pin(to: snippetLabelContainer) typingIndicatorView.pin(.leading, to: .leading, of: snippetLabelContainer) typingIndicatorView.centerYAnchor.constraint(equalTo: snippetLabel.centerYAnchor).isActive = true @@ -75,18 +105,13 @@ final class ConversationCell : UITableViewCell { stackView.pin(.top, to: .top, of: contentView) contentView.pin(.trailing, to: .trailing, of: stackView, withInset: Values.mediumSpacing) contentView.pin(.bottom, to: .bottom, of: stackView) - stackView.set(.width, to: UIScreen.main.bounds.width - 2 * Values.mediumSpacing) // Workaround for weird constraints issue + stackView.set(.width, to: UIScreen.main.bounds.width - Values.mediumSpacing) // Workaround for weird constraints issue } // MARK: Updating private func update() { LokiAPI.populateUserHexEncodedPublicKeyCacheIfNeeded(for: threadViewModel.threadRecord.uniqueId!) // FIXME: This is a terrible place to do this - unreadMessagesIndicator.isHidden = !threadViewModel.hasUnreadMessages - if threadViewModel.hasUnreadMessages { - backgroundColor = UIColor(hex: 0x1B1B1B) - } else { - backgroundColor = .clear - } + unreadMessagesIndicatorView.alpha = threadViewModel.hasUnreadMessages ? 1 : 0 if threadViewModel.isGroupThread { let users = LokiAPI.userHexEncodedPublicKeyCache[threadViewModel.threadRecord.uniqueId!] ?? [] let randomUsers = users.sorted().prefix(2) // Sort to provide a level of stability @@ -102,6 +127,7 @@ final class ConversationCell : UITableViewCell { } profilePictureView.update() displayNameLabel.text = getDisplayName() + timestampLabel.text = DateUtil.formatDateShort(threadViewModel.lastMessageDate) if SSKEnvironment.shared.typingIndicators.typingRecipientId(forThread: self.threadViewModel.threadRecord) != nil { snippetLabel.text = "" typingIndicatorView.isHidden = false @@ -111,6 +137,21 @@ final class ConversationCell : UITableViewCell { typingIndicatorView.isHidden = true typingIndicatorView.stopAnimation() } + let lastMessage = threadViewModel.lastMessageForInbox + if let lastMessage = lastMessage as? TSOutgoingMessage { + let image: UIImage + let status = MessageRecipientStatusUtils.recipientStatus(outgoingMessage: lastMessage) + switch status { + case .calculatingPoW, .uploading, .sending: image = #imageLiteral(resourceName: "Cog") + case .sent, .skipped, .delivered: image = #imageLiteral(resourceName: "TickOutline") + case .read: image = #imageLiteral(resourceName: "TickFilled") + case .failed: image = #imageLiteral(resourceName: "message_status_failed") + } + statusIndicatorView.image = image + statusIndicatorView.isHidden = false + } else { + statusIndicatorView.isHidden = true + } } private func getDisplayName() -> String { @@ -133,11 +174,12 @@ final class ConversationCell : UITableViewCell { private func getSnippet() -> NSMutableAttributedString { let result = NSMutableAttributedString() if threadViewModel.isMuted { - result.append(NSAttributedString(string: "\u{e067} ", attributes: [ .font : UIFont.ows_elegantIconsFont(10), .foregroundColor : Colors.unimportant ])) + result.append(NSAttributedString(string: "\u{e067} ", attributes: [ .font : UIFont.ows_elegantIconsFont(10), .foregroundColor : Colors.unimportant ])) } if let rawSnippet = threadViewModel.lastMessageText { let snippet = MentionUtilities.highlightMentions(in: rawSnippet, threadID: threadViewModel.threadRecord.uniqueId!) - result.append(NSAttributedString(string: snippet, attributes: [ .font : UIFont.systemFont(ofSize: Values.smallFontSize), .foregroundColor : Colors.text ])) + let font = threadViewModel.hasUnreadMessages ? UIFont.boldSystemFont(ofSize: Values.smallFontSize) : UIFont.systemFont(ofSize: Values.smallFontSize) + result.append(NSAttributedString(string: snippet, attributes: [ .font : font, .foregroundColor : Colors.text ])) } return result } diff --git a/Signal/src/Loki/HomeVC.swift b/Signal/src/Loki/HomeVC.swift index 18768828a..956ad0dfd 100644 --- a/Signal/src/Loki/HomeVC.swift +++ b/Signal/src/Loki/HomeVC.swift @@ -1,6 +1,8 @@ -public final class HomeVC : UIViewController, UITableViewDataSource, UITableViewDelegate { +final class HomeVC : UIViewController, UITableViewDataSource, UITableViewDelegate, UIViewControllerPreviewingDelegate { private var threadViewModelCache: [String:ThreadViewModel] = [:] + private var isObservingDatabase = true + private var isViewVisible = false { didSet { updateIsObservingDatabase() } } private var threads: YapDatabaseViewMappings = { let result = YapDatabaseViewMappings(groups: [ TSInboxGroup ], view: TSThreadDatabaseViewExtensionName) @@ -14,10 +16,14 @@ public final class HomeVC : UIViewController, UITableViewDataSource, UITableView return result }() + private let editingDatabaseConnection = OWSPrimaryStorage.shared().newDatabaseConnection() + // MARK: Settings public override var preferredStatusBarStyle: UIStatusBarStyle { return .lightContent } // MARK: Components + private lazy var searchBar = SearchBar() + private lazy var tableView: UITableView = { let result = UITableView() result.backgroundColor = .clear @@ -27,20 +33,97 @@ public final class HomeVC : UIViewController, UITableViewDataSource, UITableView }() // MARK: Lifecycle - public override func viewDidLoad() { + override func viewDidLoad() { // Set gradient background view.backgroundColor = .clear let gradient = Gradients.defaultLokiBackground view.setGradient(gradient) + // Set navigation bar background color + if let navigationBar = navigationController?.navigationBar { + navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default) + navigationBar.shadowImage = UIImage() + navigationBar.isTranslucent = false + navigationBar.barTintColor = Colors.navigationBarBackground + } + // Set up the navigation bar buttons + updateNavigationBarButtons() // Customize title - navigationItem.title = NSLocalizedString("Messages", comment: "") - navigationController?.navigationBar.titleTextAttributes = [ .foregroundColor : Colors.text, .font : UIFont.boldSystemFont(ofSize: Values.veryLargeFontSize) ] + let titleLabel = UILabel() + titleLabel.text = NSLocalizedString("Messages", comment: "") + titleLabel.textColor = Colors.text + titleLabel.font = UIFont.boldSystemFont(ofSize: Values.veryLargeFontSize) + navigationItem.titleView = titleLabel // Set up table view tableView.dataSource = self tableView.delegate = self view.addSubview(tableView) tableView.pin(to: view) + // Set up search bar + tableView.tableHeaderView = searchBar + searchBar.sizeToFit() + tableView.contentOffset = CGPoint(x: 0, y: searchBar.frame.height) + // Set up new conversation button +// let newConversationButton = UIImageView(image: #imageLiteral(resourceName: "ic_plus_24").asTintedImage(color: UIColor(hex: 0x121212))) +// newConversationButton.backgroundColor = Colors.accent +// newConversationButton.set(.width, to: Values.newConversationButtonSize) +// newConversationButton.set(.height, to: Values.newConversationButtonSize) +// view.addSubview(newConversationButton) +// newConversationButton.center(.horizontal, in: view) +// newConversationButton.center(.vertical, in: view) + // Set up previewing + if (traitCollection.forceTouchCapability == .available) { + registerForPreviewing(with: self, sourceView: tableView) + } + // Listen for notifications + let notificationCenter = NotificationCenter.default + notificationCenter.addObserver(self, selector: #selector(handleYapDatabaseModifiedNotification(_:)), name: .YapDatabaseModified, object: OWSPrimaryStorage.shared().dbNotificationObject) + notificationCenter.addObserver(self, selector: #selector(handleYapDatabaseModifiedExternallyNotification(_:)), name: .YapDatabaseModifiedExternally, object: OWSPrimaryStorage.shared().dbNotificationObject) + notificationCenter.addObserver(self, selector: #selector(handleApplicationDidBecomeActiveNotification(_:)), name: .OWSApplicationDidBecomeActive, object: nil) + notificationCenter.addObserver(self, selector: #selector(handleApplicationWillResignActiveNotification(_:)), name: .OWSApplicationWillResignActive, object: nil) + notificationCenter.addObserver(self, selector: #selector(handleLocalProfileDidChangeNotification(_:)), name: Notification.Name(kNSNotificationName_LocalProfileDidChange), object: nil) + // Set up public chats and RSS feeds if needed + if OWSIdentityManager.shared().identityKeyPair() != nil { + let appDelegate = UIApplication.shared.delegate as! AppDelegate + appDelegate.setUpDefaultPublicChatsIfNeeded() + appDelegate.createRSSFeedsIfNeeded() + LokiPublicChatManager.shared.startPollersIfNeeded() + appDelegate.startRSSFeedPollersIfNeeded() + } // Do initial update + reload() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + isViewVisible = true + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + isViewVisible = false + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + // MARK: Data + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return Int(threads.numberOfItems(inGroup: TSInboxGroup)) + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: ConversationCell.reuseIdentifier) as! ConversationCell + cell.threadViewModel = threadViewModel(at: indexPath.row) + return cell + } + + // MARK: Updating + private func updateIsObservingDatabase() { + isObservingDatabase = isViewVisible && CurrentAppContext().isAppForegroundAndActive() + } + + private func reload() { uiDatabaseConnection.beginLongLivedReadTransaction() uiDatabaseConnection.read { transaction in self.threads.update(with: transaction) @@ -48,15 +131,156 @@ public final class HomeVC : UIViewController, UITableViewDataSource, UITableView tableView.reloadData() } - // MARK: Data - public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return Int(threads.numberOfItems(inGroup: TSInboxGroup)) + @objc private func handleYapDatabaseModifiedExternallyNotification(_ notification: Notification) { + guard isObservingDatabase else { return } + reload() } - public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: ConversationCell.reuseIdentifier) as! ConversationCell - cell.threadViewModel = threadViewModel(at: indexPath.row) - return cell + @objc private func handleYapDatabaseModifiedNotification(_ notification: Notification) { + guard isObservingDatabase else { return } + let transaction = uiDatabaseConnection.beginLongLivedReadTransaction() + let hasChanges = (uiDatabaseConnection.ext(TSThreadDatabaseViewExtensionName) as! YapDatabaseViewConnection).hasChanges(forGroup: TSInboxGroup, in: transaction) + guard hasChanges else { + uiDatabaseConnection.read { transaction in + self.threads.update(with: transaction) + } + return + } + var sectionChanges = NSArray() + var rowChanges = NSArray() + (uiDatabaseConnection.ext(TSThreadDatabaseViewExtensionName) as! YapDatabaseViewConnection).getSectionChanges(§ionChanges, rowChanges: &rowChanges, for: transaction, with: threads) + guard sectionChanges.count > 0 || rowChanges.count > 0 else { return } + tableView.beginUpdates() + rowChanges.forEach { rowChange in + let rowChange = rowChange as! YapDatabaseViewRowChange + let key = rowChange.collectionKey.key + threadViewModelCache[key] = nil + switch rowChange.type { + case .delete: tableView.deleteRows(at: [ rowChange.indexPath! ], with: UITableView.RowAnimation.automatic) + case .insert: tableView.insertRows(at: [ rowChange.newIndexPath! ], with: UITableView.RowAnimation.automatic) + case .move: + tableView.deleteRows(at: [ rowChange.indexPath! ], with: UITableView.RowAnimation.automatic) + tableView.insertRows(at: [ rowChange.newIndexPath! ], with: UITableView.RowAnimation.automatic) + case .update: + tableView.reloadRows(at: [ rowChange.indexPath! ], with: UITableView.RowAnimation.none) + default: break + } + } + tableView.endUpdates() + } + + @objc private func handleApplicationDidBecomeActiveNotification(_ notification: Notification) { + updateIsObservingDatabase() + } + + @objc private func handleApplicationWillResignActiveNotification(_ notification: Notification) { + updateIsObservingDatabase() + } + + @objc private func handleLocalProfileDidChangeNotification(_ notification: Notification) { + updateNavigationBarButtons() + } + + private func updateNavigationBarButtons() { + let profilePictureSize = Values.verySmallProfilePictureSize + let profilePictureView = ProfilePictureView() + profilePictureView.size = profilePictureSize + let userHexEncodedPublicKey = OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey + profilePictureView.hexEncodedPublicKey = userHexEncodedPublicKey + profilePictureView.update() + profilePictureView.set(.width, to: profilePictureSize) + profilePictureView.set(.height, to: profilePictureSize) + profilePictureView.onTap = { [weak self] in self?.openSettings() } + navigationItem.leftBarButtonItem = UIBarButtonItem(customView: profilePictureView) + let createPrivateGroupChatButton = UIBarButtonItem(image: #imageLiteral(resourceName: "Contacts"), style: .plain, target: self, action: #selector(createPrivateGroupChat)) + createPrivateGroupChatButton.tintColor = Colors.text + let joinPublicChatButton = UIBarButtonItem(image: #imageLiteral(resourceName: "Globe"), style: .plain, target: self, action: #selector(joinPublicChat)) + joinPublicChatButton.tintColor = Colors.text + navigationItem.rightBarButtonItems = [ createPrivateGroupChatButton, joinPublicChatButton ] + } + + // MARK: Interaction + func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? { + guard let indexPath = tableView.indexPathForRow(at: location), let thread = self.thread(at: indexPath.row) else { return nil } + previewingContext.sourceRect = tableView.rectForRow(at: indexPath) + let conversationVC = ConversationViewController() + conversationVC.configure(for: thread, action: .none, focusMessageId: nil) + conversationVC.peekSetup() + return conversationVC + } + + func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) { + guard let conversationVC = viewControllerToCommit as? ConversationViewController else { return } + conversationVC.popped() + navigationController?.pushViewController(conversationVC, animated: false) + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let thread = self.thread(at: indexPath.row) else { return } + show(thread, with: ConversationViewAction.none, animated: true) + tableView.deselectRow(at: indexPath, animated: true) + } + + private func show(_ thread: TSThread, with action: ConversationViewAction, animated: Bool) { + DispatchMainThreadSafe { + let conversationVC = ConversationViewController() + conversationVC.configure(for: thread, action: action, focusMessageId: nil) // TODO: focusMessageId + self.navigationController?.setViewControllers([ self, conversationVC ], animated: true) + } + } + + func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { + guard let threadID = self.thread(at: indexPath.row)?.uniqueId else { return false } + var publicChat: LokiPublicChat? + OWSPrimaryStorage.shared().dbReadConnection.read { transaction in + publicChat = LokiDatabaseUtilities.getPublicChat(for: threadID, in: transaction) + } + if let publicChat = publicChat { + return publicChat.isDeletable + } else { + return true + } + } + + func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? { + guard let thread = self.thread(at: indexPath.row) else { return [] } + var publicChat: LokiPublicChat? + OWSPrimaryStorage.shared().dbReadConnection.read { transaction in + publicChat = LokiDatabaseUtilities.getPublicChat(for: thread.uniqueId!, in: transaction) + } + let delete = UITableViewRowAction(style: .destructive, title: NSLocalizedString("Delete", comment: "")) { [weak self] action, indexPath in + let alert = UIAlertController(title: NSLocalizedString("CONVERSATION_DELETE_CONFIRMATION_ALERT_TITLE", comment: ""), message: NSLocalizedString("CONVERSATION_DELETE_CONFIRMATION_ALERT_MESSAGE", comment: ""), preferredStyle: .alert) + alert.addAction(UIAlertAction(title: NSLocalizedString("TXT_DELETE_TITLE", comment: ""), style: .destructive) { _ in + guard let self = self else { return } + self.editingDatabaseConnection.readWrite { transaction in + thread.remove(with: transaction) + } + NotificationCenter.default.post(name: .threadDeleted, object: nil, userInfo: [ "threadId" : thread.uniqueId! ]) + }) + alert.addAction(UIAlertAction(title: NSLocalizedString("TXT_CANCEL_TITLE", comment: ""), style: .default) { _ in }) + guard let self = self else { return } + self.present(alert, animated: true, completion: nil) + } + if let publicChat = publicChat { + return publicChat.isDeletable ? [ delete ] : [] + } else { + return [ delete ] + } + } + + @objc private func openSettings() { + let navigationController = AppSettingsViewController.inModalNavigationController() + present(navigationController, animated: true, completion: nil) + } + + @objc private func joinPublicChat() { + let joinPublicChatVC = JoinPublicChatVC() + let navigationController = OWSNavigationController(rootViewController: joinPublicChatVC) + present(navigationController, animated: true, completion: nil) + } + + @objc private func createPrivateGroupChat() { + // TODO: Implement } // MARK: Convenience diff --git a/Signal/src/Loki/Messaging/NewPublicChatVC.swift b/Signal/src/Loki/Messaging/JoinPublicChatVC.swift similarity index 98% rename from Signal/src/Loki/Messaging/NewPublicChatVC.swift rename to Signal/src/Loki/Messaging/JoinPublicChatVC.swift index 205c18a65..42deb659a 100644 --- a/Signal/src/Loki/Messaging/NewPublicChatVC.swift +++ b/Signal/src/Loki/Messaging/JoinPublicChatVC.swift @@ -1,6 +1,6 @@ -@objc(LKNewPublicChatVC) -final class NewPublicChatVC : OWSViewController { +@objc(LKJoinPublicChatVC) +final class JoinPublicChatVC : OWSViewController { // MARK: Components private lazy var urlTextField: UITextField = { diff --git a/Signal/src/Loki/ProfilePictureView.swift b/Signal/src/Loki/ProfilePictureView.swift index f712cec63..88ba18440 100644 --- a/Signal/src/Loki/ProfilePictureView.swift +++ b/Signal/src/Loki/ProfilePictureView.swift @@ -1,22 +1,23 @@ -public final class ProfilePictureView : UIView { +final class ProfilePictureView : UIView { private var imageViewWidthConstraint: NSLayoutConstraint! private var imageViewHeightConstraint: NSLayoutConstraint! - public var size: CGFloat! - public var hexEncodedPublicKey: String! - public var additionalHexEncodedPublicKey: String? + var size: CGFloat! + var hexEncodedPublicKey: String! + var additionalHexEncodedPublicKey: String? + var onTap: (() -> Void)? = nil // MARK: Components private lazy var imageView = getImageView() private lazy var additionalImageView = getImageView() // MARK: Lifecycle - public override init(frame: CGRect) { + override init(frame: CGRect) { super.init(frame: frame) initialize() } - public required init?(coder: NSCoder) { + required init?(coder: NSCoder) { super.init(coder: coder) initialize() } @@ -34,21 +35,32 @@ public final class ProfilePictureView : UIView { additionalImageView.set(.width, to: additionalImageViewSize) additionalImageView.set(.height, to: additionalImageViewSize) additionalImageView.layer.cornerRadius = additionalImageViewSize / 2 + // Set up gesture recognizer + let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap)) + addGestureRecognizer(gestureRecognizer) } // MARK: Updating - public func update() { + func update() { if let imageViewWidthConstraint = imageViewWidthConstraint, let imageViewHeightConstraint = imageViewHeightConstraint { imageView.removeConstraint(imageViewWidthConstraint) imageView.removeConstraint(imageViewHeightConstraint) } + func getProfilePicture(of size: CGFloat, for hexEncodedPublicKey: String) -> UIImage { + let userHexEncodedPublicKey = OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey + if hexEncodedPublicKey == userHexEncodedPublicKey, let profilePicture = OWSProfileManager.shared().localProfileAvatarImage() { + return profilePicture + } else { + return Identicon.generateIcon(string: hexEncodedPublicKey, size: size) + } + } let size: CGFloat if let additionalHexEncodedPublicKey = additionalHexEncodedPublicKey { size = Values.smallProfilePictureSize imageViewWidthConstraint = imageView.set(.width, to: size) imageViewHeightConstraint = imageView.set(.height, to: size) additionalImageView.isHidden = false - additionalImageView.image = Identicon.generateIcon(string: additionalHexEncodedPublicKey, size: size) + additionalImageView.image = getProfilePicture(of: size, for: additionalHexEncodedPublicKey) } else { size = self.size imageViewWidthConstraint = imageView.pin(.trailing, to: .trailing, of: self) @@ -56,17 +68,23 @@ public final class ProfilePictureView : UIView { additionalImageView.isHidden = true additionalImageView.image = nil } - imageView.image = Identicon.generateIcon(string: hexEncodedPublicKey, size: size) + imageView.image = getProfilePicture(of: size, for: hexEncodedPublicKey) imageView.layer.cornerRadius = size / 2 } + // MARK: Interaction + @objc private func handleTap() { + onTap?() + } + // MARK: Convenience private func getImageView() -> UIImageView { let result = UIImageView() result.layer.masksToBounds = true result.backgroundColor = Colors.unimportant - result.layer.borderColor = Colors.border.cgColor + result.layer.borderColor = Colors.profilePictureBorder.cgColor result.layer.borderWidth = Values.profilePictureBorderThickness + result.contentMode = .scaleAspectFit return result } } diff --git a/Signal/src/Loki/SearchBar.swift b/Signal/src/Loki/SearchBar.swift new file mode 100644 index 000000000..d6232929a --- /dev/null +++ b/Signal/src/Loki/SearchBar.swift @@ -0,0 +1,37 @@ + +final class SearchBar : UISearchBar { + + override init(frame: CGRect) { + super.init(frame: frame) + update() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + update() + } + + private func update() { + searchBarStyle = .minimal // Hide the border around the search bar + barStyle = .black // Use Apple's black design as a base + tintColor = Colors.accent // The cursor color + let searchImage = #imageLiteral(resourceName: "searchbar_search").asTintedImage(color: Colors.searchBarPlaceholder)! + setImage(searchImage, for: .search, state: .normal) + let clearImage = #imageLiteral(resourceName: "searchbar_clear").asTintedImage(color: Colors.searchBarPlaceholder)! + setImage(clearImage, for: .clear, state: .normal) + searchTextField.backgroundColor = Colors.searchBarBackground // The search bar background color + searchTextField.textColor = Colors.text + searchTextField.attributedPlaceholder = NSAttributedString(string: NSLocalizedString("Search", comment: ""), attributes: [ .foregroundColor : Colors.searchBarPlaceholder ]) + searchTextField.keyboardAppearance = .dark + setPositionAdjustment(UIOffset(horizontal: 4, vertical: 0), for: UISearchBar.Icon.search) + searchTextPositionAdjustment = UIOffset(horizontal: 2, vertical: 0) + setPositionAdjustment(UIOffset(horizontal: -4, vertical: 0), for: UISearchBar.Icon.clear) + searchTextField.removeConstraints(searchTextField.constraints) + searchTextField.pin(.leading, to: .leading, of: searchTextField.superview!, withInset: Values.mediumSpacing + 3) + searchTextField.pin(.top, to: .top, of: searchTextField.superview!, withInset: 10) + searchTextField.superview!.pin(.trailing, to: .trailing, of: searchTextField, withInset: Values.mediumSpacing + 3) + searchTextField.superview!.pin(.bottom, to: .bottom, of: searchTextField, withInset: 10) + searchTextField.set(.height, to: Values.searchBarHeight) + searchTextField.set(.width, to: UIScreen.main.bounds.width - 2 * Values.mediumSpacing) + } +} diff --git a/Signal/src/Loki/Utilities/Style Guide/Colors.swift b/Signal/src/Loki/Utilities/Style Guide/Colors.swift index 38239f9e6..a92bc1f54 100644 --- a/Signal/src/Loki/Utilities/Style Guide/Colors.swift +++ b/Signal/src/Loki/Utilities/Style Guide/Colors.swift @@ -1,7 +1,7 @@ -@objc public extension UIColor { +@objc extension UIColor { - @objc convenience init(hex value: UInt) { // Doesn't need to be declared public because the extension is already public + @objc convenience init(hex value: UInt) { let red = CGFloat((value >> 16) & 0xff) / 255 let green = CGFloat((value >> 8) & 0xff) / 255 let blue = CGFloat((value >> 0) & 0xff) / 255 @@ -10,10 +10,15 @@ } @objc(LKColors) -public final class Colors : NSObject { +final class Colors : NSObject { - @objc public static let accent = UIColor(hex: 0x00F782) - @objc public static let text = UIColor(hex: 0xFFFFFF) - @objc public static let unimportant = UIColor(hex: 0xD8D8D8) - @objc public static let border = UIColor(hex: 0x979797) + @objc static let accent = UIColor(hex: 0x00F782) + @objc static let text = UIColor(hex: 0xFFFFFF) + @objc static let unimportant = UIColor(hex: 0xD8D8D8) + @objc static let profilePictureBorder = UIColor(hex: 0x979797) + @objc static let conversationCellBackground = UIColor(hex: 0x1B1B1B) + @objc static let conversationCellSelected = UIColor(hex: 0x0C0C0C) + @objc static let navigationBarBackground = UIColor(hex: 0x161616) + @objc static let searchBarPlaceholder = UIColor(hex: 0x8E8E93) // Also used for the icons + @objc static let searchBarBackground = UIColor(red: 142 / 255, green: 142 / 255, blue: 147 / 255, alpha: 0.12) } diff --git a/Signal/src/Loki/Utilities/Style Guide/Gradients.swift b/Signal/src/Loki/Utilities/Style Guide/Gradients.swift index 2e26454cb..4b44f9e75 100644 --- a/Signal/src/Loki/Utilities/Style Guide/Gradients.swift +++ b/Signal/src/Loki/Utilities/Style Guide/Gradients.swift @@ -1,33 +1,31 @@ @objc(LKGradient) -public final class Gradient : NSObject { - public let start: UIColor - public let end: UIColor +final class Gradient : NSObject { + let start: UIColor + let end: UIColor private override init() { preconditionFailure("Use init(start:end:) instead.") } - @objc public init(start: UIColor, end: UIColor) { + @objc init(start: UIColor, end: UIColor) { self.start = start self.end = end super.init() } } -@objc public extension UIView { +@objc extension UIView { - @objc func setGradient(_ gradient: Gradient) { // Doesn't need to be declared public because the extension is already public + @objc func setGradient(_ gradient: Gradient) { let layer = CAGradientLayer() layer.frame = UIScreen.main.bounds layer.colors = [ gradient.start.cgColor, gradient.end.cgColor ] - layer.startPoint = CGPoint(x: 0.5, y: 0) - layer.endPoint = CGPoint(x: 0.5, y: 1) let index = UInt32((self.layer.sublayers ?? []).count) self.layer.insertSublayer(layer, at: index) } } @objc(LKGradients) -public final class Gradients : NSObject { +final class Gradients : NSObject { - @objc public 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)) } diff --git a/Signal/src/Loki/Utilities/Style Guide/Values.swift b/Signal/src/Loki/Utilities/Style Guide/Values.swift index d84632dfc..e5dc00cb1 100644 --- a/Signal/src/Loki/Utilities/Style Guide/Values.swift +++ b/Signal/src/Loki/Utilities/Style Guide/Values.swift @@ -1,26 +1,32 @@ @objc(LKValues) -public final class Values : NSObject { +final class Values : NSObject { // MARK: - Alpha Values - @objc public static let inactiveElementOpacity = CGFloat(0.6) + @objc static let inactiveElementOpacity = CGFloat(0.6) + @objc static let conversationCellTimestampOpacity = CGFloat(0.4) // MARK: - Font Sizes - @objc public static let smallFontSize = CGFloat(13) - @objc public static let mediumFontSize = CGFloat(15) - @objc public static let largeFontSize = CGFloat(20) - @objc public static let veryLargeFontSize = CGFloat(25) - @objc public static let massiveFontSize = CGFloat(50) + @objc static let smallFontSize = CGFloat(13) + @objc static let mediumFontSize = CGFloat(15) + @objc static let largeFontSize = CGFloat(20) + @objc static let veryLargeFontSize = CGFloat(25) + @objc static let massiveFontSize = CGFloat(50) // MARK: - Element Sizes - @objc public static let buttonHeight = CGFloat(34) - @objc public static let accentLineThickness = CGFloat(4) - @objc public static let smallProfilePictureSize = CGFloat(35) - @objc public static let mediumProfilePictureSize = CGFloat(45) - @objc public static let largeProfilePictureSize = CGFloat(65) - @objc public static let profilePictureBorderThickness = CGFloat(1) + @objc static let buttonHeight = CGFloat(34) + @objc static let accentLineThickness = CGFloat(4) + @objc static let verySmallProfilePictureSize = CGFloat(26) + @objc static let smallProfilePictureSize = CGFloat(35) + @objc static let mediumProfilePictureSize = CGFloat(45) + @objc static let largeProfilePictureSize = CGFloat(65) + @objc static let profilePictureBorderThickness = CGFloat(1) + @objc static let conversationCellStatusIndicatorSize = CGFloat(14) + @objc static let searchBarHeight = CGFloat(36) + @objc static let newConversationButtonSize = CGFloat(45) // MARK: - Distances - @objc public static let smallSpacing = CGFloat(8) - @objc public static let mediumSpacing = CGFloat(16) + @objc static let smallSpacing = CGFloat(8) + @objc static let mediumSpacing = CGFloat(16) + @objc static let largeSpacing = CGFloat(24) } diff --git a/Signal/src/ViewControllers/HomeView/HomeViewController.m b/Signal/src/ViewControllers/HomeView/HomeViewController.m index 01efc8aaf..91f862a6b 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewController.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewController.m @@ -867,8 +867,8 @@ typedef NS_ENUM(NSInteger, HomeViewControllerSection) { - (void)showNewPublicChatVC { - LKNewPublicChatVC *newPublicChatVC = [LKNewPublicChatVC new]; - OWSNavigationController *navigationController = [[OWSNavigationController alloc] initWithRootViewController:newPublicChatVC]; + LKJoinPublicChatVC *joinPublicChatVC = [LKJoinPublicChatVC new]; + OWSNavigationController *navigationController = [[OWSNavigationController alloc] initWithRootViewController:joinPublicChatVC]; [self.navigationController presentViewController:navigationController animated:YES completion:nil]; } diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 3ea6f7b20..9d1dda515 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -2677,3 +2677,5 @@ "Messages" = "Messages"; "Note to Self" = "Note to Self"; "New Group" = "New Group"; +"Delete" = "Delete"; +"Search" = "Search";