From e98770d418e52b6518a363961de2192b6166a872 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 19 Jun 2023 21:00:34 +0400 Subject: [PATCH] Improve saved / archive stories design. --- .../icons/info/info_stories_archive.png | Bin 0 -> 727 bytes .../icons/info/info_stories_archive@2x.png | Bin 0 -> 1450 bytes .../icons/info/info_stories_archive@3x.png | Bin 0 -> 2221 bytes .../icons/info/info_stories_recent.png | Bin 0 -> 606 bytes .../icons/info/info_stories_recent@2x.png | Bin 0 -> 1131 bytes .../icons/info/info_stories_recent@3x.png | Bin 0 -> 1719 bytes Telegram/Resources/langs/lang.strings | 10 +- .../boxes/peer_list_controllers.cpp | 1 + Telegram/SourceFiles/data/data_stories.cpp | 1 + Telegram/SourceFiles/dialogs/dialogs.style | 19 ++ .../dialogs/dialogs_inner_widget.cpp | 1 + .../dialogs/ui/dialogs_stories_content.cpp | 292 +++++++++++++++++- .../dialogs/ui/dialogs_stories_content.h | 2 + .../dialogs/ui/dialogs_stories_list.cpp | 208 +++++++------ .../dialogs/ui/dialogs_stories_list.h | 40 ++- Telegram/SourceFiles/history/history_item.cpp | 2 + Telegram/SourceFiles/info/info.style | 2 + Telegram/SourceFiles/info/info_top_bar.cpp | 2 + .../info/media/info_media_buttons.h | 2 +- .../info/media/info_media_list_section.cpp | 4 +- .../info/media/info_media_list_section.h | 1 + .../info/media/info_media_provider.cpp | 18 +- .../stories/info_stories_inner_widget.cpp | 123 ++++++-- .../info/stories/info_stories_inner_widget.h | 2 +- .../info/stories/info_stories_provider.cpp | 19 +- .../SourceFiles/overview/overview_layout.cpp | 92 +++--- .../SourceFiles/overview/overview_layout.h | 11 +- .../window/window_session_controller.cpp | 6 +- .../window/window_session_controller.h | 4 +- 29 files changed, 660 insertions(+), 202 deletions(-) create mode 100644 Telegram/Resources/icons/info/info_stories_archive.png create mode 100644 Telegram/Resources/icons/info/info_stories_archive@2x.png create mode 100644 Telegram/Resources/icons/info/info_stories_archive@3x.png create mode 100644 Telegram/Resources/icons/info/info_stories_recent.png create mode 100644 Telegram/Resources/icons/info/info_stories_recent@2x.png create mode 100644 Telegram/Resources/icons/info/info_stories_recent@3x.png diff --git a/Telegram/Resources/icons/info/info_stories_archive.png b/Telegram/Resources/icons/info/info_stories_archive.png new file mode 100644 index 0000000000000000000000000000000000000000..9b4b79ce4365cb35163cb2cb7eb37c13479bc395 GIT binary patch literal 727 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 J@+F7Ze*lqM@|*ww literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/info/info_stories_recent.png b/Telegram/Resources/icons/info/info_stories_recent.png new file mode 100644 index 0000000000000000000000000000000000000000..341ee2a06ce04c9b9a159b216e977e5653984211 GIT binary patch literal 606 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1SJ1Ryj={W7>k44ofy`glX(f`xTHpSruq6Z zXaU(A42G$TlC0TWzSVF5FO4N|zKE$K5**$Gb<#}E(R zx1sy{8672__H}4*a2@G)S-kj$%chV?xh@MYb-Sou6#C!d=DbvJE!)&2(Jex~K_ZKS z12|4Ti^~(98GV19;=5U!k3Kv5`(91{x7joEeV2XzyzapLumf3JXPtd^z4}?v&bs~k zZ@(3BU3vYrL5JJon>pWD{U&<2OndtHLvyrk}1VuB_Z!7ONxXzx;BB$*$X6JLlxpTbxWWntZarx6b2B^!_vLVjso+ c@8_yvl)trP%Io6H%b>XRboFyt=akR{0PKbPApigX literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/info/info_stories_recent@2x.png b/Telegram/Resources/icons/info/info_stories_recent@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..ecb3fc72d91d3cbbc9ec444f1b5151960118bb19 GIT binary patch literal 1131 zcmV-x1eE)UP)00001b5ch_0Itp) z=>Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91K%fHv1ONa40RR91KmY&$07g+lumAu9)k#D_RA>e5S<5SYQ5^RkGlVco zW2Go1k)&UFEU=kKR*FcJ1q&3}%*sMm{s${7C9jCZM5cy96q%Yl@|X$bk$xWG_xU+> z;|}+J&)j>>sB;##d(ZiNU!QxP-xCobieNx6AQ%t~2nGZLf&syRU?7lz$jC^m)fy8M z6WA2KH90vsgM))kr}N<8;PLVC?d{F$^~S};g|ElVGmzcX)U>_54Uc8z`uaLCF;NO& zWMstcb^{C%sWQYbWo2dXN%H&Y>1lp`et3AeySuxgp&>mz{iA<}hldQgwzgJYUjDHu zH17_F~t4;va9V&0FBkDE*; zjRwqSv(0AHsDwkjzP_II`=zC&=;&w;$#0+2Z$cDwzXc3rE%*6Haf=*LW-mX@ZA zZUg{@9*-wBHdYsfN=gO>23Y5*sHjlFy|>lX)s2phCM6{)BLQPNdwYA8ky=}mlasHm zu3&6#ZjPc0vlGA_9UZD*^hO{rE-qA&ngDQxQB_n_L{W5qe-C_LU!N*CFE8)%@)E0? zDpCgE+iBrnLmn#;icFEl&%M39%*;$IZM=Fo0*H@~udc3!f$8aKUNaQ@^Yb$WCc}`{ zP9{@YTAGlMfcKf18JU(4esTnmm6gRD-QC?a$YZRJ`}_MD85wl_908z@Lhs$(9fl^| z02i{evokk0x3I8~F2x-HW=6VpUL-rdaUB&n1~IQU5*|w)wjR;7CnPb-7&2%ga?&RX^cY5H_SMCR}9vrYoi|L&b0T xeV2S+Mj|Q&1A+m;fM7r{AQ%t~2nGyd;2#KPV-@s>`&Y@eYtJ|^E}=E0ky)6cX=!C+C0diFCK_s~U}!G5WHm~f zTP{r82^TcV4HaXPvfRtk$HJ>lTA3ju;(|8#PxIgW@jajOp7)$T&ikD6ygA2wJ#=+U zbpQa+#e3rX)ePAbL_=+fs}nJ5fa3f;4g%~x^AG9+F7y|Cn2!&DQu`19lobJJZKkMY zsulofR)GLbH9?!PDvfWqAyu0H_?w9=$6#Lo*n-64+)gBdK9_w$hXxx*# zY-~IhH0NGHij7VCnyhSFtFF ztl-B)qG^`R1On>AFvY%xRYs4Fj*3L~!92+2dA!jS;_zZwS=s99>K}33jq{AGfpTMj zz#X~cp|@mfYpcJ;U@&mAe=0Q+5)!IyMG}dj=0HwH!0U!&Ds}%(c6y7h80yp_j$H$HynSf*C0fq_A` zTX7_vLo%`kjLgiQKYTMbCg4<)E+Yv88l1*POGm}ShdYPZoJ{oya5xw!6iRjs2c4Vu z6$I>ao7H8+AZ={IuU_5u{PythuxR&S?~n$0ZOL}2zuP|M!hA2 zGi{d-iLCUbxVhiOVsU6#m{t5hBfGV$OFBCGtl@!ww`JxrNEuhkyEY|_MxB>iu-R;Z zUh;A$e!qe|f}`Bv@52ZWP=CSUGAeY7+LN{dH*eiCsCC3(JWJpku-_v2FN&{wai+bX zkcg_FprFKDlkt0Y&lp&&WdV!1SFh@nbu0JURH39se6jAEJ!a|q5 z=@b3+ZsbkFqM{!WAY{g!9Uar^*S?*T zGuU3sfZG&oq!Hmm*GfxEac1@&?(PN;8~`LmCr?Hs5^b|8ot%yEeBtyqLkr#GXH9S4 zZ+Kp?r^(UX?#9MYYQEpuGiPqFYu@S|DjM*xO7izNU|fjyJWQ}!FYOQjJCfl@XJ=FT zPx8l;I*CuvbI49Io)ESo`+xfCT4--?@8bIiqT7+~`y+>6fnkSsJ2_=!Wz_~BZ0->V z1Y=`k>eke2B$s0dEywkR7fW5*7;^&etTcP8tf;TgpzK{`UCm2>m4WN7QZ()pLqLYz5Gb3eu$Pa g= PrepareContactsBox( auto stories = object_ptr( box, + st::dialogsStoriesList, Stories::ContentForSession( &sessionController->session(), Data::StorySourcesList::All), diff --git a/Telegram/SourceFiles/data/data_stories.cpp b/Telegram/SourceFiles/data/data_stories.cpp index 260e4d766a..f8a51b93ef 100644 --- a/Telegram/SourceFiles/data/data_stories.cpp +++ b/Telegram/SourceFiles/data/data_stories.cpp @@ -423,6 +423,7 @@ void Stories::apply(const MTPDupdateStory &data) { if (!user->hasStoriesHidden()) { refreshInList(StorySourcesList::NotHidden); } + _sourceChanged.fire_copy(peerId); } void Stories::apply(not_null peer, const MTPUserStories *data) { diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index 47a0256114..30b36538d9 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -500,6 +500,12 @@ DialogsStories { nameTop: pixels; nameStyle: TextStyle; } +DialogsStoriesList { + small: DialogsStories; + full: DialogsStories; + bg: color; + readOpacity: double; +} dialogsStories: DialogsStories { left: 4px; @@ -532,3 +538,16 @@ dialogsStoriesFull: DialogsStories { linkFontOver: font(11px); } } + +dialogsStoriesList: DialogsStoriesList { + small: dialogsStories; + full: dialogsStoriesFull; + bg: dialogsBg; + readOpacity: 0.6; +} +dialogsStoriesListInfo: DialogsStoriesList(dialogsStoriesList) { + bg: transparent; +} +dialogsStoriesListMine: DialogsStoriesList(dialogsStoriesListInfo) { + readOpacity: 1.; +} diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 22ac098424..a63e159c73 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -142,6 +142,7 @@ InnerWidget::InnerWidget( , _controller(controller) , _stories(std::make_unique( this, + st::dialogsStoriesList, Stories::ContentForSession( &controller->session(), Data::StorySourcesList::NotHidden), diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.cpp index 332dec14a1..24a97ae705 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.cpp @@ -8,6 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/ui/dialogs_stories_content.h" #include "data/data_changes.h" +#include "data/data_document.h" +#include "data/data_document_media.h" +#include "data/data_file_origin.h" +#include "data/data_photo.h" +#include "data/data_photo_media.h" #include "data/data_session.h" #include "data/data_stories.h" #include "data/data_user.h" @@ -19,7 +24,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Dialogs::Stories { namespace { -class PeerUserpic final : public Userpic { +constexpr auto kShownLastCount = 3; + +class PeerUserpic final : public Thumbnail { public: explicit PeerUserpic(not_null peer); @@ -48,6 +55,70 @@ private: }; +class StoryThumbnail : public Thumbnail { +public: + explicit StoryThumbnail(FullStoryId id); + virtual ~StoryThumbnail() = default; + + QImage image(int size) override; + void subscribeToUpdates(Fn callback) override; + +protected: + struct Thumb { + Image *image = nullptr; + bool blurred = false; + }; + [[nodiscard]] virtual Main::Session &session() = 0; + [[nodiscard]] virtual Thumb loaded(FullStoryId id) = 0; + virtual void clear() = 0; + +private: + const FullStoryId _id; + QImage _full; + rpl::lifetime _subscription; + QImage _prepared; + bool _blurred = false; + +}; + +class PhotoThumbnail final : public StoryThumbnail { +public: + PhotoThumbnail(not_null photo, FullStoryId id); + +private: + Main::Session &session() override; + Thumb loaded(FullStoryId id) override; + void clear() override; + + const not_null _photo; + std::shared_ptr _media; + +}; + +class VideoThumbnail final : public StoryThumbnail { +public: + VideoThumbnail(not_null video, FullStoryId id); + +private: + Main::Session &session() override; + Thumb loaded(FullStoryId id) override; + void clear() override; + + const not_null _video; + std::shared_ptr _media; + +}; + +class EmptyThumbnail final : public Thumbnail { +public: + QImage image(int size) override; + void subscribeToUpdates(Fn callback) override; + +private: + QImage _cached; + +}; + class State final { public: State(not_null data, Data::StorySourcesList list); @@ -57,7 +128,9 @@ public: private: const not_null _data; const Data::StorySourcesList _list; - base::flat_map, std::shared_ptr> _userpics; + base::flat_map< + not_null, + std::shared_ptr> _userpics; }; @@ -122,6 +195,127 @@ void PeerUserpic::processNewPhoto() { }, _subscribed->downloadLifetime); } +StoryThumbnail::StoryThumbnail(FullStoryId id) +: _id(id) { +} + +QImage StoryThumbnail::image(int size) { + const auto ratio = style::DevicePixelRatio(); + if (_prepared.width() != size * ratio) { + if (_full.isNull()) { + _prepared = QImage( + QSize(size, size) * ratio, + QImage::Format_ARGB32_Premultiplied); + _prepared.fill(Qt::black); + } else { + const auto width = _full.width(); + const auto skip = std::max((_full.height() - width) / 2, 0); + _prepared = _full.copy(0, skip, width, width).scaled( + QSize(size, size) * ratio, + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation); + } + _prepared = Images::Circle(std::move(_prepared)); + } + return _prepared; +} + +void StoryThumbnail::subscribeToUpdates(Fn callback) { + _subscription.destroy(); + if (!callback) { + clear(); + return; + } else if (!_full.isNull() && !_blurred) { + return; + } + const auto thumbnail = loaded(_id); + if (const auto image = thumbnail.image) { + _full = image->original(); + } + _blurred = thumbnail.blurred; + if (!_blurred) { + _prepared = QImage(); + } else { + _subscription = session().downloaderTaskFinished( + ) | rpl::filter([=] { + const auto thumbnail = loaded(_id); + if (!thumbnail.blurred) { + _full = thumbnail.image->original(); + _prepared = QImage(); + _blurred = false; + return true; + } + return false; + }) | rpl::take(1) | rpl::start_with_next(callback); + } +} + +PhotoThumbnail::PhotoThumbnail(not_null photo, FullStoryId id) +: StoryThumbnail(id) +, _photo(photo) { +} + +Main::Session &PhotoThumbnail::session() { + return _photo->session(); +} + +StoryThumbnail::Thumb PhotoThumbnail::loaded(FullStoryId id) { + if (!_media) { + _media = _photo->createMediaView(); + _media->wanted( + Data::PhotoSize::Small, + Data::FileOriginStory(id.peer, id.story)); + } + if (const auto small = _media->image(Data::PhotoSize::Small)) { + return { .image = small }; + } + return { .image = _media->thumbnailInline(), .blurred = true }; +} + +void PhotoThumbnail::clear() { + _media = nullptr; +} + +VideoThumbnail::VideoThumbnail( + not_null video, + FullStoryId id) +: StoryThumbnail(id) +, _video(video) { +} + +Main::Session &VideoThumbnail::session() { + return _video->session(); +} + +StoryThumbnail::Thumb VideoThumbnail::loaded(FullStoryId id) { + if (!_media) { + _media = _video->createMediaView(); + _media->thumbnailWanted(Data::FileOriginStory(id.peer, id.story)); + } + if (const auto small = _media->thumbnail()) { + return { .image = small }; + } + return { .image = _media->thumbnailInline(), .blurred = true }; +} + +void VideoThumbnail::clear() { + _media = nullptr; +} + +QImage EmptyThumbnail::image(int size) { + const auto ratio = style::DevicePixelRatio(); + if (_cached.width() != size * ratio) { + _cached = QImage( + QSize(size, size) * ratio, + QImage::Format_ARGB32_Premultiplied); + _cached.fill(Qt::black); + } + return _cached; +} + +void EmptyThumbnail::subscribeToUpdates(Fn callback) { +} + State::State(not_null data, Data::StorySourcesList list) : _data(data) , _list(list) { @@ -130,12 +324,12 @@ State::State(not_null data, Data::StorySourcesList list) Content State::next() { auto result = Content{ .full = (_list == Data::StorySourcesList::All) }; const auto &sources = _data->sources(_list); - result.users.reserve(sources.size()); + result.elements.reserve(sources.size()); for (const auto &info : sources) { const auto source = _data->source(info.id); Assert(source != nullptr); - auto userpic = std::shared_ptr(); + auto userpic = std::shared_ptr(); const auto user = source->user; if (const auto i = _userpics.find(user); i != end(_userpics)) { userpic = i->second; @@ -143,15 +337,15 @@ Content State::next() { userpic = std::make_shared(user); _userpics.emplace(user, userpic); } - result.users.push_back({ + result.elements.push_back({ .id = uint64(user->id.value), .name = (user->isSelf() ? tr::lng_stories_my_name(tr::now) : user->shortName()), - .userpic = std::move(userpic), + .thumbnail = std::move(userpic), .unread = info.unread, .hidden = info.hidden, - .self = user->isSelf(), + .skipSmall = user->isSelf(), }); } return result; @@ -177,4 +371,88 @@ rpl::producer ContentForSession( }; } +[[nodiscard]] std::shared_ptr PrepareThumbnail( + not_null story) { + using Result = std::shared_ptr; + const auto id = story->fullId(); + return v::match(story->media().data, [](v::null_t) -> Result { + return std::make_shared(); + }, [&](not_null photo) -> Result { + return std::make_shared(photo, id); + }, [&](not_null video) -> Result { + return std::make_shared(video, id); + }); +} + +rpl::producer LastForPeer(not_null peer) { + using namespace rpl::mappers; + + const auto stories = &peer->owner().stories(); + const auto peerId = peer->id; + + return rpl::single( + peerId + ) | rpl::then( + stories->sourceChanged() | rpl::filter(_1 == peerId) + ) | rpl::map([=] { + auto ids = std::vector(); + auto readTill = StoryId(); + if (const auto source = stories->source(peerId)) { + readTill = source->readTill; + ids = ranges::views::all(source->ids) + | ranges::views::reverse + | ranges::views::take(kShownLastCount) + | ranges::views::transform(&Data::StoryIdDates::id) + | ranges::to_vector; + } + return rpl::make_producer([=](auto consumer) { + auto lifetime = rpl::lifetime(); + + struct State { + Fn check; + base::has_weak_ptr guard; + bool pushed = false; + }; + const auto state = lifetime.make_state(); + state->check = [=] { + if (state->pushed) { + return; + } + auto resolving = false; + auto result = Content(); + for (const auto id : ids) { + const auto storyId = FullStoryId{ peerId, id }; + const auto maybe = stories->lookup(storyId); + if (maybe) { + if (!resolving) { + result.elements.reserve(ids.size()); + result.elements.push_back({ + .id = uint64(id), + .thumbnail = PrepareThumbnail(*maybe), + .unread = (id > readTill), + }); + } + } else if (maybe.error() == Data::NoStory::Unknown) { + resolving = true; + stories->resolve( + storyId, + crl::guard(&state->guard, state->check)); + } + } + if (resolving) { + return; + } + state->pushed = true; + consumer.put_next(std::move(result)); + consumer.put_done(); + }; + rpl::single(peerId) | rpl::then( + stories->itemsChanged() | rpl::filter(_1 == peerId) + ) | rpl::start_with_next(state->check, lifetime); + + return lifetime; + }); + }) | rpl::flatten_latest(); +} + } // namespace Dialogs::Stories diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.h b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.h index 1feb6a77ac..8b25c83134 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.h @@ -23,4 +23,6 @@ struct Content; not_null session, Data::StorySourcesList list); +[[nodiscard]] rpl::producer LastForPeer(not_null peer); + } // namespace Dialogs::Stories diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp index c53282395b..c022e025cf 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp @@ -17,13 +17,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Dialogs::Stories { namespace { -constexpr auto kSmallUserpicsShown = 3; -constexpr auto kSmallReadOpacity = 0.6; +constexpr auto kSmallThumbsShown = 3; constexpr auto kSummaryExpandLeft = 1.5; constexpr auto kPreloadPages = 2; -[[nodiscard]] int AvailableNameWidth() { - const auto &full = st::dialogsStoriesFull; +[[nodiscard]] int AvailableNameWidth(const style::DialogsStoriesList &st) { + const auto &full = st.full; const auto &font = full.nameStyle.font; const auto skip = font->spacew; return full.photoLeft * 2 + full.photo - 2 * skip; @@ -35,7 +34,7 @@ struct List::Layout { int itemsCount = 0; int shownHeight = 0; float64 ratio = 0.; - float64 userpicLeft = 0.; + float64 thumbnailLeft = 0.; float64 photoLeft = 0.; float64 left = 0.; float64 single = 0.; @@ -52,9 +51,11 @@ struct List::Layout { List::List( not_null parent, + const style::DialogsStoriesList &st, rpl::producer content, Fn shownHeight) : RpWidget(parent) +, _st(st) , _shownHeight(shownHeight) { setCursor(style::cur_default); @@ -64,45 +65,46 @@ List::List( _shownAnimation.stop(); setMouseTracking(true); - resize(0, _data.empty() ? 0 : st::dialogsStoriesFull.height); + resize(0, _data.empty() ? 0 : st.full.height); } void List::showContent(Content &&content) { if (_content == content) { return; } - if (content.users.empty()) { + if (content.elements.empty()) { _hidingData = base::take(_data); if (!_hidingData.empty()) { toggleAnimated(false); } return; } - const auto hidden = _content.users.empty(); + const auto hidden = _content.elements.empty(); _content = std::move(content); auto items = base::take( _data.items.empty() ? _hidingData.items : _data.items); _hidingData = {}; - _data.items.reserve(_content.users.size()); - for (const auto &user : _content.users) { - const auto i = ranges::find(items, user.id, [](const Item &item) { - return item.user.id; + _data.items.reserve(_content.elements.size()); + for (const auto &element : _content.elements) { + const auto id = element.id; + const auto i = ranges::find(items, id, [](const Item &item) { + return item.element.id; }); if (i != end(items)) { _data.items.push_back(std::move(*i)); auto &item = _data.items.back(); - if (item.user.userpic != user.userpic) { - item.user.userpic = user.userpic; + if (item.element.thumbnail != element.thumbnail) { + item.element.thumbnail = element.thumbnail; item.subscribed = false; } - if (item.user.name != user.name) { - item.user.name = user.name; + if (item.element.name != element.name) { + item.element.name = element.name; item.nameCache = QImage(); } - item.user.unread = user.unread; - item.user.hidden = user.hidden; + item.element.unread = element.unread; + item.element.hidden = element.hidden; } else { - _data.items.emplace_back(Item{ .user = user }); + _data.items.emplace_back(Item{ .element = element }); } } updateScrollMax(); @@ -115,23 +117,25 @@ void List::showContent(Content &&content) { List::Summaries List::ComposeSummaries(Data &data) { const auto total = int(data.items.size()); - const auto skip = (total > 1 && data.items[0].user.self) ? 1 : 0; + const auto skip = (total > 1 && data.items[0].element.skipSmall) + ? 1 + : 0; auto unreadInFirst = 0; auto unreadTotal = 0; for (auto i = skip; i != total; ++i) { - if (data.items[i].user.unread) { + if (data.items[i].element.unread) { ++unreadTotal; - if (i < skip + kSmallUserpicsShown) { + if (i < skip + kSmallThumbsShown) { ++unreadInFirst; } } } - auto result = Summaries{ .skipSelf = (skip > 0) }; + auto result = Summaries{ .skipOne = (skip > 0) }; result.total.string = tr::lng_stories_row_count(tr::now, lt_count, total); const auto append = [&](QString &to, int index, bool last) { if (to.isEmpty()) { - to = data.items[index].user.name; + to = data.items[index].element.name; } else { to = (last ? tr::lng_stories_row_unread_and_last @@ -140,19 +144,19 @@ List::Summaries List::ComposeSummaries(Data &data) { lt_accumulated, to, lt_user, - data.items[index].user.name); + data.items[index].element.name); } }; if (!total) { return result; - } else if (total <= skip + kSmallUserpicsShown) { + } else if (total <= skip + kSmallThumbsShown) { for (auto i = skip; i != total; ++i) { append(result.allNames.string, i, i == total - 1); } } if (unreadInFirst > 0 && unreadInFirst == unreadTotal) { for (auto i = skip; i != total; ++i) { - if (data.items[i].user.unread) { + if (data.items[i].element.unread) { append(result.unreadNames.string, i, !--unreadTotal); } } @@ -166,20 +170,22 @@ bool List::StringsEqual(const Summaries &a, const Summaries &b) { && (a.unreadNames.string == b.unreadNames.string); } -void List::Populate(Summary &summary) { +void List::Populate( + const style::DialogsStories &st, + Summary &summary) { if (summary.empty()) { return; } summary.cache = QImage(); - summary.text = Ui::Text::String( - st::dialogsStories.nameStyle, - summary.string); + summary.text = Ui::Text::String(st.nameStyle, summary.string); } -void List::Populate(Summaries &summaries) { - Populate(summaries.total); - Populate(summaries.allNames); - Populate(summaries.unreadNames); +void List::Populate( + const style::DialogsStories &st, + Summaries &summaries) { + Populate(st, summaries.total); + Populate(st, summaries.allNames); + Populate(st, summaries.unreadNames); } void List::updateSummary(Data &data) { @@ -188,7 +194,7 @@ void List::updateSummary(Data &data) { return; } data.summaries = std::move(summaries); - Populate(data.summaries); + Populate(_st.small, data.summaries); } void List::toggleAnimated(bool shown) { @@ -203,14 +209,14 @@ void List::updateHeight() { const auto shown = _shownAnimation.value(_data.empty() ? 0. : 1.); resize( width(), - anim::interpolate(0, st::dialogsStoriesFull.height, shown)); + anim::interpolate(0, _st.full.height, shown)); if (_data.empty() && shown == 0.) { _hidingData = {}; } } void List::updateScrollMax() { - const auto &full = st::dialogsStoriesFull; + const auto &full = _st.full; const auto singleFull = full.photoLeft * 2 + full.photo; const auto widthFull = full.left + int(_data.items.size()) * singleFull; _scrollLeftMax = std::max(widthFull - width(), 0); @@ -252,8 +258,8 @@ void List::resizeEvent(QResizeEvent *e) { } List::Layout List::computeLayout() const { - const auto &st = st::dialogsStories; - const auto &full = st::dialogsStoriesFull; + const auto &st = _st.small; + const auto &full = _st.full; const auto shownHeight = std::max(_shownHeight(), st.height); const auto ratio = float64(shownHeight - st.height) / (full.height - st.height); @@ -267,11 +273,12 @@ List::Layout List::computeLayout() const { + st::defaultDialogRow.photoSize + st::defaultDialogRow.padding.left(); const auto narrow = (width() <= narrowWidth); - const auto smallSkip = (itemsCount > 1 && rendering.items[0].user.self) + const auto smallSkip = (itemsCount > 1 + && rendering.items[0].element.skipSmall) ? 1 : 0; const auto smallCount = std::min( - kSmallUserpicsShown, + kSmallThumbsShown, itemsCount - smallSkip); const auto smallWidth = st.photo + (smallCount - 1) * st.shift; const auto leftSmall = (narrow @@ -288,17 +295,17 @@ List::Layout List::computeLayout() const { const auto startIndexSmall = std::min(startIndexFull, smallSkip); const auto endIndexSmall = smallSkip + smallCount; const auto cellLeftSmall = leftSmall + (startIndexSmall * st.shift); - const auto userpicLeftFull = cellLeftFull + full.photoLeft; - const auto userpicLeftSmall = cellLeftSmall + st.photoLeft; - const auto userpicLeft = lerp(userpicLeftSmall, userpicLeftFull); + const auto thumbnailLeftFull = cellLeftFull + full.photoLeft; + const auto thumbnailLeftSmall = cellLeftSmall + st.photoLeft; + const auto thumbnailLeft = lerp(thumbnailLeftSmall, thumbnailLeftFull); const auto photoLeft = lerp(st.photoLeft, full.photoLeft); return Layout{ .itemsCount = itemsCount, .shownHeight = shownHeight, .ratio = ratio, - .userpicLeft = userpicLeft, + .thumbnailLeft = thumbnailLeft, .photoLeft = photoLeft, - .left = userpicLeft - photoLeft, + .left = thumbnailLeft - photoLeft, .single = lerp(st.shift, singleFull), .smallSkip = smallSkip, .leftFull = leftFull, @@ -313,8 +320,8 @@ List::Layout List::computeLayout() const { } void List::paintEvent(QPaintEvent *e) { - const auto &st = st::dialogsStories; - const auto &full = st::dialogsStoriesFull; + const auto &st = _st.small; + const auto &full = _st.full; const auto layout = computeLayout(); const auto ratio = layout.ratio; const auto lerp = [&](float64 a, float64 b) { @@ -331,14 +338,14 @@ void List::paintEvent(QPaintEvent *e) { + (photoTop + (photo / 2.)); const auto nameScale = layout.shownHeight / float64(full.height); const auto nameTop = nameScale * full.nameTop; - const auto nameWidth = nameScale * AvailableNameWidth(); + const auto nameWidth = nameScale * AvailableNameWidth(_st); const auto nameHeight = nameScale * full.nameStyle.font->height; const auto nameLeft = layout.photoLeft + (photo - nameWidth) / 2.; - const auto readUserpicOpacity = lerp(kSmallReadOpacity, 1.); - const auto readUserpicAppearingOpacity = lerp(kSmallReadOpacity, 0.); + const auto readUserpicOpacity = lerp(_st.readOpacity, 1.); + const auto readUserpicAppearingOpacity = lerp(_st.readOpacity, 0.); auto p = QPainter(this); - p.fillRect(e->rect(), st::dialogsBg); + p.fillRect(e->rect(), _st.bg); p.translate(0, height() - layout.shownHeight); const auto drawSmall = (ratio < 1.); @@ -375,8 +382,8 @@ void List::paintEvent(QPaintEvent *e) { return Single{ x, indexSmall, small, indexFull, full }; }; const auto hasUnread = [&](const Single &single) { - return (single.itemSmall && single.itemSmall->user.unread) - || (single.itemFull && single.itemFull->user.unread); + return (single.itemSmall && single.itemSmall->element.unread) + || (single.itemFull && single.itemFull->element.unread); }; const auto enumerate = [&](auto &&paintGradient, auto &&paintOther) { auto nextGradientPainted = false; @@ -398,7 +405,9 @@ void List::paintEvent(QPaintEvent *e) { } if (i > first && hasUnread(current) && next) { if (current.itemSmall || !next.itemSmall) { - if (i - 1 == first && first > 0 && !skippedPainted) { + if (i - 1 == first + && first > 0 + && !skippedPainted) { if (const auto skipped = lookup(i - 2)) { skippedPainted = true; paintGradient(skipped); @@ -425,11 +434,15 @@ void List::paintEvent(QPaintEvent *e) { // Unread gradient. const auto x = single.x; - const auto userpic = QRectF(x + layout.photoLeft, photoTop, photo, photo); + const auto userpic = QRectF( + x + layout.photoLeft, + photoTop, + photo, + photo); const auto small = single.itemSmall; const auto itemFull = single.itemFull; - const auto smallUnread = small && small->user.unread; - const auto fullUnread = itemFull && itemFull->user.unread; + const auto smallUnread = small && small->element.unread; + const auto fullUnread = itemFull && itemFull->element.unread; const auto unreadOpacity = (smallUnread && fullUnread) ? 1. : smallUnread @@ -458,11 +471,15 @@ void List::paintEvent(QPaintEvent *e) { Expects(single.itemSmall || single.itemFull); const auto x = single.x; - const auto userpic = QRectF(x + layout.photoLeft, photoTop, photo, photo); + const auto userpic = QRectF( + x + layout.photoLeft, + photoTop, + photo, + photo); const auto small = single.itemSmall; const auto itemFull = single.itemFull; - const auto smallUnread = small && small->user.unread; - const auto fullUnread = itemFull && itemFull->user.unread; + const auto smallUnread = small && small->element.unread; + const auto fullUnread = itemFull && itemFull->element.unread; // White circle with possible read gray line. const auto hasReadLine = (itemFull && !fullUnread); @@ -483,25 +500,27 @@ void List::paintEvent(QPaintEvent *e) { // Userpic. if (itemFull == small) { p.setOpacity(smallUnread ? 1. : readUserpicOpacity); - validateUserpic(itemFull); + validateThumbnail(itemFull); const auto size = full.photo; - p.drawImage(userpic, itemFull->user.userpic->image(size)); + p.drawImage(userpic, itemFull->element.thumbnail->image(size)); } else { if (small) { p.setOpacity(smallUnread ? (itemFull ? 1. : (1. - ratio)) : (itemFull - ? kSmallReadOpacity + ? _st.readOpacity : readUserpicAppearingOpacity)); - validateUserpic(small); + validateThumbnail(small); const auto size = (ratio > 0.) ? full.photo : st.photo; - p.drawImage(userpic, small->user.userpic->image(size)); + p.drawImage(userpic, small->element.thumbnail->image(size)); } if (itemFull) { p.setOpacity(ratio); - validateUserpic(itemFull); + validateThumbnail(itemFull); const auto size = full.photo; - p.drawImage(userpic, itemFull->user.userpic->image(size)); + p.drawImage( + userpic, + itemFull->element.thumbnail->image(size)); } } p.setOpacity(1.); @@ -510,11 +529,11 @@ void List::paintEvent(QPaintEvent *e) { paintSummary(p, rendering, summaryTop, ratio); } -void List::validateUserpic(not_null item) { +void List::validateThumbnail(not_null item) { if (!item->subscribed) { item->subscribed = true; - //const auto id = item.user.id; - item->user.userpic->subscribeToUpdates([=] { + //const auto id = item.element.id; + item->element.thumbnail->subscribeToUpdates([=] { update(); }); } @@ -525,10 +544,10 @@ void List::validateName(not_null item) { if (!item->nameCache.isNull() && item->nameCacheColor == color->c) { return; } - const auto &full = st::dialogsStoriesFull; + const auto &full = _st.full; const auto &font = full.nameStyle.font; - const auto available = AvailableNameWidth(); - const auto text = Ui::Text::String(full.nameStyle, item->user.name); + const auto available = AvailableNameWidth(_st); + const auto text = Ui::Text::String(full.nameStyle, item->element.name); const auto ratio = style::DevicePixelRatio(); item->nameCacheColor = color->c; item->nameCache = QImage( @@ -542,13 +561,13 @@ void List::validateName(not_null item) { } List::Summary &List::ChooseSummary( + const style::DialogsStories &st, Summaries &summaries, int totalItems, int fullWidth) { - const auto &st = st::dialogsStories; const auto used = std::min( - totalItems - (summaries.skipSelf ? 1 : 0), - kSmallUserpicsShown); + totalItems - (summaries.skipOne ? 1 : 0), + kSmallThumbsShown); const auto taken = st.left + st.photoLeft + st.photo @@ -572,13 +591,14 @@ List::Summary &List::ChooseSummary( return summaries.total; } -void List::PrerenderSummary(Summary &summary) { +void List::PrerenderSummary( + const style::DialogsStories &st, + Summary &summary) { if (!summary.cache.isNull() && summary.cacheForWidth == summary.available && summary.cacheColor == st::dialogsNameFg->c) { return; } - const auto &st = st::dialogsStories; const auto use = std::min(summary.text.maxWidth(), summary.available); const auto ratio = style::DevicePixelRatio(); summary.cache = QImage( @@ -597,16 +617,20 @@ void List::paintSummary( float64 summaryTop, float64 hidden) { const auto total = int(data.items.size()); - auto &summary = ChooseSummary(data.summaries, total, width()); - PrerenderSummary(summary); + auto &summary = ChooseSummary( + _st.small, + data.summaries, + total, + width()); + PrerenderSummary(_st.small, summary); const auto lerp = [&](float64 from, float64 to) { return from + (to - from) * hidden; }; - const auto &st = st::dialogsStories; - const auto &full = st::dialogsStoriesFull; + const auto &st = _st.small; + const auto &full = _st.full; const auto used = std::min( - total - (data.summaries.skipSelf ? 1 : 0), - kSmallUserpicsShown); + total - (data.summaries.skipOne ? 1 : 0), + kSmallThumbsShown); const auto fullLeft = st.left + st.photoLeft + st.photo @@ -671,7 +695,7 @@ void List::mouseMoveEvent(QMouseEvent *e) { if (!_dragging && _mouseDownPosition) { if ((_lastMousePosition - *_mouseDownPosition).manhattanLength() >= QApplication::startDragDistance()) { - if (_shownHeight() < st::dialogsStoriesFull.height) { + if (_shownHeight() < _st.full.height) { _expandRequests.fire({}); } _dragging = true; @@ -718,7 +742,7 @@ void List::mouseReleaseEvent(QMouseEvent *e) { if (_selected < 0) { _expandRequests.fire({}); } else if (_selected < _data.items.size()) { - _clicks.fire_copy(_data.items[_selected].user.id); + _clicks.fire_copy(_data.items[_selected].element.id); } } } @@ -737,8 +761,8 @@ void List::contextMenuEvent(QContextMenuEvent *e) { auto &item = _data.items[_selected]; _menu = base::make_unique_q(this); - const auto id = item.user.id; - const auto hidden = item.user.hidden; + const auto id = item.element.id; + const auto hidden = item.element.hidden; _menu->addAction(tr::lng_context_view_profile(tr::now), [=] { _showProfileRequests.fire_copy(id); }); @@ -781,8 +805,8 @@ void List::updateSelected() { if (_pressed >= 0) { return; } - const auto &st = st::dialogsStories; - const auto &full = st::dialogsStoriesFull; + const auto &st = _st.small; + const auto &full = _st.full; const auto p = mapFromGlobal(_lastMousePosition); const auto layout = computeLayout(); const auto firstRightFull = layout.leftFull diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.h b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.h index 72fd67a9ad..f81f90c058 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.h @@ -13,31 +13,38 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class QPainter; +namespace style { +struct DialogsStories; +struct DialogsStoriesList; +} // namespace style + namespace Ui { class PopupMenu; } // namespace Ui namespace Dialogs::Stories { -class Userpic { +class Thumbnail { public: [[nodiscard]] virtual QImage image(int size) = 0; virtual void subscribeToUpdates(Fn callback) = 0; }; -struct User { +struct Element { uint64 id = 0; QString name; - std::shared_ptr userpic; + std::shared_ptr thumbnail; bool unread = false; bool hidden = false; - bool self = false; + bool skipSmall = false; - friend inline bool operator==(const User &a, const User &b) = default; + friend inline bool operator==( + const Element &a, + const Element &b) = default; }; struct Content { - std::vector users; + std::vector elements; bool full = false; friend inline bool operator==( @@ -54,6 +61,7 @@ class List final : public Ui::RpWidget { public: List( not_null parent, + const style::DialogsStoriesList &st, rpl::producer content, Fn shownHeight); @@ -67,7 +75,7 @@ public: private: struct Layout; struct Item { - User user; + Element element; QImage nameCache; QColor nameCacheColor; bool subscribed = false; @@ -88,7 +96,7 @@ private: Summary total; Summary allNames; Summary unreadNames; - bool skipSelf = false; + bool skipOne = false; }; struct Data { std::vector items; @@ -103,13 +111,20 @@ private: [[nodiscard]] static bool StringsEqual( const Summaries &a, const Summaries &b); - static void Populate(Summary &summary); - static void Populate(Summaries &summaries); + static void Populate( + const style::DialogsStories &st, + Summary &summary); + static void Populate( + const style::DialogsStories &st, + Summaries &summaries); [[nodiscard]] static Summary &ChooseSummary( + const style::DialogsStories &st, Summaries &summaries, int totalItems, int fullWidth); - static void PrerenderSummary(Summary &summary); + static void PrerenderSummary( + const style::DialogsStories &st, + Summary &summary); void showContent(Content &&content); void enterEventHook(QEnterEvent *e) override; @@ -121,7 +136,7 @@ private: void mouseReleaseEvent(QMouseEvent *e) override; void contextMenuEvent(QContextMenuEvent *e) override; - void validateUserpic(not_null item); + void validateThumbnail(not_null item); void validateName(not_null item); void updateScrollMax(); void updateSummary(Data &data); @@ -140,6 +155,7 @@ private: [[nodiscard]] Layout computeLayout() const; + const style::DialogsStoriesList &_st; Content _content; Data _data; Data _hidingData; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 3fb9ab3097..e2bee8c067 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -1979,6 +1979,8 @@ bool HistoryItem::forbidsSaving() const { bool HistoryItem::canDelete() const { if (isSponsored()) { return false; + } else if (IsStoryMsgId(id)) { + return false && _history->peer->isSelf(); // #TODO stories } else if (isService() && !isRegular()) { return false; } else if (topicRootId() == id) { diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style index 62febc455a..f2a9482cba 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -368,6 +368,8 @@ infoIconMediaLink: icon {{ "info/info_media_link", infoIconFg }}; infoIconMediaGroup: icon {{ "info/info_common_groups", infoIconFg }}; infoIconMediaVoice: icon {{ "info/info_media_voice", infoIconFg }}; infoIconMediaStories: icon {{ "info/info_media_stories", infoIconFg }}; +infoIconMediaStoriesArchive: icon {{ "info/info_stories_archive", infoIconFg }}; +infoIconMediaStoriesRecent: icon {{ "info/info_stories_recent", infoIconFg }}; infoRoundedIconRequests: icon {{ "info/edit/group_manage_join_requests", settingsIconFg }}; infoRoundedIconRecentActions: icon {{ "info/edit/group_manage_actions", settingsIconFg }}; diff --git a/Telegram/SourceFiles/info/info_top_bar.cpp b/Telegram/SourceFiles/info/info_top_bar.cpp index 37dfe55298..a9e1145158 100644 --- a/Telegram/SourceFiles/info/info_top_bar.cpp +++ b/Telegram/SourceFiles/info/info_top_bar.cpp @@ -534,6 +534,8 @@ Ui::StringWithNumbers TopBar::generateSelectedText() const { case Type::MusicFile: return tr::lng_media_selected_song; case Type::Link: return tr::lng_media_selected_link; case Type::RoundVoiceFile: return tr::lng_media_selected_audio; + // #TODO stories + case Type::PhotoVideo: return tr::lng_media_selected_photo; } Unexpected("Type in TopBar::generateSelectedText()"); }(); diff --git a/Telegram/SourceFiles/info/media/info_media_buttons.h b/Telegram/SourceFiles/info/media/info_media_buttons.h index af1facc588..9d36932807 100644 --- a/Telegram/SourceFiles/info/media/info_media_buttons.h +++ b/Telegram/SourceFiles/info/media/info_media_buttons.h @@ -142,7 +142,7 @@ inline auto AddStoriesButton( parent, std::move(count), [](int count) { - return tr::lng_profile_stories(tr::now, lt_count, count); + return tr::lng_profile_saved_stories(tr::now, lt_count, count); }, tracker)->entity(); result->addClickHandler([=] { diff --git a/Telegram/SourceFiles/info/media/info_media_list_section.cpp b/Telegram/SourceFiles/info/media/info_media_list_section.cpp index 970b63d02e..9fabb28ad7 100644 --- a/Telegram/SourceFiles/info/media/info_media_list_section.cpp +++ b/Telegram/SourceFiles/info/media/info_media_list_section.cpp @@ -347,7 +347,7 @@ void ListSection::resizeToWidth(int newWidth) { _itemWidth = ((newWidth - _itemsLeft) / _itemsInRow) - st::infoMediaSkip; for (auto &item : _items) { - item->resizeGetHeight(_itemWidth); + _itemHeight = item->resizeGetHeight(_itemWidth); } } break; @@ -378,7 +378,7 @@ int ListSection::recountHeight() { case Type::Video: case Type::PhotoVideo: // #TODO stories case Type::RoundFile: { - auto itemHeight = _itemWidth + st::infoMediaSkip; + auto itemHeight = _itemHeight + st::infoMediaSkip; auto index = 0; result += _itemsTop; for (auto &item : _items) { diff --git a/Telegram/SourceFiles/info/media/info_media_list_section.h b/Telegram/SourceFiles/info/media/info_media_list_section.h index 77666a31a0..358712a75e 100644 --- a/Telegram/SourceFiles/info/media/info_media_list_section.h +++ b/Telegram/SourceFiles/info/media/info_media_list_section.h @@ -82,6 +82,7 @@ private: int _itemsLeft = 0; int _itemsTop = 0; int _itemWidth = 0; + int _itemHeight = 0; int _itemsInRow = 1; mutable int _rowsCount = 0; int _top = 0; diff --git a/Telegram/SourceFiles/info/media/info_media_provider.cpp b/Telegram/SourceFiles/info/media/info_media_provider.cpp index f2d8dcde78..3e75975faf 100644 --- a/Telegram/SourceFiles/info/media/info_media_provider.cpp +++ b/Telegram/SourceFiles/info/media/info_media_provider.cpp @@ -421,19 +421,21 @@ std::unique_ptr Provider::createLayout( } return nullptr; }; - const auto spoiler = [&] { - if (const auto media = item->media()) { - return media->hasSpoiler(); - } - return false; - }; const auto &songSt = st::overviewFileLayout; using namespace Overview::Layout; + const auto options = [&] { + const auto media = item->media(); + return MediaOptions{ .spoiler = media && media->hasSpoiler() }; + }; switch (type) { case Type::Photo: if (const auto photo = getPhoto()) { - return std::make_unique(delegate, item, photo, spoiler()); + return std::make_unique( + delegate, + item, + photo, + options()); } return nullptr; case Type::GIF: @@ -443,7 +445,7 @@ std::unique_ptr Provider::createLayout( return nullptr; case Type::Video: if (const auto file = getFile()) { - return std::make_unique