mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Add a tab with "Who Seen" to "Who Reacted" box.
This commit is contained in:
parent
74a28ffdf7
commit
c8f7a8c795
9 changed files with 252 additions and 137 deletions
|
@ -51,16 +51,16 @@ inline bool operator==(
|
||||||
|
|
||||||
struct PeersWithReactions {
|
struct PeersWithReactions {
|
||||||
std::vector<PeerWithReaction> list;
|
std::vector<PeerWithReaction> list;
|
||||||
|
std::vector<PeerId> read;
|
||||||
int fullReactionsCount = 0;
|
int fullReactionsCount = 0;
|
||||||
int fullReadCount = 0;
|
|
||||||
bool unknown = false;
|
bool unknown = false;
|
||||||
};
|
};
|
||||||
inline bool operator==(
|
inline bool operator==(
|
||||||
const PeersWithReactions &a,
|
const PeersWithReactions &a,
|
||||||
const PeersWithReactions &b) noexcept {
|
const PeersWithReactions &b) noexcept {
|
||||||
return (a.fullReactionsCount == b.fullReactionsCount)
|
return (a.fullReactionsCount == b.fullReactionsCount)
|
||||||
&& (a.fullReadCount == b.fullReadCount)
|
|
||||||
&& (a.list == b.list)
|
&& (a.list == b.list)
|
||||||
|
&& (a.read == b.read)
|
||||||
&& (a.unknown == b.unknown);
|
&& (a.unknown == b.unknown);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,14 +246,15 @@ struct State {
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] PeersWithReactions WithEmptyReactions(
|
[[nodiscard]] PeersWithReactions WithEmptyReactions(
|
||||||
const Peers &peers) {
|
Peers &&peers) {
|
||||||
return PeersWithReactions{
|
auto result = PeersWithReactions{
|
||||||
.list = peers.list | ranges::views::transform([](PeerId peer) {
|
.list = peers.list | ranges::views::transform([](PeerId peer) {
|
||||||
return PeerWithReaction{.peer = peer };
|
return PeerWithReaction{.peer = peer };
|
||||||
}) | ranges::to_vector,
|
}) | ranges::to_vector,
|
||||||
.fullReadCount = int(peers.list.size()),
|
|
||||||
.unknown = peers.unknown,
|
.unknown = peers.unknown,
|
||||||
};
|
};
|
||||||
|
result.read = std::move(peers.list);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<PeersWithReactions> WhoReactedIds(
|
[[nodiscard]] rpl::producer<PeersWithReactions> WhoReactedIds(
|
||||||
|
@ -322,17 +323,17 @@ struct State {
|
||||||
return rpl::combine(
|
return rpl::combine(
|
||||||
WhoReactedIds(item, QString(), context),
|
WhoReactedIds(item, QString(), context),
|
||||||
WhoReadIds(item, context)
|
WhoReadIds(item, context)
|
||||||
) | rpl::map([=](PeersWithReactions reacted, Peers read) {
|
) | rpl::map([=](PeersWithReactions &&reacted, Peers &&read) {
|
||||||
if (reacted.unknown || read.unknown) {
|
if (reacted.unknown || read.unknown) {
|
||||||
return PeersWithReactions{ .unknown = true };
|
return PeersWithReactions{ .unknown = true };
|
||||||
}
|
}
|
||||||
auto &list = reacted.list;
|
auto &list = reacted.list;
|
||||||
reacted.fullReadCount = int(read.list.size());
|
|
||||||
for (const auto &peer : read.list) {
|
for (const auto &peer : read.list) {
|
||||||
if (!ranges::contains(list, peer, &PeerWithReaction::peer)) {
|
if (!ranges::contains(list, peer, &PeerWithReaction::peer)) {
|
||||||
list.push_back({ .peer = peer });
|
list.push_back({ .peer = peer });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
reacted.read = std::move(read.list);
|
||||||
return reacted;
|
return reacted;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -442,6 +443,104 @@ void RegenerateParticipants(not_null<State*> state, int small, int large) {
|
||||||
RegenerateUserpics(state, small, large);
|
RegenerateUserpics(state, small, large);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
|
const QString &reaction,
|
||||||
|
not_null<QWidget*> context,
|
||||||
|
const style::WhoRead &st,
|
||||||
|
std::shared_ptr<WhoReadList> whoReadIds) {
|
||||||
|
const auto small = st.userpics.size;
|
||||||
|
const auto large = st.photoSize;
|
||||||
|
return [=](auto consumer) {
|
||||||
|
auto lifetime = rpl::lifetime();
|
||||||
|
|
||||||
|
const auto resolveWhoRead = reaction.isEmpty()
|
||||||
|
&& WhoReadExists(item);
|
||||||
|
|
||||||
|
const auto state = lifetime.make_state<State>();
|
||||||
|
const auto pushNext = [=] {
|
||||||
|
consumer.put_next_copy(state->current);
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto resolveWhoReacted = !reaction.isEmpty()
|
||||||
|
|| item->canViewReactions();
|
||||||
|
auto idsWithReactions = (resolveWhoRead && resolveWhoReacted)
|
||||||
|
? WhoReadOrReactedIds(item, context)
|
||||||
|
: resolveWhoRead
|
||||||
|
? (WhoReadIds(item, context) | rpl::map(WithEmptyReactions))
|
||||||
|
: WhoReactedIds(item, reaction, context);
|
||||||
|
state->current.type = resolveWhoRead
|
||||||
|
? DetectSeenType(item)
|
||||||
|
: Ui::WhoReadType::Reacted;
|
||||||
|
if (resolveWhoReacted) {
|
||||||
|
const auto &list = item->reactions();
|
||||||
|
state->current.fullReactionsCount = reaction.isEmpty()
|
||||||
|
? ranges::accumulate(
|
||||||
|
list,
|
||||||
|
0,
|
||||||
|
ranges::plus{},
|
||||||
|
[](const auto &pair) { return pair.second; })
|
||||||
|
: list.contains(reaction)
|
||||||
|
? list.find(reaction)->second
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
// #TODO reactions
|
||||||
|
state->current.singleReaction = !reaction.isEmpty()
|
||||||
|
? reaction
|
||||||
|
: (list.size() == 1)
|
||||||
|
? list.front().first
|
||||||
|
: QString();
|
||||||
|
}
|
||||||
|
std::move(
|
||||||
|
idsWithReactions
|
||||||
|
) | rpl::start_with_next([=](PeersWithReactions &&peers) {
|
||||||
|
if (peers.unknown) {
|
||||||
|
state->userpics.clear();
|
||||||
|
consumer.put_next(Ui::WhoReadContent{
|
||||||
|
.type = state->current.type,
|
||||||
|
.fullReactionsCount = state->current.fullReactionsCount,
|
||||||
|
.fullReadCount = state->current.fullReadCount,
|
||||||
|
.unknown = true,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state->current.fullReadCount = int(peers.read.size());
|
||||||
|
state->current.fullReactionsCount = peers.fullReactionsCount;
|
||||||
|
if (whoReadIds) {
|
||||||
|
whoReadIds->list = (peers.read.size() > peers.list.size())
|
||||||
|
? std::move(peers.read)
|
||||||
|
: std::vector<PeerId>();
|
||||||
|
}
|
||||||
|
if (UpdateUserpics(state, item, peers.list)) {
|
||||||
|
RegenerateParticipants(state, small, large);
|
||||||
|
pushNext();
|
||||||
|
} else if (peers.list.empty()) {
|
||||||
|
pushNext();
|
||||||
|
}
|
||||||
|
}, lifetime);
|
||||||
|
|
||||||
|
item->history()->session().downloaderTaskFinished(
|
||||||
|
) | rpl::filter([=] {
|
||||||
|
return state->someUserpicsNotLoaded && !state->scheduled;
|
||||||
|
}) | rpl::start_with_next([=] {
|
||||||
|
for (const auto &userpic : state->userpics) {
|
||||||
|
if (userpic.peer->userpicUniqueKey(userpic.view)
|
||||||
|
!= userpic.uniqueKey) {
|
||||||
|
state->scheduled = true;
|
||||||
|
crl::on_main(&state->guard, [=] {
|
||||||
|
state->scheduled = false;
|
||||||
|
RegenerateUserpics(state, small, large);
|
||||||
|
pushNext();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, lifetime);
|
||||||
|
|
||||||
|
return lifetime;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
bool WhoReadExists(not_null<HistoryItem*> item) {
|
bool WhoReadExists(not_null<HistoryItem*> item) {
|
||||||
|
@ -486,8 +585,9 @@ bool WhoReactedExists(not_null<HistoryItem*> item) {
|
||||||
rpl::producer<Ui::WhoReadContent> WhoReacted(
|
rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
not_null<QWidget*> context,
|
not_null<QWidget*> context,
|
||||||
const style::WhoRead &st) {
|
const style::WhoRead &st,
|
||||||
return WhoReacted(item, QString(), context, st);
|
std::shared_ptr<WhoReadList> whoReadIds) {
|
||||||
|
return WhoReacted(item, QString(), context, st, std::move(whoReadIds));
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<Ui::WhoReadContent> WhoReacted(
|
rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||||
|
@ -495,90 +595,7 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||||
const QString &reaction,
|
const QString &reaction,
|
||||||
not_null<QWidget*> context,
|
not_null<QWidget*> context,
|
||||||
const style::WhoRead &st) {
|
const style::WhoRead &st) {
|
||||||
const auto small = st.userpics.size;
|
return WhoReacted(item, reaction, context, st, nullptr);
|
||||||
const auto large = st.photoSize;
|
|
||||||
return [=](auto consumer) {
|
|
||||||
auto lifetime = rpl::lifetime();
|
|
||||||
|
|
||||||
const auto resolveWhoRead = reaction.isEmpty() && WhoReadExists(item);
|
|
||||||
|
|
||||||
const auto state = lifetime.make_state<State>();
|
|
||||||
const auto pushNext = [=] {
|
|
||||||
consumer.put_next_copy(state->current);
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto resolveWhoReacted = !reaction.isEmpty()
|
|
||||||
|| item->canViewReactions();
|
|
||||||
auto idsWithReactions = (resolveWhoRead && resolveWhoReacted)
|
|
||||||
? WhoReadOrReactedIds(item, context)
|
|
||||||
: resolveWhoRead
|
|
||||||
? (WhoReadIds(item, context) | rpl::map(WithEmptyReactions))
|
|
||||||
: WhoReactedIds(item, reaction, context);
|
|
||||||
state->current.type = resolveWhoRead
|
|
||||||
? DetectSeenType(item)
|
|
||||||
: Ui::WhoReadType::Reacted;
|
|
||||||
if (resolveWhoReacted) {
|
|
||||||
const auto &list = item->reactions();
|
|
||||||
state->current.fullReactionsCount = reaction.isEmpty()
|
|
||||||
? ranges::accumulate(
|
|
||||||
list,
|
|
||||||
0,
|
|
||||||
ranges::plus{},
|
|
||||||
[](const auto &pair) { return pair.second; })
|
|
||||||
: list.contains(reaction)
|
|
||||||
? list.find(reaction)->second
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
// #TODO reactions
|
|
||||||
state->current.singleReaction = !reaction.isEmpty()
|
|
||||||
? reaction
|
|
||||||
: (list.size() == 1)
|
|
||||||
? list.front().first
|
|
||||||
: QString();
|
|
||||||
}
|
|
||||||
std::move(
|
|
||||||
idsWithReactions
|
|
||||||
) | rpl::start_with_next([=](const PeersWithReactions &peers) {
|
|
||||||
if (peers.unknown) {
|
|
||||||
state->userpics.clear();
|
|
||||||
consumer.put_next(Ui::WhoReadContent{
|
|
||||||
.type = state->current.type,
|
|
||||||
.fullReactionsCount = state->current.fullReactionsCount,
|
|
||||||
.fullReadCount = state->current.fullReadCount,
|
|
||||||
.unknown = true,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
state->current.fullReadCount = peers.fullReadCount;
|
|
||||||
state->current.fullReactionsCount = peers.fullReactionsCount;
|
|
||||||
if (UpdateUserpics(state, item, peers.list)) {
|
|
||||||
RegenerateParticipants(state, small, large);
|
|
||||||
pushNext();
|
|
||||||
} else if (peers.list.empty()) {
|
|
||||||
pushNext();
|
|
||||||
}
|
|
||||||
}, lifetime);
|
|
||||||
|
|
||||||
item->history()->session().downloaderTaskFinished(
|
|
||||||
) | rpl::filter([=] {
|
|
||||||
return state->someUserpicsNotLoaded && !state->scheduled;
|
|
||||||
}) | rpl::start_with_next([=] {
|
|
||||||
for (const auto &userpic : state->userpics) {
|
|
||||||
if (userpic.peer->userpicUniqueKey(userpic.view)
|
|
||||||
!= userpic.uniqueKey) {
|
|
||||||
state->scheduled = true;
|
|
||||||
crl::on_main(&state->guard, [=] {
|
|
||||||
state->scheduled = false;
|
|
||||||
RegenerateUserpics(state, small, large);
|
|
||||||
pushNext();
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, lifetime);
|
|
||||||
|
|
||||||
return lifetime;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Api
|
} // namespace Api
|
||||||
|
|
|
@ -15,6 +15,7 @@ struct WhoRead;
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
struct WhoReadContent;
|
struct WhoReadContent;
|
||||||
|
enum class WhoReadType;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
||||||
namespace Api {
|
namespace Api {
|
||||||
|
@ -22,15 +23,21 @@ namespace Api {
|
||||||
[[nodiscard]] bool WhoReadExists(not_null<HistoryItem*> item);
|
[[nodiscard]] bool WhoReadExists(not_null<HistoryItem*> item);
|
||||||
[[nodiscard]] bool WhoReactedExists(not_null<HistoryItem*> item);
|
[[nodiscard]] bool WhoReactedExists(not_null<HistoryItem*> item);
|
||||||
|
|
||||||
|
struct WhoReadList {
|
||||||
|
std::vector<PeerId> list;
|
||||||
|
Ui::WhoReadType type = {};
|
||||||
|
};
|
||||||
|
|
||||||
// The context must be destroyed before the session holding this item.
|
// The context must be destroyed before the session holding this item.
|
||||||
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhoReacted(
|
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
not_null<QWidget*> context,
|
not_null<QWidget*> context, // Cache results for this lifetime.
|
||||||
const style::WhoRead &st); // Cache results for this lifetime.
|
const style::WhoRead &st,
|
||||||
|
std::shared_ptr<WhoReadList> whoReadIds = nullptr);
|
||||||
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhoReacted(
|
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
const QString &reaction,
|
const QString &reaction,
|
||||||
not_null<QWidget*> context,
|
not_null<QWidget*> context, // Cache results for this lifetime.
|
||||||
const style::WhoRead &st); // Cache results for this lifetime.
|
const style::WhoRead &st);
|
||||||
|
|
||||||
} // namespace Api
|
} // namespace Api
|
||||||
|
|
|
@ -1078,6 +1078,7 @@ void AddWhoReactedAction(
|
||||||
not_null<QWidget*> context,
|
not_null<QWidget*> context,
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
not_null<Window::SessionController*> controller) {
|
not_null<Window::SessionController*> controller) {
|
||||||
|
const auto whoReadIds = std::make_shared<Api::WhoReadList>();
|
||||||
const auto participantChosen = [=](uint64 id) {
|
const auto participantChosen = [=](uint64 id) {
|
||||||
controller->showPeerInfo(PeerId(id));
|
controller->showPeerInfo(PeerId(id));
|
||||||
};
|
};
|
||||||
|
@ -1091,7 +1092,8 @@ void AddWhoReactedAction(
|
||||||
controller->window().show(ReactionsListBox(
|
controller->window().show(ReactionsListBox(
|
||||||
controller,
|
controller,
|
||||||
item,
|
item,
|
||||||
QString()));
|
QString(),
|
||||||
|
whoReadIds));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (!menu->empty()) {
|
if (!menu->empty()) {
|
||||||
|
@ -1099,7 +1101,7 @@ void AddWhoReactedAction(
|
||||||
}
|
}
|
||||||
menu->addAction(Ui::WhoReactedContextAction(
|
menu->addAction(Ui::WhoReactedContextAction(
|
||||||
menu.get(),
|
menu.get(),
|
||||||
Api::WhoReacted(item, context, st::defaultWhoRead),
|
Api::WhoReacted(item, context, st::defaultWhoRead, whoReadIds),
|
||||||
participantChosen,
|
participantChosen,
|
||||||
showAllChosen));
|
showAllChosen));
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
|
#include "api/api_who_reacted.h"
|
||||||
|
#include "ui/controls/who_reacted_context_action.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
|
@ -50,7 +52,8 @@ public:
|
||||||
not_null<Window::SessionController*> window,
|
not_null<Window::SessionController*> window,
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
const QString &selected,
|
const QString &selected,
|
||||||
rpl::producer<QString> switches);
|
rpl::producer<QString> switches,
|
||||||
|
std::shared_ptr<Api::WhoReadList> whoReadIds);
|
||||||
|
|
||||||
Main::Session &session() const override;
|
Main::Session &session() const override;
|
||||||
void prepare() override;
|
void prepare() override;
|
||||||
|
@ -60,7 +63,8 @@ public:
|
||||||
private:
|
private:
|
||||||
using AllEntry = std::pair<not_null<UserData*>, QString>;
|
using AllEntry = std::pair<not_null<UserData*>, QString>;
|
||||||
|
|
||||||
void loadMore(const QString &offset);
|
void fillWhoRead();
|
||||||
|
void loadMore(const QString &reaction);
|
||||||
bool appendRow(not_null<UserData*> user, QString reaction);
|
bool appendRow(not_null<UserData*> user, QString reaction);
|
||||||
std::unique_ptr<PeerListRow> createRow(
|
std::unique_ptr<PeerListRow> createRow(
|
||||||
not_null<UserData*> user,
|
not_null<UserData*> user,
|
||||||
|
@ -72,6 +76,8 @@ private:
|
||||||
MTP::Sender _api;
|
MTP::Sender _api;
|
||||||
|
|
||||||
QString _shownReaction;
|
QString _shownReaction;
|
||||||
|
std::shared_ptr<Api::WhoReadList> _whoReadIds;
|
||||||
|
std::vector<not_null<UserData*>> _whoRead;
|
||||||
|
|
||||||
std::vector<AllEntry> _all;
|
std::vector<AllEntry> _all;
|
||||||
QString _allOffset;
|
QString _allOffset;
|
||||||
|
@ -127,11 +133,13 @@ Controller::Controller(
|
||||||
not_null<Window::SessionController*> window,
|
not_null<Window::SessionController*> window,
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
const QString &selected,
|
const QString &selected,
|
||||||
rpl::producer<QString> switches)
|
rpl::producer<QString> switches,
|
||||||
|
std::shared_ptr<Api::WhoReadList> whoReadIds)
|
||||||
: _window(window)
|
: _window(window)
|
||||||
, _item(item)
|
, _item(item)
|
||||||
, _api(&window->session().mtp())
|
, _api(&window->session().mtp())
|
||||||
, _shownReaction(selected) {
|
, _shownReaction(selected)
|
||||||
|
, _whoReadIds(whoReadIds) {
|
||||||
std::move(
|
std::move(
|
||||||
switches
|
switches
|
||||||
) | rpl::filter([=](const QString &reaction) {
|
) | rpl::filter([=](const QString &reaction) {
|
||||||
|
@ -146,10 +154,14 @@ Main::Session &Controller::session() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::prepare() {
|
void Controller::prepare() {
|
||||||
setDescriptionText(tr::lng_contacts_loading(tr::now));
|
if (_shownReaction == u"read"_q) {
|
||||||
|
fillWhoRead();
|
||||||
|
setDescriptionText(QString());
|
||||||
|
} else {
|
||||||
|
setDescriptionText(tr::lng_contacts_loading(tr::now));
|
||||||
|
}
|
||||||
delegate()->peerListRefreshRows();
|
delegate()->peerListRefreshRows();
|
||||||
|
loadMore(_shownReaction);
|
||||||
loadMore(QString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::showReaction(const QString &reaction) {
|
void Controller::showReaction(const QString &reaction) {
|
||||||
|
@ -163,7 +175,9 @@ void Controller::showReaction(const QString &reaction) {
|
||||||
}
|
}
|
||||||
|
|
||||||
_shownReaction = reaction;
|
_shownReaction = reaction;
|
||||||
if (_shownReaction.isEmpty()) {
|
if (_shownReaction == u"read"_q) {
|
||||||
|
fillWhoRead();
|
||||||
|
} else if (_shownReaction.isEmpty()) {
|
||||||
_filtered.clear();
|
_filtered.clear();
|
||||||
for (const auto &[user, reaction] : _all) {
|
for (const auto &[user, reaction] : _all) {
|
||||||
appendRow(user, reaction);
|
appendRow(user, reaction);
|
||||||
|
@ -177,14 +191,29 @@ void Controller::showReaction(const QString &reaction) {
|
||||||
for (const auto user : _filtered) {
|
for (const auto user : _filtered) {
|
||||||
appendRow(user, _shownReaction);
|
appendRow(user, _shownReaction);
|
||||||
}
|
}
|
||||||
loadMore(QString());
|
_filteredOffset = QString();
|
||||||
}
|
}
|
||||||
|
loadMore(_shownReaction);
|
||||||
setDescriptionText(delegate()->peerListFullRowsCount()
|
setDescriptionText(delegate()->peerListFullRowsCount()
|
||||||
? QString()
|
? QString()
|
||||||
: tr::lng_contacts_loading(tr::now));
|
: tr::lng_contacts_loading(tr::now));
|
||||||
delegate()->peerListRefreshRows();
|
delegate()->peerListRefreshRows();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Controller::fillWhoRead() {
|
||||||
|
if (_whoReadIds && !_whoReadIds->list.empty() && _whoRead.empty()) {
|
||||||
|
auto &owner = _window->session().data();
|
||||||
|
for (const auto &peerId : _whoReadIds->list) {
|
||||||
|
if (const auto user = owner.userLoaded(peerToUser(peerId))) {
|
||||||
|
_whoRead.push_back(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto &user : _whoRead) {
|
||||||
|
appendRow(user, QString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Controller::loadMoreRows() {
|
void Controller::loadMoreRows() {
|
||||||
const auto &offset = _shownReaction.isEmpty()
|
const auto &offset = _shownReaction.isEmpty()
|
||||||
? _allOffset
|
? _allOffset
|
||||||
|
@ -192,26 +221,37 @@ void Controller::loadMoreRows() {
|
||||||
if (_loadRequestId || offset.isEmpty()) {
|
if (_loadRequestId || offset.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
loadMore(offset);
|
loadMore(_shownReaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::loadMore(const QString &offset) {
|
void Controller::loadMore(const QString &reaction) {
|
||||||
|
if (reaction == u"read"_q) {
|
||||||
|
loadMore(QString());
|
||||||
|
return;
|
||||||
|
} else if (reaction.isEmpty() && _allOffset.isEmpty() && !_all.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
_api.request(_loadRequestId).cancel();
|
_api.request(_loadRequestId).cancel();
|
||||||
|
|
||||||
|
const auto &offset = reaction.isEmpty()
|
||||||
|
? _allOffset
|
||||||
|
: _filteredOffset;
|
||||||
|
|
||||||
using Flag = MTPmessages_GetMessageReactionsList::Flag;
|
using Flag = MTPmessages_GetMessageReactionsList::Flag;
|
||||||
const auto flags = Flag(0)
|
const auto flags = Flag(0)
|
||||||
| (offset.isEmpty() ? Flag(0) : Flag::f_offset)
|
| (offset.isEmpty() ? Flag(0) : Flag::f_offset)
|
||||||
| (_shownReaction.isEmpty() ? Flag(0) : Flag::f_reaction);
|
| (reaction.isEmpty() ? Flag(0) : Flag::f_reaction);
|
||||||
_loadRequestId = _api.request(MTPmessages_GetMessageReactionsList(
|
_loadRequestId = _api.request(MTPmessages_GetMessageReactionsList(
|
||||||
MTP_flags(flags),
|
MTP_flags(flags),
|
||||||
_item->history()->peer->input,
|
_item->history()->peer->input,
|
||||||
MTP_int(_item->id),
|
MTP_int(_item->id),
|
||||||
MTP_string(_shownReaction),
|
MTP_string(reaction),
|
||||||
MTP_string(offset),
|
MTP_string(offset),
|
||||||
MTP_int(kPerPageFirst)
|
MTP_int(kPerPageFirst)
|
||||||
)).done([=](const MTPmessages_MessageReactionsList &result) {
|
)).done([=](const MTPmessages_MessageReactionsList &result) {
|
||||||
_loadRequestId = 0;
|
_loadRequestId = 0;
|
||||||
const auto filtered = !_shownReaction.isEmpty();
|
const auto filtered = !reaction.isEmpty();
|
||||||
|
const auto shown = (reaction == _shownReaction);
|
||||||
result.match([&](const MTPDmessages_messageReactionsList &data) {
|
result.match([&](const MTPDmessages_messageReactionsList &data) {
|
||||||
const auto sessionData = &session().data();
|
const auto sessionData = &session().data();
|
||||||
sessionData->processUsers(data.vusers());
|
sessionData->processUsers(data.vusers());
|
||||||
|
@ -222,7 +262,7 @@ void Controller::loadMore(const QString &offset) {
|
||||||
const auto user = sessionData->userLoaded(
|
const auto user = sessionData->userLoaded(
|
||||||
data.vuser_id().v);
|
data.vuser_id().v);
|
||||||
const auto reaction = qs(data.vreaction());
|
const auto reaction = qs(data.vreaction());
|
||||||
if (user && appendRow(user, reaction)) {
|
if (user && (!shown || appendRow(user, reaction))) {
|
||||||
if (filtered) {
|
if (filtered) {
|
||||||
_filtered.emplace_back(user);
|
_filtered.emplace_back(user);
|
||||||
} else {
|
} else {
|
||||||
|
@ -232,8 +272,10 @@ void Controller::loadMore(const QString &offset) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
setDescriptionText(QString());
|
if (shown) {
|
||||||
delegate()->peerListRefreshRows();
|
setDescriptionText(QString());
|
||||||
|
delegate()->peerListRefreshRows();
|
||||||
|
}
|
||||||
}).send();
|
}).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,20 +306,29 @@ std::unique_ptr<PeerListRow> Controller::createRow(
|
||||||
object_ptr<Ui::BoxContent> ReactionsListBox(
|
object_ptr<Ui::BoxContent> ReactionsListBox(
|
||||||
not_null<Window::SessionController*> window,
|
not_null<Window::SessionController*> window,
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
QString selected) {
|
QString selected,
|
||||||
|
std::shared_ptr<Api::WhoReadList> whoReadIds) {
|
||||||
Expects(IsServerMsgId(item->id));
|
Expects(IsServerMsgId(item->id));
|
||||||
|
|
||||||
if (!item->reactions().contains(selected)) {
|
if (!item->reactions().contains(selected)) {
|
||||||
selected = QString();
|
selected = QString();
|
||||||
}
|
}
|
||||||
|
if (selected.isEmpty() && whoReadIds && !whoReadIds->list.empty()) {
|
||||||
|
selected = u"read"_q;
|
||||||
|
}
|
||||||
const auto tabRequests = std::make_shared<rpl::event_stream<QString>>();
|
const auto tabRequests = std::make_shared<rpl::event_stream<QString>>();
|
||||||
const auto initBox = [=](not_null<PeerListBox*> box) {
|
const auto initBox = [=](not_null<PeerListBox*> box) {
|
||||||
box->setNoContentMargin(true);
|
box->setNoContentMargin(true);
|
||||||
|
|
||||||
|
auto map = item->reactions();
|
||||||
|
if (whoReadIds && !whoReadIds->list.empty()) {
|
||||||
|
map.emplace(u"read"_q, int(whoReadIds->list.size()));
|
||||||
|
}
|
||||||
const auto selector = CreateReactionSelector(
|
const auto selector = CreateReactionSelector(
|
||||||
box,
|
box,
|
||||||
item->reactions(),
|
map,
|
||||||
selected);
|
selected,
|
||||||
|
whoReadIds ? whoReadIds->type : Ui::WhoReadType::Reacted);
|
||||||
selector->changes(
|
selector->changes(
|
||||||
) | rpl::start_to_stream(*tabRequests, box->lifetime());
|
) | rpl::start_to_stream(*tabRequests, box->lifetime());
|
||||||
|
|
||||||
|
@ -299,7 +350,8 @@ object_ptr<Ui::BoxContent> ReactionsListBox(
|
||||||
window,
|
window,
|
||||||
item,
|
item,
|
||||||
selected,
|
selected,
|
||||||
tabRequests->events()),
|
tabRequests->events(),
|
||||||
|
whoReadIds),
|
||||||
initBox);
|
initBox);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
class HistoryItem;
|
class HistoryItem;
|
||||||
|
|
||||||
|
namespace Api {
|
||||||
|
struct WhoReadList;
|
||||||
|
} // namespace Api
|
||||||
|
|
||||||
namespace Window {
|
namespace Window {
|
||||||
class SessionController;
|
class SessionController;
|
||||||
} // namespace Window
|
} // namespace Window
|
||||||
|
@ -24,6 +28,7 @@ namespace HistoryView {
|
||||||
object_ptr<Ui::BoxContent> ReactionsListBox(
|
object_ptr<Ui::BoxContent> ReactionsListBox(
|
||||||
not_null<Window::SessionController*> window,
|
not_null<Window::SessionController*> window,
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
QString selected);
|
QString selected,
|
||||||
|
std::shared_ptr<Api::WhoReadList> whoReadIds = nullptr);
|
||||||
|
|
||||||
} // namespace HistoryView
|
} // namespace HistoryView
|
||||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "ui/rp_widget.h"
|
#include "ui/rp_widget.h"
|
||||||
#include "ui/abstract_button.h"
|
#include "ui/abstract_button.h"
|
||||||
|
#include "ui/controls/who_reacted_context_action.h"
|
||||||
#include "styles/style_widgets.h"
|
#include "styles/style_widgets.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
|
||||||
|
@ -19,6 +20,7 @@ not_null<Ui::AbstractButton*> CreateTab(
|
||||||
not_null<QWidget*> parent,
|
not_null<QWidget*> parent,
|
||||||
const style::MultiSelect &st,
|
const style::MultiSelect &st,
|
||||||
const QString &reaction,
|
const QString &reaction,
|
||||||
|
Ui::WhoReadType whoReadType,
|
||||||
int count,
|
int count,
|
||||||
rpl::producer<bool> selected) {
|
rpl::producer<bool> selected) {
|
||||||
struct State {
|
struct State {
|
||||||
|
@ -71,9 +73,19 @@ not_null<Ui::AbstractButton*> CreateTab(
|
||||||
const auto shift = (height - (size / factor)) / 2;
|
const auto shift = (height - (size / factor)) / 2;
|
||||||
Ui::Emoji::Draw(p, emoji, size, icon.x() + shift, shift);
|
Ui::Emoji::Draw(p, emoji, size, icon.x() + shift, shift);
|
||||||
} else {
|
} else {
|
||||||
(state->selected
|
using Type = Ui::WhoReadType;
|
||||||
? st::reactionsTabAllSelected
|
(reaction.isEmpty()
|
||||||
: st::reactionsTabAll).paintInCenter(p, icon);
|
? (state->selected
|
||||||
|
? st::reactionsTabAllSelected
|
||||||
|
: st::reactionsTabAll)
|
||||||
|
: (whoReadType == Type::Watched
|
||||||
|
|| whoReadType == Type::Listened)
|
||||||
|
? (state->selected
|
||||||
|
? st::reactionsTabPlayedSelected
|
||||||
|
: st::reactionsTabPlayed)
|
||||||
|
: (state->selected
|
||||||
|
? st::reactionsTabChecksSelected
|
||||||
|
: st::reactionsTabChecks)).paintInCenter(p, icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto textLeft = height + stm->padding.left();
|
const auto textLeft = height + stm->padding.left();
|
||||||
|
@ -91,23 +103,14 @@ not_null<Ui::AbstractButton*> CreateTab(
|
||||||
not_null<Selector*> CreateReactionSelector(
|
not_null<Selector*> CreateReactionSelector(
|
||||||
not_null<QWidget*> parent,
|
not_null<QWidget*> parent,
|
||||||
const base::flat_map<QString, int> &items,
|
const base::flat_map<QString, int> &items,
|
||||||
const QString &selected) {
|
const QString &selected,
|
||||||
|
Ui::WhoReadType whoReadType) {
|
||||||
struct State {
|
struct State {
|
||||||
rpl::variable<QString> selected;
|
rpl::variable<QString> selected;
|
||||||
std::vector<not_null<Ui::AbstractButton*>> tabs;
|
std::vector<not_null<Ui::AbstractButton*>> tabs;
|
||||||
};
|
};
|
||||||
const auto result = Ui::CreateChild<Selector>(parent.get());
|
const auto result = Ui::CreateChild<Selector>(parent.get());
|
||||||
using Entry = std::pair<int, QString>;
|
using Entry = std::pair<int, QString>;
|
||||||
auto sorted = std::vector<Entry>();
|
|
||||||
for (const auto &[reaction, count] : items) {
|
|
||||||
sorted.emplace_back(count, reaction);
|
|
||||||
}
|
|
||||||
ranges::sort(sorted, std::greater<>(), &Entry::first);
|
|
||||||
const auto count = ranges::accumulate(
|
|
||||||
sorted,
|
|
||||||
0,
|
|
||||||
std::plus<>(),
|
|
||||||
&Entry::first);
|
|
||||||
auto tabs = Ui::CreateChild<Ui::RpWidget>(parent.get());
|
auto tabs = Ui::CreateChild<Ui::RpWidget>(parent.get());
|
||||||
const auto st = &st::reactionsTabs;
|
const auto st = &st::reactionsTabs;
|
||||||
const auto state = tabs->lifetime().make_state<State>();
|
const auto state = tabs->lifetime().make_state<State>();
|
||||||
|
@ -118,6 +121,7 @@ not_null<Selector*> CreateReactionSelector(
|
||||||
tabs,
|
tabs,
|
||||||
*st,
|
*st,
|
||||||
reaction,
|
reaction,
|
||||||
|
whoReadType,
|
||||||
count,
|
count,
|
||||||
state->selected.value() | rpl::map(_1 == reaction));
|
state->selected.value() | rpl::map(_1 == reaction));
|
||||||
tab->setClickedCallback([=] {
|
tab->setClickedCallback([=] {
|
||||||
|
@ -125,6 +129,20 @@ not_null<Selector*> CreateReactionSelector(
|
||||||
});
|
});
|
||||||
state->tabs.push_back(tab);
|
state->tabs.push_back(tab);
|
||||||
};
|
};
|
||||||
|
auto sorted = std::vector<Entry>();
|
||||||
|
for (const auto &[reaction, count] : items) {
|
||||||
|
if (reaction == u"read"_q) {
|
||||||
|
append(reaction, count);
|
||||||
|
} else {
|
||||||
|
sorted.emplace_back(count, reaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ranges::sort(sorted, std::greater<>(), &Entry::first);
|
||||||
|
const auto count = ranges::accumulate(
|
||||||
|
sorted,
|
||||||
|
0,
|
||||||
|
std::plus<>(),
|
||||||
|
&Entry::first);
|
||||||
append(QString(), count);
|
append(QString(), count);
|
||||||
for (const auto &[count, reaction] : sorted) {
|
for (const auto &[count, reaction] : sorted) {
|
||||||
append(reaction, count);
|
append(reaction, count);
|
||||||
|
|
|
@ -7,6 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
enum class WhoReadType;
|
||||||
|
} // namespace Ui
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
|
|
||||||
struct Selector {
|
struct Selector {
|
||||||
|
@ -19,6 +23,7 @@ struct Selector {
|
||||||
not_null<Selector*> CreateReactionSelector(
|
not_null<Selector*> CreateReactionSelector(
|
||||||
not_null<QWidget*> parent,
|
not_null<QWidget*> parent,
|
||||||
const base::flat_map<QString, int> &items,
|
const base::flat_map<QString, int> &items,
|
||||||
const QString &selected);
|
const QString &selected,
|
||||||
|
Ui::WhoReadType whoReadType);
|
||||||
|
|
||||||
} // namespace HistoryView
|
} // namespace HistoryView
|
||||||
|
|
|
@ -926,6 +926,10 @@ whoReadReactionsDisabled: icon{{ "menu/read_reactions", menuFgDisabled }};
|
||||||
|
|
||||||
reactionsTabAll: icon {{ "menu/read_reactions", windowFg }};
|
reactionsTabAll: icon {{ "menu/read_reactions", windowFg }};
|
||||||
reactionsTabAllSelected: icon {{ "menu/read_reactions", activeButtonFg }};
|
reactionsTabAllSelected: icon {{ "menu/read_reactions", activeButtonFg }};
|
||||||
|
reactionsTabPlayed: icon {{ "menu/read_audio", windowFg }};
|
||||||
|
reactionsTabPlayedSelected: icon {{ "menu/read_audio", activeButtonFg }};
|
||||||
|
reactionsTabChecks: icon {{ "menu/read_ticks", windowFg }};
|
||||||
|
reactionsTabChecksSelected: icon {{ "menu/read_ticks", activeButtonFg }};
|
||||||
reactionsTabs: MultiSelect(defaultMultiSelect) {
|
reactionsTabs: MultiSelect(defaultMultiSelect) {
|
||||||
padding: margins(12px, 10px, 12px, 10px);
|
padding: margins(12px, 10px, 12px, 10px);
|
||||||
}
|
}
|
||||||
|
|
|
@ -352,6 +352,10 @@ void Action::paint(Painter &p) {
|
||||||
|
|
||||||
void Action::refreshText() {
|
void Action::refreshText() {
|
||||||
const auto usersCount = int(_content.participants.size());
|
const auto usersCount = int(_content.participants.size());
|
||||||
|
const auto onlySeenCount = ranges::count(
|
||||||
|
_content.participants,
|
||||||
|
QString(),
|
||||||
|
&WhoReadParticipant::reaction);
|
||||||
const auto count = std::max(_content.fullReactionsCount, usersCount);
|
const auto count = std::max(_content.fullReactionsCount, usersCount);
|
||||||
_text.setMarkedText(
|
_text.setMarkedText(
|
||||||
_st.itemStyle,
|
_st.itemStyle,
|
||||||
|
@ -365,7 +369,8 @@ void Action::refreshText() {
|
||||||
_content.fullReactionsCount,
|
_content.fullReactionsCount,
|
||||||
_content.fullReadCount)
|
_content.fullReadCount)
|
||||||
: (_content.type == WhoReadType::Reacted
|
: (_content.type == WhoReadType::Reacted
|
||||||
|| (count > 0 && _content.fullReactionsCount > usersCount))
|
|| (count > 0 && _content.fullReactionsCount > usersCount)
|
||||||
|
|| (count > 0 && onlySeenCount == 0))
|
||||||
? (count
|
? (count
|
||||||
? tr::lng_context_seen_reacted(
|
? tr::lng_context_seen_reacted(
|
||||||
tr::now,
|
tr::now,
|
||||||
|
|
Loading…
Add table
Reference in a new issue