diff --git a/Telegram/Resources/animations/collectible_phone.tgs b/Telegram/Resources/animations/collectible_phone.tgs
new file mode 100644
index 000000000..4e7284d5f
Binary files /dev/null and b/Telegram/Resources/animations/collectible_phone.tgs differ
diff --git a/Telegram/Resources/animations/collectible_username.tgs b/Telegram/Resources/animations/collectible_username.tgs
new file mode 100644
index 000000000..f93fd6d74
Binary files /dev/null and b/Telegram/Resources/animations/collectible_username.tgs differ
diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index d9e5dba0a..1dbe16fbb 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -471,6 +471,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_bio_placeholder" = "Bio";
+"lng_collectible_username_title" = "{username} is a collectible username that belongs to";
+"lng_collectible_username_info" = "This username was bought on **Fragment** on {date} for {price}";
+"lng_collectible_username_copy" = "Copy Link";
+"lng_collectible_phone_title" = "{phone} is a collectible phone number that belongs to";
+"lng_collectible_phone_info" = "This phone number was bought on **Fragment** on {date} for {price}";
+"lng_collectible_phone_copy" = "Copy Phone Number";
+"lng_collectible_learn_more" = "Learn More";
+
"lng_settings_section_info" = "My info";
"lng_settings_section_notify" = "Notifications";
diff --git a/Telegram/Resources/qrc/telegram/animations.qrc b/Telegram/Resources/qrc/telegram/animations.qrc
index b63b6d15f..5b72c461f 100644
--- a/Telegram/Resources/qrc/telegram/animations.qrc
+++ b/Telegram/Resources/qrc/telegram/animations.qrc
@@ -22,5 +22,7 @@
../../animations/hours.tgs
../../animations/phone.tgs
../../animations/chat_link.tgs
+ ../../animations/collectible_username.tgs
+ ../../animations/collectible_phone.tgs
diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index e53e7a5d7..b8919617d 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -796,7 +796,7 @@ QString ApiWrap::exportDirectStoryLink(not_null story) {
const auto storyId = story->fullId();
const auto peer = story->peer();
const auto fallback = [&] {
- const auto base = peer->userName();
+ const auto base = peer->username();
const auto story = QString::number(storyId.story);
const auto query = base + "/s/" + story;
return session().createInternalLinkFull(query);
diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style
index 2ae5bba8d..f64f2103d 100644
--- a/Telegram/SourceFiles/boxes/boxes.style
+++ b/Telegram/SourceFiles/boxes/boxes.style
@@ -1044,3 +1044,33 @@ inviteForbiddenTitle: FlatLabel(boxTitle) {
inviteForbiddenTitlePadding: margins(32px, 4px, 32px, 0px);
inviteForbiddenLockBg: dialogsUnreadBgMuted;
inviteForbiddenLockIcon: icon {{ "emoji/premium_lock", dialogsUnreadFg }};
+
+collectibleIconDiameter: 72px;
+collectibleIcon: 64px;
+collectibleIconPadding: margins(24px, 32px, 24px, 12px);
+collectibleHeader: FlatLabel(boxTitle) {
+ minWidth: 120px;
+ maxHeight: 0px;
+ align: align(top);
+}
+collectibleHeaderPadding: margins(24px, 16px, 24px, 12px);
+collectibleOwnerPadding: margins(24px, 4px, 24px, 8px);
+collectibleInfo: inviteForbiddenInfo;
+collectibleInfoPadding: margins(24px, 12px, 24px, 12px);
+collectibleInfoTonMargins: margins(0px, 3px, 0px, 0px);
+collectibleMore: RoundButton(defaultActiveButton) {
+ height: 36px;
+ textTop: 9px;
+ radius: 6px;
+}
+collectibleMorePadding: margins(24px, 12px, 24px, 0px);
+collectibleCopy: RoundButton(defaultLightButton) {
+ height: 36px;
+ textTop: 9px;
+ radius: 6px;
+}
+collectibleBox: Box(defaultBox) {
+ buttonPadding: margins(24px, 12px, 24px, 12px);
+ buttonHeight: 36px;
+ button: collectibleCopy;
+}
diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp
index 2430c583e..149171e5b 100644
--- a/Telegram/SourceFiles/boxes/peer_list_box.cpp
+++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp
@@ -1789,10 +1789,10 @@ crl::time PeerListContent::paintRow(
if (row->isSearchResult()
&& !_mentionHighlight.isEmpty()
&& peer
- && peer->userName().startsWith(
+ && peer->username().startsWith(
_mentionHighlight,
Qt::CaseInsensitive)) {
- const auto username = peer->userName();
+ const auto username = peer->username();
const auto availableWidth = statusw;
auto highlightedPart = '@' + username.mid(0, _mentionHighlight.size());
auto grayedPart = username.mid(_mentionHighlight.size());
diff --git a/Telegram/SourceFiles/boxes/peers/edit_linked_chat_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_linked_chat_box.cpp
index f8a7ea6e8..bae3d8a76 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_linked_chat_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_linked_chat_box.cpp
@@ -100,7 +100,7 @@ void Controller::prepare() {
return;
}
auto row = std::make_unique(chat);
- const auto username = chat->userName();
+ const auto username = chat->username();
row->setCustomStatus(!username.isEmpty()
? ('@' + username)
: (chat->isChannel() && !chat->isMegagroup())
diff --git a/Telegram/SourceFiles/boxes/peers/prepare_short_info_box.cpp b/Telegram/SourceFiles/boxes/peers/prepare_short_info_box.cpp
index a6b442aca..6c19d4055 100644
--- a/Telegram/SourceFiles/boxes/peers/prepare_short_info_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/prepare_short_info_box.cpp
@@ -207,7 +207,7 @@ void ProcessFullPhoto(
| UpdateFlag::Birthday)
) | rpl::map([=] {
const auto user = peer->asUser();
- const auto username = peer->userName();
+ const auto username = peer->username();
return PeerShortInfoFields{
.name = peer->name(),
.phone = user ? Ui::FormatPhone(user->phone()) : QString(),
diff --git a/Telegram/SourceFiles/boxes/premium_limits_box.cpp b/Telegram/SourceFiles/boxes/premium_limits_box.cpp
index 2a9c0d4b1..451957c21 100644
--- a/Telegram/SourceFiles/boxes/premium_limits_box.cpp
+++ b/Telegram/SourceFiles/boxes/premium_limits_box.cpp
@@ -322,7 +322,7 @@ void PublicsController::prepare() {
auto &owner = _navigation->session().data();
for (const auto &chat : chats) {
if (const auto peer = owner.processChat(chat)) {
- if (!peer->isChannel() || peer->userName().isEmpty()) {
+ if (!peer->isChannel() || peer->username().isEmpty()) {
continue;
}
appendRow(peer);
@@ -346,7 +346,7 @@ void PublicsController::rowRightActionClicked(not_null row) {
const auto text = textMethod(
tr::now,
lt_link,
- peer->session().createInternalLink(peer->userName()),
+ peer->session().createInternalLink(peer->username()),
lt_group,
peer->name());
const auto confirmText = tr::lng_channels_too_much_public_revoke(
@@ -389,7 +389,7 @@ std::unique_ptr PublicsController::createRow(
auto result = std::make_unique(peer);
result->setActionLink(tr::lng_channels_too_much_public_revoke(tr::now));
result->setCustomStatus(
- _navigation->session().createInternalLink(peer->userName()));
+ _navigation->session().createInternalLink(peer->username()));
return result;
}
diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp
index 7ddee02d2..58c7af3a6 100644
--- a/Telegram/SourceFiles/core/local_url_handlers.cpp
+++ b/Telegram/SourceFiles/core/local_url_handlers.cpp
@@ -897,6 +897,34 @@ bool ShowEditPersonalChannel(
return true;
}
+bool ShowCollectiblePhone(
+ Window::SessionController *controller,
+ const Match &match,
+ const QVariant &context) {
+ if (!controller) {
+ return false;
+ }
+ const auto phone = match->captured(1);
+ const auto peerId = PeerId(match->captured(2).toULongLong());
+ controller->resolveCollectible(
+ peerId,
+ phone.startsWith('+') ? phone : '+' + phone);
+ return true;
+}
+
+bool ShowCollectibleUsername(
+ Window::SessionController *controller,
+ const Match &match,
+ const QVariant &context) {
+ if (!controller) {
+ return false;
+ }
+ const auto username = match->captured(1);
+ const auto peerId = PeerId(match->captured(2).toULongLong());
+ controller->resolveCollectible(peerId, username);
+ return true;
+}
+
void ExportTestChatTheme(
not_null controller,
not_null theme) {
@@ -1299,6 +1327,14 @@ const std::vector &InternalUrlHandlers() {
u"^edit_personal_channel$"_q,
ShowEditPersonalChannel,
},
+ {
+ u"^collectible_phone/([\\+0-9\\-\\s]+)@([0-9]+)$"_q,
+ ShowCollectiblePhone,
+ },
+ {
+ u"^collectible_username/([a-zA-Z0-9\\-\\_\\.]+)@([0-9]+)$"_q,
+ ShowCollectibleUsername,
+ },
};
return Result;
}
diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp
index b32c85c53..b088be2d7 100644
--- a/Telegram/SourceFiles/data/data_channel.cpp
+++ b/Telegram/SourceFiles/data/data_channel.cpp
@@ -139,6 +139,10 @@ const std::vector &ChannelData::usernames() const {
return _username.usernames();
}
+bool ChannelData::isUsernameEditable(QString username) const {
+ return _username.isEditable(username);
+}
+
void ChannelData::setAccessHash(uint64 accessHash) {
access = accessHash;
input = MTP_inputPeerChannel(
diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h
index d22352020..6dbe84329 100644
--- a/Telegram/SourceFiles/data/data_channel.h
+++ b/Telegram/SourceFiles/data/data_channel.h
@@ -182,6 +182,7 @@ public:
[[nodiscard]] QString username() const;
[[nodiscard]] QString editableUsername() const;
[[nodiscard]] const std::vector &usernames() const;
+ [[nodiscard]] bool isUsernameEditable(QString username) const;
[[nodiscard]] int membersCount() const {
return std::max(_membersCount, 1);
diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp
index 76389df4e..9b5d6d379 100644
--- a/Telegram/SourceFiles/data/data_peer.cpp
+++ b/Telegram/SourceFiles/data/data_peer.cpp
@@ -940,7 +940,7 @@ const QString &PeerData::shortName() const {
return _name;
}
-QString PeerData::userName() const {
+QString PeerData::username() const {
if (const auto user = asUser()) {
return user->username();
} else if (const auto channel = asChannel()) {
@@ -949,6 +949,34 @@ QString PeerData::userName() const {
return QString();
}
+QString PeerData::editableUsername() const {
+ if (const auto user = asUser()) {
+ return user->editableUsername();
+ } else if (const auto channel = asChannel()) {
+ return channel->editableUsername();
+ }
+ return QString();
+}
+
+const std::vector &PeerData::usernames() const {
+ if (const auto user = asUser()) {
+ return user->usernames();
+ } else if (const auto channel = asChannel()) {
+ return channel->usernames();
+ }
+ static const auto kEmpty = std::vector();
+ return kEmpty;
+}
+
+bool PeerData::isUsernameEditable(QString username) const {
+ if (const auto user = asUser()) {
+ return user->isUsernameEditable(username);
+ } else if (const auto channel = asChannel()) {
+ return channel->isUsernameEditable(username);
+ }
+ return false;
+}
+
bool PeerData::changeColorIndex(uint8 index) {
index %= Ui::kColorIndexCount;
if (_colorIndexCloud && _colorIndex == index) {
diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h
index bf7d4e025..b7f98e599 100644
--- a/Telegram/SourceFiles/data/data_peer.h
+++ b/Telegram/SourceFiles/data/data_peer.h
@@ -279,7 +279,11 @@ public:
[[nodiscard]] const QString &name() const;
[[nodiscard]] const QString &shortName() const;
[[nodiscard]] const QString &topBarNameText() const;
- [[nodiscard]] QString userName() const;
+
+ [[nodiscard]] QString username() const;
+ [[nodiscard]] QString editableUsername() const;
+ [[nodiscard]] const std::vector &usernames() const;
+ [[nodiscard]] bool isUsernameEditable(QString username) const;
[[nodiscard]] const base::flat_set &nameWords() const {
return _nameWords;
diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp
index 6bfd2b74e..4614fae09 100644
--- a/Telegram/SourceFiles/data/data_session.cpp
+++ b/Telegram/SourceFiles/data/data_session.cpp
@@ -1230,7 +1230,7 @@ PeerData *Session::peerByUsername(const QString &username) const {
const auto uname = username.trimmed();
for (const auto &[peerId, peer] : _peers) {
if (peer->isLoaded()
- && !peer->userName().compare(uname, Qt::CaseInsensitive)) {
+ && !peer->username().compare(uname, Qt::CaseInsensitive)) {
return peer.get();
}
}
diff --git a/Telegram/SourceFiles/data/data_sponsored_messages.cpp b/Telegram/SourceFiles/data/data_sponsored_messages.cpp
index a856e6156..22cfbfeef 100644
--- a/Telegram/SourceFiles/data/data_sponsored_messages.cpp
+++ b/Telegram/SourceFiles/data/data_sponsored_messages.cpp
@@ -303,7 +303,7 @@ void SponsoredMessages::append(
? _session->data().processBotApp(peerId, *data.vapp())
: nullptr;
result.botLinkInfo = Window::PeerByLinkInfo{
- .usernameOrId = user->userName(),
+ .usernameOrId = user->username(),
.resolveType = botAppData
? Window::ResolveType::BotApp
: data.vstart_param()
diff --git a/Telegram/SourceFiles/data/data_story.cpp b/Telegram/SourceFiles/data/data_story.cpp
index d4b12de6a..cc0fefe99 100644
--- a/Telegram/SourceFiles/data/data_story.cpp
+++ b/Telegram/SourceFiles/data/data_story.cpp
@@ -450,7 +450,7 @@ bool Story::hasDirectLink() const {
if (!_privacyPublic || (!_pinned && expired())) {
return false;
}
- return !_peer->userName().isEmpty();
+ return !_peer->username().isEmpty();
}
std::optional Story::errorTextForForward(
diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp
index cffe8ad67..f4f8faccd 100644
--- a/Telegram/SourceFiles/data/data_user.cpp
+++ b/Telegram/SourceFiles/data/data_user.cpp
@@ -472,6 +472,10 @@ const std::vector &UserData::usernames() const {
return _username.usernames();
}
+bool UserData::isUsernameEditable(QString username) const {
+ return _username.isEditable(username);
+}
+
const QString &UserData::phone() const {
return _phone;
}
diff --git a/Telegram/SourceFiles/data/data_user.h b/Telegram/SourceFiles/data/data_user.h
index 7a8a22582..67da9e3f5 100644
--- a/Telegram/SourceFiles/data/data_user.h
+++ b/Telegram/SourceFiles/data/data_user.h
@@ -150,15 +150,11 @@ public:
// a full check by canShareThisContact() call.
[[nodiscard]] bool canShareThisContactFast() const;
- MTPInputUser inputUser = MTP_inputUserEmpty();
-
- QString firstName;
- QString lastName;
[[nodiscard]] const QString &phone() const;
[[nodiscard]] QString username() const;
[[nodiscard]] QString editableUsername() const;
[[nodiscard]] const std::vector &usernames() const;
- QString nameOrPhone;
+ [[nodiscard]] bool isUsernameEditable(QString username) const;
enum class ContactStatus : char {
Unknown,
@@ -186,8 +182,6 @@ public:
void setBirthday(Data::Birthday value);
void setBirthday(const tl::conditional &value);
- std::unique_ptr botInfo;
-
void setUnavailableReasons(
std::vector &&reasons);
@@ -209,6 +203,14 @@ public:
[[nodiscard]] MsgId personalChannelMessageId() const;
void setPersonalChannel(ChannelId channelId, MsgId messageId);
+ MTPInputUser inputUser = MTP_inputUserEmpty();
+
+ QString firstName;
+ QString lastName;
+ QString nameOrPhone;
+
+ std::unique_ptr botInfo;
+
private:
auto unavailableReasons() const
-> const std::vector & override;
diff --git a/Telegram/SourceFiles/data/data_user_names.cpp b/Telegram/SourceFiles/data/data_user_names.cpp
index b69756b5e..c2edd6b01 100644
--- a/Telegram/SourceFiles/data/data_user_names.cpp
+++ b/Telegram/SourceFiles/data/data_user_names.cpp
@@ -80,4 +80,10 @@ const std::vector &UsernamesInfo::usernames() const {
return _usernames;
}
+bool UsernamesInfo::isEditable(const QString &username) const {
+ return (_indexEditableUsername >= 0)
+ && (_indexEditableUsername < _usernames.size())
+ && (_usernames[_indexEditableUsername] == username);
+}
+
} // namespace Data
diff --git a/Telegram/SourceFiles/data/data_user_names.h b/Telegram/SourceFiles/data/data_user_names.h
index 03853b520..04912ced9 100644
--- a/Telegram/SourceFiles/data/data_user_names.h
+++ b/Telegram/SourceFiles/data/data_user_names.h
@@ -27,6 +27,7 @@ public:
[[nodiscard]] QString username() const;
[[nodiscard]] QString editableUsername() const;
[[nodiscard]] const std::vector &usernames() const;
+ [[nodiscard]] bool isEditable(const QString &username) const;
private:
std::vector _usernames;
diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
index 354a148eb..0da825209 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
@@ -1087,7 +1087,7 @@ void InnerWidget::paintPeerSearchResult(
QRect tr(context.st->textLeft, context.st->textTop, namewidth, st::dialogsTextFont->height);
p.setFont(st::dialogsTextFont);
- QString username = peer->userName();
+ QString username = peer->username();
if (!context.active && username.startsWith(_peerSearchQuery, Qt::CaseInsensitive)) {
auto first = '@' + username.mid(0, _peerSearchQuery.size());
auto second = username.mid(_peerSearchQuery.size());
@@ -4021,7 +4021,7 @@ void InnerWidget::setupShortcuts() {
const auto history = thread->owningHistory();
const auto isArchived = history->folder()
&& (history->folder()->id() == Data::Folder::kId);
-
+
Window::ToggleHistoryArchived(
_controller->uiShow(),
history,
diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp
index ab1f19958..5899117be 100644
--- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp
+++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp
@@ -496,7 +496,7 @@ auto GenerateParticipantString(
data,
});
}
- const auto username = peer->userName();
+ const auto username = peer->username();
if (username.isEmpty()) {
return name;
}
diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp
index 1a0cedb1b..494f58f9a 100644
--- a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp
+++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp
@@ -217,7 +217,18 @@ void AddRecipient(not_null box, const TextWithEntities &t) {
}
#endif
-[[nodiscard]] QImage IconCurrency(
+[[nodiscard]] QString FormatDate(const QDateTime &date) {
+ return tr::lng_group_call_starts_short_date(
+ tr::now,
+ lt_date,
+ langDayOfMonth(date.date()),
+ lt_time,
+ QLocale().toString(date.time(), QLocale::ShortFormat));
+}
+
+} // namespace
+
+QImage IconCurrency(
const style::FlatLabel &label,
const QColor &c) {
const auto s = Size(label.style.font->ascent);
@@ -234,17 +245,6 @@ void AddRecipient(not_null box, const TextWithEntities &t) {
return image;
}
-[[nodiscard]] QString FormatDate(const QDateTime &date) {
- return tr::lng_group_call_starts_short_date(
- tr::now,
- lt_date,
- langDayOfMonth(date.date()),
- lt_time,
- QLocale().toString(date.time(), QLocale::ShortFormat));
-}
-
-} // namespace
-
InnerWidget::InnerWidget(
QWidget *parent,
not_null controller,
diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.h b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.h
index 69d1587d8..99e9511be 100644
--- a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.h
+++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.h
@@ -23,6 +23,10 @@ namespace Info::ChannelEarn {
class Memento;
+[[nodiscard]] QImage IconCurrency(
+ const style::FlatLabel &label,
+ const QColor &c);
+
class InnerWidget final : public Ui::VerticalLayout {
public:
struct ShowRequest final {
diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp
index 19d7eebae..84d8daba3 100644
--- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp
+++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp
@@ -116,16 +116,25 @@ base::options::toggle ShowPeerIdBelowAbout({
[[nodiscard]] Fn UsernamesLinkCallback(
not_null peer,
- std::shared_ptr show,
+ not_null controller,
const QString &addToLink) {
+ const auto weak = base::make_weak(controller);
return [=](QString link) {
- if (!link.startsWith(u"https://"_q)) {
- link = peer->session().createInternalLinkFull(peer->userName())
+ if (link.startsWith(u"internal:"_q)) {
+ Core::App().openInternalUrl(link,
+ QVariant::fromValue(ClickHandlerContext{
+ .sessionWindow = weak,
+ }));
+ return;
+ } else if (!link.startsWith(u"https://"_q)) {
+ link = peer->session().createInternalLinkFull(peer->username())
+ addToLink;
}
if (!link.isEmpty()) {
QGuiApplication::clipboard()->setText(link);
- show->showToast(tr::lng_username_copied(tr::now));
+ if (const auto window = weak.get()) {
+ window->showToast(tr::lng_username_copied(tr::now));
+ }
}
};
}
@@ -1041,16 +1050,13 @@ object_ptr DetailsFiller::setupInfo() {
UsernameValue(user, true) | rpl::map([=](TextWithEntities u) {
return u.text.isEmpty()
? TextWithEntities()
- : Ui::Text::Link(
- u,
- user->session().createInternalLinkFull(
- u.text.mid(1)));
+ : Ui::Text::Link(u, UsernameUrl(user, u.text.mid(1)));
}),
QString(),
st::infoProfileLabeledUsernamePadding);
const auto callback = UsernamesLinkCallback(
_peer,
- controller->uiShow(),
+ controller,
QString());
const auto hook = [=](Ui::FlatLabel::ContextMenuRequest request) {
if (!request.link) {
@@ -1094,7 +1100,7 @@ object_ptr DetailsFiller::setupInfo() {
}, copyUsername->lifetime());
copyUsername->setClickedCallback([=] {
const auto link = user->session().createInternalLinkFull(
- user->userName());
+ user->username());
if (!link.isEmpty()) {
QGuiApplication::clipboard()->setText(link);
controller->showToast(tr::lng_username_copied(tr::now));
@@ -1159,7 +1165,7 @@ object_ptr DetailsFiller::setupInfo() {
const auto controller = _controller->parentController();
const auto linkCallback = UsernamesLinkCallback(
_peer,
- controller->uiShow(),
+ controller,
addToLink);
linkLine.text->overrideLinkClickHandler(linkCallback);
linkLine.subtext->overrideLinkClickHandler(linkCallback);
diff --git a/Telegram/SourceFiles/info/profile/info_profile_phone_menu.cpp b/Telegram/SourceFiles/info/profile/info_profile_phone_menu.cpp
index 58c51ed31..85fde827f 100644
--- a/Telegram/SourceFiles/info/profile/info_profile_phone_menu.cpp
+++ b/Telegram/SourceFiles/info/profile/info_profile_phone_menu.cpp
@@ -112,23 +112,22 @@ int TextItem::contentHeight() const {
} // namespace
-void AddPhoneMenu(not_null menu, not_null user) {
- if (user->isSelf()) {
- return;
- }
+bool IsCollectiblePhone(not_null user) {
using Strings = std::vector;
const auto prefixes = user->session().appConfig().get(
u"fragment_prefixes"_q,
- std::vector());
- {
- const auto proj = [&phone = user->phone()](const QString &p) {
- return phone.startsWith(p);
- };
- if (ranges::none_of(prefixes, proj)) {
- return;
- }
- }
- if (const auto url = AppConfig::FragmentLink(&user->session())) {
+ Strings{ u"888"_q });
+ const auto phone = user->phone();
+ const auto proj = [&](const QString &p) {
+ return phone.startsWith(p);
+ };
+ return ranges::any_of(prefixes, proj);
+}
+
+void AddPhoneMenu(not_null menu, not_null user) {
+ if (user->isSelf() || !IsCollectiblePhone(user)) {
+ return;
+ } else if (const auto url = AppConfig::FragmentLink(&user->session())) {
menu->addSeparator(&st::expandedMenuSeparator);
const auto link = Ui::Text::Link(
tr::lng_info_mobile_context_menu_fragment_about_link(tr::now),
diff --git a/Telegram/SourceFiles/info/profile/info_profile_phone_menu.h b/Telegram/SourceFiles/info/profile/info_profile_phone_menu.h
index 82ed6bb8c..25e6c8b5c 100644
--- a/Telegram/SourceFiles/info/profile/info_profile_phone_menu.h
+++ b/Telegram/SourceFiles/info/profile/info_profile_phone_menu.h
@@ -16,6 +16,8 @@ class PopupMenu;
namespace Info {
namespace Profile {
+[[nodiscard]] bool IsCollectiblePhone(not_null user);
+
void AddPhoneMenu(not_null menu, not_null user);
} // namespace Profile
diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.cpp b/Telegram/SourceFiles/info/profile/info_profile_values.cpp
index 9fe23da1f..6159fe907 100644
--- a/Telegram/SourceFiles/info/profile/info_profile_values.cpp
+++ b/Telegram/SourceFiles/info/profile/info_profile_values.cpp
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_chat_participants.h"
#include "apiwrap.h"
+#include "info/profile/info_profile_phone_menu.h"
#include "info/profile/info_profile_badge.h"
#include "core/application.h"
#include "core/click_handler_types.h"
@@ -55,7 +56,7 @@ auto PlainUsernameValue(not_null peer) {
peer->session().changes().peerFlagsValue(peer, UpdateFlag::Username),
peer->session().changes().peerFlagsValue(peer, UpdateFlag::Usernames)
) | rpl::map([=] {
- return peer->userName();
+ return peer->username();
});
}
@@ -136,14 +137,19 @@ rpl::producer PhoneOrHiddenValue(not_null user) {
PlainUsernameValue(user),
PlainAboutValue(user),
tr::lng_info_mobile_hidden()
- ) | rpl::map([](
+ ) | rpl::map([user](
const TextWithEntities &phone,
const QString &username,
const QString &about,
const QString &hidden) {
- return (phone.text.isEmpty() && username.isEmpty() && about.isEmpty())
- ? Ui::Text::WithEntities(hidden)
- : phone;
+ if (phone.text.isEmpty() && username.isEmpty() && about.isEmpty()) {
+ return Ui::Text::WithEntities(hidden);
+ } else if (IsCollectiblePhone(user)) {
+ return Ui::Text::Link(phone, u"internal:collectible_phone/"_q
+ + user->phone() + '@' + QString::number(user->id.value));
+ } else {
+ return phone;
+ }
});
}
@@ -160,15 +166,22 @@ rpl::producer UsernameValue(
}) | Ui::Text::ToWithEntities();
}
+QString UsernameUrl(not_null peer, const QString &username) {
+ return peer->isUsernameEditable(username)
+ ? peer->session().createInternalLinkFull(username)
+ : (u"internal:collectible_username/"_q
+ + username
+ + "@"
+ + QString::number(peer->id.value));
+}
+
rpl::producer> UsernamesValue(
not_null peer) {
const auto map = [=](const std::vector &usernames) {
return ranges::views::all(
usernames
) | ranges::views::transform([&](const QString &u) {
- return Ui::Text::Link(
- u,
- peer->session().createInternalLinkFull(u));
+ return Ui::Text::Link(u, UsernameUrl(peer, u));
}) | ranges::to_vector;
};
auto value = rpl::merge(
@@ -224,9 +237,7 @@ rpl::producer LinkValue(not_null peer, bool primary) {
? PlainPrimaryUsernameValue(peer)
: PlainUsernameValue(peer) | rpl::type_erased()
) | rpl::map([=](QString &&username) {
- return username.isEmpty()
- ? QString()
- : peer->session().createInternalLinkFull(username);
+ return username.isEmpty() ? QString() : UsernameUrl(peer, username);
});
}
diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.h b/Telegram/SourceFiles/info/profile/info_profile_values.h
index 6b7ccf475..625ccfa2a 100644
--- a/Telegram/SourceFiles/info/profile/info_profile_values.h
+++ b/Telegram/SourceFiles/info/profile/info_profile_values.h
@@ -61,6 +61,9 @@ rpl::producer> MigratedOrMeValue(
bool primary = false);
[[nodiscard]] rpl::producer> UsernamesValue(
not_null peer);
+[[nodiscard]] QString UsernameUrl(
+ not_null peer,
+ const QString &username);
[[nodiscard]] TextWithEntities AboutWithEntities(
not_null peer,
const QString &value);
diff --git a/Telegram/SourceFiles/main/main_domain.cpp b/Telegram/SourceFiles/main/main_domain.cpp
index cc6bd9457..aafbcd2ac 100644
--- a/Telegram/SourceFiles/main/main_domain.cpp
+++ b/Telegram/SourceFiles/main/main_domain.cpp
@@ -53,7 +53,7 @@ Domain::Domain(const QString &dataName)
: rpl::never();
}) | rpl::flatten_latest(
) | rpl::start_with_next([](const Data::PeerUpdate &update) {
- CrashReports::SetAnnotation("Username", update.peer->userName());
+ CrashReports::SetAnnotation("Username", update.peer->username());
}, _lifetime);
}
diff --git a/Telegram/SourceFiles/settings/settings_main.cpp b/Telegram/SourceFiles/settings/settings_main.cpp
index ffa288223..896a38a6b 100644
--- a/Telegram/SourceFiles/settings/settings_main.cpp
+++ b/Telegram/SourceFiles/settings/settings_main.cpp
@@ -224,7 +224,7 @@ void Cover::initViewers() {
}, lifetime());
_username->overrideLinkClickHandler([=] {
- const auto username = _user->userName();
+ const auto username = _user->username();
if (username.isEmpty()) {
_controller->show(Box(UsernamesBox, _user));
} else {
diff --git a/Telegram/SourceFiles/ui/boxes/collectible_info_box.cpp b/Telegram/SourceFiles/ui/boxes/collectible_info_box.cpp
new file mode 100644
index 000000000..4498becad
--- /dev/null
+++ b/Telegram/SourceFiles/ui/boxes/collectible_info_box.cpp
@@ -0,0 +1,271 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "ui/boxes/collectible_info_box.h"
+
+#include "base/unixtime.h"
+#include "core/file_utilities.h"
+#include "lang/lang_keys.h"
+#include "lottie/lottie_icon.h"
+#include "info/channel_statistics/earn/earn_format.h"
+#include "ui/layers/generic_box.h"
+#include "ui/text/format_values.h"
+#include "ui/text/text_utilities.h"
+#include "ui/widgets/buttons.h"
+#include "ui/widgets/labels.h"
+#include "ui/dynamic_image.h"
+#include "ui/painter.h"
+#include "settings/settings_common.h"
+#include "styles/style_boxes.h"
+#include "styles/style_layers.h"
+
+#include
+#include
+
+namespace Ui {
+namespace {
+
+constexpr auto kTonMultiplier = uint64(1000000000);
+
+[[nodiscard]] QString FormatEntity(CollectibleType type, QString entity) {
+ switch (type) {
+ case CollectibleType::Phone: {
+ static const auto kNonDigits = QRegularExpression(u"[^\\d]"_q);
+ entity.replace(kNonDigits, QString());
+ } return Ui::FormatPhone(entity);
+ case CollectibleType::Username:
+ return entity.startsWith('@') ? entity : ('@' + entity);
+ }
+ Unexpected("CollectibleType in FormatEntity.");
+}
+
+[[nodiscard]] QString FormatDate(TimeId date) {
+ return langDateTime(base::unixtime::parse(date));
+}
+
+[[nodiscard]] TextWithEntities FormatPrice(
+ const CollectibleInfo &info,
+ const CollectibleDetails &details) {
+ auto minor = Info::ChannelEarn::MinorPart(info.cryptoAmount);
+ if (minor.size() == 1 && minor.at(0) == '.') {
+ minor += '0';
+ }
+ auto price = (info.cryptoCurrency == u"TON"_q)
+ ? base::duplicate(
+ details.tonEmoji
+ ).append(
+ Info::ChannelEarn::MajorPart(info.cryptoAmount)
+ ).append(minor)
+ : TextWithEntities{ ('{'
+ + info.cryptoCurrency + ':' + QString::number(info.cryptoAmount)
+ + '}') };
+ const auto fiat = Ui::FillAmountAndCurrency(info.amount, info.currency);
+ return Ui::Text::Wrapped(
+ price,
+ EntityType::Bold
+ ).append(u" ("_q + fiat + ')');
+}
+
+[[nodiscard]] object_ptr MakeOwnerCell(
+ not_null parent,
+ const CollectibleInfo &info) {
+ const auto st = &st::defaultMultiSelectItem;
+ const auto size = st->height;
+ auto result = object_ptr(parent.get(), size);
+ const auto raw = result.data();
+
+ const auto name = info.ownerName;
+ const auto userpic = info.ownerUserpic;
+ const auto nameWidth = st->style.font->width(name);
+ const auto added = size + st->padding.left() + st->padding.right();
+ const auto subscribed = std::make_shared(false);
+ raw->paintRequest() | rpl::start_with_next([=] {
+ const auto use = std::min(nameWidth + added, raw->width());
+ const auto x = (raw->width() - use) / 2;
+ if (const auto available = use - added; available > 0) {
+ auto p = QPainter(raw);
+ auto hq = PainterHighQualityEnabler(p);
+ p.setPen(Qt::NoPen);
+ p.setBrush(st->textBg);
+ p.drawRoundedRect(x, 0, use, size, size / 2., size / 2.);
+
+ if (!*subscribed) {
+ *subscribed = true;
+ userpic->subscribeToUpdates([=] { raw->update(); });
+ }
+ p.drawImage(QRect(x, 0, size, size), userpic->image(size));
+
+ const auto textx = x + size + st->padding.left();
+ const auto texty = st->padding.top() + st->style.font->ascent;
+ const auto text = (use == nameWidth + added)
+ ? name
+ : st->style.font->elided(name, available);
+ p.setPen(st->textFg);
+ p.setFont(st->style.font);
+ p.drawText(textx, texty, text);
+ }
+ }, raw->lifetime());
+
+ return result;
+}
+
+} // namespace
+
+CollectibleType DetectCollectibleType(const QString &entity) {
+ return entity.startsWith('+')
+ ? CollectibleType::Phone
+ : CollectibleType::Username;
+}
+
+void CollectibleInfoBox(
+ not_null box,
+ CollectibleInfo info,
+ CollectibleDetails details) {
+ box->setWidth(st::boxWideWidth);
+ box->setStyle(st::collectibleBox);
+
+ const auto type = DetectCollectibleType(info.entity);
+
+ const auto icon = box->addRow(
+ object_ptr(box, st::collectibleIconDiameter),
+ st::collectibleIconPadding);
+ icon->paintRequest(
+ ) | rpl::start_with_next([=](QRect clip) {
+ const auto size = icon->height();
+ const auto inner = QRect(
+ (icon->width() - size) / 2,
+ 0,
+ size,
+ size);
+ if (!inner.intersects(clip)) {
+ return;
+ }
+ auto p = QPainter(icon);
+ auto hq = PainterHighQualityEnabler(p);
+ p.setBrush(st::defaultActiveButton.textBg);
+ p.setPen(Qt::NoPen);
+ p.drawEllipse(inner);
+ }, icon->lifetime());
+ const auto lottieSize = st::collectibleIcon;
+ auto lottie = Settings::CreateLottieIcon(
+ icon,
+ {
+ .name = (type == CollectibleType::Phone
+ ? u"collectible_phone"_q
+ : u"collectible_username"_q),
+ .color = &st::defaultActiveButton.textFg,
+ .sizeOverride = { lottieSize, lottieSize },
+ },
+ QMargins());
+ box->showFinishes(
+ ) | rpl::start_with_next([animate = std::move(lottie.animate)] {
+ animate(anim::repeat::once);
+ }, box->lifetime());
+ const auto animation = lottie.widget.release();
+ icon->sizeValue() | rpl::start_with_next([=](QSize size) {
+ const auto skip = (type == CollectibleType::Phone)
+ ? style::ConvertScale(2)
+ : 0;
+ animation->move(
+ (size.width() - animation->width()) / 2,
+ skip + (size.height() - animation->height()) / 2);
+ }, animation->lifetime());
+
+ const auto formatted = FormatEntity(type, info.entity);
+ const auto header = (type == CollectibleType::Phone)
+ ? tr::lng_collectible_phone_title(
+ tr::now,
+ lt_phone,
+ Ui::Text::Link(formatted),
+ Ui::Text::WithEntities)
+ : tr::lng_collectible_username_title(
+ tr::now,
+ lt_username,
+ Ui::Text::Link(formatted),
+ Ui::Text::WithEntities);
+ const auto copyCallback = [box, type, formatted, text = info.copyText] {
+ QGuiApplication::clipboard()->setText(
+ text.isEmpty() ? formatted : text);
+ box->uiShow()->showToast((type == CollectibleType::Phone)
+ ? tr::lng_text_copied(tr::now)
+ : tr::lng_username_copied(tr::now));
+ };
+ box->addRow(
+ object_ptr(
+ box,
+ rpl::single(header),
+ st::collectibleHeader),
+ st::collectibleHeaderPadding
+ )->setClickHandlerFilter([copyCallback](const auto &...) {
+ copyCallback();
+ return false;
+ });
+
+ box->addRow(MakeOwnerCell(box, info), st::collectibleOwnerPadding);
+
+ const auto text = ((type == CollectibleType::Phone)
+ ? tr::lng_collectible_phone_info
+ : tr::lng_collectible_username_info)(
+ tr::now,
+ lt_date,
+ TextWithEntities{ FormatDate(info.date) },
+ lt_price,
+ FormatPrice(info, details),
+ Ui::Text::RichLangValue);
+ const auto label = box->addRow(
+ object_ptr(box, st::collectibleInfo),
+ st::collectibleInfoPadding);
+ label->setAttribute(Qt::WA_TransparentForMouseEvents);
+ label->setMarkedText(text, details.tonEmojiContext());
+
+ const auto more = box->addRow(
+ object_ptr(
+ box,
+ tr::lng_collectible_learn_more(),
+ st::collectibleMore),
+ st::collectibleMorePadding);
+ more->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
+ more->setClickedCallback([url = info.url] {
+ File::OpenUrl(url);
+ });
+
+ const auto phrase = (type == CollectibleType::Phone)
+ ? tr::lng_collectible_phone_copy
+ : tr::lng_collectible_username_copy;
+ auto owned = object_ptr(
+ box,
+ phrase(),
+ st::collectibleCopy);
+ const auto copy = owned.data();
+ copy->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
+ copy->setClickedCallback(copyCallback);
+ box->addButton(std::move(owned));
+
+ box->setNoContentMargin(true);
+ const auto buttonsParent = box->verticalLayout().get();
+ const auto close = Ui::CreateChild(
+ buttonsParent,
+ st::boxTitleClose);
+ close->setClickedCallback([=] {
+ box->closeBox();
+ });
+ box->widthValue(
+ ) | rpl::start_with_next([=](int width) {
+ close->moveToRight(0, 0);
+ }, box->lifetime());
+
+ box->widthValue() | rpl::start_with_next([=](int width) {
+ more->setFullWidth(width
+ - st::collectibleMorePadding.left()
+ - st::collectibleMorePadding.right());
+ copy->setFullWidth(width
+ - st::collectibleBox.buttonPadding.left()
+ - st::collectibleBox.buttonPadding.right());
+ }, box->lifetime());
+}
+
+} // namespace Ui
diff --git a/Telegram/SourceFiles/ui/boxes/collectible_info_box.h b/Telegram/SourceFiles/ui/boxes/collectible_info_box.h
new file mode 100644
index 000000000..8b80b9650
--- /dev/null
+++ b/Telegram/SourceFiles/ui/boxes/collectible_info_box.h
@@ -0,0 +1,45 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+namespace Ui {
+
+class GenericBox;
+class DynamicImage;
+
+enum class CollectibleType {
+ Phone,
+ Username,
+};
+
+[[nodiscard]] CollectibleType DetectCollectibleType(const QString &entity);
+
+struct CollectibleInfo {
+ QString entity;
+ QString copyText;
+ std::shared_ptr ownerUserpic;
+ QString ownerName;
+ uint64 cryptoAmount = 0;
+ uint64 amount = 0;
+ QString cryptoCurrency;
+ QString currency;
+ QString url;
+ TimeId date = 0;
+};
+
+struct CollectibleDetails {
+ TextWithEntities tonEmoji;
+ Fn tonEmojiContext;
+};
+
+void CollectibleInfoBox(
+ not_null box,
+ CollectibleInfo info,
+ CollectibleDetails details);
+
+} // namespace Ui
diff --git a/Telegram/SourceFiles/ui/text/format_values.cpp b/Telegram/SourceFiles/ui/text/format_values.cpp
index dec5eb312..41e985723 100644
--- a/Telegram/SourceFiles/ui/text/format_values.cpp
+++ b/Telegram/SourceFiles/ui/text/format_values.cpp
@@ -357,6 +357,7 @@ CurrencyRule LookupCurrencyRule(const QString ¤cy) {
char do_decimal_point() const override { return decimal; }
char do_thousands_sep() const override { return thousands; }
+ std::string do_grouping() const override { return "\3"; }
char decimal = '.';
char thousands = ',';
diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp
index 0276c3771..d5c2d3fce 100644
--- a/Telegram/SourceFiles/window/window_main_menu.cpp
+++ b/Telegram/SourceFiles/window/window_main_menu.cpp
@@ -135,8 +135,8 @@ not_null AddMyChannelsBox(
const auto count = c ? c->membersCount() : g->count;
_status.setText(
st::defaultTextStyle,
- !p->userName().isEmpty()
- ? ('@' + p->userName())
+ !p->username().isEmpty()
+ ? ('@' + p->username())
: count
? tr::lng_chat_status_subscribers(
tr::now,
diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp
index 18c536cf3..86a5f53c1 100644
--- a/Telegram/SourceFiles/window/window_session_controller.cpp
+++ b/Telegram/SourceFiles/window/window_session_controller.cpp
@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/delete_messages_box.h"
#include "window/window_controller.h"
#include "window/window_filters_menu.h"
+#include "info/channel_statistics/earn/info_earn_inner_widget.h"
#include "info/info_memento.h"
#include "info/info_controller.h"
#include "inline_bots/bot_attach_web_view.h"
@@ -26,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_scheduled_section.h"
#include "media/player/media_player_instance.h"
#include "media/view/media_view_open_common.h"
+#include "data/stickers/data_custom_emoji.h"
#include "data/data_document_resolver.h"
#include "data/data_download_manager.h"
#include "data/data_session.h"
@@ -49,6 +51,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/shortcuts.h"
#include "core/application.h"
#include "core/click_handler_types.h"
+#include "core/ui_integration.h"
#include "base/unixtime.h"
#include "ui/controls/userpic_button.h"
#include "ui/text/text_utilities.h"
@@ -62,7 +65,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "calls/calls_instance.h" // Core::App().calls().inCall().
#include "calls/group/calls_group_call.h"
#include "ui/boxes/calendar_box.h"
+#include "ui/boxes/collectible_info_box.h"
#include "ui/boxes/confirm_box.h"
+#include "ui/dynamic_thumbnails.h"
#include "mainwidget.h"
#include "main/main_app_config.h"
#include "main/main_domain.h"
@@ -83,6 +88,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_premium.h"
#include "settings/settings_privacy_security.h"
#include "styles/style_window.h"
+#include "styles/style_boxes.h"
#include "styles/style_dialogs.h"
#include "styles/style_layers.h" // st::boxLabel
@@ -155,6 +161,47 @@ private:
return false;
}
+[[nodiscard]] Ui::CollectibleDetails PrepareCollectibleDetails(
+ not_null session) {
+ const auto makeContext = [=] {
+ return Core::MarkedTextContext{
+ .session = session,
+ .customEmojiRepaint = [] {},
+ };
+ };
+ return {
+ .tonEmoji = Ui::Text::SingleCustomEmoji(
+ session->data().customEmojiManager().registerInternalEmoji(
+ Info::ChannelEarn::IconCurrency(
+ st::collectibleInfo,
+ st::collectibleInfo.textFg->c),
+ st::collectibleInfoTonMargins,
+ true)),
+ .tonEmojiContext = makeContext,
+ };
+}
+
+[[nodiscard]] Ui::CollectibleInfo Parse(
+ const QString &entity,
+ not_null owner,
+ const MTPfragment_CollectibleInfo &info) {
+ const auto &data = info.data();
+ return {
+ .entity = entity,
+ .copyText = (entity.startsWith('+')
+ ? QString()
+ : owner->session().createInternalLinkFull(entity)),
+ .ownerUserpic = Ui::MakeUserpicThumbnail(owner, true),
+ .ownerName = owner->name(),
+ .cryptoAmount = data.vcrypto_amount().v,
+ .amount = data.vamount().v,
+ .cryptoCurrency = qs(data.vcrypto_currency()),
+ .currency = qs(data.vcurrency()),
+ .url = qs(data.vurl()),
+ .date = data.vpurchase_date().v,
+ };
+}
+
MainWindowShow::MainWindowShow(not_null controller)
: _window(base::make_weak(controller)) {
}
@@ -688,6 +735,36 @@ void SessionNavigation::resolveBoostState(not_null channel) {
}).send();
}
+void SessionNavigation::resolveCollectible(
+ PeerId ownerId,
+ const QString &entity,
+ Fn fail) {
+ if (_collectibleEntity == entity) {
+ return;
+ } else {
+ _api.request(base::take(_collectibleRequestId)).cancel();
+ }
+ _collectibleEntity = entity;
+ _collectibleRequestId = _api.request(MTPfragment_GetCollectibleInfo(
+ ((Ui::DetectCollectibleType(entity) == Ui::CollectibleType::Phone)
+ ? MTP_inputCollectiblePhone(MTP_string(entity))
+ : MTP_inputCollectibleUsername(MTP_string(entity)))
+ )).done([=](const MTPfragment_CollectibleInfo &result) {
+ const auto entity = base::take(_collectibleEntity);
+ _collectibleRequestId = 0;
+ uiShow()->show(Box(
+ Ui::CollectibleInfoBox,
+ Parse(entity, _session->data().peer(ownerId), result),
+ PrepareCollectibleDetails(_session)));
+ }).fail([=](const MTP::Error &error) {
+ _collectibleEntity = QString();
+ _collectibleRequestId = 0;
+ if (fail) {
+ fail(error.type());
+ }
+ }).send();
+}
+
void SessionNavigation::applyBoost(
not_null channel,
Fn done) {
diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h
index 811fb87a3..293dda20c 100644
--- a/Telegram/SourceFiles/window/window_session_controller.h
+++ b/Telegram/SourceFiles/window/window_session_controller.h
@@ -245,6 +245,11 @@ public:
void resolveBoostState(not_null channel);
+ void resolveCollectible(
+ PeerId ownerId,
+ const QString &entity,
+ Fn fail = nullptr);
+
base::weak_ptr showToast(
Ui::Toast::Config &&config);
base::weak_ptr showToast(
@@ -304,6 +309,9 @@ private:
ChannelData *_boostStateResolving = nullptr;
+ QString _collectibleEntity;
+ mtpRequestId _collectibleRequestId = 0;
+
};
class SessionController : public SessionNavigation {
diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake
index 3cd0defa7..3a8f8ef34 100644
--- a/Telegram/cmake/td_ui.cmake
+++ b/Telegram/cmake/td_ui.cmake
@@ -240,6 +240,8 @@ PRIVATE
ui/boxes/choose_language_box.h
ui/boxes/choose_time.cpp
ui/boxes/choose_time.h
+ ui/boxes/collectible_info_box.cpp
+ ui/boxes/collectible_info_box.h
ui/boxes/confirm_box.cpp
ui/boxes/confirm_box.h
ui/boxes/confirm_phone_box.cpp