Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7c14f17a7 | ||
|
|
0b6bd7075a | ||
|
|
148690d8b1 | ||
|
|
a422aec99a | ||
|
|
813d0501da | ||
|
|
db80096e6b | ||
|
|
cf896aeb13 | ||
|
|
76314e3c03 | ||
|
|
8959679b3c | ||
|
|
bb6c94ef4f | ||
|
|
fb9ce6d3a8 | ||
|
|
dac4389e37 | ||
|
|
a25b2e9700 | ||
|
|
699a7bdc58 | ||
|
|
4108debca0 | ||
|
|
8b2bbfba6a | ||
|
|
4f37343e8b | ||
|
|
2dcf40817e | ||
|
|
3eeb01be61 | ||
|
|
6a8a85e395 | ||
|
|
031233ea98 | ||
|
|
4b09050061 | ||
|
|
992c876930 | ||
|
|
a5ffd8b7cf | ||
|
|
5fdd4eba80 | ||
|
|
54ce85f8e6 | ||
|
|
0bfb0fd045 | ||
|
|
8ad2d3d39a | ||
|
|
b8a19b56b6 | ||
|
|
24b93a5eff | ||
|
|
127f651d5e | ||
|
|
e760a0983f | ||
|
|
3f2cb8f8c9 | ||
|
|
bcb6e9e1af | ||
|
|
847d66c973 | ||
|
|
5c797d1f31 | ||
|
|
f749616dd8 | ||
|
|
3cc92e01fe | ||
|
|
06f2b23687 | ||
|
|
6a167b33f5 | ||
|
|
850155b3be | ||
|
|
358e586801 | ||
|
|
54214ff2ad | ||
|
|
06fc813e95 | ||
|
|
0046bae53f | ||
|
|
aeb5e57061 | ||
|
|
a32b781e49 | ||
|
|
caef698e54 | ||
|
|
e9650385ad | ||
|
|
f55584b160 |
@@ -272,6 +272,8 @@ PRIVATE
|
||||
boxes/edit_caption_box.h
|
||||
boxes/edit_privacy_box.cpp
|
||||
boxes/edit_privacy_box.h
|
||||
boxes/gift_credits_box.cpp
|
||||
boxes/gift_credits_box.h
|
||||
boxes/gift_premium_box.cpp
|
||||
boxes/gift_premium_box.h
|
||||
boxes/language_box.cpp
|
||||
|
||||
@@ -618,9 +618,6 @@ var IV = {
|
||||
element.getAnimations().forEach(
|
||||
(animation) => animation.finish());
|
||||
},
|
||||
back: function () {
|
||||
window.history.back();
|
||||
},
|
||||
menuShown: function (shown) {
|
||||
var already = document.getElementById('menu_page_blocker');
|
||||
if (already && shown) {
|
||||
|
||||
@@ -1448,6 +1448,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_info_topic_title" = "Topic Info";
|
||||
"lng_profile_enable_notifications" = "Notifications";
|
||||
"lng_profile_send_message" = "Send Message";
|
||||
"lng_profile_open_app" = "Open App";
|
||||
"lng_profile_open_app_about" = "By launching this mini app, you agree to the {terms}.";
|
||||
"lng_profile_open_app_terms" = "Terms of Service for Mini Apps";
|
||||
"lng_info_add_as_contact" = "Add to contacts";
|
||||
"lng_profile_shared_media" = "Shared media";
|
||||
"lng_profile_suggest_photo" = "Suggest Profile Photo";
|
||||
@@ -1845,6 +1848,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_action_webview_data_done" = "You have just successfully transferred data from the «{text}» button to the bot.";
|
||||
"lng_action_gift_received" = "{user} sent you a gift for {cost}";
|
||||
"lng_action_gift_received_me" = "You sent to {user} a gift for {cost}";
|
||||
"lng_action_gift_received_anonymous" = "Unknown user sent you a gift for {cost}";
|
||||
"lng_action_suggested_photo_me" = "You suggested {user} to use this profile photo.";
|
||||
"lng_action_suggested_photo" = "{user} suggests you to use this profile photo.";
|
||||
"lng_action_suggested_photo_button" = "View Photo";
|
||||
@@ -2342,6 +2346,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_credits_summary_history_tab_out" = "Outgoing";
|
||||
"lng_credits_summary_history_entry_inner_in" = "In-App Purchase";
|
||||
"lng_credits_summary_balance" = "Balance";
|
||||
"lng_credits_gift_button" = "Gift Stars to Friends";
|
||||
"lng_credits_box_out_title" = "Confirm Your Purchase";
|
||||
"lng_credits_box_out_sure#one" = "Do you want to buy **\"{text}\"** in **{bot}** for **{count} Star**?";
|
||||
"lng_credits_box_out_sure#other" = "Do you want to buy **\"{text}\"** in **{bot}** for **{count} Stars**?";
|
||||
@@ -2357,6 +2362,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_credits_box_out_confirm#one" = "Confirm and Pay {emoji} {count} Star";
|
||||
"lng_credits_box_out_confirm#other" = "Confirm and Pay {emoji} {count} Stars";
|
||||
"lng_credits_box_out_about" = "Review the {link} for Stars.";
|
||||
"lng_credits_box_out_about_link" = "https://telegram.org/tos/stars";
|
||||
"lng_credits_media_done_title" = "Media Unlocked";
|
||||
"lng_credits_media_done_text#one" = "**{count} Star** transferred to {chat}.";
|
||||
"lng_credits_media_done_text#other" = "**{count} Stars** transferred to {chat}.";
|
||||
@@ -2369,6 +2375,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_credits_box_history_entry_play_market" = "Play Market";
|
||||
"lng_credits_box_history_entry_app_store" = "App Store";
|
||||
"lng_credits_box_history_entry_fragment" = "Fragment";
|
||||
"lng_credits_box_history_entry_anonymous" = "Unknown User";
|
||||
"lng_credits_box_history_entry_gift_name" = "Received Gift";
|
||||
"lng_credits_box_history_entry_gift_sent" = "Sent Gift";
|
||||
"lng_credits_box_history_entry_gift_out_about" = "With Stars, **{user}** will be able to unlock content and services on Telegram.\n{link}";
|
||||
"lng_credits_box_history_entry_gift_in_about" = "Use Stars to unlock content and services on Telegram. {link}";
|
||||
"lng_credits_box_history_entry_gift_about_link" = "See Examples {emoji}";
|
||||
"lng_credits_box_history_entry_gift_about_url" = "https://telegram.org/blog/telegram-stars";
|
||||
"lng_credits_box_history_entry_ads" = "Ads Platform";
|
||||
"lng_credits_box_history_entry_premium_bot" = "Stars Top-Up";
|
||||
"lng_credits_box_history_entry_via_premium_bot" = "Premium Bot";
|
||||
@@ -2384,6 +2397,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_credits_small_balance_about" = "Buy **Stars** and use them on **{bot}** and other miniapps.";
|
||||
"lng_credits_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars.";
|
||||
|
||||
"lng_credits_gift_title" = "Gift Telegram Stars";
|
||||
|
||||
"lng_location_title" = "Location";
|
||||
"lng_location_about" = "Display the location of your business on your account.";
|
||||
"lng_location_address" = "Enter Address";
|
||||
@@ -2854,6 +2869,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_gift_link_pending_toast" = "Only the recipient can see the link.";
|
||||
"lng_gift_link_pending_footer" = "This link hasn't been activated yet.";
|
||||
|
||||
"lng_gift_stars_title#one" = "{count} Star";
|
||||
"lng_gift_stars_title#other" = "{count} Stars";
|
||||
"lng_gift_stars_outgoing" = "With Stars, {user} will be able to unlock content and services on Telegram.";
|
||||
"lng_gift_stars_incoming" = "Use Stars to unlock content and services on Telegram.";
|
||||
|
||||
"lng_accounts_limit_title" = "Limit Reached";
|
||||
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected accounts.";
|
||||
"lng_accounts_limit1#other" = "You have reached the limit of **{count}** connected accounts.";
|
||||
@@ -2929,6 +2949,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_masks_has_been_archived" = "Mask pack has been archived.";
|
||||
"lng_masks_installed" = "Mask pack has been installed.";
|
||||
"lng_emoji_nothing_found" = "No emoji found";
|
||||
"lng_stickers_context_reorder" = "Reorder";
|
||||
"lng_stickers_context_edit_name" = "Edit name";
|
||||
"lng_stickers_context_delete" = "Delete sticker";
|
||||
"lng_stickers_context_delete_sure" = "Are you sure you want to delete the sticker from your sticker set?";
|
||||
"lng_stickers_box_edit_name_title" = "Edit Sticker Set Name";
|
||||
"lng_stickers_box_edit_name_about" = "Choose a name for your set.";
|
||||
"lng_stickers_creator_badge" = "edit";
|
||||
|
||||
"lng_in_dlg_photo" = "Photo";
|
||||
"lng_in_dlg_album" = "Album";
|
||||
@@ -3162,6 +3189,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_bot_add_to_side_menu_done" = "Bot added to the main menu.";
|
||||
"lng_bot_no_scan_qr" = "QR Codes for bots are not supported on Desktop. Please use one of Telegram's mobile apps.";
|
||||
"lng_bot_click_to_start" = "Click here to use this bot.";
|
||||
"lng_bot_status_users#one" = "{count} user";
|
||||
"lng_bot_status_users#other" = "{count} users";
|
||||
|
||||
"lng_typing" = "typing";
|
||||
"lng_user_typing" = "{user} is typing";
|
||||
@@ -5317,12 +5346,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_recent_none" = "Recent search results\nwill appear here.";
|
||||
"lng_recent_chats" = "Chats";
|
||||
"lng_recent_channels" = "Channels";
|
||||
"lng_recent_apps" = "Apps";
|
||||
"lng_channels_none_title" = "No channels yet...";
|
||||
"lng_channels_none_about" = "You are not currently subscribed to any channels.";
|
||||
"lng_channels_your_title" = "Channels you joined";
|
||||
"lng_channels_your_more" = "Show more";
|
||||
"lng_channels_your_less" = "Show less";
|
||||
"lng_channels_recommended" = "Recommended channels";
|
||||
"lng_bot_apps_your" = "Apps you use";
|
||||
"lng_bot_apps_popular" = "Popular apps";
|
||||
|
||||
"lng_font_box_title" = "Choose font family";
|
||||
"lng_font_default" = "Default";
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="5.2.6.0" />
|
||||
Version="5.3.0.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
@@ -37,6 +37,9 @@
|
||||
<Extensions>
|
||||
<uap3:Extension Category="windows.protocol">
|
||||
<uap3:Protocol Name="tg" Parameters="-- "%1"" />
|
||||
<uap3:Extension Category="windows.protocol">
|
||||
</uap3:Extension>
|
||||
<uap3:Protocol Name="tonsite" Parameters="-- "%1"" />
|
||||
</uap3:Extension>
|
||||
<desktop:Extension
|
||||
Category="windows.startupTask"
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,2,6,0
|
||||
PRODUCTVERSION 5,2,6,0
|
||||
FILEVERSION 5,3,0,0
|
||||
PRODUCTVERSION 5,3,0,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "5.2.6.0"
|
||||
VALUE "FileVersion", "5.3.0.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.2.6.0"
|
||||
VALUE "ProductVersion", "5.3.0.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,2,6,0
|
||||
PRODUCTVERSION 5,2,6,0
|
||||
FILEVERSION 5,3,0,0
|
||||
PRODUCTVERSION 5,3,0,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "5.2.6.0"
|
||||
VALUE "FileVersion", "5.3.0.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.2.6.0"
|
||||
VALUE "ProductVersion", "5.3.0.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -102,6 +102,7 @@ constexpr auto kTransactionsLimit = 100;
|
||||
: QDateTime(),
|
||||
.successLink = qs(tl.data().vtransaction_url().value_or_empty()),
|
||||
.in = (int64(tl.data().vstars().v) >= 0),
|
||||
.gift = tl.data().is_gift(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -133,12 +134,12 @@ rpl::producer<rpl::no_value, QString> CreditsTopupOptions::request() {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
using TLOption = MTPStarsTopupOption;
|
||||
_api.request(MTPpayments_GetStarsTopupOptions(
|
||||
)).done([=](const MTPVector<TLOption> &result) {
|
||||
_options = ranges::views::all(
|
||||
result.v
|
||||
) | ranges::views::transform([](const TLOption &option) {
|
||||
const auto giftBarePeerId = !_peer->isSelf() ? _peer->id.value : 0;
|
||||
|
||||
const auto optionsFromTL = [giftBarePeerId](const auto &options) {
|
||||
return ranges::views::all(
|
||||
options
|
||||
) | ranges::views::transform([=](const auto &option) {
|
||||
return Data::CreditTopupOption{
|
||||
.credits = option.data().vstars().v,
|
||||
.product = qs(
|
||||
@@ -146,12 +147,31 @@ rpl::producer<rpl::no_value, QString> CreditsTopupOptions::request() {
|
||||
.currency = qs(option.data().vcurrency()),
|
||||
.amount = option.data().vamount().v,
|
||||
.extended = option.data().is_extended(),
|
||||
.giftBarePeerId = giftBarePeerId,
|
||||
};
|
||||
}) | ranges::to_vector;
|
||||
consumer.put_done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
};
|
||||
const auto fail = [=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
};
|
||||
|
||||
if (_peer->isSelf()) {
|
||||
using TLOption = MTPStarsTopupOption;
|
||||
_api.request(MTPpayments_GetStarsTopupOptions(
|
||||
)).done([=](const MTPVector<TLOption> &result) {
|
||||
_options = optionsFromTL(result.v);
|
||||
consumer.put_done();
|
||||
}).fail(fail).send();
|
||||
} else if (const auto user = _peer->asUser()) {
|
||||
using TLOption = MTPStarsGiftOption;
|
||||
_api.request(MTPpayments_GetStarsGiftOptions(
|
||||
MTP_flags(MTPpayments_GetStarsGiftOptions::Flag::f_user_id),
|
||||
user->inputUser
|
||||
)).done([=](const MTPVector<TLOption> &result) {
|
||||
_options = optionsFromTL(result.v);
|
||||
consumer.put_done();
|
||||
}).fail(fail).send();
|
||||
}
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
|
||||
@@ -128,7 +128,7 @@ mtpRequestId EditMessage(
|
||||
}
|
||||
|
||||
if (updateRecentStickers) {
|
||||
api->requestRecentStickersForce(true);
|
||||
api->requestSpecialStickersForce(false, false, true);
|
||||
}
|
||||
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
|
||||
if constexpr (ErrorWithId<FailCallback>) {
|
||||
|
||||
@@ -36,7 +36,8 @@ MTPVector<MTPDocumentAttribute> ComposeSendingDocumentAttributes(
|
||||
MTP_double(document->duration() / 1000.),
|
||||
MTP_int(dimensions.width()),
|
||||
MTP_int(dimensions.height()),
|
||||
MTPint())); // preload_prefix_size
|
||||
MTPint(), // preload_prefix_size
|
||||
MTPdouble())); // video_start_ts
|
||||
} else {
|
||||
attributes.push_back(MTP_documentAttributeImageSize(
|
||||
MTP_int(dimensions.width()),
|
||||
|
||||
@@ -2585,7 +2585,7 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &resu
|
||||
void ApiWrap::updateStickers() {
|
||||
const auto now = crl::now();
|
||||
requestStickers(now);
|
||||
requestRecentStickers(now);
|
||||
requestRecentStickers(now, false);
|
||||
requestFavedStickers(now);
|
||||
requestFeaturedStickers(now);
|
||||
}
|
||||
@@ -2607,8 +2607,15 @@ void ApiWrap::updateCustomEmoji() {
|
||||
requestFeaturedEmoji(now);
|
||||
}
|
||||
|
||||
void ApiWrap::requestRecentStickersForce(bool attached) {
|
||||
requestRecentStickersWithHash(0, attached);
|
||||
void ApiWrap::requestSpecialStickersForce(
|
||||
bool faved,
|
||||
bool recent,
|
||||
bool attached) {
|
||||
if (faved) {
|
||||
requestFavedStickers(std::nullopt);
|
||||
} else if (recent || attached) {
|
||||
requestRecentStickers(std::nullopt, attached);
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::setGroupStickerSet(
|
||||
@@ -2761,18 +2768,17 @@ void ApiWrap::requestCustomEmoji(TimeId now) {
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ApiWrap::requestRecentStickers(TimeId now, bool attached) {
|
||||
const auto needed = attached
|
||||
? _session->data().stickers().recentAttachedUpdateNeeded(now)
|
||||
: _session->data().stickers().recentUpdateNeeded(now);
|
||||
void ApiWrap::requestRecentStickers(
|
||||
std::optional<TimeId> now,
|
||||
bool attached) {
|
||||
const auto needed = !now
|
||||
? true
|
||||
: attached
|
||||
? _session->data().stickers().recentAttachedUpdateNeeded(*now)
|
||||
: _session->data().stickers().recentUpdateNeeded(*now);
|
||||
if (!needed) {
|
||||
return;
|
||||
}
|
||||
requestRecentStickersWithHash(
|
||||
Api::CountRecentStickersHash(_session, attached), attached);
|
||||
}
|
||||
|
||||
void ApiWrap::requestRecentStickersWithHash(uint64 hash, bool attached) {
|
||||
const auto requestId = [=]() -> mtpRequestId & {
|
||||
return attached
|
||||
? _recentAttachedStickersUpdateRequest
|
||||
@@ -2795,7 +2801,7 @@ void ApiWrap::requestRecentStickersWithHash(uint64 hash, bool attached) {
|
||||
: MTPmessages_getRecentStickers::Flags(0);
|
||||
requestId() = request(MTPmessages_GetRecentStickers(
|
||||
MTP_flags(flags),
|
||||
MTP_long(hash)
|
||||
MTP_long(now ? Api::CountRecentStickersHash(_session, attached) : 0)
|
||||
)).done([=](const MTPmessages_RecentStickers &result) {
|
||||
finish();
|
||||
|
||||
@@ -2822,13 +2828,15 @@ void ApiWrap::requestRecentStickersWithHash(uint64 hash, bool attached) {
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ApiWrap::requestFavedStickers(TimeId now) {
|
||||
if (!_session->data().stickers().favedUpdateNeeded(now)
|
||||
|| _favedStickersUpdateRequest) {
|
||||
return;
|
||||
void ApiWrap::requestFavedStickers(std::optional<TimeId> now) {
|
||||
if (now) {
|
||||
if (!_session->data().stickers().favedUpdateNeeded(*now)
|
||||
|| _favedStickersUpdateRequest) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
_favedStickersUpdateRequest = request(MTPmessages_GetFavedStickers(
|
||||
MTP_long(Api::CountFavedStickersHash(_session))
|
||||
MTP_long(now ? Api::CountFavedStickersHash(_session) : 0)
|
||||
)).done([=](const MTPmessages_FavedStickers &result) {
|
||||
_session->data().stickers().setLastFavedUpdate(crl::now());
|
||||
_favedStickersUpdateRequest = 0;
|
||||
@@ -4204,7 +4212,7 @@ void ApiWrap::sendMediaWithRandomId(
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
if (done) done(true);
|
||||
if (updateRecentStickers) {
|
||||
requestRecentStickersForce(true);
|
||||
requestRecentStickers(std::nullopt, true);
|
||||
}
|
||||
}, [=](const MTP::Error &error, const MTP::Response &response) {
|
||||
if (done) done(false);
|
||||
|
||||
@@ -244,7 +244,10 @@ public:
|
||||
void updateSavedGifs();
|
||||
void updateMasks();
|
||||
void updateCustomEmoji();
|
||||
void requestRecentStickersForce(bool attached = false);
|
||||
void requestSpecialStickersForce(
|
||||
bool faved,
|
||||
bool recent,
|
||||
bool attached);
|
||||
void setGroupStickerSet(
|
||||
not_null<ChannelData*> megagroup,
|
||||
const StickerSetIdentifier &set);
|
||||
@@ -477,9 +480,10 @@ private:
|
||||
void requestStickers(TimeId now);
|
||||
void requestMasks(TimeId now);
|
||||
void requestCustomEmoji(TimeId now);
|
||||
void requestRecentStickers(TimeId now, bool attached = false);
|
||||
void requestRecentStickersWithHash(uint64 hash, bool attached = false);
|
||||
void requestFavedStickers(TimeId now);
|
||||
void requestRecentStickers(
|
||||
std::optional<TimeId> now,
|
||||
bool attached);
|
||||
void requestFavedStickers(std::optional<TimeId> now);
|
||||
void requestFeaturedStickers(TimeId now);
|
||||
void requestFeaturedEmoji(TimeId now);
|
||||
void requestSavedGifs(TimeId now);
|
||||
|
||||
180
Telegram/SourceFiles/boxes/gift_credits_box.cpp
Normal file
180
Telegram/SourceFiles/boxes/gift_credits_box.cpp
Normal file
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
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 "boxes/gift_credits_box.h"
|
||||
|
||||
#include "api/api_credits.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/session/session_show.h"
|
||||
#include "settings/settings_credits_graphics.h"
|
||||
#include "ui/controls/userpic_button.h"
|
||||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/effects/premium_stars_colored.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/widgets/label_with_custom_emoji.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_channel_earn.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_credits.h"
|
||||
#include "styles/style_giveaway.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_premium.h"
|
||||
|
||||
namespace Ui {
|
||||
|
||||
void GiftCreditsBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<PeerData*> peer,
|
||||
Fn<void()> gifted) {
|
||||
box->setStyle(st::creditsGiftBox);
|
||||
box->setNoContentMargin(true);
|
||||
box->addButton(tr::lng_create_group_back(), [=] { box->closeBox(); });
|
||||
|
||||
const auto content = box->setPinnedToTopContent(
|
||||
object_ptr<Ui::VerticalLayout>(box));
|
||||
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
const auto &stUser = st::premiumGiftsUserpicButton;
|
||||
const auto userpicWrap = content->add(
|
||||
object_ptr<Ui::CenterWrap<>>(
|
||||
content,
|
||||
object_ptr<Ui::UserpicButton>(content, peer, stUser)));
|
||||
userpicWrap->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
|
||||
{
|
||||
const auto widget = Ui::CreateChild<Ui::RpWidget>(content);
|
||||
using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
|
||||
const auto stars = widget->lifetime().make_state<ColoredMiniStars>(
|
||||
widget,
|
||||
false,
|
||||
Ui::Premium::MiniStars::Type::BiStars);
|
||||
stars->setColorOverride(Ui::Premium::CreditsIconGradientStops());
|
||||
widget->resize(
|
||||
st::boxWidth - stUser.photoSize,
|
||||
stUser.photoSize * 2);
|
||||
content->sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &size) {
|
||||
widget->moveToLeft(stUser.photoSize / 2, 0);
|
||||
const auto starsRect = Rect(widget->size());
|
||||
stars->setPosition(starsRect.topLeft());
|
||||
stars->setSize(starsRect.size());
|
||||
widget->lower();
|
||||
}, widget->lifetime());
|
||||
widget->paintRequest(
|
||||
) | rpl::start_with_next([=](const QRect &r) {
|
||||
auto p = QPainter(widget);
|
||||
p.fillRect(r, Qt::transparent);
|
||||
stars->paint(p);
|
||||
}, widget->lifetime());
|
||||
}
|
||||
{
|
||||
Ui::AddSkip(content);
|
||||
const auto arrow = Ui::Text::SingleCustomEmoji(
|
||||
peer->owner().customEmojiManager().registerInternalEmoji(
|
||||
st::topicButtonArrow,
|
||||
st::channelEarnLearnArrowMargins,
|
||||
false));
|
||||
auto link = tr::lng_credits_box_history_entry_gift_about_link(
|
||||
lt_emoji,
|
||||
rpl::single(arrow),
|
||||
Ui::Text::RichLangValue
|
||||
) | rpl::map([](TextWithEntities text) {
|
||||
return Ui::Text::Link(
|
||||
std::move(text),
|
||||
tr::lng_credits_box_history_entry_gift_about_url(tr::now));
|
||||
});
|
||||
content->add(
|
||||
object_ptr<Ui::CenterWrap<>>(
|
||||
content,
|
||||
Ui::CreateLabelWithCustomEmoji(
|
||||
content,
|
||||
tr::lng_credits_box_history_entry_gift_out_about(
|
||||
lt_user,
|
||||
rpl::single(TextWithEntities{ peer->shortName() }),
|
||||
lt_link,
|
||||
std::move(link),
|
||||
Ui::Text::RichLangValue),
|
||||
{ .session = &peer->session() },
|
||||
st::creditsBoxAbout)),
|
||||
st::boxRowPadding);
|
||||
}
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(box->verticalLayout());
|
||||
|
||||
Settings::FillCreditOptions(
|
||||
Main::MakeSessionShow(box->uiShow(), &peer->session()),
|
||||
box->verticalLayout(),
|
||||
peer,
|
||||
0,
|
||||
[=] { gifted(); box->uiShow()->hideLayer(); });
|
||||
|
||||
box->setPinnedToBottomContent(
|
||||
object_ptr<Ui::VerticalLayout>(box));
|
||||
}
|
||||
|
||||
void ShowGiftCreditsBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
Fn<void()> gifted) {
|
||||
|
||||
class Controller final : public ContactsBoxController {
|
||||
public:
|
||||
Controller(
|
||||
not_null<Main::Session*> session,
|
||||
Fn<void(not_null<PeerData*>)> choose)
|
||||
: ContactsBoxController(session)
|
||||
, _choose(std::move(choose)) {
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unique_ptr<PeerListRow> createRow(
|
||||
not_null<UserData*> user) override {
|
||||
if (user->isSelf()
|
||||
|| user->isBot()
|
||||
|| user->isServiceUser()
|
||||
|| user->isInaccessible()) {
|
||||
return nullptr;
|
||||
}
|
||||
return ContactsBoxController::createRow(user);
|
||||
}
|
||||
|
||||
void rowClicked(not_null<PeerListRow*> row) override {
|
||||
_choose(row->peer());
|
||||
}
|
||||
|
||||
private:
|
||||
const Fn<void(not_null<PeerData*>)> _choose;
|
||||
|
||||
};
|
||||
auto initBox = [=](not_null<PeerListBox*> peersBox) {
|
||||
peersBox->setTitle(tr::lng_credits_gift_title());
|
||||
peersBox->addButton(tr::lng_cancel(), [=] { peersBox->closeBox(); });
|
||||
};
|
||||
|
||||
const auto show = controller->uiShow();
|
||||
auto listController = std::make_unique<Controller>(
|
||||
&controller->session(),
|
||||
[=](not_null<PeerData*> peer) {
|
||||
show->showBox(Box(GiftCreditsBox, peer, gifted));
|
||||
});
|
||||
show->showBox(
|
||||
Box<PeerListBox>(std::move(listController), std::move(initBox)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
20
Telegram/SourceFiles/boxes/gift_credits_box.h
Normal file
20
Telegram/SourceFiles/boxes/gift_credits_box.h
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
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 Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace Ui {
|
||||
|
||||
void ShowGiftCreditsBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
Fn<void()> gifted);
|
||||
|
||||
} // namespace Ui
|
||||
@@ -1694,9 +1694,13 @@ void AddCreditsHistoryEntryTable(
|
||||
} else if (entry.peerType == Type::Fragment) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_via(),
|
||||
tr::lng_credits_box_history_entry_fragment(
|
||||
Ui::Text::RichLangValue));
|
||||
(entry.gift
|
||||
? tr::lng_credits_box_history_entry_peer_in
|
||||
: tr::lng_credits_box_history_entry_via)(),
|
||||
(entry.gift
|
||||
? tr::lng_credits_box_history_entry_anonymous
|
||||
: tr::lng_credits_box_history_entry_fragment)(
|
||||
Ui::Text::RichLangValue));
|
||||
} else if (entry.peerType == Type::Ads) {
|
||||
AddTableRow(
|
||||
table,
|
||||
|
||||
@@ -7,50 +7,55 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/sticker_set_box.h"
|
||||
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "menu/menu_send.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "api/api_common.h"
|
||||
#include "api/api_toggling_media.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "chat_helpers/compose/compose_show.h"
|
||||
#include "chat_helpers/stickers_list_widget.h"
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
#include "core/application.h"
|
||||
#include "mtproto/sender.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "dialogs/ui/dialogs_layout.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/widgets/gradient_round_button.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/image/image_location_factory.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/text/custom_emoji_instance.h"
|
||||
#include "info/channel_statistics/boosts/giveaway/boost_badge.h" // InfiniteRadialAnimationWidget.
|
||||
#include "lang/lang_keys.h"
|
||||
#include "lottie/lottie_animation.h"
|
||||
#include "lottie/lottie_multi_player.h"
|
||||
#include "main/main_session.h"
|
||||
#include "mainwindow.h"
|
||||
#include "media/clip/media_clip_reader.h"
|
||||
#include "menu/menu_send.h"
|
||||
#include "mtproto/sender.h"
|
||||
#include "settings/settings_premium.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/cached_round_corners.h"
|
||||
#include "ui/effects/animation_value_f.h"
|
||||
#include "ui/effects/path_shift_gradient.h"
|
||||
#include "ui/emoji_config.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/image/image_location_factory.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/power_saving.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/text/custom_emoji_instance.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "ui/widgets/gradient_round_button.h"
|
||||
#include "ui/widgets/menu/menu_add_action_callback.h"
|
||||
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/cached_round_corners.h"
|
||||
#include "lottie/lottie_multi_player.h"
|
||||
#include "lottie/lottie_animation.h"
|
||||
#include "chat_helpers/compose/compose_show.h"
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
#include "chat_helpers/stickers_list_widget.h"
|
||||
#include "media/clip/media_clip_reader.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "settings/settings_premium.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "main/main_session.h"
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_toggling_media.h"
|
||||
#include "api/api_common.h"
|
||||
#include "mainwidget.h"
|
||||
#include "mainwindow.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_info.h"
|
||||
@@ -68,10 +73,12 @@ constexpr auto kEmojiPerRow = 8;
|
||||
constexpr auto kMinRepaintDelay = crl::time(33);
|
||||
constexpr auto kMinAfterScrollDelay = crl::time(33);
|
||||
constexpr auto kGrayLockOpacity = 0.3;
|
||||
constexpr auto kStickerMoveDuration = crl::time(200);
|
||||
|
||||
using Data::StickersSet;
|
||||
using Data::StickersPack;
|
||||
using SetFlag = Data::StickersSetFlag;
|
||||
using TLStickerSet = MTPmessages_StickerSet;
|
||||
|
||||
[[nodiscard]] std::optional<QColor> ComputeImageColor(
|
||||
const style::icon &lockIcon,
|
||||
@@ -259,6 +266,20 @@ public:
|
||||
[[nodiscard]] rpl::producer<uint64> setArchived() const;
|
||||
[[nodiscard]] rpl::producer<> updateControls() const;
|
||||
|
||||
void setReorderState(bool enabled) {
|
||||
_dragging.enabled = enabled;
|
||||
if (enabled) {
|
||||
_shakeAnimation.init([=] { update(); });
|
||||
_shakeAnimation.start();
|
||||
} else {
|
||||
_shakeAnimation.stop();
|
||||
update();
|
||||
}
|
||||
}
|
||||
[[nodiscard]] bool reorderState() const {
|
||||
return _dragging.enabled;
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<Error> errors() const;
|
||||
|
||||
void archiveStickers();
|
||||
@@ -271,6 +292,12 @@ public:
|
||||
: Data::StickersType::Stickers;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool amSetCreator() const {
|
||||
return _amSetCreator;
|
||||
}
|
||||
|
||||
void applySet(const TLStickerSet &set);
|
||||
|
||||
~Inner();
|
||||
|
||||
protected:
|
||||
@@ -306,6 +333,11 @@ private:
|
||||
QPoint position,
|
||||
bool paused,
|
||||
crl::time now) const;
|
||||
void shakeTransform(
|
||||
QPainter &p,
|
||||
int index,
|
||||
QPoint position,
|
||||
crl::time now) const;
|
||||
void setupLottie(int index);
|
||||
void setupWebm(int index);
|
||||
void clipCallback(
|
||||
@@ -322,14 +354,19 @@ private:
|
||||
void startOverAnimation(int index, float64 from, float64 to);
|
||||
int stickerFromGlobalPos(const QPoint &p) const;
|
||||
|
||||
void gotSet(const MTPmessages_StickerSet &set);
|
||||
void installDone(const MTPmessages_StickerSetInstallResult &result);
|
||||
|
||||
void requestReorder(not_null<DocumentData*> document, int index);
|
||||
void fillDeleteStickerBox(not_null<Ui::GenericBox*> box, int index);
|
||||
|
||||
void chosen(
|
||||
int index,
|
||||
not_null<DocumentData*> sticker,
|
||||
Api::SendOptions options);
|
||||
|
||||
[[nodiscard]] QPoint posFromIndex(int index) const;
|
||||
[[nodiscard]] bool isDraggedAnimating() const;
|
||||
|
||||
not_null<Lottie::MultiPlayer*> getLottiePlayer();
|
||||
|
||||
void showPreview();
|
||||
@@ -366,6 +403,24 @@ private:
|
||||
TimeId _setInstallDate = TimeId(0);
|
||||
StickerType _setThumbnailType = StickerType::Webp;
|
||||
ImageWithLocation _setThumbnail;
|
||||
bool _amSetCreator = false;
|
||||
|
||||
struct {
|
||||
bool enabled = false;
|
||||
int index = -1;
|
||||
int lastSelected = -1;
|
||||
QPoint point;
|
||||
} _dragging;
|
||||
Ui::Animations::Basic _shakeAnimation;
|
||||
std::deque<Fn<void()>> _reorderRequests;
|
||||
std::optional<MTP::Sender> _apiReorder;
|
||||
|
||||
struct ShiftAnimation final {
|
||||
Ui::Animations::Simple animation;
|
||||
Ui::Animations::Simple yAnimation;
|
||||
int shift = 0;
|
||||
};
|
||||
base::flat_map<int, ShiftAnimation> _shiftAnimations;
|
||||
|
||||
const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
|
||||
mutable StickerPremiumMark _premiumMark;
|
||||
@@ -538,9 +593,112 @@ void StickerSetBox::updateTitleAndButtons() {
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
void ChangeSetNameBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Data::Session*> data,
|
||||
const StickerSetIdentifier &input,
|
||||
Fn<void(TLStickerSet)> done) {
|
||||
struct State final {
|
||||
rpl::variable<mtpRequestId> requestId = 0;
|
||||
Ui::RpWidget* saveButton = nullptr;
|
||||
};
|
||||
box->setTitle(tr::lng_stickers_box_edit_name_title());
|
||||
box->addRow(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_stickers_box_edit_name_about(),
|
||||
st::boxLabel));
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
|
||||
const auto wasName = [&] {
|
||||
const auto &sets = data->stickers().sets();
|
||||
const auto it = sets.find(input.id);
|
||||
return (it == sets.end()) ? QString() : it->second->title;
|
||||
}();
|
||||
const auto wrap = box->addRow(object_ptr<Ui::FixedHeightWidget>(
|
||||
box,
|
||||
st::editStickerSetNameField.heightMin));
|
||||
auto owned = object_ptr<Ui::InputField>(
|
||||
wrap,
|
||||
st::editStickerSetNameField,
|
||||
tr::lng_stickers_context_edit_name(),
|
||||
wasName);
|
||||
const auto field = owned.data();
|
||||
wrap->widthValue() | rpl::start_with_next([=](int width) {
|
||||
field->move(0, 0);
|
||||
field->resize(width, field->height());
|
||||
wrap->resize(width, field->height());
|
||||
}, wrap->lifetime());
|
||||
field->selectAll();
|
||||
constexpr auto kMaxSetNameLength = 50;
|
||||
field->setMaxLength(kMaxSetNameLength);
|
||||
Ui::AddLengthLimitLabel(field, kMaxSetNameLength, kMaxSetNameLength + 1);
|
||||
box->setFocusCallback([=] { field->setFocusFast(); });
|
||||
const auto close = crl::guard(box, [=] { box->closeBox(); });
|
||||
const auto save = [=, show = box->uiShow()] {
|
||||
if (state->requestId.current()) {
|
||||
return;
|
||||
}
|
||||
const auto text = field->getLastText().trimmed();
|
||||
if ((Ui::ComputeRealUnicodeCharactersCount(text) > kMaxSetNameLength)
|
||||
|| text.isEmpty()) {
|
||||
field->showError();
|
||||
return;
|
||||
}
|
||||
const auto buttonWidth = state->saveButton
|
||||
? state->saveButton->width()
|
||||
: 0;
|
||||
state->requestId = data->session().api().request(
|
||||
MTPstickers_RenameStickerSet(
|
||||
Data::InputStickerSet(input),
|
||||
MTP_string(text))
|
||||
).done([=](const TLStickerSet &result) {
|
||||
result.match([&](const MTPDmessages_stickerSet &d) {
|
||||
data->stickers().feedSetFull(d);
|
||||
data->stickers().notifyUpdated(Data::StickersType::Stickers);
|
||||
}, [](const auto &) {
|
||||
});
|
||||
done(result);
|
||||
close();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
show->showToast(error.type());
|
||||
close();
|
||||
}).send();
|
||||
if (state->saveButton) {
|
||||
state->saveButton->resizeToWidth(buttonWidth);
|
||||
}
|
||||
};
|
||||
|
||||
state->saveButton = box->addButton(
|
||||
rpl::conditional(
|
||||
state->requestId.value() | rpl::map(rpl::mappers::_1 > 0),
|
||||
rpl::single(QString()),
|
||||
tr::lng_box_done()),
|
||||
save);
|
||||
if (const auto saveButton = state->saveButton) {
|
||||
using namespace Info::Statistics;
|
||||
const auto loadingAnimation = InfiniteRadialAnimationWidget(
|
||||
saveButton,
|
||||
saveButton->height() / 2,
|
||||
&st::editStickerSetNameLoading);
|
||||
AddChildToWidgetCenter(saveButton, loadingAnimation);
|
||||
loadingAnimation->showOn(
|
||||
state->requestId.value() | rpl::map(rpl::mappers::_1 > 0));
|
||||
}
|
||||
box->addButton(tr::lng_cancel(), [=] {
|
||||
data->session().api().request(state->requestId.current()).cancel();
|
||||
close();
|
||||
});
|
||||
}
|
||||
|
||||
void StickerSetBox::updateButtons() {
|
||||
clearButtons();
|
||||
if (_inner->loaded()) {
|
||||
if (_inner->reorderState()) {
|
||||
addButton(tr::lng_box_done(), [=] {
|
||||
_inner->setReorderState(false);
|
||||
updateButtons();
|
||||
});
|
||||
} else if (_inner->loaded()) {
|
||||
const auto type = _inner->setType();
|
||||
const auto share = [=] {
|
||||
copyStickersLink();
|
||||
@@ -548,6 +706,34 @@ void StickerSetBox::updateButtons() {
|
||||
? tr::lng_stickers_copied_emoji(tr::now)
|
||||
: tr::lng_stickers_copied(tr::now));
|
||||
};
|
||||
const auto fillSetCreatorMenu = [&] {
|
||||
using Filler = Fn<void(not_null<Ui::PopupMenu*>)>;
|
||||
if (!_inner->amSetCreator()) {
|
||||
return Filler(nullptr);
|
||||
}
|
||||
const auto data = &_session->data();
|
||||
return Filler([=, show = _show, set = _set](
|
||||
not_null<Ui::PopupMenu*> menu) {
|
||||
const auto done = [inner = _inner](const TLStickerSet &set) {
|
||||
if (const auto raw = inner.data()) {
|
||||
raw->applySet(set);
|
||||
}
|
||||
};
|
||||
menu->addAction(
|
||||
tr::lng_stickers_context_edit_name(tr::now),
|
||||
[=] {
|
||||
show->showBox(Box(ChangeSetNameBox, data, set, done));
|
||||
},
|
||||
&st::menuIconEdit);
|
||||
menu->addAction(
|
||||
tr::lng_stickers_context_reorder(tr::now),
|
||||
[=] {
|
||||
_inner->setReorderState(true);
|
||||
updateButtons();
|
||||
},
|
||||
&st::menuIconManage);
|
||||
});
|
||||
}();
|
||||
if (_inner->notInstalled()) {
|
||||
if (!_session->premium()
|
||||
&& _session->premiumPossible()
|
||||
@@ -586,6 +772,9 @@ void StickerSetBox::updateButtons() {
|
||||
*menu = base::make_unique_q<Ui::PopupMenu>(
|
||||
top,
|
||||
st::popupMenuWithIcons);
|
||||
if (fillSetCreatorMenu) {
|
||||
fillSetCreatorMenu(*menu);
|
||||
}
|
||||
(*menu)->addAction(
|
||||
((type == Data::StickersType::Emoji)
|
||||
? tr::lng_stickers_share_emoji
|
||||
@@ -636,6 +825,9 @@ void StickerSetBox::updateButtons() {
|
||||
remove,
|
||||
&st::menuIconRemove);
|
||||
} else {
|
||||
if (fillSetCreatorMenu) {
|
||||
fillSetCreatorMenu(*menu);
|
||||
}
|
||||
(*menu)->addAction(
|
||||
(type == Data::StickersType::Masks
|
||||
? tr::lng_masks_archive_pack(tr::now)
|
||||
@@ -687,8 +879,8 @@ StickerSetBox::Inner::Inner(
|
||||
_api.request(MTPmessages_GetStickerSet(
|
||||
Data::InputStickerSet(_input),
|
||||
MTP_int(0) // hash
|
||||
)).done([=](const MTPmessages_StickerSet &result) {
|
||||
gotSet(result);
|
||||
)).done([=](const TLStickerSet &result) {
|
||||
applySet(result);
|
||||
}).fail([=] {
|
||||
_loaded = true;
|
||||
_errors.fire(Error::NotFound);
|
||||
@@ -704,7 +896,7 @@ StickerSetBox::Inner::Inner(
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) {
|
||||
void StickerSetBox::Inner::applySet(const TLStickerSet &set) {
|
||||
_pack.clear();
|
||||
_emoji.clear();
|
||||
_elements.clear();
|
||||
@@ -748,7 +940,9 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) {
|
||||
}
|
||||
});
|
||||
}
|
||||
data.vset().match([&](const MTPDstickerSet &set) {
|
||||
|
||||
{
|
||||
const auto &set = data.vset().data();
|
||||
_setTitle = _session->data().stickers().getSetTitle(
|
||||
set);
|
||||
_setShortName = qs(set.vshort_name());
|
||||
@@ -759,6 +953,7 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) {
|
||||
_setFlags = Data::ParseStickersSetFlags(set);
|
||||
_setInstallDate = set.vinstalled_date().value_or(0);
|
||||
_setThumbnailDocumentId = set.vthumb_document_id().value_or_empty();
|
||||
_amSetCreator = set.is_creator();
|
||||
_setThumbnail = [&] {
|
||||
if (const auto thumbs = set.vthumbs()) {
|
||||
for (const auto &thumb : thumbs->v) {
|
||||
@@ -791,7 +986,7 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) {
|
||||
set->emoji = _emoji;
|
||||
set->setThumbnail(_setThumbnail, _setThumbnailType);
|
||||
}
|
||||
});
|
||||
};
|
||||
}, [&](const MTPDmessages_stickerSetNotModified &data) {
|
||||
LOG(("API Error: Unexpected messages.stickerSetNotModified."));
|
||||
});
|
||||
@@ -932,11 +1127,100 @@ void StickerSetBox::Inner::mousePressEvent(QMouseEvent *e) {
|
||||
if (index < 0 || index >= _pack.size()) {
|
||||
return;
|
||||
}
|
||||
if (_dragging.enabled) {
|
||||
_previewTimer.cancel();
|
||||
if (isDraggedAnimating()) {
|
||||
return;
|
||||
}
|
||||
_dragging.index = index;
|
||||
_dragging.point = mapFromGlobal(QCursor::pos()) - posFromIndex(index);
|
||||
return;
|
||||
}
|
||||
_previewTimer.callOnce(QApplication::startDragTime());
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::mouseMoveEvent(QMouseEvent *e) {
|
||||
updateSelected();
|
||||
const auto draggedAnimating = isDraggedAnimating();
|
||||
if (_selected >= 0 && !draggedAnimating) {
|
||||
_dragging.lastSelected = _selected;
|
||||
}
|
||||
if (_dragging.index >= 0
|
||||
&& _dragging.index < _pack.size()
|
||||
&& _dragging.lastSelected >= 0
|
||||
&& !draggedAnimating) {
|
||||
for (auto i = 0; i < _pack.size(); i++) {
|
||||
if (i == _dragging.index) {
|
||||
continue;
|
||||
}
|
||||
auto &entry = _shiftAnimations[i];
|
||||
const auto wasShift = entry.shift;
|
||||
if ((i >= _dragging.index) && (i <= _dragging.lastSelected)) {
|
||||
if (entry.shift == 0) {
|
||||
entry.shift = -1;
|
||||
} else if (entry.shift == 1) {
|
||||
entry.shift = 0;
|
||||
}
|
||||
} else if ((i < _dragging.index)
|
||||
&& (i >= _dragging.lastSelected)) {
|
||||
if (entry.shift == 0) {
|
||||
entry.shift = 1;
|
||||
} else if (entry.shift == -1) {
|
||||
entry.shift = 0;
|
||||
}
|
||||
}
|
||||
if ((i < std::min(_dragging.index, _dragging.lastSelected))
|
||||
|| (i > std::max(_dragging.index, _dragging.lastSelected))) {
|
||||
entry.shift = 0;
|
||||
}
|
||||
if (wasShift != entry.shift) {
|
||||
const auto fromPoint = posFromIndex(i + wasShift);
|
||||
const auto toPoint = posFromIndex(i + entry.shift);
|
||||
const auto toX = float64(toPoint.x());
|
||||
const auto toY = float64(toPoint.y());
|
||||
const auto ratio = [&] {
|
||||
const auto fromX = entry.animation.value(toX);
|
||||
const auto ratioX = std::min(toX, fromX)
|
||||
/ std::max(toX, fromX);
|
||||
const auto fromY = entry.yAnimation.value(toY);
|
||||
const auto ratioY = std::min(toY, fromY)
|
||||
/ std::max(toY, fromY);
|
||||
return (ratioX == 1.)
|
||||
? ratioY
|
||||
: (ratioY == 1.)
|
||||
? ratioX
|
||||
: std::max(ratioX, ratioY);
|
||||
}();
|
||||
if (!entry.animation.animating()) {
|
||||
entry.animation.stop();
|
||||
entry.animation.start(
|
||||
[=] { update(); },
|
||||
fromPoint.x(),
|
||||
toX,
|
||||
kStickerMoveDuration);
|
||||
} else {
|
||||
entry.animation.change(
|
||||
toX,
|
||||
kStickerMoveDuration * (1. - ratio),
|
||||
anim::linear);
|
||||
}
|
||||
if (!entry.yAnimation.animating()) {
|
||||
entry.yAnimation.stop();
|
||||
entry.yAnimation.start(
|
||||
[=] { update(); },
|
||||
fromPoint.y(),
|
||||
toY,
|
||||
kStickerMoveDuration);
|
||||
} else {
|
||||
entry.yAnimation.change(
|
||||
toY,
|
||||
kStickerMoveDuration * (1. - ratio),
|
||||
anim::linear);
|
||||
}
|
||||
}
|
||||
}
|
||||
update();
|
||||
}
|
||||
if (_previewShown >= 0) {
|
||||
showPreviewAt(e->globalPos());
|
||||
}
|
||||
@@ -958,7 +1242,86 @@ void StickerSetBox::Inner::leaveEventHook(QEvent *e) {
|
||||
setSelected(-1);
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::requestReorder(
|
||||
not_null<DocumentData*> document,
|
||||
int index) {
|
||||
if (!_apiReorder) {
|
||||
_apiReorder.emplace(&_session->mtp());
|
||||
}
|
||||
_reorderRequests.emplace_back([document, index, this] {
|
||||
_apiReorder->request(
|
||||
MTPstickers_ChangeStickerPosition(
|
||||
document->mtpInput(),
|
||||
MTP_int(index))
|
||||
).done([this, document](const TLStickerSet &result) {
|
||||
result.match([&](const MTPDmessages_stickerSet &d) {
|
||||
document->owner().stickers().feedSetFull(d);
|
||||
document->owner().stickers().notifyUpdated(
|
||||
Data::StickersType::Stickers);
|
||||
}, [](const auto &) {
|
||||
});
|
||||
if (!_reorderRequests.empty()) {
|
||||
_reorderRequests.pop_front();
|
||||
}
|
||||
if (_reorderRequests.empty()) {
|
||||
// applySet(result); // Causes stickers blink.
|
||||
} else {
|
||||
_reorderRequests.front()();
|
||||
}
|
||||
}).fail([show = _show](const MTP::Error &error) {
|
||||
show->showToast(error.type());
|
||||
}).send();
|
||||
});
|
||||
if (_reorderRequests.size() == 1) {
|
||||
_reorderRequests.front()();
|
||||
}
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
|
||||
if (_dragging.index >= 0 && !isDraggedAnimating()) {
|
||||
const auto fromPos = mapFromGlobal(e->globalPos()) - _dragging.point;
|
||||
const auto toPos = posFromIndex(_dragging.lastSelected);
|
||||
const auto document = _pack[_dragging.index];
|
||||
const auto wasPosition = _dragging.index;
|
||||
const auto nowPosition = _dragging.lastSelected;
|
||||
const auto finish = [=, this] {
|
||||
requestReorder(document, nowPosition);
|
||||
base::reorder(_pack, wasPosition, nowPosition);
|
||||
base::reorder(_elements, wasPosition, nowPosition);
|
||||
_dragging = {};
|
||||
_dragging.enabled = true;
|
||||
_shiftAnimations.clear();
|
||||
};
|
||||
auto &entry = _shiftAnimations[_dragging.index];
|
||||
entry.animation.stop();
|
||||
entry.yAnimation.stop();
|
||||
entry.animation.start(
|
||||
[finish, toPos, this](float64 value) {
|
||||
const auto index = _dragging.index;
|
||||
if (value >= toPos.x()
|
||||
&& index >= 0
|
||||
&& !_shiftAnimations[index].yAnimation.animating()) {
|
||||
finish();
|
||||
}
|
||||
update();
|
||||
},
|
||||
fromPos.x(),
|
||||
toPos.x(),
|
||||
kStickerMoveDuration);
|
||||
entry.yAnimation.start(
|
||||
[finish, toPos, this](float64 value) {
|
||||
const auto index = _dragging.index;
|
||||
if (value >= toPos.y()
|
||||
&& index >= 0
|
||||
&& !_shiftAnimations[index].animation.animating()) {
|
||||
finish();
|
||||
}
|
||||
update();
|
||||
},
|
||||
fromPos.y(),
|
||||
toPos.y(),
|
||||
kStickerMoveDuration);
|
||||
}
|
||||
if (_previewShown >= 0) {
|
||||
_previewShown = -1;
|
||||
return;
|
||||
@@ -1061,6 +1424,20 @@ void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) {
|
||||
(isFaved
|
||||
? &st::menuIconUnfave
|
||||
: &st::menuIconFave));
|
||||
if (amSetCreator()) {
|
||||
const auto addAction = Ui::Menu::CreateAddActionCallback(
|
||||
_menu.get());
|
||||
addAction({
|
||||
.text = tr::lng_stickers_context_delete(tr::now),
|
||||
.handler = [index, this, show = _show] {
|
||||
show->showBox(Box([=](not_null<Ui::GenericBox*> box) {
|
||||
fillDeleteStickerBox(box, index);
|
||||
}));
|
||||
},
|
||||
.icon = &st::menuIconDeleteAttention,
|
||||
.isAttention = true,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (_menu->empty()) {
|
||||
_menu = nullptr;
|
||||
@@ -1069,6 +1446,129 @@ void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) {
|
||||
}
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::fillDeleteStickerBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
int index) {
|
||||
Expects(index >= 0 || index < _pack.size());
|
||||
const auto document = _pack[index];
|
||||
const auto weak = Ui::MakeWeak(this);
|
||||
const auto show = _show;
|
||||
|
||||
const auto container = box->verticalLayout();
|
||||
Ui::AddSkip(container);
|
||||
Ui::AddSkip(container);
|
||||
const auto line = container->add(object_ptr<Ui::RpWidget>(container));
|
||||
line->resize(line->width(), _singleSize.height());
|
||||
|
||||
const auto sticker = Ui::CreateChild<Ui::RpWidget>(line);
|
||||
auto &lifetime = sticker->lifetime();
|
||||
struct State final {
|
||||
rpl::variable<mtpRequestId> requestId = 0;
|
||||
Ui::RpWidget* saveButton = nullptr;
|
||||
};
|
||||
const auto state = lifetime.make_state<State>();
|
||||
sticker->resize(_singleSize);
|
||||
{
|
||||
const auto animation = lifetime.make_state<Ui::Animations::Basic>();
|
||||
animation->init([=] { sticker->update(); });
|
||||
animation->start();
|
||||
}
|
||||
sticker->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
auto p = Painter(sticker);
|
||||
if (const auto strong = weak.data()) {
|
||||
const auto paused = On(PowerSaving::kStickersPanel)
|
||||
|| show->paused(ChatHelpers::PauseReason::Layer);
|
||||
paintSticker(p, index, QPoint(), paused, crl::now());
|
||||
if (_lottiePlayer && !paused) {
|
||||
_lottiePlayer->markFrameShown();
|
||||
}
|
||||
}
|
||||
}, sticker->lifetime());
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
line,
|
||||
tr::lng_stickers_context_delete(),
|
||||
box->getDelegate()->style().title);
|
||||
line->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
sticker->moveToLeft(st::boxRowPadding.left(), 0);
|
||||
const auto skip = st::defaultBoxCheckbox.textPosition.x();
|
||||
label->resizeToWidth(width
|
||||
- rect::right(sticker)
|
||||
- skip
|
||||
- st::boxRowPadding.right());
|
||||
label->moveToLeft(
|
||||
rect::right(sticker) + skip,
|
||||
((sticker->height() - label->height()) / 2));
|
||||
}, label->lifetime());
|
||||
|
||||
sticker->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
label->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
Ui::AddSkip(container);
|
||||
Ui::AddSkip(container);
|
||||
|
||||
box->addRow(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
container,
|
||||
tr::lng_stickers_context_delete_sure(),
|
||||
st::boxLabel));
|
||||
const auto save = [=] {
|
||||
if (state->requestId.current()) {
|
||||
return;
|
||||
}
|
||||
const auto weakBox = Ui::MakeWeak(box);
|
||||
const auto buttonWidth = state->saveButton
|
||||
? state->saveButton->width()
|
||||
: 0;
|
||||
state->requestId = document->owner().session().api().request(
|
||||
MTPstickers_RemoveStickerFromSet(document->mtpInput()
|
||||
)).done([=](const TLStickerSet &result) {
|
||||
result.match([&](const MTPDmessages_stickerSet &d) {
|
||||
document->owner().stickers().feedSetFull(d);
|
||||
document->owner().stickers().notifyUpdated(
|
||||
Data::StickersType::Stickers);
|
||||
}, [](const auto &) {
|
||||
});
|
||||
if (const auto strong = weak.data()) {
|
||||
applySet(result);
|
||||
}
|
||||
if (const auto strongBox = weakBox.data()) {
|
||||
strongBox->closeBox();
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
if (const auto strongBox = weakBox.data()) {
|
||||
strongBox->uiShow()->showToast(error.type());
|
||||
}
|
||||
}).send();
|
||||
if (state->saveButton) {
|
||||
state->saveButton->resizeToWidth(buttonWidth);
|
||||
}
|
||||
};
|
||||
state->saveButton = box->addButton(
|
||||
rpl::conditional(
|
||||
state->requestId.value() | rpl::map(rpl::mappers::_1 > 0),
|
||||
rpl::single(QString()),
|
||||
tr::lng_selected_delete()),
|
||||
save,
|
||||
st::attentionBoxButton);
|
||||
if (const auto saveButton = state->saveButton) {
|
||||
using namespace Info::Statistics;
|
||||
const auto loadingAnimation = InfiniteRadialAnimationWidget(
|
||||
saveButton,
|
||||
saveButton->height() / 2,
|
||||
&st::editStickerSetNameLoading);
|
||||
AddChildToWidgetCenter(saveButton, loadingAnimation);
|
||||
loadingAnimation->showOn(
|
||||
state->requestId.value() | rpl::map(rpl::mappers::_1 > 0));
|
||||
}
|
||||
box->addButton(tr::lng_close(), [=] {
|
||||
document->owner().session().api().request(
|
||||
state->requestId.current()).cancel();
|
||||
box->closeBox();
|
||||
});
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::updateSelected() {
|
||||
auto selected = stickerFromGlobalPos(QCursor::pos());
|
||||
setSelected(setType() == Data::StickersType::Masks ? -1 : selected);
|
||||
@@ -1079,7 +1579,11 @@ void StickerSetBox::Inner::setSelected(int selected) {
|
||||
startOverAnimation(_selected, 1., 0.);
|
||||
_selected = selected;
|
||||
startOverAnimation(_selected, 0., 1.);
|
||||
setCursor(_selected >= 0 ? style::cur_pointer : style::cur_default);
|
||||
setCursor((_selected < 0)
|
||||
? style::cur_default
|
||||
: _dragging.enabled
|
||||
? style::cur_sizeall
|
||||
: style::cur_pointer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1101,6 +1605,24 @@ void StickerSetBox::Inner::showPreview() {
|
||||
showPreviewAt(QCursor::pos());
|
||||
}
|
||||
|
||||
QPoint StickerSetBox::Inner::posFromIndex(int index) const {
|
||||
return {
|
||||
_padding.left() + (index % _perRow) * _singleSize.width(),
|
||||
_padding.top() + (index / _perRow) * _singleSize.height(),
|
||||
};
|
||||
}
|
||||
|
||||
bool StickerSetBox::Inner::isDraggedAnimating() const {
|
||||
if (_dragging.index < 0) {
|
||||
return false;
|
||||
}
|
||||
const auto it = _shiftAnimations.find(_dragging.index);
|
||||
return (it == _shiftAnimations.end())
|
||||
? false
|
||||
: (it->second.animation.animating()
|
||||
|| it->second.yAnimation.animating());
|
||||
}
|
||||
|
||||
not_null<Lottie::MultiPlayer*> StickerSetBox::Inner::getLottiePlayer() {
|
||||
if (!_lottiePlayer) {
|
||||
_lottiePlayer = std::make_unique<Lottie::MultiPlayer>(
|
||||
@@ -1140,12 +1662,36 @@ void StickerSetBox::Inner::paintEvent(QPaintEvent *e) {
|
||||
|
||||
_pathGradient->startFrame(0, width(), width() / 2);
|
||||
|
||||
const auto indexUnderCursor = (_dragging.index >= 0
|
||||
&& _dragging.index < _elements.size())
|
||||
? stickerFromGlobalPos(QCursor::pos())
|
||||
: -2;
|
||||
const auto lastIndex = indexUnderCursor >= 0
|
||||
? indexUnderCursor
|
||||
: _dragging.lastSelected;
|
||||
|
||||
const auto now = crl::now();
|
||||
const auto paused = On(PowerSaving::kStickersPanel)
|
||||
|| _show->paused(ChatHelpers::PauseReason::Layer);
|
||||
for (int32 i = from; i < to; ++i) {
|
||||
for (int32 j = 0; j < _perRow; ++j) {
|
||||
int32 index = i * _perRow + j;
|
||||
|
||||
if (lastIndex >= 0) {
|
||||
if (_dragging.index == index) {
|
||||
continue;
|
||||
}
|
||||
const auto it = _shiftAnimations.find(index);
|
||||
if (it != _shiftAnimations.end()) {
|
||||
const auto &entry = it->second;
|
||||
const auto toPos = posFromIndex(index + entry.shift);
|
||||
const auto pos = QPoint(
|
||||
entry.animation.value(toPos.x()),
|
||||
entry.yAnimation.value(toPos.y()));
|
||||
paintSticker(p, index, pos, paused, now);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (index >= _elements.size()) {
|
||||
break;
|
||||
}
|
||||
@@ -1155,6 +1701,14 @@ void StickerSetBox::Inner::paintEvent(QPaintEvent *e) {
|
||||
paintSticker(p, index, pos, paused, now);
|
||||
}
|
||||
}
|
||||
if (_dragging.index >= 0 && _dragging.index < _elements.size()) {
|
||||
const auto pos = isDraggedAnimating()
|
||||
? QPoint(
|
||||
_shiftAnimations[_dragging.index].animation.value(0),
|
||||
_shiftAnimations[_dragging.index].yAnimation.value(0))
|
||||
: (mapFromGlobal(QCursor::pos()) - _dragging.point);
|
||||
paintSticker(p, _dragging.index, pos, paused, now);
|
||||
}
|
||||
|
||||
if (_lottiePlayer && !paused) {
|
||||
_lottiePlayer->markFrameShown();
|
||||
@@ -1310,18 +1864,99 @@ void StickerSetBox::Inner::customEmojiRepaint() {
|
||||
update();
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::shakeTransform(
|
||||
QPainter &p,
|
||||
int index,
|
||||
QPoint position,
|
||||
crl::time now) const {
|
||||
constexpr auto kShakeADuration = crl::time(400);
|
||||
constexpr auto kShakeXDuration = crl::time(kShakeADuration * 1.2);
|
||||
constexpr auto kShakeYDuration = kShakeADuration;
|
||||
const auto diff = ((index % 2) ? 0 : kShakeYDuration / 2)
|
||||
+ (now - _shakeAnimation.started());
|
||||
const auto pX = (diff % kShakeXDuration)
|
||||
/ float64(kShakeXDuration);
|
||||
const auto pY = (diff % kShakeYDuration)
|
||||
/ float64(kShakeYDuration);
|
||||
const auto pA = (diff % kShakeADuration)
|
||||
/ float64(kShakeADuration);
|
||||
|
||||
constexpr auto kMaxA = 2.;
|
||||
constexpr auto kMaxTranslation = .5;
|
||||
constexpr auto kAStep = 1. / 5;
|
||||
constexpr auto kXStep = 1. / 5;
|
||||
constexpr auto kYStep = 1. / 4;
|
||||
|
||||
// 0, -kMaxA, 0, kMaxA, 0.
|
||||
const auto angle = (pA < kAStep)
|
||||
? anim::interpolateF(0., -kMaxA, pA / kAStep)
|
||||
: (pA < kAStep * 2.)
|
||||
? anim::interpolateF(-kMaxA, 0, (pA - kAStep) / kAStep)
|
||||
: (pA < kAStep * 3.)
|
||||
? anim::interpolateF(0, kMaxA, (pA - kAStep * 2.) / kAStep)
|
||||
: (pA < kAStep * 4.)
|
||||
? anim::interpolateF(kMaxA, 0, (pA - kAStep * 3.) / kAStep)
|
||||
: anim::interpolateF(0, 0., (pA - kAStep * 4.) / kAStep);
|
||||
|
||||
// 0, kMaxTranslation, 0, -kMaxTranslation, 0.
|
||||
const auto x = (pX < kXStep)
|
||||
? anim::interpolateF(0., kMaxTranslation, pX / kXStep)
|
||||
: (pX < kXStep * 2.)
|
||||
? anim::interpolateF(kMaxTranslation, 0, (pX - kXStep) / kXStep)
|
||||
: (pX < kXStep * 3.)
|
||||
? anim::interpolateF(0, -kMaxTranslation, (pX - kXStep * 2.) / kXStep)
|
||||
: (pX < kXStep * 4.)
|
||||
? anim::interpolateF(-kMaxTranslation, 0, (pX - kXStep * 3.) / kXStep)
|
||||
: anim::interpolateF(0, 0., (pX - kXStep * 4.) / kXStep);
|
||||
|
||||
// 0, kMaxTranslation, -kMaxTranslation, 0.
|
||||
const auto y = (pY < kYStep)
|
||||
? anim::interpolateF(0., kMaxTranslation, pY / kYStep)
|
||||
: (pY < kYStep * 2.)
|
||||
? anim::interpolateF(kMaxTranslation, 0, (pY - kYStep) / kYStep)
|
||||
: (pY < kYStep * 3.)
|
||||
? anim::interpolateF(0, -kMaxTranslation, (pY - kYStep * 2.) / kYStep)
|
||||
: anim::interpolateF(-kMaxTranslation, 0, (pY - kYStep * 3) / kYStep);
|
||||
|
||||
const auto center = position + QPoint(
|
||||
_singleSize.width() / 2,
|
||||
_singleSize.height() / 2);
|
||||
|
||||
p.translate(center);
|
||||
p.rotate(angle);
|
||||
p.translate(-center);
|
||||
p.translate(x, y);
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::paintSticker(
|
||||
Painter &p,
|
||||
int index,
|
||||
QPoint position,
|
||||
bool paused,
|
||||
crl::time now) const {
|
||||
if (const auto over = _elements[index].overAnimation.value((index == _selected) ? 1. : 0.)) {
|
||||
p.setOpacity(over);
|
||||
auto tl = position;
|
||||
if (rtl()) tl.setX(width() - tl.x() - _singleSize.width());
|
||||
Ui::FillRoundRect(p, QRect(tl, _singleSize), st::emojiPanHover, Ui::StickerHoverCorners);
|
||||
p.setOpacity(1);
|
||||
if (_dragging.index != index) {
|
||||
const auto over = _elements[index].overAnimation.value(
|
||||
(index == _selected) ? 1. : 0.);
|
||||
if (over) {
|
||||
p.setOpacity(over);
|
||||
Ui::FillRoundRect(
|
||||
p,
|
||||
QRect(
|
||||
rtl()
|
||||
? QPoint(
|
||||
width() - position.x() - _singleSize.width(),
|
||||
position.y())
|
||||
: position,
|
||||
_singleSize),
|
||||
st::emojiPanHover,
|
||||
Ui::StickerHoverCorners);
|
||||
p.setOpacity(1);
|
||||
}
|
||||
}
|
||||
|
||||
const auto hasShake = _shakeAnimation.animating();
|
||||
if (hasShake) {
|
||||
shakeTransform(p, index, position, now);
|
||||
}
|
||||
|
||||
const auto &element = _elements[index];
|
||||
@@ -1390,6 +2025,9 @@ void StickerSetBox::Inner::paintSticker(
|
||||
_singleSize,
|
||||
width());
|
||||
}
|
||||
if (hasShake) {
|
||||
p.resetTransform();
|
||||
}
|
||||
}
|
||||
|
||||
bool StickerSetBox::Inner::loaded() const {
|
||||
|
||||
@@ -276,8 +276,8 @@ void Panel::initControls() {
|
||||
_layerBg->showBox(std::move(box));
|
||||
}
|
||||
} else if (const auto source = env->uniqueDesktopCaptureSource()) {
|
||||
if (_call->isSharingScreen()) {
|
||||
_call->toggleScreenSharing(std::nullopt);
|
||||
if (!chooseSourceActiveDeviceId().isEmpty()) {
|
||||
chooseSourceStop();
|
||||
} else {
|
||||
chooseSourceAccepted(*source, false);
|
||||
}
|
||||
|
||||
@@ -1195,24 +1195,7 @@ base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
|
||||
const auto addVolumeItem = (!muted || isMe(participantPeer));
|
||||
const auto admin = IsGroupCallAdmin(_peer, participantPeer);
|
||||
const auto session = &_peer->session();
|
||||
const auto getCurrentWindow = [=]() -> Window::SessionController* {
|
||||
if (const auto window = Core::App().windowFor(participantPeer)) {
|
||||
if (const auto controller = window->sessionController()) {
|
||||
if (&controller->session() == session) {
|
||||
return controller;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
const auto getWindow = [=] {
|
||||
if (const auto current = getCurrentWindow()) {
|
||||
return current;
|
||||
} else if (&Core::App().domain().active() != &session->account()) {
|
||||
Core::App().domain().activate(&session->account());
|
||||
}
|
||||
return getCurrentWindow();
|
||||
};
|
||||
const auto account = &session->account();
|
||||
|
||||
auto result = base::make_unique_q<Ui::PopupMenu>(
|
||||
parent,
|
||||
@@ -1223,7 +1206,7 @@ base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
|
||||
: st::groupCallPopupMenu));
|
||||
const auto weakMenu = Ui::MakeWeak(result.get());
|
||||
const auto withActiveWindow = [=](auto callback) {
|
||||
if (const auto window = getWindow()) {
|
||||
if (const auto window = Core::App().activePrimaryWindow()) {
|
||||
if (const auto menu = weakMenu.data()) {
|
||||
menu->discardParentReActivate();
|
||||
|
||||
@@ -1232,8 +1215,13 @@ base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
|
||||
// PopupMenu::hide activates back the group call panel :(
|
||||
delete weakMenu;
|
||||
}
|
||||
callback(window);
|
||||
window->widget()->activate();
|
||||
window->invokeForSessionController(
|
||||
account,
|
||||
participantPeer,
|
||||
[&](not_null<Window::SessionController*> newController) {
|
||||
callback(newController);
|
||||
newController->widget()->activate();
|
||||
});
|
||||
}
|
||||
};
|
||||
const auto showProfile = [=] {
|
||||
|
||||
@@ -284,6 +284,10 @@ stickersTrendingSubheaderFont: normalFont;
|
||||
stickersTrendingSubheaderFg: windowSubTextFg;
|
||||
stickersTrendingSubheaderTop: 31px;
|
||||
|
||||
stickersHeaderBadgeFont: font(10px);
|
||||
stickersHeaderBadgeFontTop: 12px;
|
||||
stickersHeaderBadgeFontSkip: 12px;
|
||||
|
||||
emojiPanButtonRight: 7px;
|
||||
emojiPanButtonTop: 8px;
|
||||
emojiPanButton: RoundButton(defaultActiveButton) {
|
||||
@@ -1409,6 +1413,15 @@ editTagLimit: FlatLabel(defaultFlatLabel) {
|
||||
textFg: windowSubTextFg;
|
||||
}
|
||||
|
||||
editStickerSetNameField: InputField(defaultInputField) {
|
||||
textMargins: margins(0px, 28px, 26px, 4px);
|
||||
heightMax: 55px;
|
||||
}
|
||||
editStickerSetNameLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) {
|
||||
color: lightButtonFg;
|
||||
thickness: 2px;
|
||||
}
|
||||
|
||||
paidStarIcon: icon {{ "settings/premium/star", creditsBg1 }};
|
||||
paidStarIconTop: 7px;
|
||||
paidAmountAbout: FlatLabel(defaultFlatLabel) {
|
||||
|
||||
@@ -392,6 +392,9 @@ void InitMessageFieldHandlers(
|
||||
Fn<bool()> customEmojiPaused,
|
||||
Fn<bool(not_null<DocumentData*>)> allowPremiumEmoji,
|
||||
const style::InputField *fieldStyle) {
|
||||
const auto paused = [customEmojiPaused] {
|
||||
return customEmojiPaused && customEmojiPaused();
|
||||
};
|
||||
field->setTagMimeProcessor(
|
||||
FieldTagMimeProcessor(session, allowPremiumEmoji));
|
||||
field->setCustomTextContext([=](Fn<void()> repaint) {
|
||||
@@ -399,10 +402,10 @@ void InitMessageFieldHandlers(
|
||||
.session = session,
|
||||
.customEmojiRepaint = std::move(repaint),
|
||||
});
|
||||
}, [customEmojiPaused] {
|
||||
return On(PowerSaving::kEmojiChat) || customEmojiPaused();
|
||||
}, [customEmojiPaused] {
|
||||
return On(PowerSaving::kChatSpoiler) || customEmojiPaused();
|
||||
}, [paused] {
|
||||
return On(PowerSaving::kEmojiChat) || paused();
|
||||
}, [paused] {
|
||||
return On(PowerSaving::kChatSpoiler) || paused();
|
||||
});
|
||||
field->setInstantReplaces(Ui::InstantReplaces::Default());
|
||||
field->setInstantReplacesEnabled(
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_session.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
@@ -21,6 +22,16 @@ GiftBoxPack::GiftBoxPack(not_null<Main::Session*> session)
|
||||
|
||||
GiftBoxPack::~GiftBoxPack() = default;
|
||||
|
||||
int GiftBoxPack::monthsForStars(int stars) const {
|
||||
if (stars <= 1000) {
|
||||
return 3;
|
||||
} else if (stars < 2500) {
|
||||
return 6;
|
||||
} else {
|
||||
return 12;
|
||||
}
|
||||
}
|
||||
|
||||
DocumentData *GiftBoxPack::lookup(int months) const {
|
||||
const auto it = ranges::lower_bound(_localMonths, months);
|
||||
const auto fallback = _documents.empty() ? nullptr : _documents[0];
|
||||
@@ -38,6 +49,10 @@ DocumentData *GiftBoxPack::lookup(int months) const {
|
||||
return (index >= _documents.size()) ? fallback : _documents[index];
|
||||
}
|
||||
|
||||
Data::FileOrigin GiftBoxPack::origin() const {
|
||||
return Data::FileOriginStickerSet(_setId, _accessHash);
|
||||
}
|
||||
|
||||
void GiftBoxPack::load() {
|
||||
if (_requestId || !_documents.empty()) {
|
||||
return;
|
||||
@@ -59,6 +74,7 @@ void GiftBoxPack::load() {
|
||||
|
||||
void GiftBoxPack::applySet(const MTPDmessages_stickerSet &data) {
|
||||
_setId = data.vset().data().vid().v;
|
||||
_accessHash = data.vset().data().vaccess_hash().v;
|
||||
auto documents = base::flat_map<DocumentId, not_null<DocumentData*>>();
|
||||
for (const auto &sticker : data.vdocuments().v) {
|
||||
const auto document = _session->data().processDocument(sticker);
|
||||
|
||||
@@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
class DocumentData;
|
||||
|
||||
namespace Data {
|
||||
struct FileOrigin;
|
||||
} // namespace Data
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
@@ -21,7 +25,9 @@ public:
|
||||
~GiftBoxPack();
|
||||
|
||||
void load();
|
||||
[[nodiscard]] int monthsForStars(int stars) const;
|
||||
[[nodiscard]] DocumentData *lookup(int months) const;
|
||||
[[nodiscard]] Data::FileOrigin origin() const;
|
||||
|
||||
private:
|
||||
using SetId = uint64;
|
||||
@@ -32,6 +38,7 @@ private:
|
||||
|
||||
std::vector<DocumentData*> _documents;
|
||||
SetId _setId = 0;
|
||||
uint64 _accessHash = 0;
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "chat_helpers/stickers_list_widget.h"
|
||||
|
||||
#include "base/timer_rpl.h"
|
||||
#include "core/application.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
@@ -932,6 +933,9 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
|
||||
if (sets.empty() && _section == Section::Search) {
|
||||
paintEmptySearchResults(p);
|
||||
}
|
||||
const auto badgeText = tr::lng_stickers_creator_badge(tr::now);
|
||||
const auto &badgeFont = st::stickersHeaderBadgeFont;
|
||||
const auto badgeWidth = badgeFont->width(badgeText);
|
||||
enumerateSections([&](const SectionInfo &info) {
|
||||
if (clip.top() >= info.rowsBottom) {
|
||||
return true;
|
||||
@@ -1050,6 +1054,12 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
|
||||
|
||||
widthForTitle -= remove.width();
|
||||
}
|
||||
const auto amCreator = (set.flags & Data::StickersSetFlag::AmCreator);
|
||||
if (amCreator) {
|
||||
widthForTitle -= badgeWidth
|
||||
+ st::stickersFeaturedUnreadSkip
|
||||
+ st::stickersHeaderBadgeFontSkip;
|
||||
}
|
||||
if (titleWidth > widthForTitle) {
|
||||
titleText = st::stickersTrendingHeaderFont->elided(titleText, widthForTitle);
|
||||
titleWidth = st::stickersTrendingHeaderFont->width(titleText);
|
||||
@@ -1057,6 +1067,39 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
|
||||
p.setFont(st::emojiPanHeaderFont);
|
||||
p.setPen(st().headerFg);
|
||||
p.drawTextLeft(st().headerLeft - st().margin.left(), info.top + st().headerTop, width(), titleText, titleWidth);
|
||||
if (amCreator) {
|
||||
const auto badgeLeft = st().headerLeft
|
||||
- st().margin.left()
|
||||
+ titleWidth
|
||||
+ st::stickersFeaturedUnreadSkip;
|
||||
{
|
||||
auto color = st().headerFg->c;
|
||||
color.setAlphaF(st().headerFg->c.alphaF() * 0.15);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(color);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.drawRoundedRect(
|
||||
style::rtlrect(
|
||||
badgeLeft,
|
||||
info.top + st::stickersHeaderBadgeFontTop,
|
||||
badgeWidth + badgeFont->height,
|
||||
badgeFont->height,
|
||||
width()),
|
||||
badgeFont->height / 2.,
|
||||
badgeFont->height / 2.);
|
||||
}
|
||||
p.setPen(st().headerFg);
|
||||
p.setBrush(Qt::NoBrush);
|
||||
p.setFont(badgeFont);
|
||||
p.drawText(
|
||||
QRect(
|
||||
badgeLeft + badgeFont->height / 2,
|
||||
info.top + st::stickersHeaderBadgeFontTop,
|
||||
badgeWidth,
|
||||
badgeFont->height),
|
||||
badgeText,
|
||||
style::al_center);
|
||||
}
|
||||
}
|
||||
if (clip.top() + clip.height() <= info.rowsTop) {
|
||||
return true;
|
||||
@@ -1675,12 +1718,32 @@ QPoint StickersListWidget::buttonRippleTopLeft(int section) const {
|
||||
+ st().removeSet.rippleAreaPosition;
|
||||
}
|
||||
|
||||
void StickersListWidget::showStickerSetBox(not_null<DocumentData*> document) {
|
||||
void StickersListWidget::showStickerSetBox(
|
||||
not_null<DocumentData*> document,
|
||||
uint64 setId) {
|
||||
if (document->sticker() && document->sticker()->set) {
|
||||
checkHideWithBox(Box<StickerSetBox>(
|
||||
_show,
|
||||
document->sticker()->set,
|
||||
document->sticker()->setType));
|
||||
} else if ((setId == Data::Stickers::FavedSetId)
|
||||
|| (setId == Data::Stickers::RecentSetId)) {
|
||||
const auto lifetime = std::make_shared<rpl::lifetime>();
|
||||
constexpr auto kTimeout = 10000;
|
||||
rpl::merge(
|
||||
base::timer_once(kTimeout),
|
||||
document->owner().stickers().updated(
|
||||
Data::StickersType::Stickers)
|
||||
) | rpl::start_with_next([=, weak = Ui::MakeWeak(this)] {
|
||||
if (weak.data()) {
|
||||
showStickerSetBox(document, setId);
|
||||
}
|
||||
lifetime->destroy();
|
||||
}, *lifetime);
|
||||
document->owner().session().api().requestSpecialStickersForce(
|
||||
setId == Data::Stickers::FavedSetId,
|
||||
setId == Data::Stickers::RecentSetId,
|
||||
false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1737,8 +1800,8 @@ base::unique_qptr<Ui::PopupMenu> StickersListWidget::fillContextMenu(
|
||||
isFaved ? &icons->menuUnfave : &icons->menuFave);
|
||||
|
||||
if (_features.openStickerSets) {
|
||||
menu->addAction(tr::lng_context_pack_info(tr::now), [=] {
|
||||
showStickerSetBox(document);
|
||||
menu->addAction(tr::lng_context_pack_info(tr::now), [=, id = set.id] {
|
||||
showStickerSetBox(document, id);
|
||||
}, &icons->menuStickerSet);
|
||||
}
|
||||
|
||||
@@ -1808,7 +1871,7 @@ void StickersListWidget::mouseReleaseEvent(QMouseEvent *e) {
|
||||
const auto document = set.stickers[sticker->index].document;
|
||||
if (_features.openStickerSets
|
||||
&& (e->modifiers() & Qt::ControlModifier)) {
|
||||
showStickerSetBox(document);
|
||||
showStickerSetBox(document, set.id);
|
||||
} else {
|
||||
_chosen.fire({
|
||||
.document = document,
|
||||
|
||||
@@ -350,7 +350,9 @@ private:
|
||||
void refreshFooterIcons();
|
||||
void refreshIcons(ValidateIconAnimations animations);
|
||||
|
||||
void showStickerSetBox(not_null<DocumentData*> document);
|
||||
void showStickerSetBox(
|
||||
not_null<DocumentData*> document,
|
||||
uint64 setId);
|
||||
|
||||
void cancelSetsSearch();
|
||||
void showSearchResults();
|
||||
|
||||
@@ -188,8 +188,11 @@ Application::Application()
|
||||
_platformIntegration->init();
|
||||
|
||||
passcodeLockChanges(
|
||||
) | rpl::start_with_next([=] {
|
||||
) | rpl::start_with_next([=](bool locked) {
|
||||
_shouldLockAt = 0;
|
||||
if (locked) {
|
||||
closeAdditionalWindows();
|
||||
}
|
||||
}, _lifetime);
|
||||
|
||||
passcodeLockChanges(
|
||||
@@ -211,6 +214,16 @@ Application::Application()
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
void Application::closeAdditionalWindows() {
|
||||
Payments::CheckoutProcess::ClearAll();
|
||||
for (const auto &[index, account] : _domain->accounts()) {
|
||||
if (account->sessionExists()) {
|
||||
account->session().attachWebView().closeAll();
|
||||
}
|
||||
}
|
||||
_iv->closeAll();
|
||||
}
|
||||
|
||||
Application::~Application() {
|
||||
if (_saveSettingsTimer && _saveSettingsTimer->isActive()) {
|
||||
Local::writeSettings();
|
||||
@@ -230,13 +243,7 @@ Application::~Application() {
|
||||
//
|
||||
// For example Domain::removeRedundantAccounts() is called from
|
||||
// Domain::finish() and there is a violation on Ensures(started()).
|
||||
Payments::CheckoutProcess::ClearAll();
|
||||
for (const auto &[index, account] : _domain->accounts()) {
|
||||
if (account->sessionExists()) {
|
||||
account->session().attachWebView().closeAll();
|
||||
}
|
||||
}
|
||||
_iv->closeAll();
|
||||
closeAdditionalWindows();
|
||||
|
||||
_domain->finish();
|
||||
|
||||
@@ -683,7 +690,8 @@ bool Application::eventFilter(QObject *object, QEvent *e) {
|
||||
if (const auto file = event->file(); !file.isEmpty()) {
|
||||
_filesToOpen.append(file);
|
||||
_fileOpenTimer.callOnce(kFileOpenTimeoutMs);
|
||||
} else if (event->url().scheme() == u"tg"_q) {
|
||||
} else if (event->url().scheme() == u"tg"_q
|
||||
|| event->url().scheme() == u"tonsite"_q) {
|
||||
const auto url = QString::fromUtf8(
|
||||
event->url().toEncoded().trimmed());
|
||||
cSetStartUrl(url.mid(0, 8192));
|
||||
@@ -1084,13 +1092,18 @@ void Application::checkSendPaths() {
|
||||
}
|
||||
|
||||
void Application::checkStartUrl() {
|
||||
if (!cStartUrl().isEmpty()
|
||||
&& _lastActivePrimaryWindow
|
||||
&& !_lastActivePrimaryWindow->locked()) {
|
||||
if (!cStartUrl().isEmpty()) {
|
||||
const auto url = cStartUrl();
|
||||
cSetStartUrl(QString());
|
||||
if (!openLocalUrl(url, {})) {
|
||||
cSetStartUrl(url);
|
||||
if (!Core::App().passcodeLocked()) {
|
||||
if (url.startsWith("tonsite://", Qt::CaseInsensitive)) {
|
||||
cSetStartUrl(QString());
|
||||
iv().showTonSite(url, {});
|
||||
} else if (_lastActivePrimaryWindow) {
|
||||
cSetStartUrl(QString());
|
||||
if (!openLocalUrl(url, {})) {
|
||||
cSetStartUrl(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1341,7 +1354,7 @@ Window::Controller *Application::ensureSeparateWindowFor(
|
||||
Window::Controller *Application::windowFor(Window::SeparateId id) const {
|
||||
if (const auto separate = separateWindowFor(id)) {
|
||||
return separate;
|
||||
} else if (id && id.primary()) {
|
||||
} else if (id && !id.primary()) {
|
||||
return windowFor(not_null(id.account));
|
||||
}
|
||||
return activePrimaryWindow();
|
||||
@@ -1798,11 +1811,13 @@ void Application::startShortcuts() {
|
||||
}
|
||||
|
||||
void Application::RegisterUrlScheme() {
|
||||
const auto arguments = Launcher::Instance().customWorkingDir()
|
||||
? u"-workdir \"%1\""_q.arg(cWorkingDir())
|
||||
: QString();
|
||||
|
||||
base::Platform::RegisterUrlScheme(base::Platform::UrlSchemeDescriptor{
|
||||
.executable = Platform::ExecutablePathForShortcuts(),
|
||||
.arguments = Launcher::Instance().customWorkingDir()
|
||||
? u"-workdir \"%1\""_q.arg(cWorkingDir())
|
||||
: QString(),
|
||||
.arguments = arguments,
|
||||
.protocol = u"tg"_q,
|
||||
.protocolName = u"Telegram Link"_q,
|
||||
.shortAppName = u"tdesktop"_q,
|
||||
@@ -1810,6 +1825,17 @@ void Application::RegisterUrlScheme() {
|
||||
.displayAppName = AppName.utf16(),
|
||||
.displayAppDescription = AppName.utf16(),
|
||||
});
|
||||
|
||||
base::Platform::RegisterUrlScheme(base::Platform::UrlSchemeDescriptor{
|
||||
.executable = Platform::ExecutablePathForShortcuts(),
|
||||
.arguments = arguments,
|
||||
.protocol = u"tonsite"_q,
|
||||
.protocolName = u"TonSite Link"_q,
|
||||
.shortAppName = u"tdesktop"_q,
|
||||
.longAppName = QCoreApplication::applicationName(),
|
||||
.displayAppName = AppName.utf16(),
|
||||
.displayAppDescription = AppName.utf16(),
|
||||
});
|
||||
}
|
||||
|
||||
bool IsAppLaunched() {
|
||||
|
||||
@@ -378,6 +378,7 @@ private:
|
||||
|
||||
void showOpenGLCrashNotification();
|
||||
void clearPasscodeLock();
|
||||
void closeAdditionalWindows();
|
||||
|
||||
bool openCustomUrl(
|
||||
const QString &protocol,
|
||||
|
||||
@@ -122,7 +122,9 @@ void HiddenUrlClickHandler::Open(QString url, QVariant context) {
|
||||
return result;
|
||||
}()));
|
||||
} else {
|
||||
const auto parsedUrl = QUrl::fromUserInput(url);
|
||||
const auto parsedUrl = url.startsWith(u"tonsite://"_q)
|
||||
? QUrl(url)
|
||||
: QUrl::fromUserInput(url);
|
||||
if (UrlRequiresConfirmation(parsedUrl) && !base::IsCtrlPressed()) {
|
||||
const auto my = context.value<ClickHandlerContext>();
|
||||
if (!my.show) {
|
||||
|
||||
@@ -220,7 +220,8 @@ QByteArray Settings::serialize() const {
|
||||
+ Serialize::bytearraySize(ivPosition)
|
||||
+ Serialize::stringSize(noWarningExtensions)
|
||||
+ Serialize::stringSize(_customFontFamily)
|
||||
+ sizeof(qint32) * 2;
|
||||
+ sizeof(qint32) * 3
|
||||
+ Serialize::bytearraySize(_tonsiteStorageToken);
|
||||
|
||||
auto result = QByteArray();
|
||||
result.reserve(size);
|
||||
@@ -372,7 +373,9 @@ QByteArray Settings::serialize() const {
|
||||
qRound(_dialogsNoChatWidthRatio.current() * 1000000),
|
||||
0,
|
||||
1000000))
|
||||
<< qint32(_systemUnlockEnabled ? 1 : 0);
|
||||
<< qint32(_systemUnlockEnabled ? 1 : 0)
|
||||
<< qint32(!_weatherInCelsius ? 0 : *_weatherInCelsius ? 1 : 2)
|
||||
<< _tonsiteStorageToken;
|
||||
}
|
||||
|
||||
Ensures(result.size() == size);
|
||||
@@ -493,6 +496,8 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
QByteArray ivPosition;
|
||||
QString customFontFamily = _customFontFamily;
|
||||
qint32 systemUnlockEnabled = _systemUnlockEnabled ? 1 : 0;
|
||||
qint32 weatherInCelsius = !_weatherInCelsius ? 0 : *_weatherInCelsius ? 1 : 2;
|
||||
QByteArray tonsiteStorageToken = _tonsiteStorageToken;
|
||||
|
||||
stream >> themesAccentColors;
|
||||
if (!stream.atEnd()) {
|
||||
@@ -793,6 +798,12 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
if (!stream.atEnd()) {
|
||||
stream >> systemUnlockEnabled;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> weatherInCelsius;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> tonsiteStorageToken;
|
||||
}
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("App Error: "
|
||||
"Bad data for Core::Settings::constructFromSerialized()"));
|
||||
@@ -1001,6 +1012,10 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
}
|
||||
_customFontFamily = customFontFamily;
|
||||
_systemUnlockEnabled = (systemUnlockEnabled == 1);
|
||||
_weatherInCelsius = !weatherInCelsius
|
||||
? std::optional<bool>()
|
||||
: (weatherInCelsius == 1);
|
||||
_tonsiteStorageToken = tonsiteStorageToken;
|
||||
}
|
||||
|
||||
QString Settings::getSoundPath(const QString &key) const {
|
||||
|
||||
@@ -891,6 +891,20 @@ public:
|
||||
_systemUnlockEnabled = enabled;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<bool> weatherInCelsius() const {
|
||||
return _weatherInCelsius;
|
||||
}
|
||||
void setWeatherInCelsius(bool value) {
|
||||
_weatherInCelsius = value;
|
||||
}
|
||||
|
||||
[[nodiscard]] QByteArray tonsiteStorageToken() const {
|
||||
return _tonsiteStorageToken;
|
||||
}
|
||||
void setTonsiteStorageToken(const QByteArray &value) {
|
||||
_tonsiteStorageToken = value;
|
||||
}
|
||||
|
||||
[[nodiscard]] static bool ThirdColumnByDefault();
|
||||
[[nodiscard]] static float64 DefaultDialogsWidthRatio();
|
||||
|
||||
@@ -1022,6 +1036,8 @@ private:
|
||||
WindowPosition _ivPosition;
|
||||
QString _customFontFamily;
|
||||
bool _systemUnlockEnabled = false;
|
||||
std::optional<bool> _weatherInCelsius;
|
||||
QByteArray _tonsiteStorageToken;
|
||||
|
||||
bool _tabbedReplacedWithInfo = false; // per-window
|
||||
rpl::event_stream<bool> _tabbedReplacedWithInfoValue; // per-window
|
||||
|
||||
@@ -1337,6 +1337,13 @@ QString TryConvertUrlToLocal(QString url) {
|
||||
|
||||
using namespace qthelp;
|
||||
auto matchOptions = RegExOption::CaseInsensitive;
|
||||
auto tonsiteMatch = (url.indexOf(u".ton") >= 0)
|
||||
? regex_match(u"^(https?://)?[^/@:]+\\.ton($|/)"_q, url, matchOptions)
|
||||
: RegularExpressionMatch(QRegularExpressionMatch());
|
||||
if (tonsiteMatch) {
|
||||
const auto protocol = tonsiteMatch->captured(1);
|
||||
return u"tonsite://"_q + url.mid(protocol.size());
|
||||
}
|
||||
auto subdomainMatch = regex_match(u"^(https?://)?([a-zA-Z0-9\\_]+)\\.t\\.me(/\\d+)?/?(\\?.+)?"_q, url, matchOptions);
|
||||
if (subdomainMatch) {
|
||||
const auto name = subdomainMatch->captured(2);
|
||||
|
||||
@@ -238,6 +238,9 @@ bool UiIntegration::handleUrlClick(
|
||||
} else if (local.startsWith(u"tg://"_q, Qt::CaseInsensitive)) {
|
||||
Core::App().openLocalUrl(local, context);
|
||||
return true;
|
||||
} else if (local.startsWith(u"tonsite://"_q, Qt::CaseInsensitive)) {
|
||||
Core::App().iv().showTonSite(url, context);
|
||||
return true;
|
||||
} else if (local.startsWith(u"internal:"_q, Qt::CaseInsensitive)) {
|
||||
Core::App().openInternalUrl(local, context);
|
||||
return true;
|
||||
|
||||
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
|
||||
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
|
||||
constexpr auto AppName = "Telegram Desktop"_cs;
|
||||
constexpr auto AppFile = "Telegram"_cs;
|
||||
constexpr auto AppVersion = 5002006;
|
||||
constexpr auto AppVersionStr = "5.2.6";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppVersion = 5003000;
|
||||
constexpr auto AppVersionStr = "5.3";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -41,12 +41,36 @@ constexpr auto kRequestTimeLimit = 10 * crl::time(1000);
|
||||
) / 1'000'000.;
|
||||
}
|
||||
|
||||
[[nodiscard]] MTPTopPeerCategory TypeToCategory(TopPeerType type) {
|
||||
switch (type) {
|
||||
case TopPeerType::Chat: return MTP_topPeerCategoryCorrespondents();
|
||||
case TopPeerType::BotApp: return MTP_topPeerCategoryBotsApp();
|
||||
}
|
||||
Unexpected("Type in TypeToCategory.");
|
||||
}
|
||||
|
||||
[[nodiscard]] auto TypeToGetFlags(TopPeerType type) {
|
||||
using Flag = MTPcontacts_GetTopPeers::Flag;
|
||||
switch (type) {
|
||||
case TopPeerType::Chat: return Flag::f_correspondents;
|
||||
case TopPeerType::BotApp: return Flag::f_bots_app;
|
||||
}
|
||||
Unexpected("Type in TypeToGetFlags.");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TopPeers::TopPeers(not_null<Main::Session*> session)
|
||||
: _session(session) {
|
||||
TopPeers::TopPeers(not_null<Main::Session*> session, TopPeerType type)
|
||||
: _session(session)
|
||||
, _type(type) {
|
||||
if (_type == TopPeerType::Chat) {
|
||||
loadAfterChats();
|
||||
}
|
||||
}
|
||||
|
||||
void TopPeers::loadAfterChats() {
|
||||
using namespace rpl::mappers;
|
||||
crl::on_main(session, [=] {
|
||||
crl::on_main(_session, [=] {
|
||||
_session->data().chatsListLoadedEvents(
|
||||
) | rpl::filter(_1 == nullptr) | rpl::start_with_next([=] {
|
||||
crl::on_main(_session, [=] {
|
||||
@@ -84,7 +108,7 @@ void TopPeers::remove(not_null<PeerData*> peer) {
|
||||
}
|
||||
|
||||
_requestId = _session->api().request(MTPcontacts_ResetTopPeerRating(
|
||||
MTP_topPeerCategoryCorrespondents(),
|
||||
TypeToCategory(_type),
|
||||
peer->input
|
||||
)).send();
|
||||
}
|
||||
@@ -160,11 +184,13 @@ void TopPeers::request() {
|
||||
}
|
||||
|
||||
_requestId = _session->api().request(MTPcontacts_GetTopPeers(
|
||||
MTP_flags(MTPcontacts_GetTopPeers::Flag::f_correspondents),
|
||||
MTP_flags(TypeToGetFlags(_type)),
|
||||
MTP_int(0),
|
||||
MTP_int(kLimit),
|
||||
MTP_long(countHash())
|
||||
)).done([=](const MTPcontacts_TopPeers &result, const MTP::Response &response) {
|
||||
)).done([=](
|
||||
const MTPcontacts_TopPeers &result,
|
||||
const MTP::Response &response) {
|
||||
_lastReceivedDate = TimeId(response.outerMsgId >> 32);
|
||||
_lastReceived = crl::now();
|
||||
_requestId = 0;
|
||||
@@ -176,19 +202,22 @@ void TopPeers::request() {
|
||||
owner->processChats(data.vchats());
|
||||
for (const auto &category : data.vcategories().v) {
|
||||
const auto &data = category.data();
|
||||
data.vcategory().match(
|
||||
[&](const MTPDtopPeerCategoryCorrespondents &) {
|
||||
_list = ranges::views::all(
|
||||
data.vpeers().v
|
||||
) | ranges::views::transform([&](const MTPTopPeer &top) {
|
||||
return TopPeer{
|
||||
owner->peer(peerFromMTP(top.data().vpeer())),
|
||||
top.data().vrating().v,
|
||||
};
|
||||
}) | ranges::to_vector;
|
||||
}, [](const auto &) {
|
||||
const auto cons = (_type == TopPeerType::Chat)
|
||||
? mtpc_topPeerCategoryCorrespondents
|
||||
: mtpc_topPeerCategoryBotsApp;
|
||||
if (data.vcategory().type() != cons) {
|
||||
LOG(("API Error: Unexpected top peer category."));
|
||||
});
|
||||
continue;
|
||||
}
|
||||
_list = ranges::views::all(
|
||||
data.vpeers().v
|
||||
) | ranges::views::transform([&](
|
||||
const MTPTopPeer &top) {
|
||||
return TopPeer{
|
||||
owner->peer(peerFromMTP(top.data().vpeer())),
|
||||
top.data().vrating().v,
|
||||
};
|
||||
}) | ranges::to_vector;
|
||||
}
|
||||
updated();
|
||||
}, [&](const MTPDcontacts_topPeersDisabled &) {
|
||||
|
||||
@@ -13,9 +13,14 @@ class Session;
|
||||
|
||||
namespace Data {
|
||||
|
||||
enum class TopPeerType {
|
||||
Chat,
|
||||
BotApp,
|
||||
};
|
||||
|
||||
class TopPeers final {
|
||||
public:
|
||||
explicit TopPeers(not_null<Main::Session*> session);
|
||||
TopPeers(not_null<Main::Session*> session, TopPeerType type);
|
||||
~TopPeers();
|
||||
|
||||
[[nodiscard]] std::vector<not_null<PeerData*>> list() const;
|
||||
@@ -36,11 +41,13 @@ private:
|
||||
float64 rating = 0.;
|
||||
};
|
||||
|
||||
void loadAfterChats();
|
||||
void request();
|
||||
[[nodiscard]] uint64 countHash() const;
|
||||
void updated();
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
const TopPeerType _type = {};
|
||||
|
||||
std::vector<TopPeer> _list;
|
||||
rpl::event_stream<> _updates;
|
||||
|
||||
@@ -15,6 +15,7 @@ struct CreditTopupOption final {
|
||||
QString currency;
|
||||
uint64 amount = 0;
|
||||
bool extended = false;
|
||||
uint64 giftBarePeerId = 0;
|
||||
};
|
||||
|
||||
using CreditTopupOptions = std::vector<CreditTopupOption>;
|
||||
@@ -57,7 +58,7 @@ struct CreditsHistoryEntry final {
|
||||
QDateTime successDate;
|
||||
QString successLink;
|
||||
bool in = false;
|
||||
|
||||
bool gift = false;
|
||||
};
|
||||
|
||||
struct CreditsStatusSlice final {
|
||||
|
||||
@@ -2303,8 +2303,9 @@ ClickHandlerPtr MediaDice::MakeHandler(
|
||||
MediaGiftBox::MediaGiftBox(
|
||||
not_null<HistoryItem*> parent,
|
||||
not_null<PeerData*> from,
|
||||
int months)
|
||||
: MediaGiftBox(parent, from, GiftCode{ .months = months }) {
|
||||
GiftType type,
|
||||
int count)
|
||||
: MediaGiftBox(parent, from, GiftCode{ .count = count, .type = type }) {
|
||||
}
|
||||
|
||||
MediaGiftBox::MediaGiftBox(
|
||||
|
||||
@@ -125,10 +125,16 @@ struct GiveawayResults {
|
||||
bool all = false;
|
||||
};
|
||||
|
||||
enum class GiftType : uchar {
|
||||
Premium, // count - months
|
||||
Stars, // count - stars
|
||||
};
|
||||
|
||||
struct GiftCode {
|
||||
QString slug;
|
||||
ChannelData *channel = nullptr;
|
||||
int months = 0;
|
||||
int count = 0;
|
||||
GiftType type = GiftType::Premium;
|
||||
bool viaGiveaway = false;
|
||||
bool unclaimed = false;
|
||||
};
|
||||
@@ -591,7 +597,8 @@ public:
|
||||
MediaGiftBox(
|
||||
not_null<HistoryItem*> parent,
|
||||
not_null<PeerData*> from,
|
||||
int months);
|
||||
GiftType type,
|
||||
int count);
|
||||
MediaGiftBox(
|
||||
not_null<HistoryItem*> parent,
|
||||
not_null<PeerData*> from,
|
||||
|
||||
@@ -721,6 +721,8 @@ not_null<UserData*> Session::processUser(const MTPUser &data) {
|
||||
result->botInfo->supportsAttachMenu = data.is_bot_attach_menu();
|
||||
result->botInfo->supportsBusiness = data.is_bot_business();
|
||||
result->botInfo->canEditInformation = data.is_bot_can_edit();
|
||||
result->botInfo->activeUsers = data.vbot_active_users().value_or_empty();
|
||||
result->botInfo->hasMainApp = data.is_bot_has_main_app();
|
||||
} else {
|
||||
result->setBotInfoVersion(-1);
|
||||
}
|
||||
@@ -4533,7 +4535,8 @@ void Session::serviceNotification(
|
||||
MTPVector<MTPUsername>(),
|
||||
MTPint(), // stories_max_id
|
||||
MTPPeerColor(), // color
|
||||
MTPPeerColor())); // profile_color
|
||||
MTPPeerColor(), // profile_color
|
||||
MTPint())); // bot_active_users
|
||||
}
|
||||
const auto history = this->history(PeerData::kServiceNotificationsId);
|
||||
const auto insert = [=] {
|
||||
|
||||
@@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "storage/download_manager_mtproto.h"
|
||||
#include "storage/file_download.h" // kMaxFileInMemory
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/color_int_conversion.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
@@ -40,6 +41,7 @@ using UpdateFlag = StoryUpdate::Flag;
|
||||
return {
|
||||
.geometry = { corner / 100., size / 100. },
|
||||
.rotation = data.vrotation().v,
|
||||
.radius = data.vradius().value_or_empty(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -83,6 +85,7 @@ using UpdateFlag = StoryUpdate::Flag;
|
||||
}, [&](const MTPDmediaAreaSuggestedReaction &data) {
|
||||
}, [&](const MTPDmediaAreaChannelPost &data) {
|
||||
}, [&](const MTPDmediaAreaUrl &data) {
|
||||
}, [&](const MTPDmediaAreaWeather &data) {
|
||||
}, [&](const MTPDinputMediaAreaChannelPost &data) {
|
||||
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
|
||||
}, [&](const MTPDinputMediaAreaVenue &data) {
|
||||
@@ -105,6 +108,7 @@ using UpdateFlag = StoryUpdate::Flag;
|
||||
});
|
||||
}, [&](const MTPDmediaAreaChannelPost &data) {
|
||||
}, [&](const MTPDmediaAreaUrl &data) {
|
||||
}, [&](const MTPDmediaAreaWeather &data) {
|
||||
}, [&](const MTPDinputMediaAreaChannelPost &data) {
|
||||
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
|
||||
}, [&](const MTPDinputMediaAreaVenue &data) {
|
||||
@@ -127,6 +131,7 @@ using UpdateFlag = StoryUpdate::Flag;
|
||||
data.vmsg_id().v),
|
||||
});
|
||||
}, [&](const MTPDmediaAreaUrl &data) {
|
||||
}, [&](const MTPDmediaAreaWeather &data) {
|
||||
}, [&](const MTPDinputMediaAreaChannelPost &data) {
|
||||
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
|
||||
}, [&](const MTPDinputMediaAreaVenue &data) {
|
||||
@@ -147,6 +152,33 @@ using UpdateFlag = StoryUpdate::Flag;
|
||||
.area = ParseArea(data.vcoordinates()),
|
||||
.url = qs(data.vurl()),
|
||||
});
|
||||
}, [&](const MTPDmediaAreaWeather &data) {
|
||||
}, [&](const MTPDinputMediaAreaChannelPost &data) {
|
||||
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
|
||||
}, [&](const MTPDinputMediaAreaVenue &data) {
|
||||
LOG(("API Error: Unexpected inputMediaAreaVenue from API."));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto ParseWeatherArea(const MTPMediaArea &area)
|
||||
-> std::optional<WeatherArea> {
|
||||
auto result = std::optional<WeatherArea>();
|
||||
area.match([&](const MTPDmediaAreaVenue &data) {
|
||||
}, [&](const MTPDmediaAreaGeoPoint &data) {
|
||||
}, [&](const MTPDmediaAreaSuggestedReaction &data) {
|
||||
}, [&](const MTPDmediaAreaChannelPost &data) {
|
||||
}, [&](const MTPDmediaAreaUrl &data) {
|
||||
}, [&](const MTPDmediaAreaWeather &data) {
|
||||
result.emplace(WeatherArea{
|
||||
.area = ParseArea(data.vcoordinates()),
|
||||
.emoji = qs(data.vemoji()),
|
||||
.color = Ui::Color32FromSerialized(data.vcolor().v),
|
||||
.millicelsius = int(1000. * std::clamp(
|
||||
data.vtemperature_c().v,
|
||||
-274.,
|
||||
1'000'000.)),
|
||||
});
|
||||
}, [&](const MTPDinputMediaAreaChannelPost &data) {
|
||||
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
|
||||
}, [&](const MTPDinputMediaAreaVenue &data) {
|
||||
@@ -689,6 +721,10 @@ const std::vector<UrlArea> &Story::urlAreas() const {
|
||||
return _urlAreas;
|
||||
}
|
||||
|
||||
const std::vector<WeatherArea> &Story::weatherAreas() const {
|
||||
return _weatherAreas;
|
||||
}
|
||||
|
||||
void Story::applyChanges(
|
||||
StoryMedia media,
|
||||
const MTPDstoryItem &data,
|
||||
@@ -793,6 +829,7 @@ void Story::applyFields(
|
||||
auto suggestedReactions = std::vector<SuggestedReaction>();
|
||||
auto channelPosts = std::vector<ChannelPost>();
|
||||
auto urlAreas = std::vector<UrlArea>();
|
||||
auto weatherAreas = std::vector<WeatherArea>();
|
||||
if (const auto areas = data.vmedia_areas()) {
|
||||
for (const auto &area : areas->v) {
|
||||
if (const auto location = ParseLocation(area)) {
|
||||
@@ -808,6 +845,8 @@ void Story::applyFields(
|
||||
channelPosts.push_back(*post);
|
||||
} else if (auto url = ParseUrlArea(area)) {
|
||||
urlAreas.push_back(*url);
|
||||
} else if (auto weather = ParseWeatherArea(area)) {
|
||||
weatherAreas.push_back(*weather);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -821,6 +860,7 @@ void Story::applyFields(
|
||||
= (_suggestedReactions != suggestedReactions);
|
||||
const auto channelPostsChanged = (_channelPosts != channelPosts);
|
||||
const auto urlAreasChanged = (_urlAreas != urlAreas);
|
||||
const auto weatherAreasChanged = (_weatherAreas != weatherAreas);
|
||||
const auto reactionChanged = (_sentReactionId != reaction);
|
||||
|
||||
_out = out;
|
||||
@@ -849,6 +889,9 @@ void Story::applyFields(
|
||||
if (urlAreasChanged) {
|
||||
_urlAreas = std::move(urlAreas);
|
||||
}
|
||||
if (weatherAreasChanged) {
|
||||
_weatherAreas = std::move(weatherAreas);
|
||||
}
|
||||
if (reactionChanged) {
|
||||
_sentReactionId = reaction;
|
||||
}
|
||||
@@ -859,7 +902,8 @@ void Story::applyFields(
|
||||
|| mediaChanged
|
||||
|| locationsChanged
|
||||
|| channelPostsChanged
|
||||
|| urlAreasChanged;
|
||||
|| urlAreasChanged
|
||||
|| weatherAreasChanged;
|
||||
const auto reactionsChanged = reactionChanged
|
||||
|| suggestedReactionsChanged;
|
||||
if (!initial && (changed || reactionsChanged)) {
|
||||
|
||||
@@ -81,6 +81,7 @@ struct StoryViews {
|
||||
struct StoryArea {
|
||||
QRectF geometry;
|
||||
float64 rotation = 0;
|
||||
float64 radius = 0;
|
||||
|
||||
friend inline bool operator==(
|
||||
const StoryArea &,
|
||||
@@ -131,6 +132,17 @@ struct UrlArea {
|
||||
const UrlArea &) = default;
|
||||
};
|
||||
|
||||
struct WeatherArea {
|
||||
StoryArea area;
|
||||
QString emoji;
|
||||
QColor color;
|
||||
int millicelsius = 0;
|
||||
|
||||
friend inline bool operator==(
|
||||
const WeatherArea &,
|
||||
const WeatherArea &) = default;
|
||||
};
|
||||
|
||||
class Story final {
|
||||
public:
|
||||
Story(
|
||||
@@ -208,6 +220,8 @@ public:
|
||||
-> const std::vector<ChannelPost> &;
|
||||
[[nodiscard]] auto urlAreas() const
|
||||
-> const std::vector<UrlArea> &;
|
||||
[[nodiscard]] auto weatherAreas() const
|
||||
-> const std::vector<WeatherArea> &;
|
||||
|
||||
void applyChanges(
|
||||
StoryMedia media,
|
||||
@@ -259,6 +273,7 @@ private:
|
||||
std::vector<SuggestedReaction> _suggestedReactions;
|
||||
std::vector<ChannelPost> _channelPosts;
|
||||
std::vector<UrlArea> _urlAreas;
|
||||
std::vector<WeatherArea> _weatherAreas;
|
||||
StoryViews _views;
|
||||
StoryViews _channelReactions;
|
||||
const TimeId _date = 0;
|
||||
|
||||
@@ -40,12 +40,14 @@ struct BotInfo {
|
||||
|
||||
int version = 0;
|
||||
int descriptionVersion = 0;
|
||||
int activeUsers = 0;
|
||||
bool inited : 1 = false;
|
||||
bool readsAllHistory : 1 = false;
|
||||
bool cantJoinGroups : 1 = false;
|
||||
bool supportsAttachMenu : 1 = false;
|
||||
bool canEditInformation : 1 = false;
|
||||
bool supportsBusiness : 1 = false;
|
||||
bool hasMainApp : 1 = false;
|
||||
};
|
||||
|
||||
enum class UserDataFlag : uint32 {
|
||||
|
||||
@@ -228,7 +228,7 @@ void Stickers::incrementSticker(not_null<DocumentData*> document) {
|
||||
auto index = set->stickers.indexOf(document);
|
||||
if (index > 0) {
|
||||
if (set->dates.empty()) {
|
||||
session().api().requestRecentStickersForce();
|
||||
session().api().requestSpecialStickersForce(false, true, false);
|
||||
} else {
|
||||
Assert(set->dates.size() == set->stickers.size());
|
||||
set->dates.erase(set->dates.begin() + index);
|
||||
@@ -260,7 +260,7 @@ void Stickers::incrementSticker(not_null<DocumentData*> document) {
|
||||
set->emoji[emoji].push_front(document);
|
||||
}
|
||||
} else {
|
||||
session().api().requestRecentStickersForce();
|
||||
session().api().requestSpecialStickersForce(false, true, false);
|
||||
}
|
||||
|
||||
writeRecentStickers = true;
|
||||
|
||||
@@ -55,7 +55,8 @@ StickersSetFlags ParseStickersSetFlags(const MTPDstickerSet &data) {
|
||||
| (data.vinstalled_date() ? Flag::Installed : Flag())
|
||||
//| (data.is_videos() ? Flag::Webm : Flag())
|
||||
| (data.is_text_color() ? Flag::TextColor : Flag())
|
||||
| (data.is_channel_emoji_status() ? Flag::ChannelStatus : Flag());
|
||||
| (data.is_channel_emoji_status() ? Flag::ChannelStatus : Flag())
|
||||
| (data.is_creator() ? Flag::AmCreator : Flag());
|
||||
}
|
||||
|
||||
StickersSet::StickersSet(
|
||||
|
||||
@@ -59,6 +59,7 @@ enum class StickersSetFlag : ushort {
|
||||
Emoji = (1 << 9),
|
||||
TextColor = (1 << 10),
|
||||
ChannelStatus = (1 << 11),
|
||||
AmCreator = (1 << 12),
|
||||
};
|
||||
inline constexpr bool is_flag_type(StickersSetFlag) { return true; };
|
||||
using StickersSetFlags = base::flags<StickersSetFlag>;
|
||||
|
||||
@@ -78,6 +78,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_stories.h"
|
||||
#include "info/downloads/info_downloads_widget.h"
|
||||
#include "info/info_memento.h"
|
||||
#include "inline_bots/bot_attach_web_view.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
@@ -1265,6 +1266,27 @@ void Widget::updateSuggestions(anim::type animated) {
|
||||
}
|
||||
}, _suggestions->lifetime());
|
||||
|
||||
_suggestions->recentAppChosen(
|
||||
) | rpl::start_with_next([=](not_null<PeerData*> peer) {
|
||||
if (const auto user = peer->asUser()) {
|
||||
if (const auto info = user->botInfo.get()) {
|
||||
if (info->hasMainApp) {
|
||||
openBotMainApp(user);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
chosenRow({
|
||||
.key = peer->owner().history(peer),
|
||||
.newWindow = base::IsCtrlPressed(),
|
||||
});
|
||||
}, _suggestions->lifetime());
|
||||
|
||||
_suggestions->popularAppChosen(
|
||||
) | rpl::start_with_next([=](not_null<PeerData*> peer) {
|
||||
controller()->showPeerInfo(peer);
|
||||
}, _suggestions->lifetime());
|
||||
|
||||
updateControlsGeometry();
|
||||
|
||||
_suggestions->show(animated, [=] {
|
||||
@@ -1276,6 +1298,17 @@ void Widget::updateSuggestions(anim::type animated) {
|
||||
}
|
||||
}
|
||||
|
||||
void Widget::openBotMainApp(not_null<UserData*> bot) {
|
||||
session().attachWebView().open({
|
||||
.bot = bot,
|
||||
.context = {
|
||||
.controller = controller(),
|
||||
.maySkipConfirmation = true,
|
||||
},
|
||||
.source = InlineBots::WebViewSourceBotProfile(),
|
||||
});
|
||||
}
|
||||
|
||||
void Widget::changeOpenedSubsection(
|
||||
FnMut<void()> change,
|
||||
bool fromRight,
|
||||
|
||||
@@ -214,6 +214,7 @@ private:
|
||||
void refreshTopBars();
|
||||
void showSearchInTopBar(anim::type animated);
|
||||
void checkUpdateStatus();
|
||||
void openBotMainApp(not_null<UserData*> bot);
|
||||
void changeOpenedSubsection(
|
||||
FnMut<void()> change,
|
||||
bool fromRight,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/rp_widget.h"
|
||||
|
||||
class PeerListContent;
|
||||
|
||||
namespace Data {
|
||||
class Thread;
|
||||
} // namespace Data
|
||||
@@ -67,21 +69,32 @@ public:
|
||||
}
|
||||
[[nodiscard]] auto recentPeerChosen() const
|
||||
-> rpl::producer<not_null<PeerData*>> {
|
||||
return _recentPeerChosen.events();
|
||||
return _recent->chosen.events();
|
||||
}
|
||||
[[nodiscard]] auto myChannelChosen() const
|
||||
-> rpl::producer<not_null<PeerData*>> {
|
||||
return _myChannelChosen.events();
|
||||
return _myChannels->chosen.events();
|
||||
}
|
||||
[[nodiscard]] auto recommendationChosen() const
|
||||
-> rpl::producer<not_null<PeerData*>> {
|
||||
return _recommendationChosen.events();
|
||||
return _recommendations->chosen.events();
|
||||
}
|
||||
[[nodiscard]] auto recentAppChosen() const
|
||||
-> rpl::producer<not_null<PeerData*>> {
|
||||
return _recentApps->chosen.events();
|
||||
}
|
||||
[[nodiscard]] auto popularAppChosen() const
|
||||
-> rpl::producer<not_null<PeerData*>> {
|
||||
return _popularApps->chosen.events();
|
||||
}
|
||||
|
||||
class ObjectListController;
|
||||
|
||||
private:
|
||||
enum class Tab : uchar {
|
||||
Chats,
|
||||
Channels,
|
||||
Apps,
|
||||
};
|
||||
enum class JumpResult : uchar {
|
||||
NotApplied,
|
||||
@@ -89,29 +102,54 @@ private:
|
||||
AppliedAndOut,
|
||||
};
|
||||
|
||||
struct ObjectList {
|
||||
not_null<Ui::SlideWrap<PeerListContent>*> wrap;
|
||||
rpl::variable<int> count;
|
||||
Fn<bool()> choose;
|
||||
Fn<JumpResult(Qt::Key, int)> selectJump;
|
||||
Fn<uint64(QPoint)> updateFromParentDrag;
|
||||
Fn<void()> dragLeft;
|
||||
Fn<bool(not_null<QTouchEvent*>)> processTouch;
|
||||
rpl::event_stream<not_null<PeerData*>> chosen;
|
||||
};
|
||||
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
void setupTabs();
|
||||
void setupChats();
|
||||
void setupChannels();
|
||||
void setupApps();
|
||||
|
||||
void selectJumpChats(Qt::Key direction, int pageSize);
|
||||
void selectJumpChannels(Qt::Key direction, int pageSize);
|
||||
void selectJumpApps(Qt::Key direction, int pageSize);
|
||||
|
||||
[[nodiscard]] Data::Thread *updateFromChatsDrag(QPoint globalPosition);
|
||||
[[nodiscard]] Data::Thread *updateFromChannelsDrag(
|
||||
QPoint globalPosition);
|
||||
[[nodiscard]] Data::Thread *updateFromAppsDrag(QPoint globalPosition);
|
||||
[[nodiscard]] Data::Thread *fromListId(uint64 peerListRowId);
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::SlideWrap<Ui::RpWidget>> setupRecentPeers(
|
||||
[[nodiscard]] std::unique_ptr<ObjectList> setupRecentPeers(
|
||||
RecentPeersList recentPeers);
|
||||
[[nodiscard]] object_ptr<Ui::SlideWrap<Ui::RpWidget>> setupEmptyRecent();
|
||||
[[nodiscard]] object_ptr<Ui::SlideWrap<Ui::RpWidget>> setupMyChannels();
|
||||
[[nodiscard]] auto setupRecommendations()
|
||||
[[nodiscard]] auto setupEmptyRecent()
|
||||
-> object_ptr<Ui::SlideWrap<Ui::RpWidget>>;
|
||||
|
||||
[[nodiscard]] std::unique_ptr<ObjectList> setupMyChannels();
|
||||
[[nodiscard]] std::unique_ptr<ObjectList> setupRecommendations();
|
||||
[[nodiscard]] auto setupEmptyChannels()
|
||||
-> object_ptr<Ui::SlideWrap<Ui::RpWidget>>;
|
||||
|
||||
[[nodiscard]] std::unique_ptr<ObjectList> setupRecentApps();
|
||||
[[nodiscard]] std::unique_ptr<ObjectList> setupPopularApps();
|
||||
|
||||
[[nodiscard]] std::unique_ptr<ObjectList> setupObjectList(
|
||||
not_null<Ui::ElasticScroll*> scroll,
|
||||
not_null<Ui::VerticalLayout*> parent,
|
||||
not_null<ObjectListController*> controller,
|
||||
Fn<int()> addToScroll = nullptr);
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::SlideWrap<Ui::RpWidget>> setupEmpty(
|
||||
not_null<QWidget*> parent,
|
||||
SearchEmptyIcon icon,
|
||||
@@ -119,7 +157,7 @@ private:
|
||||
|
||||
void switchTab(Tab tab);
|
||||
void startShownAnimation(bool shown, Fn<void()> finish);
|
||||
void startSlideAnimation();
|
||||
void startSlideAnimation(Tab was, Tab now);
|
||||
void finishShow();
|
||||
|
||||
void handlePressForChatPreview(PeerId id, Fn<void(bool)> callback);
|
||||
@@ -131,43 +169,28 @@ private:
|
||||
|
||||
const std::unique_ptr<Ui::ElasticScroll> _chatsScroll;
|
||||
const not_null<Ui::VerticalLayout*> _chatsContent;
|
||||
|
||||
const not_null<Ui::SlideWrap<TopPeersStrip>*> _topPeersWrap;
|
||||
const not_null<TopPeersStrip*> _topPeers;
|
||||
rpl::event_stream<not_null<PeerData*>> _topPeerChosen;
|
||||
|
||||
const std::unique_ptr<ObjectList> _recent;
|
||||
|
||||
rpl::variable<int> _recentCount;
|
||||
Fn<bool()> _recentPeersChoose;
|
||||
Fn<JumpResult(Qt::Key, int)> _recentSelectJump;
|
||||
Fn<uint64(QPoint)> _recentUpdateFromParentDrag;
|
||||
Fn<void()> _recentDragLeft;
|
||||
Fn<bool(not_null<QTouchEvent*>)> _recentProcessTouch;
|
||||
const not_null<Ui::SlideWrap<Ui::RpWidget>*> _recentPeers;
|
||||
const not_null<Ui::SlideWrap<Ui::RpWidget>*> _emptyRecent;
|
||||
|
||||
const std::unique_ptr<Ui::ElasticScroll> _channelsScroll;
|
||||
const not_null<Ui::VerticalLayout*> _channelsContent;
|
||||
|
||||
rpl::variable<int> _myChannelsCount;
|
||||
Fn<bool()> _myChannelsChoose;
|
||||
Fn<JumpResult(Qt::Key, int)> _myChannelsSelectJump;
|
||||
Fn<uint64(QPoint)> _myChannelsUpdateFromParentDrag;
|
||||
Fn<void()> _myChannelsDragLeft;
|
||||
Fn<bool(not_null<QTouchEvent*>)> _myChannelsProcessTouch;
|
||||
const not_null<Ui::SlideWrap<Ui::RpWidget>*> _myChannels;
|
||||
|
||||
rpl::variable<int> _recommendationsCount;
|
||||
Fn<bool()> _recommendationsChoose;
|
||||
Fn<JumpResult(Qt::Key, int)> _recommendationsSelectJump;
|
||||
Fn<uint64(QPoint)> _recommendationsUpdateFromParentDrag;
|
||||
Fn<void()> _recommendationsDragLeft;
|
||||
Fn<bool(not_null<QTouchEvent*>)> _recommendationsProcessTouch;
|
||||
const not_null<Ui::SlideWrap<Ui::RpWidget>*> _recommendations;
|
||||
const std::unique_ptr<ObjectList> _myChannels;
|
||||
const std::unique_ptr<ObjectList> _recommendations;
|
||||
|
||||
const not_null<Ui::SlideWrap<Ui::RpWidget>*> _emptyChannels;
|
||||
|
||||
rpl::event_stream<not_null<PeerData*>> _topPeerChosen;
|
||||
rpl::event_stream<not_null<PeerData*>> _recentPeerChosen;
|
||||
rpl::event_stream<not_null<PeerData*>> _myChannelChosen;
|
||||
rpl::event_stream<not_null<PeerData*>> _recommendationChosen;
|
||||
const std::unique_ptr<Ui::ElasticScroll> _appsScroll;
|
||||
const not_null<Ui::VerticalLayout*> _appsContent;
|
||||
|
||||
const std::unique_ptr<ObjectList> _recentApps;
|
||||
const std::unique_ptr<ObjectList> _popularApps;
|
||||
|
||||
Ui::Animations::Simple _shownAnimation;
|
||||
Fn<void()> _showFinished;
|
||||
@@ -179,6 +202,9 @@ private:
|
||||
QPixmap _slideLeft;
|
||||
QPixmap _slideRight;
|
||||
|
||||
int _slideLeftTop = 0;
|
||||
int _slideRightTop = 0;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] rpl::producer<TopPeersList> TopPeersContent(
|
||||
|
||||
@@ -1522,6 +1522,13 @@ ServiceAction ParseServiceAction(
|
||||
content.peerId = ParsePeerId(data.vpeer());
|
||||
content.transactionId = data.vcharge().data().vid().v;
|
||||
result.content = content;
|
||||
}, [&](const MTPDmessageActionGiftStars &data) {
|
||||
auto content = ActionGiftStars();
|
||||
content.cost = Ui::FillAmountAndCurrency(
|
||||
data.vamount().v,
|
||||
qs(data.vcurrency())).toUtf8();
|
||||
content.stars = data.vstars().v;
|
||||
result.content = content;
|
||||
}, [](const MTPDmessageActionEmpty &data) {});
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -529,7 +529,7 @@ struct ActionWebViewDataSent {
|
||||
|
||||
struct ActionGiftPremium {
|
||||
Utf8String cost;
|
||||
int months;
|
||||
int months = 0;
|
||||
};
|
||||
|
||||
struct ActionTopicCreate {
|
||||
@@ -583,6 +583,11 @@ struct ActionPaymentRefunded {
|
||||
Utf8String transactionId;
|
||||
};
|
||||
|
||||
struct ActionGiftStars {
|
||||
Utf8String cost;
|
||||
int stars = 0;
|
||||
};
|
||||
|
||||
struct ServiceAction {
|
||||
std::variant<
|
||||
v::null_t,
|
||||
@@ -625,7 +630,8 @@ struct ServiceAction {
|
||||
ActionGiveawayLaunch,
|
||||
ActionGiveawayResults,
|
||||
ActionBoostApply,
|
||||
ActionPaymentRefunded> content;
|
||||
ActionPaymentRefunded,
|
||||
ActionGiftStars> content;
|
||||
};
|
||||
|
||||
ServiceAction ParseServiceAction(
|
||||
|
||||
@@ -1321,6 +1321,16 @@ auto HtmlWriter::Wrap::pushMessage(
|
||||
+ " refunded back "
|
||||
+ amount;
|
||||
return result;
|
||||
}, [&](const ActionGiftStars &data) {
|
||||
if (!data.stars || data.cost.isEmpty()) {
|
||||
return serviceFrom + " sent you a gift.";
|
||||
}
|
||||
return serviceFrom
|
||||
+ " sent you a gift for "
|
||||
+ data.cost
|
||||
+ ": "
|
||||
+ QString::number(data.stars).toUtf8()
|
||||
+ " Telegram Stars.";
|
||||
}, [](v::null_t) { return QByteArray(); });
|
||||
|
||||
if (!serviceText.isEmpty()) {
|
||||
|
||||
@@ -632,6 +632,15 @@ QByteArray SerializeMessage(
|
||||
pushBare("peer_name", wrapPeerName(data.peerId));
|
||||
push("peer_id", data.peerId);
|
||||
push("charge_id", data.transactionId);
|
||||
}, [&](const ActionGiftStars &data) {
|
||||
pushActor();
|
||||
pushAction("send_stars_gift");
|
||||
if (!data.cost.isEmpty()) {
|
||||
push("cost", data.cost);
|
||||
}
|
||||
if (data.stars) {
|
||||
push("stars", data.stars);
|
||||
}
|
||||
}, [](v::null_t) {});
|
||||
|
||||
if (v::is_null(message.action.content)) {
|
||||
|
||||
@@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/history_item_helpers.h"
|
||||
#include "history/history_translation.h"
|
||||
#include "history/history_unread_things.h"
|
||||
#include "core/ui_integration.h"
|
||||
#include "dialogs/ui/dialogs_layout.h"
|
||||
#include "data/business/data_shortcut_messages.h"
|
||||
#include "data/components/scheduled_messages.h"
|
||||
@@ -1128,14 +1129,23 @@ void History::applyServiceChanges(
|
||||
}
|
||||
if (paid) {
|
||||
// Toast on a current active window.
|
||||
const auto context = [=](not_null<QWidget*> toast) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = &session(),
|
||||
.customEmojiRepaint = [=] { toast->update(); },
|
||||
};
|
||||
};
|
||||
Ui::Toast::Show({
|
||||
.text = tr::lng_payments_success(
|
||||
tr::now,
|
||||
lt_amount,
|
||||
Ui::Text::Bold(payment->amount),
|
||||
Ui::Text::Wrapped(
|
||||
payment->amount,
|
||||
EntityType::Bold),
|
||||
lt_title,
|
||||
Ui::Text::Bold(paid->title),
|
||||
Ui::Text::WithEntities),
|
||||
.textContext = context,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/click_handler_types.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "base/timer_rpl.h"
|
||||
#include "boxes/send_credits_box.h"
|
||||
#include "api/api_text_entities.h"
|
||||
#include "api/api_updates.h"
|
||||
#include "data/components/scheduled_messages.h"
|
||||
@@ -137,6 +138,17 @@ template <typename T>
|
||||
return fields;
|
||||
}
|
||||
|
||||
[[nodiscard]] TextWithEntities AmountAndStarCurrency(
|
||||
not_null<Main::Session*> session,
|
||||
int64 amount,
|
||||
const QString ¤cy) {
|
||||
if (currency == Ui::kCreditsCurrency) {
|
||||
return Ui::CreditsEmojiSmall(session).append(
|
||||
Lang::FormatCountDecimal(std::abs(amount)));
|
||||
}
|
||||
return { Ui::FillAmountAndCurrency(amount, currency) };
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void HistoryItem::HistoryItem::Destroyer::operator()(HistoryItem *value) {
|
||||
@@ -3957,7 +3969,10 @@ void HistoryItem::createServiceFromMtp(const MTPDmessageService &message) {
|
||||
payment->recurringInit = data.is_recurring_init();
|
||||
payment->recurringUsed = data.is_recurring_used();
|
||||
payment->isCreditsCurrency = (currency == Ui::kCreditsCurrency);
|
||||
payment->amount = Ui::FillAmountAndCurrency(amount, currency);
|
||||
payment->amount = AmountAndStarCurrency(
|
||||
&_history->session(),
|
||||
amount,
|
||||
currency);
|
||||
payment->invoiceLink = std::make_shared<LambdaClickHandler>([=](
|
||||
ClickContext context) {
|
||||
using namespace Payments;
|
||||
@@ -4679,21 +4694,32 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
|
||||
auto prepareGiftPremium = [&](
|
||||
const MTPDmessageActionGiftPremium &action) {
|
||||
auto result = PreparedServiceText();
|
||||
const auto isSelf = (_from->id == _from->session().userPeerId());
|
||||
const auto session = &_history->session();
|
||||
const auto isSelf = _from->isSelf();
|
||||
const auto peer = isSelf ? _history->peer : _from;
|
||||
_history->session().giftBoxStickersPacks().load();
|
||||
session->giftBoxStickersPacks().load();
|
||||
const auto amount = action.vamount().v;
|
||||
const auto currency = qs(action.vcurrency());
|
||||
result.links.push_back(peer->createOpenLink());
|
||||
result.text = (isSelf
|
||||
? tr::lng_action_gift_received_me
|
||||
: tr::lng_action_gift_received)(
|
||||
const auto cost = AmountAndStarCurrency(session, amount, currency);
|
||||
const auto anonymous = _from->isServiceUser();
|
||||
if (anonymous) {
|
||||
result.text = tr::lng_action_gift_received_anonymous(
|
||||
tr::now,
|
||||
lt_user,
|
||||
Ui::Text::Link(peer->name(), 1), // Link 1.
|
||||
lt_cost,
|
||||
{ Ui::FillAmountAndCurrency(amount, currency) },
|
||||
cost,
|
||||
Ui::Text::WithEntities);
|
||||
} else {
|
||||
result.links.push_back(peer->createOpenLink());
|
||||
result.text = (isSelf
|
||||
? tr::lng_action_gift_received_me
|
||||
: tr::lng_action_gift_received)(
|
||||
tr::now,
|
||||
lt_user,
|
||||
Ui::Text::Link(peer->name(), 1), // Link 1.
|
||||
lt_cost,
|
||||
cost,
|
||||
Ui::Text::WithEntities);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -4905,9 +4931,10 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
|
||||
lt_user,
|
||||
Ui::Text::Link(peer->name(), 1), // Link 1.
|
||||
lt_cost,
|
||||
{ Ui::FillAmountAndCurrency(
|
||||
AmountAndStarCurrency(
|
||||
&_history->session(),
|
||||
action.vamount().value_or_empty(),
|
||||
qs(action.vcurrency().value_or_empty())) },
|
||||
qs(action.vcurrency().value_or_empty())),
|
||||
Ui::Text::WithEntities);
|
||||
|
||||
}
|
||||
@@ -4957,7 +4984,6 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
|
||||
Ui::Text::WithEntities);
|
||||
return result;
|
||||
};
|
||||
|
||||
auto preparePaymentRefunded = [&](const MTPDmessageActionPaymentRefunded &action) {
|
||||
auto result = PreparedServiceText();
|
||||
const auto refund = Get<HistoryServicePaymentRefund>();
|
||||
@@ -4972,11 +4998,35 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
|
||||
lt_peer,
|
||||
Ui::Text::Link(refund->peer->name(), 1), // Link 1.
|
||||
lt_amount,
|
||||
{ Ui::FillAmountAndCurrency(amount, currency) },
|
||||
AmountAndStarCurrency(&_history->session(), amount, currency),
|
||||
Ui::Text::WithEntities);
|
||||
return result;
|
||||
};
|
||||
|
||||
auto prepareGiftStars = [&](
|
||||
const MTPDmessageActionGiftStars &action) {
|
||||
auto result = PreparedServiceText();
|
||||
const auto isSelf = (_from->id == _from->session().userPeerId());
|
||||
const auto peer = isSelf ? _history->peer : _from;
|
||||
_history->session().giftBoxStickersPacks().load();
|
||||
const auto amount = action.vamount().v;
|
||||
const auto currency = qs(action.vcurrency());
|
||||
result.links.push_back(peer->createOpenLink());
|
||||
result.text = (isSelf
|
||||
? tr::lng_action_gift_received_me
|
||||
: tr::lng_action_gift_received)(
|
||||
tr::now,
|
||||
lt_user,
|
||||
Ui::Text::Link(peer->name(), 1), // Link 1.
|
||||
lt_cost,
|
||||
AmountAndStarCurrency(
|
||||
&_history->session(),
|
||||
amount,
|
||||
currency),
|
||||
Ui::Text::WithEntities);
|
||||
return result;
|
||||
};
|
||||
|
||||
setServiceText(action.match(
|
||||
prepareChatAddUserText,
|
||||
prepareChatJoinedByLink,
|
||||
@@ -5020,6 +5070,7 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
|
||||
prepareGiveawayResults,
|
||||
prepareBoostApply,
|
||||
preparePaymentRefunded,
|
||||
prepareGiftStars,
|
||||
PrepareEmptyText<MTPDmessageActionRequestedPeerSentMe>,
|
||||
PrepareErrorText<MTPDmessageActionEmpty>));
|
||||
|
||||
@@ -5072,6 +5123,7 @@ void HistoryItem::applyAction(const MTPMessageAction &action) {
|
||||
_media = std::make_unique<Data::MediaGiftBox>(
|
||||
this,
|
||||
_from,
|
||||
Data::GiftType::Premium,
|
||||
data.vmonths().v);
|
||||
}, [&](const MTPDmessageActionSuggestProfilePhoto &data) {
|
||||
data.vphoto().match([&](const MTPDphoto &photo) {
|
||||
@@ -5106,10 +5158,17 @@ void HistoryItem::applyAction(const MTPMessageAction &action) {
|
||||
.channel = (boostedId
|
||||
? history()->owner().channel(boostedId).get()
|
||||
: nullptr),
|
||||
.months = data.vmonths().v,
|
||||
.count = data.vmonths().v,
|
||||
.type = Data::GiftType::Premium,
|
||||
.viaGiveaway = data.is_via_giveaway(),
|
||||
.unclaimed = data.is_unclaimed(),
|
||||
});
|
||||
}, [&](const MTPDmessageActionGiftStars &data) {
|
||||
_media = std::make_unique<Data::MediaGiftBox>(
|
||||
this,
|
||||
_from,
|
||||
Data::GiftType::Stars,
|
||||
data.vstars().v);
|
||||
}, [](const auto &) {
|
||||
});
|
||||
}
|
||||
@@ -5382,7 +5441,7 @@ PreparedServiceText HistoryItem::preparePaymentSentText() {
|
||||
result.text = tr::lng_action_payment_used_recurring(
|
||||
tr::now,
|
||||
lt_amount,
|
||||
{ .text = payment->amount },
|
||||
payment->amount,
|
||||
Ui::Text::WithEntities);
|
||||
} else {
|
||||
result.text = (payment->recurringInit
|
||||
@@ -5390,7 +5449,7 @@ PreparedServiceText HistoryItem::preparePaymentSentText() {
|
||||
: tr::lng_action_payment_done)(
|
||||
tr::now,
|
||||
lt_amount,
|
||||
{ .text = payment->amount },
|
||||
payment->amount,
|
||||
lt_user,
|
||||
{ .text = _history->peer->name() },
|
||||
Ui::Text::WithEntities);
|
||||
@@ -5401,7 +5460,7 @@ PreparedServiceText HistoryItem::preparePaymentSentText() {
|
||||
: tr::lng_action_payment_done_for)(
|
||||
tr::now,
|
||||
lt_amount,
|
||||
{ .text = payment->amount },
|
||||
payment->amount,
|
||||
lt_user,
|
||||
{ .text = _history->peer->name() },
|
||||
lt_invoice,
|
||||
|
||||
@@ -649,7 +649,7 @@ struct HistoryServicePayment
|
||||
: public RuntimeComponent<HistoryServicePayment, HistoryItem>
|
||||
, public HistoryServiceDependentData {
|
||||
QString slug;
|
||||
QString amount;
|
||||
TextWithEntities amount;
|
||||
ClickHandlerPtr invoiceLink;
|
||||
bool recurringInit = false;
|
||||
bool recurringUsed = false;
|
||||
|
||||
@@ -59,7 +59,8 @@ PeerId GenerateUser(not_null<History*> history, const QString &name) {
|
||||
MTPVector<MTPUsername>(),
|
||||
MTPint(), // stories_max_id
|
||||
MTPPeerColor(), // color
|
||||
MTPPeerColor())); // profile_color
|
||||
MTPPeerColor(), // profile_color
|
||||
MTPint())); // bot_active_users
|
||||
return peerId;
|
||||
}
|
||||
|
||||
|
||||
@@ -2406,10 +2406,10 @@ TextState Message::textState(
|
||||
if (getStateForwardedInfo(point, trect, &result, request)) {
|
||||
return result;
|
||||
}
|
||||
if (getStateReplyInfo(point, trect, &result)) {
|
||||
if (getStateViaBotIdInfo(point, trect, &result)) {
|
||||
return result;
|
||||
}
|
||||
if (getStateViaBotIdInfo(point, trect, &result)) {
|
||||
if (getStateReplyInfo(point, trect, &result)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/click_handler_types.h" // ClickHandlerContext
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_user.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "settings/settings_credits.h" // Settings::CreditsId
|
||||
#include "settings/settings_credits_graphics.h" // GiftedCreditsBox
|
||||
#include "settings/settings_premium.h" // Settings::ShowGiftPremium
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_chat.h"
|
||||
@@ -45,6 +49,9 @@ QSize PremiumGift::size() {
|
||||
}
|
||||
|
||||
QString PremiumGift::title() {
|
||||
if (const auto count = stars()) {
|
||||
return tr::lng_gift_stars_title(tr::now, lt_count, count);
|
||||
}
|
||||
return gift()
|
||||
? tr::lng_premium_summary_title(tr::now)
|
||||
: _data.unclaimed
|
||||
@@ -53,8 +60,16 @@ QString PremiumGift::title() {
|
||||
}
|
||||
|
||||
TextWithEntities PremiumGift::subtitle() {
|
||||
if (gift()) {
|
||||
return { GiftDuration(_data.months) };
|
||||
if (const auto count = stars()) {
|
||||
return outgoingGift()
|
||||
? tr::lng_gift_stars_outgoing(
|
||||
tr::now,
|
||||
lt_user,
|
||||
Ui::Text::Bold(_parent->history()->peer->shortName()),
|
||||
Ui::Text::WithEntities)
|
||||
: tr::lng_gift_stars_incoming(tr::now, Ui::Text::WithEntities);
|
||||
} else if (gift()) {
|
||||
return { GiftDuration(_data.count) };
|
||||
}
|
||||
const auto name = _data.channel ? _data.channel->name() : "channel";
|
||||
auto result = (_data.unclaimed
|
||||
@@ -74,7 +89,7 @@ TextWithEntities PremiumGift::subtitle() {
|
||||
: tr::lng_prize_gift_duration)(
|
||||
tr::now,
|
||||
lt_duration,
|
||||
Ui::Text::Bold(GiftDuration(_data.months)),
|
||||
Ui::Text::Bold(GiftDuration(_data.count)),
|
||||
Ui::Text::RichLangValue));
|
||||
return result;
|
||||
}
|
||||
@@ -87,20 +102,29 @@ rpl::producer<QString> PremiumGift::button() {
|
||||
|
||||
ClickHandlerPtr PremiumGift::createViewLink() {
|
||||
const auto from = _gift->from();
|
||||
const auto to = _parent->history()->peer;
|
||||
const auto peer = _parent->history()->peer;
|
||||
const auto date = _parent->data()->date();
|
||||
const auto data = _gift->data();
|
||||
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
|
||||
const auto my = context.other.value<ClickHandlerContext>();
|
||||
if (const auto controller = my.sessionWindow.get()) {
|
||||
const auto selfId = controller->session().userPeerId();
|
||||
const auto self = (from->id == selfId);
|
||||
if (data.slug.isEmpty()) {
|
||||
const auto peer = self ? to : from;
|
||||
const auto months = data.months;
|
||||
Settings::ShowGiftPremium(controller, peer, months, self);
|
||||
const auto sent = (from->id == selfId);
|
||||
if (data.type == Data::GiftType::Stars) {
|
||||
const auto to = sent ? peer : peer->session().user();
|
||||
controller->show(Box(
|
||||
Settings::GiftedCreditsBox,
|
||||
controller,
|
||||
from,
|
||||
to,
|
||||
data.count,
|
||||
date));
|
||||
} else if (data.slug.isEmpty()) {
|
||||
const auto months = data.count;
|
||||
Settings::ShowGiftPremium(controller, peer, months, sent);
|
||||
} else {
|
||||
const auto fromId = from->id;
|
||||
const auto toId = self ? to->id : selfId;
|
||||
const auto toId = sent ? peer->id : selfId;
|
||||
ResolveGiftCode(controller, data.slug, fromId, toId);
|
||||
}
|
||||
}
|
||||
@@ -162,13 +186,18 @@ bool PremiumGift::gift() const {
|
||||
return _data.slug.isEmpty() || !_data.channel;
|
||||
}
|
||||
|
||||
int PremiumGift::stars() const {
|
||||
return (_data.type == Data::GiftType::Stars) ? _data.count : 0;
|
||||
}
|
||||
|
||||
void PremiumGift::ensureStickerCreated() const {
|
||||
if (_sticker) {
|
||||
return;
|
||||
}
|
||||
const auto &session = _parent->history()->session();
|
||||
const auto months = _gift->data().months;
|
||||
auto &packs = session.giftBoxStickersPacks();
|
||||
const auto count = stars();
|
||||
const auto months = count ? packs.monthsForStars(count) : _data.count;
|
||||
if (const auto document = packs.lookup(months)) {
|
||||
if (const auto sticker = document->sticker()) {
|
||||
const auto skipPremiumEffect = false;
|
||||
|
||||
@@ -49,6 +49,7 @@ private:
|
||||
[[nodiscard]] bool incomingGift() const;
|
||||
[[nodiscard]] bool outgoingGift() const;
|
||||
[[nodiscard]] bool gift() const;
|
||||
[[nodiscard]] int stars() const;
|
||||
void ensureStickerCreated() const;
|
||||
|
||||
const not_null<Element*> _parent;
|
||||
|
||||
@@ -20,12 +20,17 @@ namespace Info::Statistics {
|
||||
|
||||
not_null<Ui::RpWidget*> InfiniteRadialAnimationWidget(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
int size) {
|
||||
int size,
|
||||
const style::InfiniteRadialAnimation *st) {
|
||||
class Widget final : public Ui::RpWidget {
|
||||
public:
|
||||
Widget(not_null<Ui::RpWidget*> p, int size)
|
||||
Widget(
|
||||
not_null<Ui::RpWidget*> p,
|
||||
int size,
|
||||
const style::InfiniteRadialAnimation *st)
|
||||
: Ui::RpWidget(p)
|
||||
, _animation([=] { update(); }, st::startGiveawayButtonLoading) {
|
||||
, _st(st ? st : &st::startGiveawayButtonLoading)
|
||||
, _animation([=] { update(); }, *_st) {
|
||||
resize(size, size);
|
||||
shownValue() | rpl::start_with_next([=](bool v) {
|
||||
return v
|
||||
@@ -39,17 +44,17 @@ not_null<Ui::RpWidget*> InfiniteRadialAnimationWidget(
|
||||
auto p = QPainter(this);
|
||||
p.setPen(st::activeButtonFg);
|
||||
p.setBrush(st::activeButtonFg);
|
||||
const auto r = rect()
|
||||
- Margins(st::startGiveawayButtonLoading.thickness);
|
||||
const auto r = rect() - Margins(_st->thickness);
|
||||
_animation.draw(p, r.topLeft(), r.size(), width());
|
||||
}
|
||||
|
||||
private:
|
||||
const style::InfiniteRadialAnimation *_st;
|
||||
Ui::InfiniteRadialAnimation _animation;
|
||||
|
||||
};
|
||||
|
||||
return Ui::CreateChild<Widget>(parent.get(), size);
|
||||
return Ui::CreateChild<Widget>(parent.get(), size, st);
|
||||
}
|
||||
|
||||
void AddChildToWidgetCenter(
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#pragma once
|
||||
|
||||
namespace style {
|
||||
struct InfiniteRadialAnimation;
|
||||
struct TextStyle;
|
||||
} // namespace style
|
||||
|
||||
@@ -30,7 +31,8 @@ namespace Info::Statistics {
|
||||
|
||||
[[nodiscard]] not_null<Ui::RpWidget*> InfiniteRadialAnimationWidget(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
int size);
|
||||
int size,
|
||||
const style::InfiniteRadialAnimation *st = nullptr);
|
||||
|
||||
void AddChildToWidgetCenter(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
|
||||
@@ -469,6 +469,12 @@ infoSharedMediaButtonIconPosition: point(20px, 3px);
|
||||
infoGroupMembersIconPosition: point(20px, 10px);
|
||||
infoChannelMembersIconPosition: point(20px, 19px);
|
||||
|
||||
infoOpenApp: RoundButton(defaultActiveButton) {
|
||||
textTop: 11px;
|
||||
height: 40px;
|
||||
}
|
||||
infoOpenAppMargin: margins(16px, 12px, 16px, 12px);
|
||||
|
||||
infoPersonalChannelIconPosition: point(25px, 20px);
|
||||
infoPersonalChannelNameLabel: FlatLabel(infoProfileStatus) {
|
||||
textFg: windowBoldFg;
|
||||
|
||||
@@ -45,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "info/profile/info_profile_text.h"
|
||||
#include "info/profile/info_profile_values.h"
|
||||
#include "info/profile/info_profile_widget.h"
|
||||
#include "inline_bots/bot_attach_web_view.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "menu/menu_mute.h"
|
||||
@@ -776,6 +777,7 @@ private:
|
||||
object_ptr<Ui::RpWidget> setupPersonalChannel(not_null<UserData*> user);
|
||||
object_ptr<Ui::RpWidget> setupInfo();
|
||||
object_ptr<Ui::RpWidget> setupMuteToggle();
|
||||
void setupMainApp();
|
||||
void setupMainButtons();
|
||||
Ui::MultiSlideTracker fillTopicButtons();
|
||||
Ui::MultiSlideTracker fillUserButtons(
|
||||
@@ -1209,10 +1211,21 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
|
||||
}
|
||||
if (!_peer->isSelf()) {
|
||||
// No notifications toggle for Self => no separator.
|
||||
|
||||
const auto user = _peer->asUser();
|
||||
const auto app = user && user->botInfo && user->botInfo->hasMainApp;
|
||||
const auto padding = app
|
||||
? QMargins(
|
||||
st::infoOpenAppMargin.left(),
|
||||
st::infoProfileSeparatorPadding.top(),
|
||||
st::infoOpenAppMargin.right(),
|
||||
0)
|
||||
: st::infoProfileSeparatorPadding;
|
||||
|
||||
result->add(object_ptr<Ui::SlideWrap<>>(
|
||||
result,
|
||||
object_ptr<Ui::PlainShadow>(result),
|
||||
st::infoProfileSeparatorPadding)
|
||||
padding)
|
||||
)->setDuration(
|
||||
st::infoSlideDuration
|
||||
)->toggleOn(
|
||||
@@ -1548,6 +1561,42 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupMuteToggle() {
|
||||
return result;
|
||||
}
|
||||
|
||||
void DetailsFiller::setupMainApp() {
|
||||
const auto button = _wrap->add(
|
||||
object_ptr<Ui::RoundButton>(
|
||||
_wrap,
|
||||
tr::lng_profile_open_app(),
|
||||
st::infoOpenApp),
|
||||
st::infoOpenAppMargin);
|
||||
button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
|
||||
|
||||
const auto user = _peer->asUser();
|
||||
const auto controller = _controller->parentController();
|
||||
button->setClickedCallback([=] {
|
||||
user->session().attachWebView().open({
|
||||
.bot = user,
|
||||
.context = {
|
||||
.controller = controller,
|
||||
.maySkipConfirmation = true,
|
||||
},
|
||||
.source = InlineBots::WebViewSourceBotProfile(),
|
||||
});
|
||||
});
|
||||
|
||||
const auto url = tr::lng_mini_apps_tos_url(tr::now);
|
||||
Ui::AddDividerText(
|
||||
_wrap,
|
||||
tr::lng_profile_open_app_about(
|
||||
lt_terms,
|
||||
tr::lng_profile_open_app_terms() | Ui::Text::ToLink(url),
|
||||
Ui::Text::WithEntities)
|
||||
)->setClickHandlerFilter([=](const auto &...) {
|
||||
UrlClickHandler::Open(url);
|
||||
return false;
|
||||
});
|
||||
Ui::AddSkip(_wrap);
|
||||
}
|
||||
|
||||
void DetailsFiller::setupMainButtons() {
|
||||
auto wrapButtons = [=](auto &&callback) {
|
||||
auto topSkip = _wrap->add(CreateSlideSkipWidget(_wrap));
|
||||
@@ -1737,6 +1786,13 @@ object_ptr<Ui::RpWidget> DetailsFiller::fill() {
|
||||
add(object_ptr<Ui::BoxContentDivider>(_wrap));
|
||||
add(CreateSkipWidget(_wrap));
|
||||
add(setupInfo());
|
||||
if (const auto user = _peer->asUser()) {
|
||||
if (const auto info = user->botInfo.get()) {
|
||||
if (info->hasMainApp) {
|
||||
setupMainApp();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!_peer->isSelf()) {
|
||||
add(setupMuteToggle());
|
||||
}
|
||||
|
||||
@@ -800,7 +800,9 @@ void CreditsRow::init() {
|
||||
: _entry.failed
|
||||
? (joiner + tr::lng_channel_earn_history_failed(tr::now))
|
||||
: QString())
|
||||
+ (_entry.title.isEmpty() ? QString() : (joiner + _name)));
|
||||
+ ((_entry.gift && PeerListRow::special())
|
||||
? (joiner + tr::lng_credits_box_history_entry_anonymous(tr::now))
|
||||
: (_entry.title.isEmpty() ? QString() : (joiner + _name))));
|
||||
{
|
||||
constexpr auto kMinus = QChar(0x2212);
|
||||
_rightText.setText(
|
||||
|
||||
@@ -69,6 +69,7 @@ namespace {
|
||||
|
||||
constexpr auto kProlongTimeout = 60 * crl::time(1000);
|
||||
constexpr auto kRefreshBotsTimeout = 60 * 60 * crl::time(1000);
|
||||
constexpr auto kPopularAppBotsLimit = 100;
|
||||
|
||||
[[nodiscard]] DocumentData *ResolveIcon(
|
||||
not_null<Main::Session*> session,
|
||||
@@ -651,7 +652,9 @@ void WebViewInstance::resolve() {
|
||||
}, [&](WebViewSourceLinkApp data) {
|
||||
resolveApp(data.appname, data.token, !_context.maySkipConfirmation);
|
||||
}, [&](WebViewSourceLinkBotProfile) {
|
||||
requestWithMenuAdd();
|
||||
confirmOpen([=] {
|
||||
requestMain();
|
||||
});
|
||||
}, [&](WebViewSourceLinkAttachMenu data) {
|
||||
requestWithMenuAdd();
|
||||
}, [&](WebViewSourceMainMenu) {
|
||||
@@ -667,7 +670,13 @@ void WebViewInstance::resolve() {
|
||||
}, [&](WebViewSourceGame game) {
|
||||
showGame();
|
||||
}, [&](WebViewSourceBotProfile) {
|
||||
requestWithMenuAdd();
|
||||
if (_context.maySkipConfirmation) {
|
||||
requestMain();
|
||||
} else {
|
||||
confirmOpen([=] {
|
||||
requestMain();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -753,6 +762,10 @@ void WebViewInstance::confirmOpen(Fn<void()> done) {
|
||||
close();
|
||||
done();
|
||||
};
|
||||
const auto cancel = [=](Fn<void()> close) {
|
||||
botClose();
|
||||
close();
|
||||
};
|
||||
_parentShow->show(Ui::MakeConfirmBox({
|
||||
.text = tr::lng_allow_bot_webview(
|
||||
tr::now,
|
||||
@@ -760,7 +773,7 @@ void WebViewInstance::confirmOpen(Fn<void()> done) {
|
||||
Ui::Text::Bold(_bot->name()),
|
||||
Ui::Text::RichLangValue),
|
||||
.confirmed = crl::guard(this, callback),
|
||||
.cancelled = crl::guard(this, [=] { botClose(); }),
|
||||
.cancelled = crl::guard(this, cancel),
|
||||
.confirmText = tr::lng_box_ok(),
|
||||
}));
|
||||
}
|
||||
@@ -774,6 +787,10 @@ void WebViewInstance::confirmAppOpen(
|
||||
done((*allowed) && (*allowed)->checked());
|
||||
close();
|
||||
};
|
||||
const auto cancelled = [=](Fn<void()> close) {
|
||||
botClose();
|
||||
close();
|
||||
};
|
||||
Ui::ConfirmBox(box, {
|
||||
tr::lng_allow_bot_webview(
|
||||
tr::now,
|
||||
@@ -781,7 +798,7 @@ void WebViewInstance::confirmAppOpen(
|
||||
Ui::Text::Bold(_bot->name()),
|
||||
Ui::Text::RichLangValue),
|
||||
crl::guard(this, callback),
|
||||
crl::guard(this, [=] { botClose(); }),
|
||||
crl::guard(this, cancelled),
|
||||
});
|
||||
if (writeAccess) {
|
||||
(*allowed) = box->addRow(
|
||||
@@ -868,6 +885,31 @@ void WebViewInstance::requestSimple() {
|
||||
}).send();
|
||||
}
|
||||
|
||||
void WebViewInstance::requestMain() {
|
||||
using Flag = MTPmessages_RequestMainWebView::Flag;
|
||||
_requestId = _session->api().request(MTPmessages_RequestMainWebView(
|
||||
MTP_flags(Flag::f_theme_params
|
||||
| (_button.startCommand.isEmpty()
|
||||
? Flag()
|
||||
: Flag::f_start_param)
|
||||
| (v::is<WebViewSourceLinkBotProfile>(_source)
|
||||
? (v::get<WebViewSourceLinkBotProfile>(_source).compact
|
||||
? Flag::f_compact
|
||||
: Flag(0))
|
||||
: Flag(0))),
|
||||
_context.action->history->peer->input,
|
||||
_bot->inputUser,
|
||||
MTP_string(_button.startCommand),
|
||||
MTP_dataJSON(MTP_bytes(botThemeParams().json)),
|
||||
MTP_string("tdesktop")
|
||||
)).done([=](const MTPWebViewResult &result) {
|
||||
show(qs(result.data().vurl()));
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_parentShow->showToast(error.type());
|
||||
close();
|
||||
}).send();
|
||||
}
|
||||
|
||||
void WebViewInstance::requestApp(bool allowWrite) {
|
||||
Expects(_app != nullptr);
|
||||
Expects(_context.action.has_value());
|
||||
@@ -1415,7 +1457,10 @@ AttachWebView::AttachWebView(not_null<Main::Session*> session)
|
||||
_refreshTimer.callEach(kRefreshBotsTimeout);
|
||||
}
|
||||
|
||||
AttachWebView::~AttachWebView() = default;
|
||||
AttachWebView::~AttachWebView() {
|
||||
closeAll();
|
||||
_session->api().request(_popularAppBotsRequestId).cancel();
|
||||
}
|
||||
|
||||
void AttachWebView::openByUsername(
|
||||
not_null<Window::SessionController*> controller,
|
||||
@@ -1472,6 +1517,40 @@ void AttachWebView::closeAll() {
|
||||
base::take(_instances);
|
||||
}
|
||||
|
||||
void AttachWebView::loadPopularAppBots() {
|
||||
if (_popularAppBotsLoaded.current() || _popularAppBotsRequestId) {
|
||||
return;
|
||||
}
|
||||
_popularAppBotsRequestId = _session->api().request(
|
||||
MTPbots_GetPopularAppBots(
|
||||
MTP_string(),
|
||||
MTP_int(kPopularAppBotsLimit))
|
||||
).done([=](const MTPbots_PopularAppBots &result) {
|
||||
_popularAppBotsRequestId = 0;
|
||||
|
||||
const auto &list = result.data().vusers().v;
|
||||
auto parsed = std::vector<not_null<UserData*>>();
|
||||
parsed.reserve(list.size());
|
||||
for (const auto &user : list) {
|
||||
const auto bot = _session->data().processUser(user);
|
||||
if (bot->isBot()) {
|
||||
parsed.push_back(bot);
|
||||
}
|
||||
}
|
||||
_popularAppBots = std::move(parsed);
|
||||
_popularAppBotsLoaded = true;
|
||||
}).send();
|
||||
}
|
||||
|
||||
auto AttachWebView::popularAppBots() const
|
||||
-> const std::vector<not_null<UserData*>> & {
|
||||
return _popularAppBots;
|
||||
}
|
||||
|
||||
rpl::producer<> AttachWebView::popularAppBotsLoaded() const {
|
||||
return _popularAppBotsLoaded.changes() | rpl::to_empty;
|
||||
}
|
||||
|
||||
void AttachWebView::cancel() {
|
||||
_session->api().request(base::take(_requestId)).cancel();
|
||||
_botUsername = QString();
|
||||
|
||||
@@ -239,6 +239,7 @@ private:
|
||||
|
||||
void requestButton();
|
||||
void requestSimple();
|
||||
void requestMain();
|
||||
void requestApp(bool allowWrite);
|
||||
void requestWithMainMenuDisclaimer();
|
||||
void requestWithMenuAdd();
|
||||
@@ -255,9 +256,6 @@ private:
|
||||
void showGame();
|
||||
void started(uint64 queryId);
|
||||
|
||||
[[nodiscard]] Window::SessionController *windowForThread(
|
||||
not_null<Data::Thread*> thread);
|
||||
|
||||
auto nonPanelPaymentFormFactory(
|
||||
Fn<void(Payments::CheckoutResult)> reactivate)
|
||||
-> Fn<void(Payments::NonPanelPaymentForm)>;
|
||||
@@ -351,6 +349,11 @@ public:
|
||||
void close(not_null<WebViewInstance*> instance);
|
||||
void closeAll();
|
||||
|
||||
void loadPopularAppBots();
|
||||
[[nodiscard]] auto popularAppBots() const
|
||||
-> const std::vector<not_null<UserData*>> &;
|
||||
[[nodiscard]] rpl::producer<> popularAppBotsLoaded() const;
|
||||
|
||||
private:
|
||||
void resolveUsername(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
@@ -394,6 +397,10 @@ private:
|
||||
|
||||
std::vector<std::unique_ptr<WebViewInstance>> _instances;
|
||||
|
||||
std::vector<not_null<UserData*>> _popularAppBots;
|
||||
mtpRequestId _popularAppBotsRequestId = 0;
|
||||
rpl::variable<bool> _popularAppBotsLoaded = false;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] std::unique_ptr<Ui::DropdownMenu> MakeAttachBotsMenu(
|
||||
|
||||
@@ -22,12 +22,21 @@ ivMenuToggle: IconButton(defaultIconButton) {
|
||||
}
|
||||
}
|
||||
ivMenuPosition: point(-2px, 40px);
|
||||
ivBackIcon: icon {{ "box_button_back", menuIconColor }};
|
||||
ivBack: IconButton(ivMenuToggle) {
|
||||
width: 60px;
|
||||
icon: icon {{ "box_button_back", menuIconColor }};
|
||||
iconOver: icon {{ "box_button_back", menuIconColor }};
|
||||
icon: ivBackIcon;
|
||||
iconOver: ivBackIcon;
|
||||
rippleAreaPosition: point(12px, 6px);
|
||||
}
|
||||
ivBackIconDisabled: icon {{ "box_button_back", menuIconFg }};
|
||||
ivForwardIcon: icon {{ "box_button_back-flip_horizontal", menuIconColor }};
|
||||
ivForward: IconButton(ivBack) {
|
||||
width: 48px;
|
||||
icon: ivForwardIcon;
|
||||
iconOver: ivForwardIcon;
|
||||
rippleAreaPosition: point(0px, 6px);
|
||||
}
|
||||
ivSubtitleFont: font(16px semibold);
|
||||
ivSubtitle: FlatLabel(defaultFlatLabel) {
|
||||
textFg: boxTitleFg;
|
||||
|
||||
@@ -11,8 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/invoke_queued.h"
|
||||
#include "base/qt_signal_producer.h"
|
||||
#include "base/qthelp_url.h"
|
||||
#include "core/file_utilities.h"
|
||||
#include "iv/iv_data.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/chat/attach/attach_bot_webview.h"
|
||||
#include "ui/platform/ui_platform_window_title.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
@@ -28,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/palette.h"
|
||||
#include "styles/style_iv.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
#include "styles/style_payments.h" // paymentsCriticalError
|
||||
#include "styles/style_widgets.h"
|
||||
#include "styles/style_window.h"
|
||||
|
||||
@@ -62,7 +65,7 @@ namespace {
|
||||
{ "box-divider-bg", &st::boxDividerBg },
|
||||
{ "box-divider-fg", &st::boxDividerFg },
|
||||
{ "light-button-fg", &st::lightButtonFg },
|
||||
{ "light-button-bg-over", &st::lightButtonBgOver },
|
||||
//{ "light-button-bg-over", &st::lightButtonBgOver },
|
||||
{ "menu-icon-fg", &st::menuIconFg },
|
||||
{ "menu-icon-fg-over", &st::menuIconFgOver },
|
||||
{ "menu-bg", &st::menuBg },
|
||||
@@ -79,7 +82,12 @@ namespace {
|
||||
static const auto phrases = base::flat_map<QByteArray, tr::phrase<>>{
|
||||
{ "iv-join-channel", tr::lng_iv_join_channel },
|
||||
};
|
||||
return Ui::ComputeStyles(map, phrases);
|
||||
return Ui::ComputeStyles(map, phrases)
|
||||
+ ';'
|
||||
+ Ui::ComputeSemiTransparentOverStyle(
|
||||
"light-button-bg-over",
|
||||
st::lightButtonBgOver,
|
||||
st::windowBg);
|
||||
}
|
||||
|
||||
[[nodiscard]] QByteArray WrapPage(const Prepared &page) {
|
||||
@@ -127,6 +135,55 @@ namespace {
|
||||
return file.open(QIODevice::ReadOnly) ? file.readAll() : QByteArray();
|
||||
}
|
||||
|
||||
[[nodiscard]] QString TonsiteToHttps(QString value) {
|
||||
const auto ChangeHost = [](QString tonsite) {
|
||||
tonsite = tonsite.replace('-', "-h");
|
||||
tonsite = tonsite.replace('.', "-d");
|
||||
return tonsite + ".magic.org";
|
||||
};
|
||||
auto parsed = QUrl(value);
|
||||
if (parsed.isValid()) {
|
||||
parsed.setScheme("https");
|
||||
parsed.setHost(ChangeHost(parsed.host()));
|
||||
if (parsed.path().isEmpty()) {
|
||||
parsed.setPath(u"/"_q);
|
||||
}
|
||||
return parsed.toString();
|
||||
}
|
||||
const auto part = value.mid(u"tonsite://"_q.size());
|
||||
const auto split = part.indexOf('/');
|
||||
return "https://"
|
||||
+ ChangeHost((split < 0) ? part : part.left(split))
|
||||
+ ((split < 0) ? u"/"_q : part.mid(split));
|
||||
}
|
||||
|
||||
[[nodiscard]] QString HttpsToTonsite(QString value) {
|
||||
const auto ChangeHost = [](QString https) {
|
||||
https.replace(".magic.org", QString());
|
||||
https = https.replace("-d", ".");
|
||||
https = https.replace("-h", "-");
|
||||
return https;
|
||||
};
|
||||
auto parsed = QUrl(value);
|
||||
if (parsed.isValid()) {
|
||||
const auto host = ChangeHost(parsed.host());
|
||||
const auto emptyPath = parsed.path().isEmpty();
|
||||
parsed.setScheme("tonsite");
|
||||
parsed.setHost(host);
|
||||
if (emptyPath) {
|
||||
parsed.setPath(u"/"_q);
|
||||
}
|
||||
if (parsed.isValid()) {
|
||||
return parsed.toString();
|
||||
}
|
||||
}
|
||||
const auto part = value.mid(u"https://"_q.size());
|
||||
const auto split = part.indexOf('/');
|
||||
return "tonsite://"
|
||||
+ ChangeHost((split < 0) ? part : part.left(split))
|
||||
+ ((split < 0) ? u"/"_q : part.mid(split));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Controller::Controller(
|
||||
@@ -149,8 +206,9 @@ Controller::~Controller() {
|
||||
_window->hide();
|
||||
}
|
||||
_ready = false;
|
||||
_webview = nullptr;
|
||||
base::take(_webview);
|
||||
_back.destroy();
|
||||
_forward.destroy();
|
||||
_menu = nullptr;
|
||||
_menuToggle.destroy();
|
||||
_subtitle = nullptr;
|
||||
@@ -168,15 +226,22 @@ void Controller::updateTitleGeometry(int newWidth) const {
|
||||
QPainter(_subtitleWrap.get()).fillRect(clip, st::windowBg);
|
||||
}, _subtitleWrap->lifetime());
|
||||
|
||||
const auto progress = _subtitleLeft.value(_back->toggled() ? 1. : 0.);
|
||||
const auto left = anim::interpolate(
|
||||
st::ivSubtitleLeft,
|
||||
_back->width() + st::ivSubtitleSkip,
|
||||
progress);
|
||||
const auto progressBack = _subtitleBackShift.value(
|
||||
_back->toggled() ? 1. : 0.);
|
||||
const auto progressForward = _subtitleForwardShift.value(
|
||||
_forward->toggled() ? 1. : 0.);
|
||||
const auto backAdded = _back->width()
|
||||
+ st::ivSubtitleSkip
|
||||
- st::ivSubtitleLeft;
|
||||
const auto forwardAdded = _forward->width();
|
||||
const auto left = st::ivSubtitleLeft
|
||||
+ anim::interpolate(0, backAdded, progressBack)
|
||||
+ anim::interpolate(0, forwardAdded, progressForward);
|
||||
_subtitle->resizeToWidth(newWidth - left - _menuToggle->width());
|
||||
_subtitle->moveToLeft(left, st::ivSubtitleTop);
|
||||
|
||||
_back->moveToLeft(0, 0);
|
||||
_forward->moveToLeft(_back->width() * progressBack, 0);
|
||||
_menuToggle->moveToRight(0, 0);
|
||||
}
|
||||
|
||||
@@ -191,12 +256,12 @@ void Controller::initControls() {
|
||||
_subtitleWrap.get(),
|
||||
_subtitleText.value(),
|
||||
st::ivSubtitle);
|
||||
_subtitle->setSelectable(true);
|
||||
_subtitleText.value(
|
||||
) | rpl::start_with_next([=](const QString &subtitle) {
|
||||
const auto prefix = tr::lng_iv_window_title(tr::now);
|
||||
_window->setWindowTitle(prefix + ' ' + QChar(0x2014) + ' ' + subtitle);
|
||||
}, _subtitle->lifetime());
|
||||
_subtitle->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
_menuToggle.create(_subtitleWrap.get(), st::ivMenuToggle);
|
||||
_menuToggle->setClickedCallback([=] { showMenu(); });
|
||||
@@ -206,15 +271,25 @@ void Controller::initControls() {
|
||||
object_ptr<Ui::IconButton>(_subtitleWrap.get(), st::ivBack));
|
||||
_back->entity()->setClickedCallback([=] {
|
||||
if (_webview) {
|
||||
_webview->eval("IV.back();");
|
||||
_webview->eval("window.history.back();");
|
||||
} else {
|
||||
_back->hide(anim::type::normal);
|
||||
}
|
||||
});
|
||||
_forward.create(
|
||||
_subtitleWrap.get(),
|
||||
object_ptr<Ui::IconButton>(_subtitleWrap.get(), st::ivForward));
|
||||
_forward->entity()->setClickedCallback([=] {
|
||||
if (_webview) {
|
||||
_webview->eval("window.history.forward();");
|
||||
} else {
|
||||
_forward->hide(anim::type::normal);
|
||||
}
|
||||
});
|
||||
|
||||
_back->toggledValue(
|
||||
) | rpl::start_with_next([=](bool toggled) {
|
||||
_subtitleLeft.start(
|
||||
_subtitleBackShift.start(
|
||||
[=] { updateTitleGeometry(_window->body()->width()); },
|
||||
toggled ? 0. : 1.,
|
||||
toggled ? 1. : 0.,
|
||||
@@ -222,7 +297,18 @@ void Controller::initControls() {
|
||||
}, _back->lifetime());
|
||||
_back->hide(anim::type::instant);
|
||||
|
||||
_subtitleLeft.stop();
|
||||
_forward->toggledValue(
|
||||
) | rpl::start_with_next([=](bool toggled) {
|
||||
_subtitleForwardShift.start(
|
||||
[=] { updateTitleGeometry(_window->body()->width()); },
|
||||
toggled ? 0. : 1.,
|
||||
toggled ? 1. : 0.,
|
||||
st::fadeWrapDuration);
|
||||
}, _forward->lifetime());
|
||||
_forward->hide(anim::type::instant);
|
||||
|
||||
_subtitleBackShift.stop();
|
||||
_subtitleForwardShift.stop();
|
||||
}
|
||||
|
||||
void Controller::show(
|
||||
@@ -251,6 +337,27 @@ void Controller::update(Prepared page) {
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::showTonSite(
|
||||
const Webview::StorageId &storageId,
|
||||
QString uri) {
|
||||
const auto url = TonsiteToHttps(uri);
|
||||
if (!_webview) {
|
||||
createWebview(storageId);
|
||||
}
|
||||
if (_webview && _webview->widget()) {
|
||||
_webview->navigate(url);
|
||||
activate();
|
||||
}
|
||||
_url = url;
|
||||
_subtitleText = _url.value(
|
||||
) | rpl::filter([=](const QString &url) {
|
||||
return !url.isEmpty() && url != u"about:blank"_q;
|
||||
}) | rpl::map([=](QString value) {
|
||||
return HttpsToTonsite(value);
|
||||
});
|
||||
_menuToggle->hide();
|
||||
}
|
||||
|
||||
QByteArray Controller::fillInChannelValuesScript(
|
||||
base::flat_map<QByteArray, rpl::producer<bool>> inChannelValues) {
|
||||
auto result = QByteArray();
|
||||
@@ -338,7 +445,7 @@ void Controller::createWebview(const Webview::StorageId &storageId) {
|
||||
|
||||
window->lifetime().add([=] {
|
||||
_ready = false;
|
||||
_webview = nullptr;
|
||||
base::take(_webview);
|
||||
});
|
||||
|
||||
window->events(
|
||||
@@ -352,11 +459,32 @@ void Controller::createWebview(const Webview::StorageId &storageId) {
|
||||
}
|
||||
}
|
||||
}, window->lifetime());
|
||||
raw->widget()->show();
|
||||
|
||||
const auto widget = raw->widget();
|
||||
if (!widget) {
|
||||
base::take(_webview);
|
||||
showWebviewError();
|
||||
return;
|
||||
}
|
||||
widget->show();
|
||||
|
||||
QObject::connect(widget, &QObject::destroyed, [=] {
|
||||
if (!_webview) {
|
||||
// If we destroyed _webview ourselves,
|
||||
// we don't show any message, nothing crashed.
|
||||
return;
|
||||
}
|
||||
crl::on_main(window, [=] {
|
||||
showWebviewError({ "Error: WebView has crashed." });
|
||||
});
|
||||
base::take(_webview);
|
||||
});
|
||||
|
||||
_container->sizeValue(
|
||||
) | rpl::start_with_next([=](QSize size) {
|
||||
raw->widget()->setGeometry(QRect(QPoint(), size));
|
||||
if (const auto widget = raw->widget()) {
|
||||
widget->setGeometry(QRect(QPoint(), size));
|
||||
}
|
||||
}, _container->lifetime());
|
||||
|
||||
raw->setNavigationStartHandler([=](const QString &uri, bool newWindow) {
|
||||
@@ -407,9 +535,7 @@ void Controller::createWebview(const Webview::StorageId &storageId) {
|
||||
} else if (event == u"location_change"_q) {
|
||||
_index = object.value("index").toInt();
|
||||
_hash = object.value("hash").toString();
|
||||
_back->toggle(
|
||||
(object.value("position").toInt() > 0),
|
||||
anim::type::normal);
|
||||
_webview->refreshNavigationHistoryState();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -490,9 +616,58 @@ void Controller::createWebview(const Webview::StorageId &storageId) {
|
||||
return Webview::DataResult::Failed;
|
||||
});
|
||||
|
||||
raw->navigationHistoryState(
|
||||
) | rpl::start_with_next([=](Webview::NavigationHistoryState state) {
|
||||
_back->toggle(
|
||||
state.canGoBack || state.canGoForward,
|
||||
anim::type::normal);
|
||||
_forward->toggle(state.canGoForward, anim::type::normal);
|
||||
_back->entity()->setDisabled(!state.canGoBack);
|
||||
_back->entity()->setIconOverride(
|
||||
state.canGoBack ? nullptr : &st::ivBackIconDisabled,
|
||||
state.canGoBack ? nullptr : &st::ivBackIconDisabled);
|
||||
_back->setAttribute(
|
||||
Qt::WA_TransparentForMouseEvents,
|
||||
!state.canGoBack);
|
||||
_url = QString::fromStdString(state.url);
|
||||
}, _webview->lifetime());
|
||||
|
||||
raw->init(R"()");
|
||||
}
|
||||
|
||||
void Controller::showWebviewError() {
|
||||
const auto available = Webview::Availability();
|
||||
if (available.error != Webview::Available::Error::None) {
|
||||
showWebviewError(Ui::BotWebView::ErrorText(available));
|
||||
} else {
|
||||
showWebviewError({ "Error: Could not initialize WebView." });
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::showWebviewError(TextWithEntities text) {
|
||||
auto error = Ui::CreateChild<Ui::PaddingWrap<Ui::FlatLabel>>(
|
||||
_container,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
_container,
|
||||
rpl::single(text),
|
||||
st::paymentsCriticalError),
|
||||
st::paymentsCriticalErrorPadding);
|
||||
error->entity()->setClickHandlerFilter([=](
|
||||
const ClickHandlerPtr &handler,
|
||||
Qt::MouseButton) {
|
||||
const auto entity = handler->getTextEntity();
|
||||
if (entity.type != EntityType::CustomUrl) {
|
||||
return true;
|
||||
}
|
||||
File::OpenUrl(entity.data);
|
||||
return false;
|
||||
});
|
||||
error->show();
|
||||
_container->sizeValue() | rpl::start_with_next([=](QSize size) {
|
||||
error->setGeometry(0, 0, size.width(), size.height() * 2 / 3);
|
||||
}, error->lifetime());
|
||||
}
|
||||
|
||||
void Controller::showInWindow(
|
||||
const Webview::StorageId &storageId,
|
||||
Prepared page) {
|
||||
|
||||
@@ -76,6 +76,8 @@ public:
|
||||
base::flat_map<QByteArray, rpl::producer<bool>> inChannelValues);
|
||||
void update(Prepared page);
|
||||
|
||||
void showTonSite(const Webview::StorageId &storageId, QString uri);
|
||||
|
||||
[[nodiscard]] bool active() const;
|
||||
void showJoinedTooltip();
|
||||
void minimize();
|
||||
@@ -121,15 +123,21 @@ private:
|
||||
void showShareMenu();
|
||||
void destroyShareMenu();
|
||||
|
||||
void showWebviewError();
|
||||
void showWebviewError(TextWithEntities text);
|
||||
|
||||
const not_null<Delegate*> _delegate;
|
||||
|
||||
std::unique_ptr<Ui::RpWindow> _window;
|
||||
std::unique_ptr<Ui::RpWidget> _subtitleWrap;
|
||||
rpl::variable<QString> _url;
|
||||
rpl::variable<QString> _subtitleText;
|
||||
std::unique_ptr<Ui::FlatLabel> _subtitle;
|
||||
Ui::Animations::Simple _subtitleLeft;
|
||||
Ui::Animations::Simple _subtitleBackShift;
|
||||
Ui::Animations::Simple _subtitleForwardShift;
|
||||
object_ptr<Ui::IconButton> _menuToggle = { nullptr };
|
||||
object_ptr<Ui::FadeWrapScaled<Ui::IconButton>> _back = { nullptr };
|
||||
object_ptr<Ui::FadeWrapScaled<Ui::IconButton>> _forward = { nullptr };
|
||||
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||
Ui::RpWidget *_container = nullptr;
|
||||
std::unique_ptr<Webview::Window> _webview;
|
||||
|
||||
@@ -171,6 +171,39 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class TonSite final : public base::has_weak_ptr {
|
||||
public:
|
||||
TonSite(not_null<Delegate*> delegate, QString uri);
|
||||
|
||||
[[nodiscard]] bool active() const;
|
||||
|
||||
void moveTo(QString uri);
|
||||
|
||||
void minimize();
|
||||
|
||||
[[nodiscard]] rpl::producer<Controller::Event> events() const {
|
||||
return _events.events();
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime() {
|
||||
return _lifetime;
|
||||
}
|
||||
|
||||
private:
|
||||
void createController();
|
||||
|
||||
void showWindowed();
|
||||
|
||||
const not_null<Delegate*> _delegate;
|
||||
QString _uri;
|
||||
std::unique_ptr<Controller> _controller;
|
||||
|
||||
rpl::event_stream<Controller::Event> _events;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
Shown::Shown(
|
||||
not_null<Delegate*> delegate,
|
||||
not_null<Main::Session*> session,
|
||||
@@ -742,6 +775,48 @@ void Shown::minimize() {
|
||||
}
|
||||
}
|
||||
|
||||
TonSite::TonSite(not_null<Delegate*> delegate, QString uri)
|
||||
: _delegate(delegate)
|
||||
, _uri(uri) {
|
||||
showWindowed();
|
||||
}
|
||||
|
||||
void TonSite::createController() {
|
||||
Expects(!_controller);
|
||||
|
||||
const auto showShareBox = [=](ShareBoxDescriptor &&descriptor) {
|
||||
return ShareBoxResult();
|
||||
};
|
||||
_controller = std::make_unique<Controller>(
|
||||
_delegate,
|
||||
std::move(showShareBox));
|
||||
|
||||
_controller->events(
|
||||
) | rpl::start_to_stream(_events, _controller->lifetime());
|
||||
}
|
||||
|
||||
void TonSite::showWindowed() {
|
||||
if (!_controller) {
|
||||
createController();
|
||||
}
|
||||
|
||||
_controller->showTonSite(Storage::TonSiteStorageId(), _uri);
|
||||
}
|
||||
|
||||
bool TonSite::active() const {
|
||||
return _controller && _controller->active();
|
||||
}
|
||||
|
||||
void TonSite::moveTo(QString uri) {
|
||||
_controller->showTonSite({}, uri);
|
||||
}
|
||||
|
||||
void TonSite::minimize() {
|
||||
if (_controller) {
|
||||
_controller->minimize();
|
||||
}
|
||||
}
|
||||
|
||||
Instance::Instance(not_null<Delegate*> delegate) : _delegate(delegate) {
|
||||
}
|
||||
|
||||
@@ -785,6 +860,7 @@ void Instance::show(
|
||||
const auto lower = event.url.toLower();
|
||||
const auto urlChecked = lower.startsWith("http://")
|
||||
|| lower.startsWith("https://");
|
||||
const auto tonsite = lower.startsWith("tonsite://");
|
||||
switch (event.type) {
|
||||
case Type::Close:
|
||||
_shown = nullptr;
|
||||
@@ -801,8 +877,10 @@ void Instance::show(
|
||||
case Type::OpenLinkExternal:
|
||||
if (urlChecked) {
|
||||
File::OpenUrl(event.url);
|
||||
closeAll();
|
||||
} else if (tonsite) {
|
||||
showTonSite(event.url);
|
||||
}
|
||||
closeAll();
|
||||
break;
|
||||
case Type::OpenMedia:
|
||||
if (const auto window = Core::App().activeWindow()) {
|
||||
@@ -840,7 +918,10 @@ void Instance::show(
|
||||
break;
|
||||
case Type::OpenPage:
|
||||
case Type::OpenLink: {
|
||||
if (!urlChecked) {
|
||||
if (tonsite) {
|
||||
showTonSite(event.url);
|
||||
break;
|
||||
} else if (!urlChecked) {
|
||||
break;
|
||||
}
|
||||
const auto session = _shownSession;
|
||||
@@ -990,6 +1071,51 @@ void Instance::openWithIvPreferred(
|
||||
}).send();
|
||||
}
|
||||
|
||||
void Instance::showTonSite(
|
||||
const QString &uri,
|
||||
QVariant context) {
|
||||
if (Platform::IsMac()) {
|
||||
// Otherwise IV is not visible under the media viewer.
|
||||
Core::App().hideMediaView();
|
||||
}
|
||||
if (_tonSite) {
|
||||
_tonSite->moveTo(uri);
|
||||
return;
|
||||
}
|
||||
_tonSite = std::make_unique<TonSite>(_delegate, uri);
|
||||
_tonSite->events() | rpl::start_with_next([=](Controller::Event event) {
|
||||
using Type = Controller::Event::Type;
|
||||
const auto lower = event.url.toLower();
|
||||
const auto urlChecked = lower.startsWith("http://")
|
||||
|| lower.startsWith("https://");
|
||||
const auto tonsite = lower.startsWith("tonsite://");
|
||||
switch (event.type) {
|
||||
case Type::Close:
|
||||
_tonSite = nullptr;
|
||||
break;
|
||||
case Type::Quit:
|
||||
Shortcuts::Launch(Shortcuts::Command::Quit);
|
||||
break;
|
||||
case Type::OpenLinkExternal:
|
||||
if (urlChecked) {
|
||||
File::OpenUrl(event.url);
|
||||
closeAll();
|
||||
} else if (tonsite) {
|
||||
showTonSite(event.url);
|
||||
}
|
||||
break;
|
||||
case Type::OpenPage:
|
||||
case Type::OpenLink:
|
||||
if (urlChecked) {
|
||||
UrlClickHandler::Open(event.url);
|
||||
} else if (tonsite) {
|
||||
showTonSite(event.url);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}, _tonSite->lifetime());
|
||||
}
|
||||
|
||||
void Instance::requestFull(
|
||||
not_null<Main::Session*> session,
|
||||
const QString &id) {
|
||||
@@ -1085,23 +1211,30 @@ bool Instance::hasActiveWindow(not_null<Main::Session*> session) const {
|
||||
}
|
||||
|
||||
bool Instance::closeActive() {
|
||||
if (!_shown || !_shown->active()) {
|
||||
return false;
|
||||
if (_shown && _shown->active()) {
|
||||
_shown = nullptr;
|
||||
return true;
|
||||
} else if (_tonSite && _tonSite->active()) {
|
||||
_tonSite = nullptr;
|
||||
return true;
|
||||
}
|
||||
_shown = nullptr;
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Instance::minimizeActive() {
|
||||
if (!_shown || !_shown->active()) {
|
||||
return false;
|
||||
if (_shown && _shown->active()) {
|
||||
_shown->minimize();
|
||||
return true;
|
||||
} else if (_tonSite && _tonSite->active()) {
|
||||
_tonSite->minimize();
|
||||
return true;
|
||||
}
|
||||
_shown->minimize();
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void Instance::closeAll() {
|
||||
_shown = nullptr;
|
||||
_tonSite = nullptr;
|
||||
}
|
||||
|
||||
bool PreferForUri(const QString &uri) {
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace Iv {
|
||||
|
||||
class Data;
|
||||
class Shown;
|
||||
class TonSite;
|
||||
|
||||
class Instance final {
|
||||
public:
|
||||
@@ -50,6 +51,10 @@ public:
|
||||
QString uri,
|
||||
QVariant context = {});
|
||||
|
||||
void showTonSite(
|
||||
const QString &uri,
|
||||
QVariant context = {});
|
||||
|
||||
[[nodiscard]] bool hasActiveWindow(
|
||||
not_null<Main::Session*> session) const;
|
||||
|
||||
@@ -97,6 +102,7 @@ private:
|
||||
QString _ivRequestUri;
|
||||
mtpRequestId _ivRequestId = 0;
|
||||
|
||||
std::unique_ptr<TonSite> _tonSite;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
|
||||
@@ -171,7 +171,8 @@ void Account::createSession(
|
||||
MTPVector<MTPUsername>(),
|
||||
MTPint(), // stories_max_id
|
||||
MTPPeerColor(), // color
|
||||
MTPPeerColor()), // profile_color
|
||||
MTPPeerColor(), // profile_color
|
||||
MTPint()), // bot_active_users
|
||||
serialized,
|
||||
streamVersion,
|
||||
std::move(settings));
|
||||
|
||||
@@ -110,7 +110,9 @@ Session::Session(
|
||||
, _recentPeers(std::make_unique<Data::RecentPeers>(this))
|
||||
, _scheduledMessages(std::make_unique<Data::ScheduledMessages>(this))
|
||||
, _sponsoredMessages(std::make_unique<Data::SponsoredMessages>(this))
|
||||
, _topPeers(std::make_unique<Data::TopPeers>(this))
|
||||
, _topPeers(std::make_unique<Data::TopPeers>(this, Data::TopPeerType::Chat))
|
||||
, _topBotApps(
|
||||
std::make_unique<Data::TopPeers>(this, Data::TopPeerType::BotApp))
|
||||
, _factchecks(std::make_unique<Data::Factchecks>(this))
|
||||
, _locationPickers(std::make_unique<Data::LocationPickers>())
|
||||
, _cachedReactionIconFactory(std::make_unique<ReactionIconFactory>())
|
||||
|
||||
@@ -129,6 +129,9 @@ public:
|
||||
[[nodiscard]] Data::TopPeers &topPeers() const {
|
||||
return *_topPeers;
|
||||
}
|
||||
[[nodiscard]] Data::TopPeers &topBotApps() const {
|
||||
return *_topBotApps;
|
||||
}
|
||||
[[nodiscard]] Data::Factchecks &factchecks() const {
|
||||
return *_factchecks;
|
||||
}
|
||||
@@ -262,6 +265,7 @@ private:
|
||||
const std::unique_ptr<Data::ScheduledMessages> _scheduledMessages;
|
||||
const std::unique_ptr<Data::SponsoredMessages> _sponsoredMessages;
|
||||
const std::unique_ptr<Data::TopPeers> _topPeers;
|
||||
const std::unique_ptr<Data::TopPeers> _topBotApps;
|
||||
const std::unique_ptr<Data::Factchecks> _factchecks;
|
||||
const std::unique_ptr<Data::LocationPickers> _locationPickers;
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ SessionSettings::SessionSettings()
|
||||
|
||||
QByteArray SessionSettings::serialize() const {
|
||||
const auto autoDownload = _autoDownload.serialize();
|
||||
auto size = sizeof(qint32) * 4
|
||||
const auto size = sizeof(qint32) * 4
|
||||
+ _groupStickersSectionHidden.size() * sizeof(quint64)
|
||||
+ sizeof(qint32) * 4
|
||||
+ Serialize::bytearraySize(autoDownload)
|
||||
@@ -103,6 +103,8 @@ QByteArray SessionSettings::serialize() const {
|
||||
<< qint32(_lastNonPremiumLimitDownload)
|
||||
<< qint32(_lastNonPremiumLimitUpload);
|
||||
}
|
||||
|
||||
Ensures(result.size() == size);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "media/stories/media_stories_controller.h"
|
||||
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/power_save_blocker.h"
|
||||
#include "base/qt_signal_producer.h"
|
||||
#include "base/unixtime.h"
|
||||
@@ -128,6 +129,13 @@ struct SameDayRange {
|
||||
int(base::SafeRound(asin * point.x() + acos * point.y())));
|
||||
}
|
||||
|
||||
[[nodiscard]] bool ResolveWeatherInCelsius() {
|
||||
const auto saved = Core::App().settings().weatherInCelsius();
|
||||
return saved.value_or(!ranges::contains(
|
||||
std::array{ u"US"_q, u"BS"_q, u"KY"_q, u"LR"_q, u"BZ"_q },
|
||||
Platform::SystemCountry().toUpper()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class Controller::PhotoPlayback final {
|
||||
@@ -284,7 +292,8 @@ Controller::Controller(not_null<Delegate*> delegate)
|
||||
, _slider(std::make_unique<Slider>(this))
|
||||
, _replyArea(std::make_unique<ReplyArea>(this))
|
||||
, _reactions(std::make_unique<Reactions>(this))
|
||||
, _recentViews(std::make_unique<RecentViews>(this)) {
|
||||
, _recentViews(std::make_unique<RecentViews>(this))
|
||||
, _weatherInCelsius(ResolveWeatherInCelsius()){
|
||||
initLayout();
|
||||
|
||||
using namespace rpl::mappers;
|
||||
@@ -536,8 +545,9 @@ void Controller::rebuildActiveAreas(const Layout &layout) const {
|
||||
int(base::SafeRound(general.width() * scale.width())),
|
||||
int(base::SafeRound(general.height() * scale.height()))
|
||||
).translated(origin);
|
||||
if (const auto reaction = area.reaction.get()) {
|
||||
reaction->setAreaGeometry(area.geometry);
|
||||
area.radius = scale.width() * area.radiusOriginal / 100.;
|
||||
if (const auto view = area.view.get()) {
|
||||
view->setAreaGeometry(area.geometry, area.radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1050,6 +1060,9 @@ void Controller::updateAreas(Data::Story *story) {
|
||||
const auto &urlAreas = story
|
||||
? story->urlAreas()
|
||||
: std::vector<Data::UrlArea>();
|
||||
const auto &weatherAreas = story
|
||||
? story->weatherAreas()
|
||||
: std::vector<Data::WeatherArea>();
|
||||
if (_locations != locations) {
|
||||
_locations = locations;
|
||||
_areas.clear();
|
||||
@@ -1062,13 +1075,18 @@ void Controller::updateAreas(Data::Story *story) {
|
||||
_urlAreas = urlAreas;
|
||||
_areas.clear();
|
||||
}
|
||||
if (_weatherAreas != weatherAreas) {
|
||||
_weatherAreas = weatherAreas;
|
||||
_areas.clear();
|
||||
}
|
||||
const auto reactionsCount = int(suggestedReactions.size());
|
||||
if (_suggestedReactions.size() == reactionsCount && !_areas.empty()) {
|
||||
for (auto i = 0; i != reactionsCount; ++i) {
|
||||
const auto count = suggestedReactions[i].count;
|
||||
if (_suggestedReactions[i].count != count) {
|
||||
_suggestedReactions[i].count = count;
|
||||
_areas[i + _locations.size()].reaction->updateCount(count);
|
||||
const auto view = _areas[i + _locations.size()].view.get();
|
||||
view->updateReactionsCount(count);
|
||||
}
|
||||
if (_suggestedReactions[i] != suggestedReactions[i]) {
|
||||
_suggestedReactions = suggestedReactions;
|
||||
@@ -1206,7 +1224,8 @@ ClickHandlerPtr Controller::lookupAreaHandler(QPoint point) const {
|
||||
|| (_locations.empty()
|
||||
&& _suggestedReactions.empty()
|
||||
&& _channelPosts.empty()
|
||||
&& _urlAreas.empty())) {
|
||||
&& _urlAreas.empty()
|
||||
&& _weatherAreas.empty())) {
|
||||
return nullptr;
|
||||
} else if (_areas.empty()) {
|
||||
const auto now = story();
|
||||
@@ -1240,7 +1259,7 @@ ClickHandlerPtr Controller::lookupAreaHandler(QPoint point) const {
|
||||
}
|
||||
}
|
||||
}),
|
||||
.reaction = std::move(widget),
|
||||
.view = std::move(widget),
|
||||
});
|
||||
}
|
||||
if (const auto session = now ? &now->session() : nullptr) {
|
||||
@@ -1261,19 +1280,27 @@ ClickHandlerPtr Controller::lookupAreaHandler(QPoint point) const {
|
||||
.handler = std::make_shared<HiddenUrlClickHandler>(url.url),
|
||||
});
|
||||
}
|
||||
for (const auto &weather : _weatherAreas) {
|
||||
_areas.push_back({
|
||||
.original = weather.area.geometry,
|
||||
.radiusOriginal = weather.area.radius,
|
||||
.rotation = weather.area.rotation,
|
||||
.handler = std::make_shared<LambdaClickHandler>([=] {
|
||||
toggleWeatherMode();
|
||||
}),
|
||||
.view = _reactions->makeWeatherAreaWidget(
|
||||
weather,
|
||||
_weatherInCelsius.value()),
|
||||
});
|
||||
}
|
||||
rebuildActiveAreas(*layout);
|
||||
}
|
||||
|
||||
const auto circleContains = [&](QRect circle) {
|
||||
const auto radius = std::min(circle.width(), circle.height()) / 2;
|
||||
const auto delta = circle.center() - point;
|
||||
return QPoint::dotProduct(delta, delta) < (radius * radius);
|
||||
};
|
||||
for (const auto &area : _areas) {
|
||||
const auto center = area.geometry.center();
|
||||
const auto angle = -area.rotation;
|
||||
const auto contains = area.reaction
|
||||
? circleContains(area.geometry)
|
||||
const auto contains = area.view
|
||||
? area.view->contains(point)
|
||||
: area.geometry.contains(Rotated(point, center, angle));
|
||||
if (contains) {
|
||||
return area.handler;
|
||||
@@ -1282,6 +1309,13 @@ ClickHandlerPtr Controller::lookupAreaHandler(QPoint point) const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Controller::toggleWeatherMode() const {
|
||||
const auto now = !_weatherInCelsius.current();
|
||||
Core::App().settings().setWeatherInCelsius(now);
|
||||
Core::App().saveSettingsDelayed();
|
||||
_weatherInCelsius = now;
|
||||
}
|
||||
|
||||
void Controller::maybeMarkAsRead(const Player::TrackState &state) {
|
||||
const auto length = state.length;
|
||||
const auto position = Player::IsStoppedAtEnd(state.state)
|
||||
|
||||
@@ -68,7 +68,7 @@ struct ContentLayout;
|
||||
class CaptionFullView;
|
||||
class RepostView;
|
||||
enum class ReactionsMode;
|
||||
class SuggestedReactionView;
|
||||
class StoryAreaView;
|
||||
struct RepostClickHandler;
|
||||
|
||||
enum class HeaderLayout {
|
||||
@@ -208,10 +208,12 @@ private:
|
||||
};
|
||||
struct ActiveArea {
|
||||
QRectF original;
|
||||
float64 radiusOriginal = 0.;
|
||||
QRect geometry;
|
||||
float64 rotation = 0.;
|
||||
float64 radius = 0.;
|
||||
ClickHandlerPtr handler;
|
||||
std::unique_ptr<SuggestedReactionView> reaction;
|
||||
std::unique_ptr<StoryAreaView> view;
|
||||
};
|
||||
|
||||
void initLayout();
|
||||
@@ -227,6 +229,7 @@ private:
|
||||
void updatePlayingAllowed();
|
||||
void setPlayingAllowed(bool allowed);
|
||||
void rebuildActiveAreas(const Layout &layout) const;
|
||||
void toggleWeatherMode() const;
|
||||
|
||||
void hideSiblings();
|
||||
void showSiblings(not_null<Main::Session*> session);
|
||||
@@ -303,7 +306,9 @@ private:
|
||||
std::vector<Data::SuggestedReaction> _suggestedReactions;
|
||||
std::vector<Data::ChannelPost> _channelPosts;
|
||||
std::vector<Data::UrlArea> _urlAreas;
|
||||
std::vector<Data::WeatherArea> _weatherAreas;
|
||||
mutable std::vector<ActiveArea> _areas;
|
||||
mutable rpl::variable<bool> _weatherInCelsius;
|
||||
|
||||
std::vector<CachedSource> _cachedSourcesList;
|
||||
int _cachedSourceIndex = -1;
|
||||
|
||||
@@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "chat_helpers/compose/compose_show.h"
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
#include "chat_helpers/stickers_emoji_pack.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
@@ -20,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/admin_log/history_admin_log_item.h"
|
||||
#include "history/view/media/history_view_custom_emoji.h"
|
||||
#include "history/view/media/history_view_media_unwrapped.h"
|
||||
#include "history/view/media/history_view_sticker_player.h"
|
||||
#include "history/view/reactions/history_view_reactions_selector.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "history/history_item_reply_markup.h"
|
||||
@@ -61,7 +64,7 @@ constexpr auto kStoppingFadeDuration = crl::time(150);
|
||||
|
||||
class ReactionView final
|
||||
: public Ui::RpWidget
|
||||
, public SuggestedReactionView
|
||||
, public StoryAreaView
|
||||
, public HistoryView::DefaultElementDelegate {
|
||||
public:
|
||||
ReactionView(
|
||||
@@ -69,9 +72,10 @@ public:
|
||||
not_null<Main::Session*> session,
|
||||
const Data::SuggestedReaction &reaction);
|
||||
|
||||
void setAreaGeometry(QRect geometry) override;
|
||||
void updateCount(int count) override;
|
||||
void setAreaGeometry(QRect geometry, float64 radius) override;
|
||||
void updateReactionsCount(int count) override;
|
||||
void playEffect() override;
|
||||
bool contains(QPoint point) override;
|
||||
|
||||
private:
|
||||
using Element = HistoryView::Element;
|
||||
@@ -108,6 +112,7 @@ private:
|
||||
Ui::Text::String _counter;
|
||||
Ui::Animations::Simple _counterAnimation;
|
||||
QRectF _bubbleGeometry;
|
||||
QRect _apiGeometry;
|
||||
int _size = 0;
|
||||
int _mediaLeft = 0;
|
||||
int _mediaTop = 0;
|
||||
@@ -126,6 +131,58 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class WeatherView final : public Ui::RpWidget, public StoryAreaView {
|
||||
public:
|
||||
WeatherView(
|
||||
QWidget *parent,
|
||||
not_null<Main::Session*> session,
|
||||
const Data::WeatherArea &data,
|
||||
rpl::producer<bool> weatherInCelsius);
|
||||
|
||||
void setAreaGeometry(QRect geometry, float64 radius) override;
|
||||
void updateReactionsCount(int count) override;
|
||||
void playEffect() override;
|
||||
bool contains(QPoint point) override;
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
void cacheBackground();
|
||||
void watchForSticker();
|
||||
void setStickerFrom(not_null<DocumentData*> document);
|
||||
[[nodiscard]] QSize stickerSize() const;
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
Data::WeatherArea _data;
|
||||
EmojiPtr _emoji;
|
||||
QColor _fg;
|
||||
QImage _background;
|
||||
QFont _font;
|
||||
QRectF _rect;
|
||||
QRect _wrapped;
|
||||
float64 _radius = 0.;
|
||||
int _emojiSize = 0;
|
||||
int _padding = 0;
|
||||
bool _celsius = true;
|
||||
|
||||
std::shared_ptr<HistoryView::StickerPlayer> _sticker;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] QPoint Rotated(QPoint point, QPoint origin, float64 angle) {
|
||||
if (std::abs(angle) < 1.) {
|
||||
return point;
|
||||
}
|
||||
const auto alpha = angle / 180. * M_PI;
|
||||
const auto acos = cos(alpha);
|
||||
const auto asin = sin(alpha);
|
||||
point -= origin;
|
||||
return origin + QPoint(
|
||||
int(base::SafeRound(acos * point.x() - asin * point.y())),
|
||||
int(base::SafeRound(asin * point.x() + acos * point.y())));
|
||||
}
|
||||
|
||||
[[nodiscard]] AdminLog::OwnedItem GenerateFakeItem(
|
||||
not_null<HistoryView::ElementDelegate*> delegate,
|
||||
not_null<History*> history) {
|
||||
@@ -140,6 +197,13 @@ private:
|
||||
return AdminLog::OwnedItem(delegate, item);
|
||||
}
|
||||
|
||||
[[nodiscard]] QColor ChooseWeatherFg(const QColor &bg) {
|
||||
const auto luminance = (0.2126 * bg.redF())
|
||||
+ (0.7152 * bg.greenF())
|
||||
+ (0.0722 * bg.blueF());
|
||||
return (luminance > 0.705) ? QColor(0, 0, 0) : QColor(255, 255, 255);
|
||||
}
|
||||
|
||||
ReactionView::ReactionView(
|
||||
QWidget *parent,
|
||||
not_null<Main::Session*> session,
|
||||
@@ -198,7 +262,7 @@ ReactionView::ReactionView(
|
||||
}, lifetime());
|
||||
|
||||
_data.count = 0;
|
||||
updateCount(reaction.count);
|
||||
updateReactionsCount(reaction.count);
|
||||
_counterAnimation.stop();
|
||||
|
||||
setupCustomChatStylePalette();
|
||||
@@ -212,7 +276,8 @@ void ReactionView::setupCustomChatStylePalette() {
|
||||
_chatStyle->applyCustomPalette(_chatStyle.get());
|
||||
}
|
||||
|
||||
void ReactionView::setAreaGeometry(QRect geometry) {
|
||||
void ReactionView::setAreaGeometry(QRect geometry, float64 radius) {
|
||||
_apiGeometry = geometry;
|
||||
_size = std::min(geometry.width(), geometry.height());
|
||||
_bubble = _size * kSuggestedBubbleSize;
|
||||
_bigOffset = _bubble * kSuggestedTailBigOffset;
|
||||
@@ -228,7 +293,7 @@ void ReactionView::setAreaGeometry(QRect geometry) {
|
||||
updateEffectGeometry();
|
||||
}
|
||||
|
||||
void ReactionView::updateCount(int count) {
|
||||
void ReactionView::updateReactionsCount(int count) {
|
||||
if (_data.count == count) {
|
||||
return;
|
||||
}
|
||||
@@ -283,6 +348,13 @@ void ReactionView::playEffect() {
|
||||
}
|
||||
}
|
||||
|
||||
bool ReactionView::contains(QPoint point) {
|
||||
const auto circle = _apiGeometry;
|
||||
const auto radius = std::min(circle.width(), circle.height()) / 2;
|
||||
const auto delta = circle.center() - point;
|
||||
return QPoint::dotProduct(delta, delta) < (radius * radius);
|
||||
}
|
||||
|
||||
void ReactionView::paintEffectFrame(
|
||||
QPainter &p,
|
||||
not_null<Ui::ReactionFlyAnimation*> effect,
|
||||
@@ -457,6 +529,206 @@ void ReactionView::cacheBackground() {
|
||||
paintShape(_data.dark ? dark : QColor(255, 255, 255));
|
||||
}
|
||||
|
||||
WeatherView::WeatherView(
|
||||
QWidget *parent,
|
||||
not_null<Main::Session*> session,
|
||||
const Data::WeatherArea &data,
|
||||
rpl::producer<bool> weatherInCelsius)
|
||||
: RpWidget(parent)
|
||||
, _session(session)
|
||||
, _data(data)
|
||||
, _emoji(Ui::Emoji::Find(_data.emoji))
|
||||
, _fg(ChooseWeatherFg(_data.color)) {
|
||||
watchForSticker();
|
||||
setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
show();
|
||||
|
||||
std::move(weatherInCelsius) | rpl::start_with_next([=](bool celsius) {
|
||||
_celsius = celsius;
|
||||
_background = {};
|
||||
update();
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void WeatherView::watchForSticker() {
|
||||
if (!_emoji) {
|
||||
return;
|
||||
}
|
||||
const auto emojiStickers = &_session->emojiStickersPack();
|
||||
if (const auto sticker = emojiStickers->stickerForEmoji(_emoji)) {
|
||||
setStickerFrom(sticker.document);
|
||||
} else {
|
||||
emojiStickers->refreshed() | rpl::map([=] {
|
||||
return emojiStickers->stickerForEmoji(_emoji).document;
|
||||
}) | rpl::filter([=](DocumentData *document) {
|
||||
return document != nullptr;
|
||||
}) | rpl::take(
|
||||
1
|
||||
) | rpl::start_with_next([=](not_null<DocumentData*> document) {
|
||||
setStickerFrom(document);
|
||||
update();
|
||||
}, lifetime());
|
||||
}
|
||||
}
|
||||
|
||||
void WeatherView::setAreaGeometry(QRect geometry, float64 radius) {
|
||||
const auto diagxdiag = (geometry.width() * geometry.width())
|
||||
+ (geometry.height() * geometry.height());
|
||||
const auto diag = std::sqrt(diagxdiag);
|
||||
const auto topleft = QRectF(geometry).center()
|
||||
- QPointF(diag / 2., diag / 2.);
|
||||
const auto bottomright = topleft + QPointF(diag, diag);
|
||||
const auto left = int(std::floor(topleft.x()));
|
||||
const auto top = int(std::floor(topleft.y()));
|
||||
const auto right = int(std::ceil(bottomright.x()));
|
||||
const auto bottom = int(std::ceil(bottomright.y()));
|
||||
setGeometry(left, top, right - left, bottom - top);
|
||||
_rect = QRectF(geometry).translated(-left, -top);
|
||||
_radius = radius;
|
||||
|
||||
_emojiSize = int(base::SafeRound(_rect.height() * 2 / 3.));
|
||||
_font = st::semiboldFont->f;
|
||||
_font.setPixelSize(_emojiSize);
|
||||
_background = {};
|
||||
}
|
||||
|
||||
void WeatherView::updateReactionsCount(int count) {
|
||||
Unexpected("WeatherView::updateRactionsCount.");
|
||||
}
|
||||
|
||||
void WeatherView::playEffect() {
|
||||
Unexpected("WeatherView::playEffect.");
|
||||
}
|
||||
|
||||
bool WeatherView::contains(QPoint point) {
|
||||
const auto geometry = _rect.translated(pos()).toRect();
|
||||
const auto angle = -_data.area.rotation;
|
||||
return geometry.contains(Rotated(point, geometry.center(), angle));
|
||||
}
|
||||
|
||||
void WeatherView::paintEvent(QPaintEvent *e) {
|
||||
auto p = Painter(this);
|
||||
if (_background.size() != size() * style::DevicePixelRatio()) {
|
||||
cacheBackground();
|
||||
}
|
||||
p.drawImage(0, 0, _background);
|
||||
if (_sticker && _sticker->ready()) {
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
const auto rcenter = _wrapped.center();
|
||||
p.translate(rcenter);
|
||||
p.rotate(_data.area.rotation);
|
||||
p.translate(-rcenter);
|
||||
|
||||
const auto image = _sticker->frame(
|
||||
stickerSize(),
|
||||
QColor(0, 0, 0, 0),
|
||||
false,
|
||||
crl::now(),
|
||||
false).image;
|
||||
const auto size = image.size() / style::DevicePixelRatio();
|
||||
const auto rect = QRectF(
|
||||
_wrapped.x() + _padding + (_emojiSize - size.width()) / 2.,
|
||||
_wrapped.y() + (_wrapped.height() - size.height()) / 2.,
|
||||
size.width(),
|
||||
size.height());
|
||||
const auto scenter = rect.center();
|
||||
const auto scale = (_emojiSize * 1.) / stickerSize().width();
|
||||
p.translate(scenter);
|
||||
p.scale(scale, scale);
|
||||
p.translate(-scenter);
|
||||
p.drawImage(rect, image);
|
||||
_sticker->markFrameShown();
|
||||
}
|
||||
}
|
||||
|
||||
QSize WeatherView::stickerSize() const {
|
||||
return QSize(st::chatIntroStickerSize, st::chatIntroStickerSize);
|
||||
}
|
||||
|
||||
void WeatherView::setStickerFrom(not_null<DocumentData*> document) {
|
||||
if (_sticker || !_emoji) {
|
||||
return;
|
||||
}
|
||||
const auto media = document->createMediaView();
|
||||
media->checkStickerLarge();
|
||||
media->goodThumbnailWanted();
|
||||
|
||||
rpl::single() | rpl::then(
|
||||
document->owner().session().downloaderTaskFinished()
|
||||
) | rpl::filter([=] {
|
||||
return media->loaded();
|
||||
}) | rpl::take(1) | rpl::start_with_next([=] {
|
||||
const auto sticker = document->sticker();
|
||||
if (sticker->isLottie()) {
|
||||
_sticker = std::make_shared<HistoryView::LottiePlayer>(
|
||||
ChatHelpers::LottiePlayerFromDocument(
|
||||
media.get(),
|
||||
ChatHelpers::StickerLottieSize::StickerSet,
|
||||
stickerSize(),
|
||||
Lottie::Quality::High));
|
||||
} else if (sticker->isWebm()) {
|
||||
_sticker = std::make_shared<HistoryView::WebmPlayer>(
|
||||
media->owner()->location(),
|
||||
media->bytes(),
|
||||
stickerSize());
|
||||
} else {
|
||||
_sticker = std::make_shared<HistoryView::StaticStickerPlayer>(
|
||||
media->owner()->location(),
|
||||
media->bytes(),
|
||||
stickerSize());
|
||||
}
|
||||
_sticker->setRepaintCallback([=] { update(); });
|
||||
update();
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void WeatherView::cacheBackground() {
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
_background = QImage(
|
||||
size() * ratio,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
_background.setDevicePixelRatio(ratio);
|
||||
_background.fill(Qt::transparent);
|
||||
|
||||
auto p = QPainter(&_background);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setBrush(_data.color);
|
||||
p.setPen(Qt::NoPen);
|
||||
const auto center = _rect.center();
|
||||
p.translate(center);
|
||||
p.rotate(_data.area.rotation);
|
||||
p.translate(-center);
|
||||
|
||||
const auto format = [](float64 value) {
|
||||
return QString::number(int(base::SafeRound(value * 10)) / 10.);
|
||||
};
|
||||
const auto text = [&] {
|
||||
const auto celsius = _data.millicelsius / 1000.;
|
||||
if (_celsius) {
|
||||
return format(celsius);
|
||||
}
|
||||
const auto fahrenheit = (celsius * 9.0 / 5.0) + 32;
|
||||
return format(fahrenheit);
|
||||
}().append(QChar(0xb0)).append(_celsius ? "C" : "F");
|
||||
const auto metrics = QFontMetrics(_font);
|
||||
const auto textWidth = metrics.horizontalAdvance(text);
|
||||
_padding = int(_rect.height() / 6);
|
||||
const auto fullWidth = (_emoji ? _emojiSize : 0)
|
||||
+ textWidth
|
||||
+ (2 * _padding);
|
||||
const auto left = _rect.x() + (_rect.width() - fullWidth) / 2;
|
||||
_wrapped = QRect(left, _rect.y(), fullWidth, _rect.height());
|
||||
|
||||
p.drawRoundedRect(_wrapped, _radius, _radius);
|
||||
|
||||
p.setPen(_fg);
|
||||
p.setFont(_font);
|
||||
p.drawText(_wrapped.marginsRemoved(
|
||||
{ _padding + (_emoji ? _emojiSize : 0), 0, _padding, 0 }),
|
||||
text,
|
||||
style::al_center);
|
||||
}
|
||||
|
||||
[[nodiscard]] Data::ReactionId HeartReactionId() {
|
||||
return { QString() + QChar(10084) };
|
||||
}
|
||||
@@ -804,13 +1076,24 @@ auto Reactions::chosen() const -> rpl::producer<Chosen> {
|
||||
|
||||
auto Reactions::makeSuggestedReactionWidget(
|
||||
const Data::SuggestedReaction &reaction)
|
||||
-> std::unique_ptr<SuggestedReactionView> {
|
||||
-> std::unique_ptr<StoryAreaView> {
|
||||
return std::make_unique<ReactionView>(
|
||||
_controller->wrap(),
|
||||
&_controller->uiShow()->session(),
|
||||
reaction);
|
||||
}
|
||||
|
||||
auto Reactions::makeWeatherAreaWidget(
|
||||
const Data::WeatherArea &data,
|
||||
rpl::producer<bool> weatherInCelsius)
|
||||
-> std::unique_ptr<StoryAreaView> {
|
||||
return std::make_unique<WeatherView>(
|
||||
_controller->wrap(),
|
||||
&_controller->uiShow()->session(),
|
||||
data,
|
||||
std::move(weatherInCelsius));
|
||||
}
|
||||
|
||||
void Reactions::setReplyFieldState(
|
||||
rpl::producer<bool> focused,
|
||||
rpl::producer<bool> hasSendText) {
|
||||
|
||||
@@ -16,6 +16,7 @@ struct ReactionId;
|
||||
class Session;
|
||||
class Story;
|
||||
struct SuggestedReaction;
|
||||
struct WeatherArea;
|
||||
} // namespace Data
|
||||
|
||||
namespace HistoryView::Reactions {
|
||||
@@ -41,13 +42,14 @@ enum class ReactionsMode {
|
||||
Reaction,
|
||||
};
|
||||
|
||||
class SuggestedReactionView {
|
||||
class StoryAreaView {
|
||||
public:
|
||||
virtual ~SuggestedReactionView() = default;
|
||||
virtual ~StoryAreaView() = default;
|
||||
|
||||
virtual void setAreaGeometry(QRect geometry) = 0;
|
||||
virtual void updateCount(int count) = 0;
|
||||
virtual void setAreaGeometry(QRect geometry, float64 radius) = 0;
|
||||
virtual void updateReactionsCount(int count) = 0;
|
||||
virtual void playEffect() = 0;
|
||||
virtual bool contains(QPoint point) = 0;
|
||||
};
|
||||
|
||||
class Reactions final {
|
||||
@@ -79,7 +81,11 @@ public:
|
||||
|
||||
[[nodiscard]] auto makeSuggestedReactionWidget(
|
||||
const Data::SuggestedReaction &reaction)
|
||||
-> std::unique_ptr<SuggestedReactionView>;
|
||||
-> std::unique_ptr<StoryAreaView>;
|
||||
[[nodiscard]] auto makeWeatherAreaWidget(
|
||||
const Data::WeatherArea &data,
|
||||
rpl::producer<bool> weatherInCelsius)
|
||||
-> std::unique_ptr<StoryAreaView>;
|
||||
|
||||
void setReplyFieldState(
|
||||
rpl::producer<bool> focused,
|
||||
|
||||
@@ -83,7 +83,7 @@ storage.fileMp4#b3cea0e4 = storage.FileType;
|
||||
storage.fileWebp#1081464c = storage.FileType;
|
||||
|
||||
userEmpty#d3bc4b7a id:long = User;
|
||||
user#215c4438 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true premium:flags.28?true attach_menu_enabled:flags.29?true flags2:# bot_can_edit:flags2.1?true close_friend:flags2.2?true stories_hidden:flags2.3?true stories_unavailable:flags2.4?true contact_require_premium:flags2.10?true bot_business:flags2.11?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector<RestrictionReason> bot_inline_placeholder:flags.19?string lang_code:flags.22?string emoji_status:flags.30?EmojiStatus usernames:flags2.0?Vector<Username> stories_max_id:flags2.5?int color:flags2.8?PeerColor profile_color:flags2.9?PeerColor = User;
|
||||
user#83314fca flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true premium:flags.28?true attach_menu_enabled:flags.29?true flags2:# bot_can_edit:flags2.1?true close_friend:flags2.2?true stories_hidden:flags2.3?true stories_unavailable:flags2.4?true contact_require_premium:flags2.10?true bot_business:flags2.11?true bot_has_main_app:flags2.13?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector<RestrictionReason> bot_inline_placeholder:flags.19?string lang_code:flags.22?string emoji_status:flags.30?EmojiStatus usernames:flags2.0?Vector<Username> stories_max_id:flags2.5?int color:flags2.8?PeerColor profile_color:flags2.9?PeerColor bot_active_users:flags2.12?int = User;
|
||||
|
||||
userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto;
|
||||
userProfilePhoto#82d1f706 flags:# has_video:flags.0?true personal:flags.2?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = UserProfilePhoto;
|
||||
@@ -180,6 +180,7 @@ messageActionGiveawayResults#2a9fadc5 winners_count:int unclaimed_count:int = Me
|
||||
messageActionBoostApply#cc02aa6d boosts:int = MessageAction;
|
||||
messageActionRequestedPeerSentMe#93b31848 button_id:int peers:Vector<RequestedPeer> = MessageAction;
|
||||
messageActionPaymentRefunded#41b3e202 flags:# peer:Peer currency:string total_amount:long payload:flags.0?bytes charge:PaymentCharge = MessageAction;
|
||||
messageActionGiftStars#45d5b021 flags:# currency:string amount:long stars:long crypto_currency:flags.0?string crypto_amount:flags.0?long transaction_id:flags.1?string = MessageAction;
|
||||
|
||||
dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true view_forum_as_messages:flags.6?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?int = Dialog;
|
||||
dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog;
|
||||
@@ -568,7 +569,7 @@ accountDaysTTL#b8d0afdf days:int = AccountDaysTTL;
|
||||
documentAttributeImageSize#6c37c15c w:int h:int = DocumentAttribute;
|
||||
documentAttributeAnimated#11b58939 = DocumentAttribute;
|
||||
documentAttributeSticker#6319d612 flags:# mask:flags.1?true alt:string stickerset:InputStickerSet mask_coords:flags.0?MaskCoords = DocumentAttribute;
|
||||
documentAttributeVideo#d38ff1c2 flags:# round_message:flags.0?true supports_streaming:flags.1?true nosound:flags.3?true duration:double w:int h:int preload_prefix_size:flags.2?int = DocumentAttribute;
|
||||
documentAttributeVideo#17399fad flags:# round_message:flags.0?true supports_streaming:flags.1?true nosound:flags.3?true duration:double w:int h:int preload_prefix_size:flags.2?int video_start_ts:flags.4?double = DocumentAttribute;
|
||||
documentAttributeAudio#9852f9c6 flags:# voice:flags.10?true duration:int title:flags.0?string performer:flags.1?string waveform:flags.2?bytes = DocumentAttribute;
|
||||
documentAttributeFilename#15590068 file_name:string = DocumentAttribute;
|
||||
documentAttributeHasStickers#9801d2f7 = DocumentAttribute;
|
||||
@@ -629,7 +630,7 @@ messages.stickerSetNotModified#d3f924eb = messages.StickerSet;
|
||||
|
||||
botCommand#c27ac8c7 command:string description:string = BotCommand;
|
||||
|
||||
botInfo#8f300b57 flags:# user_id:flags.0?long description:flags.1?string description_photo:flags.4?Photo description_document:flags.5?Document commands:flags.2?Vector<BotCommand> menu_button:flags.3?BotMenuButton = BotInfo;
|
||||
botInfo#8f300b57 flags:# has_preview_medias:flags.6?true user_id:flags.0?long description:flags.1?string description_photo:flags.4?Photo description_document:flags.5?Document commands:flags.2?Vector<BotCommand> menu_button:flags.3?BotMenuButton = BotInfo;
|
||||
|
||||
keyboardButton#a2fa4880 text:string = KeyboardButton;
|
||||
keyboardButtonUrl#258aff05 text:string url:string = KeyboardButton;
|
||||
@@ -789,6 +790,7 @@ topPeerCategoryChannels#161d9628 = TopPeerCategory;
|
||||
topPeerCategoryPhoneCalls#1e76a78c = TopPeerCategory;
|
||||
topPeerCategoryForwardUsers#a8406ca9 = TopPeerCategory;
|
||||
topPeerCategoryForwardChats#fbeec0f0 = TopPeerCategory;
|
||||
topPeerCategoryBotsApp#fd9e7bec = TopPeerCategory;
|
||||
|
||||
topPeerCategoryPeers#fb834291 category:TopPeerCategory count:int peers:Vector<TopPeer> = TopPeerCategoryPeers;
|
||||
|
||||
@@ -1453,7 +1455,7 @@ attachMenuPeerTypeBroadcast#7bfbdefc = AttachMenuPeerType;
|
||||
inputInvoiceMessage#c5b56859 peer:InputPeer msg_id:int = InputInvoice;
|
||||
inputInvoiceSlug#c326caef slug:string = InputInvoice;
|
||||
inputInvoicePremiumGiftCode#98986c0d purpose:InputStorePaymentPurpose option:PremiumGiftCodeOption = InputInvoice;
|
||||
inputInvoiceStars#1da33ad8 option:StarsTopupOption = InputInvoice;
|
||||
inputInvoiceStars#65f00ce3 purpose:InputStorePaymentPurpose = InputInvoice;
|
||||
|
||||
payments.exportedInvoice#aed0cbd9 url:string = payments.ExportedInvoice;
|
||||
|
||||
@@ -1465,7 +1467,8 @@ inputStorePaymentPremiumSubscription#a6751e66 flags:# restore:flags.0?true upgra
|
||||
inputStorePaymentGiftPremium#616f7fe8 user_id:InputUser currency:string amount:long = InputStorePaymentPurpose;
|
||||
inputStorePaymentPremiumGiftCode#a3805f3f flags:# users:Vector<InputUser> boost_peer:flags.0?InputPeer currency:string amount:long = InputStorePaymentPurpose;
|
||||
inputStorePaymentPremiumGiveaway#160544ca flags:# only_new_subscribers:flags.0?true winners_are_visible:flags.3?true boost_peer:InputPeer additional_peers:flags.1?Vector<InputPeer> countries_iso2:flags.2?Vector<string> prize_description:flags.4?string random_id:long until_date:int currency:string amount:long = InputStorePaymentPurpose;
|
||||
inputStorePaymentStars#4f0ee8df flags:# stars:long currency:string amount:long = InputStorePaymentPurpose;
|
||||
inputStorePaymentStarsTopup#dddd0f56 stars:long currency:string amount:long = InputStorePaymentPurpose;
|
||||
inputStorePaymentStarsGift#1d741ef7 user_id:InputUser stars:long currency:string amount:long = InputStorePaymentPurpose;
|
||||
|
||||
premiumGiftOption#74c34319 flags:# months:int currency:string amount:long bot_url:string store_product:flags.0?string = PremiumGiftOption;
|
||||
|
||||
@@ -1613,6 +1616,7 @@ mediaAreaSuggestedReaction#14455871 flags:# dark:flags.0?true flipped:flags.1?tr
|
||||
mediaAreaChannelPost#770416af coordinates:MediaAreaCoordinates channel_id:long msg_id:int = MediaArea;
|
||||
inputMediaAreaChannelPost#2271f2bf coordinates:MediaAreaCoordinates channel:InputChannel msg_id:int = MediaArea;
|
||||
mediaAreaUrl#37381085 coordinates:MediaAreaCoordinates url:string = MediaArea;
|
||||
mediaAreaWeather#49a6549c coordinates:MediaAreaCoordinates emoji:string temperature_c:double color:int = MediaArea;
|
||||
|
||||
peerStories#9a35e999 flags:# peer:Peer max_read_id:flags.0?int stories:Vector<StoryItem> = PeerStories;
|
||||
|
||||
@@ -1806,7 +1810,7 @@ starsTransactionPeerAds#60682812 = StarsTransactionPeer;
|
||||
|
||||
starsTopupOption#bd915c0 flags:# extended:flags.1?true stars:long store_product:flags.0?string currency:string amount:long = StarsTopupOption;
|
||||
|
||||
starsTransaction#2db5418f flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true id:string stars:long date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string bot_payload:flags.7?bytes msg_id:flags.8?int extended_media:flags.9?Vector<MessageMedia> = StarsTransaction;
|
||||
starsTransaction#2db5418f flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true gift:flags.10?true id:string stars:long date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string bot_payload:flags.7?bytes msg_id:flags.8?int extended_media:flags.9?Vector<MessageMedia> = StarsTransaction;
|
||||
|
||||
payments.starsStatus#8cf4ee60 flags:# balance:long history:Vector<StarsTransaction> next_offset:flags.0?string chats:Vector<Chat> users:Vector<User> = payments.StarsStatus;
|
||||
|
||||
@@ -1826,6 +1830,14 @@ payments.starsRevenueAdsAccountUrl#394e7f21 url:string = payments.StarsRevenueAd
|
||||
|
||||
inputStarsTransaction#206ae6d1 flags:# refund:flags.0?true id:string = InputStarsTransaction;
|
||||
|
||||
starsGiftOption#5e0589f1 flags:# extended:flags.1?true stars:long store_product:flags.0?string currency:string amount:long = StarsGiftOption;
|
||||
|
||||
bots.popularAppBots#1991b13b flags:# next_offset:flags.0?string users:Vector<User> = bots.PopularAppBots;
|
||||
|
||||
botPreviewMedia#23e91ba3 date:int media:MessageMedia = BotPreviewMedia;
|
||||
|
||||
bots.previewInfo#ca71d64 media:Vector<BotPreviewMedia> lang_codes:Vector<string> = bots.PreviewInfo;
|
||||
|
||||
---functions---
|
||||
|
||||
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
|
||||
@@ -1992,7 +2004,7 @@ contacts.unblock#b550d328 flags:# my_stories_from:flags.0?true id:InputPeer = Bo
|
||||
contacts.getBlocked#9a868f80 flags:# my_stories_from:flags.0?true offset:int limit:int = contacts.Blocked;
|
||||
contacts.search#11f812d8 q:string limit:int = contacts.Found;
|
||||
contacts.resolveUsername#f93ccba3 username:string = contacts.ResolvedPeer;
|
||||
contacts.getTopPeers#973478b6 flags:# correspondents:flags.0?true bots_pm:flags.1?true bots_inline:flags.2?true phone_calls:flags.3?true forward_users:flags.4?true forward_chats:flags.5?true groups:flags.10?true channels:flags.15?true offset:int limit:int hash:long = contacts.TopPeers;
|
||||
contacts.getTopPeers#973478b6 flags:# correspondents:flags.0?true bots_pm:flags.1?true bots_inline:flags.2?true phone_calls:flags.3?true forward_users:flags.4?true forward_chats:flags.5?true groups:flags.10?true channels:flags.15?true bots_app:flags.16?true offset:int limit:int hash:long = contacts.TopPeers;
|
||||
contacts.resetTopPeerRating#1ae373ac category:TopPeerCategory peer:InputPeer = Bool;
|
||||
contacts.resetSaved#879537f1 = Bool;
|
||||
contacts.getSaved#82f1e39f = Vector<SavedContact>;
|
||||
@@ -2221,6 +2233,7 @@ messages.getAvailableEffects#dea20a39 hash:int = messages.AvailableEffects;
|
||||
messages.editFactCheck#589ee75 peer:InputPeer msg_id:int text:TextWithEntities = Updates;
|
||||
messages.deleteFactCheck#d1da940c peer:InputPeer msg_id:int = Updates;
|
||||
messages.getFactCheck#b9cdc5ee peer:InputPeer msg_id:Vector<int> = Vector<FactCheck>;
|
||||
messages.requestMainWebView#c9e01e7b flags:# compact:flags.7?true peer:InputPeer bot:InputUser start_param:flags.1?string theme_params:flags.0?DataJSON platform:string = WebViewResult;
|
||||
|
||||
updates.getState#edd4882a = updates.State;
|
||||
updates.getDifference#19c2f763 flags:# pts:int pts_limit:flags.1?int pts_total_limit:flags.0?int date:int qts:int qts_limit:flags.2?int = updates.Difference;
|
||||
@@ -2349,6 +2362,13 @@ bots.toggleUsername#53ca973 bot:InputUser username:string active:Bool = Bool;
|
||||
bots.canSendMessage#1359f4e6 bot:InputUser = Bool;
|
||||
bots.allowSendMessage#f132e3ef bot:InputUser = Updates;
|
||||
bots.invokeWebViewCustomMethod#87fc5e7 bot:InputUser custom_method:string params:DataJSON = DataJSON;
|
||||
bots.getPopularAppBots#c2510192 offset:string limit:int = bots.PopularAppBots;
|
||||
bots.addPreviewMedia#17aeb75a bot:InputUser lang_code:string media:InputMedia = BotPreviewMedia;
|
||||
bots.editPreviewMedia#8525606f bot:InputUser lang_code:string media:InputMedia new_media:InputMedia = BotPreviewMedia;
|
||||
bots.deletePreviewMedia#2d0135b3 bot:InputUser lang_code:string media:Vector<InputMedia> = Bool;
|
||||
bots.reorderPreviewMedias#b627f3aa bot:InputUser lang_code:string order:Vector<InputMedia> = Bool;
|
||||
bots.getPreviewInfo#423ab3ad bot:InputUser lang_code:string = bots.PreviewInfo;
|
||||
bots.getPreviewMedias#a2a5594d bot:InputUser = Vector<BotPreviewMedia>;
|
||||
|
||||
payments.getPaymentForm#37148dbb flags:# invoice:InputInvoice theme_params:flags.0?DataJSON = payments.PaymentForm;
|
||||
payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt;
|
||||
@@ -2375,6 +2395,7 @@ payments.getStarsRevenueStats#d91ffad6 flags:# dark:flags.0?true peer:InputPeer
|
||||
payments.getStarsRevenueWithdrawalUrl#13bbe8b3 peer:InputPeer stars:long password:InputCheckPasswordSRP = payments.StarsRevenueWithdrawalUrl;
|
||||
payments.getStarsRevenueAdsAccountUrl#d1d7efc5 peer:InputPeer = payments.StarsRevenueAdsAccountUrl;
|
||||
payments.getStarsTransactionsByID#27842d2e peer:InputPeer id:Vector<InputStarsTransaction> = payments.StarsStatus;
|
||||
payments.getStarsGiftOptions#d3c96bc8 flags:# user_id:flags.0?InputUser = Vector<StarsGiftOption>;
|
||||
|
||||
stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true emojis:flags.5?true text_color:flags.6?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector<InputStickerSetItem> software:flags.3?string = messages.StickerSet;
|
||||
stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet;
|
||||
@@ -2494,4 +2515,4 @@ smsjobs.finishJob#4f1ebf24 flags:# job_id:string error:flags.0?string = Bool;
|
||||
|
||||
fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo;
|
||||
|
||||
// LAYER 184
|
||||
// LAYER 185
|
||||
|
||||
@@ -318,20 +318,21 @@ MTPInputInvoice Form::inputInvoice() const {
|
||||
} else if (const auto slug = std::get_if<InvoiceSlug>(&_id.value)) {
|
||||
return MTP_inputInvoiceSlug(MTP_string(slug->slug));
|
||||
} else if (const auto credits = std::get_if<InvoiceCredits>(&_id.value)) {
|
||||
using Flag = MTPDstarsTopupOption::Flag;
|
||||
const auto emptyFlag = MTPDstarsTopupOption::Flags(0);
|
||||
return MTP_inputInvoiceStars(MTP_starsTopupOption(
|
||||
MTP_flags(emptyFlag
|
||||
| (credits->product.isEmpty()
|
||||
? Flag::f_store_product
|
||||
: emptyFlag)
|
||||
| (credits->extended
|
||||
? Flag::f_extended
|
||||
: emptyFlag)),
|
||||
MTP_long(credits->credits),
|
||||
MTP_string(credits->product),
|
||||
MTP_string(credits->currency),
|
||||
MTP_long(credits->amount)));
|
||||
if (const auto userId = peerToUser(credits->giftPeerId)) {
|
||||
if (const auto user = _session->data().user(userId)) {
|
||||
return MTP_inputInvoiceStars(
|
||||
MTP_inputStorePaymentStarsGift(
|
||||
user->inputUser,
|
||||
MTP_long(credits->credits),
|
||||
MTP_string(credits->currency),
|
||||
MTP_long(credits->amount)));
|
||||
}
|
||||
}
|
||||
return MTP_inputInvoiceStars(
|
||||
MTP_inputStorePaymentStarsTopup(
|
||||
MTP_long(credits->credits),
|
||||
MTP_string(credits->currency),
|
||||
MTP_long(credits->amount)));
|
||||
}
|
||||
const auto &giftCode = v::get<InvoicePremiumGiftCode>(_id.value);
|
||||
using Flag = MTPDpremiumGiftCodeOption::Flag;
|
||||
|
||||
@@ -167,6 +167,7 @@ struct InvoiceCredits {
|
||||
QString currency;
|
||||
uint64 amount = 0;
|
||||
bool extended = false;
|
||||
PeerId giftPeerId = PeerId(0);
|
||||
};
|
||||
|
||||
struct InvoiceId {
|
||||
|
||||
@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/wrap/fade_wrap.h"
|
||||
#include "ui/boxes/single_choice_box.h"
|
||||
#include "ui/chat/attach/attach_bot_webview.h"
|
||||
#include "ui/text/format_values.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/effects/radial_animation.h"
|
||||
@@ -908,32 +909,9 @@ std::shared_ptr<Show> Panel::uiShow() {
|
||||
void Panel::showWebviewError(
|
||||
const QString &text,
|
||||
const Webview::Available &information) {
|
||||
using Error = Webview::Available::Error;
|
||||
Expects(information.error != Error::None);
|
||||
|
||||
auto rich = TextWithEntities{ text };
|
||||
rich.append("\n\n");
|
||||
switch (information.error) {
|
||||
case Error::NoWebview2: {
|
||||
rich.append(tr::lng_payments_webview_install_edge(
|
||||
tr::now,
|
||||
lt_link,
|
||||
Text::Link(
|
||||
"Microsoft Edge WebView2 Runtime",
|
||||
"https://go.microsoft.com/fwlink/p/?LinkId=2124703"),
|
||||
Ui::Text::WithEntities));
|
||||
} break;
|
||||
case Error::NoWebKitGTK:
|
||||
rich.append(tr::lng_payments_webview_install_webkit(tr::now));
|
||||
break;
|
||||
case Error::OldWindows:
|
||||
rich.append(tr::lng_payments_webview_update_windows(tr::now));
|
||||
break;
|
||||
default:
|
||||
rich.append(QString::fromStdString(information.details));
|
||||
break;
|
||||
}
|
||||
showCriticalError(rich);
|
||||
showCriticalError(TextWithEntities{ text }.append(
|
||||
"\n\n"
|
||||
).append(BotWebView::ErrorText(information)));
|
||||
}
|
||||
|
||||
void Panel::updateThemeParams(const Webview::ThemeParams ¶ms) {
|
||||
|
||||
@@ -7,30 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "settings/settings_common_session.h"
|
||||
|
||||
#include "api/api_cloud_password.h"
|
||||
#include "apiwrap.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_cloud_password.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_domain.h"
|
||||
#include "main/main_session.h"
|
||||
#include "settings/cloud_password/settings_cloud_password_email_confirm.h"
|
||||
#include "settings/settings_advanced.h"
|
||||
#include "settings/settings_calls.h"
|
||||
#include "settings/settings_chat.h"
|
||||
#include "settings/settings_experimental.h"
|
||||
#include "settings/settings_folders.h"
|
||||
#include "settings/settings_information.h"
|
||||
#include "settings/settings_main.h"
|
||||
#include "settings/settings_notifications.h"
|
||||
#include "settings/settings_privacy_security.h"
|
||||
#include "ui/widgets/menu/menu_add_action_callback.h"
|
||||
#include "window/themes/window_theme_editor_box.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
|
||||
#include <QAction>
|
||||
|
||||
namespace Settings {
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "settings/settings_credits.h"
|
||||
|
||||
#include "api/api_credits.h"
|
||||
#include "boxes/gift_credits_box.h"
|
||||
#include "boxes/gift_premium_box.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "data/data_file_origin.h"
|
||||
@@ -289,7 +290,21 @@ void Credits::setupContent() {
|
||||
Ui::StartFireworks(_parent);
|
||||
}
|
||||
};
|
||||
FillCreditOptions(_controller->uiShow(), content, 0, paid);
|
||||
const auto self = _controller->session().user();
|
||||
FillCreditOptions(_controller->uiShow(), content, self, 0, paid);
|
||||
{
|
||||
Ui::AddSkip(content);
|
||||
const auto giftButton = AddButtonWithIcon(
|
||||
content,
|
||||
tr::lng_credits_gift_button(),
|
||||
st::settingsButtonLightNoIcon);
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddDivider(content);
|
||||
giftButton->setClickedCallback([=] {
|
||||
Ui::ShowGiftCreditsBox(_controller, paid);
|
||||
});
|
||||
}
|
||||
|
||||
setupHistory(content);
|
||||
|
||||
Ui::ResizeFitChild(this, content);
|
||||
|
||||
@@ -12,11 +12,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/timer_rpl.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/gift_premium_box.h"
|
||||
#include "chat_helpers/stickers_gift_box_pack.h"
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "core/click_handler_types.h" // UrlClickHandler
|
||||
#include "core/ui_integration.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "core/click_handler_types.h" // UrlClickHandler
|
||||
#include "data/data_photo_media.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
@@ -27,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "info/settings/info_settings_widget.h" // SectionCustomTopBarData.
|
||||
#include "info/statistics/info_statistics_list_controllers.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "lottie/lottie_single_player.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "main/main_session.h"
|
||||
#include "payments/payments_checkout_process.h"
|
||||
@@ -227,6 +231,7 @@ void AddViewMediaHandler(
|
||||
void FillCreditOptions(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<PeerData*> peer,
|
||||
int minimumCredits,
|
||||
Fn<void()> paid) {
|
||||
const auto options = container->add(
|
||||
@@ -309,6 +314,7 @@ void FillCreditOptions(
|
||||
.currency = option.currency,
|
||||
.amount = option.amount,
|
||||
.extended = option.extended,
|
||||
.giftPeerId = PeerId(option.giftBarePeerId),
|
||||
};
|
||||
|
||||
const auto weak = Ui::MakeWeak(button);
|
||||
@@ -347,8 +353,7 @@ void FillCreditOptions(
|
||||
};
|
||||
|
||||
using ApiOptions = Api::CreditsTopupOptions;
|
||||
const auto apiCredits = content->lifetime().make_state<ApiOptions>(
|
||||
show->session().user());
|
||||
const auto apiCredits = content->lifetime().make_state<ApiOptions>(peer);
|
||||
|
||||
if (show->session().premiumPossible()) {
|
||||
apiCredits->request(
|
||||
@@ -457,10 +462,66 @@ void ReceiptCreditsBox(
|
||||
content,
|
||||
GenericEntryPhoto(content, callback, stUser.photoSize)));
|
||||
AddViewMediaHandler(thumb->entity(), controller, e);
|
||||
} else if (peer) {
|
||||
} else if (peer && !e.gift) {
|
||||
content->add(object_ptr<Ui::CenterWrap<>>(
|
||||
content,
|
||||
object_ptr<Ui::UserpicButton>(content, peer, stUser)));
|
||||
} else if (e.gift) {
|
||||
struct State final {
|
||||
DocumentData *sticker = nullptr;
|
||||
std::shared_ptr<Data::DocumentMedia> media;
|
||||
std::unique_ptr<Lottie::SinglePlayer> lottie;
|
||||
rpl::lifetime downloadLifetime;
|
||||
};
|
||||
Ui::AddSkip(
|
||||
content,
|
||||
st::creditsHistoryEntryGiftStickerSpace);
|
||||
const auto icon = Ui::CreateChild<Ui::RpWidget>(content);
|
||||
icon->resize(Size(st::creditsHistoryEntryGiftStickerSize));
|
||||
const auto state = icon->lifetime().make_state<State>();
|
||||
auto &packs = session->giftBoxStickersPacks();
|
||||
const auto document = packs.lookup(packs.monthsForStars(e.credits));
|
||||
if (document && document->sticker()) {
|
||||
state->sticker = document;
|
||||
state->media = document->createMediaView();
|
||||
state->media->thumbnailWanted(packs.origin());
|
||||
state->media->automaticLoad(packs.origin(), nullptr);
|
||||
rpl::single() | rpl::then(
|
||||
session->downloaderTaskFinished()
|
||||
) | rpl::filter([=] {
|
||||
return state->media->loaded();
|
||||
}) | rpl::start_with_next([=] {
|
||||
state->lottie = ChatHelpers::LottiePlayerFromDocument(
|
||||
state->media.get(),
|
||||
ChatHelpers::StickerLottieSize::MessageHistory,
|
||||
icon->size(),
|
||||
Lottie::Quality::High);
|
||||
state->lottie->updates() | rpl::start_with_next([=] {
|
||||
icon->update();
|
||||
}, icon->lifetime());
|
||||
state->downloadLifetime.destroy();
|
||||
}, state->downloadLifetime);
|
||||
}
|
||||
icon->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
auto p = Painter(icon);
|
||||
const auto &lottie = state->lottie;
|
||||
const auto frame = (lottie && lottie->ready())
|
||||
? lottie->frameInfo({ .box = icon->size() })
|
||||
: Lottie::Animation::FrameInfo();
|
||||
if (!frame.image.isNull()) {
|
||||
p.drawImage(0, 0, frame.image);
|
||||
if (lottie->frameIndex() < lottie->framesCount() - 1) {
|
||||
lottie->markFrameShown();
|
||||
}
|
||||
}
|
||||
}, icon->lifetime());
|
||||
content->sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &size) {
|
||||
icon->move(
|
||||
(size.width() - icon->width()) / 2,
|
||||
st::creditsHistoryEntryGiftStickerSkip);
|
||||
}, icon->lifetime());
|
||||
} else {
|
||||
const auto widget = content->add(
|
||||
object_ptr<Ui::CenterWrap<>>(
|
||||
@@ -480,7 +541,6 @@ void ReceiptCreditsBox(
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
|
||||
|
||||
box->addRow(object_ptr<Ui::CenterWrap<>>(
|
||||
box,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
@@ -488,6 +548,8 @@ void ReceiptCreditsBox(
|
||||
rpl::single(
|
||||
!e.title.isEmpty()
|
||||
? e.title
|
||||
: e.gift
|
||||
? tr::lng_credits_box_history_entry_gift_name(tr::now)
|
||||
: peer
|
||||
? peer->name()
|
||||
: Ui::GenerateEntryName(e).text),
|
||||
@@ -500,7 +562,7 @@ void ReceiptCreditsBox(
|
||||
auto &lifetime = content->lifetime();
|
||||
const auto text = lifetime.make_state<Ui::Text::String>(
|
||||
st::semiboldTextStyle,
|
||||
(e.in ? QChar('+') : kMinus)
|
||||
(e.in ? u"+"_q : e.gift ? QString() : QString(kMinus))
|
||||
+ Lang::FormatCountDecimal(std::abs(int64(e.credits))));
|
||||
const auto roundedText = e.refunded
|
||||
? tr::lng_channel_earn_history_return(tr::now)
|
||||
@@ -540,6 +602,8 @@ void ReceiptCreditsBox(
|
||||
? st::creditsStroke
|
||||
: e.in
|
||||
? st::boxTextFgGood
|
||||
: e.gift
|
||||
? st::windowBoldFg
|
||||
: st::menuIconAttentionColor);
|
||||
const auto x = (amount->width() - fullWidth) / 2;
|
||||
text->draw(p, Ui::Text::PaintContext{
|
||||
@@ -593,16 +657,47 @@ void ReceiptCreditsBox(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
rpl::single(e.description),
|
||||
st::defaultFlatLabel)));
|
||||
st::creditsBoxAbout)));
|
||||
}
|
||||
if (e.gift) {
|
||||
Ui::AddSkip(content);
|
||||
const auto arrow = Ui::Text::SingleCustomEmoji(
|
||||
session->data().customEmojiManager().registerInternalEmoji(
|
||||
st::topicButtonArrow,
|
||||
st::channelEarnLearnArrowMargins,
|
||||
false));
|
||||
auto link = tr::lng_credits_box_history_entry_gift_about_link(
|
||||
lt_emoji,
|
||||
rpl::single(arrow),
|
||||
Ui::Text::RichLangValue
|
||||
) | rpl::map([](TextWithEntities text) {
|
||||
return Ui::Text::Link(
|
||||
std::move(text),
|
||||
tr::lng_credits_box_history_entry_gift_about_url(tr::now));
|
||||
});
|
||||
box->addRow(object_ptr<Ui::CenterWrap<>>(
|
||||
box,
|
||||
Ui::CreateLabelWithCustomEmoji(
|
||||
box,
|
||||
(!e.in && peer)
|
||||
? tr::lng_credits_box_history_entry_gift_out_about(
|
||||
lt_user,
|
||||
rpl::single(TextWithEntities{ peer->shortName() }),
|
||||
lt_link,
|
||||
std::move(link),
|
||||
Ui::Text::RichLangValue)
|
||||
: tr::lng_credits_box_history_entry_gift_in_about(
|
||||
lt_link,
|
||||
std::move(link),
|
||||
Ui::Text::RichLangValue),
|
||||
{ .session = session },
|
||||
st::creditsBoxAbout)));
|
||||
}
|
||||
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
|
||||
AddCreditsHistoryEntryTable(
|
||||
controller,
|
||||
box->verticalLayout(),
|
||||
e);
|
||||
AddCreditsHistoryEntryTable(controller, content, e);
|
||||
|
||||
Ui::AddSkip(content);
|
||||
|
||||
@@ -613,10 +708,9 @@ void ReceiptCreditsBox(
|
||||
tr::lng_credits_box_out_about(
|
||||
lt_link,
|
||||
tr::lng_payments_terms_link(
|
||||
) | rpl::map([](const QString &t) {
|
||||
using namespace Ui::Text;
|
||||
return Link(t, u"https://telegram.org/tos"_q);
|
||||
}),
|
||||
) | Ui::Text::ToLink(
|
||||
tr::lng_credits_box_out_about_link(tr::now)
|
||||
),
|
||||
Ui::Text::WithEntities),
|
||||
st::creditsBoxAboutDivider)));
|
||||
|
||||
@@ -661,6 +755,32 @@ void ReceiptCreditsBox(
|
||||
}, button->lifetime());
|
||||
}
|
||||
|
||||
void GiftedCreditsBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<PeerData*> from,
|
||||
not_null<PeerData*> to,
|
||||
int count,
|
||||
TimeId date) {
|
||||
const auto received = to->isSelf();
|
||||
const auto anonymous = from->isServiceUser();
|
||||
const auto peer = received ? from : to;
|
||||
using PeerType = Data::CreditsHistoryEntry::PeerType;
|
||||
Settings::ReceiptCreditsBox(box, controller, nullptr, {
|
||||
.id = QString(),
|
||||
.title = (received
|
||||
? tr::lng_credits_box_history_entry_gift_name
|
||||
: tr::lng_credits_box_history_entry_gift_sent)(tr::now),
|
||||
.date = base::unixtime::parse(date),
|
||||
.credits = uint64(count),
|
||||
.bareMsgId = uint64(),
|
||||
.barePeerId = (anonymous ? uint64() : peer->id.value),
|
||||
.peerType = (anonymous ? PeerType::Fragment : PeerType::Peer),
|
||||
.in = received,
|
||||
.gift = true,
|
||||
});
|
||||
}
|
||||
|
||||
void ShowRefundInfoBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
FullMsgId refundItemId) {
|
||||
@@ -769,7 +889,12 @@ void SmallBalanceBox(
|
||||
}));
|
||||
}();
|
||||
|
||||
FillCreditOptions(show, box->verticalLayout(), creditsNeeded, done);
|
||||
FillCreditOptions(
|
||||
show,
|
||||
box->verticalLayout(),
|
||||
show->session().user(),
|
||||
creditsNeeded,
|
||||
done);
|
||||
|
||||
content->setMaximumHeight(st::creditsLowBalancePremiumCoverHeight);
|
||||
content->setMinimumHeight(st::infoLayerTopBarHeight);
|
||||
|
||||
@@ -35,6 +35,7 @@ namespace Settings {
|
||||
void FillCreditOptions(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<PeerData*> peer,
|
||||
int minCredits,
|
||||
Fn<void()> paid);
|
||||
|
||||
@@ -58,6 +59,13 @@ void ReceiptCreditsBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
PeerData *premiumBot,
|
||||
const Data::CreditsHistoryEntry &e);
|
||||
void GiftedCreditsBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<PeerData*> from,
|
||||
not_null<PeerData*> to,
|
||||
int count,
|
||||
TimeId date);
|
||||
void ShowRefundInfoBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
FullMsgId refundItemId);
|
||||
|
||||
@@ -149,6 +149,7 @@ void SetupExperimental(
|
||||
addToggle(Media::Player::kOptionDisableAutoplayNext);
|
||||
addToggle(kOptionSendLargePhotos);
|
||||
addToggle(Webview::kOptionWebviewDebugEnabled);
|
||||
addToggle(Webview::kOptionWebviewLegacyEdge);
|
||||
addToggle(kOptionAutoScrollInactiveChat);
|
||||
addToggle(Window::Notifications::kOptionGNotification);
|
||||
addToggle(Core::kOptionFreeType);
|
||||
|
||||
@@ -844,7 +844,8 @@ void FileLoadTask::process(Args &&args) {
|
||||
MTP_double(realSeconds),
|
||||
MTP_int(coverWidth),
|
||||
MTP_int(coverHeight),
|
||||
MTPint())); // preload_prefix_size
|
||||
MTPint(), // preload_prefix_size
|
||||
MTPdouble())); // video_start_ts
|
||||
|
||||
if (args.generateGoodThumbnail) {
|
||||
goodThumbnail = video->thumbnail;
|
||||
|
||||
@@ -207,7 +207,8 @@ DocumentData *Document::readFromStreamHelper(
|
||||
MTP_double(duration / 1000.),
|
||||
MTP_int(width),
|
||||
MTP_int(height),
|
||||
MTPint())); // preload_prefix_size
|
||||
MTPint(), // preload_prefix_size
|
||||
MTPdouble())); // video_start_ts
|
||||
} else {
|
||||
attributes.push_back(MTP_documentAttributeImageSize(
|
||||
MTP_int(width),
|
||||
|
||||
@@ -3198,4 +3198,18 @@ bool Account::decrypt(
|
||||
return true;
|
||||
}
|
||||
|
||||
Webview::StorageId TonSiteStorageId() {
|
||||
auto result = Webview::StorageId{
|
||||
.path = BaseGlobalPath() + u"webview-tonsite"_q,
|
||||
.token = Core::App().settings().tonsiteStorageToken(),
|
||||
};
|
||||
if (result.token.isEmpty()) {
|
||||
result.token = QByteArray::fromStdString(
|
||||
Webview::GenerateStorageToken());
|
||||
Core::App().settings().setTonsiteStorageToken(result.token);
|
||||
Core::App().saveSettingsDelayed();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Storage
|
||||
|
||||
@@ -327,4 +327,6 @@ private:
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] Webview::StorageId TonSiteStorageId();
|
||||
|
||||
} // namespace Storage
|
||||
|
||||
@@ -746,8 +746,9 @@ postEvent: function(eventType, eventData) {
|
||||
&QGuiApplication::focusWindowChanged
|
||||
) | rpl::filter([=](QWindow *focused) {
|
||||
const auto handle = _widget->window()->windowHandle();
|
||||
return _webview
|
||||
&& !_webview->window.widget()->isHidden()
|
||||
const auto widget = _webview ? _webview->window.widget() : nullptr;
|
||||
return widget
|
||||
&& !widget->isHidden()
|
||||
&& handle
|
||||
&& (focused == handle);
|
||||
}) | rpl::start_with_next([=] {
|
||||
@@ -1400,32 +1401,34 @@ if (window.TelegramGameProxy) {
|
||||
)");
|
||||
}
|
||||
|
||||
void Panel::showWebviewError(
|
||||
const QString &text,
|
||||
const Webview::Available &information) {
|
||||
using Error = Webview::Available::Error;
|
||||
Expects(information.error != Error::None);
|
||||
TextWithEntities ErrorText(const Webview::Available &info) {
|
||||
Expects(info.error != Webview::Available::Error::None);
|
||||
|
||||
auto rich = TextWithEntities{ text };
|
||||
rich.append("\n\n");
|
||||
switch (information.error) {
|
||||
case Error::NoWebview2: {
|
||||
rich.append(tr::lng_payments_webview_install_edge(
|
||||
using Error = Webview::Available::Error;
|
||||
switch (info.error) {
|
||||
case Error::NoWebview2:
|
||||
return tr::lng_payments_webview_install_edge(
|
||||
tr::now,
|
||||
lt_link,
|
||||
Text::Link(
|
||||
"Microsoft Edge WebView2 Runtime",
|
||||
"https://go.microsoft.com/fwlink/p/?LinkId=2124703"),
|
||||
Ui::Text::WithEntities));
|
||||
} break;
|
||||
Ui::Text::WithEntities);
|
||||
case Error::NoWebKitGTK:
|
||||
rich.append(tr::lng_payments_webview_install_webkit(tr::now));
|
||||
break;
|
||||
return { tr::lng_payments_webview_install_webkit(tr::now) };
|
||||
case Error::OldWindows:
|
||||
return { tr::lng_payments_webview_update_windows(tr::now) };
|
||||
default:
|
||||
rich.append(QString::fromStdString(information.details));
|
||||
break;
|
||||
return { QString::fromStdString(info.details) };
|
||||
}
|
||||
showCriticalError(rich);
|
||||
}
|
||||
|
||||
void Panel::showWebviewError(
|
||||
const QString &text,
|
||||
const Webview::Available &information) {
|
||||
showCriticalError(TextWithEntities{ text }.append(
|
||||
"\n\n"
|
||||
).append(ErrorText(information)));
|
||||
}
|
||||
|
||||
rpl::lifetime &Panel::lifetime() {
|
||||
|
||||
@@ -30,6 +30,8 @@ struct Available;
|
||||
|
||||
namespace Ui::BotWebView {
|
||||
|
||||
[[nodiscard]] TextWithEntities ErrorText(const Webview::Available &info);
|
||||
|
||||
struct MainButtonArgs {
|
||||
bool isActive = false;
|
||||
bool isVisible = false;
|
||||
|
||||
@@ -22,4 +22,12 @@ std::optional<QColor> MaybeColorFromSerialized(quint32 serialized) {
|
||||
: std::make_optional(ColorFromSerialized(serialized));
|
||||
}
|
||||
|
||||
QColor Color32FromSerialized(quint32 serialized) {
|
||||
return QColor(
|
||||
int((serialized >> 24) & 0xFFU),
|
||||
int((serialized >> 16) & 0xFFU),
|
||||
int((serialized >> 8) & 0xFFU),
|
||||
int(serialized & 0xFFU));
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
@@ -12,5 +12,6 @@ namespace Ui {
|
||||
[[nodiscard]] QColor ColorFromSerialized(quint32 serialized);
|
||||
[[nodiscard]] std::optional<QColor> MaybeColorFromSerialized(
|
||||
quint32 serialized);
|
||||
[[nodiscard]] QColor Color32FromSerialized(quint32 serialized);
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
@@ -49,3 +49,11 @@ starIconSmall: icon{{ "payments/small_star", windowFg }};
|
||||
starIconSmallPadding: margins(0px, -2px, 0px, 0px);
|
||||
|
||||
creditsHistoryEntryTypeAds: icon {{ "folders/folders_channels", premiumButtonFg }};
|
||||
|
||||
creditsHistoryEntryGiftStickerSkip: -20px;
|
||||
creditsHistoryEntryGiftStickerSize: 150px;
|
||||
creditsHistoryEntryGiftStickerSpace: 105px;
|
||||
|
||||
creditsGiftBox: Box(defaultBox) {
|
||||
shadowIgnoreTopSkip: true;
|
||||
}
|
||||
|
||||
@@ -217,7 +217,7 @@ PaintRoundImageCallback GenerateCreditsPaintUserpicCallback(
|
||||
case Data::CreditsHistoryEntry::PeerType::PlayMarket:
|
||||
return { st::historyPeer2UserpicBg, st::historyPeer2UserpicBg2 };
|
||||
case Data::CreditsHistoryEntry::PeerType::Fragment:
|
||||
return { st::historyPeer8UserpicBg, st::historyPeer8UserpicBg2 };
|
||||
return { st::windowSubTextFg, st::imageBg };
|
||||
case Data::CreditsHistoryEntry::PeerType::PremiumBot:
|
||||
return { st::historyPeer8UserpicBg, st::historyPeer8UserpicBg2 };
|
||||
case Data::CreditsHistoryEntry::PeerType::Ads:
|
||||
@@ -455,8 +455,10 @@ Fn<PaintRoundImageCallback(Fn<void()>)> PaintPreviewCallback(
|
||||
}
|
||||
|
||||
TextWithEntities GenerateEntryName(const Data::CreditsHistoryEntry &entry) {
|
||||
return ((entry.peerType == Data::CreditsHistoryEntry::PeerType::Fragment)
|
||||
? tr::lng_bot_username_description1_link
|
||||
return (entry.gift
|
||||
? tr::lng_credits_box_history_entry_gift_name
|
||||
: (entry.peerType == Data::CreditsHistoryEntry::PeerType::Fragment)
|
||||
? tr::lng_credits_box_history_entry_fragment
|
||||
: (entry.peerType == Data::CreditsHistoryEntry::PeerType::PremiumBot)
|
||||
? tr::lng_credits_box_history_entry_premium_bot
|
||||
: (entry.peerType == Data::CreditsHistoryEntry::PeerType::Ads)
|
||||
|
||||
@@ -146,11 +146,11 @@ QString FillAmountAndCurrency(
|
||||
// std::abs doesn't work on that one :/
|
||||
Expects(amount != std::numeric_limits<int64>::min());
|
||||
|
||||
const auto rule = LookupCurrencyRule(currency);
|
||||
if (currency == kCreditsCurrency) {
|
||||
return QChar(0x2B50) + Lang::FormatCountDecimal(std::abs(amount));
|
||||
}
|
||||
|
||||
const auto rule = LookupCurrencyRule(currency);
|
||||
const auto prefix = (amount < 0)
|
||||
? QString::fromUtf8("\xe2\x88\x92")
|
||||
: QString();
|
||||
|
||||
@@ -28,31 +28,34 @@ void AddDivider(not_null<Ui::VerticalLayout*> container) {
|
||||
container->add(object_ptr<Ui::BoxContentDivider>(container));
|
||||
}
|
||||
|
||||
void AddDividerText(
|
||||
not_null<Ui::FlatLabel*> AddDividerText(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
rpl::producer<QString> text,
|
||||
const style::margins &margins,
|
||||
RectParts parts) {
|
||||
AddDividerText(
|
||||
return AddDividerText(
|
||||
container,
|
||||
std::move(text) | Ui::Text::ToWithEntities(),
|
||||
margins,
|
||||
parts);
|
||||
}
|
||||
|
||||
void AddDividerText(
|
||||
not_null<Ui::FlatLabel*> AddDividerText(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
rpl::producer<TextWithEntities> text,
|
||||
const style::margins &margins,
|
||||
RectParts parts) {
|
||||
auto label = object_ptr<Ui::FlatLabel>(
|
||||
container,
|
||||
std::move(text),
|
||||
st::boxDividerLabel);
|
||||
const auto result = label.data();
|
||||
container->add(object_ptr<Ui::DividerLabel>(
|
||||
container,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
container,
|
||||
std::move(text),
|
||||
st::boxDividerLabel),
|
||||
std::move(label),
|
||||
margins,
|
||||
parts));
|
||||
return result;
|
||||
}
|
||||
|
||||
not_null<Ui::FlatLabel*> AddSubsectionTitle(
|
||||
|
||||
@@ -25,12 +25,12 @@ class VerticalLayout;
|
||||
void AddSkip(not_null<Ui::VerticalLayout*> container);
|
||||
void AddSkip(not_null<Ui::VerticalLayout*> container, int skip);
|
||||
void AddDivider(not_null<Ui::VerticalLayout*> container);
|
||||
void AddDividerText(
|
||||
not_null<Ui::FlatLabel*> AddDividerText(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
rpl::producer<QString> text,
|
||||
const style::margins &margins = st::defaultBoxDividerLabelPadding,
|
||||
RectParts parts = RectPart::Top | RectPart::Bottom);
|
||||
void AddDividerText(
|
||||
not_null<Ui::FlatLabel*> AddDividerText(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
rpl::producer<TextWithEntities> text,
|
||||
const style::margins &margins = st::defaultBoxDividerLabelPadding,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user