mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-15 21:57:10 +02:00
Show recently joined by permanent link userpics.
This commit is contained in:
parent
8c7030378a
commit
be1afb4781
13 changed files with 300 additions and 7 deletions
BIN
Telegram/Resources/icons/info/edit/links_link.png
Normal file
BIN
Telegram/Resources/icons/info/edit/links_link.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 606 B |
BIN
Telegram/Resources/icons/info/edit/links_link@2x.png
Normal file
BIN
Telegram/Resources/icons/info/edit/links_link@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
BIN
Telegram/Resources/icons/info/edit/links_link@3x.png
Normal file
BIN
Telegram/Resources/icons/info/edit/links_link@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
BIN
Telegram/Resources/icons/info/edit/roundbtn_plus.png
Normal file
BIN
Telegram/Resources/icons/info/edit/roundbtn_plus.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 289 B |
BIN
Telegram/Resources/icons/info/edit/roundbtn_plus@2x.png
Normal file
BIN
Telegram/Resources/icons/info/edit/roundbtn_plus@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 460 B |
BIN
Telegram/Resources/icons/info/edit/roundbtn_plus@3x.png
Normal file
BIN
Telegram/Resources/icons/info/edit/roundbtn_plus@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 752 B |
|
@ -1203,7 +1203,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_group_invite_usage_about" = "You can make the link expire after it has been used for a certain number of times.";
|
||||
"lng_group_invite_usage_custom" = "Enter custom limit";
|
||||
|
||||
|
||||
"lng_channel_public_link_copied" = "Link copied to clipboard.";
|
||||
"lng_context_about_private_link" = "This link will only work for members of this chat.";
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ namespace {
|
|||
|
||||
constexpr auto kFirstPage = 10;
|
||||
constexpr auto kPerPage = 50;
|
||||
constexpr auto kJoinedFirstPage = 10;
|
||||
|
||||
void BringPermanentToFront(PeerInviteLinks &links) {
|
||||
auto &list = links.links;
|
||||
|
@ -145,7 +146,7 @@ void InviteLinks::performEdit(
|
|||
bool revoke,
|
||||
TimeId expireDate,
|
||||
int usageLimit) {
|
||||
const auto key = EditKey{ peer, link };
|
||||
const auto key = LinkKey{ peer, link };
|
||||
if (const auto i = _editCallbacks.find(key); i != end(_editCallbacks)) {
|
||||
if (done) {
|
||||
i->second.push_back(std::move(done));
|
||||
|
@ -250,6 +251,58 @@ void InviteLinks::requestLinks(not_null<PeerData*> peer) {
|
|||
_firstSliceRequests.emplace(peer, requestId);
|
||||
}
|
||||
|
||||
JoinedByLinkSlice InviteLinks::lookupJoinedFirstSlice(LinkKey key) const {
|
||||
const auto i = _firstJoined.find(key);
|
||||
return (i != end(_firstJoined)) ? i->second : JoinedByLinkSlice();
|
||||
}
|
||||
|
||||
rpl::producer<JoinedByLinkSlice> InviteLinks::joinedFirstSliceValue(
|
||||
not_null<PeerData*> peer,
|
||||
const QString &link,
|
||||
int fullCount) {
|
||||
const auto key = LinkKey{ peer, link };
|
||||
auto current = lookupJoinedFirstSlice(key);
|
||||
if (current.count == fullCount
|
||||
&& (!fullCount || !current.users.empty())) {
|
||||
return rpl::single(current);
|
||||
}
|
||||
current.count = fullCount;
|
||||
const auto remove = int(current.users.size()) - current.count;
|
||||
if (remove > 0) {
|
||||
current.users.erase(end(current.users) - remove, end(current.users));
|
||||
}
|
||||
requestJoinedFirstSlice(key);
|
||||
using namespace rpl::mappers;
|
||||
return rpl::single(
|
||||
current
|
||||
) | rpl::then(_joinedFirstSliceLoaded.events(
|
||||
) | rpl::filter(
|
||||
_1 == key
|
||||
) | rpl::map([=] {
|
||||
return lookupJoinedFirstSlice(key);
|
||||
}));
|
||||
}
|
||||
|
||||
void InviteLinks::requestJoinedFirstSlice(LinkKey key) {
|
||||
if (_firstJoinedRequests.contains(key)) {
|
||||
return;
|
||||
}
|
||||
const auto requestId = _api->request(MTPmessages_GetChatInviteImporters(
|
||||
key.peer->input,
|
||||
MTP_string(key.link),
|
||||
MTP_int(0), // offset_date
|
||||
MTP_inputUserEmpty(), // offset_user
|
||||
MTP_int(kJoinedFirstPage)
|
||||
)).done([=](const MTPmessages_ChatInviteImporters &result) {
|
||||
_firstJoinedRequests.remove(key);
|
||||
_firstJoined[key] = parseSlice(key.peer, result);
|
||||
_joinedFirstSliceLoaded.fire_copy(key);
|
||||
}).fail([=](const RPCError &error) {
|
||||
_firstJoinedRequests.remove(key);
|
||||
}).send();
|
||||
_firstJoinedRequests.emplace(key, requestId);
|
||||
}
|
||||
|
||||
void InviteLinks::setPermanent(
|
||||
not_null<PeerData*> peer,
|
||||
const MTPExportedChatInvite &invite) {
|
||||
|
@ -320,6 +373,27 @@ auto InviteLinks::parseSlice(
|
|||
return result;
|
||||
}
|
||||
|
||||
JoinedByLinkSlice InviteLinks::parseSlice(
|
||||
not_null<PeerData*> peer,
|
||||
const MTPmessages_ChatInviteImporters &slice) const {
|
||||
auto result = JoinedByLinkSlice();
|
||||
slice.match([&](const MTPDmessages_chatInviteImporters &data) {
|
||||
auto &owner = peer->session().data();
|
||||
owner.processUsers(data.vusers());
|
||||
result.count = data.vcount().v;
|
||||
result.users.reserve(data.vimporters().v.size());
|
||||
for (const auto importer : data.vimporters().v) {
|
||||
importer.match([&](const MTPDchatInviteImporter &data) {
|
||||
result.users.push_back({
|
||||
.user = owner.user(data.vuser_id().v),
|
||||
.date = data.vdate().v,
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
auto InviteLinks::parse(
|
||||
not_null<PeerData*> peer,
|
||||
const MTPExportedChatInvite &invite) const -> Link {
|
||||
|
|
|
@ -28,6 +28,16 @@ struct PeerInviteLinks {
|
|||
int count = 0;
|
||||
};
|
||||
|
||||
struct JoinedByLinkUser {
|
||||
not_null<UserData*> user;
|
||||
TimeId date = 0;
|
||||
};
|
||||
|
||||
struct JoinedByLinkSlice {
|
||||
std::vector<JoinedByLinkUser> users;
|
||||
int count = 0;
|
||||
};
|
||||
|
||||
class InviteLinks final {
|
||||
public:
|
||||
explicit InviteLinks(not_null<ApiWrap*> api);
|
||||
|
@ -59,21 +69,29 @@ public:
|
|||
void requestLinks(not_null<PeerData*> peer);
|
||||
[[nodiscard]] const Links &links(not_null<PeerData*> peer) const;
|
||||
|
||||
[[nodiscard]] rpl::producer<JoinedByLinkSlice> joinedFirstSliceValue(
|
||||
not_null<PeerData*> peer,
|
||||
const QString &link,
|
||||
int fullCount);
|
||||
|
||||
void requestMoreLinks(
|
||||
not_null<PeerData*> peer,
|
||||
const QString &last,
|
||||
Fn<void(Links)> done);
|
||||
|
||||
private:
|
||||
struct EditKey {
|
||||
struct LinkKey {
|
||||
not_null<PeerData*> peer;
|
||||
QString link;
|
||||
|
||||
friend inline bool operator<(const EditKey &a, const EditKey &b) {
|
||||
friend inline bool operator<(const LinkKey &a, const LinkKey &b) {
|
||||
return (a.peer == b.peer)
|
||||
? (a.link < b.link)
|
||||
: (a.peer < b.peer);
|
||||
}
|
||||
friend inline bool operator==(const LinkKey &a, const LinkKey &b) {
|
||||
return (a.peer == b.peer) && (a.link == b.link);
|
||||
}
|
||||
};
|
||||
|
||||
[[nodiscard]] Links parseSlice(
|
||||
|
@ -82,6 +100,9 @@ private:
|
|||
[[nodiscard]] Link parse(
|
||||
not_null<PeerData*> peer,
|
||||
const MTPExportedChatInvite &invite) const;
|
||||
[[nodiscard]] JoinedByLinkSlice parseSlice(
|
||||
not_null<PeerData*> peer,
|
||||
const MTPmessages_ChatInviteImporters &slice) const;
|
||||
[[nodiscard]] Link *lookupPermanent(not_null<PeerData*> peer);
|
||||
[[nodiscard]] Link *lookupPermanent(Links &links);
|
||||
[[nodiscard]] const Link *lookupPermanent(const Links &links) const;
|
||||
|
@ -108,15 +129,23 @@ private:
|
|||
TimeId expireDate = 0,
|
||||
int usageLimit = 0);
|
||||
|
||||
void requestJoinedFirstSlice(LinkKey key);
|
||||
[[nodiscard]] JoinedByLinkSlice lookupJoinedFirstSlice(
|
||||
LinkKey key) const;
|
||||
|
||||
const not_null<ApiWrap*> _api;
|
||||
|
||||
base::flat_map<not_null<PeerData*>, Links> _firstSlices;
|
||||
base::flat_map<not_null<PeerData*>, mtpRequestId> _firstSliceRequests;
|
||||
|
||||
base::flat_map<LinkKey, JoinedByLinkSlice> _firstJoined;
|
||||
base::flat_map<LinkKey, mtpRequestId> _firstJoinedRequests;
|
||||
rpl::event_stream<LinkKey> _joinedFirstSliceLoaded;
|
||||
|
||||
base::flat_map<
|
||||
not_null<PeerData*>,
|
||||
std::vector<Fn<void(Link)>>> _createCallbacks;
|
||||
base::flat_map<EditKey, std::vector<Fn<void(Link)>>> _editCallbacks;
|
||||
base::flat_map<LinkKey, std::vector<Fn<void(Link)>>> _editCallbacks;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -8,15 +8,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "boxes/peers/edit_peer_invite_links.h"
|
||||
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_user.h"
|
||||
#include "main/main_session.h"
|
||||
#include "api/api_invite_links.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "ui/abstract_button.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/controls/invite_link_label.h"
|
||||
#include "ui/controls/invite_link_buttons.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "history/view/history_view_group_call_tracker.h" // GenerateUs...
|
||||
#include "lang/lang_keys.h"
|
||||
#include "apiwrap.h"
|
||||
#include "styles/style_info.h"
|
||||
|
@ -40,7 +42,8 @@ void AddPermanentLinkBlock(
|
|||
return link
|
||||
? std::make_tuple(link->link, link->usage)
|
||||
: std::make_tuple(QString(), 0);
|
||||
}) | rpl::start_spawning(container->lifetime());
|
||||
}) | rpl::distinct_until_changed(
|
||||
) | rpl::start_spawning(container->lifetime());
|
||||
|
||||
const auto copyLink = [=] {
|
||||
if (const auto link = computePermanentLink()) {
|
||||
|
@ -95,4 +98,84 @@ void AddPermanentLinkBlock(
|
|||
container,
|
||||
copyLink,
|
||||
shareLink);
|
||||
|
||||
struct JoinedState {
|
||||
QImage cachedUserpics;
|
||||
std::vector<HistoryView::UserpicInRow> list;
|
||||
int count = 0;
|
||||
bool allUserpicsLoaded = false;
|
||||
rpl::variable<Ui::JoinedCountContent> content;
|
||||
rpl::lifetime lifetime;
|
||||
};
|
||||
const auto state = container->lifetime().make_state<JoinedState>();
|
||||
const auto push = [=] {
|
||||
HistoryView::GenerateUserpicsInRow(
|
||||
state->cachedUserpics,
|
||||
state->list,
|
||||
st::inviteLinkUserpics,
|
||||
0);
|
||||
state->allUserpicsLoaded = ranges::all_of(
|
||||
state->list,
|
||||
[](const HistoryView::UserpicInRow &element) {
|
||||
return !element.peer->hasUserpic() || element.view->image();
|
||||
});
|
||||
state->content = Ui::JoinedCountContent{
|
||||
.count = state->count,
|
||||
.userpics = state->cachedUserpics
|
||||
};
|
||||
};
|
||||
std::move(
|
||||
value
|
||||
) | rpl::map([=](QString link, int usage) {
|
||||
return peer->session().api().inviteLinks().joinedFirstSliceValue(
|
||||
peer,
|
||||
link,
|
||||
usage);
|
||||
}) | rpl::flatten_latest(
|
||||
) | rpl::start_with_next([=](const Api::JoinedByLinkSlice &slice) {
|
||||
auto list = std::vector<HistoryView::UserpicInRow>();
|
||||
list.reserve(slice.users.size());
|
||||
for (const auto &item : slice.users) {
|
||||
const auto i = ranges::find(
|
||||
state->list,
|
||||
item.user,
|
||||
&HistoryView::UserpicInRow::peer);
|
||||
if (i != end(state->list)) {
|
||||
list.push_back(std::move(*i));
|
||||
} else {
|
||||
list.push_back({ item.user });
|
||||
}
|
||||
}
|
||||
state->count = slice.count;
|
||||
state->list = std::move(list);
|
||||
push();
|
||||
}, state->lifetime);
|
||||
|
||||
peer->session().downloaderTaskFinished(
|
||||
) | rpl::filter([=] {
|
||||
return !state->allUserpicsLoaded;
|
||||
}) | rpl::start_with_next([=] {
|
||||
auto pushing = false;
|
||||
state->allUserpicsLoaded = true;
|
||||
for (const auto &element : state->list) {
|
||||
if (!element.peer->hasUserpic()) {
|
||||
continue;
|
||||
} else if (element.peer->userpicUniqueKey(element.view)
|
||||
!= element.uniqueKey) {
|
||||
pushing = true;
|
||||
} else if (!element.view->image()) {
|
||||
state->allUserpicsLoaded = false;
|
||||
}
|
||||
}
|
||||
if (pushing) {
|
||||
push();
|
||||
}
|
||||
}, state->lifetime);
|
||||
|
||||
Ui::AddJoinedCountButton(
|
||||
container,
|
||||
state->content.value(),
|
||||
st::inviteLinkJoinedRowPadding
|
||||
)->setClickedCallback([=] {
|
||||
});
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ using "ui/basic.style";
|
|||
|
||||
using "boxes/boxes.style";
|
||||
using "ui/widgets/widgets.style";
|
||||
using "ui/chat/chat.style"; // GroupCallUserpics.
|
||||
|
||||
InfoToggle {
|
||||
color: color;
|
||||
|
@ -865,3 +866,12 @@ inviteLinkShare: RoundButton(inviteLinkCopy) {
|
|||
icon: icon {{ "info/edit/links_share", activeButtonFg }};
|
||||
iconOver: icon {{ "info/edit/links_share", activeButtonFgOver }};
|
||||
}
|
||||
inviteLinkUserpics: GroupCallUserpics {
|
||||
size: 28px;
|
||||
shift: 6px;
|
||||
stroke: 2px;
|
||||
align: align(left);
|
||||
}
|
||||
inviteLinkUserpicsSkip: 8px;
|
||||
inviteLinkJoinedFont: font(14px);
|
||||
inviteLinkJoinedRowPadding: margins(0px, 18px, 0px, 0px);
|
||||
|
|
|
@ -11,9 +11,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_info.h"
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
class JoinedCountButton final : public AbstractButton {
|
||||
public:
|
||||
using AbstractButton::AbstractButton;
|
||||
|
||||
void onStateChanged(State was, StateChangeSource source) override {
|
||||
update();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void AddCopyShareLinkButtons(
|
||||
not_null<VerticalLayout*> container,
|
||||
|
@ -47,4 +60,78 @@ void AddCopyShareLinkButtons(
|
|||
}, wrap->lifetime());
|
||||
}
|
||||
|
||||
not_null<AbstractButton*> AddJoinedCountButton(
|
||||
not_null<VerticalLayout*> container,
|
||||
rpl::producer<JoinedCountContent> content,
|
||||
style::margins padding) {
|
||||
struct State {
|
||||
JoinedCountContent content;
|
||||
QString phrase;
|
||||
int addedWidth = 0;
|
||||
};
|
||||
const auto wrap = container->add(
|
||||
object_ptr<Ui::FixedHeightWidget>(
|
||||
container,
|
||||
st::inviteLinkUserpics.size),
|
||||
padding);
|
||||
const auto result = CreateChild<JoinedCountButton>(wrap);
|
||||
const auto state = result->lifetime().make_state<State>();
|
||||
std::move(
|
||||
content
|
||||
) | rpl::start_with_next([=](JoinedCountContent &&content) {
|
||||
state->content = std::move(content);
|
||||
result->setAttribute(
|
||||
Qt::WA_TransparentForMouseEvents,
|
||||
!state->content.count);
|
||||
const auto &st = st::inviteLinkUserpics;
|
||||
const auto imageWidth = !state->content.userpics.isNull()
|
||||
? state->content.userpics.width() / style::DevicePixelRatio()
|
||||
: !state->content.count
|
||||
? 0
|
||||
: ((std::min(state->content.count, 3) - 1) * (st.size - st.shift)
|
||||
+ st.size);
|
||||
state->addedWidth = imageWidth
|
||||
? (imageWidth + st::inviteLinkUserpicsSkip)
|
||||
: 0;
|
||||
state->phrase = state->content.count
|
||||
? tr::lng_group_invite_joined(
|
||||
tr::now,
|
||||
lt_count_decimal,
|
||||
state->content.count)
|
||||
: tr::lng_group_invite_no_joined(tr::now);
|
||||
const auto fullWidth = st::inviteLinkJoinedFont->width(state->phrase)
|
||||
+ state->addedWidth;
|
||||
result->resize(fullWidth, st.size);
|
||||
result->move((wrap->width() - fullWidth) / 2, 0);
|
||||
result->update();
|
||||
}, result->lifetime());
|
||||
|
||||
result->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
auto p = QPainter(result);
|
||||
if (!state->content.userpics.isNull()) {
|
||||
p.drawImage(0, 0, state->content.userpics);
|
||||
}
|
||||
const auto &font = st::inviteLinkJoinedFont;
|
||||
p.setPen(state->content.count
|
||||
? st::defaultLinkButton.color
|
||||
: st::windowSubTextFg);
|
||||
p.setFont((result->isOver() || result->isDown())
|
||||
? font->underline()
|
||||
: font);
|
||||
const auto top = (result->height() - font->height) / 2;
|
||||
p.drawText(
|
||||
state->addedWidth,
|
||||
top + font->ascent,
|
||||
state->phrase);
|
||||
}, result->lifetime());
|
||||
|
||||
wrap->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
result->move((width - result->width()) / 2, 0);
|
||||
}, wrap->lifetime());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
namespace Ui {
|
||||
|
||||
class AbstractButton;
|
||||
class VerticalLayout;
|
||||
|
||||
void AddCopyShareLinkButtons(
|
||||
|
@ -16,4 +17,14 @@ void AddCopyShareLinkButtons(
|
|||
Fn<void()> copyLink,
|
||||
Fn<void()> shareLink);
|
||||
|
||||
struct JoinedCountContent {
|
||||
int count = 0;
|
||||
QImage userpics;
|
||||
};
|
||||
|
||||
not_null<AbstractButton*> AddJoinedCountButton(
|
||||
not_null<VerticalLayout*> container,
|
||||
rpl::producer<JoinedCountContent> content,
|
||||
style::margins padding);
|
||||
|
||||
} // namespace Ui
|
||||
|
|
Loading…
Add table
Reference in a new issue