Compare commits

...

50 Commits

Author SHA1 Message Date
John Preston
b7c14f17a7 Version 5.3: Fix build with Xcode. 2024-07-31 23:06:39 +02:00
John Preston
0b6bd7075a Version 5.3.
- View recent and popular web apps in chats search.
- Open several web apps in different windows.
- Gift Telegram Stars to your friends.
- Send location marks and venues.
- Open tonsite:// links in webview.
- Edit order of stickers in your packs.
2024-07-31 19:06:47 +02:00
John Preston
148690d8b1 Open .ton as a tonsite. 2024-07-31 18:59:31 +02:00
John Preston
a422aec99a Fix cancel of bot app confirm. 2024-07-31 18:59:24 +02:00
John Preston
813d0501da Fix build on Windows. 2024-07-31 18:58:44 +02:00
John Preston
db80096e6b Use corporate sign certificate identity. 2024-07-31 17:03:56 +02:00
John Preston
cf896aeb13 Improve focusing of shown layers. 2024-07-31 17:03:56 +02:00
John Preston
76314e3c03 Close additional windows on passcode lock. 2024-07-31 17:03:56 +02:00
John Preston
8959679b3c Make IV internal links bg semi-transparent. 2024-07-31 17:03:56 +02:00
John Preston
bb6c94ef4f Handle tonsite:// links from the system. 2024-07-31 17:03:56 +02:00
John Preston
fb9ce6d3a8 Provide canGo[Back|Forward] for tonsite-s. 2024-07-31 17:03:56 +02:00
John Preston
dac4389e37 Fix build on Linux. 2024-07-31 17:03:55 +02:00
John Preston
a25b2e9700 Show webview error in Iv::Controller. 2024-07-31 17:03:55 +02:00
John Preston
699a7bdc58 Fix possible crash in message field.
Fixes #28203.
2024-07-31 17:03:55 +02:00
John Preston
4108debca0 Fix possible crash in web apps. 2024-07-31 17:03:55 +02:00
John Preston
8b2bbfba6a Navigate back/forward in tonsite pages. 2024-07-31 17:03:55 +02:00
John Preston
4f37343e8b Use special webview storage for tonsite. 2024-07-31 17:03:55 +02:00
John Preston
2dcf40817e Initial tonsite:// show in IV window. 2024-07-31 17:03:55 +02:00
John Preston
3eeb01be61 Save Celsius/Fahrenheit in Settings. 2024-07-31 17:03:55 +02:00
John Preston
6a8a85e395 Implement recent/popular apps list. 2024-07-31 17:03:55 +02:00
John Preston
031233ea98 Remove some code duplication. 2024-07-31 17:03:54 +02:00
John Preston
4b09050061 Implement Stars gift view from service messages. 2024-07-31 17:03:54 +02:00
John Preston
992c876930 Show correct presents in Stars gifts. 2024-07-31 17:03:54 +02:00
John Preston
a5ffd8b7cf Request new main web app. 2024-07-31 17:03:54 +02:00
John Preston
5fdd4eba80 Implement weather area in stories. 2024-07-31 17:03:54 +02:00
John Preston
54ce85f8e6 Show nice star in stars payments. 2024-07-31 17:03:54 +02:00
23rd
0bfb0fd045 Added initial ability to gift credits to users. 2024-07-31 17:03:54 +02:00
23rd
8ad2d3d39a Added api support to create invoice for credit gifts. 2024-07-31 17:03:54 +02:00
23rd
b8a19b56b6 Removed window session controller usage from list of credit options. 2024-07-31 17:03:54 +02:00
23rd
24b93a5eff Added support for credits gift options to list of credit options. 2024-07-31 17:03:53 +02:00
23rd
127f651d5e Added api support to get credits gift options. 2024-07-31 17:03:53 +02:00
23rd
e760a0983f Added gift sticker to ReceiptCreditsBox for gifts. 2024-07-31 17:03:53 +02:00
23rd
3f2cb8f8c9 Added file origin to sticker pack for gifts. 2024-07-31 17:03:53 +02:00
23rd
bcb6e9e1af Removed unused include directives from settings_common_session. 2024-07-31 17:03:53 +02:00
23rd
847d66c973 Added initial support of received gifts in list of credits history. 2024-07-31 17:03:53 +02:00
John Preston
5c797d1f31 Update API scheme to layer 185. 2024-07-31 17:03:53 +02:00
23rd
f749616dd8 Added additional request of special sticker pack on failing view pack. 2024-07-31 16:50:48 +03:00
23rd
3cc92e01fe Added ability to force request favorite stickers. 2024-07-31 16:50:04 +03:00
23rd
06f2b23687 Added badge to header for owned sticker sets in stickers list. 2024-07-31 13:05:29 +03:00
23rd
6a167b33f5 Added ability to delete sticker from sticker set box. 2024-07-31 11:57:19 +03:00
23rd
850155b3be Added shake animation while reordering to sticker set box. 2024-07-31 11:57:19 +03:00
23rd
358e586801 Added api support to reorder stickers from sticker set box. 2024-07-31 11:57:19 +03:00
23rd
54214ff2ad Added initial ability to drag stickers in sticker set box. 2024-07-30 13:52:32 +03:00
23rd
06fc813e95 Added loading state to box for renaming of stickers set. 2024-07-30 13:52:32 +03:00
23rd
0046bae53f Added ability to change name of sticker set from sticker set box. 2024-07-30 13:52:32 +03:00
23rd
aeb5e57061 Fixed profile opening of group call participant in separate windows. 2024-07-30 13:52:27 +03:00
23rd
a32b781e49 Improved Controller::invokeForSessionController for separate windows. 2024-07-30 13:52:27 +03:00
23rd
caef698e54 Fixed recursive invoking of Application::windowFor. 2024-07-30 13:52:27 +03:00
23rd
e9650385ad Added ability to provide custom style to widget with infinite animation. 2024-07-30 10:54:38 +03:00
23rd
f55584b160 Fixed position of click handler for via bot header in message view. 2024-07-30 10:54:38 +03:00
112 changed files with 3816 additions and 1008 deletions

