mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +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 "ui/controls/who_reacted_context_action.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
#include "styles/style_chat_helpers.h"
|
||||||
|
|
||||||
namespace Api {
|
namespace Api {
|
||||||
namespace {
|
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(
|
bool UpdateUserpics(
|
||||||
not_null<State*> state,
|
not_null<State*> state,
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
|
@ -614,6 +584,37 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||||
|
|
||||||
} // namespace
|
} // 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) {
|
bool WhoReadExists(not_null<HistoryItem*> item) {
|
||||||
if (!item->out()) {
|
if (!item->out()) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -29,6 +29,7 @@ enum class WhoReactedList {
|
||||||
One,
|
One,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] QString FormatReadDate(TimeId date, const QDateTime &now);
|
||||||
[[nodiscard]] bool WhoReadExists(not_null<HistoryItem*> item);
|
[[nodiscard]] bool WhoReadExists(not_null<HistoryItem*> item);
|
||||||
[[nodiscard]] bool WhoReactedExists(
|
[[nodiscard]] bool WhoReactedExists(
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
|
|
|
@ -34,7 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "boxes/abstract_box.h"
|
#include "boxes/abstract_box.h"
|
||||||
#include "base/timer.h"
|
#include "base/timer.h"
|
||||||
#include "styles/style_calls.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"
|
#include "styles/style_layers.h"
|
||||||
|
|
||||||
namespace Calls {
|
namespace Calls {
|
||||||
|
|
|
@ -201,6 +201,31 @@ ComposeControls {
|
||||||
premium: PremiumLimits;
|
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) {
|
switchPmButton: RoundButton(defaultBoxButton) {
|
||||||
width: 320px;
|
width: 320px;
|
||||||
height: 34px;
|
height: 34px;
|
||||||
|
@ -1091,3 +1116,21 @@ defaultComposeControls: ComposeControls {
|
||||||
files: defaultComposeFiles;
|
files: defaultComposeFiles;
|
||||||
premium: defaultPremiumLimits;
|
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;
|
return _recentViewers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::vector<StoryView> &Story::viewsList() const {
|
||||||
|
return _viewsList;
|
||||||
|
}
|
||||||
|
|
||||||
int Story::views() const {
|
int Story::views() const {
|
||||||
return _views;
|
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) {
|
bool Story::applyChanges(StoryMedia media, const MTPDstoryItem &data) {
|
||||||
const auto pinned = data.is_pinned();
|
const auto pinned = data.is_pinned();
|
||||||
auto caption = TextWithEntities{
|
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() {
|
bool Stories::isQuitPrevent() {
|
||||||
if (!_markReadPending.empty()) {
|
if (!_markReadPending.empty()) {
|
||||||
sendMarkAsReadRequests();
|
sendMarkAsReadRequests();
|
||||||
|
|
|
@ -28,6 +28,13 @@ struct StoryMedia {
|
||||||
friend inline bool operator==(StoryMedia, StoryMedia) = default;
|
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 {
|
class Story {
|
||||||
public:
|
public:
|
||||||
Story(
|
Story(
|
||||||
|
@ -60,7 +67,12 @@ public:
|
||||||
void setViewsData(std::vector<not_null<PeerData*>> recent, int total);
|
void setViewsData(std::vector<not_null<PeerData*>> recent, int total);
|
||||||
[[nodiscard]] auto recentViewers() const
|
[[nodiscard]] auto recentViewers() const
|
||||||
-> const std::vector<not_null<PeerData*>> &;
|
-> const std::vector<not_null<PeerData*>> &;
|
||||||
|
[[nodiscard]] const std::vector<StoryView> &viewsList() const;
|
||||||
[[nodiscard]] int views() 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);
|
bool applyChanges(StoryMedia media, const MTPDstoryItem &data);
|
||||||
|
|
||||||
|
@ -70,6 +82,7 @@ private:
|
||||||
StoryMedia _media;
|
StoryMedia _media;
|
||||||
TextWithEntities _caption;
|
TextWithEntities _caption;
|
||||||
std::vector<not_null<PeerData*>> _recentViewers;
|
std::vector<not_null<PeerData*>> _recentViewers;
|
||||||
|
std::vector<StoryView> _viewsList;
|
||||||
int _views = 0;
|
int _views = 0;
|
||||||
const TimeId _date = 0;
|
const TimeId _date = 0;
|
||||||
bool _pinned = false;
|
bool _pinned = false;
|
||||||
|
@ -124,6 +137,12 @@ public:
|
||||||
[[nodiscard]] bool isQuitPrevent();
|
[[nodiscard]] bool isQuitPrevent();
|
||||||
void markAsRead(FullStoryId id, bool viewed);
|
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:
|
private:
|
||||||
[[nodiscard]] StoriesList parse(const MTPUserStories &stories);
|
[[nodiscard]] StoriesList parse(const MTPUserStories &stories);
|
||||||
[[nodiscard]] Story *parseAndApply(
|
[[nodiscard]] Story *parseAndApply(
|
||||||
|
@ -172,6 +191,11 @@ private:
|
||||||
base::Timer _markReadTimer;
|
base::Timer _markReadTimer;
|
||||||
base::flat_set<PeerId> _markReadRequests;
|
base::flat_set<PeerId> _markReadRequests;
|
||||||
|
|
||||||
|
StoryId _viewsStoryId = 0;
|
||||||
|
std::optional<StoryView> _viewsOffset;
|
||||||
|
Fn<void(std::vector<StoryView>)> _viewsDone;
|
||||||
|
mtpRequestId _viewsRequestId = 0;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
|
@ -67,6 +67,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "info/info_memento.h"
|
#include "info/info_memento.h"
|
||||||
#include "styles/style_dialogs.h"
|
#include "styles/style_dialogs.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
#include "styles/style_chat_helpers.h"
|
||||||
#include "styles/style_info.h"
|
#include "styles/style_info.h"
|
||||||
#include "styles/style_window.h"
|
#include "styles/style_window.h"
|
||||||
#include "base/qt/qt_common_adapters.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 "data/data_session.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
#include "styles/style_chat_helpers.h"
|
||||||
#include "styles/style_window.h"
|
#include "styles/style_window.h"
|
||||||
#include "styles/style_info.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 "window/window_session_controller.h"
|
||||||
#include "styles/style_boxes.h"
|
#include "styles/style_boxes.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
#include "styles/style_chat_helpers.h"
|
||||||
#include "styles/style_dialogs.h"
|
#include "styles/style_dialogs.h"
|
||||||
#include "styles/style_info.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_peer_menu.h"
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
#include "styles/style_chat_helpers.h"
|
||||||
|
|
||||||
namespace HistoryView::Controls {
|
namespace HistoryView::Controls {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
|
@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "menu/menu_ttl_validator.h"
|
#include "menu/menu_ttl_validator.h"
|
||||||
#include "ui/text/format_values.h"
|
#include "ui/text/format_values.h"
|
||||||
#include "ui/text/text_utilities.h"
|
#include "ui/text/text_utilities.h"
|
||||||
|
#include "styles/style_chat_helpers.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
|
||||||
namespace HistoryView::Controls {
|
namespace HistoryView::Controls {
|
||||||
|
|
|
@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/paint/blobs.h"
|
#include "ui/paint/blobs.h"
|
||||||
#include "ui/painter.h"
|
#include "ui/painter.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
#include "styles/style_chat_helpers.h"
|
||||||
#include "styles/style_layers.h"
|
#include "styles/style_layers.h"
|
||||||
|
|
||||||
namespace HistoryView::Controls {
|
namespace HistoryView::Controls {
|
||||||
|
|
|
@ -40,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "base/unixtime.h"
|
#include "base/unixtime.h"
|
||||||
#include "boxes/peers/edit_contact_box.h"
|
#include "boxes/peers/edit_contact_box.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
#include "styles/style_chat_helpers.h"
|
||||||
#include "styles/style_layers.h"
|
#include "styles/style_layers.h"
|
||||||
#include "styles/style_info.h"
|
#include "styles/style_info.h"
|
||||||
#include "styles/style_menu_icons.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 "spellcheck/spellcheck_types.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
#include "styles/style_chat_helpers.h"
|
||||||
#include "styles/style_menu_icons.h"
|
#include "styles/style_menu_icons.h"
|
||||||
|
|
||||||
#include <QtGui/QGuiApplication>
|
#include <QtGui/QGuiApplication>
|
||||||
|
|
|
@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "ui/toast/toast.h"
|
#include "ui/toast/toast.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
#include "styles/style_chat_helpers.h"
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "calls/calls_instance.h"
|
#include "calls/calls_instance.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
#include "styles/style_chat_helpers.h"
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
#include "styles/style_widgets.h"
|
#include "styles/style_widgets.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
#include "styles/style_chat_helpers.h"
|
||||||
#include "styles/style_dialogs.h"
|
#include "styles/style_dialogs.h"
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
|
|
|
@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "base/weak_ptr.h"
|
#include "base/weak_ptr.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
#include "styles/style_chat_helpers.h"
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
|
@ -46,6 +46,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "platform/platform_specific.h"
|
#include "platform/platform_specific.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
#include "styles/style_chat_helpers.h"
|
||||||
#include "styles/style_window.h"
|
#include "styles/style_window.h"
|
||||||
#include "styles/style_info.h"
|
#include "styles/style_info.h"
|
||||||
#include "styles/style_boxes.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 "info/profile/info_profile_values.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
#include "styles/style_chat_helpers.h"
|
||||||
#include "styles/style_window.h"
|
#include "styles/style_window.h"
|
||||||
#include "styles/style_info.h"
|
#include "styles/style_info.h"
|
||||||
#include "styles/style_boxes.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 "inline_bots/inline_bot_result.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
#include "styles/style_chat_helpers.h"
|
||||||
#include "styles/style_window.h"
|
#include "styles/style_window.h"
|
||||||
#include "styles/style_info.h"
|
#include "styles/style_info.h"
|
||||||
#include "styles/style_boxes.h"
|
#include "styles/style_boxes.h"
|
||||||
|
|
|
@ -284,7 +284,7 @@ void TranslateBar::setup(not_null<History*> history) {
|
||||||
|
|
||||||
button->paintRequest(
|
button->paintRequest(
|
||||||
) | rpl::start_with_next([=](QRect clip) {
|
) | rpl::start_with_next([=](QRect clip) {
|
||||||
QPainter(button).fillRect(clip, st::historyComposeButton.bgColor);
|
QPainter(button).fillRect(clip, st::historyComposeButtonBg);
|
||||||
}, button->lifetime());
|
}, button->lifetime());
|
||||||
|
|
||||||
button->setClickedCallback([=] {
|
button->setClickedCallback([=] {
|
||||||
|
|
|
@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/painter.h"
|
#include "ui/painter.h"
|
||||||
#include "ui/power_saving.h"
|
#include "ui/power_saving.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
#include "styles/style_chat_helpers.h"
|
||||||
|
|
||||||
namespace HistoryView::Reactions {
|
namespace HistoryView::Reactions {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
|
@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/widgets/menu/menu_action.h"
|
#include "ui/widgets/menu/menu_action.h"
|
||||||
#include "ui/widgets/popup_menu.h"
|
#include "ui/widgets/popup_menu.h"
|
||||||
#include "styles/style_chat.h" // expandedMenuSeparator.
|
#include "styles/style_chat.h" // expandedMenuSeparator.
|
||||||
|
#include "styles/style_chat_helpers.h"
|
||||||
|
|
||||||
namespace Info {
|
namespace Info {
|
||||||
namespace Profile {
|
namespace Profile {
|
||||||
|
|
|
@ -426,7 +426,7 @@ void Controller::show(
|
||||||
_shown = storyId;
|
_shown = storyId;
|
||||||
_captionText = story->caption();
|
_captionText = story->caption();
|
||||||
_captionFullView = nullptr;
|
_captionFullView = nullptr;
|
||||||
|
invalidate_weak_ptrs(&_viewsLoadGuard);
|
||||||
if (_replyFocused) {
|
if (_replyFocused) {
|
||||||
unfocusReply();
|
unfocusReply();
|
||||||
}
|
}
|
||||||
|
@ -680,6 +680,96 @@ SiblingView Controller::sibling(SiblingType type) const {
|
||||||
return {};
|
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() {
|
void Controller::unfocusReply() {
|
||||||
_wrap->setFocus();
|
_wrap->setFocus();
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,6 +78,11 @@ struct Layout {
|
||||||
friend inline bool operator==(Layout, Layout) = default;
|
friend inline bool operator==(Layout, Layout) = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ViewsSlice {
|
||||||
|
std::vector<Data::StoryView> list;
|
||||||
|
int left = 0;
|
||||||
|
};
|
||||||
|
|
||||||
class Controller final {
|
class Controller final {
|
||||||
public:
|
public:
|
||||||
explicit Controller(not_null<Delegate*> delegate);
|
explicit Controller(not_null<Delegate*> delegate);
|
||||||
|
@ -114,6 +119,9 @@ public:
|
||||||
void repaintSibling(not_null<Sibling*> sibling);
|
void repaintSibling(not_null<Sibling*> sibling);
|
||||||
[[nodiscard]] SiblingView sibling(SiblingType type) const;
|
[[nodiscard]] SiblingView sibling(SiblingType type) const;
|
||||||
|
|
||||||
|
[[nodiscard]] ViewsSlice views(PeerId offset);
|
||||||
|
[[nodiscard]] rpl::producer<> moreViewsLoaded() const;
|
||||||
|
|
||||||
void unfocusReply();
|
void unfocusReply();
|
||||||
|
|
||||||
[[nodiscard]] rpl::lifetime &lifetime();
|
[[nodiscard]] rpl::lifetime &lifetime();
|
||||||
|
@ -142,6 +150,11 @@ private:
|
||||||
void subjumpTo(int index);
|
void subjumpTo(int index);
|
||||||
void checkWaitingFor();
|
void checkWaitingFor();
|
||||||
|
|
||||||
|
void refreshViewsFromData();
|
||||||
|
bool sliceViewsTo(PeerId offset);
|
||||||
|
[[nodiscard]] auto viewsGotMoreCallback()
|
||||||
|
-> Fn<void(std::vector<Data::StoryView>)>;
|
||||||
|
|
||||||
const not_null<Delegate*> _delegate;
|
const not_null<Delegate*> _delegate;
|
||||||
|
|
||||||
rpl::variable<std::optional<Layout>> _layout;
|
rpl::variable<std::optional<Layout>> _layout;
|
||||||
|
@ -170,6 +183,10 @@ private:
|
||||||
int _index = 0;
|
int _index = 0;
|
||||||
bool _started = false;
|
bool _started = false;
|
||||||
|
|
||||||
|
ViewsSlice _viewsSlice;
|
||||||
|
rpl::event_stream<> _moreViewsLoaded;
|
||||||
|
base::has_weak_ptr _viewsLoadGuard;
|
||||||
|
|
||||||
std::unique_ptr<Sibling> _siblingLeft;
|
std::unique_ptr<Sibling> _siblingLeft;
|
||||||
std::unique_ptr<Sibling> _siblingRight;
|
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 "media/stories/media_stories_recent_views.h"
|
||||||
|
|
||||||
|
#include "api/api_who_reacted.h" // FormatReadDate.
|
||||||
#include "data/data_peer.h"
|
#include "data/data_peer.h"
|
||||||
|
#include "data/data_stories.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "media/stories/media_stories_controller.h"
|
#include "media/stories/media_stories_controller.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "ui/chat/group_call_userpics.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/painter.h"
|
||||||
#include "ui/rp_widget.h"
|
#include "ui/rp_widget.h"
|
||||||
#include "ui/userpic_view.h"
|
#include "ui/userpic_view.h"
|
||||||
|
@ -21,6 +25,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
namespace Media::Stories {
|
namespace Media::Stories {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kAddPerPage = 50;
|
||||||
|
constexpr auto kLoadViewsPages = 2;
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<std::vector<Ui::GroupCallUser>> ContentByUsers(
|
[[nodiscard]] rpl::producer<std::vector<Ui::GroupCallUser>> ContentByUsers(
|
||||||
const std::vector<not_null<PeerData*>> &list) {
|
const std::vector<not_null<PeerData*>> &list) {
|
||||||
struct Userpic {
|
struct Userpic {
|
||||||
|
@ -37,7 +44,7 @@ namespace {
|
||||||
bool scheduled = false;
|
bool scheduled = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
static const auto size = st::storiesRecentViewsUserpics.size;
|
static const auto size = st::storiesWhoViewed.userpics.size;
|
||||||
|
|
||||||
static const auto GenerateUserpic = [](Userpic &userpic) {
|
static const auto GenerateUserpic = [](Userpic &userpic) {
|
||||||
auto result = userpic.peer->generateUserpicImage(
|
auto result = userpic.peer->generateUserpicImage(
|
||||||
|
@ -132,57 +139,302 @@ void RecentViews::show(RecentViewsData data) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!_widget) {
|
if (!_widget) {
|
||||||
const auto parent = _controller->wrap();
|
setupWidget();
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
if (!_userpics) {
|
if (!_userpics) {
|
||||||
_userpics = std::make_unique<Ui::GroupCallUserpics>(
|
setupUserpics();
|
||||||
st::storiesRecentViewsUserpics,
|
}
|
||||||
rpl::single(true),
|
if (totalChanged) {
|
||||||
[=] { _widget->update(); });
|
updateText();
|
||||||
|
|
||||||
_userpics->widthValue() | rpl::start_with_next([=](int width) {
|
|
||||||
_userpicsWidth = width;
|
|
||||||
}, _widget->lifetime());
|
|
||||||
}
|
}
|
||||||
if (usersChanged) {
|
if (usersChanged) {
|
||||||
_userpicsLifetime = ContentByUsers(
|
updateUserpics();
|
||||||
data.list
|
|
||||||
) | rpl::start_with_next([=](
|
|
||||||
const std::vector<Ui::GroupCallUser> &list) {
|
|
||||||
_userpics->update(list, true);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
} // namespace Media::Stories
|
||||||
|
|
|
@ -7,13 +7,25 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "base/unique_qptr.h"
|
||||||
#include "ui/text/text.h"
|
#include "ui/text/text.h"
|
||||||
|
#include "ui/userpic_view.h"
|
||||||
|
|
||||||
|
namespace Data {
|
||||||
|
struct StoryView;
|
||||||
|
} // namespace Data
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class RpWidget;
|
class RpWidget;
|
||||||
class GroupCallUserpics;
|
class GroupCallUserpics;
|
||||||
|
class PopupMenu;
|
||||||
|
class WhoReactedEntryAction;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
||||||
|
namespace Main {
|
||||||
|
class Session;
|
||||||
|
} // namespace Main
|
||||||
|
|
||||||
namespace Media::Stories {
|
namespace Media::Stories {
|
||||||
|
|
||||||
class Controller;
|
class Controller;
|
||||||
|
@ -39,6 +51,26 @@ public:
|
||||||
void show(RecentViewsData data);
|
void show(RecentViewsData data);
|
||||||
|
|
||||||
private:
|
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;
|
const not_null<Controller*> _controller;
|
||||||
|
|
||||||
std::unique_ptr<Ui::RpWidget> _widget;
|
std::unique_ptr<Ui::RpWidget> _widget;
|
||||||
|
@ -46,6 +78,20 @@ private:
|
||||||
Ui::Text::String _text;
|
Ui::Text::String _text;
|
||||||
RecentViewsData _data;
|
RecentViewsData _data;
|
||||||
rpl::lifetime _userpicsLifetime;
|
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;
|
int _userpicsWidth = 0;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -729,11 +729,21 @@ storiesComposeControls: ComposeControls(defaultComposeControls) {
|
||||||
}
|
}
|
||||||
premium: storiesComposePremium;
|
premium: storiesComposePremium;
|
||||||
}
|
}
|
||||||
storiesRecentViewsUserpics: GroupCallUserpics {
|
storiesViewsMenu: PopupMenu(storiesPopupMenuWithIcons) {
|
||||||
size: 24px;
|
scrollPadding: margins(0px, 6px, 0px, 4px);
|
||||||
shift: 9px;
|
maxHeight: 320px;
|
||||||
stroke: 4px;
|
menu: Menu(storiesMenuWithIcons) {
|
||||||
align: align(left);
|
widthMin: 215px;
|
||||||
|
widthMax: 215px;
|
||||||
|
}
|
||||||
|
radius: 7px;
|
||||||
}
|
}
|
||||||
storiesRecentViewsSkip: 8px;
|
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 "mainwindow.h"
|
||||||
#include "windows_quiethours_h.h"
|
#include "windows_quiethours_h.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
#include "styles/style_chat_helpers.h"
|
||||||
|
|
||||||
#include <QtCore/QOperatingSystemVersion>
|
#include <QtCore/QOperatingSystemVersion>
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "window/window_controller.h"
|
#include "window/window_controller.h"
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
#include "styles/style_chat_helpers.h"
|
||||||
#include "styles/style_boxes.h"
|
#include "styles/style_boxes.h"
|
||||||
#include "styles/style_settings.h"
|
#include "styles/style_settings.h"
|
||||||
#include "styles/style_info.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) {
|
whoReadMenu: PopupMenu(popupMenuExpandedSeparator) {
|
||||||
scrollPadding: margins(0px, 6px, 0px, 4px);
|
scrollPadding: margins(0px, 6px, 0px, 4px);
|
||||||
maxHeight: 400px;
|
maxHeight: 400px;
|
||||||
|
@ -934,24 +911,6 @@ historySendDisabledIcon: icon {{ "emoji/premium_lock", placeholderFgActive }};
|
||||||
historySendDisabledIconSkip: 20px;
|
historySendDisabledIconSkip: 20px;
|
||||||
historySendDisabledPosition: point(0px, 0px);
|
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) {
|
backgroundSwitchToDark: IconButton(defaultIconButton) {
|
||||||
width: 48px;
|
width: 48px;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
|
|
|
@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "base/unixtime.h"
|
#include "base/unixtime.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
#include "styles/style_chat_helpers.h"
|
||||||
#include "styles/style_calls.h"
|
#include "styles/style_calls.h"
|
||||||
#include "styles/style_info.h" // st::topBarArrowPadding, like TopBarWidget.
|
#include "styles/style_info.h" // st::topBarArrowPadding, like TopBarWidget.
|
||||||
#include "styles/style_window.h" // st::columnMinimalWidthLeft
|
#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 "ui/power_saving.h"
|
||||||
#include "base/random.h"
|
#include "base/random.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
#include "styles/style_chat_helpers.h"
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
|
@ -12,7 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/text/text_options.h"
|
#include "ui/text/text_options.h"
|
||||||
#include "ui/painter.h"
|
#include "ui/painter.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat_helpers.h"
|
||||||
#include "styles/style_window.h" // st::columnMinimalWidthLeft
|
#include "styles/style_window.h" // st::columnMinimalWidthLeft
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
|
|
|
@ -12,7 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/wrap/padding_wrap.h"
|
#include "ui/wrap/padding_wrap.h"
|
||||||
#include "ui/wrap/slide_wrap.h"
|
#include "ui/wrap/slide_wrap.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat_helpers.h"
|
||||||
#include "styles/style_info.h"
|
#include "styles/style_info.h"
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
|
|
|
@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
#include "styles/style_chat_helpers.h"
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
|
@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/painter.h"
|
#include "ui/painter.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
#include "styles/style_chat_helpers.h"
|
||||||
#include "styles/style_menu_icons.h"
|
#include "styles/style_menu_icons.h"
|
||||||
|
|
||||||
namespace Lang {
|
namespace Lang {
|
||||||
|
@ -69,16 +70,9 @@ StringWithReacted ReplaceTag<StringWithReacted>::Call(
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
using Text::CustomEmojiFactory;
|
constexpr auto kPreloaderAlpha = 0.2;
|
||||||
|
|
||||||
struct EntryData {
|
using Text::CustomEmojiFactory;
|
||||||
QString text;
|
|
||||||
QString date;
|
|
||||||
bool dateReacted = false;
|
|
||||||
QString customEntityData;
|
|
||||||
QImage userpic;
|
|
||||||
Fn<void()> callback;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Action final : public Menu::ItemBase {
|
class Action final : public Menu::ItemBase {
|
||||||
public:
|
public:
|
||||||
|
@ -465,44 +459,11 @@ void Action::handleKeyPress(not_null<QKeyEvent*> e) {
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
class WhoReactedListMenu::EntryAction final : public Menu::ItemBase {
|
WhoReactedEntryAction::WhoReactedEntryAction(
|
||||||
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(
|
|
||||||
not_null<RpWidget*> parent,
|
not_null<RpWidget*> parent,
|
||||||
CustomEmojiFactory customEmojiFactory,
|
CustomEmojiFactory customEmojiFactory,
|
||||||
const style::Menu &st,
|
const style::Menu &st,
|
||||||
EntryData &&data)
|
Data &&data)
|
||||||
: ItemBase(parent, st)
|
: ItemBase(parent, st)
|
||||||
, _dummyAction(CreateChild<QAction>(parent.get()))
|
, _dummyAction(CreateChild<QAction>(parent.get()))
|
||||||
, _customEmojiFactory(std::move(customEmojiFactory))
|
, _customEmojiFactory(std::move(customEmojiFactory))
|
||||||
|
@ -521,19 +482,19 @@ WhoReactedListMenu::EntryAction::EntryAction(
|
||||||
enableMouseSelecting();
|
enableMouseSelecting();
|
||||||
}
|
}
|
||||||
|
|
||||||
not_null<QAction*> WhoReactedListMenu::EntryAction::action() const {
|
not_null<QAction*> WhoReactedEntryAction::action() const {
|
||||||
return _dummyAction.get();
|
return _dummyAction.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WhoReactedListMenu::EntryAction::isEnabled() const {
|
bool WhoReactedEntryAction::isEnabled() const {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int WhoReactedListMenu::EntryAction::contentHeight() const {
|
int WhoReactedEntryAction::contentHeight() const {
|
||||||
return _height;
|
return _height;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WhoReactedListMenu::EntryAction::setData(EntryData &&data) {
|
void WhoReactedEntryAction::setData(Data &&data) {
|
||||||
setClickedCallback(std::move(data.callback));
|
setClickedCallback(std::move(data.callback));
|
||||||
_userpic = std::move(data.userpic);
|
_userpic = std::move(data.userpic);
|
||||||
_text.setMarkedText(_st.itemStyle, { data.text }, MenuTextOptions);
|
_text.setMarkedText(_st.itemStyle, { data.text }, MenuTextOptions);
|
||||||
|
@ -546,7 +507,10 @@ void WhoReactedListMenu::EntryAction::setData(EntryData &&data) {
|
||||||
MenuTextOptions);
|
MenuTextOptions);
|
||||||
}
|
}
|
||||||
_dateReacted = data.dateReacted;
|
_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 ratio = style::DevicePixelRatio();
|
||||||
const auto size = Emoji::GetSizeNormal() / ratio;
|
const auto size = Emoji::GetSizeNormal() / ratio;
|
||||||
_customSize = Text::AdjustCustomEmojiSize(size);
|
_customSize = Text::AdjustCustomEmojiSize(size);
|
||||||
|
@ -565,7 +529,7 @@ void WhoReactedListMenu::EntryAction::setData(EntryData &&data) {
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WhoReactedListMenu::EntryAction::paint(Painter &&p) {
|
void WhoReactedEntryAction::paint(Painter &&p) {
|
||||||
const auto enabled = isEnabled();
|
const auto enabled = isEnabled();
|
||||||
const auto selected = isSelected();
|
const auto selected = isSelected();
|
||||||
if (selected && _st.itemBgOver->c.alpha() < 255) {
|
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 photoSize = st::defaultWhoRead.photoSize;
|
||||||
const auto photoLeft = st::defaultWhoRead.photoLeft;
|
const auto photoLeft = st::defaultWhoRead.photoLeft;
|
||||||
const auto photoTop = (height() - photoSize) / 2;
|
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);
|
p.drawImage(photoLeft, photoTop, _userpic);
|
||||||
} else if (!_custom) {
|
} else if (!_custom) {
|
||||||
st::menuIconReactions.paintInCenter(
|
st::menuIconReactions.paintInCenter(
|
||||||
|
@ -590,17 +565,31 @@ void WhoReactedListMenu::EntryAction::paint(Painter &&p) {
|
||||||
const auto textTop = withDate
|
const auto textTop = withDate
|
||||||
? st::whoReadNameWithDateTop
|
? st::whoReadNameWithDateTop
|
||||||
: (height() - _st.itemStyle.font->height) / 2;
|
: (height() - _st.itemStyle.font->height) / 2;
|
||||||
p.setPen(selected
|
if (_preloader) {
|
||||||
? _st.itemFgOver
|
auto hq = PainterHighQualityEnabler(p);
|
||||||
: enabled
|
p.setPen(Qt::NoPen);
|
||||||
? _st.itemFg
|
p.setBrush(preloaderBrush);
|
||||||
: _st.itemFgDisabled);
|
const auto height = _st.itemStyle.font->height / 2;
|
||||||
_text.drawLeftElided(
|
p.drawRoundedRect(
|
||||||
p,
|
st::defaultWhoRead.nameLeft,
|
||||||
st::defaultWhoRead.nameLeft,
|
textTop + (_st.itemStyle.font->height - height) / 2,
|
||||||
textTop,
|
_textWidth,
|
||||||
_textWidth,
|
height,
|
||||||
width());
|
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) {
|
if (withDate) {
|
||||||
const auto iconPosition = QPoint(
|
const auto iconPosition = QPoint(
|
||||||
st::defaultWhoRead.nameLeft,
|
st::defaultWhoRead.nameLeft,
|
||||||
|
@ -690,11 +679,11 @@ void WhoReactedListMenu::populate(
|
||||||
addedToBottom = 0;
|
addedToBottom = 0;
|
||||||
}
|
}
|
||||||
auto index = 0;
|
auto index = 0;
|
||||||
const auto append = [&](EntryData &&data) {
|
const auto append = [&](WhoReactedEntryData &&data) {
|
||||||
if (index < _actions.size()) {
|
if (index < _actions.size()) {
|
||||||
_actions[index]->setData(std::move(data));
|
_actions[index]->setData(std::move(data));
|
||||||
} else {
|
} else {
|
||||||
auto item = base::make_unique_q<EntryAction>(
|
auto item = base::make_unique_q<WhoReactedEntryAction>(
|
||||||
menu->menu(),
|
menu->menu(),
|
||||||
_customEmojiFactory,
|
_customEmojiFactory,
|
||||||
menu->menu()->st(),
|
menu->menu()->st(),
|
||||||
|
|
|
@ -9,11 +9,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "base/unique_qptr.h"
|
#include "base/unique_qptr.h"
|
||||||
#include "ui/text/text_block.h"
|
#include "ui/text/text_block.h"
|
||||||
|
#include "ui/widgets/menu/menu_item_base.h"
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
namespace Menu {
|
|
||||||
class ItemBase;
|
|
||||||
} // namespace Menu
|
|
||||||
|
|
||||||
class PopupMenu;
|
class PopupMenu;
|
||||||
|
|
||||||
|
@ -56,6 +54,52 @@ struct WhoReadContent {
|
||||||
Fn<void(uint64)> participantChosen,
|
Fn<void(uint64)> participantChosen,
|
||||||
Fn<void()> showAllChosen);
|
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 {
|
class WhoReactedListMenu final {
|
||||||
public:
|
public:
|
||||||
WhoReactedListMenu(
|
WhoReactedListMenu(
|
||||||
|
@ -72,13 +116,11 @@ public:
|
||||||
Fn<void()> appendBottomActions = nullptr);
|
Fn<void()> appendBottomActions = nullptr);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class EntryAction;
|
|
||||||
|
|
||||||
const Text::CustomEmojiFactory _customEmojiFactory;
|
const Text::CustomEmojiFactory _customEmojiFactory;
|
||||||
const Fn<void(uint64)> _participantChosen;
|
const Fn<void(uint64)> _participantChosen;
|
||||||
const Fn<void()> _showAllChosen;
|
const Fn<void()> _showAllChosen;
|
||||||
|
|
||||||
std::vector<not_null<EntryAction*>> _actions;
|
std::vector<not_null<WhoReactedEntryAction*>> _actions;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue