From 4bd925ac2c730bca750fdf03dd0d1a0eee0c1e1c Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 2 Aug 2023 21:41:58 +0200 Subject: [PATCH] Implement simple UI for single-type likes in stories. --- Telegram/Resources/icons/chat/input_like.png | Bin 0 -> 699 bytes .../Resources/icons/chat/input_like@2x.png | Bin 0 -> 1356 bytes .../Resources/icons/chat/input_like@3x.png | Bin 0 -> 2003 bytes Telegram/Resources/icons/chat/input_liked.png | Bin 0 -> 559 bytes .../Resources/icons/chat/input_liked@2x.png | Bin 0 -> 942 bytes .../Resources/icons/chat/input_liked@3x.png | Bin 0 -> 1362 bytes Telegram/Resources/langs/lang.strings | 2 + .../chat_helpers/chat_helpers.style | 2 + .../chat_helpers/compose/compose_features.h | 1 + Telegram/SourceFiles/data/data_story.cpp | 9 +- Telegram/SourceFiles/data/data_story.h | 3 +- .../view/controls/compose_controls_common.h | 1 + .../history_view_compose_controls.cpp | 49 +++++++- .../controls/history_view_compose_controls.h | 7 ++ .../stories/media_stories_controller.cpp | 20 +++- .../media/stories/media_stories_controller.h | 7 +- .../media/stories/media_stories_reply.cpp | 11 ++ .../media/stories/media_stories_reply.h | 2 + .../media/stories/media_stories_stealth.cpp | 2 +- .../SourceFiles/media/view/media_view.style | 7 ++ .../media/view/media_view_overlay_opengl.cpp | 45 +++++--- .../media/view/media_view_overlay_opengl.h | 3 +- .../media/view/media_view_overlay_widget.cpp | 107 +++++++++++++----- .../media/view/media_view_overlay_widget.h | 13 ++- .../ui/effects/emoji_fly_animation.cpp | 3 +- .../ui/effects/emoji_fly_animation.h | 4 +- Telegram/SourceFiles/ui/menu_icons.style | 1 + 27 files changed, 235 insertions(+), 64 deletions(-) create mode 100644 Telegram/Resources/icons/chat/input_like.png create mode 100644 Telegram/Resources/icons/chat/input_like@2x.png create mode 100644 Telegram/Resources/icons/chat/input_like@3x.png create mode 100644 Telegram/Resources/icons/chat/input_liked.png create mode 100644 Telegram/Resources/icons/chat/input_liked@2x.png create mode 100644 Telegram/Resources/icons/chat/input_liked@3x.png diff --git a/Telegram/Resources/icons/chat/input_like.png b/Telegram/Resources/icons/chat/input_like.png new file mode 100644 index 0000000000000000000000000000000000000000..c7ccead7e2d8a4b30fce9a3dcb2d7f254b984943 GIT binary patch literal 699 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDq2jfl1ZV#WBP} z@NCH0?#e`weZ~ZuZ|y~{NCW}o$2etG%jmw!*U9Zocns{OeoLdQ(1cc0Pw21Xt( zS0-t(?$EVi{l}BFre1nk!qs}{@kfzvSA+X@ynYJ<3U=SMm>9O&S8X!G;~0gYEkd0v z3mz8C5K`?(FyLU~;nNlEba7yu*yCJkH$Q!Iq|ukN+$>CW`~OEblsGa@w3%`+p<&zg z*Q$!=D;G3ySUp%@aY1v<JY7?zqEz9S`X-paRY_U zrU`#u6tuE#Dbt3VImcuY9(g3K49VIWHABMa%ZX&8nau|k=ARF4;A>}2=sMd<8v2o$fmn77@zUVcJm=H!!GV)Ux_&WlPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NGwn;=mR9Fe^SXn5wT@*fkQ)GIFeD5QjxdAu=ap8fyx{i|=i@7LMq zJ?Hq(`7XY^7w4?A*0Y|q_ImeTd+(!CeLvq7_`@n-qC6faC#S5etc;8dA0Hn_N5{Ln zyS=@=rKP3*{{G|RV+CJdU*Cd)g6QaIe}8{lTifgF>%+st(b3V7k&%mw3k64wCL0@@ z#>U3S$H#v}&*$go`1p8COpLmJY;5f0yhYo~IO-&$IcT~~@1qJQz?`tBJ>$|$T zPyphB(B0iFw`!6>ju1*2$Zb(kQD9uCA^^$L#EE&=L}X5Kah_lo&mskB*LlgM%f5NWx^l zyu2hled1?&db$WJ=|Vz6upkJJaFRh?0vF-l-`|B3R^0{cTpI}Zsg61#Zq8kAjxRNtTd{yxVT9C@}49} zXlrZBsCh6s2jW`5BqhW+yjqrjk6>wO$$_MVuCA^~iw%{{Mio3gJ&7NEp94t=p{J1+ z8#SAaDtLH!5I?py4kRVSQ!Fhljm<_C;^N|ne|2?L5Xi#9;{N^~>kvl^efyArPMZq* z60P}{mlr!bJBCXYLW7!^n1I2=#H6OCh7m>*Dk>_789{GvZ^cnkQ&TBxAdEV-9UL6a z&(A3q_J46Uq@A0aBV0>MixTet+1lFLD9ZZ!I$nqp8D=y=ajZ#5NKk@))rNyMAEc6# zleN?EG4$ZzKtA2Hd+LC&va-UOqA~aM^yuKBQsH)Sa&kgGI2&jwsZNv}>8r1;ttC!;78Mog=U!i5&(1j5>*p*&BLf~}Yip|rqNu~0 z;`24O+uYn-MYtAYW@aXx{xC?~6-`Y|wKxdP*4EbZ^Yc_E?0-o~NkW)bWny9?UpR5t z()*Su4}b~Iivk-I7=NniB@V*=%ob^Lay; zktkujy}fsLcPXcbhX?F1O6Z@ql#~>J$qVjYUS2;fhWN(6A6zHQCoLP0N`*TlU$>DB z+zpY%uN){i-`=rga7+2sF8#{O%X@x)rrO}(Y~b2Raa1bIr?s^;iiE3Yd3m|qN^(g_ z30=4V!z{-AOC4iK4VKCB@-pRwMu$H}vz&Wun zI5>zihCxDng(N>eA8!XM9LoyZLUD2NS6B@0g9(Jk;I!X_-xc^u1^xx$3&oh>MItu< O0000Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS?JxN4CRA>e5TWd%aTNKt_q@|fc zRzG@3Wn~7^AECFx%?c!nj7Xs%$fU$TP$cL_(i`m|S(bu5D2YOgB2%yeMe|3gX{2aY z^nP5!-df!c=eo~2YtD>&&r#3e-gExAX79DW^{q8~_Uyg(=;h`4c{1S1fF}c<4Ezr= z;B75NZ*TA5;NY=6zmX$Hwzs!Gd-klmyW1L?uo^L9#FQyhCQqLHeV0nUsqRGOG`^mPR`=Ri-!yuBF|vx(4kRLQMtLfPo6ya z_3M{C>Yul7-xd}Y#>K_KwLGSR=CEPIwr<_}=FJ=C*x@C@-nnzzJ2=i>Dt;4g1nWHCMeOT_x}C+Mw+(vdGqGcWMH1P^*JZ~)$?->!)CpA?_SCi zv~7J3C9Ls;0cPiE)22C8b9p0(37!x@yAMhwB_+ZD&qE*DF73v;M^FWA#)a=M=mZE$ zrtMajlpYpzj2jP(zFw#$GjoSaN)IngMu`VTH&K{*n6_J8K7amP@L{WDwDZ6|e*7r3 zC0B7me~<;#5roI@-@gl46U+h&3JPM{ZgojldH<=vun822#@Af#fwt@qobpR4Fp*;DeV4HI(qadWr#|aEG~wuuynVZhWU){pi$wGjS6}( zF)<=A2v@FLQ7|50)$`}iQ*N6!ZBlc{inZ(3ty8lf7;V&KX$=n>D>^qy`9#TUYuveTFMCWWyoH#-Gl$V!V#G5c-LPtjj#X`m0 zB9_x;8#Zj9Og?}9Jay_+>!|D3ucvS*N-tTm#5$Z+ODqc!31yO;oNN`W*DgOlpCV$F zOqbQYS_AkOyW^)%pHdE2uU^Gjv9m7m$kx@>QCzGz^*dH)-5A*GYuY%`l`Z`mVA`7T zPGkqFw{G21o@Z@%vJzZezkZ$O6az9MB0|<)MsMN5h3r)6!i5X$Ku`upUI*ZCL8HPN zC^j}$9zj-f^XARe4djz2Ps$pr>j9j(+p%MZI*gnJJ6?k0?CfkgQ#Gx_hYz!;_V3@X z2BE5*mzT#}9y)YL6~tUWD=Uk+fKzjme(2Q?eULbK@L)ec_dp;`EUKcSq5*xFZOri% z3EjYIJdkFahE)j(Vo_mZF$mMIjW4$1cuO)8_}<8w_vVo!M;IWE8U{5gEk49{?b+2}WxM|*IM$EJvuDpL9fBeew92Ao3d6x=QEBF5nq2YeVS3c zefze_S{Y$u=gLgZoH?U3j8%DB(Z>Y9s$w9G!ioNSFrXvgJ9qA|s7{?aWntp6!qtKe zJIn|5`uci$N(xMDI#{I$YFI-#^Qy2wRoNsXBZJ)ve5NP%-I0-z_?VI@WM*b6yK#vD zN+YyLriFnW9lkkbA>lGTJ>4ZFEjrz}aU(6S>*GNSf_Gtc$YNkHTfKTUtH99kaJZU~ zkYF9%wU#(KEGsLc@o4hhyLbC}hp&s%=~|BtQQv@rC>)63KLs%Fq27c-C#pkwHBXBt l1D*_cGT_O8Cj-hE_y@$!OKwN9Y<2(u002ovPDHLkV1f}0vta-L literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/chat/input_liked.png b/Telegram/Resources/icons/chat/input_liked.png new file mode 100644 index 0000000000000000000000000000000000000000..5a267ca1dcc25a971c766b452bb6fcd064def113 GIT binary patch literal 559 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlgfY^JA+V~B;| z*(tW`njA!0ze;j(aZf!Ja7#+KV=5;b@4F`57i=+Hs-2>1bq#m%NOw-{VwJeq@XJr; zX}Utk+{qd5=2o9Oo|-lz=;d{{nOPmrD?KNzy85bWubJ=Xy8Z17RJ$Jl1$87e;ikP~-lPO7&*LHtu;7N)Q;!xo^ye4e51?#-qZ`=Og zEN;o1vo%I9(PzW5%b6<{FkM*5euUM)O(7uncHi{w;|6ZNor@yw%nUo!x9s1C!+x%B zYvoO6pA|n*IOqM_iOKu#@1In)ci!eXFUvd6C!|~Ub+53f-uM2vsOuvIj%S}MT^vnh z__{4FOSuLLDX3VKKVeyRb*6E~aRZ)NO?$#tKU8+GyzJ)d#8$ZY;t7VtRd@5|`z`OU zZDfjEe);6G2{-HZ*YAxhj?jqJ6OUo86bU-~%W>Avz8Oc7-4%qC-eNI=J6lD0=H^m=+yz%ziX8|u(Pn-1U$LdvG)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NF7)eAyR9Fe^SkFshQ53FmRPqNQ zW>o%Y5xLN%DQz%$8)~V^RD;3%b!W`{?!h!^6Xag9D4jqSNVKUS68b zW~o$)#bOT+4~*Q+&CNg{;B-2-wzjld?bFj!yWLKuQpsep-|sULsUeL<6AFc1UtfQa zuF+_ePN!Wi7eC+ac4xC$Qqm^}1y4^;p*TO0#aOM@R;xuTit}e@XV8FgEdY^71dq}% z6bA{G13b2~v(xMKXtnr!yb?0w0tpmkVDc0u)n`AhAO)zh!U)fsdzljP8K5yCkWZl>gwvp$A=h_?3y47 z(#X>{guGs_TrNjT$pDCYJRT&+8QR|7My$jDKa>6mLwGMZLlSodms2X0XvrBG3gYjbhROIEM@d!}Ig= zSdQxu1TvY7G$wEr5Jhfq{uA%+?y^r+F``FDN0UDA_2uQI7^<|I)oOKdk=r)7lizr6 zZ;zHN2JiFv2qq?|zkCgy-QW57`Ghoi3DZTV)0th5U12yJZftCj9q?JWaWGe~E6x}V z27`QE7ISiPGNU9TJsb|Rw23j;{35+y!>lNi$%vwVAXO?=p-^CaQ2`;CdZ(TeahtHg z;Xxxnn3z(l)nGQq>kGkaYioCRcSM1y$lKdnqtPhD0YArVHs9ahPbEL`VMsTbO#I5i z8Tj`@aG$^pqzl)kWkSQJn%>7vYBU;O+#B2+5)hugCB+DVNI( z6DV|jeT`3mUvC9fTwY$z=krLI2(GTK;K)EJq60WHFD@>KR)Eaqa_C7^dL; zf$u4iNGvTap^r%o{C+>&9T=?M-{0Y}aD03$wF9Ew_g@z=G$-dBkh%ju0gw9pbUZyc QRR91007*qoM6N<$f^i3!+5i9m literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/chat/input_liked@3x.png b/Telegram/Resources/icons/chat/input_liked@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..5d6c546e61902de675c4cd4131b4e8f09e0ed329 GIT binary patch literal 1362 zcmV-Y1+DstP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*ISe5TT3W)Q560?Q%wGW zJO&;kqLgHaGQmV5N>Y^a2r-kCM9EB2$}>tx-<`xhT@cZB6 zx3_m#SXgLi=+o2FpMTHk>FN9X`=^4V3VwcmSy@@Vy}f5= zXCGgmmzS6Q{r&d#_N1gFcXxMw1Rfq9$;rtb9UTV;2XAk0U#)&zUS9V1_vhr~z%{>3 z0ZmU&&(hM;i;D~5SW^V;y1F`FUthKZV6CsO2Xsv%q!ManWo3ZDwkg(%LD<>ZA)f4t z=jZ2u;^pO~c81FG@^a2FDIM+Z?j|NCstwB2U>ab?E3FHaS5{U+LPC^|A+D~j3K>x< z159>yw$h+X86j8Cd!-wm^7Zv~YHF&@eOg-D&CLx@FNO{v0hkOQTc4_`Du#8TCcN2- zi;GK2N>C8$$WR768E!E#G0)G>47;LDprj)!c*mRsNHK2saj&nhi$XVEA0RbfPGIYy z$+)4R0q+dWCYP!V3=G6pVR3O$jY?zZf$ap=d+aCF+Adi0`1lwc9E@EG!KSfA$ji$! z3oDQTQ~)Ndv_(_=x|F}?wY4>ECd%5lxHx4Wvy=f_$pU3M6|sO44O56}*pyg&e7q@` z5RhuvlvreBBU2^T^i7FVPSy*p4Ai-6m)lYX8=SA7Y`2);o;#d^z63W+*}E-26U;c ztYjm%-`dmDV*pSg7gJMHH~_W}ovne3$&HN-A;%6?#*Hq{s@a%%TXEaXd7{4A_Eryt1yRorRN1#HcT3TB8 zamr|Rc6JH@(nT3gWv~kM!N(~>Lqod2&p~1_sD&kUEY=*C3B* zYilDwg~YC|E`!!9iP$tZHw%&ZRBml;)gi@_K+NBzwzl>YE@z>ssfpjdGc_wJD)>Ki ztEs7RCRS131qB88sGB`vAde7;T6VsEW@aW5-tV~K);K>u-}xxz5JX2u&(F`Rxh*X% z>73!K@csC#M`cl65fv4cl9Gad3gEqWbaaG+PMi*@)vOvT0#*d92v`xYA|M=rzw!y> Uh($ literal 0 HcmV?d00001 diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index e6dfeeb0a..70306ad8f 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -272,6 +272,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_error_noforwards_channel" = "Sorry, forwarding from this channel is disabled by admins."; "lng_error_nocopy_group" = "Sorry, copying from this group is disabled by admins."; "lng_error_nocopy_channel" = "Sorry, copying from this channel is disabled by admins."; +"lng_error_nocopy_story" = "Sorry, copying of this story is disabled by the author."; "lng_sure_add_admin_invite" = "This user is not a member of this group. Add them to the group and promote them to admin?"; "lng_sure_add_admin_invite_channel" = "This user is not a subscriber of this channel. Add them to the channel and promote them to admin?"; "lng_sure_add_admin_unremove" = "This user is currently restricted or removed. Are you sure you want to promote them?"; @@ -3876,6 +3877,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_stories_archive_done" = "This story is hidden from your profile."; "lng_stories_archive_done_many#one" = "{count} story is hidden from your profile."; "lng_stories_archive_done_many#other" = "{count} stories are hidden from your profile."; +"lng_stories_save_promo" = "Subscribe to {link} to download other people's unprotected stories to disk."; "lng_stealth_mode_menu_item" = "Stealth Mode"; "lng_stealth_mode_title" = "Stealth Mode"; diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 79c85a03d..75fbfbbb0 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -196,6 +196,8 @@ ComposeControls { send: SendButton; attach: IconButton; emoji: EmojiButton; + like: IconButton; + liked: icon; suggestions: EmojiSuggestions; tabbed: EmojiPan; tabbedHeightMin: pixels; diff --git a/Telegram/SourceFiles/chat_helpers/compose/compose_features.h b/Telegram/SourceFiles/chat_helpers/compose/compose_features.h index 2f9915679..ba6f43b4e 100644 --- a/Telegram/SourceFiles/chat_helpers/compose/compose_features.h +++ b/Telegram/SourceFiles/chat_helpers/compose/compose_features.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace ChatHelpers { struct ComposeFeatures { + bool likes = false; bool sendAs = true; bool ttlInfo = true; bool botCommandSend = true; diff --git a/Telegram/SourceFiles/data/data_story.cpp b/Telegram/SourceFiles/data/data_story.cpp index fbc678581..e8389ad2c 100644 --- a/Telegram/SourceFiles/data/data_story.cpp +++ b/Telegram/SourceFiles/data/data_story.cpp @@ -276,8 +276,13 @@ bool Story::edited() const { return _edited; } -bool Story::canDownload() const { - return /*!forbidsForward() || */_peer->isSelf(); +bool Story::canDownloadIfPremium() const { + return !forbidsForward() || _peer->isSelf(); +} + +bool Story::canDownloadChecked() const { + return _peer->isSelf() + || (canDownloadIfPremium() && _peer->session().premium()); } bool Story::canShare() const { diff --git a/Telegram/SourceFiles/data/data_story.h b/Telegram/SourceFiles/data/data_story.h index 8ea17b7b7..70370f176 100644 --- a/Telegram/SourceFiles/data/data_story.h +++ b/Telegram/SourceFiles/data/data_story.h @@ -100,7 +100,8 @@ public: [[nodiscard]] bool forbidsForward() const; [[nodiscard]] bool edited() const; - [[nodiscard]] bool canDownload() const; + [[nodiscard]] bool canDownloadIfPremium() const; + [[nodiscard]] bool canDownloadChecked() const; [[nodiscard]] bool canShare() const; [[nodiscard]] bool canDelete() const; [[nodiscard]] bool canReport() const; diff --git a/Telegram/SourceFiles/history/view/controls/compose_controls_common.h b/Telegram/SourceFiles/history/view/controls/compose_controls_common.h index 06f295ee8..8af04e830 100644 --- a/Telegram/SourceFiles/history/view/controls/compose_controls_common.h +++ b/Telegram/SourceFiles/history/view/controls/compose_controls_common.h @@ -41,6 +41,7 @@ struct SetHistoryArgs { Fn sendActionFactory; rpl::producer slowmodeSecondsLeft; rpl::producer sendDisabledBySlowmode; + rpl::producer liked; rpl::producer> writeRestriction; }; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index 4901c3bf9..6990d5b22 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -1069,9 +1069,10 @@ ComposeControls::ComposeControls( , _wrap(std::make_unique(parent)) , _writeRestricted(std::make_unique(parent)) , _send(std::make_shared(_wrap.get(), _st.send)) -, _attachToggle(Ui::CreateChild( - _wrap.get(), - _st.attach)) +, _like(_features.likes + ? Ui::CreateChild(_wrap.get(), _st.like) + : nullptr) +, _attachToggle(Ui::CreateChild(_wrap.get(), _st.attach)) , _tabbedSelectorToggle(Ui::CreateChild( _wrap.get(), _st.emoji)) @@ -1138,6 +1139,7 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) { | rpl::then(std::move(args.slowmodeSecondsLeft)); _sendDisabledBySlowmode = rpl::single(false) | rpl::then(std::move(args.sendDisabledBySlowmode)); + _liked = args.liked ? std::move(args.liked) : rpl::single(false); _writeRestriction = rpl::single(std::optional()) | rpl::then(std::move(args.writeRestriction)); const auto history = *args.history; @@ -1153,6 +1155,7 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) { initWebpageProcess(); initForwardProcess(); updateBotCommandShown(); + updateLikeShown(); updateMessagesTTLShown(); updateControlsGeometry(_wrap->size()); updateControlsVisibility(); @@ -1559,6 +1562,15 @@ void ComposeControls::init() { _botCommandStart->setClickedCallback([=] { setText({ "/" }); }); } + if (_like) { + _like->setClickedCallback([=] { _likeToggled.fire({}); }); + _liked.value( + ) | rpl::start_with_next([=](bool liked) { + const auto icon = liked ? &_st.liked : nullptr; + _like->setIconOverride(icon, icon); + }, _like->lifetime()); + } + _wrap->sizeValue( ) | rpl::start_with_next([=](QSize size) { updateControlsGeometry(size); @@ -1980,7 +1992,7 @@ void ComposeControls::fieldChanged() { if (!_hasSendText.current() && _preview) { _preview->setState(Data::PreviewState::Allowed); } - if (updateBotCommandShown()) { + if (updateBotCommandShown() || updateLikeShown()) { updateControlsVisibility(); updateControlsGeometry(_wrap->size()); } @@ -2521,6 +2533,7 @@ void ComposeControls::updateControlsGeometry(QSize size) { - st::historySendRight - _send->width() - _tabbedSelectorToggle->width() + - (_likeShown ? _like->width() : 0) - (_botCommandShown ? _botCommandStart->width() : 0) - (_silent ? _silent->width() : 0) - (_ttlInfo ? _ttlInfo->width() : 0); @@ -2560,6 +2573,12 @@ void ComposeControls::updateControlsGeometry(QSize size) { right += _send->width(); _tabbedSelectorToggle->moveToRight(right, buttonsTop); right += _tabbedSelectorToggle->width(); + if (_like) { + _like->moveToRight(right, buttonsTop); + if (_likeShown) { + right += _like->width(); + } + } if (_botCommandStart) { _botCommandStart->moveToRight(right, buttonsTop); if (_botCommandShown) { @@ -2584,6 +2603,9 @@ void ComposeControls::updateControlsVisibility() { if (_botCommandStart) { _botCommandStart->setVisible(_botCommandShown); } + if (_like) { + _like->setVisible(_likeShown); + } if (_ttlInfo) { _ttlInfo->show(); } @@ -2598,6 +2620,15 @@ void ComposeControls::updateControlsVisibility() { } } +bool ComposeControls::updateLikeShown() { + auto shown = _like && !HasSendText(_field); + if (_likeShown != shown) { + _likeShown = shown; + return true; + } + return false; +} + bool ComposeControls::updateBotCommandShown() { auto shown = false; const auto peer = _history ? _history->peer.get() : nullptr; @@ -3100,6 +3131,10 @@ rpl::producer> ComposeControls::viewportEvents() const { return _voiceRecordBar->lockViewportEvents(); } +rpl::producer<> ComposeControls::likeToggled() const { + return _likeToggled.events(); +} + bool ComposeControls::isRecording() const { return _voiceRecordBar->isRecording(); } @@ -3123,6 +3158,12 @@ rpl::producer ComposeControls::fieldMenuShownValue() const { return _field->menuShownValue(); } +not_null ComposeControls::likeAnimationTarget() const { + Expects(_like != nullptr); + + return _like; +} + bool ComposeControls::preventsClose(Fn &&continueCallback) const { if (_voiceRecordBar->isActive()) { _voiceRecordBar->showDiscardBox(std::move(continueCallback)); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index de7002012..80ad5e7b2 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -161,6 +161,7 @@ public: [[nodiscard]] rpl::producer inlineResultChosen() const; [[nodiscard]] rpl::producer sendActionUpdates() const; [[nodiscard]] rpl::producer> viewportEvents() const; + [[nodiscard]] rpl::producer<> likeToggled() const; [[nodiscard]] auto scrollKeyEvents() const -> rpl::producer>; [[nodiscard]] auto editLastMessageRequests() const @@ -221,6 +222,7 @@ public: [[nodiscard]] rpl::producer recordingActiveValue() const; [[nodiscard]] rpl::producer hasSendTextValue() const; [[nodiscard]] rpl::producer fieldMenuShownValue() const; + [[nodiscard]] not_null likeAnimationTarget() const; void applyCloudDraft(); void applyDraft( @@ -292,6 +294,7 @@ private: bool showRecordButton() const; void drawRestrictedWrite(QPainter &p, const QString &error); bool updateBotCommandShown(); + bool updateLikeShown(); void cancelInlineBot(); void clearInlineBot(); @@ -344,6 +347,7 @@ private: Fn _sendActionFactory; rpl::variable _slowmodeSecondsLeft; rpl::variable _sendDisabledBySlowmode; + rpl::variable _liked; rpl::variable> _writeRestriction; rpl::variable _hidden; Mode _mode = Mode::Normal; @@ -354,6 +358,7 @@ private: std::optional _backgroundRect; const std::shared_ptr _send; + Ui::IconButton * const _like = nullptr; const not_null _attachToggle; std::unique_ptr _replaceMedia; const not_null _tabbedSelectorToggle; @@ -386,6 +391,7 @@ private: rpl::event_stream> _scrollKeyEvents; rpl::event_stream> _editLastMessageRequests; rpl::event_stream> _attachRequests; + rpl::event_stream<> _likeToggled; rpl::event_stream _replyNextRequests; rpl::event_stream<> _focusRequests; rpl::variable _recording; @@ -407,6 +413,7 @@ private: mtpRequestId _inlineBotResolveRequestId = 0; bool _isInlineBot = false; bool _botCommandShown = false; + bool _likeShown = false; FullMsgId _editingId; std::shared_ptr _photoEditMedia; diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index 2ee9496dd..32eae7905 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -313,7 +313,7 @@ Controller::Controller(not_null delegate) .type = Ui::MessageSendingAnimationFrom::Type::Emoji, .globalStartGeometry = id.globalGeometry, .frame = id.icon, - }); + }, _wrap.get()); _replyArea->sendReaction(id.id); unfocusReply(); }, _lifetime); @@ -587,6 +587,18 @@ bool Controller::skipCaption() const { return _captionFullView != nullptr; } +bool Controller::liked() const { + return _liked.current(); +} + +rpl::producer Controller::likedValue() const { + return _liked.value(); +} + +void Controller::toggleLiked(bool liked) { + _liked = liked; +} + void Controller::showFullCaption() { if (_captionText.empty()) { return; @@ -892,6 +904,7 @@ bool Controller::changeShown(Data::Story *story) { story, Data::Stories::Polling::Viewer); } + _liked = false; return true; } @@ -1551,7 +1564,8 @@ void Controller::updatePowerSaveBlocker(const Player::TrackState &state) { void Controller::startReactionAnimation( Data::ReactionId id, - Ui::MessageSendingAnimationFrom from) { + Ui::MessageSendingAnimationFrom from, + not_null target) { Expects(shown()); auto args = Ui::ReactionFlyAnimationArgs{ @@ -1568,7 +1582,7 @@ void Controller::startReactionAnimation( Data::CustomEmojiSizeTag::Isolated); const auto layer = _reactionAnimation->layer(); _wrap->paintRequest() | rpl::start_with_next([=] { - if (!_reactionAnimation->paintBadgeFrame(_wrap.get())) { + if (!_reactionAnimation->paintBadgeFrame(target)) { InvokeQueued(layer, [=] { _reactionAnimation = nullptr; _wrap->update(); diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h index a9703c423..115e7e80a 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.h +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -123,6 +123,9 @@ public: [[nodiscard]] Data::FileOrigin fileOrigin() const; [[nodiscard]] TextWithEntities captionText() const; [[nodiscard]] bool skipCaption() const; + [[nodiscard]] bool liked() const; + [[nodiscard]] rpl::producer likedValue() const; + void toggleLiked(bool liked); void showFullCaption(); void captionClosing(); void captionClosed(); @@ -236,7 +239,8 @@ private: void startReactionAnimation( Data::ReactionId id, - Ui::MessageSendingAnimationFrom from); + Ui::MessageSendingAnimationFrom from, + not_null target); const not_null _delegate; @@ -269,6 +273,7 @@ private: Data::StoriesContext _context; std::optional _source; std::optional _list; + rpl::variable _liked; FullStoryId _waitingForId; int _waitingForDelta = 0; int _index = 0; diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp index d6fbfd7c6..53a5c3435 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp @@ -121,6 +121,7 @@ ReplyArea::ReplyArea(not_null controller) .voiceCustomCancelText = tr::lng_record_cancel_stories(tr::now), .voiceLockFromBottom = true, .features = { + .likes = true, .sendAs = false, .ttlInfo = false, .botCommandSend = false, @@ -620,6 +621,11 @@ void ReplyArea::initActions() { sendInlineResult(chosen.result, chosen.bot, chosen.options, localId); }, _lifetime); + _controls->likeToggled( + ) | rpl::start_with_next([=] { + _controller->toggleLiked(!_controller->liked()); + }, _lifetime); + _controls->setMimeDataHook([=]( not_null data, Ui::InputField::MimeAction action) { @@ -660,6 +666,7 @@ void ReplyArea::show(ReplyAreaData data) { const auto history = user ? user->owner().history(user).get() : nullptr; _controls->setHistory({ .history = history, + .liked = _controller->likedValue(), }); _controls->clear(); const auto hidden = user && user->isSelf(); @@ -718,6 +725,10 @@ void ReplyArea::tryProcessKeyInput(not_null e) { _controls->tryProcessKeyInput(e); } +not_null ReplyArea::likeAnimationTarget() const { + return _controls->likeAnimationTarget(); +} + void ReplyArea::showPremiumToast(not_null emoji) { // #TODO stories } diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.h b/Telegram/SourceFiles/media/stories/media_stories_reply.h index 9b9662b4a..c3c13abdf 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.h +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.h @@ -70,6 +70,8 @@ public: [[nodiscard]] bool ignoreWindowMove(QPoint position) const; void tryProcessKeyInput(not_null e); + [[nodiscard]] not_null likeAnimationTarget() const; + private: class Cant; diff --git a/Telegram/SourceFiles/media/stories/media_stories_stealth.cpp b/Telegram/SourceFiles/media/stories/media_stories_stealth.cpp index 7bf25a1cf..8bacca224 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_stealth.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_stealth.cpp @@ -353,7 +353,7 @@ struct Feature { if (const auto window = show->resolveWindow(usage)) { Settings::ShowPremium( window, - u"stories_stealth_mode"_q); + u"stories__stealth_mode"_q); window->window().activate(); } } else if (now.mode.cooldownTill > now.now) { diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style index e291abe45..5eb311f76 100644 --- a/Telegram/SourceFiles/media/view/media_view.style +++ b/Telegram/SourceFiles/media/view/media_view.style @@ -109,6 +109,7 @@ mediaviewRight: icon { { "mediaview/next", mediaviewControlFg } }; mediaviewSave: icon {{ "mediaview/download", mediaviewControlFg }}; +mediaviewSaveLocked: icon {{ "mediaview/download_locked", mediaviewControlFg }}; mediaviewShare: icon {{ "mediaview/viewer_share", mediaviewControlFg }}; mediaviewRotate: icon {{ "mediaview/rotate", mediaviewControlFg }}; mediaviewMore: icon {{ "title_menu_dots", mediaviewControlFg }}; @@ -466,6 +467,10 @@ storiesAttach: IconButton(historyAttach) { iconOver: icon {{ "chat/input_attach", storiesComposeGrayIcon }}; ripple: storiesComposeRippleLight; } +storiesLike: IconButton(storiesAttach) { + icon: icon {{ "chat/input_like", storiesComposeGrayIcon }}; + iconOver: icon {{ "chat/input_like", storiesComposeGrayIcon }}; +} storiesRecordVoice: icon {{ "chat/input_record", storiesComposeGrayIcon }}; storiesRecordVoiceOver: icon {{ "chat/input_record", storiesComposeGrayIcon }}; storiesRemoveSet: IconButton(stickerPanRemoveSet) { @@ -676,6 +681,8 @@ storiesComposeControls: ComposeControls(defaultComposeControls) { } attach: storiesAttach; emoji: storiesAttachEmoji; + like: storiesLike; + liked: icon{{ "chat/input_liked", settingsIconBg1 }}; suggestions: EmojiSuggestions(defaultEmojiSuggestions) { dropdown: InnerDropdown(emojiSuggestionsDropdown) { animation: PanelAnimation(defaultPanelAnimation) { diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_opengl.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_opengl.cpp index 7851ed7f9..570eed0af 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_opengl.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_opengl.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/view/media_view_overlay_opengl.h" +#include "data/data_peer_values.h" // AmPremiumValue. #include "ui/gl/gl_shader.h" #include "ui/painter.h" #include "media/stories/media_stories_view.h" @@ -122,7 +123,16 @@ OverlayWidget::RendererGL::RendererGL(not_null owner) crl::on_main(this, [=] { _owner->_storiesChanged.events( ) | rpl::start_with_next([=] { - invalidateControls(); + if (_owner->_storiesSession) { + Data::AmPremiumValue( + _owner->_storiesSession + ) | rpl::start_with_next([=] { + invalidateControls(); + }, _storiesLifetime); + } else { + _storiesLifetime.destroy(); + invalidateControls(); + } }, _lifetime); }); } @@ -648,8 +658,7 @@ void OverlayWidget::RendererGL::paintControl( QRect inner, float64 innerOpacity, const style::icon &icon) { - const auto stories = (_owner->_stories != nullptr); - const auto meta = ControlMeta(control, stories); + const auto meta = controlMeta(control); Assert(meta.icon == &icon); const auto overAlpha = overOpacity * kOverBackgroundOpacity; @@ -707,18 +716,25 @@ void OverlayWidget::RendererGL::paintControl( FillTexturedRectangle(*_f, &*_controlsProgram, fgOffset); } -auto OverlayWidget::RendererGL::ControlMeta(Over control, bool stories) --> Control { +auto OverlayWidget::RendererGL::controlMeta(Over control) const -> Control { + const auto stories = [&] { + return (_owner->_stories != nullptr); + }; switch (control) { case Over::Left: return { 0, - stories ? &st::storiesLeft : &st::mediaviewLeft + stories() ? &st::storiesLeft : &st::mediaviewLeft }; case Over::Right: return { 1, - stories ? &st::storiesRight : &st::mediaviewRight + stories() ? &st::storiesRight : &st::mediaviewRight + }; + case Over::Save: return { + 2, + (_owner->saveControlLocked() + ? &st::mediaviewSaveLocked + : &st::mediaviewSave) }; - case Over::Save: return { 2, &st::mediaviewSave }; case Over::Share: return { 3, &st::mediaviewShare }; case Over::Rotate: return { 4, &st::mediaviewRotate }; case Over::More: return { 5, &st::mediaviewMore }; @@ -730,14 +746,13 @@ void OverlayWidget::RendererGL::validateControls() { if (!_controlsImage.image().isNull()) { return; } - const auto stories = (_owner->_stories != nullptr); const auto metas = { - ControlMeta(Over::Left, stories), - ControlMeta(Over::Right, stories), - ControlMeta(Over::Save, stories), - ControlMeta(Over::Share, stories), - ControlMeta(Over::Rotate, stories), - ControlMeta(Over::More, stories), + controlMeta(Over::Left), + controlMeta(Over::Right), + controlMeta(Over::Save), + controlMeta(Over::Share), + controlMeta(Over::Rotate), + controlMeta(Over::More), }; auto maxWidth = 0; auto fullHeight = 0; diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_opengl.h b/Telegram/SourceFiles/media/view/media_view_overlay_opengl.h index a0342518a..f64fb7e58 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_opengl.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_opengl.h @@ -149,7 +149,7 @@ private: Ui::GL::Image _storiesSiblingParts[kStoriesSiblingPartsCount]; static constexpr auto kControlsCount = 6; - [[nodiscard]] static Control ControlMeta(Over control, bool stories); + [[nodiscard]] Control controlMeta(Over control) const; // Last one is for the over circle image. std::array _controlsTextures; @@ -158,6 +158,7 @@ private: bool _shadowsForStories = false; bool _blendingEnabled = false; + rpl::lifetime _storiesLifetime; rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 0cd3ca35e..119604e1c 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -86,6 +86,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session_settings.h" #include "layout/layout_document_generic_preview.h" #include "platform/platform_overlay_widget.h" +#include "settings/settings_premium.h" #include "storage/file_download.h" #include "storage/storage_account.h" #include "calls/calls_instance.h" @@ -130,6 +131,7 @@ constexpr auto kIdsPreloadAfter = 28; constexpr auto kLeftSiblingTextureIndex = 1; constexpr auto kRightSiblingTextureIndex = 2; constexpr auto kStoriesControlsOpacity = 1.; +constexpr auto kStorySavePromoDuration = 3 * crl::time(1000); class PipDelegate final : public Pip::Delegate { public: @@ -326,16 +328,18 @@ public: return _widget->_body; } bool valid() const override { - return _widget->_storiesSession != nullptr; + return _widget->_session || _widget->_storiesSession; } operator bool() const override { return valid(); } Main::Session &session() const override { - Expects(_widget->_storiesSession != nullptr); + Expects(_widget->_session || _widget->_storiesSession); - return *_widget->_storiesSession; + return _widget->_session + ? *_widget->_session + : *_widget->_storiesSession; } bool paused(ChatHelpers::PauseReason reason) const override { if (_widget->isHidden() @@ -1024,22 +1028,26 @@ QSize OverlayWidget::flipSizeByRotation(QSize size) const { return FlipSizeByRotation(size, _rotation); } -bool OverlayWidget::hasCopyMediaRestriction() const { - const auto story = _stories ? _stories->story() : nullptr; - return (story && !story->canDownload()) - || (_history && !_history->peer->allowsForwarding()) +bool OverlayWidget::hasCopyMediaRestriction(bool skipPremiumCheck) const { + if (const auto story = _stories ? _stories->story() : nullptr) { + return skipPremiumCheck + ? story->canDownloadIfPremium() + : story->canDownloadChecked(); + } + return (_history && !_history->peer->allowsForwarding()) || (_message && _message->forbidsSaving()); } -bool OverlayWidget::showCopyMediaRestriction() { - if (!hasCopyMediaRestriction()) { +bool OverlayWidget::showCopyMediaRestriction(bool skipPRemiumCheck) { + if (!hasCopyMediaRestriction(skipPRemiumCheck)) { return false; - } else if (!_history) { - return true; + } else if (_stories) { + uiShow()->showToast(tr::lng_error_nocopy_story(tr::now)); + } else if (_history) { + uiShow()->showToast(_history->peer->isBroadcast() + ? tr::lng_error_nocopy_channel(tr::now) + : tr::lng_error_nocopy_group(tr::now)); } - Ui::Toast::Show(_widget, _history->peer->isBroadcast() - ? tr::lng_error_nocopy_channel(tr::now) - : tr::lng_error_nocopy_group(tr::now)); return true; } @@ -1210,8 +1218,8 @@ void OverlayWidget::refreshNavVisibility() { } } -bool OverlayWidget::contentCanBeSaved() const { - if (hasCopyMediaRestriction()) { +bool OverlayWidget::computeSaveButtonVisible() const { + if (hasCopyMediaRestriction(true)) { return false; } else if (_photo) { return _photo->hasVideo() || _photoMedia->loaded(); @@ -1240,6 +1248,26 @@ void OverlayWidget::checkForSaveLoaded() { } } +void OverlayWidget::showPremiumDownloadPromo() { + const auto filter = [=](const auto &...) { + const auto usage = ChatHelpers::WindowUsage::PremiumPromo; + if (const auto window = uiShow()->resolveWindow(usage)) { + const auto ref = u"stories__save_stories_to_gallery"_q; + Settings::ShowPremium(window, ref); + } + return false; + }; + uiShow()->showToast({ + .text = tr::lng_stories_save_promo( + tr::now, + lt_link, + Ui::Text::Bold(tr::lng_send_as_premium_required_link(tr::now)), + Ui::Text::WithEntities), + .duration = kStorySavePromoDuration, + .filter = filter, + }); +} + void OverlayWidget::updateControls() { if (_document && documentBubbleShown()) { _docRect = QRect( @@ -1290,7 +1318,7 @@ void OverlayWidget::updateControls() { const auto overRect = QRect( QPoint(), QSize(st::mediaviewIconOver, st::mediaviewIconOver)); - _saveVisible = contentCanBeSaved(); + _saveVisible = computeSaveButtonVisible(); _shareVisible = story && story->canShare(); _rotateVisible = !_themePreviewShown && !story; const auto navRect = [&](int i) { @@ -1320,6 +1348,7 @@ void OverlayWidget::updateControls() { _saveNav = navRect(index); _saveNavOver = style::centerrect(_saveNav, overRect); _saveNavIcon = style::centerrect(_saveNav, st::mediaviewSave); + Assert(st::mediaviewSave.size() == st::mediaviewSaveLocked.size()); const auto dNow = QDateTime::currentDateTime(); const auto d = [&] { @@ -1471,7 +1500,7 @@ void OverlayWidget::fillContextMenuActions(const MenuCallback &addAction) { ? &st::mediaMenuIconArchiveStory : &st::mediaMenuIconSaveStory); } - if ((!story || story->canDownload()) + if ((!story || story->canDownloadChecked()) && _document && !_document->filepath(true).isEmpty()) { const auto text = Platform::IsMac() @@ -1533,11 +1562,13 @@ void OverlayWidget::fillContextMenuActions(const MenuCallback &addAction) { [=] { deleteMedia(); }, &st::mediaMenuIconDelete); } - if (!hasCopyMediaRestriction()) { + if (!hasCopyMediaRestriction(true)) { addAction( tr::lng_mediaview_save_as(tr::now), [=] { saveAs(); }, - &st::mediaMenuIconDownload); + (saveControlLocked() + ? &st::mediaMenuIconDownloadLocked + : &st::mediaMenuIconDownload)); } if (const auto overviewType = computeOverviewType()) { @@ -2285,7 +2316,11 @@ void OverlayWidget::notifyFileDialogShown(bool shown) { } void OverlayWidget::saveAs() { - if (showCopyMediaRestriction()) { + if (showCopyMediaRestriction(true)) { + return; + } else if (hasCopyMediaRestriction()) { + Assert(_stories != nullptr); + showPremiumDownloadPromo(); return; } QString file; @@ -2421,9 +2456,13 @@ void OverlayWidget::handleDocumentClick() { void OverlayWidget::downloadMedia() { if (!_photo && !_document) { return; - } - if (Core::App().settings().askDownloadPath()) { + } else if (Core::App().settings().askDownloadPath()) { return saveAs(); + } else if (hasCopyMediaRestriction()) { + if (_stories && !hasCopyMediaRestriction(true)) { + showPremiumDownloadPromo(); + } + return; } QString path; @@ -2478,7 +2517,7 @@ void OverlayWidget::downloadMedia() { } } } else { - _saveVisible = contentCanBeSaved(); + _saveVisible = computeSaveButtonVisible(); update(_saveNavOver); } updateOver(_lastMouseMovePos); @@ -2498,7 +2537,7 @@ void OverlayWidget::downloadMedia() { } } else { if (!_photo || !_photoMedia->loaded()) { - _saveVisible = contentCanBeSaved(); + _saveVisible = computeSaveButtonVisible(); update(_saveNavOver); } else { if (!QDir().exists(path)) { @@ -3899,8 +3938,7 @@ void OverlayWidget::initThemePreview() { _themeShare->setClickedCallback([=] { QGuiApplication::clipboard()->setText( session->createInternalLinkFull("addtheme/" + slug)); - Ui::Toast::Show( - _body, + uiShow()->showToast( tr::lng_background_link_copied(tr::now)); }); } else { @@ -4198,6 +4236,10 @@ not_null OverlayWidget::storiesWrap() { } std::shared_ptr OverlayWidget::storiesShow() { + return uiShow(); +} + +std::shared_ptr OverlayWidget::uiShow() { if (!_cachedShow) { _cachedShow = std::make_shared(this); } @@ -4778,6 +4820,13 @@ void OverlayWidget::paintSaveMsgContent( p.setOpacity(1); } +bool OverlayWidget::saveControlLocked() const { + const auto story = _stories ? _stories->story() : nullptr; + return story + && story->canDownloadIfPremium() + && !story->canDownloadChecked(); +} + void OverlayWidget::paintControls( not_null renderer, float64 opacity) { @@ -4811,7 +4860,9 @@ void OverlayWidget::paintControls( _saveVisible, _saveNavOver, _saveNavIcon, - st::mediaviewSave }, + (saveControlLocked() + ? st::mediaviewSaveLocked + : st::mediaviewSave) }, { Over::Share, _shareVisible, diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h index 06d0c0468..864e21084 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h @@ -245,6 +245,7 @@ private: void playbackPauseMusic(); void switchToPip(); [[nodiscard]] int topNotchSkip() const; + [[nodiscard]] std::shared_ptr uiShow(); not_null storiesWrap() override; std::shared_ptr storiesShow() override; @@ -310,8 +311,9 @@ private: void handleScreenChanged(QScreen *screen); - bool contentCanBeSaved() const; + [[nodiscard]] bool computeSaveButtonVisible() const; void checkForSaveLoaded(); + void showPremiumDownloadPromo(); Entity entityForUserPhotos(int index) const; Entity entityForSharedMedia(int index) const; @@ -495,8 +497,10 @@ private: void validatePhotoImage(Image *image, bool blurred); void validatePhotoCurrentImage(); - [[nodiscard]] bool hasCopyMediaRestriction() const; - [[nodiscard]] bool showCopyMediaRestriction(); + [[nodiscard]] bool hasCopyMediaRestriction( + bool skipPremiumCheck = false) const; + [[nodiscard]] bool showCopyMediaRestriction( + bool skipPRemiumCheck = false); [[nodiscard]] QSize flipSizeByRotation(QSize size) const; @@ -518,7 +522,8 @@ private: [[nodiscard]] bool contentShown() const; [[nodiscard]] bool opaqueContentShown() const; void clearStreaming(bool savePosition = true); - bool canInitStreaming() const; + [[nodiscard]] bool canInitStreaming() const; + [[nodiscard]] bool saveControlLocked() const; [[nodiscard]] bool topShadowOnTheRight() const; void applyHideWindowWorkaround(); diff --git a/Telegram/SourceFiles/ui/effects/emoji_fly_animation.cpp b/Telegram/SourceFiles/ui/effects/emoji_fly_animation.cpp index 8e55994cc..c655ab678 100644 --- a/Telegram/SourceFiles/ui/effects/emoji_fly_animation.cpp +++ b/Telegram/SourceFiles/ui/effects/emoji_fly_animation.cpp @@ -95,8 +95,7 @@ void EmojiFlyAnimation::repaint() { } } -bool EmojiFlyAnimation::paintBadgeFrame( - not_null widget) { +bool EmojiFlyAnimation::paintBadgeFrame(not_null widget) { _target = widget; return !_fly.finished(); } diff --git a/Telegram/SourceFiles/ui/effects/emoji_fly_animation.h b/Telegram/SourceFiles/ui/effects/emoji_fly_animation.h index 7bfd08ce2..f5dc8102b 100644 --- a/Telegram/SourceFiles/ui/effects/emoji_fly_animation.h +++ b/Telegram/SourceFiles/ui/effects/emoji_fly_animation.h @@ -25,7 +25,7 @@ public: [[nodiscard]] bool finished() const; void repaint(); - bool paintBadgeFrame(not_null widget); + bool paintBadgeFrame(not_null widget); private: const int _flySize = 0; @@ -33,7 +33,7 @@ private: Ui::RpWidget _layer; QRect _area; bool _areaUpdated = false; - QPointer _target; + QPointer _target; }; diff --git a/Telegram/SourceFiles/ui/menu_icons.style b/Telegram/SourceFiles/ui/menu_icons.style index dfe5430b2..641390f2e 100644 --- a/Telegram/SourceFiles/ui/menu_icons.style +++ b/Telegram/SourceFiles/ui/menu_icons.style @@ -126,6 +126,7 @@ mediaMenuIconCancel: icon {{ "menu/cancel", mediaviewMenuFg }}; mediaMenuIconShowInChat: icon {{ "menu/show_in_chat", mediaviewMenuFg }}; mediaMenuIconShowInFolder: icon {{ "menu/show_in_folder", mediaviewMenuFg }}; mediaMenuIconDownload: icon {{ "menu/download", mediaviewMenuFg }}; +mediaMenuIconDownloadLocked: icon {{ "menu/download_locked", mediaviewMenuFg }}; mediaMenuIconCopy: icon {{ "menu/copy", mediaviewMenuFg }}; mediaMenuIconForward: icon {{ "menu/forward", mediaviewMenuFg }}; mediaMenuIconDelete: icon {{ "menu/delete", mediaviewMenuFg }};