diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml
index ca0c7bf09..6de53f12d 100644
--- a/.github/workflows/win.yml
+++ b/.github/workflows/win.yml
@@ -169,6 +169,8 @@ jobs:
%TDESKTOP_BUILD_GENERATOR% ^
%TDESKTOP_BUILD_ARCH% ^
%TDESKTOP_BUILD_API% ^
+ -D CMAKE_C_FLAGS="/WX" ^
+ -D CMAKE_CXX_FLAGS="/WX" ^
-D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF ^
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF ^
-D DESKTOP_APP_NO_PDB=ON ^
diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt
index c4dc6043d..91f9a4bed 100644
--- a/Telegram/CMakeLists.txt
+++ b/Telegram/CMakeLists.txt
@@ -341,6 +341,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
@@ -544,6 +546,8 @@ PRIVATE
data/business/data_shortcut_messages.h
data/components/factchecks.cpp
data/components/factchecks.h
+ data/components/location_pickers.cpp
+ data/components/location_pickers.h
data/components/recent_peers.cpp
data/components/recent_peers.h
data/components/scheduled_messages.cpp
@@ -1541,6 +1545,8 @@ PRIVATE
ui/chat/choose_send_as.h
ui/chat/choose_theme_controller.cpp
ui/chat/choose_theme_controller.h
+ ui/controls/location_picker.cpp
+ ui/controls/location_picker.h
ui/controls/silent_toggle.cpp
ui/controls/silent_toggle.h
ui/controls/userpic_button.cpp
@@ -1562,6 +1568,10 @@ PRIVATE
ui/image/image_location.h
ui/image/image_location_factory.cpp
ui/image/image_location_factory.h
+ ui/text/format_song_document_name.cpp
+ ui/text/format_song_document_name.h
+ ui/widgets/label_with_custom_emoji.cpp
+ ui/widgets/label_with_custom_emoji.h
ui/countryinput.cpp
ui/countryinput.h
ui/dynamic_thumbnails.cpp
@@ -1575,10 +1585,6 @@ PRIVATE
ui/resize_area.h
ui/search_field_controller.cpp
ui/search_field_controller.h
- ui/text/format_song_document_name.cpp
- ui/text/format_song_document_name.h
- ui/widgets/label_with_custom_emoji.cpp
- ui/widgets/label_with_custom_emoji.h
ui/unread_badge.cpp
ui/unread_badge.h
window/main_window.cpp
@@ -1685,6 +1691,7 @@ PRIVATE
qrc/telegram/animations.qrc
qrc/telegram/export.qrc
qrc/telegram/iv.qrc
+ qrc/telegram/picker.qrc
qrc/telegram/telegram.qrc
qrc/telegram/sounds.qrc
winrc/Telegram.rc
@@ -1915,9 +1922,14 @@ if (WIN32)
/DELAYLOAD:propsys.dll
)
if (QT_VERSION GREATER 6)
+ if (NOT build_winarm)
+ target_link_options(Telegram PRIVATE
+ /DELAYLOAD:API-MS-Win-EventLog-Legacy-l1-1-0.dll
+ )
+ endif()
+
target_link_options(Telegram
PRIVATE
- /DELAYLOAD:API-MS-Win-EventLog-Legacy-l1-1-0.dll
/DELAYLOAD:API-MS-Win-Core-Console-l1-1-0.dll
/DELAYLOAD:API-MS-Win-Core-Fibers-l2-1-0.dll
/DELAYLOAD:API-MS-Win-Core-Fibers-l2-1-1.dll
@@ -1934,7 +1946,7 @@ if (WIN32)
/DELAYLOAD:API-MS-Win-Core-WinRT-Error-l1-1-0.dll
/DELAYLOAD:API-MS-Win-Core-WinRT-String-l1-1-0.dll
/DELAYLOAD:API-MS-Win-Security-CryptoAPI-l1-1-0.dll
- /DELAYLOAD:API-MS-Win-Shcore-Scaling-l1-1-1.dll
+ # /DELAYLOAD:API-MS-Win-Shcore-Scaling-l1-1-1.dll # We shadowed GetDpiForMonitor
/DELAYLOAD:authz.dll # Authz.lib
/DELAYLOAD:comdlg32.dll
/DELAYLOAD:dwrite.dll # DWrite.lib
diff --git a/Telegram/Resources/icons/chat/filled_location.png b/Telegram/Resources/icons/chat/filled_location.png
new file mode 100644
index 000000000..12cd2dcc8
Binary files /dev/null and b/Telegram/Resources/icons/chat/filled_location.png differ
diff --git a/Telegram/Resources/icons/chat/filled_location@2x.png b/Telegram/Resources/icons/chat/filled_location@2x.png
new file mode 100644
index 000000000..cdef3f274
Binary files /dev/null and b/Telegram/Resources/icons/chat/filled_location@2x.png differ
diff --git a/Telegram/Resources/icons/chat/filled_location@3x.png b/Telegram/Resources/icons/chat/filled_location@3x.png
new file mode 100644
index 000000000..11caf17ca
Binary files /dev/null and b/Telegram/Resources/icons/chat/filled_location@3x.png differ
diff --git a/Telegram/Resources/icons/settings/premium/features/feature_off_sponsored.png b/Telegram/Resources/icons/settings/premium/features/feature_off_sponsored.png
new file mode 100644
index 000000000..06a33dc3c
Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_off_sponsored.png differ
diff --git a/Telegram/Resources/icons/settings/premium/features/feature_off_sponsored@2x.png b/Telegram/Resources/icons/settings/premium/features/feature_off_sponsored@2x.png
new file mode 100644
index 000000000..996851dbc
Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_off_sponsored@2x.png differ
diff --git a/Telegram/Resources/icons/settings/premium/features/feature_off_sponsored@3x.png b/Telegram/Resources/icons/settings/premium/features/feature_off_sponsored@3x.png
new file mode 100644
index 000000000..0863b33e8
Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_off_sponsored@3x.png differ
diff --git a/Telegram/Resources/iv_html/page.js b/Telegram/Resources/iv_html/page.js
index bae02fe48..fda34772f 100644
--- a/Telegram/Resources/iv_html/page.js
+++ b/Telegram/Resources/iv_html/page.js
@@ -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) {
diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 9213a3203..9eb209ceb 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -1115,6 +1115,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_faq" = "Telegram FAQ";
"lng_settings_faq_link" = "https://telegram.org/faq#general-questions";
"lng_settings_features" = "Telegram Features";
+"lng_settings_credits" = "Your Stars";
"lng_settings_logout" = "Log Out";
"lng_sure_logout" = "Are you sure you want to log out?";
@@ -1447,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";
@@ -1844,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";
@@ -1888,6 +1893,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_boost_apply#one" = "{from} boosted the group";
"lng_action_boost_apply#other" = "{from} boosted the group {count} times";
"lng_action_set_chat_intro" = "{from} added the message below for all empty chats. How?";
+"lng_action_payment_refunded" = "{peer} refunded back {amount}";
"lng_similar_channels_title" = "Similar channels";
"lng_similar_channels_view_all" = "View all";
@@ -2340,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**?";
@@ -2355,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}.";
@@ -2362,11 +2370,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_summary_in_toast_about#one" = "**{count}** Star added to your balance.";
"lng_credits_summary_in_toast_about#other" = "**{count}** Stars added to your balance.";
"lng_credits_box_history_entry_peer" = "Recipient";
+"lng_credits_box_history_entry_peer_in" = "From";
"lng_credits_box_history_entry_via" = "Via";
"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";
"lng_credits_box_history_entry_id" = "Transaction ID";
"lng_credits_box_history_entry_id_copied" = "Transaction ID copied to clipboard.";
"lng_credits_box_history_entry_success_date" = "Transaction date";
@@ -2379,9 +2397,12 @@ 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";
+"lng_location_set_map" = "Set Location on Map";
"lng_location_fallback" = "You can set your location on the map from your mobile device.";
"lng_hours_title" = "Business Hours";
@@ -2810,12 +2831,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_prizes_badge" = "x{amount}";
"lng_prizes_results_title" = "Winners Selected!";
+"lng_prizes_results_title_one" = "Winner Selected!";
"lng_prizes_results_about#one" = "**{count}** winner of the {link} was randomly selected by Telegram.";
"lng_prizes_results_about#other" = "**{count}** winners of the {link} were randomly selected by Telegram.";
"lng_prizes_results_link" = "Giveaway";
+"lng_prizes_results_winner" = "Winner";
"lng_prizes_results_winners" = "Winners";
"lng_prizes_results_more#one" = "and {count} more!";
"lng_prizes_results_more#other" = "and {count} more!";
+"lng_prizes_results_one" = "The winner received their gift link in a private message.";
"lng_prizes_results_all" = "All winners received gift links in private messages.";
"lng_prizes_results_some" = "Some winners couldn't be selected.";
@@ -2845,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.";
@@ -2920,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";
@@ -3151,6 +3187,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_bot_close_warning_sure" = "Close anyway";
"lng_bot_add_to_side_menu" = "{bot} asks your permission to be added as an option to your main menu so you can access it any time.";
"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";
@@ -3186,6 +3226,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_unread_bar_some" = "Unread messages";
"lng_maps_point" = "Location";
+"lng_maps_select_on_map" = "Select on the Map";
+"lng_maps_point_send" = "Send This Location";
+"lng_maps_point_set" = "Set This Location";
+"lng_maps_or_choose" = "Or choose a venue";
+"lng_maps_places_in_area" = "Places in this area";
+"lng_maps_no_places" = "No places found";
+"lng_maps_choose_to_search" = "Choose location to see places nearby.";
+"lng_maps_venues_source" = "Powered by Foursquare";
"lng_live_location" = "Live Location";
"lng_live_location_now" = "updated just now";
"lng_live_location_minutes#one" = "updated {count} minute ago";
@@ -3740,6 +3788,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_payments_card_declined" = "Your card was declined.";
"lng_payments_payment_failed" = "Payment failed. Your card has not been billed.";
"lng_payments_precheckout_failed" = "The bot couldn't process your payment. Your card has not been billed.";
+"lng_payments_precheckout_timeout" = "The bot didn't respond in time. Your card has not been billed.";
+"lng_payments_precheckout_stars_failed" = "The bot couldn't process your payment.";
+"lng_payments_precheckout_stars_timeout" = "The bot didn't respond in time.";
"lng_payments_already_paid" = "You have already paid for this item.";
"lng_payments_terms_title" = "Terms of Service";
@@ -5267,6 +5318,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_iv_join_channel" = "Join";
"lng_iv_window_title" = "Instant View";
"lng_iv_wrong_layout" = "Wrong layout?";
+"lng_iv_not_supported" = "This link appears to be invalid.";
"lng_limit_download_title" = "Download speed limited";
"lng_limit_download_subscribe" = "Subscribe to {link} and increase download speed {increase}.";
@@ -5295,12 +5347,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";
diff --git a/Telegram/Resources/picker_html/picker.css b/Telegram/Resources/picker_html/picker.css
new file mode 100644
index 000000000..ac3d5912b
--- /dev/null
+++ b/Telegram/Resources/picker_html/picker.css
@@ -0,0 +1,120 @@
+:root {
+ --font-sans: -apple-system, BlinkMacSystemFont, avenir next, avenir, Segoe UI Variable Text, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, tahoma, arial, sans-serif;
+}
+
+html {
+ width: 100%;
+ height: 100%;
+ padding: 0;
+ margin: 0;
+}
+
+body {
+ font-family: var(--font-sans);
+ width: 100%;
+ height: 100%;
+ padding: 0;
+ margin: 0;
+ background-color: var(--td-window-bg);
+ color: var(--td-window-fg);
+}
+
+html.custom_scroll ::-webkit-scrollbar {
+ border-radius: 5px !important;
+ border: 3px solid transparent !important;
+ background-color: var(--td-scroll-bg) !important;
+ background-clip: content-box !important;
+ width: 10px !important;
+}
+html.custom_scroll ::-webkit-scrollbar:hover {
+ background-color: var(--td-scroll-bg-over) !important;
+}
+html.custom_scroll ::-webkit-scrollbar-thumb {
+ border-radius: 5px !important;
+ border: 3px solid transparent !important;
+ background-color: var(--td-scroll-bar-bg) !important;
+ background-clip: content-box !important;
+}
+html.custom_scroll ::-webkit-scrollbar-thumb:hover {
+ background-color: var(--td-scroll-bar-bg-over) !important;
+}
+
+#map {
+ position: relative;
+ width: 100%;
+ height: 100%;
+}
+#marker {
+ pointer-events: none;
+ display: none;
+ z-index: 2;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ justify-content: center;
+ align-items: center;
+}
+#marker_drop {
+ margin-bottom: 0px;
+ transition: margin 160ms ease-in-out;
+}
+#marker_drop.moving {
+ margin-bottom: 24px;
+}
+#marker_shadow {
+ position: absolute;
+}
+#search_venues {
+ position: absolute;
+ left: 50%;
+ transform: translateX(-50%);
+ z-index: 2;
+ top: -30px;
+ transition: top 200ms ease-in-out;
+}
+#search_venues.shown {
+ top: 6px;
+}
+#search_venues_inner {
+ position: relative;
+ overflow: hidden;
+ font-size: 13px;
+ font-weight: 500;
+ background: var(--td-window-bg);
+ color: var(--td-window-active-text-fg);
+ cursor: pointer;
+ border-radius: 14px;
+ padding: 5px 12px 6px;
+ box-shadow: 0 0 3px 0px var(--td-history-to-down-shadow);
+}
+#search_venues_inner:hover {
+ background: var(--td-window-bg-over);
+}
+#search_venues_content {
+ position: relative;
+ z-index: 2;
+}
+#search_venues_content:before {
+ content: var(--td-lng-maps-places-in-area);
+}
+#search_venues_inner .ripple .inner {
+ position: absolute;
+ border-radius: 50%;
+ transform: scale(0);
+ opacity: 1;
+ animation: ripple 650ms cubic-bezier(0.22, 1, 0.36, 1) forwards;
+ background-color: var(--td-window-bg-ripple);
+}
+#search_venues_inner .ripple.hiding {
+ animation: fadeOut 200ms linear forwards;
+}
+@keyframes ripple {
+ to {
+ transform: scale(2);
+ }
+}
+@keyframes fadeOut {
+ to {
+ opacity: 0;
+ }
+}
diff --git a/Telegram/Resources/picker_html/picker.js b/Telegram/Resources/picker_html/picker.js
new file mode 100644
index 000000000..e44fd51a9
--- /dev/null
+++ b/Telegram/Resources/picker_html/picker.js
@@ -0,0 +1,199 @@
+var LocationPicker = {
+ startZoom: 14,
+ flySpeed: 2.4,
+ notify: function(message) {
+ if (window.external && window.external.invoke) {
+ window.external.invoke(JSON.stringify(message));
+ }
+ },
+ frameKeyDown: function (e) {
+ const keyW = (e.key === 'w')
+ || (e.code === 'KeyW')
+ || (e.keyCode === 87);
+ const keyQ = (e.key === 'q')
+ || (e.code === 'KeyQ')
+ || (e.keyCode === 81);
+ const keyM = (e.key === 'm')
+ || (e.code === 'KeyM')
+ || (e.keyCode === 77);
+ if ((e.metaKey || e.ctrlKey) && (keyW || keyQ || keyM)) {
+ e.preventDefault();
+ LocationPicker.notify({
+ event: 'keydown',
+ modifier: e.ctrlKey ? 'ctrl' : 'cmd',
+ key: keyW ? 'w' : keyQ ? 'q' : 'm',
+ });
+ } else if (e.key === 'Escape' || e.keyCode === 27) {
+ e.preventDefault();
+ LocationPicker.notify({
+ event: 'keydown',
+ key: 'escape',
+ });
+ }
+ },
+ isNight: function() {
+ var html = document.getElementsByTagName('html')[0];
+ return html.style.getPropertyValue('--td-night') == '1';
+ },
+ lightPreset: function() {
+ return LocationPicker.isNight() ? 'night' : 'day';
+ },
+ updateStyles: function (styles) {
+ if (LocationPicker.styles !== styles) {
+ LocationPicker.styles = styles;
+ document.getElementsByTagName('html')[0].style = styles;
+
+ LocationPicker.map.setConfigProperty(
+ 'basemap',
+ 'lightPreset',
+ LocationPicker.lightPreset());
+ }
+ },
+ init: function (params) {
+ mapboxgl.accessToken = params.token;
+ if (params.protocol) {
+ mapboxgl.config.API_URL = params.protocol + '://domain/api.mapbox.com';
+ }
+
+ var options = { container: 'map', config: {
+ basemap: { lightPreset: LocationPicker.lightPreset() }
+ } };
+ var center = params.center;
+ if (center) {
+ center = [center[1], center[0]];
+ options.center = center;
+ options.zoom = LocationPicker.startZoom;
+ } else if (params.bounds) {
+ options.bounds = params.bounds;
+ center = new mapboxgl.LngLatBounds(params.bounds).getCenter();
+ } else {
+ center = [0, 0];
+ }
+ LocationPicker.map = new mapboxgl.Map(options);
+ LocationPicker.createMarker(center);
+ LocationPicker.trackMovement();
+ LocationPicker.initSearchVenueRipple();
+ },
+ marker: function() {
+ return document.getElementById('marker_drop');
+ },
+ createMarker: function(center) {
+ document.getElementById('marker').style.display = 'flex';
+ },
+ clearMovingTimer: function() {
+ if (LocationPicker.clearMovingTimeoutId) {
+ clearTimeout(LocationPicker.clearMovingTimeoutId);
+ LocationPicker.clearMovingTimeoutId = 0;
+ }
+ },
+ startMovingTimer: function(done) {
+ LocationPicker.clearMovingTimer();
+ LocationPicker.clearMovingTimeoutId = setTimeout(done, 500);
+ },
+ trackMovement: function() {
+ LocationPicker.map.on('movestart', function() {
+ LocationPicker.marker().classList.add('moving');
+ LocationPicker.clearMovingTimer();
+ LocationPicker.toggleSearchVenues(false);
+ LocationPicker.notify({ event: 'move_start' });
+ });
+ LocationPicker.map.on('moveend', function() {
+ LocationPicker.startMovingTimer(function() {
+ LocationPicker.marker().classList.remove('moving');
+ LocationPicker.notify({
+ event: 'move_end',
+ latitude: LocationPicker.map.getCenter().lat,
+ longitude: LocationPicker.map.getCenter().lng
+ });
+ });
+ });
+ },
+ narrowTo: function (point) {
+ LocationPicker.map.flyTo({
+ center: [point[1], point[0]],
+ zoom: LocationPicker.startZoom,
+ speed: LocationPicker.flySpeed,
+ });
+ },
+ send: function () {
+ LocationPicker.notify({
+ event: 'send',
+ latitude: LocationPicker.map.getCenter().lat,
+ longitude: LocationPicker.map.getCenter().lng
+ });
+ },
+ addRipple: function (button, x, y) {
+ const ripple = document.createElement('span');
+ ripple.classList.add('ripple');
+
+ const inner = document.createElement('span');
+ inner.classList.add('inner');
+
+ var rect = button.getBoundingClientRect();
+ x -= rect.x;
+ y -= rect.y;
+
+ const mx = button.clientWidth - x;
+ const my = button.clientHeight - y;
+ const sq1 = x * x + y * y;
+ const sq2 = mx * mx + y * y;
+ const sq3 = x * x + my * my;
+ const sq4 = mx * mx + my * my;
+ const radius = Math.sqrt(Math.max(sq1, sq2, sq3, sq4));
+
+ inner.style.width = inner.style.height = `${2 * radius}px`;
+ inner.style.left = `${x - radius}px`;
+ inner.style.top = `${y - radius}px`;
+ inner.classList.add('inner');
+
+ ripple.addEventListener('animationend', function (e) {
+ if (e.animationName === 'fadeOut') {
+ ripple.remove();
+ }
+ });
+
+ ripple.appendChild(inner);
+ button.appendChild(ripple);
+ },
+ stopRipples: function (button) {
+ const id = button.id ? button.id : button;
+ button = document.getElementById(id);
+ const ripples = button.getElementsByClassName('ripple');
+ for (var i = 0; i < ripples.length; ++i) {
+ const ripple = ripples[i];
+ if (!ripple.classList.contains('hiding')) {
+ ripple.classList.add('hiding');
+ }
+ }
+ },
+ initSearchVenueRipple: function() {
+ var button = document.getElementById('search_venues_inner');
+ button.addEventListener('mousedown', function (e) {
+ LocationPicker.addRipple(e.currentTarget, e.clientX, e.clientY);
+ LocationPicker.searchVenuesPressed = true;
+ });
+ button.addEventListener('mouseup', function (e) {
+ const id = e.currentTarget.id;
+ setTimeout(function () {
+ LocationPicker.stopRipples(id);
+ }, 0);
+ if (LocationPicker.searchVenuesPressed) {
+ LocationPicker.searchVenuesPressed = false;
+ LocationPicker.toggleSearchVenues(false);
+ LocationPicker.notify({
+ event: 'search_venues',
+ latitude: LocationPicker.map.getCenter().lat,
+ longitude: LocationPicker.map.getCenter().lng
+ });
+ }
+ });
+ button.addEventListener('mouseleave', function (e) {
+ LocationPicker.stopRipples(e.currentTarget);
+ LocationPicker.searchVenuesPressed = false;
+ });
+ },
+ toggleSearchVenues: function(shown) {
+ var button = document.getElementById('search_venues');
+ button.classList.toggle('shown', shown);
+ },
+};
diff --git a/Telegram/Resources/qrc/telegram/picker.qrc b/Telegram/Resources/qrc/telegram/picker.qrc
new file mode 100644
index 000000000..10a810aa9
--- /dev/null
+++ b/Telegram/Resources/qrc/telegram/picker.qrc
@@ -0,0 +1,6 @@
+
+
+ ../../picker_html/picker.css
+ ../../picker_html/picker.js
+
+
diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml
index 53203464b..89f5f8761 100644
--- a/Telegram/Resources/uwp/AppX/AppxManifest.xml
+++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml
@@ -10,7 +10,7 @@
+ Version="5.3.2.0" />
Telegram Desktop
Telegram Messenger LLP
@@ -37,6 +37,9 @@
+
+
+
fullId());
- BotGameUrlClickHandler(bot, scoreLink).onClick({
+ BotGameUrlClickHandler(bot, link).onClick({
Qt::LeftButton,
QVariant::fromValue(ClickHandlerContext{
.itemId = item->fullId(),
@@ -492,20 +488,23 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
case ButtonType::WebView: {
if (const auto bot = item->getMessageBot()) {
- bot->session().attachWebView().request(
- controller,
- Api::SendAction(bot->owner().history(bot)),
- bot,
- { .text = button->text, .url = button->data });
+ bot->session().attachWebView().open({
+ .bot = bot,
+ .context = { .controller = controller },
+ .button = { .text = button->text, .url = button->data },
+ .source = InlineBots::WebViewSourceButton{ .simple = false },
+ });
}
} break;
case ButtonType::SimpleWebView: {
if (const auto bot = item->getMessageBot()) {
- bot->session().attachWebView().requestSimple(
- controller,
- bot,
- { .text = button->text, .url = button->data });
+ bot->session().attachWebView().open({
+ .bot = bot,
+ .context = { .controller = controller },
+ .button = {.text = button->text, .url = button->data },
+ .source = InlineBots::WebViewSourceButton{ .simple = true },
+ });
}
} break;
}
diff --git a/Telegram/SourceFiles/api/api_common.h b/Telegram/SourceFiles/api/api_common.h
index cd8aa54e2..81f098d67 100644
--- a/Telegram/SourceFiles/api/api_common.h
+++ b/Telegram/SourceFiles/api/api_common.h
@@ -30,6 +30,10 @@ struct SendOptions {
bool invertCaption = false;
bool hideViaBot = false;
crl::time ttlSeconds = 0;
+
+ friend inline bool operator==(
+ const SendOptions &,
+ const SendOptions &) = default;
};
[[nodiscard]] SendOptions DefaultSendWhenOnlineOptions();
@@ -52,6 +56,10 @@ struct SendAction {
MsgId replaceMediaOf = 0;
[[nodiscard]] MTPInputReplyTo mtpReplyTo() const;
+
+ friend inline bool operator==(
+ const SendAction &,
+ const SendAction &) = default;
};
struct MessageToSend {
diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp
index 356e2cffa..727cee565 100644
--- a/Telegram/SourceFiles/api/api_credits.cpp
+++ b/Telegram/SourceFiles/api/api_credits.cpp
@@ -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 CreditsTopupOptions::request() {
return [=](auto consumer) {
auto lifetime = rpl::lifetime();
- using TLOption = MTPStarsTopupOption;
- _api.request(MTPpayments_GetStarsTopupOptions(
- )).done([=](const MTPVector &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 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 &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 &result) {
+ _options = optionsFromTL(result.v);
+ consumer.put_done();
+ }).fail(fail).send();
+ }
return lifetime;
};
diff --git a/Telegram/SourceFiles/api/api_editing.cpp b/Telegram/SourceFiles/api/api_editing.cpp
index f326275fd..662bfc980 100644
--- a/Telegram/SourceFiles/api/api_editing.cpp
+++ b/Telegram/SourceFiles/api/api_editing.cpp
@@ -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) {
@@ -153,9 +153,7 @@ mtpRequestId EditMessage(
const auto &text = item->originalText();
const auto webpage = (!item->media() || !item->media()->webpage())
? Data::WebPageDraft{ .removed = true }
- : Data::WebPageDraft{
- .id = item->media()->webpage()->id,
- };
+ : Data::WebPageDraft::FromItem(item);
return EditMessage(
item,
text,
diff --git a/Telegram/SourceFiles/api/api_media.cpp b/Telegram/SourceFiles/api/api_media.cpp
index a76cb4b67..46a5b7fa3 100644
--- a/Telegram/SourceFiles/api/api_media.cpp
+++ b/Telegram/SourceFiles/api/api_media.cpp
@@ -36,7 +36,8 @@ MTPVector 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()),
diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp
index ade419248..c05a42e95 100644
--- a/Telegram/SourceFiles/api/api_sending.cpp
+++ b/Telegram/SourceFiles/api/api_sending.cpp
@@ -62,6 +62,79 @@ void InnerFillMessagePostFlags(
}
}
+void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) {
+ const auto history = action.history;
+ const auto peer = history->peer;
+ const auto session = &history->session();
+ const auto api = &session->api();
+
+ action.clearDraft = false;
+ action.generateLocal = false;
+ api->sendAction(action);
+
+ const auto randomId = base::RandomValue();
+
+ auto flags = NewMessageFlags(peer);
+ auto sendFlags = MTPmessages_SendMedia::Flags(0);
+ if (action.replyTo) {
+ flags |= MessageFlag::HasReplyInfo;
+ sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to;
+ }
+ const auto silentPost = ShouldSendSilent(peer, action.options);
+ InnerFillMessagePostFlags(action.options, peer, flags);
+ if (silentPost) {
+ sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
+ }
+ const auto sendAs = action.options.sendAs;
+ if (sendAs) {
+ sendFlags |= MTPmessages_SendMedia::Flag::f_send_as;
+ }
+ const auto messagePostAuthor = peer->isBroadcast()
+ ? session->user()->name()
+ : QString();
+
+ if (action.options.scheduled) {
+ flags |= MessageFlag::IsOrWasScheduled;
+ sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
+ }
+ if (action.options.shortcutId) {
+ flags |= MessageFlag::ShortcutMessage;
+ sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
+ }
+ if (action.options.effectId) {
+ sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
+ }
+ if (action.options.invertCaption) {
+ flags |= MessageFlag::InvertMedia;
+ sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
+ }
+
+ auto &histories = history->owner().histories();
+ histories.sendPreparedMessage(
+ history,
+ action.replyTo,
+ randomId,
+ Data::Histories::PrepareMessage(
+ MTP_flags(sendFlags),
+ peer->input,
+ Data::Histories::ReplyToPlaceholder(),
+ std::move(inputMedia),
+ MTPstring(),
+ MTP_long(randomId),
+ MTPReplyMarkup(),
+ MTPvector(),
+ MTP_int(action.options.scheduled),
+ (sendAs ? sendAs->input : MTP_inputPeerEmpty()),
+ Data::ShortcutIdToMTP(session, action.options.shortcutId),
+ MTP_long(action.options.effectId)
+ ), [=](const MTPUpdates &result, const MTP::Response &response) {
+ }, [=](const MTP::Error &error, const MTP::Response &response) {
+ api->sendMessageFail(error, peer, randomId);
+ });
+
+ api->finishForwarding(action);
+}
+
template
void SendExistingMedia(
MessageToSend &&message,
@@ -362,6 +435,33 @@ bool SendDice(MessageToSend &message) {
return true;
}
+void SendLocation(SendAction action, float64 lat, float64 lon) {
+ SendSimpleMedia(
+ action,
+ MTP_inputMediaGeoPoint(
+ MTP_inputGeoPoint(
+ MTP_flags(0),
+ MTP_double(lat),
+ MTP_double(lon),
+ MTPint()))); // accuracy_radius
+}
+
+void SendVenue(SendAction action, Data::InputVenue venue) {
+ SendSimpleMedia(
+ action,
+ MTP_inputMediaVenue(
+ MTP_inputGeoPoint(
+ MTP_flags(0),
+ MTP_double(venue.lat),
+ MTP_double(venue.lon),
+ MTPint()), // accuracy_radius
+ MTP_string(venue.title),
+ MTP_string(venue.address),
+ MTP_string(venue.provider),
+ MTP_string(venue.id),
+ MTP_string(venue.venueType)));
+}
+
void FillMessagePostFlags(
const SendAction &action,
not_null peer,
diff --git a/Telegram/SourceFiles/api/api_sending.h b/Telegram/SourceFiles/api/api_sending.h
index 2fdbad843..c4bafc537 100644
--- a/Telegram/SourceFiles/api/api_sending.h
+++ b/Telegram/SourceFiles/api/api_sending.h
@@ -7,15 +7,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
-namespace Main {
-class Session;
-} // namespace Main
-
class History;
class PhotoData;
class DocumentData;
struct FilePrepareResult;
+namespace Data {
+struct InputVenue;
+} // namespace Data
+
+namespace Main {
+class Session;
+} // namespace Main
+
namespace Api {
struct MessageToSend;
@@ -33,6 +37,13 @@ void SendExistingPhoto(
bool SendDice(MessageToSend &message);
+// We can't create Data::LocationPoint() and use it
+// for a local sending message, because we can't request
+// map thumbnail in messages history without access hash.
+void SendLocation(SendAction action, float64 lat, float64 lon);
+
+void SendVenue(SendAction action, Data::InputVenue venue);
+
void FillMessagePostFlags(
const SendAction &action,
not_null peer,
diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index 9e465962b..4903d9892 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -2629,7 +2629,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);
}
@@ -2651,8 +2651,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(
@@ -2805,18 +2812,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 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
@@ -2839,7 +2845,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();
@@ -2866,13 +2872,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 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;
@@ -4281,7 +4289,7 @@ void ApiWrap::sendMediaWithRandomId(
), [=](const MTPUpdates &result, const MTP::Response &response) {
if (done) done(true);
if (updateRecentStickers) {
- requestRecentStickersForce(true);
+ requestRecentStickers(std::nullopt, true);
}
AyuWorker::markAsOnline(_session);
diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h
index 15c0941c3..7259c410d 100644
--- a/Telegram/SourceFiles/apiwrap.h
+++ b/Telegram/SourceFiles/apiwrap.h
@@ -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 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 now,
+ bool attached);
+ void requestFavedStickers(std::optional now);
void requestFeaturedStickers(TimeId now);
void requestFeaturedEmoji(TimeId now);
void requestSavedGifs(TimeId now);
diff --git a/Telegram/SourceFiles/boxes/about_box.cpp b/Telegram/SourceFiles/boxes/about_box.cpp
index 94faa948c..6f22d75ad 100644
--- a/Telegram/SourceFiles/boxes/about_box.cpp
+++ b/Telegram/SourceFiles/boxes/about_box.cpp
@@ -100,6 +100,8 @@ void AboutBox::showVersionHistory() {
url += u"win/%1.zip"_q;
} else if (Platform::IsWindows64Bit()) {
url += u"win64/%1.zip"_q;
+ } else if (Platform::IsWindowsARM64()) {
+ url += u"winarm/%1.zip"_q;
} else if (Platform::IsMac()) {
url += u"mac/%1.zip"_q;
} else if (Platform::IsLinux()) {
@@ -155,6 +157,8 @@ QString currentVersionText() {
}
if (Platform::IsWindows64Bit()) {
result += " x64";
+ } else if (Platform::IsWindowsARM64()) {
+ result += " arm64";
}
return result;
}
diff --git a/Telegram/SourceFiles/boxes/choose_filter_box.cpp b/Telegram/SourceFiles/boxes/choose_filter_box.cpp
index 5af8577bf..4e99a01ba 100644
--- a/Telegram/SourceFiles/boxes/choose_filter_box.cpp
+++ b/Telegram/SourceFiles/boxes/choose_filter_box.cpp
@@ -39,6 +39,7 @@ Data::ChatFilter ChangedFilter(
filter.id(),
filter.title(),
filter.iconEmoji(),
+ filter.colorIndex(),
filter.flags(),
std::move(always),
filter.pinned(),
@@ -58,6 +59,7 @@ Data::ChatFilter ChangedFilter(
filter.id(),
filter.title(),
filter.iconEmoji(),
+ filter.colorIndex(),
filter.flags(),
std::move(always),
filter.pinned(),
diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp
index ce23d235f..df85e6254 100644
--- a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp
+++ b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp
@@ -83,6 +83,7 @@ not_null SetupChatsPreview(
rules.id(),
rules.title(),
rules.iconEmoji(),
+ rules.colorIndex(),
(rules.flags() & ~flag),
rules.always(),
rules.pinned(),
@@ -104,6 +105,7 @@ not_null SetupChatsPreview(
rules.id(),
rules.title(),
rules.iconEmoji(),
+ rules.colorIndex(),
rules.flags(),
std::move(always),
std::move(pinned),
@@ -170,6 +172,7 @@ void EditExceptions(
rules.id(),
rules.title(),
rules.iconEmoji(),
+ rules.colorIndex(),
((rules.flags() & ~options)
| rawController->chosenOptions()),
include ? std::move(changed) : std::move(removeFrom),
@@ -240,6 +243,7 @@ void CreateIconSelector(
rules.id(),
rules.title(),
Ui::LookupFilterIcon(icon).emoji,
+ rules.colorIndex(),
rules.flags(),
rules.always(),
rules.pinned(),
diff --git a/Telegram/SourceFiles/boxes/gift_credits_box.cpp b/Telegram/SourceFiles/boxes/gift_credits_box.cpp
new file mode 100644
index 000000000..a2e36038a
--- /dev/null
+++ b/Telegram/SourceFiles/boxes/gift_credits_box.cpp
@@ -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 box,
+ not_null peer,
+ Fn gifted) {
+ box->setStyle(st::creditsGiftBox);
+ box->setNoContentMargin(true);
+ box->addButton(tr::lng_create_group_back(), [=] { box->closeBox(); });
+
+ const auto content = box->setPinnedToTopContent(
+ object_ptr(box));
+
+ Ui::AddSkip(content);
+ Ui::AddSkip(content);
+ const auto &stUser = st::premiumGiftsUserpicButton;
+ const auto userpicWrap = content->add(
+ object_ptr>(
+ content,
+ object_ptr(content, peer, stUser)));
+ userpicWrap->setAttribute(Qt::WA_TransparentForMouseEvents);
+ Ui::AddSkip(content);
+ Ui::AddSkip(content);
+
+ {
+ const auto widget = Ui::CreateChild(content);
+ using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
+ const auto stars = widget->lifetime().make_state(
+ 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>(
+ 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(box));
+}
+
+void ShowGiftCreditsBox(
+ not_null controller,
+ Fn gifted) {
+
+ class Controller final : public ContactsBoxController {
+ public:
+ Controller(
+ not_null session,
+ Fn)> choose)
+ : ContactsBoxController(session)
+ , _choose(std::move(choose)) {
+ }
+
+ protected:
+ std::unique_ptr createRow(
+ not_null user) override {
+ if (user->isSelf()
+ || user->isBot()
+ || user->isServiceUser()
+ || user->isInaccessible()) {
+ return nullptr;
+ }
+ return ContactsBoxController::createRow(user);
+ }
+
+ void rowClicked(not_null row) override {
+ _choose(row->peer());
+ }
+
+ private:
+ const Fn)> _choose;
+
+ };
+ auto initBox = [=](not_null 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->session(),
+ [=](not_null peer) {
+ show->showBox(Box(GiftCreditsBox, peer, gifted));
+ });
+ show->showBox(
+ Box(std::move(listController), std::move(initBox)),
+ Ui::LayerOption::KeepOther);
+}
+
+} // namespace Ui
diff --git a/Telegram/SourceFiles/boxes/gift_credits_box.h b/Telegram/SourceFiles/boxes/gift_credits_box.h
new file mode 100644
index 000000000..43b8556f3
--- /dev/null
+++ b/Telegram/SourceFiles/boxes/gift_credits_box.h
@@ -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 controller,
+ Fn gifted);
+
+} // namespace Ui
diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp
index 972a7a5f4..d478a7c6e 100644
--- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp
+++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp
@@ -1014,6 +1014,7 @@ void GiftPremiumValidator::showChoosePeerBox(const QString &ref) {
if (users.empty()) {
show->showToast(
tr::lng_settings_gift_premium_choose(tr::now));
+ return;
}
const auto giftBox = show->show(
Box(GiftsBox, _controller, users, api, ref));
@@ -1648,7 +1649,9 @@ void AddCreditsHistoryEntryTable(
st::giveawayGiftCodeTableMargin);
const auto peerId = PeerId(entry.barePeerId);
if (peerId) {
- auto text = tr::lng_credits_box_history_entry_peer();
+ auto text = entry.in
+ ? tr::lng_credits_box_history_entry_peer_in()
+ : tr::lng_credits_box_history_entry_peer();
AddTableRow(table, std::move(text), controller, peerId);
}
if (const auto msgId = MsgId(peerId ? entry.bareMsgId : 0)) {
@@ -1692,14 +1695,24 @@ 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,
tr::lng_credits_box_history_entry_via(),
tr::lng_credits_box_history_entry_ads(Ui::Text::RichLangValue));
+ } else if (entry.peerType == Type::PremiumBot) {
+ AddTableRow(
+ table,
+ tr::lng_credits_box_history_entry_via(),
+ tr::lng_credits_box_history_entry_via_premium_bot(
+ Ui::Text::RichLangValue));
}
if (!entry.id.isEmpty()) {
constexpr auto kOneLineCount = 18;
diff --git a/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp b/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp
index 70648d627..a3bc8b69b 100644
--- a/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp
@@ -459,6 +459,7 @@ Ui::BoostFeatures LookupBoostFeatures(not_null channel) {
.customWallpaperLevel = group
? levelLimits.groupCustomWallpaperLevelMin()
: levelLimits.channelCustomWallpaperLevelMin(),
+ .sponsoredLevel = levelLimits.channelRestrictSponsoredLevelMin(),
};
}
diff --git a/Telegram/SourceFiles/boxes/premium_limits_box.cpp b/Telegram/SourceFiles/boxes/premium_limits_box.cpp
index 451957c21..8febb5f42 100644
--- a/Telegram/SourceFiles/boxes/premium_limits_box.cpp
+++ b/Telegram/SourceFiles/boxes/premium_limits_box.cpp
@@ -418,7 +418,9 @@ void SimpleLimitBox(
BoxShowFinishes(box),
0,
descriptor.current,
- descriptor.premiumLimit,
+ (descriptor.complexRatio
+ ? descriptor.premiumLimit
+ : 2 * descriptor.current),
premiumPossible,
descriptor.phrase,
descriptor.icon);
@@ -769,7 +771,7 @@ void FilterLinksLimitBox(
premiumLimit,
&st::premiumIconChats,
std::nullopt,
- true });
+ /*true */}); // Don't use real ratio, "Free" doesn't fit.
}
@@ -856,7 +858,7 @@ void ShareableFiltersLimitBox(
premiumLimit,
&st::premiumIconFolders,
std::nullopt,
- true });
+ /*true*/ }); // Don't use real ratio, "Free" doesn't fit.
}
void FilterPinsLimitBox(
diff --git a/Telegram/SourceFiles/boxes/send_credits_box.cpp b/Telegram/SourceFiles/boxes/send_credits_box.cpp
index 39d7983d7..a8771cc17 100644
--- a/Telegram/SourceFiles/boxes/send_credits_box.cpp
+++ b/Telegram/SourceFiles/boxes/send_credits_box.cpp
@@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "payments/payments_checkout_process.h"
#include "payments/payments_form.h"
#include "settings/settings_credits_graphics.h"
+#include "ui/boxes/confirm_box.h"
#include "ui/controls/userpic_button.h"
#include "ui/effects/premium_graphics.h"
#include "ui/effects/premium_top_bar.h" // Ui::Premium::ColorizedSvg.
@@ -257,6 +258,8 @@ void SendCreditsBox(
if (state->confirmButtonBusy.current()) {
return;
}
+ const auto show = box->uiShow();
+ const auto weak = MakeWeak(box.get());
state->confirmButtonBusy = true;
session->api().request(
MTPpayments_SendStarsForm(
@@ -264,12 +267,31 @@ void SendCreditsBox(
MTP_long(form->formId),
form->inputInvoice)
).done([=](auto result) {
- state->confirmButtonBusy = false;
- box->closeBox();
+ if (weak) {
+ state->confirmButtonBusy = false;
+ box->closeBox();
+ }
sent();
}).fail([=](const MTP::Error &error) {
- state->confirmButtonBusy = false;
- box->uiShow()->showToast(error.type());
+ if (weak) {
+ state->confirmButtonBusy = false;
+ }
+ const auto id = error.type();
+ if (id == u"BOT_PRECHECKOUT_FAILED"_q) {
+ auto error = ::Ui::MakeInformBox(
+ tr::lng_payments_precheckout_stars_failed(tr::now));
+ error->boxClosing() | rpl::start_with_next([=] {
+ if (const auto paybox = weak.data()) {
+ paybox->closeBox();
+ }
+ }, error->lifetime());
+ show->showBox(std::move(error));
+ } else if (id == u"BOT_PRECHECKOUT_TIMEOUT"_q) {
+ show->showToast(
+ tr::lng_payments_precheckout_stars_timeout(tr::now));
+ } else {
+ show->showToast(id);
+ }
}).send();
});
{
diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp
index d86e24510..520ac1ac6 100644
--- a/Telegram/SourceFiles/boxes/share_box.cpp
+++ b/Telegram/SourceFiles/boxes/share_box.cpp
@@ -1414,55 +1414,6 @@ std::vector> ShareBox::Inner::selected() const {
return result;
}
-QString AppendShareGameScoreUrl(
- not_null session,
- const QString &url,
- const FullMsgId &fullId) {
- auto shareHashData = QByteArray(0x20, Qt::Uninitialized);
- auto shareHashDataInts = reinterpret_cast(shareHashData.data());
- const auto peer = fullId.peer
- ? session->data().peerLoaded(fullId.peer)
- : static_cast(nullptr);
- const auto channelAccessHash = uint64((peer && peer->isChannel())
- ? peer->asChannel()->access
- : 0);
- shareHashDataInts[0] = session->userId().bare;
- shareHashDataInts[1] = fullId.peer.value;
- shareHashDataInts[2] = uint64(fullId.msg.bare);
- shareHashDataInts[3] = channelAccessHash;
-
- // Count SHA1() of data.
- auto key128Size = 0x10;
- auto shareHashEncrypted = QByteArray(key128Size + shareHashData.size(), Qt::Uninitialized);
- hashSha1(shareHashData.constData(), shareHashData.size(), shareHashEncrypted.data());
-
- //// Mix in channel access hash to the first 64 bits of SHA1 of data.
- //*reinterpret_cast(shareHashEncrypted.data()) ^= channelAccessHash;
-
- // Encrypt data.
- if (!session->local().encrypt(shareHashData.constData(), shareHashEncrypted.data() + key128Size, shareHashData.size(), shareHashEncrypted.constData())) {
- return url;
- }
-
- auto shareHash = shareHashEncrypted.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
- auto shareUrl = u"tg://share_game_score?hash="_q + QString::fromLatin1(shareHash);
-
- auto shareComponent = u"tgShareScoreUrl="_q + qthelp::url_encode(shareUrl);
-
- auto hashPosition = url.indexOf('#');
- if (hashPosition < 0) {
- return url + '#' + shareComponent;
- }
- auto hash = url.mid(hashPosition + 1);
- if (hash.indexOf('=') >= 0 || hash.indexOf('?') >= 0) {
- return url + '&' + shareComponent;
- }
- if (!hash.isEmpty()) {
- return url + '?' + shareComponent;
- }
- return url + shareComponent;
-}
-
ChatHelpers::ForwardedMessagePhraseArgs CreateForwardedMessagePhraseArgs(
const std::vector> &result,
const MessageIdsList &msgIds) {
@@ -1624,9 +1575,8 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
}
void FastShareMessage(
- not_null controller,
+ std::shared_ptr show,
not_null item) {
- const auto show = controller->uiShow();
const auto history = item->history();
const auto owner = &history->owner();
const auto session = &history->session();
@@ -1655,7 +1605,7 @@ void FastShareMessage(
}
if (item->hasDirectLink()) {
using namespace HistoryView;
- CopyPostLink(controller, item->fullId(), Context::History);
+ CopyPostLink(show, item->fullId(), Context::History);
} else if (const auto bot = item->getMessageBot()) {
if (const auto media = item->media()) {
if (const auto game = media->game()) {
@@ -1687,23 +1637,27 @@ void FastShareMessage(
auto copyLinkCallback = canCopyLink
? Fn(std::move(copyCallback))
: Fn();
- controller->show(
- Box(ShareBox::Descriptor{
- .session = session,
- .copyCallback = std::move(copyLinkCallback),
- .submitCallback = ShareBox::DefaultForwardCallback(
- show,
- history,
- msgIds),
- .filterCallback = std::move(filterCallback),
- .forwardOptions = {
- .sendersCount = ItemsForwardSendersCount(items),
- .captionsCount = ItemsForwardCaptionsCount(items),
- .show = !hasOnlyForcedForwardedInfo,
- },
- .premiumRequiredError = SharePremiumRequiredError(),
- }),
- Ui::LayerOption::CloseOther);
+ show->show(Box(ShareBox::Descriptor{
+ .session = session,
+ .copyCallback = std::move(copyLinkCallback),
+ .submitCallback = ShareBox::DefaultForwardCallback(
+ show,
+ history,
+ msgIds),
+ .filterCallback = std::move(filterCallback),
+ .forwardOptions = {
+ .sendersCount = ItemsForwardSendersCount(items),
+ .captionsCount = ItemsForwardCaptionsCount(items),
+ .show = !hasOnlyForcedForwardedInfo,
+ },
+ .premiumRequiredError = SharePremiumRequiredError(),
+ }), Ui::LayerOption::CloseOther);
+}
+
+void FastShareMessage(
+ not_null controller,
+ not_null item) {
+ FastShareMessage(controller->uiShow(), item);
}
void FastShareLink(
@@ -1805,111 +1759,3 @@ auto SharePremiumRequiredError()
-> Fn)> {
return WritePremiumRequiredError;
}
-
-void ShareGameScoreByHash(
- not_null controller,
- const QString &hash) {
- auto &session = controller->session();
- auto key128Size = 0x10;
-
- auto hashEncrypted = QByteArray::fromBase64(hash.toLatin1(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
- if (hashEncrypted.size() <= key128Size || (hashEncrypted.size() != key128Size + 0x20)) {
- controller->show(
- Ui::MakeInformBox(tr::lng_confirm_phone_link_invalid()),
- Ui::LayerOption::CloseOther);
- return;
- }
-
- // Decrypt data.
- auto hashData = QByteArray(hashEncrypted.size() - key128Size, Qt::Uninitialized);
- if (!session.local().decrypt(hashEncrypted.constData() + key128Size, hashData.data(), hashEncrypted.size() - key128Size, hashEncrypted.constData())) {
- return;
- }
-
- // Count SHA1() of data.
- char dataSha1[20] = { 0 };
- hashSha1(hashData.constData(), hashData.size(), dataSha1);
-
- //// Mix out channel access hash from the first 64 bits of SHA1 of data.
- //auto channelAccessHash = *reinterpret_cast(hashEncrypted.data()) ^ *reinterpret_cast(dataSha1);
-
- //// Check next 64 bits of SHA1() of data.
- //auto skipSha1Part = sizeof(channelAccessHash);
- //if (memcmp(dataSha1 + skipSha1Part, hashEncrypted.constData() + skipSha1Part, key128Size - skipSha1Part) != 0) {
- // Ui::show(Box(tr::lng_share_wrong_user(tr::now)));
- // return;
- //}
-
- // Check 128 bits of SHA1() of data.
- if (memcmp(dataSha1, hashEncrypted.constData(), key128Size) != 0) {
- controller->show(
- Ui::MakeInformBox(tr::lng_share_wrong_user()),
- Ui::LayerOption::CloseOther);
- return;
- }
-
- auto hashDataInts = reinterpret_cast(hashData.data());
- if (hashDataInts[0] != session.userId().bare) {
- controller->show(
- Ui::MakeInformBox(tr::lng_share_wrong_user()),
- Ui::LayerOption::CloseOther);
- return;
- }
-
- const auto peerId = PeerId(hashDataInts[1]);
- const auto channelAccessHash = hashDataInts[3];
- if (!peerIsChannel(peerId) && channelAccessHash) {
- // If there is no channel id, there should be no channel access_hash.
- controller->show(
- Ui::MakeInformBox(tr::lng_share_wrong_user()),
- Ui::LayerOption::CloseOther);
- return;
- }
-
- const auto msgId = MsgId(int64(hashDataInts[2]));
- if (const auto item = session.data().message(peerId, msgId)) {
- FastShareMessage(controller, item);
- } else {
- const auto weak = base::make_weak(controller);
- const auto resolveMessageAndShareScore = crl::guard(weak, [=](
- PeerData *peer) {
- auto done = crl::guard(weak, [=] {
- const auto item = weak->session().data().message(
- peerId,
- msgId);
- if (item) {
- FastShareMessage(weak.get(), item);
- } else {
- weak->show(
- Ui::MakeInformBox(tr::lng_edit_deleted()),
- Ui::LayerOption::CloseOther);
- }
- });
- auto &api = weak->session().api();
- api.requestMessageData(peer, msgId, std::move(done));
- });
-
- const auto peer = peerIsChannel(peerId)
- ? controller->session().data().peerLoaded(peerId)
- : nullptr;
- if (peer || !peerIsChannel(peerId)) {
- resolveMessageAndShareScore(peer);
- } else {
- const auto owner = &controller->session().data();
- controller->session().api().request(MTPchannels_GetChannels(
- MTP_vector(
- 1,
- MTP_inputChannel(
- MTP_long(peerToChannel(peerId).bare),
- MTP_long(channelAccessHash)))
- )).done([=](const MTPmessages_Chats &result) {
- result.match([&](const auto &data) {
- owner->processChats(data.vchats());
- });
- if (const auto peer = owner->peerLoaded(peerId)) {
- resolveMessageAndShareScore(peer);
- }
- }).send();
- }
- }
-}
diff --git a/Telegram/SourceFiles/boxes/share_box.h b/Telegram/SourceFiles/boxes/share_box.h
index 32e824b15..d0bf28ce9 100644
--- a/Telegram/SourceFiles/boxes/share_box.h
+++ b/Telegram/SourceFiles/boxes/share_box.h
@@ -59,13 +59,11 @@ class SlideWrap;
class PopupMenu;
} // namespace Ui
-QString AppendShareGameScoreUrl(
- not_null session,
- const QString &url,
- const FullMsgId &fullId);
-void ShareGameScoreByHash(
- not_null controller,
- const QString &hash);
+class ShareBox;
+
+void FastShareMessage(
+ std::shared_ptr show,
+ not_null item);
void FastShareMessage(
not_null controller,
not_null item);
diff --git a/Telegram/SourceFiles/boxes/sticker_set_box.cpp b/Telegram/SourceFiles/boxes/sticker_set_box.cpp
index 0a98761ff..8f8647cc7 100644
--- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp
+++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp
@@ -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"
@@ -75,10 +80,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 ComputeImageColor(
const style::icon &lockIcon,
@@ -266,6 +273,20 @@ public:
[[nodiscard]] rpl::producer 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 errors() const;
void archiveStickers();
@@ -278,6 +299,12 @@ public:
: Data::StickersType::Stickers;
}
+ [[nodiscard]] bool amSetCreator() const {
+ return _amSetCreator;
+ }
+
+ void applySet(const TLStickerSet &set);
+
~Inner();
protected:
@@ -313,6 +340,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(
@@ -329,14 +361,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 document, int index);
+ void fillDeleteStickerBox(not_null box, int index);
+
void chosen(
int index,
not_null sticker,
Api::SendOptions options);
+ [[nodiscard]] QPoint posFromIndex(int index) const;
+ [[nodiscard]] bool isDraggedAnimating() const;
+
not_null getLottiePlayer();
void showPreview();
@@ -373,6 +410,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> _reorderRequests;
+ std::optional _apiReorder;
+
+ struct ShiftAnimation final {
+ Ui::Animations::Simple animation;
+ Ui::Animations::Simple yAnimation;
+ int shift = 0;
+ };
+ base::flat_map _shiftAnimations;
const std::unique_ptr _pathGradient;
mutable StickerPremiumMark _premiumMark;
@@ -545,9 +600,112 @@ void StickerSetBox::updateTitleAndButtons() {
updateButtons();
}
+void ChangeSetNameBox(
+ not_null box,
+ not_null data,
+ const StickerSetIdentifier &input,
+ Fn done) {
+ struct State final {
+ rpl::variable requestId = 0;
+ Ui::RpWidget* saveButton = nullptr;
+ };
+ box->setTitle(tr::lng_stickers_box_edit_name_title());
+ box->addRow(
+ object_ptr(
+ box,
+ tr::lng_stickers_box_edit_name_about(),
+ st::boxLabel));
+ const auto state = box->lifetime().make_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(
+ box,
+ st::editStickerSetNameField.heightMin));
+ auto owned = object_ptr(
+ 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();
@@ -555,6 +713,34 @@ void StickerSetBox::updateButtons() {
? tr::lng_stickers_copied_emoji(tr::now)
: tr::lng_stickers_copied(tr::now));
};
+ const auto fillSetCreatorMenu = [&] {
+ using Filler = Fn)>;
+ if (!_inner->amSetCreator()) {
+ return Filler(nullptr);
+ }
+ const auto data = &_session->data();
+ return Filler([=, show = _show, set = _set](
+ not_null 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);
+ });
+ }();
const auto addPackOwner = [=](const std::shared_ptr> &menu)
{
if (type == Data::StickersType::Stickers || type == Data::StickersType::Emoji) {
@@ -629,6 +815,9 @@ void StickerSetBox::updateButtons() {
*menu = base::make_unique_q(
top,
st::popupMenuWithIcons);
+ if (fillSetCreatorMenu) {
+ fillSetCreatorMenu(*menu);
+ }
(*menu)->addAction(
((type == Data::StickersType::Emoji)
? tr::lng_stickers_share_emoji
@@ -680,6 +869,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)
@@ -732,8 +924,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);
@@ -749,7 +941,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();
@@ -793,7 +985,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());
@@ -804,6 +998,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) {
@@ -836,7 +1031,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."));
});
@@ -977,11 +1172,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());
}
@@ -1003,7 +1287,86 @@ void StickerSetBox::Inner::leaveEventHook(QEvent *e) {
setSelected(-1);
}
+void StickerSetBox::Inner::requestReorder(
+ not_null 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;
@@ -1106,6 +1469,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 box) {
+ fillDeleteStickerBox(box, index);
+ }));
+ },
+ .icon = &st::menuIconDeleteAttention,
+ .isAttention = true,
+ });
+ }
}
if (_menu->empty()) {
_menu = nullptr;
@@ -1114,6 +1491,129 @@ void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) {
}
}
+void StickerSetBox::Inner::fillDeleteStickerBox(
+ not_null 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(container));
+ line->resize(line->width(), _singleSize.height());
+
+ const auto sticker = Ui::CreateChild(line);
+ auto &lifetime = sticker->lifetime();
+ struct State final {
+ rpl::variable requestId = 0;
+ Ui::RpWidget* saveButton = nullptr;
+ };
+ const auto state = lifetime.make_state();
+ sticker->resize(_singleSize);
+ {
+ const auto animation = lifetime.make_state();
+ 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(
+ 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(
+ 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);
@@ -1124,7 +1624,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);
}
}
@@ -1146,6 +1650,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 StickerSetBox::Inner::getLottiePlayer() {
if (!_lottiePlayer) {
_lottiePlayer = std::make_unique(
@@ -1185,12 +1707,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;
}
@@ -1200,6 +1746,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();
@@ -1355,18 +1909,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];
@@ -1452,6 +2087,9 @@ void StickerSetBox::Inner::paintSticker(
_singleSize,
width());
}
+ if (hasShake) {
+ p.resetTransform();
+ }
}
bool StickerSetBox::Inner::loaded() const {
diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp
index 83c4fe88e..c97dee88b 100644
--- a/Telegram/SourceFiles/calls/calls_call.cpp
+++ b/Telegram/SourceFiles/calls/calls_call.cpp
@@ -39,7 +39,6 @@ namespace tgcalls {
class InstanceImpl;
class InstanceV2Impl;
class InstanceV2ReferenceImpl;
-class InstanceV2_4_0_0Impl;
class InstanceImplLegacy;
void SetLegacyGlobalServerConfig(const std::string &serverConfig);
} // namespace tgcalls
@@ -56,7 +55,6 @@ const auto kDefaultVersion = "2.4.4"_q;
const auto Register = tgcalls::Register();
const auto RegisterV2 = tgcalls::Register();
const auto RegV2Ref = tgcalls::Register();
-const auto RegisterV240 = tgcalls::Register();
const auto RegisterLegacy = tgcalls::Register();
[[nodiscard]] base::flat_set CollectEndpointIds(
diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp
index 4b92ee075..6a816af96 100644
--- a/Telegram/SourceFiles/calls/calls_panel.cpp
+++ b/Telegram/SourceFiles/calls/calls_panel.cpp
@@ -215,7 +215,7 @@ void Panel::initWindow() {
}
const auto shown = _layerBg->topShownLayer();
return (!shown || !shown->geometry().contains(widgetPoint))
- ? (Flag::Move | Flag::FullScreen)
+ ? (Flag::Move | Flag::Menu | Flag::FullScreen)
: Flag::None;
});
@@ -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);
}
diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp
index ce806928c..2853340a5 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp
+++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp
@@ -1195,24 +1195,7 @@ base::unique_qptr 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(
parent,
@@ -1223,7 +1206,7 @@ base::unique_qptr 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 Members::Controller::createRowContextMenu(
// PopupMenu::hide activates back the group call panel :(
delete weakMenu;
}
- callback(window);
- window->widget()->activate();
+ window->invokeForSessionController(
+ account,
+ participantPeer,
+ [&](not_null newController) {
+ callback(newController);
+ newController->widget()->activate();
+ });
}
};
const auto showProfile = [=] {
diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp
index 84c336131..927dba8f8 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp
+++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp
@@ -410,7 +410,7 @@ void Panel::initWindow() {
}
const auto shown = _layerBg->topShownLayer();
return (!shown || !shown->geometry().contains(widgetPoint))
- ? (Flag::Move | Flag::Maximize)
+ ? (Flag::Move | Flag::Menu | Flag::Maximize)
: Flag::None;
});
diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style
index 84fd1fdc8..3007f6a46 100644
--- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style
+++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style
@@ -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,22 @@ editTagLimit: FlatLabel(defaultFlatLabel) {
textFg: windowSubTextFg;
}
+editStickerSetNameField: InputField(defaultInputField) {
+ textMargins: margins(0px, 8px, 26px, 4px);
+ heightMin: 36px;
+ heightMax: 36px;
+ placeholderFg: placeholderFg;
+ placeholderFgActive: placeholderFgActive;
+ placeholderFgError: placeholderFgActive;
+ placeholderMargins: margins(2px, 0px, 2px, 0px);
+ placeholderScale: 0.;
+ placeholderFont: normalFont;
+}
+editStickerSetNameLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) {
+ color: lightButtonFg;
+ thickness: 2px;
+}
+
paidStarIcon: icon {{ "settings/premium/star", creditsBg1 }};
paidStarIconTop: 7px;
paidAmountAbout: FlatLabel(defaultFlatLabel) {
@@ -1423,3 +1443,61 @@ paidTagLabel: FlatLabel(defaultFlatLabel) {
style: semiboldTextStyle;
}
paidTagPadding: margins(16px, 6px, 16px, 6px);
+
+pickLocationWindow: size(364px, 680px);
+pickLocationMapHeight: 220px;
+pickLocationCollapsedHeight: 92px;
+pickLocationRowHeight: 52px;
+pickLocationButton: FlatButton {
+ height: pickLocationRowHeight;
+ bgColor: contactsBg;
+ overBgColor: contactsBgOver;
+ ripple: defaultRippleAnimation;
+}
+pickLocationButtonText: FlatLabel(defaultFlatLabel) {
+ minWidth: 128px;
+ maxHeight: 20px;
+ style: semiboldTextStyle;
+ textFg: windowBoldFg;
+}
+pickLocationButtonStatus: FlatLabel(defaultFlatLabel) {
+ minWidth: 128px;
+ maxHeight: 20px;
+ textFg: windowSubTextFg;
+}
+pickLocationButtonSkip: 6px;
+pickLocationSendIcon: icon{{ "chat/filled_location", windowFgActive }};
+pickLocationVenueItem: PeerListItem(defaultPeerListItem) {
+ height: pickLocationRowHeight;
+ photoSize: 42px;
+ photoPosition: point(18px, 5px);
+ namePosition: point(70px, 7px);
+ statusPosition: point(70px, 27px);
+ button: OutlineButton(defaultPeerListButton) {
+ textBg: contactsBg;
+ textBgOver: contactsBgOver;
+ font: normalFont;
+ padding: margins(11px, 5px, 11px, 5px);
+ ripple: defaultRippleAnimation;
+ }
+ statusFg: contactsStatusFg;
+ statusFgOver: contactsStatusFgOver;
+ statusFgActive: contactsStatusFgOnline;
+}
+pickLocationVenueList: PeerList(defaultPeerList) {
+ item: pickLocationVenueItem;
+ padding: margins(0px, 0px, 0px, 0px);
+}
+pickLocationIconSkip: 6px;
+pickLocationLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) {
+ size: size(56px, 56px);
+ color: windowSubTextFg;
+ thickness: 4px;
+}
+pickLocationPromoHeight: 32px;
+pickLocationChooseOnMap: RoundButton(defaultActiveButton) {
+ height: 44px;
+ textTop: 11px;
+ width: -96px;
+ font: font(15px semibold);
+}
diff --git a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp
index 4bab1bc3f..ca1857614 100644
--- a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp
+++ b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp
@@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_document_media.h"
#include "data/stickers/data_stickers.h"
#include "menu/menu_send.h" // SendMenu::FillSendMenu
+#include "mtproto/mtproto_config.h"
#include "core/click_handler_types.h"
#include "ui/controls/tabbed_search.h"
#include "ui/widgets/buttons.h"
@@ -55,7 +56,6 @@ namespace ChatHelpers {
namespace {
constexpr auto kSearchRequestDelay = 400;
-constexpr auto kSearchBotUsername = "gif"_cs;
constexpr auto kMinRepaintDelay = crl::time(33);
constexpr auto kMinAfterScrollDelay = crl::time(33);
@@ -893,13 +893,11 @@ void GifsListWidget::searchForGifs(const QString &query) {
}
if (!_searchBot && !_searchBotRequestId) {
- auto username = kSearchBotUsername.utf16();
+ const auto username = session().serverConfig().gifSearchUsername;
_searchBotRequestId = _api.request(MTPcontacts_ResolveUsername(
MTP_string(username)
)).done([=](const MTPcontacts_ResolvedPeer &result) {
- Expects(result.type() == mtpc_contacts_resolvedPeer);
-
- auto &data = result.c_contacts_resolvedPeer();
+ auto &data = result.data();
session().data().processUsers(data.vusers());
session().data().processChats(data.vchats());
const auto peer = session().data().peerLoaded(
diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp
index 0326743b0..c17af896d 100644
--- a/Telegram/SourceFiles/chat_helpers/message_field.cpp
+++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp
@@ -392,6 +392,9 @@ void InitMessageFieldHandlers(
Fn customEmojiPaused,
Fn)> allowPremiumEmoji,
const style::InputField *fieldStyle) {
+ const auto paused = [customEmojiPaused] {
+ return customEmojiPaused && customEmojiPaused();
+ };
field->setTagMimeProcessor(
FieldTagMimeProcessor(session, allowPremiumEmoji));
field->setCustomTextContext([=](Fn 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(
@@ -525,7 +528,7 @@ void InitMessageFieldGeometry(not_null field) {
st::historySendSize.height() - 2 * st::historySendPadding);
field->setMaxHeight(st::historyComposeFieldMaxHeight);
- field->document()->setDocumentMargin(4.);
+ field->setDocumentMargin(4.);
field->setAdditionalMargin(style::ConvertScale(4) - 4);
}
diff --git a/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.cpp b/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.cpp
index f54b1f1d3..f510540c2 100644
--- a/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.cpp
+++ b/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.cpp
@@ -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 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>();
for (const auto &sticker : data.vdocuments().v) {
const auto document = _session->data().processDocument(sticker);
diff --git a/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.h b/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.h
index e722c1979..1e6c0e934 100644
--- a/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.h
+++ b/Telegram/SourceFiles/chat_helpers/stickers_gift_box_pack.h
@@ -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 _documents;
SetId _setId = 0;
+ uint64 _accessHash = 0;
mtpRequestId _requestId = 0;
};
diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp
index 1aca8af02..9a87760ca 100644
--- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp
+++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp
@@ -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"
@@ -939,6 +940,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;
@@ -1057,6 +1061,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);
@@ -1064,6 +1074,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;
@@ -1694,12 +1737,32 @@ QPoint StickersListWidget::buttonRippleTopLeft(int section) const {
+ st().removeSet.rippleAreaPosition;
}
-void StickersListWidget::showStickerSetBox(not_null document) {
+void StickersListWidget::showStickerSetBox(
+ not_null document,
+ uint64 setId) {
if (document->sticker() && document->sticker()->set) {
checkHideWithBox(Box(
_show,
document->sticker()->set,
document->sticker()->setType));
+ } else if ((setId == Data::Stickers::FavedSetId)
+ || (setId == Data::Stickers::RecentSetId)) {
+ const auto lifetime = std::make_shared();
+ 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);
}
}
@@ -1756,8 +1819,8 @@ base::unique_qptr 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);
}
@@ -1827,7 +1890,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 {
auto settings = &AyuSettings::getInstance();
auto from = messageSentAnimationInfo(
diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h
index f1a89b210..c436273fc 100644
--- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h
+++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h
@@ -350,7 +350,9 @@ private:
void refreshFooterIcons();
void refreshIcons(ValidateIconAnimations animations);
- void showStickerSetBox(not_null document);
+ void showStickerSetBox(
+ not_null document,
+ uint64 setId);
void cancelSetsSearch();
void showSearchResults();
diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp
index 4540e704b..540271630 100644
--- a/Telegram/SourceFiles/core/application.cpp
+++ b/Telegram/SourceFiles/core/application.cpp
@@ -192,8 +192,11 @@ Application::Application()
_platformIntegration->init();
passcodeLockChanges(
- ) | rpl::start_with_next([=] {
+ ) | rpl::start_with_next([=](bool locked) {
_shouldLockAt = 0;
+ if (locked) {
+ closeAdditionalWindows();
+ }
}, _lifetime);
passcodeLockChanges(
@@ -215,6 +218,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();
@@ -234,9 +247,7 @@ Application::~Application() {
//
// For example Domain::removeRedundantAccounts() is called from
// Domain::finish() and there is a violation on Ensures(started()).
- Payments::CheckoutProcess::ClearAll();
- InlineBots::AttachWebView::ClearAll();
- _iv->closeAll();
+ closeAdditionalWindows();
_domain->finish();
@@ -274,14 +285,9 @@ void Application::run() {
refreshGlobalProxy(); // Depends on app settings being read.
- if (const auto old = Local::oldSettingsVersion()) {
- if (old < AppVersion) {
- autoRegisterUrlScheme();
- Platform::NewVersionLaunched(old);
- }
- } else {
- // Initial launch.
+ if (const auto old = Local::oldSettingsVersion(); old < AppVersion) {
autoRegisterUrlScheme();
+ Platform::NewVersionLaunched(old);
}
if (cAutoStart() && !Platform::AutostartSupported()) {
@@ -692,7 +698,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));
@@ -1093,13 +1100,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);
+ }
+ }
}
}
}
@@ -1350,7 +1362,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();
@@ -1807,11 +1819,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"AyuGram"_q,
@@ -1819,6 +1833,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() {
diff --git a/Telegram/SourceFiles/core/application.h b/Telegram/SourceFiles/core/application.h
index e73878ef2..a849eaf65 100644
--- a/Telegram/SourceFiles/core/application.h
+++ b/Telegram/SourceFiles/core/application.h
@@ -379,6 +379,7 @@ private:
void showOpenGLCrashNotification();
void clearPasscodeLock();
+ void closeAdditionalWindows();
bool openCustomUrl(
const QString &protocol,
diff --git a/Telegram/SourceFiles/core/click_handler_types.cpp b/Telegram/SourceFiles/core/click_handler_types.cpp
index 9b03e0b74..b1ba3d166 100644
--- a/Telegram/SourceFiles/core/click_handler_types.cpp
+++ b/Telegram/SourceFiles/core/click_handler_types.cpp
@@ -20,6 +20,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h"
#include "history/view/history_view_element.h"
#include "history/history_item.h"
+#include "inline_bots/bot_attach_web_view.h"
+#include "data/data_game.h"
#include "data/data_user.h"
#include "data/data_session.h"
#include "window/window_controller.h"
@@ -120,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();
if (!my.show) {
@@ -171,23 +175,42 @@ void BotGameUrlClickHandler::onClick(ClickContext context) const {
if (Core::InternalPassportLink(url)) {
return;
}
-
- const auto open = [=] {
+ const auto openLink = [=] {
UrlClickHandler::Open(url, context.other);
};
- if (url.startsWith(u"tg://"_q, Qt::CaseInsensitive)) {
- open();
- } else if (!_bot
- || _bot->isVerified()
+ const auto my = context.other.value();
+ const auto weakController = my.sessionWindow;
+ const auto controller = weakController.get();
+ const auto item = controller
+ ? controller->session().data().message(my.itemId)
+ : nullptr;
+ const auto media = item ? item->media() : nullptr;
+ const auto game = media ? media->game() : nullptr;
+ if (url.startsWith(u"tg://"_q, Qt::CaseInsensitive) || !_bot || !game) {
+ openLink();
+ }
+ const auto bot = _bot;
+ const auto title = game->title;
+ const auto itemId = my.itemId;
+ const auto openGame = [=] {
+ bot->session().attachWebView().open({
+ .bot = bot,
+ .button = {.url = url.toUtf8() },
+ .source = InlineBots::WebViewSourceGame{
+ .messageId = itemId,
+ .title = title,
+ },
+ });
+ };
+ if (_bot->isVerified()
|| _bot->session().local().isBotTrustedOpenGame(_bot->id)) {
- open();
+ openGame();
} else {
- const auto my = context.other.value();
if (const auto controller = my.sessionWindow.get()) {
const auto callback = [=, bot = _bot](Fn close) {
close();
bot->session().local().markBotTrustedOpenGame(bot->id);
- open();
+ openGame();
};
controller->show(Ui::MakeConfirmBox({
.text = tr::lng_allow_bot_pass(
diff --git a/Telegram/SourceFiles/core/click_handler_types.h b/Telegram/SourceFiles/core/click_handler_types.h
index a63594195..b3aa0bae0 100644
--- a/Telegram/SourceFiles/core/click_handler_types.h
+++ b/Telegram/SourceFiles/core/click_handler_types.h
@@ -21,6 +21,10 @@ namespace Ui {
class Show;
} // namespace Ui
+namespace InlineBots {
+struct WebViewContext;
+} // namespace InlineBots
+
namespace Main {
class Session;
} // namespace Main
@@ -38,10 +42,10 @@ class SessionController;
class PeerData;
struct ClickHandlerContext {
FullMsgId itemId;
- QString attachBotWebviewUrl;
// Is filled from sections.
Fn elementDelegate;
base::weak_ptr sessionWindow;
+ std::shared_ptr botWebviewContext;
std::shared_ptr show;
bool mayShowConfirmation = false;
bool skipBotAutoLogin = false;
diff --git a/Telegram/SourceFiles/core/core_settings.cpp b/Telegram/SourceFiles/core/core_settings.cpp
index a74468cb8..0da3ccbeb 100644
--- a/Telegram/SourceFiles/core/core_settings.cpp
+++ b/Telegram/SourceFiles/core/core_settings.cpp
@@ -224,7 +224,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);
@@ -376,7 +377,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);
@@ -499,6 +502,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()) {
@@ -799,6 +804,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()"));
@@ -1008,6 +1019,10 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
}
_customFontFamily = customFontFamily;
_systemUnlockEnabled = (systemUnlockEnabled == 1);
+ _weatherInCelsius = !weatherInCelsius
+ ? std::optional()
+ : (weatherInCelsius == 1);
+ _tonsiteStorageToken = tonsiteStorageToken;
}
QString Settings::getSoundPath(const QString &key) const {
diff --git a/Telegram/SourceFiles/core/core_settings.h b/Telegram/SourceFiles/core/core_settings.h
index f362bce7d..80b083906 100644
--- a/Telegram/SourceFiles/core/core_settings.h
+++ b/Telegram/SourceFiles/core/core_settings.h
@@ -897,6 +897,20 @@ public:
_systemUnlockEnabled = enabled;
}
+ [[nodiscard]] std::optional 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();
@@ -1028,6 +1042,8 @@ private:
WindowPosition _ivPosition;
QString _customFontFamily;
bool _systemUnlockEnabled = false;
+ std::optional _weatherInCelsius;
+ QByteArray _tonsiteStorageToken;
bool _tabbedReplacedWithInfo = false; // per-window
rpl::event_stream _tabbedReplacedWithInfoValue; // per-window
diff --git a/Telegram/SourceFiles/core/crash_reports.cpp b/Telegram/SourceFiles/core/crash_reports.cpp
index 4c1a4f50e..60762c7d0 100644
--- a/Telegram/SourceFiles/core/crash_reports.cpp
+++ b/Telegram/SourceFiles/core/crash_reports.cpp
@@ -296,13 +296,17 @@ bool DumpCallback(const google_breakpad::MinidumpDescriptor &md, void *context,
QString PlatformString() {
if (Platform::IsWindowsStoreBuild()) {
- return Platform::IsWindows64Bit()
+ return Platform::IsWindowsARM64()
+ ? u"WinStoreARM64"_q
+ : Platform::IsWindows64Bit()
? u"WinStore64Bit"_q
: u"WinStore32Bit"_q;
} else if (Platform::IsWindows32Bit()) {
return u"Windows32Bit"_q;
} else if (Platform::IsWindows64Bit()) {
return u"Windows64Bit"_q;
+ } else if (Platform::IsWindowsARM64()) {
+ return u"WindowsARM64"_q;
} else if (Platform::IsMacStoreBuild()) {
return u"MacAppStore"_q;
} else if (Platform::IsMac()) {
diff --git a/Telegram/SourceFiles/core/current_geo_location.cpp b/Telegram/SourceFiles/core/current_geo_location.cpp
new file mode 100644
index 000000000..2ec4a9cfe
--- /dev/null
+++ b/Telegram/SourceFiles/core/current_geo_location.cpp
@@ -0,0 +1,243 @@
+/*
+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 "core/current_geo_location.h"
+
+#include "base/platform/base_platform_info.h"
+#include "base/invoke_queued.h"
+#include "base/timer.h"
+#include "data/raw/raw_countries_bounds.h"
+#include "platform/platform_current_geo_location.h"
+#include "ui/ui_utility.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace Core {
+namespace {
+
+constexpr auto kDestroyManagerTimeout = 20 * crl::time(1000);
+
+[[nodiscard]] QString ChooseLanguage(const QString &language) {
+ // https://docs.mapbox.com/api/search/geocoding#language-coverage
+ auto result = language.toLower().replace('-', '_');
+ static const auto kGood = std::array{
+ // Global coverage.
+ u"de"_q, u"en"_q, u"es"_q, u"fr"_q, u"it"_q, u"nl"_q, u"pl"_q,
+
+ // Local coverage.
+ u"az"_q, u"bn"_q, u"ca"_q, u"cs"_q, u"da"_q, u"el"_q, u"fa"_q,
+ u"fi"_q, u"ga"_q, u"hu"_q, u"id"_q, u"is"_q, u"ja"_q, u"ka"_q,
+ u"km"_q, u"ko"_q, u"lt"_q, u"lv"_q, u"mn"_q, u"pt"_q, u"ro"_q,
+ u"sk"_q, u"sq"_q, u"sv"_q, u"th"_q, u"tl"_q, u"uk"_q, u"vi"_q,
+ u"zh"_q, u"zh_Hans"_q, u"zh_TW"_q,
+
+ // Limited coverage.
+ u"ar"_q, u"bs"_q, u"gu"_q, u"he"_q, u"hi"_q, u"kk"_q, u"lo"_q,
+ u"my"_q, u"nb"_q, u"ru"_q, u"sr"_q, u"te"_q, u"tk"_q, u"tr"_q,
+ u"zh_Hant"_q,
+ };
+ for (const auto &known : kGood) {
+ if (known.toLower() == result) {
+ return known;
+ }
+ }
+ if (const auto delimeter = result.indexOf('_'); delimeter > 0) {
+ result = result.mid(0, delimeter);
+ for (const auto &known : kGood) {
+ if (known == result) {
+ return known;
+ }
+ }
+ }
+ return u"en"_q;
+}
+
+void ResolveLocationAddressGeneric(
+ const GeoLocation &location,
+ const QString &language,
+ const QString &token,
+ Fn callback) {
+ const auto partialUrl = u"https://api.mapbox.com/search/geocode/v6"
+ "/reverse?longitude=%1&latitude=%2&language=%3&access_token=%4"_q
+ .arg(location.point.y())
+ .arg(location.point.x())
+ .arg(ChooseLanguage(language));
+ static auto Cache = base::flat_map();
+ const auto i = Cache.find(partialUrl);
+ if (i != end(Cache)) {
+ callback(i->second);
+ return;
+ }
+ const auto finishWith = [=](GeoAddress result) {
+ Cache[partialUrl] = result;
+ callback(result);
+ };
+
+ struct State final : QObject {
+ explicit State(QObject *parent)
+ : QObject(parent)
+ , manager(this)
+ , destroyer([=] { if (sent.empty()) delete this; }) {
+ }
+
+ QNetworkAccessManager manager;
+ std::vector> sent;
+ base::Timer destroyer;
+ };
+
+ static auto state = QPointer();
+ if (!state) {
+ state = Ui::CreateChild(qApp);
+ }
+ const auto destroyReplyDelayed = [](QNetworkReply *reply) {
+ InvokeQueued(reply, [=] {
+ for (auto i = begin(state->sent); i != end(state->sent);) {
+ if (!*i || *i == reply) {
+ i = state->sent.erase(i);
+ } else {
+ ++i;
+ }
+ }
+ delete reply;
+ if (state->sent.empty()) {
+ state->destroyer.callOnce(kDestroyManagerTimeout);
+ }
+ });
+ };
+
+ auto request = QNetworkRequest(partialUrl.arg(token));
+ request.setRawHeader("Referer", "http://desktop-app-resource/");
+
+ const auto reply = state->manager.get(request);
+ QObject::connect(reply, &QNetworkReply::finished, [=] {
+ destroyReplyDelayed(reply);
+
+ const auto json = QJsonDocument::fromJson(reply->readAll());
+ if (!json.isObject()) {
+ finishWith({});
+ return;
+ }
+ const auto features = json["features"].toArray();
+ if (features.isEmpty()) {
+ finishWith({});
+ return;
+ }
+ const auto feature = features.at(0).toObject();
+ const auto properties = feature["properties"].toObject();
+ const auto context = properties["context"].toObject();
+ auto names = QStringList();
+ auto add = [&](std::vector keys) {
+ for (const auto &key : keys) {
+ const auto value = context[key];
+ if (value.isObject()) {
+ const auto name = value.toObject()["name"].toString();
+ if (!name.isEmpty()) {
+ names.push_back(name);
+ break;
+ }
+ }
+ }
+ };
+ add({ /*u"address"_q, u"street"_q, */u"neighborhood"_q });
+ add({ u"place"_q, u"region"_q });
+ add({ u"country"_q });
+ finishWith({ .name = names.join(", ") });
+ });
+ QObject::connect(reply, &QNetworkReply::errorOccurred, [=] {
+ destroyReplyDelayed(reply);
+
+ finishWith({});
+ });
+}
+
+} // namespace
+
+GeoLocation ResolveCurrentCountryLocation() {
+ const auto iso2 = Platform::SystemCountry().toUpper();
+ const auto &bounds = Raw::CountryBounds();
+ const auto i = bounds.find(iso2);
+ if (i == end(bounds)) {
+ return {
+ .accuracy = GeoLocationAccuracy::Failed,
+ };
+ }
+ return {
+ .point = {
+ (i->second.minLat + i->second.maxLat) / 2.,
+ (i->second.minLon + i->second.maxLon) / 2.,
+ },
+ .bounds = {
+ i->second.minLat,
+ i->second.minLon,
+ i->second.maxLat - i->second.minLat,
+ i->second.maxLon - i->second.minLon,
+ },
+ .accuracy = GeoLocationAccuracy::Country,
+ };
+}
+
+void ResolveCurrentGeoLocation(Fn callback) {
+ using namespace Platform;
+ return ResolveCurrentExactLocation([done = std::move(callback)](
+ GeoLocation result) {
+ done(result.accuracy != GeoLocationAccuracy::Failed
+ ? result
+ : ResolveCurrentCountryLocation());
+ });
+}
+
+void ResolveLocationAddress(
+ const GeoLocation &location,
+ const QString &language,
+ const QString &token,
+ Fn callback) {
+ auto done = [=, done = std::move(callback)](GeoAddress result) mutable {
+ if (!result && !token.isEmpty()) {
+ ResolveLocationAddressGeneric(
+ location,
+ language,
+ token,
+ std::move(done));
+ } else {
+ done(result);
+ }
+ };
+ Platform::ResolveLocationAddress(location, language, std::move(done));
+}
+
+bool AreTheSame(const GeoLocation &a, const GeoLocation &b) {
+ if (a.accuracy != GeoLocationAccuracy::Exact
+ || b.accuracy != GeoLocationAccuracy::Exact) {
+ return false;
+ }
+ const auto normalize = [](float64 value) {
+ value = std::fmod(value + 180., 360.);
+ return (value + (value < 0. ? 360. : 0.)) - 180.;
+ };
+ constexpr auto kEpsilon = 0.0001;
+ const auto lon1 = normalize(a.point.y());
+ const auto lon2 = normalize(b.point.y());
+ const auto diffLat = std::abs(a.point.x() - b.point.x());
+ if (std::abs(a.point.x()) >= (90. - kEpsilon)
+ || std::abs(b.point.x()) >= (90. - kEpsilon)) {
+ return diffLat <= kEpsilon;
+ }
+ auto diffLon = std::abs(lon1 - lon2);
+ if (diffLon > 180.) {
+ diffLon = 360. - diffLon;
+ }
+
+ return diffLat <= kEpsilon && diffLon <= kEpsilon;
+}
+
+} // namespace Core
diff --git a/Telegram/SourceFiles/core/current_geo_location.h b/Telegram/SourceFiles/core/current_geo_location.h
new file mode 100644
index 000000000..3b495f115
--- /dev/null
+++ b/Telegram/SourceFiles/core/current_geo_location.h
@@ -0,0 +1,60 @@
+/*
+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 Core {
+
+enum class GeoLocationAccuracy : uchar {
+ Exact,
+ Country,
+ Failed,
+};
+
+struct GeoLocation {
+ QPointF point;
+ QRectF bounds;
+ GeoLocationAccuracy accuracy = GeoLocationAccuracy::Failed;
+
+ [[nodiscard]] bool exact() const {
+ return accuracy == GeoLocationAccuracy::Exact;
+ }
+ [[nodiscard]] bool country() const {
+ return accuracy == GeoLocationAccuracy::Country;
+ }
+ [[nodiscard]] bool failed() const {
+ return accuracy == GeoLocationAccuracy::Failed;
+ }
+
+ explicit operator bool() const {
+ return !failed();
+ }
+};
+
+[[nodiscard]] bool AreTheSame(const GeoLocation &a, const GeoLocation &b);
+
+struct GeoAddress {
+ QString name;
+
+ [[nodiscard]] bool empty() const {
+ return name.isEmpty();
+ }
+ explicit operator bool() const {
+ return !empty();
+ }
+};
+
+[[nodiscard]] GeoLocation ResolveCurrentCountryLocation();
+void ResolveCurrentGeoLocation(Fn callback);
+
+void ResolveLocationAddress(
+ const GeoLocation &location,
+ const QString &language,
+ const QString &token,
+ Fn callback);
+
+} // namespace Core
diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp
index 815bb7d6d..7fae94334 100644
--- a/Telegram/SourceFiles/core/local_url_handlers.cpp
+++ b/Telegram/SourceFiles/core/local_url_handlers.cpp
@@ -331,21 +331,6 @@ bool ConfirmPhone(
return true;
}
-bool ShareGameScore(
- Window::SessionController *controller,
- const Match &match,
- const QVariant &context) {
- if (!controller) {
- return false;
- }
- const auto params = url_parse_params(
- match->captured(1),
- qthelp::UrlParamNameTransform::ToLower);
- ShareGameScoreByHash(controller, params.value(u"hash"_q));
- controller->window().activate();
- return true;
-}
-
bool ApplySocksProxy(
Window::SessionController *controller,
const Match &match,
@@ -522,7 +507,9 @@ bool ResolveUsernameOrPhone(
return false;
}
using ResolveType = Window::ResolveType;
- auto resolveType = ResolveType::Default;
+ auto resolveType = params.contains(u"profile"_q)
+ ? ResolveType::Profile
+ : ResolveType::Default;
auto startToken = params.value(u"start"_q);
if (!startToken.isEmpty()) {
resolveType = ResolveType::BotStart;
@@ -592,8 +579,11 @@ bool ResolveUsernameOrPhone(
: (appname.isEmpty() && params.contains(u"startapp"_q))
? params.value(u"startapp"_q)
: std::optional()),
- .attachBotMenuOpen = (appname.isEmpty()
+ .attachBotMainOpen = (appname.isEmpty()
&& params.contains(u"startapp"_q)),
+ .attachBotMainCompact = (appname.isEmpty()
+ && params.contains(u"startapp"_q)
+ && (params.value(u"mode"_q) == u"compact"_q)),
.attachBotChooseTypes = InlineBots::ParseChooseTypes(
params.value(u"choose"_q)),
.voicechatHash = (params.contains(u"livestream"_q)
@@ -604,7 +594,7 @@ bool ResolveUsernameOrPhone(
? std::make_optional(params.value(u"voicechat"_q))
: std::nullopt),
.clickFromMessageId = myContext.itemId,
- .clickFromAttachBotWebviewUrl = myContext.attachBotWebviewUrl,
+ .clickFromBotWebviewContext = myContext.botWebviewContext,
});
return true;
}
@@ -645,7 +635,7 @@ bool ResolvePrivatePost(
}
: Window::RepliesByLinkInfo{ v::null },
.clickFromMessageId = my.itemId,
- .clickFromAttachBotWebviewUrl = my.attachBotWebviewUrl,
+ .clickFromBotWebviewContext = my.botWebviewContext,
});
controller->window().activate();
return true;
@@ -1197,7 +1187,7 @@ bool ResolveChatLink(
controller->showPeerByLink(Window::PeerByLinkInfo{
.chatLinkSlug = match->captured(1),
.clickFromMessageId = myContext.itemId,
- .clickFromAttachBotWebviewUrl = myContext.attachBotWebviewUrl,
+ .clickFromBotWebviewContext = myContext.botWebviewContext,
});
return true;
}
@@ -1234,10 +1224,6 @@ const std::vector &LocalUrlHandlers() {
u"^confirmphone/?\\?(.+)(#|$)"_q,
ConfirmPhone
},
- {
- u"^share_game_score/?\\?(.+)(#|$)"_q,
- ShareGameScore
- },
{
u"^socks/?\\?(.+)(#|$)"_q,
ApplySocksProxy
@@ -1291,7 +1277,7 @@ const std::vector &LocalUrlHandlers() {
ResolveBoost,
},
{
- u"^message/?\\?slug=([a-zA-Z0-9\\.\\_]+)(&|$)"_q,
+ u"^message/?\\?slug=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"_q,
ResolveChatLink
},
{
@@ -1363,6 +1349,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);
diff --git a/Telegram/SourceFiles/core/ui_integration.cpp b/Telegram/SourceFiles/core/ui_integration.cpp
index 1e09dd3df..67b757449 100644
--- a/Telegram/SourceFiles/core/ui_integration.cpp
+++ b/Telegram/SourceFiles/core/ui_integration.cpp
@@ -242,6 +242,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(local, context);
+ return true;
} else if (local.startsWith(u"internal:"_q, Qt::CaseInsensitive)) {
Core::App().openInternalUrl(local, context);
return true;
diff --git a/Telegram/SourceFiles/core/update_checker.cpp b/Telegram/SourceFiles/core/update_checker.cpp
index 8c530334c..0768aad93 100644
--- a/Telegram/SourceFiles/core/update_checker.cpp
+++ b/Telegram/SourceFiles/core/update_checker.cpp
@@ -245,6 +245,7 @@ QString FindUpdateFile() {
"^("
"tupdate|"
"tx64upd|"
+ "tarm64upd|"
"tmacupd|"
"tarmacupd|"
"tlinuxupd|"
diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h
index 5db1682b1..187ecb7a0 100644
--- a/Telegram/SourceFiles/core/version.h
+++ b/Telegram/SourceFiles/core/version.h
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
constexpr auto AppNameOld = "AyuGram for Windows"_cs;
constexpr auto AppName = "AyuGram Desktop"_cs;
constexpr auto AppFile = "AyuGram"_cs;
-constexpr auto AppVersion = 5002002;
-constexpr auto AppVersionStr = "5.2.2";
+constexpr auto AppVersion = 5003002;
+constexpr auto AppVersionStr = "5.3.2";
constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
diff --git a/Telegram/SourceFiles/data/business/data_business_info.cpp b/Telegram/SourceFiles/data/business/data_business_info.cpp
index e158c7a3a..d9bb6ec7a 100644
--- a/Telegram/SourceFiles/data/business/data_business_info.cpp
+++ b/Telegram/SourceFiles/data/business/data_business_info.cpp
@@ -130,6 +130,42 @@ void BusinessInfo::saveChatIntro(ChatIntro data, Fn fail) {
session->user()->setBusinessDetails(std::move(details));
}
+void BusinessInfo::saveLocation(
+ BusinessLocation data,
+ Fn fail) {
+ const auto session = &_owner->session();
+ auto details = session->user()->businessDetails();
+ const auto &was = details.location;
+ if (was == data) {
+ return;
+ } else {
+ const auto session = &_owner->session();
+ using Flag = MTPaccount_UpdateBusinessLocation::Flag;
+ session->api().request(MTPaccount_UpdateBusinessLocation(
+ MTP_flags((data.point ? Flag::f_geo_point : Flag())
+ | (data.address.isEmpty() ? Flag() : Flag::f_address)),
+ (data.point
+ ? MTP_inputGeoPoint(
+ MTP_flags(0),
+ MTP_double(data.point->lat()),
+ MTP_double(data.point->lon()),
+ MTPint()) // accuracy_radius
+ : MTP_inputGeoPointEmpty()),
+ MTP_string(data.address)
+ )).fail([=](const MTP::Error &error) {
+ auto details = session->user()->businessDetails();
+ details.location = was;
+ session->user()->setBusinessDetails(std::move(details));
+ if (fail) {
+ fail(error.type());
+ }
+ }).send();
+ }
+
+ details.location = std::move(data);
+ session->user()->setBusinessDetails(std::move(details));
+}
+
void BusinessInfo::applyAwaySettings(AwaySettings data) {
if (_awaySettings == data) {
return;
diff --git a/Telegram/SourceFiles/data/business/data_business_info.h b/Telegram/SourceFiles/data/business/data_business_info.h
index 7b99e4c8f..01993f223 100644
--- a/Telegram/SourceFiles/data/business/data_business_info.h
+++ b/Telegram/SourceFiles/data/business/data_business_info.h
@@ -22,6 +22,7 @@ public:
void saveWorkingHours(WorkingHours data, Fn fail);
void saveChatIntro(ChatIntro data, Fn fail);
+ void saveLocation(BusinessLocation data, Fn fail);
void saveAwaySettings(AwaySettings data, Fn fail);
void applyAwaySettings(AwaySettings data);
diff --git a/Telegram/SourceFiles/data/components/location_pickers.cpp b/Telegram/SourceFiles/data/components/location_pickers.cpp
new file mode 100644
index 000000000..4402e4ccf
--- /dev/null
+++ b/Telegram/SourceFiles/data/components/location_pickers.cpp
@@ -0,0 +1,44 @@
+/*
+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 "data/components/location_pickers.h"
+
+#include "api/api_common.h"
+#include "ui/controls/location_picker.h"
+
+namespace Data {
+
+struct LocationPickers::Entry {
+ Api::SendAction action;
+ base::weak_ptr picker;
+};
+
+LocationPickers::LocationPickers() = default;
+
+LocationPickers::~LocationPickers() = default;
+
+Ui::LocationPicker *LocationPickers::lookup(const Api::SendAction &action) {
+ for (auto i = begin(_pickers); i != end(_pickers);) {
+ if (const auto strong = i->picker.get()) {
+ if (i->action == action) {
+ return i->picker.get();
+ }
+ ++i;
+ } else {
+ i = _pickers.erase(i);
+ }
+ }
+ return nullptr;
+}
+
+void LocationPickers::emplace(
+ const Api::SendAction &action,
+ not_null picker) {
+ _pickers.push_back({ action, picker });
+}
+
+} // namespace Data
\ No newline at end of file
diff --git a/Telegram/SourceFiles/data/components/location_pickers.h b/Telegram/SourceFiles/data/components/location_pickers.h
new file mode 100644
index 000000000..ad1046095
--- /dev/null
+++ b/Telegram/SourceFiles/data/components/location_pickers.h
@@ -0,0 +1,39 @@
+/*
+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
+
+#include "base/weak_ptr.h"
+
+namespace Api {
+struct SendAction;
+} // namespace Api
+
+namespace Ui {
+class LocationPicker;
+} // namespace Ui
+
+namespace Data {
+
+class LocationPickers final {
+public:
+ LocationPickers();
+ ~LocationPickers();
+
+ Ui::LocationPicker *lookup(const Api::SendAction &action);
+ void emplace(
+ const Api::SendAction &action,
+ not_null picker);
+
+private:
+ struct Entry;
+
+ std::vector _pickers;
+
+};
+
+} // namespace Data
diff --git a/Telegram/SourceFiles/data/components/top_peers.cpp b/Telegram/SourceFiles/data/components/top_peers.cpp
index f476869b6..57974febf 100644
--- a/Telegram/SourceFiles/data/components/top_peers.cpp
+++ b/Telegram/SourceFiles/data/components/top_peers.cpp
@@ -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 session)
-: _session(session) {
+TopPeers::TopPeers(not_null 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 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 &) {
diff --git a/Telegram/SourceFiles/data/components/top_peers.h b/Telegram/SourceFiles/data/components/top_peers.h
index 5f1250b53..7f834ad84 100644
--- a/Telegram/SourceFiles/data/components/top_peers.h
+++ b/Telegram/SourceFiles/data/components/top_peers.h
@@ -13,9 +13,14 @@ class Session;
namespace Data {
+enum class TopPeerType {
+ Chat,
+ BotApp,
+};
+
class TopPeers final {
public:
- explicit TopPeers(not_null session);
+ TopPeers(not_null session, TopPeerType type);
~TopPeers();
[[nodiscard]] std::vector> list() const;
@@ -36,11 +41,13 @@ private:
float64 rating = 0.;
};
+ void loadAfterChats();
void request();
[[nodiscard]] uint64 countHash() const;
void updated();
const not_null _session;
+ const TopPeerType _type = {};
std::vector _list;
rpl::event_stream<> _updates;
diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp
index 2f7694395..6162024fd 100644
--- a/Telegram/SourceFiles/data/data_channel.cpp
+++ b/Telegram/SourceFiles/data/data_channel.cpp
@@ -1089,7 +1089,8 @@ void ApplyChannelUpdate(
| Flag::CanGetStatistics
| Flag::ViewAsMessages
| Flag::CanViewRevenue
- | Flag::PaidMediaAllowed;
+ | Flag::PaidMediaAllowed
+ | Flag::CanViewCreditsRevenue;
channel->setFlags((channel->flags() & ~mask)
| (update.is_can_set_username() ? Flag::CanSetUsername : Flag())
| (update.is_can_view_participants()
@@ -1107,7 +1108,10 @@ void ApplyChannelUpdate(
? Flag::ViewAsMessages
: Flag())
| (update.is_paid_media_allowed() ? Flag::PaidMediaAllowed : Flag())
- | (update.is_can_view_revenue() ? Flag::CanViewRevenue : Flag()));
+ | (update.is_can_view_revenue() ? Flag::CanViewRevenue : Flag())
+ | (update.is_can_view_stars_revenue()
+ ? Flag::CanViewCreditsRevenue
+ : Flag()));
channel->setUserpicPhoto(update.vchat_photo());
if (const auto migratedFrom = update.vmigrated_from_chat_id()) {
channel->addFlags(Flag::Megagroup);
diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h
index d6f880bda..81ad7778d 100644
--- a/Telegram/SourceFiles/data/data_channel.h
+++ b/Telegram/SourceFiles/data/data_channel.h
@@ -67,6 +67,7 @@ enum class ChannelDataFlag : uint64 {
SimilarExpanded = (1ULL << 31),
CanViewRevenue = (1ULL << 32),
PaidMediaAllowed = (1ULL << 33),
+ CanViewCreditsRevenue = (1ULL << 34),
};
inline constexpr bool is_flag_type(ChannelDataFlag) { return true; };
using ChannelDataFlags = base::flags;
diff --git a/Telegram/SourceFiles/data/data_chat_filters.cpp b/Telegram/SourceFiles/data/data_chat_filters.cpp
index 3e8006259..bf463b5ea 100644
--- a/Telegram/SourceFiles/data/data_chat_filters.cpp
+++ b/Telegram/SourceFiles/data/data_chat_filters.cpp
@@ -47,6 +47,7 @@ ChatFilter::ChatFilter(
FilterId id,
const QString &title,
const QString &iconEmoji,
+ std::optional colorIndex,
Flags flags,
base::flat_set> always,
std::vector> pinned,
@@ -54,6 +55,7 @@ ChatFilter::ChatFilter(
: _id(id)
, _title(title)
, _iconEmoji(iconEmoji)
+, _colorIndex(colorIndex)
, _always(std::move(always))
, _pinned(std::move(pinned))
, _never(std::move(never))
@@ -99,6 +101,9 @@ ChatFilter ChatFilter::FromTL(
data.vid().v,
qs(data.vtitle()),
qs(data.vemoticon().value_or_empty()),
+ data.vcolor()
+ ? std::make_optional(data.vcolor()->v)
+ : std::nullopt,
flags,
std::move(list),
std::move(pinned),
@@ -144,6 +149,9 @@ ChatFilter ChatFilter::FromTL(
data.vid().v,
qs(data.vtitle()),
qs(data.vemoticon().value_or_empty()),
+ data.vcolor()
+ ? std::make_optional(data.vcolor()->v)
+ : std::nullopt,
(Flag::Chatlist
| (data.is_has_my_invites() ? Flag::HasMyLinks : Flag())),
std::move(list),
@@ -193,18 +201,20 @@ MTPDialogFilter ChatFilter::tl(FilterId replaceId) const {
}
if (_flags & Flag::Chatlist) {
using TLFlag = MTPDdialogFilterChatlist::Flag;
- const auto flags = TLFlag::f_emoticon;
+ const auto flags = TLFlag::f_emoticon
+ | (_colorIndex ? TLFlag::f_color : TLFlag(0));
return MTP_dialogFilterChatlist(
MTP_flags(flags),
MTP_int(replaceId ? replaceId : _id),
MTP_string(_title),
MTP_string(_iconEmoji),
- MTPint(), // color
+ MTP_int(_colorIndex.value_or(0)),
MTP_vector(pinned),
MTP_vector(include));
}
using TLFlag = MTPDdialogFilter::Flag;
const auto flags = TLFlag::f_emoticon
+ | (_colorIndex ? TLFlag::f_color : TLFlag(0))
| ((_flags & Flag::Contacts) ? TLFlag::f_contacts : TLFlag(0))
| ((_flags & Flag::NonContacts) ? TLFlag::f_non_contacts : TLFlag(0))
| ((_flags & Flag::Groups) ? TLFlag::f_groups : TLFlag(0))
@@ -225,7 +235,7 @@ MTPDialogFilter ChatFilter::tl(FilterId replaceId) const {
MTP_int(replaceId ? replaceId : _id),
MTP_string(_title),
MTP_string(_iconEmoji),
- MTPint(), // color
+ MTP_int(_colorIndex.value_or(0)),
MTP_vector(pinned),
MTP_vector(include),
MTP_vector(never));
@@ -243,6 +253,10 @@ QString ChatFilter::iconEmoji() const {
return _iconEmoji;
}
+std::optional ChatFilter::colorIndex() const {
+ return _colorIndex;
+}
+
ChatFilter::Flags ChatFilter::flags() const {
return _flags;
}
@@ -572,7 +586,7 @@ void ChatFilters::applyInsert(ChatFilter filter, int position) {
_list.insert(
begin(_list) + position,
- ChatFilter(filter.id(), {}, {}, {}, {}, {}, {}));
+ ChatFilter(filter.id(), {}, {}, {}, {}, {}, {}, {}));
applyChange(*(begin(_list) + position), std::move(filter));
}
@@ -599,7 +613,7 @@ void ChatFilters::applyRemove(int position) {
Expects(position >= 0 && position < _list.size());
const auto i = begin(_list) + position;
- applyChange(*i, ChatFilter(i->id(), {}, {}, {}, {}, {}, {}));
+ applyChange(*i, ChatFilter(i->id(), {}, {}, {}, {}, {}, {}, {}));
_list.erase(i);
}
@@ -728,6 +742,7 @@ const ChatFilter &ChatFilters::applyUpdatedPinned(
id,
i->title(),
i->iconEmoji(),
+ i->colorIndex(),
i->flags(),
std::move(always),
std::move(pinned),
diff --git a/Telegram/SourceFiles/data/data_chat_filters.h b/Telegram/SourceFiles/data/data_chat_filters.h
index 7b5a96476..e123ab2c1 100644
--- a/Telegram/SourceFiles/data/data_chat_filters.h
+++ b/Telegram/SourceFiles/data/data_chat_filters.h
@@ -52,6 +52,7 @@ public:
FilterId id,
const QString &title,
const QString &iconEmoji,
+ std::optional colorIndex,
Flags flags,
base::flat_set> always,
std::vector> pinned,
@@ -71,6 +72,7 @@ public:
[[nodiscard]] FilterId id() const;
[[nodiscard]] QString title() const;
[[nodiscard]] QString iconEmoji() const;
+ [[nodiscard]] std::optional colorIndex() const;
[[nodiscard]] Flags flags() const;
[[nodiscard]] bool chatlist() const;
[[nodiscard]] bool hasMyLinks() const;
@@ -84,6 +86,7 @@ private:
FilterId _id = 0;
QString _title;
QString _iconEmoji;
+ std::optional _colorIndex;
base::flat_set> _always;
std::vector> _pinned;
base::flat_set> _never;
@@ -94,6 +97,7 @@ private:
inline bool operator==(const ChatFilter &a, const ChatFilter &b) {
return (a.title() == b.title())
&& (a.iconEmoji() == b.iconEmoji())
+ && (a.colorIndex() == b.colorIndex())
&& (a.flags() == b.flags())
&& (a.always() == b.always())
&& (a.never() == b.never());
diff --git a/Telegram/SourceFiles/data/data_credits.h b/Telegram/SourceFiles/data/data_credits.h
index 75da4db5b..ee8d948a7 100644
--- a/Telegram/SourceFiles/data/data_credits.h
+++ b/Telegram/SourceFiles/data/data_credits.h
@@ -15,6 +15,7 @@ struct CreditTopupOption final {
QString currency;
uint64 amount = 0;
bool extended = false;
+ uint64 giftBarePeerId = 0;
};
using CreditTopupOptions = std::vector;
@@ -57,7 +58,7 @@ struct CreditsHistoryEntry final {
QDateTime successDate;
QString successLink;
bool in = false;
-
+ bool gift = false;
};
struct CreditsStatusSlice final {
diff --git a/Telegram/SourceFiles/data/data_location.cpp b/Telegram/SourceFiles/data/data_location.cpp
index 80727b3ba..457bb6411 100644
--- a/Telegram/SourceFiles/data/data_location.cpp
+++ b/Telegram/SourceFiles/data/data_location.cpp
@@ -26,6 +26,11 @@ LocationPoint::LocationPoint(const MTPDgeoPoint &point)
, _access(point.vaccess_hash().v) {
}
+LocationPoint::LocationPoint(float64 lat, float64 lon, IgnoreAccessHash)
+: _lat(lat)
+, _lon(lon) {
+}
+
QString LocationPoint::latAsString() const {
return AsString(_lat);
}
diff --git a/Telegram/SourceFiles/data/data_location.h b/Telegram/SourceFiles/data/data_location.h
index a5e0090db..114d21c08 100644
--- a/Telegram/SourceFiles/data/data_location.h
+++ b/Telegram/SourceFiles/data/data_location.h
@@ -16,6 +16,11 @@ public:
LocationPoint() = default;
explicit LocationPoint(const MTPDgeoPoint &point);
+ enum IgnoreAccessHash {
+ NoAccessHash,
+ };
+ LocationPoint(float64 lat, float64 lon, IgnoreAccessHash);
+
[[nodiscard]] QString latAsString() const;
[[nodiscard]] QString lonAsString() const;
[[nodiscard]] MTPGeoPoint toMTP() const;
@@ -45,6 +50,24 @@ private:
};
+struct InputVenue {
+ float64 lat = 0.;
+ float64 lon = 0.;
+ QString title;
+ QString address;
+ QString provider;
+ QString id;
+ QString venueType;
+
+ [[nodiscard]] bool justLocation() const {
+ return id.isEmpty();
+ }
+
+ friend inline bool operator==(
+ const InputVenue &,
+ const InputVenue &) = default;
+};
+
[[nodiscard]] GeoPointLocation ComputeLocation(const LocationPoint &point);
} // namespace Data
diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp
index 15dfedecd..70d29f28a 100644
--- a/Telegram/SourceFiles/data/data_media_types.cpp
+++ b/Telegram/SourceFiles/data/data_media_types.cpp
@@ -121,8 +121,8 @@ struct AlbumCounts {
ImageRoundRadius radius,
bool spoiler) {
const auto original = image->original();
- if (original.width() * 10 < original.height()
- || original.height() * 10 < original.width()) {
+ if (original.width() * 20 < original.height()
+ || original.height() * 20 < original.width()) {
return QImage();
}
const auto factor = style::DevicePixelRatio();
@@ -2303,8 +2303,9 @@ ClickHandlerPtr MediaDice::MakeHandler(
MediaGiftBox::MediaGiftBox(
not_null parent,
not_null from,
- int months)
-: MediaGiftBox(parent, from, GiftCode{ .months = months }) {
+ GiftType type,
+ int count)
+: MediaGiftBox(parent, from, GiftCode{ .count = count, .type = type }) {
}
MediaGiftBox::MediaGiftBox(
@@ -2631,7 +2632,11 @@ const GiveawayResults *MediaGiveawayResults::giveawayResults() const {
}
TextWithEntities MediaGiveawayResults::notificationText() const {
- return Ui::Text::Colorized({ tr::lng_prizes_results_title(tr::now) });
+ return Ui::Text::Colorized({
+ ((_data.winnersCount == 1)
+ ? tr::lng_prizes_results_title_one
+ : tr::lng_prizes_results_title)(tr::now)
+ });
}
QString MediaGiveawayResults::pinnedTextSubstring() const {
diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h
index 424e2c448..b8c943edc 100644
--- a/Telegram/SourceFiles/data/data_media_types.h
+++ b/Telegram/SourceFiles/data/data_media_types.h
@@ -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 parent,
not_null from,
- int months);
+ GiftType type,
+ int count);
MediaGiftBox(
not_null parent,
not_null from,
diff --git a/Telegram/SourceFiles/data/data_peer_values.cpp b/Telegram/SourceFiles/data/data_peer_values.cpp
index f7cc4d453..1bb374ce7 100644
--- a/Telegram/SourceFiles/data/data_peer_values.cpp
+++ b/Telegram/SourceFiles/data/data_peer_values.cpp
@@ -57,6 +57,12 @@ std::optional OnlineTextSpecial(not_null user) {
} else if (user->isSupport()) {
return tr::lng_status_support(tr::now);
} else if (user->isBot()) {
+ if (const auto count = user->botInfo->activeUsers) {
+ return tr::lng_bot_status_users(
+ tr::now,
+ lt_count_decimal,
+ count);
+ }
return tr::lng_status_bot(tr::now);
} else if (user->isServiceUser()) {
return tr::lng_status_support(tr::now);
@@ -69,12 +75,14 @@ std::optional OnlineTextCommon(LastseenStatus status, TimeId now) {
return tr::lng_status_online(tr::now);
} else if (status.isLongAgo()) {
return tr::lng_status_offline(tr::now);
- } else if (status.isRecently() || status.isHidden()) {
+ } else if (status.isRecently()) {
return tr::lng_status_recently(tr::now);
} else if (status.isWithinWeek()) {
return tr::lng_status_last_week(tr::now);
} else if (status.isWithinMonth()) {
return tr::lng_status_last_month(tr::now);
+ } else if (status.isHidden()) {
+ return tr::lng_status_recently(tr::now);
}
return std::nullopt;
}
diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp
index bae83a711..4727d5d5c 100644
--- a/Telegram/SourceFiles/data/data_session.cpp
+++ b/Telegram/SourceFiles/data/data_session.cpp
@@ -731,6 +731,8 @@ not_null 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);
}
@@ -3390,6 +3392,22 @@ void Session::documentApplyFields(
}
}
+not_null Session::venueIconDocument(const QString &icon) {
+ const auto i = _venueIcons.find(icon);
+ if (i != end(_venueIcons)) {
+ return i->second;
+ }
+ const auto result = documentFromWeb(MTP_webDocumentNoProxy(
+ MTP_string(u"https://ss3.4sqi.net/img/categories_v2/"_q
+ + icon
+ + u"_64.png"_q),
+ MTP_int(0),
+ MTP_string("image/png"),
+ MTP_vector()), {}, {});
+ _venueIcons.emplace(icon, result);
+ return result;
+}
+
not_null Session::webpage(WebPageId id) {
auto i = _webpages.find(id);
if (i == _webpages.cend()) {
@@ -4582,7 +4600,8 @@ void Session::serviceNotification(
MTPVector(),
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 = [=] {
diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h
index 12c4cba3a..06b4b267d 100644
--- a/Telegram/SourceFiles/data/data_session.h
+++ b/Telegram/SourceFiles/data/data_session.h
@@ -559,6 +559,8 @@ public:
const MTPWebDocument &data,
const ImageLocation &thumbnailLocation,
const ImageLocation &videoThumbnailLocation);
+ [[nodiscard]] not_null venueIconDocument(
+ const QString &icon);
[[nodiscard]] not_null webpage(WebPageId id);
not_null processWebpage(const MTPWebPage &data);
@@ -1002,6 +1004,7 @@ private:
FullStoryId,
base::flat_set>> _storyItems;
base::flat_map> _highlightings;
+ base::flat_map> _venueIcons;
base::flat_set> _webpagesUpdated;
base::flat_set> _gamesUpdated;
diff --git a/Telegram/SourceFiles/data/data_story.cpp b/Telegram/SourceFiles/data/data_story.cpp
index 38ea28aae..d9c37de4e 100644
--- a/Telegram/SourceFiles/data/data_story.cpp
+++ b/Telegram/SourceFiles/data/data_story.cpp
@@ -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 {
+ auto result = std::optional();
+ 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 &Story::urlAreas() const {
return _urlAreas;
}
+const std::vector &Story::weatherAreas() const {
+ return _weatherAreas;
+}
+
void Story::applyChanges(
StoryMedia media,
const MTPDstoryItem &data,
@@ -793,6 +829,7 @@ void Story::applyFields(
auto suggestedReactions = std::vector();
auto channelPosts = std::vector();
auto urlAreas = std::vector();
+ auto weatherAreas = std::vector();
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)) {
diff --git a/Telegram/SourceFiles/data/data_story.h b/Telegram/SourceFiles/data/data_story.h
index b318ce9fe..bd508591c 100644
--- a/Telegram/SourceFiles/data/data_story.h
+++ b/Telegram/SourceFiles/data/data_story.h
@@ -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 &;
[[nodiscard]] auto urlAreas() const
-> const std::vector &;
+ [[nodiscard]] auto weatherAreas() const
+ -> const std::vector &;
void applyChanges(
StoryMedia media,
@@ -259,6 +273,7 @@ private:
std::vector _suggestedReactions;
std::vector _channelPosts;
std::vector _urlAreas;
+ std::vector _weatherAreas;
StoryViews _views;
StoryViews _channelReactions;
const TimeId _date = 0;
diff --git a/Telegram/SourceFiles/data/data_user.h b/Telegram/SourceFiles/data/data_user.h
index 67da9e3f5..8f84555d3 100644
--- a/Telegram/SourceFiles/data/data_user.h
+++ b/Telegram/SourceFiles/data/data_user.h
@@ -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 {
diff --git a/Telegram/SourceFiles/data/raw/raw_countries_bounds.cpp b/Telegram/SourceFiles/data/raw/raw_countries_bounds.cpp
new file mode 100644
index 000000000..d4e383121
--- /dev/null
+++ b/Telegram/SourceFiles/data/raw/raw_countries_bounds.cpp
@@ -0,0 +1,193 @@
+/*
+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 "data/raw/raw_countries_bounds.h"
+
+// Source: https://github.com/sandstrom/country-bounding-boxes
+
+namespace Raw {
+
+const base::flat_map &CountryBounds() {
+ static const auto result = base::flat_map{
+ { u"AF"_q, GeoBounds{ 60.53, 29.32, 75.16, 38.49 } },
+ { u"AO"_q, GeoBounds{ 11.64, -17.93, 24.08, -4.44 } },
+ { u"AL"_q, GeoBounds{ 19.3, 39.62, 21.02, 42.69 } },
+ { u"AE"_q, GeoBounds{ 51.58, 22.5, 56.4, 26.06 } },
+ { u"AR"_q, GeoBounds{ -73.42, -55.25, -53.63, -21.83 } },
+ { u"AM"_q, GeoBounds{ 43.58, 38.74, 46.51, 41.25 } },
+ { u"AQ"_q, GeoBounds{ -180.0, -90.0, 180.0, -63.27 } },
+ { u"TF"_q, GeoBounds{ 68.72, -49.78, 70.56, -48.63 } },
+ { u"AU"_q, GeoBounds{ 113.34, -43.63, 153.57, -10.67 } },
+ { u"AT"_q, GeoBounds{ 9.48, 46.43, 16.98, 49.04 } },
+ { u"AZ"_q, GeoBounds{ 44.79, 38.27, 50.39, 41.86 } },
+ { u"BI"_q, GeoBounds{ 29.02, -4.5, 30.75, -2.35 } },
+ { u"BE"_q, GeoBounds{ 2.51, 49.53, 6.16, 51.48 } },
+ { u"BJ"_q, GeoBounds{ 0.77, 6.14, 3.8, 12.24 } },
+ { u"BF"_q, GeoBounds{ -5.47, 9.61, 2.18, 15.12 } },
+ { u"BD"_q, GeoBounds{ 88.08, 20.67, 92.67, 26.45 } },
+ { u"BG"_q, GeoBounds{ 22.38, 41.23, 28.56, 44.23 } },
+ { u"BS"_q, GeoBounds{ -78.98, 23.71, -77.0, 27.04 } },
+ { u"BA"_q, GeoBounds{ 15.75, 42.65, 19.6, 45.23 } },
+ { u"BY"_q, GeoBounds{ 23.2, 51.32, 32.69, 56.17 } },
+ { u"BZ"_q, GeoBounds{ -89.23, 15.89, -88.11, 18.5 } },
+ { u"BO"_q, GeoBounds{ -69.59, -22.87, -57.5, -9.76 } },
+ { u"BR"_q, GeoBounds{ -73.99, -33.77, -34.73, 5.24 } },
+ { u"BN"_q, GeoBounds{ 114.2, 4.01, 115.45, 5.45 } },
+ { u"BT"_q, GeoBounds{ 88.81, 26.72, 92.1, 28.3 } },
+ { u"BW"_q, GeoBounds{ 19.9, -26.83, 29.43, -17.66 } },
+ { u"CF"_q, GeoBounds{ 14.46, 2.27, 27.37, 11.14 } },
+ { u"CA"_q, GeoBounds{ -141.0, 41.68, -52.65, 73.23 } },
+ { u"CH"_q, GeoBounds{ 6.02, 45.78, 10.44, 47.83 } },
+ { u"CL"_q, GeoBounds{ -75.64, -55.61, -66.96, -17.58 } },
+ { u"CN"_q, GeoBounds{ 73.68, 18.2, 135.03, 53.46 } },
+ { u"CI"_q, GeoBounds{ -8.6, 4.34, -2.56, 10.52 } },
+ { u"CM"_q, GeoBounds{ 8.49, 1.73, 16.01, 12.86 } },
+ { u"CD"_q, GeoBounds{ 12.18, -13.26, 31.17, 5.26 } },
+ { u"CG"_q, GeoBounds{ 11.09, -5.04, 18.45, 3.73 } },
+ { u"CO"_q, GeoBounds{ -78.99, -4.3, -66.88, 12.44 } },
+ { u"CR"_q, GeoBounds{ -85.94, 8.23, -82.55, 11.22 } },
+ { u"CU"_q, GeoBounds{ -84.97, 19.86, -74.18, 23.19 } },
+ { u"CY"_q, GeoBounds{ 32.26, 34.57, 34.0, 35.17 } },
+ { u"CZ"_q, GeoBounds{ 12.24, 48.56, 18.85, 51.12 } },
+ { u"DE"_q, GeoBounds{ 5.99, 47.3, 15.02, 54.98 } },
+ { u"DJ"_q, GeoBounds{ 41.66, 10.93, 43.32, 12.7 } },
+ { u"DK"_q, GeoBounds{ 8.09, 54.8, 12.69, 57.73 } },
+ { u"DO"_q, GeoBounds{ -71.95, 17.6, -68.32, 19.88 } },
+ { u"DZ"_q, GeoBounds{ -8.68, 19.06, 12.0, 37.12 } },
+ { u"EC"_q, GeoBounds{ -80.97, -4.96, -75.23, 1.38 } },
+ { u"EG"_q, GeoBounds{ 24.7, 22.0, 36.87, 31.59 } },
+ { u"ER"_q, GeoBounds{ 36.32, 12.46, 43.08, 18.0 } },
+ { u"ES"_q, GeoBounds{ -9.39, 35.95, 3.04, 43.75 } },
+ { u"EE"_q, GeoBounds{ 23.34, 57.47, 28.13, 59.61 } },
+ { u"ET"_q, GeoBounds{ 32.95, 3.42, 47.79, 14.96 } },
+ { u"FI"_q, GeoBounds{ 20.65, 59.85, 31.52, 70.16 } },
+ { u"FJ"_q, GeoBounds{ -180.0, -18.29, 180.0, -16.02 } },
+ { u"FK"_q, GeoBounds{ -61.2, -52.3, -57.75, -51.1 } },
+ { u"FR"_q, GeoBounds{ -5.0, 42.5, 9.56, 51.15 } },
+ { u"GA"_q, GeoBounds{ 8.8, -3.98, 14.43, 2.33 } },
+ { u"GB"_q, GeoBounds{ -7.57, 49.96, 1.68, 58.64 } },
+ { u"GE"_q, GeoBounds{ 39.96, 41.06, 46.64, 43.55 } },
+ { u"GH"_q, GeoBounds{ -3.24, 4.71, 1.06, 11.1 } },
+ { u"GN"_q, GeoBounds{ -15.13, 7.31, -7.83, 12.59 } },
+ { u"GM"_q, GeoBounds{ -16.84, 13.13, -13.84, 13.88 } },
+ { u"GW"_q, GeoBounds{ -16.68, 11.04, -13.7, 12.63 } },
+ { u"GQ"_q, GeoBounds{ 9.31, 1.01, 11.29, 2.28 } },
+ { u"GR"_q, GeoBounds{ 20.15, 34.92, 26.6, 41.83 } },
+ { u"GL"_q, GeoBounds{ -73.3, 60.04, -12.21, 83.65 } },
+ { u"GT"_q, GeoBounds{ -92.23, 13.74, -88.23, 17.82 } },
+ { u"GY"_q, GeoBounds{ -61.41, 1.27, -56.54, 8.37 } },
+ { u"HN"_q, GeoBounds{ -89.35, 12.98, -83.15, 16.01 } },
+ { u"HR"_q, GeoBounds{ 13.66, 42.48, 19.39, 46.5 } },
+ { u"HT"_q, GeoBounds{ -74.46, 18.03, -71.62, 19.92 } },
+ { u"HU"_q, GeoBounds{ 16.2, 45.76, 22.71, 48.62 } },
+ { u"ID"_q, GeoBounds{ 95.29, -10.36, 141.03, 5.48 } },
+ { u"IN"_q, GeoBounds{ 68.18, 7.97, 97.4, 35.49 } },
+ { u"IE"_q, GeoBounds{ -9.98, 51.67, -6.03, 55.13 } },
+ { u"IR"_q, GeoBounds{ 44.11, 25.08, 63.32, 39.71 } },
+ { u"IQ"_q, GeoBounds{ 38.79, 29.1, 48.57, 37.39 } },
+ { u"IS"_q, GeoBounds{ -24.33, 63.5, -13.61, 66.53 } },
+ { u"IL"_q, GeoBounds{ 34.27, 29.5, 35.84, 33.28 } },
+ { u"IT"_q, GeoBounds{ 6.75, 36.62, 18.48, 47.12 } },
+ { u"JM"_q, GeoBounds{ -78.34, 17.7, -76.2, 18.52 } },
+ { u"JO"_q, GeoBounds{ 34.92, 29.2, 39.2, 33.38 } },
+ { u"JP"_q, GeoBounds{ 129.41, 31.03, 145.54, 45.55 } },
+ { u"KZ"_q, GeoBounds{ 46.47, 40.66, 87.36, 55.39 } },
+ { u"KE"_q, GeoBounds{ 33.89, -4.68, 41.86, 5.51 } },
+ { u"KG"_q, GeoBounds{ 69.46, 39.28, 80.26, 43.3 } },
+ { u"KH"_q, GeoBounds{ 102.35, 10.49, 107.61, 14.57 } },
+ { u"KR"_q, GeoBounds{ 126.12, 34.39, 129.47, 38.61 } },
+ { u"KW"_q, GeoBounds{ 46.57, 28.53, 48.42, 30.06 } },
+ { u"LA"_q, GeoBounds{ 100.12, 13.88, 107.56, 22.46 } },
+ { u"LB"_q, GeoBounds{ 35.13, 33.09, 36.61, 34.64 } },
+ { u"LR"_q, GeoBounds{ -11.44, 4.36, -7.54, 8.54 } },
+ { u"LY"_q, GeoBounds{ 9.32, 19.58, 25.16, 33.14 } },
+ { u"LK"_q, GeoBounds{ 79.7, 5.97, 81.79, 9.82 } },
+ { u"LS"_q, GeoBounds{ 27.0, -30.65, 29.33, -28.65 } },
+ { u"LT"_q, GeoBounds{ 21.06, 53.91, 26.59, 56.37 } },
+ { u"LU"_q, GeoBounds{ 5.67, 49.44, 6.24, 50.13 } },
+ { u"LV"_q, GeoBounds{ 21.06, 55.62, 28.18, 57.97 } },
+ { u"MA"_q, GeoBounds{ -17.02, 21.42, -1.12, 35.76 } },
+ { u"MD"_q, GeoBounds{ 26.62, 45.49, 30.02, 48.47 } },
+ { u"MG"_q, GeoBounds{ 43.25, -25.6, 50.48, -12.04 } },
+ { u"MX"_q, GeoBounds{ -117.13, 14.54, -86.81, 32.72 } },
+ { u"MK"_q, GeoBounds{ 20.46, 40.84, 22.95, 42.32 } },
+ { u"ML"_q, GeoBounds{ -12.17, 10.1, 4.27, 24.97 } },
+ { u"MM"_q, GeoBounds{ 92.3, 9.93, 101.18, 28.34 } },
+ { u"ME"_q, GeoBounds{ 18.45, 41.88, 20.34, 43.52 } },
+ { u"MN"_q, GeoBounds{ 87.75, 41.6, 119.77, 52.05 } },
+ { u"MZ"_q, GeoBounds{ 30.18, -26.74, 40.78, -10.32 } },
+ { u"MR"_q, GeoBounds{ -17.06, 14.62, -4.92, 27.4 } },
+ { u"MW"_q, GeoBounds{ 32.69, -16.8, 35.77, -9.23 } },
+ { u"MY"_q, GeoBounds{ 100.09, 0.77, 119.18, 6.93 } },
+ { u"NA"_q, GeoBounds{ 11.73, -29.05, 25.08, -16.94 } },
+ { u"NC"_q, GeoBounds{ 164.03, -22.4, 167.12, -20.11 } },
+ { u"NE"_q, GeoBounds{ 0.3, 11.66, 15.9, 23.47 } },
+ { u"NG"_q, GeoBounds{ 2.69, 4.24, 14.58, 13.87 } },
+ { u"NI"_q, GeoBounds{ -87.67, 10.73, -83.15, 15.02 } },
+ { u"NL"_q, GeoBounds{ 3.31, 50.8, 7.09, 53.51 } },
+ { u"NO"_q, GeoBounds{ 4.99, 58.08, 31.29, 70.92 } },
+ { u"NP"_q, GeoBounds{ 80.09, 26.4, 88.17, 30.42 } },
+ { u"NZ"_q, GeoBounds{ 166.51, -46.64, 178.52, -34.45 } },
+ { u"OM"_q, GeoBounds{ 52.0, 16.65, 59.81, 26.4 } },
+ { u"PK"_q, GeoBounds{ 60.87, 23.69, 77.84, 37.13 } },
+ { u"PA"_q, GeoBounds{ -82.97, 7.22, -77.24, 9.61 } },
+ { u"PE"_q, GeoBounds{ -81.41, -18.35, -68.67, -0.06 } },
+ { u"PH"_q, GeoBounds{ 117.17, 5.58, 126.54, 18.51 } },
+ { u"PG"_q, GeoBounds{ 141.0, -10.65, 156.02, -2.5 } },
+ { u"PL"_q, GeoBounds{ 14.07, 49.03, 24.03, 54.85 } },
+ { u"PR"_q, GeoBounds{ -67.24, 17.95, -65.59, 18.52 } },
+ { u"KP"_q, GeoBounds{ 124.27, 37.67, 130.78, 42.99 } },
+ { u"PT"_q, GeoBounds{ -9.53, 36.84, -6.39, 42.28 } },
+ { u"PY"_q, GeoBounds{ -62.69, -27.55, -54.29, -19.34 } },
+ { u"QA"_q, GeoBounds{ 50.74, 24.56, 51.61, 26.11 } },
+ { u"RO"_q, GeoBounds{ 20.22, 43.69, 29.63, 48.22 } },
+ { u"RU"_q, GeoBounds{ -180.0, 41.15, 180.0, 81.25 } },
+ { u"RW"_q, GeoBounds{ 29.02, -2.92, 30.82, -1.13 } },
+ { u"SA"_q, GeoBounds{ 34.63, 16.35, 55.67, 32.16 } },
+ { u"SD"_q, GeoBounds{ 21.94, 8.62, 38.41, 22.0 } },
+ { u"SS"_q, GeoBounds{ 23.89, 3.51, 35.3, 12.25 } },
+ { u"SN"_q, GeoBounds{ -17.63, 12.33, -11.47, 16.6 } },
+ { u"SB"_q, GeoBounds{ 156.49, -10.83, 162.4, -6.6 } },
+ { u"SL"_q, GeoBounds{ -13.25, 6.79, -10.23, 10.05 } },
+ { u"SV"_q, GeoBounds{ -90.1, 13.15, -87.72, 14.42 } },
+ { u"SO"_q, GeoBounds{ 40.98, -1.68, 51.13, 12.02 } },
+ { u"RS"_q, GeoBounds{ 18.83, 42.25, 22.99, 46.17 } },
+ { u"SR"_q, GeoBounds{ -58.04, 1.82, -53.96, 6.03 } },
+ { u"SK"_q, GeoBounds{ 16.88, 47.76, 22.56, 49.57 } },
+ { u"SI"_q, GeoBounds{ 13.7, 45.45, 16.56, 46.85 } },
+ { u"SE"_q, GeoBounds{ 11.03, 55.36, 23.9, 69.11 } },
+ { u"SZ"_q, GeoBounds{ 30.68, -27.29, 32.07, -25.66 } },
+ { u"SY"_q, GeoBounds{ 35.7, 32.31, 42.35, 37.23 } },
+ { u"TD"_q, GeoBounds{ 13.54, 7.42, 23.89, 23.41 } },
+ { u"TG"_q, GeoBounds{ -0.05, 5.93, 1.87, 11.02 } },
+ { u"TH"_q, GeoBounds{ 97.38, 5.69, 105.59, 20.42 } },
+ { u"TJ"_q, GeoBounds{ 67.44, 36.74, 74.98, 40.96 } },
+ { u"TM"_q, GeoBounds{ 52.5, 35.27, 66.55, 42.75 } },
+ { u"TL"_q, GeoBounds{ 124.97, -9.39, 127.34, -8.27 } },
+ { u"TT"_q, GeoBounds{ -61.95, 10.0, -60.9, 10.89 } },
+ { u"TN"_q, GeoBounds{ 7.52, 30.31, 11.49, 37.35 } },
+ { u"TR"_q, GeoBounds{ 26.04, 35.82, 44.79, 42.14 } },
+ { u"TW"_q, GeoBounds{ 120.11, 21.97, 121.95, 25.3 } },
+ { u"TZ"_q, GeoBounds{ 29.34, -11.72, 40.32, -0.95 } },
+ { u"UG"_q, GeoBounds{ 29.58, -1.44, 35.04, 4.25 } },
+ { u"UA"_q, GeoBounds{ 22.09, 44.36, 40.08, 52.34 } },
+ { u"UY"_q, GeoBounds{ -58.43, -34.95, -53.21, -30.11 } },
+ { u"US"_q, GeoBounds{ -125.0, 25.0, -66.96, 49.5 } },
+ { u"UZ"_q, GeoBounds{ 55.93, 37.14, 73.06, 45.59 } },
+ { u"VE"_q, GeoBounds{ -73.3, 0.72, -59.76, 12.16 } },
+ { u"VN"_q, GeoBounds{ 102.17, 8.6, 109.34, 23.35 } },
+ { u"VU"_q, GeoBounds{ 166.63, -16.6, 167.84, -14.63 } },
+ { u"PS"_q, GeoBounds{ 34.93, 31.35, 35.55, 32.53 } },
+ { u"YE"_q, GeoBounds{ 42.6, 12.59, 53.11, 19.0 } },
+ { u"ZA"_q, GeoBounds{ 16.34, -34.82, 32.83, -22.09 } },
+ { u"ZM"_q, GeoBounds{ 21.89, -17.96, 33.49, -8.24 } },
+ { u"ZW"_q, GeoBounds{ 25.26, -22.27, 32.85, -15.51 } }
+ };
+ return result;
+}
+
+} // namespace Raw
diff --git a/Telegram/SourceFiles/data/raw/raw_countries_bounds.h b/Telegram/SourceFiles/data/raw/raw_countries_bounds.h
new file mode 100644
index 000000000..4f0944c81
--- /dev/null
+++ b/Telegram/SourceFiles/data/raw/raw_countries_bounds.h
@@ -0,0 +1,23 @@
+/*
+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
+
+#include
+
+namespace Raw {
+
+struct GeoBounds {
+ double minLat = 0.;
+ double minLon = 0.;
+ double maxLat = 0.;
+ double maxLon = 0.;
+};
+
+[[nodiscard]] const base::flat_map &CountryBounds();
+
+} // namespace Raw
diff --git a/Telegram/SourceFiles/data/stickers/data_stickers.cpp b/Telegram/SourceFiles/data/stickers/data_stickers.cpp
index 04bb9c925..1721a53ca 100644
--- a/Telegram/SourceFiles/data/stickers/data_stickers.cpp
+++ b/Telegram/SourceFiles/data/stickers/data_stickers.cpp
@@ -228,7 +228,7 @@ void Stickers::incrementSticker(not_null 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 document) {
set->emoji[emoji].push_front(document);
}
} else {
- session().api().requestRecentStickersForce();
+ session().api().requestSpecialStickersForce(false, true, false);
}
writeRecentStickers = true;
diff --git a/Telegram/SourceFiles/data/stickers/data_stickers_set.cpp b/Telegram/SourceFiles/data/stickers/data_stickers_set.cpp
index 324377242..75d98404e 100644
--- a/Telegram/SourceFiles/data/stickers/data_stickers_set.cpp
+++ b/Telegram/SourceFiles/data/stickers/data_stickers_set.cpp
@@ -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(
diff --git a/Telegram/SourceFiles/data/stickers/data_stickers_set.h b/Telegram/SourceFiles/data/stickers/data_stickers_set.h
index e77ee46b5..c218ce2fa 100644
--- a/Telegram/SourceFiles/data/stickers/data_stickers_set.h
+++ b/Telegram/SourceFiles/data/stickers/data_stickers_set.h
@@ -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;
diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
index cb33e8a9a..6e60f072d 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
@@ -3814,7 +3814,9 @@ ChosenRow InnerWidget::computeChosenRow() const {
bool InnerWidget::isUserpicPress() const {
return (_lastRowLocalMouseX >= 0)
- && (_lastRowLocalMouseX < _st->nameLeft);
+ && (_lastRowLocalMouseX < _st->nameLeft)
+ && (_collapsedSelected < 0
+ || _collapsedSelected >= _collapsedRows.size());
}
bool InnerWidget::isUserpicPressOnWide() const {
diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
index 333282c92..41a9ddd8d 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp
@@ -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"
@@ -1282,6 +1283,27 @@ void Widget::updateSuggestions(anim::type animated) {
}
}, _suggestions->lifetime());
+ _suggestions->recentAppChosen(
+ ) | rpl::start_with_next([=](not_null 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 peer) {
+ controller()->showPeerInfo(peer);
+ }, _suggestions->lifetime());
+
updateControlsGeometry();
_suggestions->show(animated, [=] {
@@ -1293,6 +1315,17 @@ void Widget::updateSuggestions(anim::type animated) {
}
}
+void Widget::openBotMainApp(not_null bot) {
+ session().attachWebView().open({
+ .bot = bot,
+ .context = {
+ .controller = controller(),
+ .maySkipConfirmation = true,
+ },
+ .source = InlineBots::WebViewSourceBotProfile(),
+ });
+}
+
void Widget::changeOpenedSubsection(
FnMut change,
bool fromRight,
diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h
index 347683d0c..a1ee3950d 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_widget.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h
@@ -214,6 +214,7 @@ private:
void refreshTopBars();
void showSearchInTopBar(anim::type animated);
void checkUpdateStatus();
+ void openBotMainApp(not_null bot);
void changeOpenedSubsection(
FnMut