diff --git a/Telegram/SourceFiles/api/api_who_reacted.cpp b/Telegram/SourceFiles/api/api_who_reacted.cpp index 6fc8800b9..f32358c06 100644 --- a/Telegram/SourceFiles/api/api_who_reacted.cpp +++ b/Telegram/SourceFiles/api/api_who_reacted.cpp @@ -28,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/controls/who_reacted_context_action.h" #include "apiwrap.h" #include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" namespace Api { namespace { @@ -357,37 +358,6 @@ 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, @@ -614,6 +584,37 @@ rpl::producer WhoReacted( } // namespace +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 WhoReadExists(not_null item) { if (!item->out()) { return false; diff --git a/Telegram/SourceFiles/api/api_who_reacted.h b/Telegram/SourceFiles/api/api_who_reacted.h index 3fdcd8a56..9a9100535 100644 --- a/Telegram/SourceFiles/api/api_who_reacted.h +++ b/Telegram/SourceFiles/api/api_who_reacted.h @@ -29,6 +29,7 @@ enum class WhoReactedList { One, }; +[[nodiscard]] QString FormatReadDate(TimeId date, const QDateTime &now); [[nodiscard]] bool WhoReadExists(not_null item); [[nodiscard]] bool WhoReactedExists( not_null item, diff --git a/Telegram/SourceFiles/calls/calls_top_bar.cpp b/Telegram/SourceFiles/calls/calls_top_bar.cpp index e43af9373..f06f78cfd 100644 --- a/Telegram/SourceFiles/calls/calls_top_bar.cpp +++ b/Telegram/SourceFiles/calls/calls_top_bar.cpp @@ -34,7 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/abstract_box.h" #include "base/timer.h" #include "styles/style_calls.h" -#include "styles/style_chat.h" // style::GroupCallUserpics +#include "styles/style_chat_helpers.h" // style::GroupCallUserpics #include "styles/style_layers.h" namespace Calls { diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 8ad939619..5b16daf5d 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -201,6 +201,31 @@ ComposeControls { premium: PremiumLimits; } +WhoRead { + userpics: GroupCallUserpics; + photoLeft: pixels; + photoSize: pixels; + photoSkip: pixels; + nameLeft: pixels; + iconPosition: point; + itemPadding: margins; +} + +defaultWhoRead: WhoRead { + userpics: GroupCallUserpics { + size: 22px; + shift: 8px; + stroke: 4px; + align: align(right); + } + photoLeft: 13px; + photoSize: 30px; + photoSkip: 5px; + nameLeft: 57px; + iconPosition: point(15px, 7px); + itemPadding: margins(44px, 9px, 17px, 7px); +} + switchPmButton: RoundButton(defaultBoxButton) { width: 320px; height: 34px; @@ -1091,3 +1116,21 @@ defaultComposeControls: ComposeControls { files: defaultComposeFiles; premium: defaultPremiumLimits; } + +moreChatsBarHeight: 48px; +moreChatsBarTextPosition: point(12px, 4px); +moreChatsBarStatusPosition: point(12px, 24px); +moreChatsBarClose: IconButton(defaultIconButton) { + width: 48px; + height: 48px; + + icon: boxTitleCloseIcon; + iconOver: boxTitleCloseIconOver; + iconPosition: point(12px, -1px); + + rippleAreaPosition: point(0px, 4px); + rippleAreaSize: 40px; + ripple: RippleAnimation(defaultRippleAnimation) { + color: windowBgOver; + } +} diff --git a/Telegram/SourceFiles/data/data_stories.cpp b/Telegram/SourceFiles/data/data_stories.cpp index fa2c61664..5cc08e8b5 100644 --- a/Telegram/SourceFiles/data/data_stories.cpp +++ b/Telegram/SourceFiles/data/data_stories.cpp @@ -181,10 +181,48 @@ const std::vector> &Story::recentViewers() const { return _recentViewers; } +const std::vector &Story::viewsList() const { + return _viewsList; +} + int Story::views() const { return _views; } +void Story::applyViewsSlice( + const std::optional &offset, + const std::vector &slice, + int total) { + _views = total; + if (!offset) { + const auto i = _viewsList.empty() + ? end(slice) + : ranges::find(slice, _viewsList.front()); + const auto merge = (i != end(slice)) + && !ranges::contains(slice, _viewsList.back()); + if (merge) { + _viewsList.insert(begin(_viewsList), begin(slice), i); + } else { + _viewsList = slice; + } + } else if (!slice.empty()) { + const auto i = ranges::find(_viewsList, *offset); + const auto merge = (i != end(_viewsList)) + && !ranges::contains(_viewsList, slice.back()); + if (merge) { + const auto after = i + 1; + if (after == end(_viewsList)) { + _viewsList.insert(after, begin(slice), end(slice)); + } else { + const auto j = ranges::find(slice, _viewsList.back()); + if (j != end(slice)) { + _viewsList.insert(end(_viewsList), j + 1, end(slice)); + } + } + } + } +} + bool Story::applyChanges(StoryMedia media, const MTPDstoryItem &data) { const auto pinned = data.is_pinned(); auto caption = TextWithEntities{ @@ -683,6 +721,56 @@ void Stories::sendMarkAsReadRequests() { } } +void Stories::loadViewsSlice( + StoryId id, + std::optional offset, + Fn)> done) { + _viewsDone = std::move(done); + if (_viewsStoryId == id && _viewsOffset == offset) { + return; + } + _viewsStoryId = id; + _viewsOffset = offset; + + const auto api = &_owner->session().api(); + api->request(_viewsRequestId).cancel(); + _viewsRequestId = api->request(MTPstories_GetStoryViewsList( + MTP_int(id), + MTP_int(offset ? offset->date : 0), + MTP_long(offset ? peerToUser(offset->peer->id).bare : 0), + MTP_int(2) + )).done([=](const MTPstories_StoryViewsList &result) { + _viewsRequestId = 0; + + auto slice = std::vector(); + + const auto &data = result.data(); + _owner->processUsers(data.vusers()); + slice.reserve(data.vviews().v.size()); + for (const auto &view : data.vviews().v) { + slice.push_back({ + .peer = _owner->peer(peerFromUser(view.data().vuser_id())), + .date = view.data().vdate().v, + }); + } + const auto fullId = FullStoryId{ + .peer = _owner->session().userPeerId(), + .story = _viewsStoryId, + }; + if (const auto story = lookup(fullId)) { + (*story)->applyViewsSlice(_viewsOffset, slice, data.vcount().v); + } + if (const auto done = base::take(_viewsDone)) { + done(std::move(slice)); + } + }).fail([=] { + _viewsRequestId = 0; + if (const auto done = base::take(_viewsDone)) { + done({}); + } + }).send(); +} + bool Stories::isQuitPrevent() { if (!_markReadPending.empty()) { sendMarkAsReadRequests(); diff --git a/Telegram/SourceFiles/data/data_stories.h b/Telegram/SourceFiles/data/data_stories.h index d8d7dcee3..b4a53f859 100644 --- a/Telegram/SourceFiles/data/data_stories.h +++ b/Telegram/SourceFiles/data/data_stories.h @@ -28,6 +28,13 @@ struct StoryMedia { friend inline bool operator==(StoryMedia, StoryMedia) = default; }; +struct StoryView { + not_null peer; + TimeId date = 0; + + friend inline bool operator==(StoryView, StoryView) = default; +}; + class Story { public: Story( @@ -60,7 +67,12 @@ public: void setViewsData(std::vector> recent, int total); [[nodiscard]] auto recentViewers() const -> const std::vector> &; + [[nodiscard]] const std::vector &viewsList() const; [[nodiscard]] int views() const; + void applyViewsSlice( + const std::optional &offset, + const std::vector &slice, + int total); bool applyChanges(StoryMedia media, const MTPDstoryItem &data); @@ -70,6 +82,7 @@ private: StoryMedia _media; TextWithEntities _caption; std::vector> _recentViewers; + std::vector _viewsList; int _views = 0; const TimeId _date = 0; bool _pinned = false; @@ -124,6 +137,12 @@ public: [[nodiscard]] bool isQuitPrevent(); void markAsRead(FullStoryId id, bool viewed); + static constexpr auto kViewsPerPage = 50; + void loadViewsSlice( + StoryId id, + std::optional offset, + Fn)> done); + private: [[nodiscard]] StoriesList parse(const MTPUserStories &stories); [[nodiscard]] Story *parseAndApply( @@ -172,6 +191,11 @@ private: base::Timer _markReadTimer; base::flat_set _markReadRequests; + StoryId _viewsStoryId = 0; + std::optional _viewsOffset; + Fn)> _viewsDone; + mtpRequestId _viewsRequestId = 0; + }; } // namespace Data diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 24ff876e9..008102df0 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -67,6 +67,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/info_memento.h" #include "styles/style_dialogs.h" #include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" #include "styles/style_info.h" #include "styles/style_window.h" #include "base/qt/qt_common_adapters.h" diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp index fb504a8b3..a64f98f47 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp @@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "lang/lang_keys.h" #include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" #include "styles/style_window.h" #include "styles/style_info.h" diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_search.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_search.cpp index 45ba94da2..0786b0745 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_search.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_search.cpp @@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_session_controller.h" #include "styles/style_boxes.h" #include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" #include "styles/style_dialogs.h" #include "styles/style_info.h" diff --git a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp index 30e3b26e0..b00624867 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp @@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_peer_menu.h" #include "window/window_session_controller.h" #include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" namespace HistoryView::Controls { namespace { diff --git a/Telegram/SourceFiles/history/view/controls/history_view_ttl_button.cpp b/Telegram/SourceFiles/history/view/controls/history_view_ttl_button.cpp index f0b2eda2f..945253ec6 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_ttl_button.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_ttl_button.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "menu/menu_ttl_validator.h" #include "ui/text/format_values.h" #include "ui/text/text_utilities.h" +#include "styles/style_chat_helpers.h" #include "styles/style_chat.h" namespace HistoryView::Controls { diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp index 9bed92be9..4dbc3cd10 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/paint/blobs.h" #include "ui/painter.h" #include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" #include "styles/style_layers.h" namespace HistoryView::Controls { diff --git a/Telegram/SourceFiles/history/view/history_view_contact_status.cpp b/Telegram/SourceFiles/history/view/history_view_contact_status.cpp index 6f6e14d4a..dffcdde95 100644 --- a/Telegram/SourceFiles/history/view/history_view_contact_status.cpp +++ b/Telegram/SourceFiles/history/view/history_view_contact_status.cpp @@ -40,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unixtime.h" #include "boxes/peers/edit_contact_box.h" #include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" #include "styles/style_layers.h" #include "styles/style_info.h" #include "styles/style_menu_icons.h" diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 14f211bac..99ebf2929 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -68,6 +68,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "spellcheck/spellcheck_types.h" #include "apiwrap.h" #include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" #include "styles/style_menu_icons.h" #include diff --git a/Telegram/SourceFiles/history/view/history_view_corner_buttons.cpp b/Telegram/SourceFiles/history/view/history_view_corner_buttons.cpp index 5bd763c48..0ea99bc50 100644 --- a/Telegram/SourceFiles/history/view/history_view_corner_buttons.cpp +++ b/Telegram/SourceFiles/history/view/history_view_corner_buttons.cpp @@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "ui/toast/toast.h" #include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" namespace HistoryView { diff --git a/Telegram/SourceFiles/history/view/history_view_group_call_bar.cpp b/Telegram/SourceFiles/history/view/history_view_group_call_bar.cpp index bce9881b4..7fc19532a 100644 --- a/Telegram/SourceFiles/history/view/history_view_group_call_bar.cpp +++ b/Telegram/SourceFiles/history/view/history_view_group_call_bar.cpp @@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/calls_instance.h" #include "core/application.h" #include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" namespace HistoryView { diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 18f699b21..e2892d105 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -45,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "styles/style_widgets.h" #include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" #include "styles/style_dialogs.h" namespace HistoryView { diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp index 7ec3b365c..037819498 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp @@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/weak_ptr.h" #include "apiwrap.h" #include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" namespace HistoryView { namespace { diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp index 0fc999bec..2a5cd65be 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp @@ -46,6 +46,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/platform_specific.h" #include "lang/lang_keys.h" #include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" #include "styles/style_window.h" #include "styles/style_info.h" #include "styles/style_boxes.h" diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 0c1b268af..fc94a905c 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -85,6 +85,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/profile/info_profile_values.h" #include "lang/lang_keys.h" #include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" #include "styles/style_window.h" #include "styles/style_info.h" #include "styles/style_boxes.h" diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 77922c6c1..998909301 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -58,6 +58,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "inline_bots/inline_bot_result.h" #include "lang/lang_keys.h" #include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" #include "styles/style_window.h" #include "styles/style_info.h" #include "styles/style_boxes.h" diff --git a/Telegram/SourceFiles/history/view/history_view_translate_bar.cpp b/Telegram/SourceFiles/history/view/history_view_translate_bar.cpp index 456e5cd87..4207c59ca 100644 --- a/Telegram/SourceFiles/history/view/history_view_translate_bar.cpp +++ b/Telegram/SourceFiles/history/view/history_view_translate_bar.cpp @@ -284,7 +284,7 @@ void TranslateBar::setup(not_null history) { button->paintRequest( ) | rpl::start_with_next([=](QRect clip) { - QPainter(button).fillRect(clip, st::historyComposeButton.bgColor); + QPainter(button).fillRect(clip, st::historyComposeButtonBg); }, button->lifetime()); button->setClickedCallback([=] { diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp index 9160e2703..911472560 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions.cpp @@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/painter.h" #include "ui/power_saving.h" #include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" namespace HistoryView::Reactions { namespace { diff --git a/Telegram/SourceFiles/info/profile/info_profile_phone_menu.cpp b/Telegram/SourceFiles/info/profile/info_profile_phone_menu.cpp index 0fea4cdb6..54bf15735 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_phone_menu.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_phone_menu.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/menu/menu_action.h" #include "ui/widgets/popup_menu.h" #include "styles/style_chat.h" // expandedMenuSeparator. +#include "styles/style_chat_helpers.h" namespace Info { namespace Profile { diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index 6d11fd247..3fc642b0a 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -426,7 +426,7 @@ void Controller::show( _shown = storyId; _captionText = story->caption(); _captionFullView = nullptr; - + invalidate_weak_ptrs(&_viewsLoadGuard); if (_replyFocused) { unfocusReply(); } @@ -680,6 +680,96 @@ SiblingView Controller::sibling(SiblingType type) const { return {}; } +ViewsSlice Controller::views(PeerId offset) { + invalidate_weak_ptrs(&_viewsLoadGuard); + if (!offset) { + refreshViewsFromData(); + } else if (!sliceViewsTo(offset)) { + return { .left = _viewsSlice.left }; + } + return _viewsSlice; +} + +rpl::producer<> Controller::moreViewsLoaded() const { + return _moreViewsLoaded.events(); +} + +Fn)> Controller::viewsGotMoreCallback() { + return crl::guard(&_viewsLoadGuard, [=]( + const std::vector &result) { + if (_viewsSlice.list.empty()) { + auto &stories = _list->user->owner().stories(); + if (const auto maybeStory = stories.lookup(_shown)) { + _viewsSlice = { + .list = result, + .left = (*maybeStory)->views() - int(result.size()), + }; + } else { + _viewsSlice = {}; + } + } else { + _viewsSlice.list.insert( + end(_viewsSlice.list), + begin(result), + end(result)); + _viewsSlice.left + = std::max(_viewsSlice.left - int(result.size()), 0); + } + _moreViewsLoaded.fire({}); + }); +} + +void Controller::refreshViewsFromData() { + Expects(_list.has_value()); + + auto &stories = _list->user->owner().stories(); + const auto maybeStory = stories.lookup(_shown); + if (!maybeStory || !_list->user->isSelf()) { + _viewsSlice = {}; + return; + } + const auto story = *maybeStory; + const auto &list = story->viewsList(); + const auto total = story->views(); + _viewsSlice.list = list + | ranges::views::take(Data::Stories::kViewsPerPage) + | ranges::to_vector; + _viewsSlice.left = total - int(_viewsSlice.list.size()); + if (_viewsSlice.list.empty() && _viewsSlice.left > 0) { + const auto done = viewsGotMoreCallback(); + stories.loadViewsSlice(_shown.story, std::nullopt, done); + } +} + +bool Controller::sliceViewsTo(PeerId offset) { + Expects(_list.has_value()); + + auto &stories = _list->user->owner().stories(); + const auto maybeStory = stories.lookup(_shown); + if (!maybeStory || !_list->user->isSelf()) { + _viewsSlice = {}; + return true; + } + const auto story = *maybeStory; + const auto &list = story->viewsList(); + const auto proj = [&](const Data::StoryView &single) { + return single.peer->id; + }; + const auto i = ranges::find(list, _viewsSlice.list.back()); + const auto add = (i != end(list)) ? int(end(list) - i - 1) : 0; + const auto j = ranges::find(_viewsSlice.list, offset, proj); + Assert(j != end(_viewsSlice.list)); + if (!add && (j + 1) == end(_viewsSlice.list)) { + const auto done = viewsGotMoreCallback(); + stories.loadViewsSlice(_shown.story, _viewsSlice.list.back(), done); + return false; + } + _viewsSlice.list.erase(begin(_viewsSlice.list), j + 1); + _viewsSlice.list.insert(end(_viewsSlice.list), i + 1, end(list)); + _viewsSlice.left -= add; + return true; +} + void Controller::unfocusReply() { _wrap->setFocus(); } diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h index eef7cc1a3..161a6971e 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.h +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -78,6 +78,11 @@ struct Layout { friend inline bool operator==(Layout, Layout) = default; }; +struct ViewsSlice { + std::vector list; + int left = 0; +}; + class Controller final { public: explicit Controller(not_null delegate); @@ -114,6 +119,9 @@ public: void repaintSibling(not_null sibling); [[nodiscard]] SiblingView sibling(SiblingType type) const; + [[nodiscard]] ViewsSlice views(PeerId offset); + [[nodiscard]] rpl::producer<> moreViewsLoaded() const; + void unfocusReply(); [[nodiscard]] rpl::lifetime &lifetime(); @@ -142,6 +150,11 @@ private: void subjumpTo(int index); void checkWaitingFor(); + void refreshViewsFromData(); + bool sliceViewsTo(PeerId offset); + [[nodiscard]] auto viewsGotMoreCallback() + -> Fn)>; + const not_null _delegate; rpl::variable> _layout; @@ -170,6 +183,10 @@ private: int _index = 0; bool _started = false; + ViewsSlice _viewsSlice; + rpl::event_stream<> _moreViewsLoaded; + base::has_weak_ptr _viewsLoadGuard; + std::unique_ptr _siblingLeft; std::unique_ptr _siblingRight; diff --git a/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp b/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp index 9148e6b6f..3703d474f 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp @@ -7,11 +7,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/stories/media_stories_recent_views.h" +#include "api/api_who_reacted.h" // FormatReadDate. #include "data/data_peer.h" +#include "data/data_stories.h" #include "main/main_session.h" #include "media/stories/media_stories_controller.h" #include "lang/lang_keys.h" #include "ui/chat/group_call_userpics.h" +#include "ui/widgets/popup_menu.h" +#include "ui/controls/who_reacted_context_action.h" #include "ui/painter.h" #include "ui/rp_widget.h" #include "ui/userpic_view.h" @@ -21,6 +25,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Media::Stories { namespace { +constexpr auto kAddPerPage = 50; +constexpr auto kLoadViewsPages = 2; + [[nodiscard]] rpl::producer> ContentByUsers( const std::vector> &list) { struct Userpic { @@ -37,7 +44,7 @@ namespace { bool scheduled = false; }; - static const auto size = st::storiesRecentViewsUserpics.size; + static const auto size = st::storiesWhoViewed.userpics.size; static const auto GenerateUserpic = [](Userpic &userpic) { auto result = userpic.peer->generateUserpicImage( @@ -132,57 +139,302 @@ void RecentViews::show(RecentViewsData data) { return; } if (!_widget) { - const auto parent = _controller->wrap(); - auto widget = std::make_unique(parent); - const auto raw = widget.get(); - raw->show(); - - _controller->layoutValue( - ) | rpl::start_with_next([=](const Layout &layout) { - raw->setGeometry(layout.views); - }, raw->lifetime()); - - raw->paintRequest( - ) | rpl::start_with_next([=](QRect clip) { - auto p = Painter(raw); - const auto skip = st::storiesRecentViewsSkip; - const auto full = _userpicsWidth + skip + _text.maxWidth(); - const auto use = std::min(full, raw->width()); - const auto ux = (raw->width() - use) / 2; - const auto height = st::storiesRecentViewsUserpics.size; - const auto uy = (raw->height() - height) / 2; - const auto tx = ux + _userpicsWidth + skip; - const auto ty = (raw->height() - st::normalFont->height) / 2; - _userpics->paint(p, ux, uy, height); - p.setPen(st::storiesComposeWhiteText); - _text.drawElided(p, tx, ty, use - _userpicsWidth - skip); - }, raw->lifetime()); - - _widget = std::move(widget); - } - if (totalChanged) { - _text.setText(st::defaultTextStyle, data.total - ? tr::lng_stories_views(tr::now, lt_count, data.total) - : tr::lng_stories_no_views(tr::now)); + setupWidget(); } if (!_userpics) { - _userpics = std::make_unique( - st::storiesRecentViewsUserpics, - rpl::single(true), - [=] { _widget->update(); }); - - _userpics->widthValue() | rpl::start_with_next([=](int width) { - _userpicsWidth = width; - }, _widget->lifetime()); + setupUserpics(); + } + if (totalChanged) { + updateText(); } if (usersChanged) { - _userpicsLifetime = ContentByUsers( - data.list - ) | rpl::start_with_next([=]( - const std::vector &list) { - _userpics->update(list, true); - }); + updateUserpics(); } } +void RecentViews::updateUserpics() { + _userpicsLifetime = ContentByUsers( + _data.list + ) | rpl::start_with_next([=]( + const std::vector &list) { + _userpics->update(list, true); + }); +} + +void RecentViews::setupUserpics() { + _userpics = std::make_unique( + st::storiesWhoViewed.userpics, + rpl::single(true), + [=] { _widget->update(); }); + + _userpics->widthValue() | rpl::start_with_next([=](int width) { + if (_userpicsWidth != width) { + _userpicsWidth = width; + updatePartsGeometry(); + } + }, _widget->lifetime()); +} + +void RecentViews::setupWidget() { + _widget = std::make_unique(_controller->wrap()); + const auto raw = _widget.get(); + raw->show(); + + _controller->layoutValue( + ) | rpl::start_with_next([=](const Layout &layout) { + _outer = layout.views; + updatePartsGeometry(); + }, raw->lifetime()); + + raw->paintRequest( + ) | rpl::start_with_next([=] { + auto p = Painter(raw); + _userpics->paint( + p, + _userpicsPosition.x(), + _userpicsPosition.y(), + st::storiesWhoViewed.userpics.size); + p.setPen(st::storiesComposeWhiteText); + _text.drawElided( + p, + _textPosition.x(), + _textPosition.y(), + raw->width() - _userpicsWidth - st::storiesRecentViewsSkip); + }, raw->lifetime()); + + raw->events( + ) | rpl::filter([=](not_null e) { + return (_data.total > 0) + && (e->type() == QEvent::MouseButtonPress) + && (static_cast(e.get())->button() + == Qt::LeftButton); + }) | rpl::start_with_next([=] { + showMenu(); + }, raw->lifetime()); + + raw->setCursor(style::cur_pointer); +} + +void RecentViews::updatePartsGeometry() { + const auto skip = st::storiesRecentViewsSkip; + const auto full = _userpicsWidth + skip + _text.maxWidth(); + const auto use = std::min(full, _outer.width()); + const auto ux = _outer.x() + (_outer.width() - use) / 2; + const auto uheight = st::storiesWhoViewed.userpics.size; + const auto uy = _outer.y() + (_outer.height() - uheight) / 2; + const auto tx = ux + _userpicsWidth + skip; + const auto theight = st::normalFont->height; + const auto ty = _outer.y() + (_outer.height() - theight) / 2; + const auto my = std::min(uy, ty); + const auto mheight = std::max(uheight, theight); + const auto padding = skip; + _userpicsPosition = QPoint(padding, uy - my); + _textPosition = QPoint(tx - ux + padding, ty - my); + _widget->setGeometry(ux - padding, my, use + 2 * padding, mheight); + _widget->update(); +} + +void RecentViews::updateText() { + _text.setText(st::defaultTextStyle, _data.total + ? tr::lng_stories_views(tr::now, lt_count, _data.total) + : tr::lng_stories_no_views(tr::now)); + updatePartsGeometry(); +} + +void RecentViews::showMenu() { + if (_menu) { + return; + } + + const auto views = _controller->views(PeerId()); + if (views.list.empty() && !views.left) { + return; + } + + using namespace Ui; + _menuShortLifetime.destroy(); + _menu = base::make_unique_q( + _widget.get(), + st::storiesViewsMenu); + auto count = 0; + const auto added = std::min(int(views.list.size()), kAddPerPage); + const auto add = std::min(added + views.left, kAddPerPage); + const auto now = QDateTime::currentDateTime(); + for (const auto &entry : views.list) { + addMenuRow(entry, now); + if (++count >= add) { + break; + } + } + while (count++ < add) { + addMenuRowPlaceholder(); + } + rpl::merge( + _controller->moreViewsLoaded(), + rpl::combine( + _menu->scrollTopValue(), + _menuEntriesCount.value() + ) | rpl::filter([=](int scrollTop, int count) { + const auto fullHeight = count + * (st::defaultWhoRead.photoSkip * 2 + + st::defaultWhoRead.photoSize); + return fullHeight + < (scrollTop + + st::storiesViewsMenu.maxHeight * kLoadViewsPages); + }) | rpl::to_empty + ) | rpl::start_with_next([=] { + rebuildMenuTail(); + }, _menuShortLifetime); + + _menu->setDestroyedCallback(crl::guard(_widget.get(), [=] { + _menuShortLifetime.destroy(); + _menuEntries.clear(); + _menuEntriesCount = 0; + _menuPlaceholderCount = 0; + })); + + const auto size = _menu->size(); + const auto geometry = _widget->mapToGlobal(_widget->rect()); + _menu->setForcedVerticalOrigin(PopupMenu::VerticalOrigin::Bottom); + _menu->popup(QPoint( + geometry.x() + (_widget->width() - size.width()) / 2, + geometry.y() + _widget->height())); + + _menuEntriesCount = _menuEntriesCount.current() + added; +} + +void RecentViews::addMenuRow(Data::StoryView entry, const QDateTime &now) { + Expects(_menu != nullptr); + + const auto peer = entry.peer; + const auto date = Api::FormatReadDate(entry.date, now); + const auto prepare = [&](Ui::PeerUserpicView &view) { + const auto size = st::storiesWhoViewed.photoSize; + auto userpic = peer->generateUserpicImage( + view, + size * style::DevicePixelRatio()); + userpic.setDevicePixelRatio(style::DevicePixelRatio()); + return Ui::WhoReactedEntryData{ + .text = peer->name(), + .date = date, + .userpic = std::move(userpic), + .callback = [] {}, + }; + }; + if (_menuPlaceholderCount > 0) { + const auto i = _menuEntries.end() - (_menuPlaceholderCount--); + i->peer = peer; + i->date = date; + i->action->setData(prepare(i->view)); + } else { + auto view = Ui::PeerUserpicView(); + auto action = base::make_unique_q( + _menu->menu(), + nullptr, + _menu->menu()->st(), + prepare(view)); + const auto raw = action.get(); + _menu->addAction(std::move(action)); + _menuEntries.push_back({ + .action = raw, + .peer = peer, + .date = date, + .view = std::move(view), + }); + } + const auto i = end(_menuEntries) - _menuPlaceholderCount - 1; + i->key = peer->userpicUniqueKey(i->view); + if (peer->hasUserpic() && peer->useEmptyUserpic(i->view)) { + if (_waitingForUserpics.emplace(i - begin(_menuEntries)).second + && _waitingForUserpics.size() == 1) { + subscribeToMenuUserpicsLoading(&peer->session()); + } + } +} + +void RecentViews::addMenuRowPlaceholder() { + auto action = base::make_unique_q( + _menu->menu(), + nullptr, + _menu->menu()->st(), + Ui::WhoReactedEntryData{ .preloader = true }); + const auto raw = action.get(); + _menu->addAction(std::move(action)); + _menuEntries.push_back({ .action = raw }); + ++_menuPlaceholderCount; +} + +void RecentViews::rebuildMenuTail() { + const auto offset = (_menuPlaceholderCount < _menuEntries.size()) + ? (end(_menuEntries) - _menuPlaceholderCount - 1)->peer->id + : PeerId(); + const auto views = _controller->views(offset); + if (views.list.empty()) { + return; + } + const auto now = QDateTime::currentDateTime(); + const auto added = std::min( + _menuPlaceholderCount + kAddPerPage, + int(views.list.size())); + auto add = added; + for (const auto &entry : views.list) { + addMenuRow(entry, now); + if (!--add) { + break; + } + } + _menuEntriesCount = _menuEntriesCount.current() + added; +} + +void RecentViews::subscribeToMenuUserpicsLoading( + not_null session) { + _shortAnimationPlaying = style::ShortAnimationPlaying(); + _waitingForUserpicsLifetime = rpl::merge( + _shortAnimationPlaying.changes() | rpl::filter([=](bool playing) { + return !playing && _waitingUserpicsCheck; + }) | rpl::to_empty, + session->downloaderTaskFinished( + ) | rpl::filter([=] { + if (_shortAnimationPlaying.current()) { + _waitingUserpicsCheck = true; + return false; + } + return true; + }) + ) | rpl::start_with_next([=] { + _waitingUserpicsCheck = false; + for (auto i = begin(_waitingForUserpics) + ; i != end(_waitingForUserpics) + ;) { + auto &entry = _menuEntries[*i]; + auto &view = entry.view; + const auto peer = entry.peer; + const auto key = peer->userpicUniqueKey(view); + const auto update = (entry.key != key); + if (update) { + const auto size = st::storiesWhoViewed.photoSize; + auto userpic = peer->generateUserpicImage( + view, + size * style::DevicePixelRatio()); + userpic.setDevicePixelRatio(style::DevicePixelRatio()); + entry.action->setData({ + .text = peer->name(), + .date = entry.date, + .userpic = std::move(userpic), + .callback = [] {}, + }); + entry.key = key; + if (!peer->hasUserpic() || !peer->useEmptyUserpic(view)) { + i = _waitingForUserpics.erase(i); + continue; + } + } + ++i; + } + if (_waitingForUserpics.empty()) { + _waitingForUserpicsLifetime.destroy(); + } + }); +} + } // namespace Media::Stories diff --git a/Telegram/SourceFiles/media/stories/media_stories_recent_views.h b/Telegram/SourceFiles/media/stories/media_stories_recent_views.h index 64a6d4e82..79b4ee3fc 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_recent_views.h +++ b/Telegram/SourceFiles/media/stories/media_stories_recent_views.h @@ -7,13 +7,25 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "base/unique_qptr.h" #include "ui/text/text.h" +#include "ui/userpic_view.h" + +namespace Data { +struct StoryView; +} // namespace Data namespace Ui { class RpWidget; class GroupCallUserpics; +class PopupMenu; +class WhoReactedEntryAction; } // namespace Ui +namespace Main { +class Session; +} // namespace Main + namespace Media::Stories { class Controller; @@ -39,6 +51,26 @@ public: void show(RecentViewsData data); private: + struct MenuEntry { + not_null action; + PeerData *peer = nullptr; + QString date; + Ui::PeerUserpicView view; + InMemoryKey key; + }; + + void setupWidget(); + void setupUserpics(); + void updateUserpics(); + void updateText(); + void updatePartsGeometry(); + void showMenu(); + + void addMenuRow(Data::StoryView entry, const QDateTime &now); + void addMenuRowPlaceholder(); + void rebuildMenuTail(); + void subscribeToMenuUserpicsLoading(not_null session); + const not_null _controller; std::unique_ptr _widget; @@ -46,6 +78,20 @@ private: Ui::Text::String _text; RecentViewsData _data; rpl::lifetime _userpicsLifetime; + + base::unique_qptr _menu; + rpl::lifetime _menuShortLifetime; + std::vector _menuEntries; + rpl::variable _menuEntriesCount = 0; + int _menuPlaceholderCount = 0; + base::flat_set _waitingForUserpics; + rpl::variable _shortAnimationPlaying; + bool _waitingUserpicsCheck = false; + rpl::lifetime _waitingForUserpicsLifetime; + + QRect _outer; + QPoint _userpicsPosition; + QPoint _textPosition; int _userpicsWidth = 0; }; diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style index 2a3c3f7b2..b1674e41b 100644 --- a/Telegram/SourceFiles/media/view/media_view.style +++ b/Telegram/SourceFiles/media/view/media_view.style @@ -729,11 +729,21 @@ storiesComposeControls: ComposeControls(defaultComposeControls) { } premium: storiesComposePremium; } -storiesRecentViewsUserpics: GroupCallUserpics { - size: 24px; - shift: 9px; - stroke: 4px; - align: align(left); +storiesViewsMenu: PopupMenu(storiesPopupMenuWithIcons) { + scrollPadding: margins(0px, 6px, 0px, 4px); + maxHeight: 320px; + menu: Menu(storiesMenuWithIcons) { + widthMin: 215px; + widthMax: 215px; + } + radius: 7px; } storiesRecentViewsSkip: 8px; - +storiesWhoViewed: WhoRead(defaultWhoRead) { + userpics: GroupCallUserpics { + size: 24px; + shift: 9px; + stroke: 4px; + align: align(left); + } +} diff --git a/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp b/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp index 156bef531..83c543fe0 100644 --- a/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp +++ b/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp @@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mainwindow.h" #include "windows_quiethours_h.h" #include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" #include diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp index 8f6b00900..5fbb5b4c3 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp @@ -54,6 +54,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_controller.h" #include "window/window_session_controller.h" #include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" #include "styles/style_boxes.h" #include "styles/style_settings.h" #include "styles/style_info.h" diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index d4b66ab07..6b718137e 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -727,29 +727,6 @@ popupMenuExpandedSeparator: PopupMenu(popupMenuWithIcons) { } } -WhoRead { - userpics: GroupCallUserpics; - photoLeft: pixels; - photoSize: pixels; - photoSkip: pixels; - nameLeft: pixels; - iconPosition: point; - itemPadding: margins; -} -defaultWhoRead: WhoRead { - userpics: GroupCallUserpics { - size: 22px; - shift: 8px; - stroke: 4px; - align: align(right); - } - photoLeft: 13px; - photoSize: 30px; - photoSkip: 5px; - nameLeft: 57px; - iconPosition: point(15px, 7px); - itemPadding: margins(44px, 9px, 17px, 7px); -} whoReadMenu: PopupMenu(popupMenuExpandedSeparator) { scrollPadding: margins(0px, 6px, 0px, 4px); maxHeight: 400px; @@ -934,24 +911,6 @@ historySendDisabledIcon: icon {{ "emoji/premium_lock", placeholderFgActive }}; historySendDisabledIconSkip: 20px; historySendDisabledPosition: point(0px, 0px); -moreChatsBarHeight: 48px; -moreChatsBarTextPosition: point(12px, 4px); -moreChatsBarStatusPosition: point(12px, 24px); -moreChatsBarClose: IconButton(defaultIconButton) { - width: 48px; - height: 48px; - - icon: boxTitleCloseIcon; - iconOver: boxTitleCloseIconOver; - iconPosition: point(12px, -1px); - - rippleAreaPosition: point(0px, 4px); - rippleAreaSize: 40px; - ripple: RippleAnimation(defaultRippleAnimation) { - color: windowBgOver; - } -} - backgroundSwitchToDark: IconButton(defaultIconButton) { width: 48px; height: 48px; diff --git a/Telegram/SourceFiles/ui/chat/group_call_bar.cpp b/Telegram/SourceFiles/ui/chat/group_call_bar.cpp index 7087bbbd4..f4da62406 100644 --- a/Telegram/SourceFiles/ui/chat/group_call_bar.cpp +++ b/Telegram/SourceFiles/ui/chat/group_call_bar.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "base/unixtime.h" #include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" #include "styles/style_calls.h" #include "styles/style_info.h" // st::topBarArrowPadding, like TopBarWidget. #include "styles/style_window.h" // st::columnMinimalWidthLeft diff --git a/Telegram/SourceFiles/ui/chat/group_call_userpics.cpp b/Telegram/SourceFiles/ui/chat/group_call_userpics.cpp index a88f2074d..b5aadd41e 100644 --- a/Telegram/SourceFiles/ui/chat/group_call_userpics.cpp +++ b/Telegram/SourceFiles/ui/chat/group_call_userpics.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/power_saving.h" #include "base/random.h" #include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" namespace Ui { namespace { diff --git a/Telegram/SourceFiles/ui/chat/more_chats_bar.cpp b/Telegram/SourceFiles/ui/chat/more_chats_bar.cpp index e645801a2..d78a60696 100644 --- a/Telegram/SourceFiles/ui/chat/more_chats_bar.cpp +++ b/Telegram/SourceFiles/ui/chat/more_chats_bar.cpp @@ -12,7 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_options.h" #include "ui/painter.h" #include "lang/lang_keys.h" -#include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" #include "styles/style_window.h" // st::columnMinimalWidthLeft namespace Ui { diff --git a/Telegram/SourceFiles/ui/controls/invite_link_buttons.cpp b/Telegram/SourceFiles/ui/controls/invite_link_buttons.cpp index 5d9a4f80d..ac13cab17 100644 --- a/Telegram/SourceFiles/ui/controls/invite_link_buttons.cpp +++ b/Telegram/SourceFiles/ui/controls/invite_link_buttons.cpp @@ -12,7 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/padding_wrap.h" #include "ui/wrap/slide_wrap.h" #include "lang/lang_keys.h" -#include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" #include "styles/style_info.h" namespace Ui { diff --git a/Telegram/SourceFiles/ui/controls/silent_toggle.cpp b/Telegram/SourceFiles/ui/controls/silent_toggle.cpp index 393ae3c2d..e583af790 100644 --- a/Telegram/SourceFiles/ui/controls/silent_toggle.cpp +++ b/Telegram/SourceFiles/ui/controls/silent_toggle.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "lang/lang_keys.h" #include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" namespace Ui { namespace { diff --git a/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp b/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp index f4914b389..ae9acc05d 100644 --- a/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp +++ b/Telegram/SourceFiles/ui/controls/who_reacted_context_action.cpp @@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/painter.h" #include "lang/lang_keys.h" #include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" #include "styles/style_menu_icons.h" namespace Lang { @@ -69,16 +70,9 @@ StringWithReacted ReplaceTag::Call( namespace Ui { namespace { -using Text::CustomEmojiFactory; +constexpr auto kPreloaderAlpha = 0.2; -struct EntryData { - QString text; - QString date; - bool dateReacted = false; - QString customEntityData; - QImage userpic; - Fn callback; -}; +using Text::CustomEmojiFactory; class Action final : public Menu::ItemBase { public: @@ -465,44 +459,11 @@ void Action::handleKeyPress(not_null e) { } // namespace -class WhoReactedListMenu::EntryAction final : public Menu::ItemBase { -public: - EntryAction( - not_null parent, - CustomEmojiFactory factory, - const style::Menu &st, - EntryData &&data); - - void setData(EntryData &&data); - - not_null action() const override; - bool isEnabled() const override; - -private: - int contentHeight() const override; - - void paint(Painter &&p); - - const not_null _dummyAction; - const CustomEmojiFactory _customEmojiFactory; - const style::Menu &_st; - const int _height = 0; - - Text::String _text; - Text::String _date; - std::unique_ptr _custom; - QImage _userpic; - int _textWidth = 0; - int _customSize = 0; - bool _dateReacted = false; - -}; - -WhoReactedListMenu::EntryAction::EntryAction( +WhoReactedEntryAction::WhoReactedEntryAction( not_null parent, CustomEmojiFactory customEmojiFactory, const style::Menu &st, - EntryData &&data) + Data &&data) : ItemBase(parent, st) , _dummyAction(CreateChild(parent.get())) , _customEmojiFactory(std::move(customEmojiFactory)) @@ -521,19 +482,19 @@ WhoReactedListMenu::EntryAction::EntryAction( enableMouseSelecting(); } -not_null WhoReactedListMenu::EntryAction::action() const { +not_null WhoReactedEntryAction::action() const { return _dummyAction.get(); } -bool WhoReactedListMenu::EntryAction::isEnabled() const { +bool WhoReactedEntryAction::isEnabled() const { return true; } -int WhoReactedListMenu::EntryAction::contentHeight() const { +int WhoReactedEntryAction::contentHeight() const { return _height; } -void WhoReactedListMenu::EntryAction::setData(EntryData &&data) { +void WhoReactedEntryAction::setData(Data &&data) { setClickedCallback(std::move(data.callback)); _userpic = std::move(data.userpic); _text.setMarkedText(_st.itemStyle, { data.text }, MenuTextOptions); @@ -546,7 +507,10 @@ void WhoReactedListMenu::EntryAction::setData(EntryData &&data) { MenuTextOptions); } _dateReacted = data.dateReacted; - _custom = _customEmojiFactory(data.customEntityData, [=] { update(); }); + _preloader = data.preloader; + _custom = _customEmojiFactory + ? _customEmojiFactory(data.customEntityData, [=] { update(); }) + : nullptr; const auto ratio = style::DevicePixelRatio(); const auto size = Emoji::GetSizeNormal() / ratio; _customSize = Text::AdjustCustomEmojiSize(size); @@ -565,7 +529,7 @@ void WhoReactedListMenu::EntryAction::setData(EntryData &&data) { update(); } -void WhoReactedListMenu::EntryAction::paint(Painter &&p) { +void WhoReactedEntryAction::paint(Painter &&p) { const auto enabled = isEnabled(); const auto selected = isSelected(); if (selected && _st.itemBgOver->c.alpha() < 255) { @@ -578,7 +542,18 @@ void WhoReactedListMenu::EntryAction::paint(Painter &&p) { const auto photoSize = st::defaultWhoRead.photoSize; const auto photoLeft = st::defaultWhoRead.photoLeft; const auto photoTop = (height() - photoSize) / 2; - if (!_userpic.isNull()) { + const auto preloaderBrush = _preloader + ? [&] { + auto color = _st.itemFg->c; + color.setAlphaF(color.alphaF() * kPreloaderAlpha); + return QBrush(color); + }() : QBrush(); + if (_preloader) { + auto hq = PainterHighQualityEnabler(p); + p.setPen(Qt::NoPen); + p.setBrush(preloaderBrush); + p.drawEllipse(photoLeft, photoTop, photoSize, photoSize); + } else if (!_userpic.isNull()) { p.drawImage(photoLeft, photoTop, _userpic); } else if (!_custom) { st::menuIconReactions.paintInCenter( @@ -590,17 +565,31 @@ void WhoReactedListMenu::EntryAction::paint(Painter &&p) { const auto textTop = withDate ? st::whoReadNameWithDateTop : (height() - _st.itemStyle.font->height) / 2; - p.setPen(selected - ? _st.itemFgOver - : enabled - ? _st.itemFg - : _st.itemFgDisabled); - _text.drawLeftElided( - p, - st::defaultWhoRead.nameLeft, - textTop, - _textWidth, - width()); + if (_preloader) { + auto hq = PainterHighQualityEnabler(p); + p.setPen(Qt::NoPen); + p.setBrush(preloaderBrush); + const auto height = _st.itemStyle.font->height / 2; + p.drawRoundedRect( + st::defaultWhoRead.nameLeft, + textTop + (_st.itemStyle.font->height - height) / 2, + _textWidth, + height, + height / 2., + height / 2.); + } else { + p.setPen(selected + ? _st.itemFgOver + : enabled + ? _st.itemFg + : _st.itemFgDisabled); + _text.drawLeftElided( + p, + st::defaultWhoRead.nameLeft, + textTop, + _textWidth, + width()); + } if (withDate) { const auto iconPosition = QPoint( st::defaultWhoRead.nameLeft, @@ -690,11 +679,11 @@ void WhoReactedListMenu::populate( addedToBottom = 0; } auto index = 0; - const auto append = [&](EntryData &&data) { + const auto append = [&](WhoReactedEntryData &&data) { if (index < _actions.size()) { _actions[index]->setData(std::move(data)); } else { - auto item = base::make_unique_q( + auto item = base::make_unique_q( menu->menu(), _customEmojiFactory, menu->menu()->st(), diff --git a/Telegram/SourceFiles/ui/controls/who_reacted_context_action.h b/Telegram/SourceFiles/ui/controls/who_reacted_context_action.h index c46f3e804..fa4d3a08f 100644 --- a/Telegram/SourceFiles/ui/controls/who_reacted_context_action.h +++ b/Telegram/SourceFiles/ui/controls/who_reacted_context_action.h @@ -9,11 +9,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unique_qptr.h" #include "ui/text/text_block.h" +#include "ui/widgets/menu/menu_item_base.h" namespace Ui { -namespace Menu { -class ItemBase; -} // namespace Menu class PopupMenu; @@ -56,6 +54,52 @@ struct WhoReadContent { Fn participantChosen, Fn showAllChosen); +struct WhoReactedEntryData { + QString text; + QString date; + bool dateReacted = false; + bool preloader = false; + QString customEntityData; + QImage userpic; + Fn callback; +}; + +class WhoReactedEntryAction final : public Menu::ItemBase { +public: + using Data = WhoReactedEntryData; + + WhoReactedEntryAction( + not_null parent, + Text::CustomEmojiFactory factory, + const style::Menu &st, + Data &&data); + + void setData(Data &&data); + + not_null action() const override; + bool isEnabled() const override; + +private: + int contentHeight() const override; + + void paint(Painter &&p); + + const not_null _dummyAction; + const Text::CustomEmojiFactory _customEmojiFactory; + const style::Menu &_st; + const int _height = 0; + + Text::String _text; + Text::String _date; + std::unique_ptr _custom; + QImage _userpic; + int _textWidth = 0; + int _customSize = 0; + bool _dateReacted = false; + bool _preloader = false; + +}; + class WhoReactedListMenu final { public: WhoReactedListMenu( @@ -72,13 +116,11 @@ public: Fn appendBottomActions = nullptr); private: - class EntryAction; - const Text::CustomEmojiFactory _customEmojiFactory; const Fn _participantChosen; const Fn _showAllChosen; - std::vector> _actions; + std::vector> _actions; };