mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-17 22:57:11 +02:00
Load and show list of users who viewed a story.
This commit is contained in:
parent
16069db3e6
commit
d28bd36d22
39 changed files with 784 additions and 200 deletions
|
@ -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*> state,
|
||||
not_null<HistoryItem*> item,
|
||||
|
@ -614,6 +584,37 @@ rpl::producer<Ui::WhoReadContent> 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<HistoryItem*> item) {
|
||||
if (!item->out()) {
|
||||
return false;
|
||||
|
|
|
@ -29,6 +29,7 @@ enum class WhoReactedList {
|
|||
One,
|
||||
};
|
||||
|
||||
[[nodiscard]] QString FormatReadDate(TimeId date, const QDateTime &now);
|
||||
[[nodiscard]] bool WhoReadExists(not_null<HistoryItem*> item);
|
||||
[[nodiscard]] bool WhoReactedExists(
|
||||
not_null<HistoryItem*> item,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -181,10 +181,48 @@ const std::vector<not_null<PeerData*>> &Story::recentViewers() const {
|
|||
return _recentViewers;
|
||||
}
|
||||
|
||||
const std::vector<StoryView> &Story::viewsList() const {
|
||||
return _viewsList;
|
||||
}
|
||||
|
||||
int Story::views() const {
|
||||
return _views;
|
||||
}
|
||||
|
||||
void Story::applyViewsSlice(
|
||||
const std::optional<StoryView> &offset,
|
||||
const std::vector<StoryView> &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<StoryView> offset,
|
||||
Fn<void(std::vector<StoryView>)> 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<StoryView>();
|
||||
|
||||
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();
|
||||
|
|
|
@ -28,6 +28,13 @@ struct StoryMedia {
|
|||
friend inline bool operator==(StoryMedia, StoryMedia) = default;
|
||||
};
|
||||
|
||||
struct StoryView {
|
||||
not_null<PeerData*> peer;
|
||||
TimeId date = 0;
|
||||
|
||||
friend inline bool operator==(StoryView, StoryView) = default;
|
||||
};
|
||||
|
||||
class Story {
|
||||
public:
|
||||
Story(
|
||||
|
@ -60,7 +67,12 @@ public:
|
|||
void setViewsData(std::vector<not_null<PeerData*>> recent, int total);
|
||||
[[nodiscard]] auto recentViewers() const
|
||||
-> const std::vector<not_null<PeerData*>> &;
|
||||
[[nodiscard]] const std::vector<StoryView> &viewsList() const;
|
||||
[[nodiscard]] int views() const;
|
||||
void applyViewsSlice(
|
||||
const std::optional<StoryView> &offset,
|
||||
const std::vector<StoryView> &slice,
|
||||
int total);
|
||||
|
||||
bool applyChanges(StoryMedia media, const MTPDstoryItem &data);
|
||||
|
||||
|
@ -70,6 +82,7 @@ private:
|
|||
StoryMedia _media;
|
||||
TextWithEntities _caption;
|
||||
std::vector<not_null<PeerData*>> _recentViewers;
|
||||
std::vector<StoryView> _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<StoryView> offset,
|
||||
Fn<void(std::vector<StoryView>)> done);
|
||||
|
||||
private:
|
||||
[[nodiscard]] StoriesList parse(const MTPUserStories &stories);
|
||||
[[nodiscard]] Story *parseAndApply(
|
||||
|
@ -172,6 +191,11 @@ private:
|
|||
base::Timer _markReadTimer;
|
||||
base::flat_set<PeerId> _markReadRequests;
|
||||
|
||||
StoryId _viewsStoryId = 0;
|
||||
std::optional<StoryView> _viewsOffset;
|
||||
Fn<void(std::vector<StoryView>)> _viewsDone;
|
||||
mtpRequestId _viewsRequestId = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Data
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 <QtGui/QGuiApplication>
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -284,7 +284,7 @@ void TranslateBar::setup(not_null<History*> 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([=] {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<void(std::vector<Data::StoryView>)> Controller::viewsGotMoreCallback() {
|
||||
return crl::guard(&_viewsLoadGuard, [=](
|
||||
const std::vector<Data::StoryView> &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();
|
||||
}
|
||||
|
|
|
@ -78,6 +78,11 @@ struct Layout {
|
|||
friend inline bool operator==(Layout, Layout) = default;
|
||||
};
|
||||
|
||||
struct ViewsSlice {
|
||||
std::vector<Data::StoryView> list;
|
||||
int left = 0;
|
||||
};
|
||||
|
||||
class Controller final {
|
||||
public:
|
||||
explicit Controller(not_null<Delegate*> delegate);
|
||||
|
@ -114,6 +119,9 @@ public:
|
|||
void repaintSibling(not_null<Sibling*> 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<void(std::vector<Data::StoryView>)>;
|
||||
|
||||
const not_null<Delegate*> _delegate;
|
||||
|
||||
rpl::variable<std::optional<Layout>> _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<Sibling> _siblingLeft;
|
||||
std::unique_ptr<Sibling> _siblingRight;
|
||||
|
||||
|
|
|
@ -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<std::vector<Ui::GroupCallUser>> ContentByUsers(
|
||||
const std::vector<not_null<PeerData*>> &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<Ui::RpWidget>(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<Ui::GroupCallUserpics>(
|
||||
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<Ui::GroupCallUser> &list) {
|
||||
_userpics->update(list, true);
|
||||
});
|
||||
updateUserpics();
|
||||
}
|
||||
}
|
||||
|
||||
void RecentViews::updateUserpics() {
|
||||
_userpicsLifetime = ContentByUsers(
|
||||
_data.list
|
||||
) | rpl::start_with_next([=](
|
||||
const std::vector<Ui::GroupCallUser> &list) {
|
||||
_userpics->update(list, true);
|
||||
});
|
||||
}
|
||||
|
||||
void RecentViews::setupUserpics() {
|
||||
_userpics = std::make_unique<Ui::GroupCallUserpics>(
|
||||
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<Ui::RpWidget>(_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<QEvent*> e) {
|
||||
return (_data.total > 0)
|
||||
&& (e->type() == QEvent::MouseButtonPress)
|
||||
&& (static_cast<QMouseEvent*>(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<PopupMenu>(
|
||||
_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<Ui::WhoReactedEntryAction>(
|
||||
_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<Ui::WhoReactedEntryAction>(
|
||||
_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<Main::Session*> 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
|
||||
|
|
|
@ -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<Ui::WhoReactedEntryAction*> 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<Main::Session*> session);
|
||||
|
||||
const not_null<Controller*> _controller;
|
||||
|
||||
std::unique_ptr<Ui::RpWidget> _widget;
|
||||
|
@ -46,6 +78,20 @@ private:
|
|||
Ui::Text::String _text;
|
||||
RecentViewsData _data;
|
||||
rpl::lifetime _userpicsLifetime;
|
||||
|
||||
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||
rpl::lifetime _menuShortLifetime;
|
||||
std::vector<MenuEntry> _menuEntries;
|
||||
rpl::variable<int> _menuEntriesCount = 0;
|
||||
int _menuPlaceholderCount = 0;
|
||||
base::flat_set<int> _waitingForUserpics;
|
||||
rpl::variable<bool> _shortAnimationPlaying;
|
||||
bool _waitingUserpicsCheck = false;
|
||||
rpl::lifetime _waitingForUserpicsLifetime;
|
||||
|
||||
QRect _outer;
|
||||
QPoint _userpicsPosition;
|
||||
QPoint _textPosition;
|
||||
int _userpicsWidth = 0;
|
||||
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <QtCore/QOperatingSystemVersion>
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<StringWithReacted>::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<void()> callback;
|
||||
};
|
||||
using Text::CustomEmojiFactory;
|
||||
|
||||
class Action final : public Menu::ItemBase {
|
||||
public:
|
||||
|
@ -465,44 +459,11 @@ void Action::handleKeyPress(not_null<QKeyEvent*> e) {
|
|||
|
||||
} // namespace
|
||||
|
||||
class WhoReactedListMenu::EntryAction final : public Menu::ItemBase {
|
||||
public:
|
||||
EntryAction(
|
||||
not_null<RpWidget*> parent,
|
||||
CustomEmojiFactory factory,
|
||||
const style::Menu &st,
|
||||
EntryData &&data);
|
||||
|
||||
void setData(EntryData &&data);
|
||||
|
||||
not_null<QAction*> action() const override;
|
||||
bool isEnabled() const override;
|
||||
|
||||
private:
|
||||
int contentHeight() const override;
|
||||
|
||||
void paint(Painter &&p);
|
||||
|
||||
const not_null<QAction*> _dummyAction;
|
||||
const CustomEmojiFactory _customEmojiFactory;
|
||||
const style::Menu &_st;
|
||||
const int _height = 0;
|
||||
|
||||
Text::String _text;
|
||||
Text::String _date;
|
||||
std::unique_ptr<Ui::Text::CustomEmoji> _custom;
|
||||
QImage _userpic;
|
||||
int _textWidth = 0;
|
||||
int _customSize = 0;
|
||||
bool _dateReacted = false;
|
||||
|
||||
};
|
||||
|
||||
WhoReactedListMenu::EntryAction::EntryAction(
|
||||
WhoReactedEntryAction::WhoReactedEntryAction(
|
||||
not_null<RpWidget*> parent,
|
||||
CustomEmojiFactory customEmojiFactory,
|
||||
const style::Menu &st,
|
||||
EntryData &&data)
|
||||
Data &&data)
|
||||
: ItemBase(parent, st)
|
||||
, _dummyAction(CreateChild<QAction>(parent.get()))
|
||||
, _customEmojiFactory(std::move(customEmojiFactory))
|
||||
|
@ -521,19 +482,19 @@ WhoReactedListMenu::EntryAction::EntryAction(
|
|||
enableMouseSelecting();
|
||||
}
|
||||
|
||||
not_null<QAction*> WhoReactedListMenu::EntryAction::action() const {
|
||||
not_null<QAction*> 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<EntryAction>(
|
||||
auto item = base::make_unique_q<WhoReactedEntryAction>(
|
||||
menu->menu(),
|
||||
_customEmojiFactory,
|
||||
menu->menu()->st(),
|
||||
|
|
|
@ -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<void(uint64)> participantChosen,
|
||||
Fn<void()> showAllChosen);
|
||||
|
||||
struct WhoReactedEntryData {
|
||||
QString text;
|
||||
QString date;
|
||||
bool dateReacted = false;
|
||||
bool preloader = false;
|
||||
QString customEntityData;
|
||||
QImage userpic;
|
||||
Fn<void()> callback;
|
||||
};
|
||||
|
||||
class WhoReactedEntryAction final : public Menu::ItemBase {
|
||||
public:
|
||||
using Data = WhoReactedEntryData;
|
||||
|
||||
WhoReactedEntryAction(
|
||||
not_null<RpWidget*> parent,
|
||||
Text::CustomEmojiFactory factory,
|
||||
const style::Menu &st,
|
||||
Data &&data);
|
||||
|
||||
void setData(Data &&data);
|
||||
|
||||
not_null<QAction*> action() const override;
|
||||
bool isEnabled() const override;
|
||||
|
||||
private:
|
||||
int contentHeight() const override;
|
||||
|
||||
void paint(Painter &&p);
|
||||
|
||||
const not_null<QAction*> _dummyAction;
|
||||
const Text::CustomEmojiFactory _customEmojiFactory;
|
||||
const style::Menu &_st;
|
||||
const int _height = 0;
|
||||
|
||||
Text::String _text;
|
||||
Text::String _date;
|
||||
std::unique_ptr<Ui::Text::CustomEmoji> _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<void()> appendBottomActions = nullptr);
|
||||
|
||||
private:
|
||||
class EntryAction;
|
||||
|
||||
const Text::CustomEmojiFactory _customEmojiFactory;
|
||||
const Fn<void(uint64)> _participantChosen;
|
||||
const Fn<void()> _showAllChosen;
|
||||
|
||||
std::vector<not_null<EntryAction*>> _actions;
|
||||
std::vector<not_null<WhoReactedEntryAction*>> _actions;
|
||||
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue