From 3fb85da558b7eb5b8dade1003a3beb9371517cde Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Sat, 30 Dec 2023 00:56:25 +0400 Subject: [PATCH] Add call localization --- .../Telegram-iOS/en.lproj/Localizable.strings | 11 ++ Tests/CallUITest/BUILD | 2 +- Tests/CallUITest/Sources/ViewController.swift | 2 + .../Sources/CallControllerNodeV2.swift | 1 + .../Components/Calls/CallScreen/BUILD | 1 + .../Call/Snow.imageset/Contents.json | 21 ++++ .../Call/Snow.imageset/Snow.png | Bin 0 -> 8351 bytes .../Sources/Components/BackButtonView.swift | 64 +++++++--- .../Sources/Components/ButtonGroupView.swift | 17 +-- .../Sources/PrivateCallScreen.swift | 119 +++++++++++++++--- 10 files changed, 197 insertions(+), 41 deletions(-) create mode 100644 submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Snow.imageset/Contents.json create mode 100644 submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Snow.imageset/Snow.png diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 7b396c5bc7b..58bce9ff44e 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -4418,6 +4418,7 @@ Sorry for the inconvenience."; "Call.Mute" = "mute"; "Call.Camera" = "camera"; +"Call.Video" = "video"; "Call.Flip" = "flip"; "Call.End" = "end"; "Call.Speaker" = "speaker"; @@ -5646,6 +5647,7 @@ Sorry for the inconvenience."; "Call.CameraConfirmationText" = "Switch to video call?"; "Call.CameraConfirmationConfirm" = "Switch"; +"Call.YourCameraOff" = "Your camera is off"; "Call.YourMicrophoneOff" = "Your microphone is off"; "Call.MicrophoneOff" = "%@'s microphone is off"; "Call.CameraOff" = "%@'s camera is off"; @@ -10867,3 +10869,12 @@ Sorry for the inconvenience."; "Chat.PlayOnceMesasgeClose" = "Close"; "Chat.PlayOnceMesasgeCloseAndDelete" = "Close and Delete"; "Chat.PlayOnceMesasge.DisableScreenCapture" = "Sorry, you can't play this message while screen recording is active."; + +"Call.EncryptedAlertTitle" = "This call is end-to-end encrypted"; +"Call.EncryptedAlertText" = "If the emoji on %@'s screen are the same, this call is 100% secure."; +"Call.EncryptionKeyTooltip" = "Encryption key of this call"; +"Call.StatusBusy" = "Line Busy"; +"Call.StatusDeclined" = "Call Declined"; +"Call.StatusFailed" = "Call Failed"; +"Call.StatusEnded" = "Call Ended"; +"Call.StatusMissed" = "Call Missed"; diff --git a/Tests/CallUITest/BUILD b/Tests/CallUITest/BUILD index a809c0ad086..7cb591d9173 100644 --- a/Tests/CallUITest/BUILD +++ b/Tests/CallUITest/BUILD @@ -43,6 +43,7 @@ swift_library( deps = [ "//submodules/Display", "//submodules/MetalEngine", + "//submodules/TelegramPresentationData", "//submodules/TelegramUI/Components/Calls/CallScreen", ], ) @@ -188,5 +189,4 @@ xcodeproj( }, }, default_xcode_configuration = "Debug" - ) diff --git a/Tests/CallUITest/Sources/ViewController.swift b/Tests/CallUITest/Sources/ViewController.swift index 7641bd93e7b..3178bdc66b5 100644 --- a/Tests/CallUITest/Sources/ViewController.swift +++ b/Tests/CallUITest/Sources/ViewController.swift @@ -4,6 +4,7 @@ import MetalEngine import Display import CallScreen import ComponentFlow +import TelegramPresentationData private extension UIScreen { private static let cornerRadiusKey: String = { @@ -24,6 +25,7 @@ private extension UIScreen { public final class ViewController: UIViewController { private var callScreenView: PrivateCallScreen? private var callState: PrivateCallScreen.State = PrivateCallScreen.State( + strings: defaultPresentationStrings, lifecycleState: .connecting, name: "Emma Walters", shortName: "Emma", diff --git a/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift b/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift index 812b95aee77..5fa6b1cd5a3 100644 --- a/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift +++ b/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift @@ -157,6 +157,7 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP } self.callScreenState = PrivateCallScreen.State( + strings: presentationData.strings, lifecycleState: .connecting, name: " ", shortName: " ", diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/BUILD b/submodules/TelegramUI/Components/Calls/CallScreen/BUILD index ac35b7c0f23..6773bd07e81 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/BUILD +++ b/submodules/TelegramUI/Components/Calls/CallScreen/BUILD @@ -68,6 +68,7 @@ swift_library( "//submodules/TelegramUI/Components/AnimatedTextComponent", "//submodules/AppBundle", "//submodules/UIKitRuntimeUtils", + "//submodules/TelegramPresentationData", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Snow.imageset/Contents.json b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Snow.imageset/Contents.json new file mode 100644 index 00000000000..de2d610148c --- /dev/null +++ b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Snow.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Snow.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Snow.imageset/Snow.png b/submodules/TelegramUI/Components/Calls/CallScreen/CallScreenAssets.xcassets/Call/Snow.imageset/Snow.png new file mode 100644 index 0000000000000000000000000000000000000000..2dddf3fa21b1642b5586706b8e65bd18683c57d5 GIT binary patch literal 8351 zcmYLPX*d+z`(_L?wlS8m#4w93O9&y`V6r75dv;zz*@Z@!p^SYgWecTOvJ=^t(b&q8 zWM4xQS;kV92K~HW{{Iipd7g95bDirt=fgSoeLpARrl|ot%LNuXIy!bEL#)NQ_xR7i zOy_ODa+c+}qYt()(50&y7Fs{=VE?f-^1N|_PU<`c)6vI!(*gfWIkyYvMn}g`L{GU|HuC&%C#=&(9t27jj%eF;q>39Xkk{9p^Qu755)p4?(XZyKq;Ct$MM)D1=H%4PuwofkQ{-pddb`S;fS1XhZtI;;w z5A1`h8b%(;MeYqqL?5Wt$cgriU|%_(vCuG0rPu_gb#e1r)1*|g)s8q{brrB+lkMsshnf`2*eh>(6Sy{#D` zH@E1O5cdj6{;_`{Ap@-R3J0J?HeLqDU*xTQ1UU1w`_GM?_@z&H^!512(9<)QDre?` zCe0*+XK5;9?8WPJed8HRH$NAWIzOHMWlC+3@EgfR?3o`E4Qi@(u76Z$fJSPn3JQV2 zln+sgYmMTKW)lTsIzpWttbK$rdeA%^KF>D<6VT5HmDqv< zZ7uct;3Wy!Wl|$xv_qsr_EhY<>KJ^FdciHSK6MxTWTeSVIBev;R6l=Cuzi*f4U|y# z1Xu~L?0~lM&as{3qGCQyDo5S;&G&D_S=NGAAdT{CQF7v=tNl~9t&S~hI@hRV;Rt&) zIsNH|jBAB-?`y@jf9+zi0V6^N#T>A+2N>XsI)38Y)gSjQlqR5$*Du^Od7&=7-L~*> zHUZ)O8OL3Q*0m~Yn#D&GQ-b$Y72L&m%nQ7R@=TomjvX5YlYgxt+aBugJ0t%JA2s&u z#>jVYE2I9H+HMrc|7|$iG>BQ*)Y8DqYnw`bct z7QFYst(c#Dzh0Fysv2*2cr=?@*&mL(FSjcTHvH;c&qOt+`Au{}^_S-T{*C9E}~T>_2SZ zNlFyCwoF?RnB(5}ae3GoeY@w0cdOSdr$@3UlB3Kzh^Sj%7KT;rvnpBDh+XOGWVLKi z7L#v0oc?9r3v$iL-ZFxA7Nu6N7+1NZ6_d`o90)N}UG0QX_Ar2My?Tqm2cPR^n{g2E~}ZB)>I&o!l$IIsGeQIBeu0@FLx-HKs+m zo?VNj4^`KgJbMfalc<6Owy#? z95^;#-X5GC+@x=Cg#9ae|6N2iS6In)Y>bpm_sLt6*NFA-o%W@?jgev8Y6nc!-iJ#I znr(m?e<0h$?*K8*ZYayi#==07Yycu@ye&$E_@f!FkGS^w-7)f6Sn;lGb*A=0Qp3th zu0c?zqmHQLMflPh0D1tK)JAK{zJ=5Rf22m77Hpo?rIruY8WH4 z$?C-71d-DBlX#63fZl#b2?@}^ApU|CK?=plnx+gK90j>n^k>7yg!t=j2Q71*C9Y;z z=x^Dg$eJWhS~c+FyZzwS*7uG&K%$b(R^?S(6?!tv1&R53N9v0?-a|L*t}io*FB4jt zs0}s_@S`dM8AML(K|x%6FGHliHg4C%S4}X>0S7s0ZWyR<=(DM<8y@ZzlQ^^1ov>B{ z_?nx2E1~HJ2Pc>QaJ>~4P-+;5W`JIo5mlF}ViaM@%V~!?DsU!8uCM@K7KE0EljB`> z_srN{;okRZ%)jC%U$Ta)-nSMGn*`QzKVdVp^hLugv*Jo_93E9wFqOd)q__nr++e#f zhG5U*{+&3CNBNzaGs6H$gwji%$CsX>6)aVPa20CLgp7Iz`H~z46&ITv$j3Dkr^5H= zMW^DOlw{H|3^wT!_ z1+@q1zL(IzZf&Q|gZRhU@Dvq>Gr0T2Jj*kN4W9|hADKg-lIBA!eUU@u$Z`rM?Ua9ji~^cn3Fry||C{k8MxjkPsqG z*b0`ypE8z>3sm+BM4vqfP<9EbF-1-_dJgFpvBL7UPXpk^cih|$elGO(;;1SOOpWVt zvQFR&3H^0%%L2vGwqie@9RjFU`-ibqCgO(5V|fsRhv1Wn^0?zKvTmXfh2-a!Zg$l) zj7<{?R&t{)@+)*;@dae{XM4WBp7^LUgokjtg}!1idnZU%B|_nQ zLy%4Y1YRoF@F+8;YIX`Zuvo|XGxqOS!FS!Zt;9v*tRD8)8A!kAkE2O0Erv#8FslfQ zpg~o z>NX!`Q+h#wEk}MFGF%zDlE%_ltKG zsH_Fj!JVrO(tNp$YRh|ZlbHw zF1@KsY~KqOF6To#IQ3KM3dZHC?xS)Cou>gh#+i}?mEk!-bcxSbSk>^xK1rS4VydUB zVC#ygoqzQo#S+%^guDA&TRDW-Mp1@%?(c;&;jI{6egQIHaloKlhuX&6kKxbF`#F z2e%9(g^P3*m!LD)yJ?U-3BX-wZDg$JQ?_o!j%X3+&dra^~+tPr(tnJ?zGl%IPu^7_Ht~oQ8^Am(690}}c z^hi$&z?RvQ>bN$hH36;q>DwvX*vkuo)RQ?=6IgC+%_0y-U`ezEI500TUjn)5tQuoH zfG;5FUS_Wn7Z=>90%=P7J?46A16_Z(NhA!Xy#yp`=i3pt$Zi^A`0|_D)q?`&C3F7F zbTNU1QrpAiz0AoL0B5QDCmbN<1;N3?7;CehoQJPXy{#xLKt_?N0A^G%l&^O4uhWty zH>*e(19tk*67imwppnjWqV7aST*CbncBG#WhJ;{lL`8n4ra7|{@7=BI z3RM@M&ZUXFJsHRRgA{y;S_n$%b%Tmk&3Dp!iCvIg1){a--=t7;X7#J zaj+HProCqr=vjIr?oEFCaTaf^TQVe;Xu_;!6Uxd3_9$TSqNr-Mjh#s_koPJrI8AC+6FP&I)>-`y3Xq-}+;oR8A8|>1JEq4aO=~EGQ6J4kqg7#&Olqci4l&MZ%UH+( z!(wp*rj5IJ*)&B}YOy3PJ!VKO*5hOO{Ru_uZc2pLwsC+ObTwn?4k7|uTA@e!ME}KV zD#ZnzFsIm2VNj7hw@-_b*PNuWY=gP@2x--NZNRKFzYt}!o=-+SN!$m(z3IWto_Jl0 z3R%f!)l0XD&GS+5CcHn=hFILW;xqL`DT#E!R$N~!lXj&^8>CtKKnQ3N;>0dmai;>O z5PmWJX`(Z8`Zm0-tDS{6w!GL7mRhor?u=p{p-#ry0BCu|{plsIdV0JD#=cSduel-} zBnB^UfIouh$<&`Y+K*aYXV;-dij$mJ7pd}bZGnD~ybB;$osgj+Mw|6atmy(+n3D?B zW{H9df%!$2=LPfGEiXFvT3$g}No6n}LI zGcI*h1KCDm1!lnaMkXg1vbm=TDsAd<4WU%*WHJpR29*kcHRmmU^((<$c|5!$R5~bYZRlx zWe8E$h2}WZ6a%?PT5k1||6<1Xn|XGw^H5>AVPb_Mf%_!tL(dQK3Z$oVRr?yLxPtfF zdg+4rkC{if4gQ|~&*a4(hoR?CR3V3`m_Sz92M7~UG*RkA5|jS=*g#%{e3{RVi~#6_ z4!n%vIP=VLPmo(hX87ZfH2b3L1va6SAc@`q7vyJUx6F6T0VZ~G9Ft~t34gvO2{4Sk zo)rR0ul?C$qs)F4!V5g<@57Ucs01@Bqu%Tyn^<*x94FCa)ruJ0tSyX~b24l>zm8O< zXRWdd!NzUqc-x7D`8K=WHvp)#E9cDAu_I)Gc>$7MGjNER-|Gd)I{tA_ueN_7<&}E?#SP*)tkTPX<0wj@4@@$~M z3FDf?=cP9?mKQYW(YIZ$Y*Vm$#E4`OD>+-(*0D~noDkRvk_uYp-wF_p;_qh{D92xd zzG<&8sL^!f6t!MSl@Hvz=;yY;QU#)*rd72mI}w)yLL$sCk5BUOnmmu2C&D@MC|9cy zy9x@X8N)AApfaUDViVm`f0~D-8%gH1YRUoNFpuwm*p6+QP-6NjWYG z>1#48haoYBaSYuV>-O|VA)<+6S^me+&-sf$U^6em_L7NK90RI=bu|)sGkwqrp?_u@ z+T-Q0(uq6r#ci~z)xRIpQxF3Yl*aKTdmYG+0?Dh!l^V>zo?$!rb9Oqdj}DLP9efF|GJ=YbYK9&1)Z&cnTq>G{an0 zWk2N2Y7nNTJR3%vSvvXopO8tIE-si!Cm#?Ayv)IvdJ$_YA>e_)Dc?-U)XhH{%m{xJ zN(IjtL*bY>XGSXMWY4ZVy()CQa~sZusT=5%Ca5#Qews{ox`FLNjg>JIt~SgoWq;k+DlzRfvqByuaeyr zsf}FN%raokOn5Yg-w~|?bYup`{Ib?8G?d(3Kzf?Fw0pd{v3F26EClPX#)`iEl90kr zZAa5}n}QCOW5Xv2pEeFb!ODFLZWI4tmEUJwuM;XL3Lqy zG`9c+d41?^3m+dMBRp}vA*S;a35R#%+OJ326eQsZ_0O}l*p7sy7adM60^JY`bEC*b);GI+g(1pyIFoIfkN$qN7$GN&?DgQ&n_NjFQAXu*7zNOiKF?UlrD&8U7>v#N&O z4)(Cyp+fg{hZ% zkdxj|M(PEg_}^=zPCc9S)=|Z30T_#aGD;)d<8B7B_c~Bh%CC^$ii$QHNUg*hf`2W9 zC2$y(`;Gt64^Sv=3JdP!&A!F`;q8(`j1ulAmlm;BND}zMcb=7=e$_iVkUv-x+QxH4 z*}+{oimKtsrZ(}!E_)XoxPSiuTj#i;A@T06J#=Ko;q$PwO@bggy-QmEoZxnYZ=xn+ z%jqp54Zus9B1HNGRIpmOVz^4Ub`yMOv>4SWcwaS44kQ6MJ2J>zVJDJ87JLG>yJ28?OaM!IjM$0yPQn!-p^UEyiZ&yM{@+0T;1qPwE^0{USi=D z9b}N&(eWQE7vQ)Kmk)|eH{igU9wdz^L%Uh)QsfwyZ^;nxUG{mp51SWklr#P7$@bXzEJO6OXp9t^NR^{1bA4ERgRmX=W2K8C%+`Wrl zHJWKs&<78c0_kInFN@@0Z^`m87>vsS_H)8m@hl7*Qi(o6X}05)L0cPlTA@(qSFp{0 z=kmN>OjQIoFnLeBJu-*ZK6sxNdSFoGFxc=|geLzQHG9Er&rB&FnvXc}$47cV|DeXMdjwYk|Bi5a@LDtqOFna+HpO zS3X(&s@y!^!JHaM{Mrb>qXfPEf&L(SVTH+94ub(K0mS5e7&XlP|8+e zq5;312qKJ~Y{MXTK%{ZzVk$R--3Vh#kEZK!IijCFI3y)v`LdZ8;^D>50@yoM%e+s7 zjx8ij@boXg|1+;!!#lOOurSawvmm&bT;29S)i<5P-ij+dUTGEe;eoDUfKnXF=(ftY ztT|^E;|KqVXC0ijVUh?*l)fue?AiTzektpOy#mQpOSq>tT*^8w>3IN<=kP_>g_Wv< z8dDoMWPolT6_CL8t^;^wjsFCsD=^YC5%{mbvSn)8j zXshw^Yr~mQ1#5%|4BhzTVUM3Fw^*qf1@%>tNXo*r=;5>9yer_~p%dv1dDajmsrqct zjwO{*8}cmne*DSmt$xnrSp%dc-Pw=2c+NPz)D0nlcV+E;(_3xlxX+K#hG4GI2mUr_ zZpFrIczHN2&cQ1BaA=DR^@Sto)~iyrL^)qEy0qJ7tP@8`{oCU>+#Yg)0dML;Iei!) z-UO`x`r=PQgtE)Wj$9Ac2jm5^4@cSv^{43{%1+wf(Eg&}oB#{is(|+o2iC+o1#d zI+Zfd~O!~seOuxE^Ngj(>oUxvkg=Lh(v?!isU?XLikIPFibb+z&su zy=W6DFWHjuW=0HISpi7kRfbjx1dHIL4_ zPuopyw0T}Wuu*+;ynD@DtmNLbAM54BQ98IlDtBKzSreTNm72KVAt1u7EaE+}CP}q= z#S*%zV57!)-#Q-Q6OFmkajcY<9yw_*2~zz0iht2@;DbE|Gw)rgyv3%lGyh|5*sE2R z7^82VUR0M+{fZeWKtfwLyl<(LhO#X)lCRvK#~Ri=vxQa1dEId`Rl*_#0m*{`+Rqdo_V(0B@5|CfQbp%b?yJDBuhs zgNVNd!DX*1#a3$lg3>tIO!wr8liK* zyT#Tc9#uQ#oXl}AO%Eyxe%kESzJO~vwE1EfV4}KmnusI-uQ#a??}p;UpA9c_XW$sJ zrqM=LjUVT^SrQ^`J9&==Gi}8@rvoHJ`ODV)6uIp7)@-qABZt@dx|}Hk z#vYM=WG-FG%cVOtayuOF?z38mm7_&dc|3CUux_XsA*|h@?$VuK-4YvO3J@$;jGI0vKF=k@MjB(X!F)WxRc*0b2>000UrhIZ_ma?gEj zSBuDt@RGy*6kWMm4?qZSlC<%mrKXmmv+w(4`$LL|`l<@N0D$U~6r<1WKe5DSxE`ci zzx4&^wYq*qV0Br>KWB#bg|+`#2ou>)SpE}u(^q9@zKQ4wsnr5&_|F#9BQh;U3Qu=g9N20diZ*Z`27nWHYqfUZ6Y7-6JT0kMH|n%oCXzzC?_)owF`d0n>7swLB% zcS{oVdkNo5_tNBFtqFJE6$^KYzQ5_sNjBiAjZ#)i{KL9=I@bwvfDo<5G~FcP8b*>e zWeKY9h3s0o%v=KT&gNJ9XxT!! zlg%-YR58>qJt}zrh099k3^hd~Hu@YUM{-mFQ{`SA6;3q7+>E-qac9zNqGM-3!t6T- z&)*k8N=<@CcAr9sE^z;mukjkkcc;dr3>kj;UVpRZF|R}P+H)^x6F;bD{i4deT>1rh fZBg^q Void)? - init(text: String) { + override init(frame: CGRect) { self.iconView = UIImageView(image: NavigationBar.backArrowImage(color: .white)) self.iconView.isUserInteractionEnabled = false self.textView = TextView() self.textView.isUserInteractionEnabled = false - let spacing: CGFloat = 8.0 - - var iconSize: CGSize = self.iconView.image?.size ?? CGSize(width: 2.0, height: 2.0) - let iconScaleFactor: CGFloat = 0.9 - iconSize.width = floor(iconSize.width * iconScaleFactor) - iconSize.height = floor(iconSize.height * iconScaleFactor) - - let textSize = self.textView.update(string: text, fontSize: 17.0, fontWeight: UIFont.Weight.regular.rawValue, color: .white, constrainedWidth: 100.0, transition: .immediate) - self.size = CGSize(width: iconSize.width + spacing + textSize.width, height: textSize.height) - - self.iconView.frame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((self.size.height - iconSize.height) * 0.5)), size: iconSize) - self.textView.frame = CGRect(origin: CGPoint(x: iconSize.width + spacing, y: floorToScreenPixels((self.size.height - textSize.height) * 0.5)), size: textSize) - - super.init(frame: CGRect()) + super.init(frame: frame) self.addSubview(self.iconView) self.addSubview(self.textView) @@ -53,4 +58,31 @@ final class BackButtonView: HighlightableButton { return nil } } + + func update(text: String) -> CGSize { + let params = Params(text: text) + if let currentLayout = self.currentLayout, currentLayout.params == params { + return currentLayout.size + } + let size = self.update(params: params) + self.currentLayout = Layout(params: params, size: size) + return size + } + + private func update(params: Params) -> CGSize { + let spacing: CGFloat = 8.0 + + var iconSize: CGSize = self.iconView.image?.size ?? CGSize(width: 2.0, height: 2.0) + let iconScaleFactor: CGFloat = 0.9 + iconSize.width = floor(iconSize.width * iconScaleFactor) + iconSize.height = floor(iconSize.height * iconScaleFactor) + + let textSize = self.textView.update(string: params.text, fontSize: 17.0, fontWeight: UIFont.Weight.regular.rawValue, color: .white, constrainedWidth: 100.0, transition: .immediate) + let size = CGSize(width: iconSize.width + spacing + textSize.width, height: textSize.height) + + self.iconView.frame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((size.height - iconSize.height) * 0.5)), size: iconSize) + self.textView.frame = CGRect(origin: CGPoint(x: iconSize.width + spacing, y: floorToScreenPixels((size.height - textSize.height) * 0.5)), size: textSize) + + return size + } } diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/ButtonGroupView.swift b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/ButtonGroupView.swift index 180fa18dca1..73c36b48cec 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/ButtonGroupView.swift +++ b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/ButtonGroupView.swift @@ -3,6 +3,7 @@ import UIKit import Display import ComponentFlow import AppBundle +import TelegramPresentationData final class ButtonGroupView: OverlayMaskContainerView { final class Button { @@ -85,7 +86,7 @@ final class ButtonGroupView: OverlayMaskContainerView { return result } - func update(size: CGSize, insets: UIEdgeInsets, minWidth: CGFloat, controlsHidden: Bool, displayClose: Bool, buttons: [Button], notices: [Notice], transition: Transition) -> CGFloat { + func update(size: CGSize, insets: UIEdgeInsets, minWidth: CGFloat, controlsHidden: Bool, displayClose: Bool, strings: PresentationStrings, buttons: [Button], notices: [Notice], transition: Transition) -> CGFloat { self.buttons = buttons let buttonSize: CGFloat = 56.0 @@ -190,7 +191,7 @@ final class ButtonGroupView: OverlayMaskContainerView { } } let closeButtonSize = CGSize(width: minWidth, height: buttonSize) - closeButtonView.update(text: "Close", size: closeButtonSize, transition: closeButtonTransition) + closeButtonView.update(text: strings.Common_Close, size: closeButtonSize, transition: closeButtonTransition) closeButtonTransition.setFrame(view: closeButtonView, frame: CGRect(origin: CGPoint(x: floor((size.width - closeButtonSize.width) * 0.5), y: buttonY), size: closeButtonSize)) if animateIn && !transition.animation.isImmediate { @@ -218,9 +219,9 @@ final class ButtonGroupView: OverlayMaskContainerView { case let .speaker(audioOutput): switch audioOutput { case .internalSpeaker, .speaker: - title = "speaker" + title = strings.Call_Speaker default: - title = "audio" + title = strings.Call_Audio } switch audioOutput { @@ -247,19 +248,19 @@ final class ButtonGroupView: OverlayMaskContainerView { isActive = true } case .flipCamera: - title = "flip" + title = strings.Call_Flip image = UIImage(bundleImageName: "Call/Flip") isActive = false case let .video(isActiveValue): - title = "video" + title = strings.Call_Video image = UIImage(bundleImageName: "Call/Video") isActive = isActiveValue case let .microphone(isActiveValue): - title = "mute" + title = strings.Call_Mute image = UIImage(bundleImageName: "Call/Mute") isActive = isActiveValue case .end: - title = "end" + title = strings.Call_End image = UIImage(bundleImageName: "Call/End") isActive = false isDestructive = true diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift index a53cfd48ad9..b974b71dc7c 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift +++ b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift @@ -7,6 +7,7 @@ import MetalEngine import ComponentFlow import SwiftSignalKit import UIKitRuntimeUtils +import TelegramPresentationData public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictureControllerDelegate { public struct State: Equatable { @@ -67,6 +68,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu case bluetooth } + public var strings: PresentationStrings public var lifecycleState: LifecycleState public var name: String public var shortName: String @@ -77,8 +79,10 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu public var localVideo: VideoSource? public var remoteVideo: VideoSource? public var isRemoteBatteryLow: Bool + public var displaySnowEffect: Bool public init( + strings: PresentationStrings, lifecycleState: LifecycleState, name: String, shortName: String, @@ -88,8 +92,10 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu isRemoteAudioMuted: Bool, localVideo: VideoSource?, remoteVideo: VideoSource?, - isRemoteBatteryLow: Bool + isRemoteBatteryLow: Bool, + displaySnowEffect: Bool = false ) { + self.strings = strings self.lifecycleState = lifecycleState self.name = name self.shortName = shortName @@ -100,9 +106,13 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu self.localVideo = localVideo self.remoteVideo = remoteVideo self.isRemoteBatteryLow = isRemoteBatteryLow + self.displaySnowEffect = displaySnowEffect } public static func ==(lhs: State, rhs: State) -> Bool { + if lhs.strings !== rhs.strings { + return false + } if lhs.lifecycleState != rhs.lifecycleState { return false } @@ -133,6 +143,9 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu if lhs.isRemoteBatteryLow != rhs.isRemoteBatteryLow { return false } + if lhs.displaySnowEffect != rhs.displaySnowEffect { + return false + } return true } } @@ -218,6 +231,8 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu private var pipVideoCallViewController: UIViewController? private var pipController: AVPictureInPictureController? + private var snowEffectView: SnowEffectView? + public override init(frame: CGRect) { self.overlayContentsView = UIView() self.overlayContentsView.isUserInteractionEnabled = false @@ -240,7 +255,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu self.titleView = TextView() self.statusView = StatusView() - self.backButtonView = BackButtonView(text: "Back") + self.backButtonView = BackButtonView(frame: CGRect()) self.pipView = PrivateCallPictureInPictureView(frame: CGRect(origin: CGPoint(), size: CGSize())) @@ -740,16 +755,16 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu var notices: [ButtonGroupView.Notice] = [] if !isTerminated { if params.state.isLocalAudioMuted { - notices.append(ButtonGroupView.Notice(id: AnyHashable(0 as Int), icon: "Call/CallToastMicrophone", text: "Your microphone is turned off")) + notices.append(ButtonGroupView.Notice(id: AnyHashable(0 as Int), icon: "Call/CallToastMicrophone", text: params.state.strings.Call_YourMicrophoneOff)) } if params.state.isRemoteAudioMuted { - notices.append(ButtonGroupView.Notice(id: AnyHashable(1 as Int), icon: "Call/CallToastMicrophone", text: "\(params.state.shortName)'s microphone is turned off")) + notices.append(ButtonGroupView.Notice(id: AnyHashable(1 as Int), icon: "Call/CallToastMicrophone", text: params.state.strings.Call_MicrophoneOff(params.state.shortName).string)) } if params.state.remoteVideo != nil && params.state.localVideo == nil { - notices.append(ButtonGroupView.Notice(id: AnyHashable(2 as Int), icon: "Call/CallToastCamera", text: "Your camera is turned off")) + notices.append(ButtonGroupView.Notice(id: AnyHashable(2 as Int), icon: "Call/CallToastCamera", text: params.state.strings.Call_YourCameraOff)) } if params.state.isRemoteBatteryLow { - notices.append(ButtonGroupView.Notice(id: AnyHashable(3 as Int), icon: "Call/CallToastBattery", text: "\(params.state.shortName)'s battery is low")) + notices.append(ButtonGroupView.Notice(id: AnyHashable(3 as Int), icon: "Call/CallToastBattery", text: params.state.strings.Call_BatteryLow(params.state.shortName).string)) } } @@ -759,7 +774,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu }*/ let displayClose = false - let contentBottomInset = self.buttonGroupView.update(size: params.size, insets: params.insets, minWidth: wideContentWidth, controlsHidden: currentAreControlsHidden, displayClose: displayClose, buttons: buttons, notices: notices, transition: transition) + let contentBottomInset = self.buttonGroupView.update(size: params.size, insets: params.insets, minWidth: wideContentWidth, controlsHidden: currentAreControlsHidden, displayClose: displayClose, strings: params.state.strings, buttons: buttons, notices: notices, transition: transition) var expandedEmojiKeyRect: CGRect? if self.isEmojiKeyExpanded { @@ -777,7 +792,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu alphaTransition = genericAlphaTransition } - emojiExpandedInfoView = EmojiExpandedInfoView(title: "This call is end-to-end encrypted", text: "If the emoji on \(params.state.shortName)'s screen are the same, this call is 100% secure.") + emojiExpandedInfoView = EmojiExpandedInfoView(title: params.state.strings.Call_EncryptedAlertTitle, text: params.state.strings.Call_EncryptedAlertText(params.state.shortName).string) self.emojiExpandedInfoView = emojiExpandedInfoView emojiExpandedInfoView.alpha = 0.0 Transition.immediate.setScale(view: emojiExpandedInfoView, scale: 0.5) @@ -824,13 +839,15 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu } } + let backButtonSize = self.backButtonView.update(text: params.state.strings.Common_Back) + let backButtonY: CGFloat if currentAreControlsHidden { - backButtonY = -self.backButtonView.size.height - 12.0 + backButtonY = -backButtonSize.height - 12.0 } else { backButtonY = params.insets.top + 12.0 } - let backButtonFrame = CGRect(origin: CGPoint(x: params.insets.left + 10.0, y: backButtonY), size: self.backButtonView.size) + let backButtonFrame = CGRect(origin: CGPoint(x: params.insets.left + 10.0, y: backButtonY), size: backButtonSize) transition.setFrame(view: self.backButtonView, frame: backButtonFrame) transition.setAlpha(view: self.backButtonView, alpha: currentAreControlsHidden ? 0.0 : 1.0) @@ -914,7 +931,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu emojiTooltipView = current } else { emojiTooltipTransition = emojiTooltipTransition.withAnimation(.none) - emojiTooltipView = EmojiTooltipView(text: "Encryption key of this call") + emojiTooltipView = EmojiTooltipView(text: params.state.strings.Call_EncryptionKeyTooltip) animateIn = true self.emojiTooltipView = emojiTooltipView self.addSubview(emojiTooltipView) @@ -1192,15 +1209,15 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu switch terminatedState.reason { case .busy: - titleString = "Line Busy" + titleString = params.state.strings.Call_StatusBusy case .declined: - titleString = "Call Declined" + titleString = params.state.strings.Call_StatusDeclined case .failed: - titleString = "Call Failed" + titleString = params.state.strings.Call_StatusFailed case .hangUp: - titleString = "Call Ended" + titleString = params.state.strings.Call_StatusEnded case .missed: - titleString = "Call Missed" + titleString = params.state.strings.Call_StatusMissed } default: displayAudioLevelBlob = !params.state.isRemoteAudioMuted @@ -1358,5 +1375,75 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu }) } } + + /*if params.state.displaySnowEffect { + let snowEffectView: SnowEffectView + if let current = self.snowEffectView { + snowEffectView = current + } else { + snowEffectView = SnowEffectView(frame: CGRect()) + self.snowEffectView = snowEffectView + self.maskContents.addSubview(snowEffectView) + } + transition.setFrame(view: snowEffectView, frame: CGRect(origin: CGPoint(), size: params.size)) + snowEffectView.update(size: params.size) + } else { + if let snowEffectView = self.snowEffectView { + self.snowEffectView = nil + snowEffectView.removeFromSuperview() + } + }*/ + } +} + +final class SnowEffectView: UIView { + private let particlesLayer: CAEmitterLayer + + override init(frame: CGRect) { + let particlesLayer = CAEmitterLayer() + self.particlesLayer = particlesLayer + self.particlesLayer.backgroundColor = nil + self.particlesLayer.isOpaque = false + + particlesLayer.emitterShape = .circle + particlesLayer.emitterMode = .surface + particlesLayer.renderMode = .oldestLast + + let image1 = UIImage(named: "Call/Snow")?.cgImage + + let cell1 = CAEmitterCell() + cell1.contents = image1 + cell1.name = "Snow" + cell1.birthRate = 92.0 + cell1.lifetime = 20.0 + cell1.velocity = 59.0 + cell1.velocityRange = -15.0 + cell1.xAcceleration = 5.0 + cell1.yAcceleration = 40.0 + cell1.emissionRange = 90.0 * (.pi / 180.0) + cell1.spin = -28.6 * (.pi / 180.0) + cell1.spinRange = 57.2 * (.pi / 180.0) + cell1.scale = 0.06 + cell1.scaleRange = 0.3 + cell1.color = UIColor(red: 255.0/255.0, green: 255.0/255.0, blue: 255.0/255.0, alpha: 1.0).cgColor + + particlesLayer.emitterCells = [cell1] + + super.init(frame: frame) + + self.layer.addSublayer(particlesLayer) + self.clipsToBounds = true + self.backgroundColor = nil + self.isOpaque = false + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(size: CGSize) { + self.particlesLayer.frame = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height) + self.particlesLayer.emitterSize = CGSize(width: size.width * 3.0, height: size.height * 2.0) + self.particlesLayer.emitterPosition = CGPoint(x: size.width * 0.5, y: -325.0) } }