View File

@@ -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

View File

@@ -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) {

View File

@@ -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";

View File

@@ -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="-- &quot;%1&quot;" />
<uap3:Extension Category="windows.protocol">
</uap3:Extension>
<uap3:Protocol Name="tonsite" Parameters="-- &quot;%1&quot;" />
</uap3:Extension>
<desktop:Extension
Category="windows.startupTask"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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;
};

View File

@@ -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>) {

View File

@@ -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()),

View File

@@ -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);

View File

@@ -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);

View 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

View 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

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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);
}

View File

@@ -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 = [=] {

View File

@@ -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) {

View File

@@ -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(

View File

@@ -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);

View File

@@ -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;
};

View File

@@ -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,

View File

@@ -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();

View File

@@ -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() {

View File

@@ -378,6 +378,7 @@ private:
void showOpenGLCrashNotification();
void clearPasscodeLock();
void closeAdditionalWindows();
bool openCustomUrl(
const QString &protocol,

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 &) {

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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(

View File

@@ -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,

View File

@@ -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 = [=] {

View File

@@ -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)) {

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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(

View File

@@ -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>;

View File

@@ -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,

View File

@@ -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

View File

@@ -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(

View File

@@ -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;
}

View File

@@ -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(

View File

@@ -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()) {

View File

@@ -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)) {

View File

@@ -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,
});
}
}

View File

@@ -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 &currency) {
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,

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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(

View File

@@ -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,

View File

@@ -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;

View File

@@ -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());
}

View File

@@ -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(

View File

@@ -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();

View File

@@ -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(

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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));

View File

@@ -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>())

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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

View File

@@ -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;

View File

@@ -167,6 +167,7 @@ struct InvoiceCredits {
QString currency;
uint64 amount = 0;
bool extended = false;
PeerId giftPeerId = PeerId(0);
};
struct InvoiceId {

View File

@@ -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 &params) {

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;

View File

@@ -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),

View File

@@ -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

View File

@@ -327,4 +327,6 @@ private:
};
[[nodiscard]] Webview::StorageId TonSiteStorageId();
} // namespace Storage

View File

@@ -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() {

View File

@@ -30,6 +30,8 @@ struct Available;
namespace Ui::BotWebView {
[[nodiscard]] TextWithEntities ErrorText(const Webview::Available &info);
struct MainButtonArgs {
bool isActive = false;
bool isVisible = false;

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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();

View File

@@ -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(

View File

@@ -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