From b95ea28e1224b9135e441101dc99753c7e3ac4fb Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 3 Mar 2023 17:15:02 +0400 Subject: [PATCH] Implement dates of who read your message list. --- Telegram/Resources/icons/menu/read_ticks.png | Bin 471 -> 414 bytes .../Resources/icons/menu/read_ticks@2x.png | Bin 720 -> 812 bytes .../Resources/icons/menu/read_ticks@3x.png | Bin 1249 -> 1125 bytes .../Resources/icons/menu/read_ticks_s.png | Bin 0 -> 348 bytes .../Resources/icons/menu/read_ticks_s@2x.png | Bin 0 -> 695 bytes .../Resources/icons/menu/read_ticks_s@3x.png | Bin 0 -> 1008 bytes Telegram/SourceFiles/api/api_who_reacted.cpp | 109 ++++++++++---- Telegram/SourceFiles/api/api_who_reacted.h | 11 +- .../reactions/history_view_reactions_list.cpp | 4 +- Telegram/SourceFiles/lang/lang_keys.cpp | 134 +++++++++--------- Telegram/SourceFiles/lang/lang_keys.h | 5 + .../SourceFiles/ui/boxes/choose_date_time.cpp | 20 +-- Telegram/SourceFiles/ui/chat/chat.style | 11 ++ .../controls/who_reacted_context_action.cpp | 41 +++++- .../ui/controls/who_reacted_context_action.h | 1 + 15 files changed, 214 insertions(+), 122 deletions(-) create mode 100644 Telegram/Resources/icons/menu/read_ticks_s.png create mode 100644 Telegram/Resources/icons/menu/read_ticks_s@2x.png create mode 100644 Telegram/Resources/icons/menu/read_ticks_s@3x.png diff --git a/Telegram/Resources/icons/menu/read_ticks.png b/Telegram/Resources/icons/menu/read_ticks.png index 70b13734207a617865f675bda9e4f051a031f997..af0709d546db96297427d7e1f15e19e7a9ce6025 100644 GIT binary patch delta 294 zcmV+>0onf71D*qrfqw`|L_t(I5o2H&1*0KA*AU?4MGpW>7>##NWSvD=RC3 z+JG)Pe*E~|yLY9frAad-ARyrP@83;LO+am2TwDha9(?rZk$-}M0!~x-`1qbaed^%g zfULsP)AQ%gpOCNshVJg&yPrLKrlO*PtOg5v!GZ*w}pe@@3APIgE^qEG#T*)~o^Ap|7ux zrUfTgNJ!|`tyo)k?%etG>C=)WOPIi5>C&YiKYlbdHN~k7tAvb<%+#q<%gV}_nVEqK xoSmINfBtN3ZH-kiX@bZJnlzoGst0!n002T>Zw;si#$f;e002ovPDHLkV1k)-ux0ddmEMoOUr5kyI$Aj1#DqSQWo>%9#UBEfsy?%;9mK+aib z@4c4m?0xUl6nmHh%mL;AbAUO(9AFMG2Y$$b_4W1B(^HvD_J6g$#bP-=KAxSO{R;B2 zHW&=uZg)5wLeq$Ee}Dh|{T-mo-Bm7A0HppYBhgqR#sLX9v(p4@Ar3hc8GX;d;8_( z1z@w;L}T%%Mt`MJ-QC@R`0ed&Z*Px?jYi|s(-Xi>r=!>F`BS4%C~j|WL5#L_I-Nwk zxw#1)0HMibGV!Mdvt25cK>Ydnd3bn8#2SqTcLWgkg|?zfiV(C70}%<}Rw zW*Z3R9Me8A@gG66*-YbFE|=%$=f|o@B$7-f^GD1u5D3tzQmL%fY5>tp4u@kbg@diz z?Iy}}Iz2r-J*p@ci?6P(=qMZx6E2s_Asyhy$H&3J!6=yHMJN;^;#e$3rDtYlE-x<$ zj6@;?E`KgA7K=rIXl;+j!?B35a`AYa=!3x^d`c#hv^_sRr${tYKA$J>=;&xHo%=0B z&;{Y&UteDn@x{f3Kp+5_R4UD8vjqD6e(vcfRzy=*DivbIFFX?pg%p`er3gPcIhmOI zQy|_hG(m|(LL?Fq4Hm6d3xdwh&VGvco4U9=tuR(Q7JyGCmv6tZ>TJs#U=A<`m;=lK d=D>gDz%Nv-dIyHwwO0TD002ovPDHLkV1n&fO6dRq delta 602 zcmV-g0;T<|2G9kNfqyzlL_t(o3GG%Zhyqa*U4MvQM1z8Y#bQtpOg4jvL8H-V5yWP+ z2}X;c(I!}ICdDBB5QD{H&|nY@eh~ccg$s|zxchcx-|m(-VK_7A%sq$mhBx!}CJxbn zXh1X|8W0VL21EmY+JNHs+#C*v!C=_$_sRHU^PCOpJTz@X-^BKG)6B(35$?Y;0 zi$R*l;{j*8-A>PCP_&FE4u`|1(+Set@ApI^L62row2UVX27|}r5z{1-NqRJcqGddB zAP_hl4v+?pbUICsW>B<@C-(dO+wB(9WV2a%G=rjLJh9j7-E20PCYQ_6qZt$}(UtuRfoSfs@aik5u~L(k|jolcEL z<7dx6+~snK%5u3ZlgWq@sKsJ|RHag(DWZ7|)a!K|*K9TwiVwapu;b0=bJSF;Rk>VF z3>XyAybNH~hQlHHl}aVa^H!^MHk%<^tJUbl3`#QO<$p_?%|>d^WHRJ=i^T#* zhDxRS4n;IS0v?ZNx7&gD?RJYsqcF>4GL6S$OarT_R;!5tgCZIZAQFjOuUAL_KN9dY z==b}Wrqyb}vJwNmUf=8WkZQNvG(|MrKq{3&Cvxs~yE>hY7-+THPN##cgd!Tw0Nx?| zqX1WUZ&M_DK|Y_yG_Z9^D9$6?f#&}1?(o6XPcKiq+R&*$?gmCAqc o7M7v`(ST?`G$0xf4X_({2ks%^Z)<= diff --git a/Telegram/Resources/icons/menu/read_ticks@3x.png b/Telegram/Resources/icons/menu/read_ticks@3x.png index 64fa1f2b424b67a369882fdfdfcc33e79dc2edc3..1e90e9d289debf604079bc5def10280854c34df5 100644 GIT binary patch delta 1011 zcmV%|{Lapfy}i9@fTk<0t*wTv4APBM z=H%o&Jv}iXG&IyGK-0^{#zu+;Xl`y+g{G&c-{0ReU~+O&W%b4N_4O1D(9_eSYE4c~ zzPY(!!2JBYi+_uY8lWkzsj2z+_@M5-zCKlGe0==nZOzluldQg=xVZTB^_9AZhlg!!Y$!K8JpB0hnC#}}rmwFr zS$#oaVd2Zm3w4im~E-voB9+sGxczu0M-HVHhuCA`U1M2wF(h^xzdDMEc z<^Vh@hku8M0RaL3_{iAUSlB_`D=RA=9v<9_o;x=;NA~viHYFv6teL?0_&627V<#u4 zzdkrBD(dv~l)Ki~*S)>Hxy#Yfab{+Q?8CzYW+<}e13^JSYKK9kR=cXHjqoX70 z-rCyo_xEQQ>LF%7vY2!+?~*k)NJ>glD_d7r*MIL6z=i$&ed^xZ+rtWj(YwaR#>k?x z<>%*ll!Wv1b5*VZqa$TxWT;x(+S=5xmX;Qk)e)zq zrKtlu)Z@~qoM0AHvubZ|*D(`QO%)Xt{A`hrj#N-kU=)KtGnm*PTA$G_EiTK-$`%$D zFdt=SXPXMb5R5{C+`7BFP1*0gg2B?y&+mKZAnL^daX=gp2gCt!KpYSU!~tkN7 zB1OUrQ4*1%BqZ@b9`ImzK*o?M6rxO#A>;R}Tf5yooBY4-{m%Vr-G|e<>-_dwzxCVu z?7hzU^M`(<04YEUkOHItDL@L40;B*bKnjooqyQ;E3XlS%0Dmc*6-{0Ka{4iEWM<*>U&DYm=cX#*e>+1)kM%;>xjeUN82GOUdCl3#g?>On}>yM9* z1BPEmN5^+CP4P!YMxq`CU0n0>@`TKEb#+HaM;Yw={9Fjr6lYji*u%pEql)G_4g>8Y!$s}L!_)7;#Ad3l*} zh0)m9D1W4tnVE?lk|8fHF1); z6CEA>Lnchi;NT#`!@zqK-ln8O!o$P)?jG|N6B8qasnO%);NW1T?mi(QK?+m5CnO|=1nV=H_O`6~^M?qOckF@$pgW?y>jsab&Q| z%F1}^eSLk51U5|m*6Ql2g@pyX%M5pScYmesK07;`-Idem>gwVPKy!pgsJgnEcW!NM zNf#3R~LU7ZnvrVRCw~#uQ)n z_<9uf(b18x8JBkV<>lpa*6>|!XJ_Y)jSZ#3@Ve#V;vyV^ZxuYarlv*+(+DSe;%Ta? zs+E-$G_2SvN=r+H=M}hOZ{(|LXlT%g0#rf*2b!X`mX;Qk1k{uunBJ_ctoZnNO(8@) zNC8rS6d(mi0aAbzAO%PPQh*d71xNu>fE4)uDew=*UG@G0uR$&V00001cO7>gDbEXwjE@n`vpL)Q!g95=%0p?thQH{&H7b=+#X+ z+j8$OatWOB+;U5UNk~Eu?_mbfQ=9H=J6n1;dWts7`8ma*p|5w%dwz41j?;;K@Atj8 zU*K@X#8Ob1@xndb^|tEPGelO^vp! QRRsCW)78&qol`;+0Gy6>a{vGU literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/read_ticks_s@2x.png b/Telegram/Resources/icons/menu/read_ticks_s@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..2566c99cb302d63c399c9491f36b874751e479b0 GIT binary patch literal 695 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1SD@H!XnzCq0zmTR7 zr{|zy7M*|GUQSRH)kI(%rEdD<?7VL9Eu) z9WirG#H=6UgiRfU+?{Kw&!~>H=so7`GrELz_c6;s7q@P+J{+GVF{Q2jbqMdfj1-M#!-I(t^{ur?~ zESahMsMqq#0pEV_J)8Dh#;IAT|M<%Df1fKo{QUE3mZ7i3Cqbg3`o>BCEM~^1h!A+0v;ramDr52iF9x z{PQzoXAIx^*=LXCm<5|{jNsuCyZ-p&g|t7uN;+J9JUYQEL)0carf6gA`6MbbAj}9_8uc7?Q#I zc1Enm=0u6(XPrI=o=_3WoRzUDDoR&VUbAyj)WjmArBia)h0?R%rWC$-?7=r>%8`lv z%vYkk)~K#^Rg&L0AwT)q-dC$uTYssus3_mR_j>V%FZVz1pZEOd{r&0ZOYcYf@Z|e6 za2hbG9blQkAe_L2i}NF%W6PE;IXO9BzJ9HhE6B`z`RUWAD_25lYW}2$zf#W4&CSox zfB*jd!vYIUP0uq?F)=lTg^Xf3U%!4`q`EsVFR!k)_UH?3ZSAC(Ubo_^s;+BSuZBiN z&3fUr_Mu{nlVZ=i&p$6+zO0!S7kBUBL&d1=+qXY``ZVRF-l|or=FOXDu!+%{8GHBaF)=X#YQFHYWcu{!5`W*kS+ilo1fk_imN;B? z=eANg!w9r^OVrz+KP}z(*_u^VRi{`O8Wz^p%Id!O_xA1El)t$P12i6d+i>*Qv47|G za&vL{`S}_A`ucTi-1_C4g@l9>#N4>Q&7C_p#nthENK4?ccIigWw)rh5PMzXnVT$PCc;p32$K9eb+B+3agIK z%ii|Nm21|#ab9rc%9XWib!9|#=FXZm>qyhajfPti>`hEmqAp(l;E*x3pr(eWo9)z; zIdk58tT3?JlE3+;k66dUhlx^GPn_t;IJv01t82mO#8YR_`exjj;IlMn?Yec34tl6u zx_D9XSgD1K$*Rsy&WsI4E$xhCXJb1tYu>!Je~P;< zWp*ERD$Gpeb6*p-ddAl6+ozv;_Wb$6>)q>?+W{r%tMpZ6?&>flVzjrNKkn3e23WR3irZh>-}r>mdKI;Vst0JhrF=Kufz literal 0 HcmV?d00001 diff --git a/Telegram/SourceFiles/api/api_who_reacted.cpp b/Telegram/SourceFiles/api/api_who_reacted.cpp index 121b75901..815f85d59 100644 --- a/Telegram/SourceFiles/api/api_who_reacted.cpp +++ b/Telegram/SourceFiles/api/api_who_reacted.cpp @@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_media_types.h" #include "data/data_message_reaction_id.h" +#include "lang/lang_keys.h" #include "main/main_app_config.h" #include "main/main_session.h" #include "main/main_account.h" @@ -36,37 +37,33 @@ constexpr auto kContextReactionsLimit = 50; using Data::ReactionId; struct Peers { - std::vector list; + std::vector list; bool unknown = false; + + friend inline bool operator==( + const Peers &a, + const Peers &b) noexcept = default; }; -inline bool operator==(const Peers &a, const Peers &b) noexcept { - return (a.list == b.list) && (a.unknown == b.unknown); -} struct PeerWithReaction { - PeerId peer = 0; + WhoReadPeer peerWithDate; ReactionId reaction; -}; -inline bool operator==( + + friend inline bool operator==( const PeerWithReaction &a, - const PeerWithReaction &b) noexcept { - return (a.peer == b.peer) && (a.reaction == b.reaction); -} + const PeerWithReaction &b) noexcept = default; +}; struct PeersWithReactions { std::vector list; - std::vector read; + std::vector read; int fullReactionsCount = 0; bool unknown = false; -}; -inline bool operator==( + + friend inline bool operator==( const PeersWithReactions &a, - const PeersWithReactions &b) noexcept { - return (a.fullReactionsCount == b.fullReactionsCount) - && (a.list == b.list) - && (a.read == b.read) - && (a.unknown == b.unknown); -} + const PeersWithReactions &b) noexcept = default; +}; struct CachedRead { CachedRead() @@ -113,6 +110,7 @@ struct Context { struct Userpic { not_null peer; + TimeId date = 0; QString customEntityData; mutable Ui::PeerUserpicView view; mutable InMemoryKey uniqueKey; @@ -234,7 +232,10 @@ struct State { auto parsed = Peers(); parsed.list.reserve(result.v.size()); for (const auto &id : result.v) { - parsed.list.push_back(UserId(id.data().vuser_id())); + parsed.list.push_back({ + .peer = UserId(id.data().vuser_id()), + .date = id.data().vdate().v, + }); } entry.data = std::move(parsed); }).fail([=] { @@ -252,8 +253,8 @@ struct State { [[nodiscard]] PeersWithReactions WithEmptyReactions( Peers &&peers) { auto result = PeersWithReactions{ - .list = peers.list | ranges::views::transform([](PeerId peer) { - return PeerWithReaction{ .peer = peer }; + .list = peers.list | ranges::views::transform([](WhoReadPeer peer) { + return PeerWithReaction{ .peerWithDate = peer }; }) | ranges::to_vector, .unknown = peers.unknown, }; @@ -302,7 +303,9 @@ struct State { for (const auto &vote : data.vreactions().v) { vote.match([&](const auto &data) { parsed.list.push_back(PeerWithReaction{ - .peer = peerFromMTP(data.vpeer_id()), + .peerWithDate = { + .peer = peerFromMTP(data.vpeer_id()), + }, .reaction = Data::ReactionFromMTP( data.vreaction()), }); @@ -334,9 +337,16 @@ struct State { return PeersWithReactions{ .unknown = true }; } auto &list = reacted.list; - for (const auto &peer : read.list) { - if (!ranges::contains(list, peer, &PeerWithReaction::peer)) { - list.push_back({ .peer = peer }); + for (const auto &peerWithDate : read.list) { + const auto i = ranges::find( + list, + peerWithDate.peer, + [](const PeerWithReaction &p) { + return p.peerWithDate.peer; }); + if (i != end(list)) { + i->peerWithDate.date = peerWithDate.date; + } else { + list.push_back({ .peerWithDate = peerWithDate }); } } reacted.read = std::move(read.list); @@ -344,6 +354,37 @@ struct State { }); } +[[nodiscard]] QString FormatReadDate(TimeId date, const QDateTime &now) { + if (!date) { + return {}; + } + const auto parsed = base::unixtime::parse(date); + const auto readDate = parsed.date(); + const auto nowDate = now.date(); + if (readDate == nowDate) { + return tr::lng_mediaview_today( + tr::now, + lt_time, + QLocale().toString(parsed.time(), QLocale::ShortFormat)); + } else if (readDate.addDays(1) == nowDate) { + return tr::lng_mediaview_yesterday( + tr::now, + lt_time, + QLocale().toString(parsed.time(), QLocale::ShortFormat)); + } + return tr::lng_mediaview_date_time( + tr::now, + lt_date, + tr::lng_month_day( + tr::now, + lt_month, + Lang::MonthDay(readDate.month())(tr::now), + lt_day, + QString::number(readDate.day())), + lt_time, + QLocale().toString(parsed.time(), QLocale::ShortFormat)); +} + bool UpdateUserpics( not_null state, not_null item, @@ -352,13 +393,15 @@ bool UpdateUserpics( struct ResolvedPeer { PeerData *peer = nullptr; + TimeId date = 0; ReactionId reaction; }; const auto peers = ranges::views::all( ids ) | ranges::views::transform([&](PeerWithReaction id) { return ResolvedPeer{ - .peer = owner.peerLoaded(id.peer), + .peer = owner.peerLoaded(id.peerWithDate.peer), + .date = id.peerWithDate.date, .reaction = id.reaction, }; }) | ranges::views::filter([](ResolvedPeer resolved) { @@ -369,8 +412,8 @@ bool UpdateUserpics( state->userpics, peers, ranges::equal_to(), - &Userpic::peer, - [](const ResolvedPeer &r) { return not_null{ r.peer }; }); + [](const Userpic &u) { return std::pair(u.peer.get(), u.date); }, + [](const ResolvedPeer &r) { return std::pair(r.peer, r.date); }); if (same) { return false; } @@ -381,12 +424,14 @@ bool UpdateUserpics( const auto &data = ReactionEntityData(resolved.reaction); const auto i = ranges::find(was, peer, &Userpic::peer); if (i != end(was) && i->view.cloud) { + i->date = resolved.date; now.push_back(std::move(*i)); now.back().customEntityData = data; continue; } now.push_back(Userpic{ .peer = peer, + .date = resolved.date, .customEntityData = data, }); auto &userpic = now.back(); @@ -422,20 +467,24 @@ void RegenerateUserpics(not_null state, int small, int large) { } void RegenerateParticipants(not_null state, int small, int large) { + const auto currentDate = QDateTime::currentDateTime(); auto old = base::take(state->current.participants); auto &now = state->current.participants; now.reserve(state->userpics.size()); for (auto &userpic : state->userpics) { const auto peer = userpic.peer; + const auto date = userpic.date; const auto id = peer->id.value; const auto was = ranges::find(old, id, &Ui::WhoReadParticipant::id); if (was != end(old)) { was->name = peer->name(); + was->date = FormatReadDate(date, currentDate); now.push_back(std::move(*was)); continue; } now.push_back({ .name = peer->name(), + .date = FormatReadDate(date, currentDate), .customEntityData = userpic.customEntityData, .userpicLarge = GenerateUserpic(userpic, large), .userpicKey = userpic.uniqueKey, @@ -522,7 +571,7 @@ rpl::producer WhoReacted( &PeerWithReaction::reaction); whoReadIds->list = (peers.read.size() > reacted) ? std::move(peers.read) - : std::vector(); + : std::vector(); } if (UpdateUserpics(state, item, peers.list)) { RegenerateParticipants(state, small, large); diff --git a/Telegram/SourceFiles/api/api_who_reacted.h b/Telegram/SourceFiles/api/api_who_reacted.h index b79b47584..b51623041 100644 --- a/Telegram/SourceFiles/api/api_who_reacted.h +++ b/Telegram/SourceFiles/api/api_who_reacted.h @@ -34,8 +34,17 @@ enum class WhoReactedList { not_null item, WhoReactedList list); +struct WhoReadPeer { + PeerId peer = 0; + TimeId date = 0; + + friend inline bool operator==( + const WhoReadPeer &a, + const WhoReadPeer &b) noexcept = default; +}; + struct WhoReadList { - std::vector list; + std::vector list; Ui::WhoReadType type = {}; }; diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_list.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_list.cpp index 05769f543..f925dcb90 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_list.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_list.cpp @@ -250,8 +250,8 @@ uint64 Controller::id( void Controller::fillWhoRead() { if (_whoReadIds && !_whoReadIds->list.empty() && _whoRead.empty()) { auto &owner = _window->session().data(); - for (const auto &peerId : _whoReadIds->list) { - if (const auto peer = owner.peerLoaded(peerId)) { + for (const auto &peerWithDate : _whoReadIds->list) { + if (const auto peer = owner.peerLoaded(peerWithDate.peer)) { _whoRead.push_back(peer); } } diff --git a/Telegram/SourceFiles/lang/lang_keys.cpp b/Telegram/SourceFiles/lang/lang_keys.cpp index 51f581a04..aa65ed81d 100644 --- a/Telegram/SourceFiles/lang/lang_keys.cpp +++ b/Telegram/SourceFiles/lang/lang_keys.cpp @@ -52,72 +52,7 @@ inline QString langDateMaybeWithYear( return withoutYear(month, year); } -tr::phrase<> Month(int index) { - switch (index) { - case 1: return tr::lng_month1; - case 2: return tr::lng_month2; - case 3: return tr::lng_month3; - case 4: return tr::lng_month4; - case 5: return tr::lng_month5; - case 6: return tr::lng_month6; - case 7: return tr::lng_month7; - case 8: return tr::lng_month8; - case 9: return tr::lng_month9; - case 10: return tr::lng_month10; - case 11: return tr::lng_month11; - case 12: return tr::lng_month12; - } - Unexpected("Index in MonthSmall."); -} - -tr::phrase<> MonthSmall(int index) { - switch (index) { - case 1: return tr::lng_month1_small; - case 2: return tr::lng_month2_small; - case 3: return tr::lng_month3_small; - case 4: return tr::lng_month4_small; - case 5: return tr::lng_month5_small; - case 6: return tr::lng_month6_small; - case 7: return tr::lng_month7_small; - case 8: return tr::lng_month8_small; - case 9: return tr::lng_month9_small; - case 10: return tr::lng_month10_small; - case 11: return tr::lng_month11_small; - case 12: return tr::lng_month12_small; - } - Unexpected("Index in MonthSmall."); -} - -tr::phrase<> MonthDay(int index) { - switch (index) { - case 1: return tr::lng_month_day1; - case 2: return tr::lng_month_day2; - case 3: return tr::lng_month_day3; - case 4: return tr::lng_month_day4; - case 5: return tr::lng_month_day5; - case 6: return tr::lng_month_day6; - case 7: return tr::lng_month_day7; - case 8: return tr::lng_month_day8; - case 9: return tr::lng_month_day9; - case 10: return tr::lng_month_day10; - case 11: return tr::lng_month_day11; - case 12: return tr::lng_month_day12; - } - Unexpected("Index in MonthDay."); -} - -tr::phrase<> Weekday(int index) { - switch (index) { - case 1: return tr::lng_weekday1; - case 2: return tr::lng_weekday2; - case 3: return tr::lng_weekday3; - case 4: return tr::lng_weekday4; - case 5: return tr::lng_weekday5; - case 6: return tr::lng_weekday6; - case 7: return tr::lng_weekday7; - } - Unexpected("Index in Weekday."); -} +using namespace Lang; } // namespace @@ -245,4 +180,71 @@ QString LanguageIdOrDefault(const QString &id) { return !id.isEmpty() ? id : DefaultLanguageId(); } +tr::phrase<> Month(int index) { + switch (index) { + case 1: return tr::lng_month1; + case 2: return tr::lng_month2; + case 3: return tr::lng_month3; + case 4: return tr::lng_month4; + case 5: return tr::lng_month5; + case 6: return tr::lng_month6; + case 7: return tr::lng_month7; + case 8: return tr::lng_month8; + case 9: return tr::lng_month9; + case 10: return tr::lng_month10; + case 11: return tr::lng_month11; + case 12: return tr::lng_month12; + } + Unexpected("Index in MonthSmall."); +} + +tr::phrase<> MonthSmall(int index) { + switch (index) { + case 1: return tr::lng_month1_small; + case 2: return tr::lng_month2_small; + case 3: return tr::lng_month3_small; + case 4: return tr::lng_month4_small; + case 5: return tr::lng_month5_small; + case 6: return tr::lng_month6_small; + case 7: return tr::lng_month7_small; + case 8: return tr::lng_month8_small; + case 9: return tr::lng_month9_small; + case 10: return tr::lng_month10_small; + case 11: return tr::lng_month11_small; + case 12: return tr::lng_month12_small; + } + Unexpected("Index in MonthSmall."); +} + +tr::phrase<> MonthDay(int index) { + switch (index) { + case 1: return tr::lng_month_day1; + case 2: return tr::lng_month_day2; + case 3: return tr::lng_month_day3; + case 4: return tr::lng_month_day4; + case 5: return tr::lng_month_day5; + case 6: return tr::lng_month_day6; + case 7: return tr::lng_month_day7; + case 8: return tr::lng_month_day8; + case 9: return tr::lng_month_day9; + case 10: return tr::lng_month_day10; + case 11: return tr::lng_month_day11; + case 12: return tr::lng_month_day12; + } + Unexpected("Index in MonthDay."); +} + +tr::phrase<> Weekday(int index) { + switch (index) { + case 1: return tr::lng_weekday1; + case 2: return tr::lng_weekday2; + case 3: return tr::lng_weekday3; + case 4: return tr::lng_weekday4; + case 5: return tr::lng_weekday5; + case 6: return tr::lng_weekday6; + case 7: return tr::lng_weekday7; + } + Unexpected("Index in Weekday."); +} + } // namespace Lang diff --git a/Telegram/SourceFiles/lang/lang_keys.h b/Telegram/SourceFiles/lang/lang_keys.h index 87934fcce..f49f4fd32 100644 --- a/Telegram/SourceFiles/lang/lang_keys.h +++ b/Telegram/SourceFiles/lang/lang_keys.h @@ -37,4 +37,9 @@ namespace Lang { [[nodiscard]] QString DefaultLanguageId(); [[nodiscard]] QString LanguageIdOrDefault(const QString &id); +[[nodiscard]] tr::phrase<> Month(int index); +[[nodiscard]] tr::phrase<> MonthSmall(int index); +[[nodiscard]] tr::phrase<> MonthDay(int index); +[[nodiscard]] tr::phrase<> Weekday(int index); + } // namespace Lang diff --git a/Telegram/SourceFiles/ui/boxes/choose_date_time.cpp b/Telegram/SourceFiles/ui/boxes/choose_date_time.cpp index 2f77253c1..a56957bc4 100644 --- a/Telegram/SourceFiles/ui/boxes/choose_date_time.cpp +++ b/Telegram/SourceFiles/ui/boxes/choose_date_time.cpp @@ -23,29 +23,11 @@ namespace { constexpr auto kMinimalSchedule = TimeId(10); -tr::phrase<> MonthDay(int index) { - switch (index) { - case 1: return tr::lng_month_day1; - case 2: return tr::lng_month_day2; - case 3: return tr::lng_month_day3; - case 4: return tr::lng_month_day4; - case 5: return tr::lng_month_day5; - case 6: return tr::lng_month_day6; - case 7: return tr::lng_month_day7; - case 8: return tr::lng_month_day8; - case 9: return tr::lng_month_day9; - case 10: return tr::lng_month_day10; - case 11: return tr::lng_month_day11; - case 12: return tr::lng_month_day12; - } - Unexpected("Index in MonthDay."); -} - QString DayString(const QDate &date) { return tr::lng_month_day( tr::now, lt_month, - MonthDay(date.month())(tr::now), + Lang::MonthDay(date.month())(tr::now), lt_day, QString::number(date.day())); } diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index a83726419..d5768e364 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1095,6 +1095,17 @@ whoReadMenu: PopupMenu(popupMenuExpandedSeparator) { scrollPadding: margins(0px, 6px, 0px, 4px); maxHeight: 400px; } +whoReadNameWithDateTop: 3px; +whoReadDateTop: 20px; +whoReadDateSkip: 15px; +whoReadDateChecks: icon{{ "menu/read_ticks_s", windowSubTextFg }}; +whoReadDateChecksOver: icon{{ "menu/read_ticks_s", windowSubTextFgOver }}; +whoReadDateChecksPosition: point(-7px, -4px); +whoReadDateStyle: TextStyle(defaultTextStyle) { + font: font(12px); + linkFont: font(12px); + linkFontOver: font(12px underline); +} whoReadChecks: icon{{ "menu/read_ticks", windowBoldFg }}; whoReadChecksOver: icon{{ "menu/read_ticks", windowBoldFg }}; whoReadChecksDisabled: icon{{ "menu/read_ticks", menuFgDisabled }}; diff --git a/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp b/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp index 98c261e1c..5b17e3832 100644 --- a/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp +++ b/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp @@ -73,6 +73,7 @@ using Text::CustomEmojiFactory; struct EntryData { QString text; + QString date; QString customEntityData; QImage userpic; Fn callback; @@ -287,7 +288,7 @@ void Action::updateUserpicsFromContent() { } void Action::populateSubmenu() { - if (_content.participants.size() < 2) { + if (_content.participants.size() < 1) { _submenu.clear(); _parentMenu->removeSubmenu(action()); if (!isEnabled()) { @@ -487,6 +488,7 @@ private: const int _height = 0; Text::String _text; + Text::String _date; std::unique_ptr _custom; QImage _userpic; int _textWidth = 0; @@ -533,11 +535,21 @@ void WhoReactedListMenu::EntryAction::setData(EntryData &&data) { setClickedCallback(std::move(data.callback)); _userpic = std::move(data.userpic); _text.setMarkedText(_st.itemStyle, { data.text }, MenuTextOptions); + if (data.date.isEmpty()) { + _date = Text::String(); + } else { + _date.setMarkedText( + st::whoReadDateStyle, + { data.date }, + MenuTextOptions); + } _custom = _customEmojiFactory(data.customEntityData, [=] { update(); }); const auto ratio = style::DevicePixelRatio(); const auto size = Emoji::GetSizeNormal() / ratio; _customSize = Text::AdjustCustomEmojiSize(size); - const auto textWidth = _text.maxWidth(); + const auto textWidth = std::max( + _text.maxWidth(), + st::whoReadDateSkip + _date.maxWidth()); const auto &padding = _st.itemPadding; const auto rightSkip = padding.right() + (_custom ? (size + padding.right()) : 0); @@ -571,6 +583,10 @@ void WhoReactedListMenu::EntryAction::paint(Painter &&p) { QRect(photoLeft, photoTop, photoSize, photoSize)); } + const auto withDate = !_date.isEmpty(); + const auto textTop = withDate + ? st::whoReadNameWithDateTop + : (height() - _st.itemStyle.font->height) / 2; p.setPen(selected ? _st.itemFgOver : enabled @@ -579,10 +595,25 @@ void WhoReactedListMenu::EntryAction::paint(Painter &&p) { _text.drawLeftElided( p, st::defaultWhoRead.nameLeft, - (height() - _st.itemStyle.font->height) / 2, + textTop, _textWidth, width()); - + if (withDate) { + const auto iconPosition = QPoint( + st::defaultWhoRead.nameLeft, + st::whoReadDateTop) + st::whoReadDateChecksPosition; + const auto &icon = selected + ? st::whoReadDateChecksOver + : st::whoReadDateChecks; + icon.paint(p, iconPosition, width()); + p.setPen(selected ? _st.itemFgShortcutOver : _st.itemFgShortcut); + _date.drawLeftElided( + p, + st::defaultWhoRead.nameLeft + st::whoReadDateSkip, + st::whoReadDateTop, + _textWidth - st::whoReadDateSkip, + width()); + } if (_custom) { const auto ratio = style::DevicePixelRatio(); const auto size = Emoji::GetSizeNormal() / ratio; @@ -600,6 +631,7 @@ void WhoReactedListMenu::EntryAction::paint(Painter &&p) { bool operator==(const WhoReadParticipant &a, const WhoReadParticipant &b) { return (a.id == b.id) && (a.name == b.name) + && (a.date == b.date) && (a.userpicKey == b.userpicKey); } @@ -680,6 +712,7 @@ void WhoReactedListMenu::populate( }; append({ .text = participant.name, + .date = participant.date, .customEntityData = participant.customEntityData, .userpic = participant.userpicLarge, .callback = chosen, diff --git a/Telegram/SourceFiles/ui/controls/who_reacted_context_action.h b/Telegram/SourceFiles/ui/controls/who_reacted_context_action.h index 46d2cecf9..49d690ec4 100644 --- a/Telegram/SourceFiles/ui/controls/who_reacted_context_action.h +++ b/Telegram/SourceFiles/ui/controls/who_reacted_context_action.h @@ -19,6 +19,7 @@ class PopupMenu; struct WhoReadParticipant { QString name; + QString date; QString customEntityData; QImage userpicSmall; QImage userpicLarge;