diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 162b6224d..4961c68f3 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -41,7 +41,7 @@ on: jobs: linux: - name: CentOS 7 + name: Rocky Linux 8 runs-on: ubuntu-latest container: image: ghcr.io/${{ github.repository }}/centos_env @@ -51,7 +51,7 @@ jobs: defaults: run: - shell: scl enable rh-python38 -- scl enable llvm-toolset-7.0 -- scl enable devtoolset-10 -- bash --noprofile --norc -eo pipefail {0} + shell: scl enable gcc-toolset-12 -- bash --noprofile --norc -eo pipefail {0} strategy: matrix: diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 4402a95f0..f924fe7a5 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -124,7 +124,7 @@ jobs: echo "TDESKTOP_BUILD_DEFINE=$DEFINE" >> $GITHUB_ENV API="-D TDESKTOP_API_TEST=ON" - if [ ${{ github.ref == 'refs/heads/nightly' }} ]; then + if [ $GITHUB_REF == 'refs/heads/nightly' ]; then echo "Use the open credentials." API="-D TDESKTOP_API_ID=611335 -D TDESKTOP_API_HASH=d524b414d21f4d37f08684c1df41ac9c" fi diff --git a/.gitmodules b/.gitmodules index 333aa6d9e..47d501392 100644 --- a/.gitmodules +++ b/.gitmodules @@ -58,9 +58,6 @@ [submodule "Telegram/ThirdParty/range-v3"] path = Telegram/ThirdParty/range-v3 url = https://github.com/ericniebler/range-v3.git -[submodule "Telegram/ThirdParty/fcitx-qt5"] - path = Telegram/ThirdParty/fcitx-qt5 - url = https://github.com/fcitx/fcitx-qt5.git [submodule "Telegram/ThirdParty/nimf"] path = Telegram/ThirdParty/nimf url = https://github.com/hamonikr/nimf.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 4beb508c0..8457b36d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,7 +61,7 @@ if (NOT DESKTOP_APP_USE_PACKAGED) if (WIN32) set(qt_version 5.15.10) elseif (APPLE) - set(qt_version 6.3.2) + set(qt_version 6.2.5) endif() endif() include(cmake/external/qt/package.cmake) diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 9bdfbc9b6..d2e79c54f 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -478,6 +478,7 @@ PRIVATE core/crash_report_window.h core/crash_reports.cpp core/crash_reports.h + core/deadlock_detector.h core/file_utilities.cpp core/file_utilities.h core/launcher.cpp @@ -1541,6 +1542,7 @@ PRIVATE qrc/emoji_5.qrc qrc/emoji_6.qrc qrc/emoji_7.qrc + qrc/emoji_8.qrc qrc/emoji_preview.qrc qrc/telegram/animations.qrc qrc/telegram/export.qrc diff --git a/Telegram/Resources/emoji/emoji_1.webp b/Telegram/Resources/emoji/emoji_1.webp index c4051383a..4bf92eca5 100644 Binary files a/Telegram/Resources/emoji/emoji_1.webp and b/Telegram/Resources/emoji/emoji_1.webp differ diff --git a/Telegram/Resources/emoji/emoji_2.webp b/Telegram/Resources/emoji/emoji_2.webp index c707db8e9..988947793 100644 Binary files a/Telegram/Resources/emoji/emoji_2.webp and b/Telegram/Resources/emoji/emoji_2.webp differ diff --git a/Telegram/Resources/emoji/emoji_3.webp b/Telegram/Resources/emoji/emoji_3.webp index d7eddeb5c..5e2172deb 100644 Binary files a/Telegram/Resources/emoji/emoji_3.webp and b/Telegram/Resources/emoji/emoji_3.webp differ diff --git a/Telegram/Resources/emoji/emoji_4.webp b/Telegram/Resources/emoji/emoji_4.webp index b668f059b..4859c1337 100644 Binary files a/Telegram/Resources/emoji/emoji_4.webp and b/Telegram/Resources/emoji/emoji_4.webp differ diff --git a/Telegram/Resources/emoji/emoji_5.webp b/Telegram/Resources/emoji/emoji_5.webp index 064a94d3f..3539ebe03 100644 Binary files a/Telegram/Resources/emoji/emoji_5.webp and b/Telegram/Resources/emoji/emoji_5.webp differ diff --git a/Telegram/Resources/emoji/emoji_6.webp b/Telegram/Resources/emoji/emoji_6.webp index fe7d47564..8da39a6b1 100644 Binary files a/Telegram/Resources/emoji/emoji_6.webp and b/Telegram/Resources/emoji/emoji_6.webp differ diff --git a/Telegram/Resources/emoji/emoji_7.webp b/Telegram/Resources/emoji/emoji_7.webp index 970a64c95..3c31b2345 100644 Binary files a/Telegram/Resources/emoji/emoji_7.webp and b/Telegram/Resources/emoji/emoji_7.webp differ diff --git a/Telegram/Resources/emoji/emoji_8.webp b/Telegram/Resources/emoji/emoji_8.webp new file mode 100644 index 000000000..c3e6b3b0d Binary files /dev/null and b/Telegram/Resources/emoji/emoji_8.webp differ diff --git a/Telegram/Resources/icons/limits/boost.png b/Telegram/Resources/icons/limits/boost.png new file mode 100644 index 000000000..b2c8bc7a0 Binary files /dev/null and b/Telegram/Resources/icons/limits/boost.png differ diff --git a/Telegram/Resources/icons/limits/boost@2x.png b/Telegram/Resources/icons/limits/boost@2x.png new file mode 100644 index 000000000..a4c54adcd Binary files /dev/null and b/Telegram/Resources/icons/limits/boost@2x.png differ diff --git a/Telegram/Resources/icons/limits/boost@3x.png b/Telegram/Resources/icons/limits/boost@3x.png new file mode 100644 index 000000000..194195fdb Binary files /dev/null and b/Telegram/Resources/icons/limits/boost@3x.png differ diff --git a/Telegram/Resources/icons/mediaview/views.png b/Telegram/Resources/icons/mediaview/views.png new file mode 100644 index 000000000..e8496be33 Binary files /dev/null and b/Telegram/Resources/icons/mediaview/views.png differ diff --git a/Telegram/Resources/icons/mediaview/views@2x.png b/Telegram/Resources/icons/mediaview/views@2x.png new file mode 100644 index 000000000..30b516900 Binary files /dev/null and b/Telegram/Resources/icons/mediaview/views@2x.png differ diff --git a/Telegram/Resources/icons/mediaview/views@3x.png b/Telegram/Resources/icons/mediaview/views@3x.png new file mode 100644 index 000000000..523f11cc8 Binary files /dev/null and b/Telegram/Resources/icons/mediaview/views@3x.png differ diff --git a/Telegram/Resources/icons/mini_forward.png b/Telegram/Resources/icons/mini_forward.png index 0b12af15d..e51712b55 100644 Binary files a/Telegram/Resources/icons/mini_forward.png and b/Telegram/Resources/icons/mini_forward.png differ diff --git a/Telegram/Resources/icons/mini_forward@2x.png b/Telegram/Resources/icons/mini_forward@2x.png index c8194ef00..8a8b8dd57 100644 Binary files a/Telegram/Resources/icons/mini_forward@2x.png and b/Telegram/Resources/icons/mini_forward@2x.png differ diff --git a/Telegram/Resources/icons/mini_forward@3x.png b/Telegram/Resources/icons/mini_forward@3x.png index 19cac4700..f6ca40a23 100644 Binary files a/Telegram/Resources/icons/mini_forward@3x.png and b/Telegram/Resources/icons/mini_forward@3x.png differ diff --git a/Telegram/Resources/icons/mini_reply_story.png b/Telegram/Resources/icons/mini_reply_story.png index bfcbc0c48..d1b436beb 100644 Binary files a/Telegram/Resources/icons/mini_reply_story.png and b/Telegram/Resources/icons/mini_reply_story.png differ diff --git a/Telegram/Resources/icons/mini_reply_story@2x.png b/Telegram/Resources/icons/mini_reply_story@2x.png index a3c75e8bf..8eddfb9ef 100644 Binary files a/Telegram/Resources/icons/mini_reply_story@2x.png and b/Telegram/Resources/icons/mini_reply_story@2x.png differ diff --git a/Telegram/Resources/icons/mini_reply_story@3x.png b/Telegram/Resources/icons/mini_reply_story@3x.png index 471cfa57d..e836bcd3d 100644 Binary files a/Telegram/Resources/icons/mini_reply_story@3x.png and b/Telegram/Resources/icons/mini_reply_story@3x.png differ diff --git a/Telegram/Resources/icons/stories/boost_mini.png b/Telegram/Resources/icons/stories/boost_mini.png new file mode 100644 index 000000000..90a24d8f1 Binary files /dev/null and b/Telegram/Resources/icons/stories/boost_mini.png differ diff --git a/Telegram/Resources/icons/stories/boost_mini@2x.png b/Telegram/Resources/icons/stories/boost_mini@2x.png new file mode 100644 index 000000000..fd746a03a Binary files /dev/null and b/Telegram/Resources/icons/stories/boost_mini@2x.png differ diff --git a/Telegram/Resources/icons/stories/boost_mini@3x.png b/Telegram/Resources/icons/stories/boost_mini@3x.png new file mode 100644 index 000000000..637a5716d Binary files /dev/null and b/Telegram/Resources/icons/stories/boost_mini@3x.png differ diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 5592445c5..ac7e1e359 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -548,6 +548,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_title_account_name" = "Show active account"; "lng_settings_title_total_count" = "Total unread count"; "lng_settings_native_frame" = "Use system window frame"; +"lng_settings_qt_frame" = "Use Qt window frame"; "lng_settings_auto_start" = "Launch Telegram when system starts"; "lng_settings_start_min" = "Launch minimized"; "lng_settings_auto_start_disabled_uwp" = "Starting with the system was disabled in Windows Settings.\n\nPlease enable Telegram Desktop in the Startup Apps Settings."; @@ -1182,6 +1183,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_profile_loading" = "Loading..."; "lng_profile_saved_stories#one" = "{count} saved story"; "lng_profile_saved_stories#other" = "{count} saved stories"; +"lng_profile_posts#one" = "{count} post"; +"lng_profile_posts#other" = "{count} posts"; "lng_profile_photos#one" = "{count} photo"; "lng_profile_photos#other" = "{count} photos"; "lng_profile_gifs#one" = "{count} GIF"; @@ -1611,6 +1614,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_suggested_video" = "{user} suggests you to use this profile video."; "lng_action_suggested_video_button" = "View Video"; "lng_action_attach_menu_bot_allowed" = "You allowed this bot to message you when you added it in the attachment menu."; +"lng_action_webapp_bot_allowed" = "You allowed this bot to message you in his web-app."; "lng_action_set_wallpaper_me" = "You set a new wallpaper for this chat"; "lng_action_set_wallpaper" = "{user} set a new wallpaper for this chat"; "lng_action_set_wallpaper_button" = "View Wallpaper"; @@ -1799,6 +1803,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_bot_share_location_unavailable" = "Sorry, location sharing is currently unavailable in Telegram Desktop."; "lng_bot_share_phone" = "Do you want to share your phone number with this bot?"; "lng_bot_share_phone_confirm" = "Share"; +"lng_bot_allow_write_title" = "Allow messaging"; +"lng_bot_allow_write" = "Do you want to allow this bot writing you?"; +"lng_bot_allow_write_confirm" = "Allow"; "lng_attach_failed" = "Failed"; "lng_attach_file" = "File"; @@ -1995,6 +2002,38 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_premium_gift_terms" = "You can review the list of features and terms of use for Telegram Premium {link}."; "lng_premium_gift_terms_link" = "here"; +"lng_boost_channel_button" = "Boost Channel"; +"lng_boost_level#one" = "Level {count}"; +"lng_boost_level#other" = "Level {count}"; +"lng_boost_channel_title_first" = "Enable stories for channel"; +"lng_boost_channel_needs_first#one" = "{channel} needs **{count}** more boost to enable posting stories. Help make it possible!"; +"lng_boost_channel_needs_first#other" = "{channel} needs **{count}** more boosts to enable posting stories. Help make it possible!"; +"lng_boost_channel_title_more" = "Help upgrade channel"; +"lng_boost_channel_needs_more#one" = "{channel} needs **{count}** more boost to be able to {post}."; +"lng_boost_channel_needs_more#other" = "{channel} needs **{count}** more boosts to be able to {post}."; +"lng_boost_channel_title_max" = "Maximum level reached"; +"lng_boost_channel_you_title" = "You boosted {channel}!"; +"lng_boost_channel_you_first#one" = "This channel needs **{count}** more boost\nto enable stories."; +"lng_boost_channel_you_first#other" = "This channel needs **{count}** more boosts\nto enable stories."; +"lng_boost_channel_you_more#one" = "This channel needs **{count}** more boost\nto be able to {post}."; +"lng_boost_channel_you_more#other" = "This channel needs **{count}** more boosts\nto be able to {post}."; +"lng_boost_channel_reached_first" = "This channel reached **Level 1** and can now post stories."; +"lng_boost_channel_reached_more#one" = "This channel reached **Level {count}** and can now {post}."; +"lng_boost_channel_reached_more#other" = "This channel reached **Level {count}** and can now {post}."; +"lng_boost_channel_post_stories#one" = "post **{count} story** per day"; +"lng_boost_channel_post_stories#other" = "post **{count} stories** per day"; +"lng_boost_error_gifted_title" = "Can't boost with gifted Premium!"; +"lng_boost_error_gifted_text" = "Because your **Telegram Premium** subscription was gifted to you, you can't use it to boost channels."; +"lng_boost_error_already_title" = "Already Boosted!"; +"lng_boost_error_already_text" = "You are already boosting this channel."; +"lng_boost_error_premium_title" = "Premium needed!"; +"lng_boost_error_premium_text" = "Only **Telegram Premium** subscribers can boost channels. Do you want to subscribe to **Telegram Premium**?"; +"lng_boost_error_premium_yes" = "Yes"; +"lng_boost_error_flood_title" = "Can't boost too often!"; +"lng_boost_error_flood_text" = "You can change the channel you boost only once a day. Next time you can boost is in {left}."; +"lng_boost_now_instead" = "You currently boost {channel}. Do you want to boost {other} instead?"; +"lng_boost_now_replace" = "Replace"; + "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."; @@ -2256,17 +2295,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_bot_remove_from_menu" = "Remove From Menu"; "lng_bot_remove_from_menu_sure" = "Remove {bot} from the attachment menu?"; "lng_bot_remove_from_menu_done" = "Bot removed from the menu."; +"lng_bot_remove_from_side_menu" = "Remove From Menu"; +"lng_bot_remove_from_side_menu_sure" = "Remove {bot} from the main menu?"; +"lng_bot_remove_from_side_menu_done" = "Bot removed from the main menu."; "lng_bot_settings" = "Settings"; "lng_bot_open" = "Open Bot"; "lng_bot_reload_page" = "Reload Page"; "lng_bot_add_to_menu" = "{bot} asks your permission to be added as an option to your attachments menu so you can access it from any chat."; "lng_bot_add_to_menu_done" = "Bot added to the menu."; +"lng_bot_will_be_added" = "{bot} shortcuts will be added to the attachment options and the main menu."; +"lng_bot_side_menu_new" = "NEW"; "lng_bot_menu_not_supported" = "This bot isn't supported in the attach menu."; "lng_bot_menu_already_added" = "This bot is already added in your attach menu."; "lng_bot_menu_button" = "Menu"; "lng_bot_close_warning_title" = "Warning"; "lng_bot_close_warning" = "Changes that you made may not be saved."; "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_typing" = "typing"; "lng_user_typing" = "{user} is typing"; @@ -2370,6 +2416,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_context_open_gif" = "Open GIF"; "lng_context_save_gif" = "Save GIF"; "lng_context_delete_gif" = "Delete GIF"; +"lng_context_open_channel" = "Open Channel"; "lng_context_attached_stickers" = "Attached Stickers"; "lng_context_to_msg" = "Go To Message"; "lng_context_reply_msg" = "Reply"; @@ -2747,6 +2794,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_payments_terms_title" = "Terms of Service"; "lng_payments_terms_text" = "Subscribe and accept terms of service of {bot}?"; +"lng_payments_terms_text_once" = "Are you accepting terms of service of {bot}?"; "lng_payments_terms_agree" = "I agree to {link}"; "lng_payments_terms_link" = "Terms of Service"; "lng_payments_terms_accept" = "Accept"; @@ -3079,9 +3127,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gigagroup_suggest_more" = "Learn more"; "lng_rights_channel_info" = "Change channel info"; +"lng_rights_channel_manage" = "Manage messages"; "lng_rights_channel_post" = "Post messages"; "lng_rights_channel_edit" = "Edit messages of others"; "lng_rights_channel_delete" = "Delete messages of others"; +"lng_rights_channel_manage_stories" = "Manage stories"; +"lng_rights_channel_post_stories" = "Post stories"; +"lng_rights_channel_edit_stories" = "Edit stories of others"; +"lng_rights_channel_delete_stories" = "Delete stories of others"; "lng_rights_channel_manage_calls" = "Manage live streams"; "lng_rights_group_info" = "Change group info"; "lng_rights_group_ban" = "Ban users"; @@ -3340,6 +3393,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_admin_log_admin_post_messages" = "Post messages"; "lng_admin_log_admin_edit_messages" = "Edit messages"; "lng_admin_log_admin_delete_messages" = "Delete messages"; +"lng_admin_log_admin_post_stories" = "Post stories"; +"lng_admin_log_admin_edit_stories" = "Edit stories"; +"lng_admin_log_admin_delete_stories" = "Delete stories"; "lng_admin_log_admin_remain_anonymous" = "Remain anonymous"; "lng_admin_log_admin_ban_users" = "Ban users"; "lng_admin_log_admin_invite_users" = "Add members"; @@ -3555,6 +3611,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_export_option_choose_format" = "Choose export format"; "lng_export_option_html" = "Human-readable HTML"; "lng_export_option_json" = "Machine-readable JSON"; +"lng_export_option_html_and_json" = "Both"; "lng_export_limits" = "From: {from}, to: {till}"; "lng_export_beginning" = "the oldest message"; "lng_export_end" = "present"; @@ -3819,6 +3876,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_view_button_voice_chat_channel" = "Live stream"; "lng_view_button_request_join" = "Request to Join"; "lng_view_button_external_link" = "Open link"; +"lng_view_button_boost" = "Boost"; "lng_sponsored_hide_ads" = "Hide"; "lng_sponsored_title" = "What are sponsored messages?"; @@ -3829,6 +3887,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_telegram_features_url" = "https://t.me/TelegramTips"; +"lng_mini_apps_disclaimer_title" = "Warning"; +"lng_mini_apps_disclaimer_text" = "You are about to use a mini app operated by an independent party **not affiliated with Telegram**. You must agree to the Terms of Use of mini apps to continue."; +"lng_mini_apps_disclaimer_button" = "I agree to the {link}"; +"lng_mini_apps_disclaimer_link" = "Terms of Use"; +"lng_mini_apps_tos_url" = "https://telegram.org/tos/mini-apps"; + "lng_ringtones_box_title" = "Notification Sound"; "lng_ringtones_box_cloud_subtitle" = "Choose your tone"; "lng_ringtones_box_upload_choose" = "Choose ringtone"; @@ -3946,6 +4010,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_stories_recent_button" = "Recent Stories"; "lng_stories_archive_title" = "Stories Archive"; "lng_stories_archive_about" = "Only you can see archived stories unless you choose to save them to your profile."; +"lng_stories_channel_archive_about" = "Only admins of the channel can see archived stories unless they are saved to the channel page."; "lng_stories_reply_sent" = "Message Sent"; "lng_stories_hidden_to_contacts" = "Stories from {user} will now be shown in **Archived Chats**."; "lng_stories_shown_in_chats" = "Stories from {user} will now be shown in the **Chats List**."; @@ -3965,6 +4030,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_stories_archive_done" = "This story is hidden from your profile."; "lng_stories_archive_done_many#one" = "{count} story is hidden from your profile."; "lng_stories_archive_done_many#other" = "{count} stories are hidden from your profile."; +"lng_stories_channel_save_sure" = "Do you want to save this story to the channel page?"; +"lng_stories_channel_save_sure_many#one" = "Do you want to save {count} story to the channel page?"; +"lng_stories_channel_save_sure_many#other" = "Do you want to save {count} stories to the channel page?"; +"lng_stories_channel_save_done" = "This story is saved to the channel page."; +"lng_stories_channel_save_done_many#one" = "{count} story is saved to the channel page."; +"lng_stories_channel_save_done_many#other" = "{count} stories are saved to the channel page."; +"lng_stories_channel_save_done_about" = "Saved stories can be viewed by others on the channel page until they are removed."; +"lng_stories_channel_archive_sure" = "Do you want to hide this story from the channel page?"; +"lng_stories_channel_archive_sure_many#one" = "Do you want to hide {count} story from the channel page?"; +"lng_stories_channel_archive_sure_many#other" = "Do you want to hide {count} stories from the channel page?"; +"lng_stories_channel_archive_done" = "This story is hidden from the channel page."; +"lng_stories_channel_archive_done_many#one" = "{count} story is hidden from the channel page."; +"lng_stories_channel_archive_done_many#other" = "{count} stories are hidden from the channel page."; "lng_stories_save_promo" = "Subscribe to {link} to download other people's unprotected stories to disk."; "lng_stealth_mode_menu_item" = "Stealth Mode"; diff --git a/Telegram/Resources/qrc/emoji_8.qrc b/Telegram/Resources/qrc/emoji_8.qrc new file mode 100644 index 000000000..51765a39b --- /dev/null +++ b/Telegram/Resources/qrc/emoji_8.qrc @@ -0,0 +1,5 @@ + + + ../emoji/emoji_8.webp + + diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 3b5ae67a8..65657e925 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="4.10.0.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index f0efceda4..b3a9340d4 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,9,4,0 - PRODUCTVERSION 4,9,4,0 + FILEVERSION 4,10,0,0 + PRODUCTVERSION 4,10,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Radolyn Labs" VALUE "FileDescription", "AyuGram Desktop" - VALUE "FileVersion", "4.9.4.0" + VALUE "FileVersion", "4.10.0.0" VALUE "LegalCopyright", "Copyright (C) 2014-2023" VALUE "ProductName", "AyuGram Desktop" - VALUE "ProductVersion", "4.9.4.0" + VALUE "ProductVersion", "4.10.0.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 5d8768e00..4b3956f9f 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,9,4,0 - PRODUCTVERSION 4,9,4,0 + FILEVERSION 4,10,0,0 + PRODUCTVERSION 4,10,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Radolyn Labs" VALUE "FileDescription", "AyuGram Desktop Updater" - VALUE "FileVersion", "4.9.4.0" + VALUE "FileVersion", "4.10.0.0" VALUE "LegalCopyright", "Copyright (C) 2014-2023" VALUE "ProductName", "AyuGram Desktop" - VALUE "ProductVersion", "4.9.4.0" + VALUE "ProductVersion", "4.10.0.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/_other/packer.cpp b/Telegram/SourceFiles/_other/packer.cpp index 0ba79c1be..156ba726a 100644 --- a/Telegram/SourceFiles/_other/packer.cpp +++ b/Telegram/SourceFiles/_other/packer.cpp @@ -267,7 +267,7 @@ int main(int argc, char *argv[]) } QByteArray inner = f.readAll(); stream << name << quint32(inner.size()) << inner; -#ifdef Q_OS_UNIX +#ifndef Q_OS_WIN stream << (QFileInfo(fullName).isExecutable() ? true : false); #endif } @@ -281,7 +281,7 @@ int main(int argc, char *argv[]) cout << "Compression start, size: " << resultSize << "\n"; QByteArray compressed, resultCheck; -#if defined Q_OS_WIN && !defined DESKTOP_APP_USE_PACKAGED // use Lzma SDK for win +#if defined Q_OS_WIN && !defined TDESKTOP_USE_PACKAGED // use Lzma SDK for win const int32 hSigLen = 128, hShaLen = 20, hPropsLen = LZMA_PROPS_SIZE, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hPropsLen + hOriginalSizeLen; // header compressed.resize(hSize + resultSize + 1024 * 1024); // rsa signature + sha1 + lzma props + max compressed size @@ -496,10 +496,8 @@ int main(int argc, char *argv[]) QString outName((targetwin64 ? QString("tx64upd%1") : QString("tupdate%1")).arg(AlphaVersion ? AlphaVersion : version)); #elif defined Q_OS_MAC QString outName((targetarmac ? QString("tarmacupd%1") : QString("tmacupd%1")).arg(AlphaVersion ? AlphaVersion : version)); -#elif defined Q_OS_UNIX - QString outName(QString("tlinuxupd%1").arg(AlphaVersion ? AlphaVersion : version)); #else -#error Unknown platform! + QString outName(QString("tlinuxupd%1").arg(AlphaVersion ? AlphaVersion : version)); #endif if (AlphaVersion) { outName += "_" + AlphaSignature; diff --git a/Telegram/SourceFiles/_other/packer.h b/Telegram/SourceFiles/_other/packer.h index d099ef0e3..4e5fbfc7a 100644 --- a/Telegram/SourceFiles/_other/packer.h +++ b/Telegram/SourceFiles/_other/packer.h @@ -27,7 +27,7 @@ extern "C" { #include } // extern "C" -#if defined Q_OS_WIN && !defined DESKTOP_APP_USE_PACKAGED // use Lzma SDK for win +#if defined Q_OS_WIN && !defined TDESKTOP_USE_PACKAGED // use Lzma SDK for win #include #else #include diff --git a/Telegram/SourceFiles/_other/updater_linux.cpp b/Telegram/SourceFiles/_other/updater_linux.cpp index f286b05be..58bc10efa 100644 --- a/Telegram/SourceFiles/_other/updater_linux.cpp +++ b/Telegram/SourceFiles/_other/updater_linux.cpp @@ -41,6 +41,7 @@ bool do_mkdir(const char *path) { // from http://stackoverflow.com/questions/675 } bool _debug = false; +bool writeprotected = false; string updaterDir; string updaterName; string workDir; @@ -88,7 +89,7 @@ void writeLog(const char *format, ...) { va_end(args); } -bool copyFile(const char *from, const char *to, bool writeprotected) { +bool copyFile(const char *from, const char *to) { FILE *ffrom = fopen(from, "rb"), *fto = fopen(to, "wb"); if (!ffrom) { if (fto) fclose(fto); @@ -211,7 +212,7 @@ void delFolder() { rmdir(delFolder.c_str()); } -bool update(bool writeprotected) { +bool update() { writeLog("Update started.."); string updDir = workDir + "tupdates/temp", readyFilePath = workDir + "tupdates/temp/ready", tdataDir = workDir + "tupdates/temp/tdata"; @@ -324,7 +325,7 @@ bool update(bool writeprotected) { writeLog("Copying file '%s' to '%s'..", fname.c_str(), tofname.c_str()); int copyTries = 0, triesLimit = 30; do { - if (!copyFile(fname.c_str(), tofname.c_str(), writeprotected)) { + if (!copyFile(fname.c_str(), tofname.c_str())) { ++copyTries; usleep(100000); } else { @@ -359,10 +360,10 @@ int main(int argc, char *argv[]) { bool needupdate = true; bool autostart = false; bool debug = false; - bool writeprotected = false; bool tosettings = false; bool startintray = false; bool customWorkingDir = false; + bool justUpdate = false; char *key = 0; char *workdir = 0; @@ -381,6 +382,9 @@ int main(int argc, char *argv[]) { customWorkingDir = true; } else if (equal(argv[i], "-writeprotected")) { writeprotected = true; + justUpdate = true; + } else if (equal(argv[i], "-justupdate")) { + justUpdate = true; } else if (equal(argv[i], "-key") && ++i < argc) { key = argv[i]; } else if (equal(argv[i], "-workpath") && ++i < argc) { @@ -455,7 +459,7 @@ int main(int argc, char *argv[]) { } else { writeLog("Passed workpath is '%s'", workDir.c_str()); } - update(writeprotected); + update(); } } else { writeLog("Error: bad exe name!"); @@ -464,36 +468,38 @@ int main(int argc, char *argv[]) { writeLog("Error: short exe name!"); } - const auto fullBinaryPath = exePath + exeName; - - auto values = vector(); - const auto push = [&](string arg) { - // Force null-terminated .data() call result. - values.push_back(arg + char(0)); - }; - push(!argv0.empty() ? argv0 : fullBinaryPath); - push("-noupdate"); - if (autostart) push("-autostart"); - if (debug) push("-debug"); - if (startintray) push("-startintray"); - if (tosettings) push("-tosettings"); - if (key) { - push("-key"); - push(key); - } - if (customWorkingDir && workdir) { - push("-workdir"); - push(workdir); - } - - auto args = vector(); - for (auto &arg : values) { - args.push_back(arg.data()); - } - args.push_back(nullptr); - // let the parent launch instead - if (!writeprotected) { + if (justUpdate) { + writeLog("Closing log and quitting.."); + } else { + const auto fullBinaryPath = exePath + exeName; + + auto values = vector(); + const auto push = [&](string arg) { + // Force null-terminated .data() call result. + values.push_back(arg + char(0)); + }; + push(!argv0.empty() ? argv0 : fullBinaryPath); + push("-noupdate"); + if (autostart) push("-autostart"); + if (debug) push("-debug"); + if (startintray) push("-startintray"); + if (tosettings) push("-tosettings"); + if (key) { + push("-key"); + push(key); + } + if (customWorkingDir && workdir) { + push("-workdir"); + push(workdir); + } + + auto args = vector(); + for (auto &arg : values) { + args.push_back(arg.data()); + } + args.push_back(nullptr); + pid_t pid = fork(); switch (pid) { case -1: @@ -503,9 +509,10 @@ int main(int argc, char *argv[]) { execv(fullBinaryPath.c_str(), args.data()); return 1; } + + writeLog("Executed Telegram, closing log and quitting.."); } - writeLog("Executed Telegram, closing log and quitting.."); closeLog(); return 0; diff --git a/Telegram/SourceFiles/api/api_blocked_peers.cpp b/Telegram/SourceFiles/api/api_blocked_peers.cpp index 9dfc886b1..0a79eb733 100644 --- a/Telegram/SourceFiles/api/api_blocked_peers.cpp +++ b/Telegram/SourceFiles/api/api_blocked_peers.cpp @@ -77,45 +77,59 @@ void BlockedPeers::block(not_null peer) { _session->changes().peerUpdated( peer, Data::PeerUpdate::Flag::IsBlocked); - } else if (_blockRequests.find(peer) == end(_blockRequests)) { - const auto requestId = _api.request(MTPcontacts_Block( - MTP_flags(0), - peer->input - )).done([=] { - _blockRequests.erase(peer); - peer->setIsBlocked(true); - if (_slice) { - _slice->list.insert( - _slice->list.begin(), - { peer->id, base::unixtime::now() }); - ++_slice->total; - _changes.fire_copy(*_slice); - } - }).fail([=] { - _blockRequests.erase(peer); - }).send(); - - _blockRequests.emplace(peer, requestId); + return; + } else if (blockAlreadySent(peer, true)) { + return; } + const auto requestId = _api.request(MTPcontacts_Block( + MTP_flags(0), + peer->input + )).done([=] { + const auto data = _blockRequests.take(peer); + peer->setIsBlocked(true); + if (_slice) { + _slice->list.insert( + _slice->list.begin(), + { peer->id, base::unixtime::now() }); + ++_slice->total; + _changes.fire_copy(*_slice); + } + if (data) { + for (const auto &callback : data->callbacks) { + callback(false); + } + } + }).fail([=] { + if (const auto data = _blockRequests.take(peer)) { + for (const auto &callback : data->callbacks) { + callback(false); + } + } + }).send(); + + _blockRequests.emplace(peer, Request{ + .requestId = requestId, + .blocking = true, + }); } void BlockedPeers::unblock( not_null peer, - Fn onDone, + Fn done, bool force) { if (!force && !peer->isBlocked()) { _session->changes().peerUpdated( peer, Data::PeerUpdate::Flag::IsBlocked); return; - } else if (_blockRequests.find(peer) != end(_blockRequests)) { + } else if (blockAlreadySent(peer, false, done)) { return; } const auto requestId = _api.request(MTPcontacts_Unblock( MTP_flags(0), peer->input )).done([=] { - _blockRequests.erase(peer); + const auto data = _blockRequests.take(peer); peer->setIsBlocked(false); if (_slice) { auto &list = _slice->list; @@ -130,13 +144,46 @@ void BlockedPeers::unblock( } _changes.fire_copy(*_slice); } - if (onDone) { - onDone(); + if (data) { + for (const auto &callback : data->callbacks) { + callback(true); + } } }).fail([=] { - _blockRequests.erase(peer); + if (const auto data = _blockRequests.take(peer)) { + for (const auto &callback : data->callbacks) { + callback(false); + } + } }).send(); - _blockRequests.emplace(peer, requestId); + const auto i = _blockRequests.emplace(peer, Request{ + .requestId = requestId, + .blocking = false, + }).first; + if (done) { + i->second.callbacks.push_back(std::move(done)); + } +} + +bool BlockedPeers::blockAlreadySent( + not_null peer, + bool blocking, + Fn done) { + const auto i = _blockRequests.find(peer); + if (i == end(_blockRequests)) { + return false; + } else if (i->second.blocking == blocking) { + if (done) { + i->second.callbacks.push_back(std::move(done)); + } + return true; + } + const auto callbacks = base::take(i->second.callbacks); + _blockRequests.erase(i); + for (const auto &callback : callbacks) { + callback(false); + } + return false; } void BlockedPeers::reload() { @@ -160,7 +207,7 @@ auto BlockedPeers::slice() -> rpl::producer { : (_changes.events() | rpl::type_erased()); } -void BlockedPeers::request(int offset, Fn onDone) { +void BlockedPeers::request(int offset, Fn done) { if (_requestId) { return; } @@ -170,7 +217,7 @@ void BlockedPeers::request(int offset, Fn onDone) { MTP_int(offset ? kBlockedPerPage : kBlockedFirstSlice) )).done([=](const MTPcontacts_Blocked &result) { _requestId = 0; - onDone(TLToSlice(result, _session->data())); + done(TLToSlice(result, _session->data())); }).fail([=] { _requestId = 0; }).send(); diff --git a/Telegram/SourceFiles/api/api_blocked_peers.h b/Telegram/SourceFiles/api/api_blocked_peers.h index af184c457..ca2000f67 100644 --- a/Telegram/SourceFiles/api/api_blocked_peers.h +++ b/Telegram/SourceFiles/api/api_blocked_peers.h @@ -39,20 +39,31 @@ public: void reload(); rpl::producer slice(); - void request(int offset, Fn onDone); + void request(int offset, Fn done); void block(not_null peer); void unblock( not_null peer, - Fn onDone = nullptr, + Fn done = nullptr, bool force = false); private: + struct Request { + std::vector> callbacks; + mtpRequestId requestId = 0; + bool blocking = false; + }; + + [[nodiscard]] bool blockAlreadySent( + not_null peer, + bool blocking, + Fn done = nullptr); + const not_null _session; MTP::Sender _api; - base::flat_map, mtpRequestId> _blockRequests; + base::flat_map, Request> _blockRequests; mtpRequestId _requestId = 0; std::optional _slice; rpl::event_stream _changes; diff --git a/Telegram/SourceFiles/api/api_chat_invite.cpp b/Telegram/SourceFiles/api/api_chat_invite.cpp index 8b395d9a9..dd06d2abc 100644 --- a/Telegram/SourceFiles/api/api_chat_invite.cpp +++ b/Telegram/SourceFiles/api/api_chat_invite.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "window/window_session_controller.h" +#include "info/profile/info_profile_badge.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "ui/empty_userpic.h" @@ -25,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/toast/toast.h" #include "boxes/premium_limits_box.h" #include "styles/style_boxes.h" +#include "styles/style_info.h" #include "styles/style_layers.h" namespace Api { @@ -195,6 +197,13 @@ ConfirmInviteBox::ConfirmInviteBox( : _session(session) , _submit(std::move(submit)) , _title(this, st::confirmInviteTitle) +, _badge(std::make_unique( + this, + st::infoPeerBadge, + _session, + rpl::single(Info::Profile::Badge::Content{ BadgeForInvite(invite) }), + nullptr, + [=] { return false; })) , _status(this, st::confirmInviteStatus) , _about(this, st::confirmInviteAbout) , _aboutRequests(this, st::confirmInviteStatus) @@ -275,9 +284,24 @@ ConfirmInviteBox::ChatInvite ConfirmInviteBox::Parse( .isMegagroup = data.is_megagroup(), .isBroadcast = data.is_broadcast(), .isRequestNeeded = data.is_request_needed(), + .isFake = data.is_fake(), + .isScam = data.is_scam(), + .isVerified = data.is_verified(), }; } +[[nodiscard]] Info::Profile::BadgeType ConfirmInviteBox::BadgeForInvite( + const ChatInvite &invite) { + using Type = Info::Profile::BadgeType; + return invite.isVerified + ? Type::Verified + : invite.isScam + ? Type::Scam + : invite.isFake + ? Type::Fake + : Type::None; +} + void ConfirmInviteBox::prepare() { addButton( (_requestApprove @@ -326,8 +350,26 @@ void ConfirmInviteBox::prepare() { void ConfirmInviteBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); - _title->move((width() - _title->width()) / 2, st::confirmInviteTitleTop); - _status->move((width() - _status->width()) / 2, st::confirmInviteStatusTop); + + const auto padding = st::boxRowPadding; + auto nameWidth = width() - padding.left() - padding.right(); + auto badgeWidth = 0; + if (const auto widget = _badge->widget()) { + badgeWidth = st::infoVerifiedCheckPosition.x() + widget->width(); + nameWidth -= badgeWidth; + } + _title->resizeToWidth(std::min(nameWidth, _title->textMaxWidth())); + _title->moveToLeft( + (width() - _title->width() - badgeWidth) / 2, + st::confirmInviteTitleTop); + const auto badgeLeft = _title->x() + _title->width(); + const auto badgeTop = _title->y(); + const auto badgeBottom = _title->y() + _title->height(); + _badge->move(badgeLeft, badgeTop, badgeBottom); + + _status->move( + (width() - _status->width()) / 2, + st::confirmInviteStatusTop); auto bottom = _status->y() + _status->height() + st::boxPadding.bottom() diff --git a/Telegram/SourceFiles/api/api_chat_invite.h b/Telegram/SourceFiles/api/api_chat_invite.h index 1ae6f2529..c8d99548c 100644 --- a/Telegram/SourceFiles/api/api_chat_invite.h +++ b/Telegram/SourceFiles/api/api_chat_invite.h @@ -12,6 +12,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class UserData; class ChannelData; +namespace Info::Profile { +class Badge; +enum class BadgeType; +} // namespace Info::Profile + namespace Main { class Session; } // namespace Main @@ -66,10 +71,15 @@ private: bool isMegagroup = false; bool isBroadcast = false; bool isRequestNeeded = false; + bool isFake = false; + bool isScam = false; + bool isVerified = false; }; [[nodiscard]] static ChatInvite Parse( not_null session, const MTPDchatInvite &data); + [[nodiscard]] Info::Profile::BadgeType BadgeForInvite( + const ChatInvite &invite); ConfirmInviteBox( not_null session, @@ -81,12 +91,14 @@ private: Fn _submit; object_ptr _title; + std::unique_ptr _badge; object_ptr _status; object_ptr _about; object_ptr _aboutRequests; std::shared_ptr _photo; std::unique_ptr _photoEmpty; std::vector _participants; + bool _isChannel = false; bool _requestApprove = false; diff --git a/Telegram/SourceFiles/api/api_report.cpp b/Telegram/SourceFiles/api/api_report.cpp index 501870180..869152737 100644 --- a/Telegram/SourceFiles/api/api_report.cpp +++ b/Telegram/SourceFiles/api/api_report.cpp @@ -78,12 +78,8 @@ void SendReport( MTP_string(comment) )).done(std::move(done)).send(); }, [&](StoryId id) { - const auto user = peer->asUser(); - if (!user) { - return; - } peer->session().api().request(MTPstories_Report( - user->inputUser, + peer->input, MTP_vector(1, MTP_int(id)), ReasonToTL(reason), MTP_string(comment) diff --git a/Telegram/SourceFiles/api/api_websites.cpp b/Telegram/SourceFiles/api/api_websites.cpp index 855056675..2ea6b2a09 100644 --- a/Telegram/SourceFiles/api/api_websites.cpp +++ b/Telegram/SourceFiles/api/api_websites.cpp @@ -17,11 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Api { namespace { -constexpr auto TestApiId = 17349; -constexpr auto SnapApiId = 611335; -constexpr auto DesktopApiId = 2040; - -Websites::Entry ParseEntry( +[[nodiscard]] Websites::Entry ParseEntry( not_null owner, const MTPDwebAuthorization &data) { auto result = Websites::Entry{ diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 4c551d0e9..8afded01f 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -100,8 +100,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/storage_media_prepare.h" #include "storage/storage_account.h" +// AyuGram includes #include "ayu/ayu_settings.h" + namespace { // Save draft to the cloud with 1 sec extra delay. @@ -791,7 +793,7 @@ QString ApiWrap::exportDirectStoryLink(not_null story) { ? i->second : fallback(); request(MTPstories_ExportStoryLink( - story->peer()->asUser()->inputUser, + story->peer()->input, MTP_int(story->id()) )).done([=](const MTPExportedStoryLink &result) { const auto link = qs(result.data().vlink()); @@ -2535,14 +2537,9 @@ void ApiWrap::refreshFileReference( }, [&](Data::FileOriginPremiumPreviews data) { request(MTPhelp_GetPremiumPromo()); }, [&](Data::FileOriginStory data) { - const auto user = _session->data().peer(data.peerId)->asUser(); - if (user) { - request(MTPstories_GetStoriesByID( - user->inputUser, - MTP_vector(1, MTP_int(data.storyId)))); - } else { - fail(); - } + request(MTPstories_GetStoriesByID( + _session->data().peer(data.peerId)->input, + MTP_vector(1, MTP_int(data.storyId)))); }, [&](v::null_t) { fail(); }); @@ -3322,25 +3319,37 @@ void ApiWrap::shareContact( const QString &phone, const QString &firstName, const QString &lastName, - const SendAction &action) { + const SendAction &action, + Fn done) { const auto userId = UserId(0); - sendSharedContact(phone, firstName, lastName, userId, action); + sendSharedContact( + phone, + firstName, + lastName, + userId, + action, + std::move(done)); } void ApiWrap::shareContact( not_null user, - const SendAction &action) { + const SendAction &action, + Fn done) { const auto userId = peerToUser(user->id); const auto phone = _session->data().findContactPhone(user); if (phone.isEmpty()) { + if (done) { + done(false); + } return; } - sendSharedContact( + return sendSharedContact( phone, user->firstName, user->lastName, userId, - action); + action, + std::move(done)); } void ApiWrap::sendSharedContact( @@ -3348,7 +3357,8 @@ void ApiWrap::sendSharedContact( const QString &firstName, const QString &lastName, UserId userId, - const SendAction &action) { + const SendAction &action, + Fn done) { sendAction(action); const auto history = action.history; @@ -3399,7 +3409,7 @@ void ApiWrap::sendSharedContact( MTP_string(firstName), MTP_string(lastName), MTP_string()); // vcard - sendMedia(item, media, action.options); + sendMedia(item, media, action.options, std::move(done)); _session->data().sendHistoryChangeNotifications(); _session->changes().historyUpdated( @@ -3949,18 +3959,20 @@ void ApiWrap::uploadAlbumMedia( void ApiWrap::sendMedia( not_null item, const MTPInputMedia &media, - Api::SendOptions options) { + Api::SendOptions options, + Fn done) { const auto randomId = base::RandomValue(); _session->data().registerMessageRandomId(randomId, item->fullId()); - sendMediaWithRandomId(item, media, options, randomId); + sendMediaWithRandomId(item, media, options, randomId, std::move(done)); } void ApiWrap::sendMediaWithRandomId( not_null item, const MTPInputMedia &media, Api::SendOptions options, - uint64 randomId) { + uint64 randomId, + Fn done) { // AyuGram useScheduledMessages const auto settings = &AyuSettings::getInstance(); if (settings->useScheduledMessages && !options.scheduled) @@ -3969,7 +3981,6 @@ void ApiWrap::sendMediaWithRandomId( auto current = base::unixtime::now(); options.scheduled = current + 12; } - const auto history = item->history(); const auto replyTo = item->replyTo(); @@ -4011,10 +4022,12 @@ void ApiWrap::sendMediaWithRandomId( MTP_int(options.scheduled), (options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()) ), [=](const MTPUpdates &result, const MTP::Response &response) { + if (done) done(true); if (updateRecentStickers) { requestRecentStickersForce(true); } }, [=](const MTP::Error &error, const MTP::Response &response) { + if (done) done(false); sendMessageFail(error, peer, randomId, itemId); }); } diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 954500f76..f61eb0957 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -294,8 +294,12 @@ public: const QString &phone, const QString &firstName, const QString &lastName, - const SendAction &action); - void shareContact(not_null user, const SendAction &action); + const SendAction &action, + Fn done = nullptr); + void shareContact( + not_null user, + const SendAction &action, + Fn done = nullptr); void applyAffectedMessages( not_null peer, const MTPmessages_AffectedMessages &result); @@ -489,7 +493,8 @@ private: const QString &firstName, const QString &lastName, UserId userId, - const SendAction &action); + const SendAction &action, + Fn done); void deleteHistory( not_null peer, @@ -516,12 +521,14 @@ private: void sendMedia( not_null item, const MTPInputMedia &media, - Api::SendOptions options); + Api::SendOptions options, + Fn done = nullptr); void sendMediaWithRandomId( not_null item, const MTPInputMedia &media, Api::SendOptions options, - uint64 randomId); + uint64 randomId, + Fn done = nullptr); FileLoadTo fileLoadTaskOptions(const SendAction &action) const; void getTopPromotionDelayed(TimeId now, TimeId next); diff --git a/Telegram/SourceFiles/boxes/add_contact_box.cpp b/Telegram/SourceFiles/boxes/add_contact_box.cpp index ccea61d70..aab01656d 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.cpp +++ b/Telegram/SourceFiles/boxes/add_contact_box.cpp @@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/toast/toast.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/fields/special_fields.h" #include "ui/widgets/popup_menu.h" #include "ui/text/format_values.h" @@ -297,8 +298,11 @@ void AddContactBox::prepare() { : tr::lng_enter_contact_data()); updateButtons(); - connect(_first, &Ui::InputField::submitted, [=] { submit(); }); - connect(_last, &Ui::InputField::submitted, [=] { submit(); }); + const auto submitted = [=] { submit(); }; + _first->submits( + ) | rpl::start_with_next(submitted, _first->lifetime()); + _last->submits( + ) | rpl::start_with_next(submitted, _last->lifetime()); connect(_phone, &Ui::PhoneInput::submitted, [=] { submit(); }); setDimensions( @@ -567,23 +571,24 @@ void GroupInfoBox::prepare() { _description->setSubmitSettings( Core::App().settings().sendSubmitWay()); - connect(_description, &Ui::InputField::resized, [=] { + _description->heightChanges( + ) | rpl::start_with_next([=] { descriptionResized(); - }); - connect(_description, &Ui::InputField::submitted, [=] { - submit(); - }); - connect(_description, &Ui::InputField::cancelled, [=] { + }, _description->lifetime()); + _description->submits( + ) | rpl::start_with_next([=] { submit(); }, _description->lifetime()); + _description->cancelled( + ) | rpl::start_with_next([=] { closeBox(); - }); + }, _description->lifetime()); Ui::Emoji::SuggestionsController::Init( getDelegate()->outerContainer(), _description, &_navigation->session()); } - - connect(_title, &Ui::InputField::submitted, [=] { submitName(); }); + _title->submits( + ) | rpl::start_with_next([=] { submitName(); }, _title->lifetime()); addButton( ((_type != Type::Group || _canAddBot) @@ -1522,20 +1527,22 @@ void EditNameBox::prepare() { _first->setMaxLength(Ui::EditPeer::kMaxUserFirstLastName); _last->setMaxLength(Ui::EditPeer::kMaxUserFirstLastName); - connect(_first, &Ui::InputField::submitted, [=] { submit(); }); - connect(_last, &Ui::InputField::submitted, [=] { submit(); }); + _first->submits( + ) | rpl::start_with_next([=] { submit(); }, _first->lifetime()); + _last->submits( + ) | rpl::start_with_next([=] { submit(); }, _last->lifetime()); _first->customTab(true); _last->customTab(true); - QObject::connect( - _first, - &Ui::InputField::tabbed, - [=] { _last->setFocus(); }); - QObject::connect( - _last, - &Ui::InputField::tabbed, - [=] { _first->setFocus(); }); + _first->tabbed( + ) | rpl::start_with_next([=] { + _last->setFocus(); + }, _first->lifetime()); + _last->tabbed( + ) | rpl::start_with_next([=] { + _first->setFocus(); + }, _last->lifetime()); } void EditNameBox::setInnerFocus() { diff --git a/Telegram/SourceFiles/boxes/choose_filter_box.cpp b/Telegram/SourceFiles/boxes/choose_filter_box.cpp index 12d58b538..bc61e9fe2 100644 --- a/Telegram/SourceFiles/boxes/choose_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/choose_filter_box.cpp @@ -32,27 +32,27 @@ Data::ChatFilter ChangedFilter( auto never = base::duplicate(filter.never()); if (add) { never.remove(history); - const auto result = Data::ChatFilter( - filter.id(), - filter.title(), - filter.iconEmoji(), - filter.flags(), - filter.always(), - filter.pinned(), - std::move(never)); - if (result.contains(history)) { - return result; - } else { - never = base::duplicate(result.never()); - always.insert(history); - } } else { - const auto alwaysIt = always.find(history); - if (alwaysIt != end(always)) { - always.erase(alwaysIt); - } else { - never.insert(history); - } + always.remove(history); + } + const auto result = Data::ChatFilter( + filter.id(), + filter.title(), + filter.iconEmoji(), + filter.flags(), + std::move(always), + filter.pinned(), + std::move(never)); + const auto in = result.contains(history); + if (in == add) { + return result; + } + always = base::duplicate(result.always()); + never = base::duplicate(result.never()); + if (add) { + always.insert(history); + } else { + never.insert(history); } return Data::ChatFilter( filter.id(), diff --git a/Telegram/SourceFiles/boxes/connection_box.cpp b/Telegram/SourceFiles/boxes/connection_box.cpp index 626671cb4..144582a53 100644 --- a/Telegram/SourceFiles/boxes/connection_box.cpp +++ b/Telegram/SourceFiles/boxes/connection_box.cpp @@ -18,7 +18,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mtproto/facade.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" +#include "ui/widgets/fields/number_input.h" +#include "ui/widgets/fields/password_input.h" #include "ui/widgets/labels.h" #include "ui/widgets/dropdown_menu.h" #include "ui/wrap/slide_wrap.h" diff --git a/Telegram/SourceFiles/boxes/create_poll_box.cpp b/Telegram/SourceFiles/boxes/create_poll_box.cpp index ca1eb70f3..3274e4eeb 100644 --- a/Telegram/SourceFiles/boxes/create_poll_box.cpp +++ b/Telegram/SourceFiles/boxes/create_poll_box.cpp @@ -13,7 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/vertical_layout.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/fade_wrap.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/shadow.h" #include "ui/widgets/labels.h" #include "ui/widgets/buttons.h" @@ -184,7 +184,8 @@ not_null CreateWarningLabel( QString(), st::createPollWarning); result->setAttribute(Qt::WA_TransparentForMouseEvents); - QObject::connect(field, &Ui::InputField::changed, [=] { + field->changes( + ) | rpl::start_with_next([=] { Ui::PostponeCall(crl::guard(field, [=] { const auto length = field->getLastText().size(); const auto value = valueLimit - length; @@ -198,7 +199,7 @@ not_null CreateWarningLabel( } result->setVisible(shown); })); - }); + }, field->lifetime()); return result; } @@ -243,13 +244,14 @@ Options::Option::Option( _content->resize(_content->width(), height); }, _field->lifetime()); - QObject::connect(_field, &Ui::InputField::changed, [=] { + _field->changes( + ) | rpl::start_with_next([=] { Ui::PostponeCall(crl::guard(_field, [=] { if (_hasCorrect) { _correct->toggle(isGood(), anim::type::normal); } })); - }); + }, _field->lifetime()); createShadow(); createRemove(); @@ -303,10 +305,11 @@ void Options::Option::createRemove() { const auto toggle = lifetime.make_state>(false); _removeAlways = lifetime.make_state>(false); - QObject::connect(field, &Ui::InputField::changed, [=] { + field->changes( + ) | rpl::start_with_next([field, toggle] { // Don't capture 'this'! Because Option is a value type. *toggle = !field->getLastText().isEmpty(); - }); + }, field->lifetime()); rpl::combine( toggle->value(), _removeAlways->value(), @@ -649,28 +652,32 @@ void Options::addEmptyOption() { _position + _list.size() + _destroyed.size(), _chooseCorrectGroup)); const auto field = _list.back()->field(); - QObject::connect(field, &Ui::InputField::submitted, [=] { + field->submits( + ) | rpl::start_with_next([=] { const auto index = findField(field); if (_list[index]->isGood() && index + 1 < _list.size()) { _list[index + 1]->setFocus(); } - }); - QObject::connect(field, &Ui::InputField::changed, [=] { + }, field->lifetime()); + field->changes( + ) | rpl::start_with_next([=] { Ui::PostponeCall(crl::guard(field, [=] { validateState(); })); - }); - QObject::connect(field, &Ui::InputField::focused, [=] { + }, field->lifetime()); + field->focusedChanges( + ) | rpl::filter(rpl::mappers::_1) | rpl::start_with_next([=] { _scrollToWidget.fire_copy(field); - }); - QObject::connect(field, &Ui::InputField::tabbed, [=] { + }, field->lifetime()); + field->tabbed( + ) | rpl::start_with_next([=] { const auto index = findField(field); if (index + 1 < _list.size()) { _list[index + 1]->setFocus(); } else { _tabbed.fire({}); } - }); + }, field->lifetime()); base::install_event_filter(field, [=](not_null event) { if (event->type() != QEvent::KeyPress || !field->getLastText().isEmpty()) { @@ -927,9 +934,10 @@ object_ptr CreatePollBox::setupContent() { st::boxDividerLabel), st::createPollLimitPadding)); - connect(question, &Ui::InputField::tabbed, [=] { + question->tabbed( + ) | rpl::start_with_next([=] { options->focusFirst(); - }); + }, question->lifetime()); AddSkip(container); AddSubsectionTitle(container, tr::lng_polls_create_settings()); @@ -975,9 +983,10 @@ object_ptr CreatePollBox::setupContent() { } }, question->lifetime()); - connect(solution, &Ui::InputField::tabbed, [=] { + solution->tabbed( + ) | rpl::start_with_next([=] { question->setFocus(); - }); + }, solution->lifetime()); quiz->setDisabled(_disabled & PollData::Flag::Quiz); if (multiple) { @@ -1009,12 +1018,12 @@ object_ptr CreatePollBox::setupContent() { const auto text = question->getLastText().trimmed(); return !text.isEmpty() && (text.size() <= kQuestionLimit); }; - - connect(question, &Ui::InputField::submitted, [=] { + question->submits( + ) | rpl::start_with_next([=] { if (isValidQuestion()) { options->focusFirst(); } - }); + }, question->lifetime()); _setInnerFocus = [=] { question->setFocusFast(); diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp index 4a2b53c5f..defc4bd64 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp @@ -54,7 +54,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/painter.h" #include "ui/ui_utility.h" #include "ui/widgets/checkbox.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/scroll_area.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" @@ -488,9 +488,16 @@ void EditCaptionBox::setupField() { Core::App().settings().sendSubmitWay()); _field->setMaxHeight(st::defaultComposeFiles.caption.heightMax); - connect(_field, &Ui::InputField::submitted, [=] { save(); }); - connect(_field, &Ui::InputField::cancelled, [=] { closeBox(); }); - connect(_field, &Ui::InputField::resized, [=] { captionResized(); }); + _field->submits( + ) | rpl::start_with_next([=] { save(); }, _field->lifetime()); + _field->cancelled( + ) | rpl::start_with_next([=] { + closeBox(); + }, _field->lifetime()); + _field->heightChanges( + ) | rpl::start_with_next([=] { + captionResized(); + }, _field->lifetime()); _field->setMimeDataHook([=]( not_null data, Ui::InputField::MimeAction action) { @@ -522,10 +529,11 @@ void EditCaptionBox::setInitialText() { setCloseByOutsideClick(true); } }); - connect(_field, &Ui::InputField::changed, [=] { + _field->changes( + ) | rpl::start_with_next([=] { _checkChangedTimer.callOnce(kChangesDebounceTimeout); setCloseByOutsideClick(false); - }); + }, _field->lifetime()); } void EditCaptionBox::setupControls() { diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp index 88bbdca03..0c3760831 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp @@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_utilities.h" #include "ui/text/text_options.h" #include "ui/widgets/buttons.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/wrap/slide_wrap.h" #include "ui/effects/panel_animation.h" #include "ui/filter_icons.h" @@ -619,11 +619,12 @@ void EditFilterBox( nameEditing->custom = true; }, box->lifetime()); - QObject::connect(name, &Ui::InputField::changed, [=] { + name->changes( + ) | rpl::start_with_next([=] { if (!nameEditing->settingDefault) { nameEditing->custom = true; } - }); + }, name->lifetime()); const auto updateDefaultTitle = [=](const Data::ChatFilter &filter) { if (nameEditing->custom) { return; diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp index d7e2c0920..557eefa9a 100644 --- a/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp +++ b/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp @@ -26,7 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/controls/invite_link_label.h" #include "ui/text/text_utilities.h" #include "ui/widgets/buttons.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/popup_menu.h" #include "ui/wrap/vertical_layout.h" #include "ui/wrap/slide_wrap.h" diff --git a/Telegram/SourceFiles/boxes/passcode_box.cpp b/Telegram/SourceFiles/boxes/passcode_box.cpp index 5ec269aac..dd165ac61 100644 --- a/Telegram/SourceFiles/boxes/passcode_box.cpp +++ b/Telegram/SourceFiles/boxes/passcode_box.cpp @@ -21,10 +21,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/layers/generic_box.h" #include "ui/text/text_utilities.h" #include "ui/widgets/buttons.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" +#include "ui/widgets/fields/password_input.h" #include "ui/widgets/labels.h" -#include "ui/widgets/sent_code_field.h" -#include "ui/wrap/vertical_layout.h" #include "ui/wrap/fade_wrap.h" #include "ui/painter.h" #include "passport/passport_encryption.h" @@ -306,15 +305,26 @@ void PasscodeBox::prepare() { connect(_oldPasscode, &Ui::MaskedInputField::changed, [=] { oldChanged(); }); connect(_newPasscode, &Ui::MaskedInputField::changed, [=] { newChanged(); }); connect(_reenterPasscode, &Ui::MaskedInputField::changed, [=] { newChanged(); }); - connect(_passwordHint, &Ui::InputField::changed, [=] { newChanged(); }); - connect(_recoverEmail, &Ui::InputField::changed, [=] { emailChanged(); }); + _passwordHint->changes( + ) | rpl::start_with_next([=] { + newChanged(); + }, _passwordHint->lifetime()); + _recoverEmail->changes( + ) | rpl::start_with_next([=] { + if (!_emailError.isEmpty()) { + _emailError = QString(); + update(); + } + }, _recoverEmail->lifetime()); const auto fieldSubmit = [=] { submit(); }; connect(_oldPasscode, &Ui::MaskedInputField::submitted, fieldSubmit); connect(_newPasscode, &Ui::MaskedInputField::submitted, fieldSubmit); connect(_reenterPasscode, &Ui::MaskedInputField::submitted, fieldSubmit); - connect(_passwordHint, &Ui::InputField::submitted, fieldSubmit); - connect(_recoverEmail, &Ui::InputField::submitted, fieldSubmit); + _passwordHint->submits( + ) | rpl::start_with_next(fieldSubmit, _passwordHint->lifetime()); + _recoverEmail->submits( + ) | rpl::start_with_next(fieldSubmit, _recoverEmail->lifetime()); _recover->addClickHandler([=] { recoverByEmail(); }); @@ -1061,13 +1071,6 @@ void PasscodeBox::newChanged() { } } -void PasscodeBox::emailChanged() { - if (!_emailError.isEmpty()) { - _emailError = QString(); - update(); - } -} - void PasscodeBox::recoverByEmail() { if (!_cloudFields.hasRecovery) { Assert(_session != nullptr); @@ -1189,8 +1192,12 @@ void RecoverBox::prepare() { + _recoverCode->height() + st::passcodeTextLine)); - connect(_recoverCode, &Ui::InputField::changed, [=] { codeChanged(); }); - connect(_recoverCode, &Ui::InputField::submitted, [=] { submit(); }); + _recoverCode->changes( + ) | rpl::start_with_next([=] { + codeChanged(); + }, _recoverCode->lifetime()); + _recoverCode->submits( + ) | rpl::start_with_next([=] { submit(); }, _recoverCode->lifetime()); } void RecoverBox::paintEvent(QPaintEvent *e) { diff --git a/Telegram/SourceFiles/boxes/passcode_box.h b/Telegram/SourceFiles/boxes/passcode_box.h index ee11765a2..75a235b16 100644 --- a/Telegram/SourceFiles/boxes/passcode_box.h +++ b/Telegram/SourceFiles/boxes/passcode_box.h @@ -90,7 +90,6 @@ private: void closeReplacedBy(); void oldChanged(); void newChanged(); - void emailChanged(); void save(bool force = false); void badOldPasscode(); void recoverByEmail(); diff --git a/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp index 0aa24c79e..19af19ac6 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp @@ -13,7 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/vertical_layout.h" #include "ui/widgets/labels.h" #include "ui/widgets/checkbox.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/text/format_values.h" // Ui::FormatPhone #include "ui/text/text_utilities.h" #include "info/profile/info_profile_cover.h" @@ -239,8 +239,8 @@ void Controller::initNameFields( _save(); } }; - QObject::connect(first, &Ui::InputField::submitted, submit); - QObject::connect(last, &Ui::InputField::submitted, submit); + first->submits() | rpl::start_with_next(submit, first->lifetime()); + last->submits() | rpl::start_with_next(submit, last->lifetime()); first->setMaxLength(Ui::EditPeer::kMaxUserFirstLastName); first->setMaxLength(Ui::EditPeer::kMaxUserFirstLastName); } diff --git a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp index d5dbc6789..5caa2ce5c 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/peers/edit_forum_topic_box.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/shadow.h" #include "ui/effects/emoji_fly_animation.h" #include "ui/abstract_button.h" @@ -465,15 +465,13 @@ void EditForumTopicBox( ChooseNextColorId(current.colorId, state->otherColorIds), }; }); - base::qt_signal_producer( - title, - &Ui::InputField::changed + title->changes( ) | rpl::start_with_next([=] { state->defaultIcon = DefaultIcon{ title->getLastText().trimmed(), state->defaultIcon.current().colorId, }; - }, box->lifetime()); + }, title->lifetime()); if (!topic || !topic->isGeneral()) { Settings::AddDividerText( diff --git a/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp index f47beae75..d867c0607 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp @@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/checkbox.h" #include "ui/widgets/labels.h" #include "ui/widgets/buttons.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/box_content_divider.h" #include "ui/layers/generic_box.h" #include "ui/toast/toast.h" @@ -384,8 +384,11 @@ void EditAdminBox::prepare() { if (!_saveCallback) { return; } else if (_addAsAdmin && !_addAsAdmin->checked()) { + const auto weak = Ui::MakeWeak(this); AddBotToGroup(user(), peer(), _addingBot->token); - getDelegate()->hideLayer(); + if (const auto strong = weak.data()) { + strong->closeBox(); + } return; } else if (_addingBot && !_addingBot->existing) { const auto phrase = peer()->isBroadcast() @@ -461,13 +464,14 @@ not_null EditAdminBox::addRankInput( st::rightsAboutMargin); result->setMaxLength(kAdminRoleLimit); result->setInstantReplaces(Ui::InstantReplaces::TextOnly()); - connect(result, &Ui::InputField::changed, [=] { + result->changes( + ) | rpl::start_with_next([=] { const auto text = result->getLastText(); const auto removed = TextUtilities::RemoveEmoji(text); if (removed != text) { result->setText(removed); } - }); + }, result->lifetime()); container->add( object_ptr( diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index 01251e1f8..3e95dcc1f 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -51,7 +51,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_utilities.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/labels.h" #include "ui/widgets/box_content_divider.h" #include "ui/wrap/padding_wrap.h" @@ -519,10 +519,10 @@ object_ptr Controller::createTitleEdit() { result->entity(), &_peer->session()); - QObject::connect( - result->entity(), - &Ui::InputField::submitted, - [=] { submitTitle(); }); + result->entity()->submits( + ) | rpl::start_with_next([=] { + submitTitle(); + }, result->entity()->lifetime()); _controls.title = result->entity(); return result; @@ -555,10 +555,10 @@ object_ptr Controller::createDescriptionEdit() { result->entity(), &_peer->session()); - QObject::connect( - result->entity(), - &Ui::InputField::submitted, - [=] { submitDescription(); }); + result->entity()->submits( + ) | rpl::start_with_next([=] { + submitDescription(); + }, result->entity()->lifetime()); _controls.description = result->entity(); return result; diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp index 97a86be3b..755368192 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp @@ -51,13 +51,14 @@ constexpr auto kForceDisableTooltipDuration = 3 * crl::time(1000); } [[nodiscard]] auto NestedRestrictionLabelsList( - Data::RestrictionsSetOptions options) { + Data::RestrictionsSetOptions options) +-> std::vector> { using Flag = ChatRestriction; auto first = std::vector{ { Flag::SendOther, tr::lng_rights_chat_send_text(tr::now) }, }; - auto inner = std::vector{ + auto media = std::vector{ { Flag::SendPhotos, tr::lng_rights_chat_photos(tr::now) }, { Flag::SendVideos, tr::lng_rights_chat_videos(tr::now) }, { Flag::SendVideoMessages, tr::lng_rights_chat_video_messages(tr::now) }, @@ -85,9 +86,64 @@ constexpr auto kForceDisableTooltipDuration = 3 * crl::time(1000); &RestrictionLabel::flags), end(second)); } - return std::vector>{ + return { { std::nullopt, std::move(first) }, - { tr::lng_rights_chat_send_media(), std::move(inner) }, + { tr::lng_rights_chat_send_media(), std::move(media) }, + { std::nullopt, std::move(second) }, + }; +} + +[[nodiscard]] auto NestedAdminRightLabels( + Data::AdminRightsSetOptions options) +-> std::vector> { + using Flag = ChatAdminRight; + + if (options.isGroup) { + auto result = std::vector{ + { Flag::ChangeInfo, tr::lng_rights_group_info(tr::now) }, + { Flag::DeleteMessages, tr::lng_rights_group_delete(tr::now) }, + { Flag::BanUsers, tr::lng_rights_group_ban(tr::now) }, + { Flag::InviteByLinkOrAdd, options.anyoneCanAddMembers + ? tr::lng_rights_group_invite_link(tr::now) + : tr::lng_rights_group_invite(tr::now) }, + { Flag::ManageTopics, tr::lng_rights_group_topics(tr::now) }, + { Flag::PinMessages, tr::lng_rights_group_pin(tr::now) }, + { Flag::ManageCall, tr::lng_rights_group_manage_calls(tr::now) }, + { Flag::Anonymous, tr::lng_rights_group_anonymous(tr::now) }, + { Flag::AddAdmins, tr::lng_rights_add_admins(tr::now) }, + }; + if (!options.isForum) { + result.erase( + ranges::remove( + result, + Flag::ManageTopics | Flag(), + &AdminRightLabel::flags), + end(result)); + } + return { { std::nullopt, std::move(result) } }; + } + auto first = std::vector{ + { Flag::ChangeInfo, tr::lng_rights_channel_info(tr::now) }, + }; + auto messages = std::vector{ + { Flag::PostMessages, tr::lng_rights_channel_post(tr::now) }, + { Flag::EditMessages, tr::lng_rights_channel_edit(tr::now) }, + { Flag::DeleteMessages, tr::lng_rights_channel_delete(tr::now) }, + }; + auto stories = std::vector{ + { Flag::PostStories, tr::lng_rights_channel_post_stories(tr::now) }, + { Flag::EditStories, tr::lng_rights_channel_edit_stories(tr::now) }, + { Flag::DeleteStories, tr::lng_rights_channel_delete_stories(tr::now) }, + }; + auto second = std::vector{ + { Flag::InviteByLinkOrAdd, tr::lng_rights_group_invite(tr::now) }, + { Flag::ManageCall, tr::lng_rights_channel_manage_calls(tr::now) }, + { Flag::AddAdmins, tr::lng_rights_add_admins(tr::now) }, + }; + return { + { std::nullopt, std::move(first) }, + { tr::lng_rights_channel_manage(), std::move(messages) }, + { tr::lng_rights_channel_manage_stories(), std::move(stories) }, { std::nullopt, std::move(second) }, }; } @@ -1031,42 +1087,11 @@ std::vector RestrictionLabels( std::vector AdminRightLabels( Data::AdminRightsSetOptions options) { - using Flag = ChatAdminRight; - - if (options.isGroup) { - auto result = std::vector{ - { Flag::ChangeInfo, tr::lng_rights_group_info(tr::now) }, - { Flag::DeleteMessages, tr::lng_rights_group_delete(tr::now) }, - { Flag::BanUsers, tr::lng_rights_group_ban(tr::now) }, - { Flag::InviteByLinkOrAdd, options.anyoneCanAddMembers - ? tr::lng_rights_group_invite_link(tr::now) - : tr::lng_rights_group_invite(tr::now) }, - { Flag::ManageTopics, tr::lng_rights_group_topics(tr::now) }, - { Flag::PinMessages, tr::lng_rights_group_pin(tr::now) }, - { Flag::ManageCall, tr::lng_rights_group_manage_calls(tr::now) }, - { Flag::Anonymous, tr::lng_rights_group_anonymous(tr::now) }, - { Flag::AddAdmins, tr::lng_rights_add_admins(tr::now) }, - }; - if (!options.isForum) { - result.erase( - ranges::remove( - result, - Flag::ManageTopics | Flag(), - &AdminRightLabel::flags), - end(result)); - } - return result; - } else { - return { - { Flag::ChangeInfo, tr::lng_rights_channel_info(tr::now) }, - { Flag::PostMessages, tr::lng_rights_channel_post(tr::now) }, - { Flag::EditMessages, tr::lng_rights_channel_edit(tr::now) }, - { Flag::DeleteMessages, tr::lng_rights_channel_delete(tr::now) }, - { Flag::InviteByLinkOrAdd, tr::lng_rights_group_invite(tr::now) }, - { Flag::ManageCall, tr::lng_rights_channel_manage_calls(tr::now) }, - { Flag::AddAdmins, tr::lng_rights_add_admins(tr::now) } - }; + auto result = std::vector(); + for (const auto &[_, r] : NestedAdminRightLabels(options)) { + result.insert(result.end(), r.begin(), r.end()); } + return result; } EditFlagsControl CreateEditRestrictions( @@ -1107,7 +1132,7 @@ EditFlagsControl CreateEditAdminRights( rights, { .header = std::move(header), - .labels = { { std::nullopt, AdminRightLabels(options) } }, + .labels = NestedAdminRightLabels(options), .disabledMessages = std::move(disabledMessages), }); result.widget = std::move(widget); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp index 2739eb125..bae0be7fe 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp @@ -32,7 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/controls/userpic_button.h" #include "ui/widgets/buttons.h" #include "ui/widgets/checkbox.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/labels.h" #include "ui/widgets/box_content_divider.h" #include "ui/wrap/padding_wrap.h" diff --git a/Telegram/SourceFiles/boxes/premium_limits_box.cpp b/Telegram/SourceFiles/boxes/premium_limits_box.cpp index 8980a9028..928915afd 100644 --- a/Telegram/SourceFiles/boxes/premium_limits_box.cpp +++ b/Telegram/SourceFiles/boxes/premium_limits_box.cpp @@ -48,19 +48,6 @@ struct InfographicDescriptor { bool complexRatio = false; }; -[[nodiscard]] rpl::producer<> BoxShowFinishes(not_null box) { - const auto singleShot = box->lifetime().make_state(); - const auto showFinishes = singleShot->make_state>(); - - box->setShowFinishedCallback([=] { - showFinishes->fire({}); - singleShot->destroy(); - box->setShowFinishedCallback(nullptr); - }); - - return showFinishes->events(); -} - void AddSubsectionTitle( not_null container, rpl::producer text) { @@ -423,8 +410,9 @@ void SimpleLimitBox( Settings::AddSkip(top, st::premiumInfographicPadding.top()); Ui::Premium::AddBubbleRow( top, + st::defaultPremiumBubble, BoxShowFinishes(box), - descriptor.defaultLimit, + 0, descriptor.current, descriptor.premiumLimit, premiumPossible, @@ -783,16 +771,18 @@ void FilterLinksLimitBox( void FiltersLimitBox( not_null box, - not_null session) { + not_null session, + std::optional filtersCountOverride) { const auto premium = session->premium(); const auto premiumPossible = session->premiumPossible(); const auto limits = Data::PremiumLimits(session); const auto defaultLimit = float64(limits.dialogFiltersDefault()); const auto premiumLimit = float64(limits.dialogFiltersPremium()); - const auto current = float64(ranges::count_if( + const auto cloud = int(ranges::count_if( session->data().chatsFilters().list(), [](const Data::ChatFilter &f) { return f.id() != FilterId(); })); + const auto current = float64(filtersCountOverride.value_or(cloud)); auto text = rpl::combine( tr::lng_filters_limit1( @@ -1092,6 +1082,7 @@ void AccountsLimitBox( Settings::AddSkip(top, st::premiumInfographicPadding.top()); Ui::Premium::AddBubbleRow( top, + st::defaultPremiumBubble, BoxShowFinishes(box), 0, current, diff --git a/Telegram/SourceFiles/boxes/premium_limits_box.h b/Telegram/SourceFiles/boxes/premium_limits_box.h index 7bcda6e53..5a4fa8a0a 100644 --- a/Telegram/SourceFiles/boxes/premium_limits_box.h +++ b/Telegram/SourceFiles/boxes/premium_limits_box.h @@ -42,7 +42,8 @@ void FilterLinksLimitBox( not_null session); void FiltersLimitBox( not_null box, - not_null session); + not_null session, + std::optional filtersCountOverride); void ShareableFiltersLimitBox( not_null box, not_null session); diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index 50f8886d9..025b4082c 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -31,7 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/scroll_content_shadow.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/popup_menu.h" #include "ui/wrap/vertical_layout.h" @@ -1033,17 +1033,21 @@ void SendFilesBox::setupCaption() { Core::App().settings().sendSubmitWay()); _caption->setMaxLength(kMaxMessageLength); - connect(_caption, &Ui::InputField::resized, [=] { + _caption->heightChanges( + ) | rpl::start_with_next([=] { captionResized(); - }); - connect(_caption, &Ui::InputField::submitted, [=]( - Qt::KeyboardModifiers modifiers) { + }, _caption->lifetime()); + _caption->submits( + ) | rpl::start_with_next([=](Qt::KeyboardModifiers modifiers) { const auto ctrlShiftEnter = modifiers.testFlag(Qt::ShiftModifier) && (modifiers.testFlag(Qt::ControlModifier) || modifiers.testFlag(Qt::MetaModifier)); send({}, ctrlShiftEnter); - }); - connect(_caption, &Ui::InputField::cancelled, [=] { closeBox(); }); + }, _caption->lifetime()); + _caption->cancelled( + ) | rpl::start_with_next([=] { + closeBox(); + }, _caption->lifetime()); _caption->setMimeDataHook([=]( not_null data, Ui::InputField::MimeAction action) { diff --git a/Telegram/SourceFiles/boxes/sessions_box.cpp b/Telegram/SourceFiles/boxes/sessions_box.cpp index e30174be6..d458111f2 100644 --- a/Telegram/SourceFiles/boxes/sessions_box.cpp +++ b/Telegram/SourceFiles/boxes/sessions_box.cpp @@ -19,7 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "main/main_session.h" #include "ui/widgets/buttons.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/labels.h" #include "ui/widgets/scroll_area.h" #include "ui/wrap/slide_wrap.h" @@ -144,7 +144,7 @@ void RenameBox(not_null box) { Core::App().settings().setCustomDeviceModel(result); Core::App().saveSettingsDelayed(); }; - QObject::connect(name, &Ui::InputField::submitted, submit); + name->submits() | rpl::start_with_next(submit, name->lifetime()); box->addButton(tr::lng_settings_save(), submit); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); } diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index 154961016..ad992b525 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -19,7 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/checkbox.h" #include "ui/widgets/multi_select.h" #include "ui/widgets/scroll_area.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/popup_menu.h" #include "ui/wrap/slide_wrap.h" #include "ui/text/text_options.h" @@ -226,9 +226,8 @@ void ShareBox::prepareCommentField() { const auto field = _comment->entity(); - connect(field, &Ui::InputField::submitted, [=] { - submit({}); - }); + field->submits( + ) | rpl::start_with_next([=] { submit({}); }, field->lifetime()); if (const auto show = uiShow(); show->valid()) { InitMessageFieldHandlers( _descriptor.session, @@ -249,6 +248,8 @@ void ShareBox::prepareCommentField() { void ShareBox::prepare() { prepareCommentField(); + setCloseByOutsideClick(false); + _select->resizeToWidth(st::boxWideWidth); Ui::SendPendingMoveResizeEvents(_select); diff --git a/Telegram/SourceFiles/boxes/stickers_box.cpp b/Telegram/SourceFiles/boxes/stickers_box.cpp index b1eef8d91..4f8c682a0 100644 --- a/Telegram/SourceFiles/boxes/stickers_box.cpp +++ b/Telegram/SourceFiles/boxes/stickers_box.cpp @@ -31,7 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/ripple_animation.h" #include "ui/effects/slide_animation.h" #include "ui/widgets/discrete_sliders.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/image/image.h" #include "ui/cached_round_corners.h" #include "ui/painter.h" diff --git a/Telegram/SourceFiles/calls/group/calls_group_menu.cpp b/Telegram/SourceFiles/calls/group/calls_group_menu.cpp index aad072371..51a9068a7 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_menu.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_menu.cpp @@ -19,7 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/menu/menu_action.h" #include "ui/widgets/labels.h" #include "ui/widgets/checkbox.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/effects/ripple_animation.h" #include "ui/layers/generic_box.h" #include "ui/painter.h" diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 210eb0dce..6002963a9 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -23,7 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/call_button.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/dropdown_menu.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/tooltip.h" #include "ui/widgets/rp_window.h" #include "ui/chat/group_call_bar.h" diff --git a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp index 59ed26a62..121d6d037 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp @@ -16,7 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/continuous_sliders.h" #include "ui/widgets/buttons.h" #include "ui/widgets/checkbox.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/popup_menu.h" #include "ui/wrap/slide_wrap.h" #include "ui/text/text_utilities.h" diff --git a/Telegram/SourceFiles/calls/group/ui/calls_group_recording_box.cpp b/Telegram/SourceFiles/calls/group/ui/calls_group_recording_box.cpp index 5d084bb37..30a492a3f 100644 --- a/Telegram/SourceFiles/calls/group/ui/calls_group_recording_box.cpp +++ b/Telegram/SourceFiles/calls/group/ui/calls_group_recording_box.cpp @@ -12,7 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/image/image_prepare.h" #include "ui/layers/generic_box.h" #include "ui/widgets/checkbox.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/labels.h" #include "styles/style_calls.h" #include "styles/style_layers.h" @@ -287,7 +287,7 @@ void EditGroupCallTitleBox( box->closeBox(); done(result); }; - QObject::connect(input, &Ui::InputField::submitted, submit); + input->submits() | rpl::start_with_next(submit, input->lifetime()); box->addButton(tr::lng_settings_save(), submit); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); } @@ -346,7 +346,7 @@ void AddTitleGroupCallRecordingBox( box->closeBox(); done(result); }; - QObject::connect(input, &Ui::InputField::submitted, submit); + input->submits() | rpl::start_with_next(submit, input->lifetime()); box->addButton(tr::lng_group_call_recording_start_button(), submit); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); } diff --git a/Telegram/SourceFiles/chat_helpers/emoji_sets_manager.cpp b/Telegram/SourceFiles/chat_helpers/emoji_sets_manager.cpp index cc70419d5..7c6ae96c8 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_sets_manager.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_sets_manager.cpp @@ -41,9 +41,9 @@ inline auto PreviewPath(int i) { const auto kSets = { Set{ { 0, 0, 0, "Mac" }, PreviewPath(0) }, - Set{ { 1, 1392, 8'184'590, "Android" }, PreviewPath(1) }, - Set{ { 2, 1393, 5'413'219, "Twemoji" }, PreviewPath(2) }, - Set{ { 3, 1394, 6'967'218, "JoyPixels" }, PreviewPath(3) }, + Set{ { 1, 1804, 8'115'639, "Android" }, PreviewPath(1) }, + Set{ { 2, 1805, 5'481'197, "Twemoji" }, PreviewPath(2) }, + Set{ { 3, 1806, 7'047'594, "JoyPixels" }, PreviewPath(3) }, }; using Loading = MTP::DedicatedLoader::Progress; diff --git a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp index a97d1fe42..f6c54ec6d 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp @@ -14,7 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/ripple_animation.h" #include "ui/widgets/shadow.h" #include "ui/widgets/inner_dropdown.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/emoji_config.h" #include "ui/ui_utility.h" #include "ui/cached_round_corners.h" diff --git a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp index 8f5f5fec5..2c7bf803c 100644 --- a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp +++ b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp @@ -31,7 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/clip/media_clip_reader.h" #include "ui/widgets/popup_menu.h" #include "ui/widgets/scroll_area.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/text/text_options.h" #include "ui/image/image.h" #include "ui/effects/path_shift_gradient.h" diff --git a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp index a5765a031..a334d8d81 100644 --- a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp @@ -24,7 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/click_handler_types.h" #include "ui/controls/tabbed_search.h" #include "ui/widgets/buttons.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/popup_menu.h" #include "ui/effects/ripple_animation.h" #include "ui/image/image.h" diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index 304c7d3a0..a9c0fd789 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -190,16 +190,18 @@ void EditLinkBox( } }; - QObject::connect(text, &Ui::InputField::submitted, [=] { + text->submits( + ) | rpl::start_with_next([=] { url->setFocusFast(); - }); - QObject::connect(url, &Ui::InputField::submitted, [=] { + }, text->lifetime()); + url->submits( + ) | rpl::start_with_next([=] { if (text->getLastText().isEmpty()) { text->setFocusFast(); } else { submit(); } - }); + }, url->lifetime()); box->setTitle(url->getLastText().isEmpty() ? tr::lng_formatting_link_create_title() @@ -223,8 +225,14 @@ void EditLinkBox( url->customTab(true); text->customTab(true); - QObject::connect(url, &Ui::InputField::tabbed, [=] { text->setFocus(); }); - QObject::connect(text, &Ui::InputField::tabbed, [=] { url->setFocus(); }); + url->tabbed( + ) | rpl::start_with_next([=] { + text->setFocus(); + }, url->lifetime()); + text->tabbed( + ) | rpl::start_with_next([=] { + url->setFocus(); + }, text->lifetime()); } TextWithEntities StripSupportHashtag(TextWithEntities text) { @@ -590,7 +598,8 @@ AutocompleteQuery ParseMentionHashtagBotCommandQuery( MessageLinksParser::MessageLinksParser(not_null field) : _field(field) , _timer([=] { parse(); }) { - _connection = QObject::connect(_field, &Ui::InputField::changed, [=] { + _lifetime = _field->changes( + ) | rpl::start_with_next([=] { const auto length = _field->getTextWithTags().text.size(); const auto timeout = (std::abs(length - _lastLength) > 2) ? 0 diff --git a/Telegram/SourceFiles/chat_helpers/message_field.h b/Telegram/SourceFiles/chat_helpers/message_field.h index acc2808a1..9c4ba031f 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.h +++ b/Telegram/SourceFiles/chat_helpers/message_field.h @@ -7,9 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "base/timer.h" -#include "base/qt_connection.h" #include "chat_helpers/compose/compose_features.h" #ifndef TDESKTOP_DISABLE_SPELLCHECK @@ -132,7 +131,7 @@ private: int _lastLength = 0; bool _disabled = false; base::Timer _timer; - base::qt_connection _connection; + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp index 15e2e8c34..0c09b6dd3 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp @@ -26,7 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lottie/lottie_single_player.h" #include "ui/dpr/dpr_icon.h" #include "ui/dpr/dpr_image.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/buttons.h" #include "ui/painter.h" #include "ui/rect_part.h" diff --git a/Telegram/SourceFiles/core/changelogs.cpp b/Telegram/SourceFiles/core/changelogs.cpp index 957b83f18..09810abfd 100644 --- a/Telegram/SourceFiles/core/changelogs.cpp +++ b/Telegram/SourceFiles/core/changelogs.cpp @@ -22,110 +22,6 @@ namespace { std::map BetaLogs() { return { - { - 4005004, - "- Allow wide range of interface scale options.\n" - - "- Show opened chat name in the window title.\n" - - "- Bug fixes and other minor improvements.\n" - - "- Fix updating on macOS older than 10.14.\n" - }, - { - 4005006, - "- Try enabling non-fractional scale " - "High DPI support on Windows and Linux.\n" - - "- Experimental setting for fractional scale " - "High DPI support on Windows and Linux.\n" - - "- Fix navigation to bottom problems in groups you didn't join.\n" - - "- Fix a crash in chat export settings changes.\n" - - "- Fix a crash in sending some of JPEG images.\n" - - "- Fix CJK fonts on Windows.\n" - }, - { - 4005007, - "- Fix glitches after moving window to another screen.\n", - }, - { - 4005008, - "- Allow opening another account in a new window " - "(see Settings > Advanced > Experimental Settings).\n" - - "- A lot of bugfixes for working with more than one window.\n" - }, - { - 4006004, - "- Allow media viewer to exit fullscreen and become a normal window." - }, - { - 4006006, - "- Confirmation window before starting a call.\n" - - "- New \"Battery and Animations\" settings section.\n" - - "- \"Save Power on Low Battery\" option for laptops.\n" - - "- Improved windowed mode support for media viewer.\n" - - "- Hardware accelerated video playback fix on macOS.\n" - - "- New application icon on macOS following the system guidelines.\n" - }, - { - 4006007, - "- Fix crash when accepting incoming calls.\n" - - "- Remove sound when cancelling an unconfirmed call.\n" - }, - { - 4006008, - "- Improve quality of voice messages with changed playback speed.\n" - - "- Show when your message was read in small groups.\n" - - "- Fix pasting images from Firefox on Windows.\n" - - "- Improve memory usage for custom emoji.\n" - }, - { - 4006010, - "- Suggest sending an invite link if user forbids " - "inviting him to groups.\n" - - "- Show when a reaction was left on your message in small groups.\n" - - "- Fix a crash in video chats on Windows.\n" - - "- Fix a crash in audio speed change.\n" - }, - { - 4006011, - "- Allow larger interface scale values on high-dpi screens.\n" - - "- Implement new voice and video speed change interface (up to 2.5x).\n" - - "- Support global Fn+F shortcut to toggle fullscreen on macOS.\n" - - "- Silent notification sound in Focus Mode on macOS.\n" - - "- Fix media viewer on macOS with several screens.\n" - - "- Fix a crash in connection type box.\n" - - "- Fix possible crash on quit.\n" - }, - { - 4006012, - "- Fix several possible crashes.\n" - - "- Deprecate macOS 10.12, Ubuntu 18.04 and CentOS 7 in July.\n" - }, { 4008011, "- Fix initial video playback speed.\n" diff --git a/Telegram/SourceFiles/core/core_settings.cpp b/Telegram/SourceFiles/core/core_settings.cpp index c21a206e9..abefbd3b4 100644 --- a/Telegram/SourceFiles/core/core_settings.cpp +++ b/Telegram/SourceFiles/core/core_settings.cpp @@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/send_files_box.h" #include "history/view/history_view_quick_action.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "storage/serialize_common.h" #include "window/section_widget.h" #include "base/platform/base_platform_info.h" diff --git a/Telegram/SourceFiles/core/crash_reports.cpp b/Telegram/SourceFiles/core/crash_reports.cpp index 24e22982c..4c1a4f50e 100644 --- a/Telegram/SourceFiles/core/crash_reports.cpp +++ b/Telegram/SourceFiles/core/crash_reports.cpp @@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include -#ifndef DESKTOP_APP_DISABLE_CRASH_REPORTS +#ifndef TDESKTOP_DISABLE_CRASH_REPORTS #ifdef Q_OS_WIN #include @@ -25,7 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #pragma warning(pop) -#elif defined Q_OS_UNIX // Q_OS_WIN +#else // Q_OS_WIN #include #include @@ -48,7 +48,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #endif // Q_OS_MAC #endif // Q_OS_WIN -#endif // !DESKTOP_APP_DISABLE_CRASH_REPORTS +#endif // !TDESKTOP_DISABLE_CRASH_REPORTS namespace CrashReports { namespace { @@ -59,7 +59,7 @@ using AnnotationRefs = std::map; Annotations ProcessAnnotations; AnnotationRefs ProcessAnnotationRefs; -#ifndef DESKTOP_APP_DISABLE_CRASH_REPORTS +#ifndef TDESKTOP_DISABLE_CRASH_REPORTS QString ReportPath; FILE *ReportFile = nullptr; @@ -197,13 +197,15 @@ const int HandledSignals[] = { SIGABRT, SIGFPE, SIGILL, -#ifdef Q_OS_UNIX +#ifndef Q_OS_WIN SIGBUS, SIGTRAP, -#endif // Q_OS_UNIX +#endif // !Q_OS_WIN }; -#ifdef Q_OS_UNIX +#ifdef Q_OS_WIN +void SignalHandler(int signum) { +#else // Q_OS_WIN struct sigaction OldSigActions[32]/* = { 0 }*/; void RestoreSignalHandlers() { @@ -229,9 +231,7 @@ void InvokeOldSignalHandler(int signum, siginfo_t *info, void *ucontext) { void SignalHandler(int signum, siginfo_t *info, void *ucontext) { RestoreSignalHandlers(); -#else // Q_OS_UNIX -void SignalHandler(int signum) { -#endif // else for Q_OS_UNIX +#endif // else for Q_OS_WIN const char* name = 0; switch (signum) { @@ -253,9 +253,9 @@ void SignalHandler(int signum) { ReportingThreadId = nullptr; } -#ifdef Q_OS_UNIX +#ifndef Q_OS_WIN InvokeOldSignalHandler(signum, info, ucontext); -#endif // Q_OS_UNIX +#endif // !Q_OS_WIN } bool SetSignalHandlers = true; @@ -267,9 +267,9 @@ google_breakpad::ExceptionHandler* BreakpadExceptionHandler = 0; bool DumpCallback(const wchar_t* _dump_dir, const wchar_t* _minidump_id, void* context, EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion, bool success) #elif defined Q_OS_MAC // Q_OS_WIN bool DumpCallback(const char* _dump_dir, const char* _minidump_id, void *context, bool success) -#elif defined Q_OS_UNIX // Q_OS_MAC +#else // Q_OS_MAC bool DumpCallback(const google_breakpad::MinidumpDescriptor &md, void *context, bool success) -#endif // Q_OS_UNIX +#endif // else for Q_OS_WIN || Q_OS_MAC { if (CrashLogged) return success; CrashLogged = true; @@ -290,7 +290,7 @@ bool DumpCallback(const google_breakpad::MinidumpDescriptor &md, void *context, } #endif // !Q_OS_MAC || MAC_USE_BREAKPAD -#endif // !DESKTOP_APP_DISABLE_CRASH_REPORTS +#endif // !TDESKTOP_DISABLE_CRASH_REPORTS } // namespace @@ -314,7 +314,7 @@ QString PlatformString() { } void StartCatching() { -#ifndef DESKTOP_APP_DISABLE_CRASH_REPORTS +#ifndef TDESKTOP_DISABLE_CRASH_REPORTS ProcessAnnotations["Binary"] = cExeName().toUtf8().constData(); ProcessAnnotations["ApiId"] = QString::number(ApiId).toUtf8().constData(); ProcessAnnotations["Version"] = (cAlphaVersion() @@ -370,7 +370,7 @@ void StartCatching() { false)) { // asynchronous_start } #endif // else for MAC_USE_BREAKPAD -#elif defined Q_OS_UNIX +#else BreakpadExceptionHandler = new google_breakpad::ExceptionHandler( google_breakpad::MinidumpDescriptor(QFile::encodeName(dumpspath).toStdString()), /*FilterCallback*/ 0, @@ -379,22 +379,22 @@ void StartCatching() { true, -1 ); -#endif // Q_OS_UNIX -#endif // !DESKTOP_APP_DISABLE_CRASH_REPORTS +#endif // else for Q_OS_WIN || Q_OS_MAC +#endif // !TDESKTOP_DISABLE_CRASH_REPORTS } void FinishCatching() { -#ifndef DESKTOP_APP_DISABLE_CRASH_REPORTS +#ifndef TDESKTOP_DISABLE_CRASH_REPORTS #if !defined Q_OS_MAC || defined MAC_USE_BREAKPAD delete base::take(BreakpadExceptionHandler); #endif // !Q_OS_MAC || MAC_USE_BREAKPAD -#endif // !DESKTOP_APP_DISABLE_CRASH_REPORTS +#endif // !TDESKTOP_DISABLE_CRASH_REPORTS } StartResult Start() { -#ifndef DESKTOP_APP_DISABLE_CRASH_REPORTS +#ifndef TDESKTOP_DISABLE_CRASH_REPORTS ReportPath = cWorkingDir() + u"tdata/working"_q; #ifdef Q_OS_WIN @@ -420,12 +420,12 @@ StartResult Start() { return lastdump; } -#endif // !DESKTOP_APP_DISABLE_CRASH_REPORTS +#endif // !TDESKTOP_DISABLE_CRASH_REPORTS return Restart(); } Status Restart() { -#ifndef DESKTOP_APP_DISABLE_CRASH_REPORTS +#ifndef TDESKTOP_DISABLE_CRASH_REPORTS if (ReportFile) { return Started; } @@ -470,13 +470,13 @@ Status Restart() { LOG(("FATAL: Could not open '%1' for writing!").arg(ReportPath)); return CantOpen; -#else // !DESKTOP_APP_DISABLE_CRASH_REPORTS +#else // !TDESKTOP_DISABLE_CRASH_REPORTS return Started; -#endif // else for !DESKTOP_APP_DISABLE_CRASH_REPORTS +#endif // else for !TDESKTOP_DISABLE_CRASH_REPORTS } void Finish() { -#ifndef DESKTOP_APP_DISABLE_CRASH_REPORTS +#ifndef TDESKTOP_DISABLE_CRASH_REPORTS FinishCatching(); if (ReportFile) { @@ -489,7 +489,7 @@ void Finish() { unlink(ReportPath.toUtf8().constData()); #endif // else for Q_OS_WIN } -#endif // !DESKTOP_APP_DISABLE_CRASH_REPORTS +#endif // !TDESKTOP_DISABLE_CRASH_REPORTS } void SetAnnotation(const std::string &key, const QString &value) { @@ -539,7 +539,7 @@ void SetAnnotationRef(const std::string &key, const QString *valuePtr) { } } -#ifndef DESKTOP_APP_DISABLE_CRASH_REPORTS +#ifndef TDESKTOP_DISABLE_CRASH_REPORTS dump::~dump() { if (ReportFile) { @@ -604,6 +604,6 @@ const dump &operator<<(const dump &stream, double num) { return stream; } -#endif // DESKTOP_APP_DISABLE_CRASH_REPORTS +#endif // TDESKTOP_DISABLE_CRASH_REPORTS } // namespace CrashReports diff --git a/Telegram/SourceFiles/core/crash_reports.h b/Telegram/SourceFiles/core/crash_reports.h index 6a1f0e1d4..bfe09e240 100644 --- a/Telegram/SourceFiles/core/crash_reports.h +++ b/Telegram/SourceFiles/core/crash_reports.h @@ -11,7 +11,7 @@ namespace CrashReports { QString PlatformString(); -#ifndef DESKTOP_APP_DISABLE_CRASH_REPORTS +#ifndef TDESKTOP_DISABLE_CRASH_REPORTS struct dump { ~dump(); @@ -24,7 +24,7 @@ const dump &operator<<(const dump &stream, unsigned long num); const dump &operator<<(const dump &stream, unsigned long long num); const dump &operator<<(const dump &stream, double num); -#endif // DESKTOP_APP_DISABLE_CRASH_REPORTS +#endif // TDESKTOP_DISABLE_CRASH_REPORTS enum Status { CantOpen, diff --git a/Telegram/SourceFiles/core/deadlock_detector.h b/Telegram/SourceFiles/core/deadlock_detector.h new file mode 100644 index 000000000..fb538cf62 --- /dev/null +++ b/Telegram/SourceFiles/core/deadlock_detector.h @@ -0,0 +1,83 @@ +/* +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::DeadlockDetector { + +class PingPongEvent : public QEvent { +public: + static auto Type() { + static const auto Result = QEvent::Type(QEvent::registerEventType()); + return Result; + } + + PingPongEvent(not_null sender) + : QEvent(Type()) + , _sender(sender) { + } + + [[nodiscard]] not_null sender() const { + return _sender; + } + +private: + not_null _sender; + +}; + +class Pinger : public QObject { +public: + Pinger(not_null receiver) + : _receiver(receiver) + , _abortTimer([] { Unexpected("Deadlock found!"); }) { + const auto callback = [=] { + QCoreApplication::postEvent(_receiver, new PingPongEvent(this)); + _abortTimer.callOnce(30000); + }; + _pingTimer.setCallback(callback); + _pingTimer.callEach(60000); + callback(); + } + +protected: + bool event(QEvent *e) override { + if (e->type() == PingPongEvent::Type() + && static_cast(e)->sender() == _receiver) { + _abortTimer.cancel(); + } + return QObject::event(e); + } + +private: + not_null _receiver; + base::Timer _pingTimer; + base::Timer _abortTimer; + +}; + +class PingThread : public QThread { +public: + PingThread(not_null parent) + : QThread(parent) { + start(); + } + + ~PingThread() { + quit(); + wait(); + } + +protected: + void run() override { + Pinger pinger(parent()); + QThread::run(); + } + +}; + +} // namespace Core::DeadlockDetector diff --git a/Telegram/SourceFiles/core/launcher.cpp b/Telegram/SourceFiles/core/launcher.cpp index fee3b0140..fa0a728fa 100644 --- a/Telegram/SourceFiles/core/launcher.cpp +++ b/Telegram/SourceFiles/core/launcher.cpp @@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/options.h" #include +#include namespace Core { namespace { @@ -115,16 +116,26 @@ void ComputeDebugMode() { } void ComputeExternalUpdater() { - QFile file(u"/etc/tdesktop/externalupdater"_q); - - if (file.exists() && file.open(QIODevice::ReadOnly)) { - QTextStream fileStream(&file); - while (!fileStream.atEnd()) { - const auto path = fileStream.readLine(); - - if (path == (cExeDir() + cExeName())) { - SetUpdaterDisabledAtStartup(); - return; + auto locations = QStandardPaths::standardLocations( + QStandardPaths::AppDataLocation); + if (locations.isEmpty()) { + locations << QString(); + } + locations[0] = QDir::cleanPath(cWorkingDir()); + locations << QDir::cleanPath(cExeDir()); + for (const auto &location : locations) { + const auto dir = location + u"/externalupdater.d"_q; + for (const auto &info : QDir(dir).entryInfoList(QDir::Files)) { + QFile file(info.absoluteFilePath()); + if (file.open(QIODevice::ReadOnly)) { + QTextStream fileStream(&file); + while (!fileStream.atEnd()) { + const auto path = fileStream.readLine(); + if (path == (cExeDir() + cExeName())) { + SetUpdaterDisabledAtStartup(); + return; + } + } } } } @@ -223,7 +234,7 @@ bool CheckPortableVersionFolder() { if (cAlphaVersion()) { Assert(*AlphaPrivateKey != 0); - cForceWorkingDir(portable + '/'); + cForceWorkingDir(portable); QDir().mkpath(cWorkingDir() + u"tdata"_q); cSetAlphaPrivateKey(QByteArray(AlphaPrivateKey)); if (!key.open(QIODevice::WriteOnly)) { @@ -239,7 +250,7 @@ bool CheckPortableVersionFolder() { if (!QDir(portable).exists()) { return true; } - cForceWorkingDir(portable + '/'); + cForceWorkingDir(portable); if (!key.exists()) { return true; } @@ -291,7 +302,8 @@ Launcher::Launcher(int argc, char *argv[]) : _argc(argc) , _argv(argv) , _arguments(readArguments(_argc, _argv)) -, _baseIntegration(_argc, _argv) { +, _baseIntegration(_argc, _argv) +, _initialWorkingDir(QDir::currentPath() + '/') { crl::toggle_fp_exceptions(true); base::Integration::Set(&_baseIntegration); @@ -446,6 +458,10 @@ const QStringList &Launcher::arguments() const { return _arguments; } +QString Launcher::initialWorkingDir() const { + return _initialWorkingDir; +} + bool Launcher::customWorkingDir() const { return !_customWorkingDir.isEmpty(); } diff --git a/Telegram/SourceFiles/core/launcher.h b/Telegram/SourceFiles/core/launcher.h index a5e5117a5..ea5b1c97f 100644 --- a/Telegram/SourceFiles/core/launcher.h +++ b/Telegram/SourceFiles/core/launcher.h @@ -29,6 +29,7 @@ public: virtual int exec(); const QStringList &arguments() const; + QString initialWorkingDir() const; bool customWorkingDir() const; uint64 installationTag() const; @@ -84,6 +85,7 @@ private: QStringList _arguments; BaseIntegration _baseIntegration; + QString _initialWorkingDir; QString _customWorkingDir; }; diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index 9daf74d62..d9db3c540 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -378,6 +378,8 @@ bool ResolveUsernameOrPhone( startToken = params.value(u"startgroup"_q); } else if (params.contains(u"startchannel"_q)) { resolveType = ResolveType::AddToChannel; + } else if (params.contains(u"boost"_q)) { + resolveType = ResolveType::Boost; } auto post = ShowAtUnreadMsgId; auto adminRights = ChatAdminRights(); @@ -392,7 +394,6 @@ bool ResolveUsernameOrPhone( const auto storyParam = params.value(u"story"_q); const auto storyId = storyParam.toInt(); const auto appname = webChannelPreviewLink ? QString() : appnameParam; - const auto appstart = params.value(u"startapp"_q); const auto commentParam = params.value(u"comment"_q); const auto commentId = commentParam.toInt(); const auto topicParam = params.value(u"topic"_q); @@ -404,11 +405,11 @@ bool ResolveUsernameOrPhone( startToken = gameParam; resolveType = ResolveType::ShareGame; } - if (startToken.isEmpty() && params.contains(u"startapp"_q)) { - startToken = params.value(u"startapp"_q); - } if (!appname.isEmpty()) { resolveType = ResolveType::BotApp; + if (startToken.isEmpty() && params.contains(u"startapp"_q)) { + startToken = params.value(u"startapp"_q); + } } const auto myContext = context.value(); using Navigation = Window::SessionNavigation; @@ -436,7 +437,11 @@ bool ResolveUsernameOrPhone( .attachBotUsername = params.value(u"attach"_q), .attachBotToggleCommand = (params.contains(u"startattach"_q) ? params.value(u"startattach"_q) + : (appname.isEmpty() && params.contains(u"startapp"_q)) + ? params.value(u"startapp"_q) : std::optional()), + .attachBotMenuOpen = (appname.isEmpty() + && params.contains(u"startapp"_q)), .attachBotChooseTypes = InlineBots::ParseChooseTypes( params.value(u"choose"_q)), .voicechatHash = (params.contains(u"livestream"_q) @@ -839,6 +844,32 @@ bool ResolveLoginCode( return true; } +bool ResolveBoost( + 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); + const auto domainParam = params.value(u"domain"_q); + const auto channelParam = params.value(u"channel"_q); + + const auto myContext = context.value(); + using Navigation = Window::SessionNavigation; + controller->window().activate(); + controller->showPeerByLink(Navigation::PeerByLinkInfo{ + .usernameOrId = (!domainParam.isEmpty() + ? std::variant(domainParam) + : ChannelId(BareId(channelParam.toULongLong()))), + .resolveType = Window::ResolveType::Boost, + .clickFromMessageId = myContext.itemId, + }); + return true; +} + } // namespace const std::vector &LocalUrlHandlers() { @@ -919,6 +950,10 @@ const std::vector &LocalUrlHandlers() { u"^login/?(\\?code=([0-9]+))(&|$)"_q, ResolveLoginCode }, + { + u"^boost/?\\?(.+)(#|$)"_q, + ResolveBoost, + }, { u"^([^\\?]+)(\\?|#|$)"_q, HandleUnknown @@ -1022,8 +1057,13 @@ QString TryConvertUrlToLocal(QString url) { "/\\d+/?(\\?|$)|" "/\\d+/\\d+/?(\\?|$)" ")"_q, query, matchOptions)) { + const auto channel = privateMatch->captured(1); const auto params = query.mid(privateMatch->captured(0).size()).toString(); - const auto base = u"tg://privatepost?channel="_q + privateMatch->captured(1); + if (params.indexOf("boost", 0, Qt::CaseInsensitive) >= 0 + && params.toLower().split('&').contains(u"boost"_q)) { + return u"tg://boost?channel="_q + channel; + } + const auto base = u"tg://privatepost?channel="_q + channel; auto added = QString(); if (const auto threadPostMatch = regex_match(u"^/(\\d+)/(\\d+)(/?\\?|/?$)"_q, privateMatch->captured(2))) { added = u"&topic=%1&post=%2"_q.arg(threadPostMatch->captured(1)).arg(threadPostMatch->captured(2)); @@ -1041,7 +1081,12 @@ QString TryConvertUrlToLocal(QString url) { "/s/\\d+/?(\\?|$)|" "/\\d+/\\d+/?(\\?|$)" ")"_q, query, matchOptions)) { + const auto domain = usernameMatch->captured(1); const auto params = query.mid(usernameMatch->captured(0).size()).toString(); + if (params.indexOf("boost", 0, Qt::CaseInsensitive) >= 0 + && params.toLower().split('&').contains(u"boost"_q)) { + return u"tg://boost?domain="_q + domain; + } const auto base = u"tg://resolve?domain="_q + url_encode(usernameMatch->captured(1)); auto added = QString(); if (const auto threadPostMatch = regex_match(u"^/(\\d+)/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) { diff --git a/Telegram/SourceFiles/core/sandbox.cpp b/Telegram/SourceFiles/core/sandbox.cpp index 1c1419bb2..2c42064ca 100644 --- a/Telegram/SourceFiles/core/sandbox.cpp +++ b/Telegram/SourceFiles/core/sandbox.cpp @@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/launcher.h" #include "core/local_url_handlers.h" #include "core/update_checker.h" +#include "core/deadlock_detector.h" #include "base/timer.h" #include "base/concurrent_timer.h" #include "base/invoke_queued.h" @@ -202,6 +203,13 @@ void Sandbox::launchApplication() { } setupScreenScale(); +#ifndef _DEBUG + if (Logs::DebugEnabled()) { + using DeadlockDetector::PingThread; + _deadlockDetector = std::make_unique(this); + } +#endif // !_DEBUG + _application = std::make_unique(); // Ideally this should go to constructor. @@ -271,6 +279,10 @@ bool Sandbox::event(QEvent *e) { return false; } else if (e->type() == QEvent::Close) { Quit(); + } else if (e->type() == DeadlockDetector::PingPongEvent::Type()) { + postEvent( + static_cast(e)->sender(), + new DeadlockDetector::PingPongEvent(this)); } return QApplication::event(e); } diff --git a/Telegram/SourceFiles/core/sandbox.h b/Telegram/SourceFiles/core/sandbox.h index d8f47cd5a..4c15c2828 100644 --- a/Telegram/SourceFiles/core/sandbox.h +++ b/Telegram/SourceFiles/core/sandbox.h @@ -131,6 +131,8 @@ private: rpl::event_stream<> _widgetUpdateRequests; + std::unique_ptr _deadlockDetector; + }; } // namespace Core diff --git a/Telegram/SourceFiles/core/ui_integration.cpp b/Telegram/SourceFiles/core/ui_integration.cpp index ead942262..8f1be8324 100644 --- a/Telegram/SourceFiles/core/ui_integration.cpp +++ b/Telegram/SourceFiles/core/ui_integration.cpp @@ -370,7 +370,6 @@ QString UiIntegration::phrasePanelCloseAnyway() { return tr::lng_bot_close_warning_sure(tr::now); } -#if 0 // disabled for now QString UiIntegration::phraseBotSharePhone() { return tr::lng_bot_share_phone(tr::now); } @@ -382,7 +381,18 @@ QString UiIntegration::phraseBotSharePhoneTitle() { QString UiIntegration::phraseBotSharePhoneConfirm() { return tr::lng_bot_share_phone_confirm(tr::now); } -#endif + +QString UiIntegration::phraseBotAllowWrite() { + return tr::lng_bot_allow_write(tr::now); +} + +QString UiIntegration::phraseBotAllowWriteTitle() { + return tr::lng_bot_allow_write_title(tr::now); +} + +QString UiIntegration::phraseBotAllowWriteConfirm() { + return tr::lng_bot_allow_write_confirm(tr::now); +} bool OpenGLLastCheckFailed() { return QFile::exists(OpenGLCheckFilePath()); diff --git a/Telegram/SourceFiles/core/ui_integration.h b/Telegram/SourceFiles/core/ui_integration.h index 65661ecfd..cd3e4ff58 100644 --- a/Telegram/SourceFiles/core/ui_integration.h +++ b/Telegram/SourceFiles/core/ui_integration.h @@ -84,11 +84,12 @@ public: QString phrasePanelCloseWarning() override; QString phrasePanelCloseUnsaved() override; QString phrasePanelCloseAnyway() override; -#if 0 // disabled for now QString phraseBotSharePhone() override; QString phraseBotSharePhoneTitle() override; QString phraseBotSharePhoneConfirm() override; -#endif + QString phraseBotAllowWrite() override; + QString phraseBotAllowWriteTitle() override; + QString phraseBotAllowWriteConfirm() override; }; diff --git a/Telegram/SourceFiles/core/update_checker.cpp b/Telegram/SourceFiles/core/update_checker.cpp index 6c7154f84..0ff7b4cc1 100644 --- a/Telegram/SourceFiles/core/update_checker.cpp +++ b/Telegram/SourceFiles/core/update_checker.cpp @@ -42,16 +42,16 @@ extern "C" { } // extern "C" #ifndef TDESKTOP_DISABLE_AUTOUPDATE -#if defined Q_OS_WIN && !defined DESKTOP_APP_USE_PACKAGED // use Lzma SDK for win +#if defined Q_OS_WIN && !defined TDESKTOP_USE_PACKAGED // use Lzma SDK for win #include -#else // Q_OS_WIN && !DESKTOP_APP_USE_PACKAGED +#else // Q_OS_WIN && !TDESKTOP_USE_PACKAGED #include -#endif // else of Q_OS_WIN && !DESKTOP_APP_USE_PACKAGED +#endif // else of Q_OS_WIN && !TDESKTOP_USE_PACKAGED #endif // !TDESKTOP_DISABLE_AUTOUPDATE -#ifdef Q_OS_UNIX +#ifndef Q_OS_WIN #include -#endif // Q_OS_UNIX +#endif // !Q_OS_WIN namespace Core { namespace { @@ -275,11 +275,11 @@ bool UnpackUpdate(const QString &filepath) { return false; } -#if defined Q_OS_WIN && !defined DESKTOP_APP_USE_PACKAGED // use Lzma SDK for win +#if defined Q_OS_WIN && !defined TDESKTOP_USE_PACKAGED // use Lzma SDK for win const int32 hSigLen = 128, hShaLen = 20, hPropsLen = LZMA_PROPS_SIZE, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hPropsLen + hOriginalSizeLen; // header -#else // Q_OS_WIN && !DESKTOP_APP_USE_PACKAGED +#else // Q_OS_WIN && !TDESKTOP_USE_PACKAGED const int32 hSigLen = 128, hShaLen = 20, hPropsLen = 0, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hOriginalSizeLen; // header -#endif // Q_OS_WIN && !DESKTOP_APP_USE_PACKAGED +#endif // Q_OS_WIN && !TDESKTOP_USE_PACKAGED QByteArray compressed = input.readAll(); int32 compressedLen = compressed.size() - hSize; @@ -330,14 +330,14 @@ bool UnpackUpdate(const QString &filepath) { uncompressed.resize(uncompressedLen); size_t resultLen = uncompressed.size(); -#if defined Q_OS_WIN && !defined DESKTOP_APP_USE_PACKAGED // use Lzma SDK for win +#if defined Q_OS_WIN && !defined TDESKTOP_USE_PACKAGED // use Lzma SDK for win SizeT srcLen = compressedLen; int uncompressRes = LzmaUncompress((uchar*)uncompressed.data(), &resultLen, (const uchar*)(compressed.constData() + hSize), &srcLen, (const uchar*)(compressed.constData() + hSigLen + hShaLen), LZMA_PROPS_SIZE); if (uncompressRes != SZ_OK) { LOG(("Update Error: could not uncompress lzma, code: %1").arg(uncompressRes)); return false; } -#else // Q_OS_WIN && !DESKTOP_APP_USE_PACKAGED +#else // Q_OS_WIN && !TDESKTOP_USE_PACKAGED lzma_stream stream = LZMA_STREAM_INIT; lzma_ret ret = lzma_stream_decoder(&stream, UINT64_MAX, LZMA_CONCATENATED); @@ -380,7 +380,7 @@ bool UnpackUpdate(const QString &filepath) { LOG(("Error in decompression: %1 (error code %2)").arg(msg).arg(res)); return false; } -#endif // Q_OS_WIN && !DESKTOP_APP_USE_PACKAGED +#endif // Q_OS_WIN && !TDESKTOP_USE_PACKAGED tempDir.mkdir(tempDir.absolutePath()); @@ -428,9 +428,9 @@ bool UnpackUpdate(const QString &filepath) { bool executable = false; stream >> relativeName >> fileSize >> fileInnerData; -#ifdef Q_OS_UNIX +#ifndef Q_OS_WIN stream >> executable; -#endif // Q_OS_UNIX +#endif // !Q_OS_WIN if (stream.status() != QDataStream::Ok) { LOG(("Update Error: cant read file from downloaded stream, status: %1").arg(stream.status())); return false; @@ -1539,10 +1539,10 @@ bool checkReadyUpdate() { #elif defined Q_OS_MAC // Q_OS_WIN QString curUpdater = (cExeDir() + cExeName() + u"/Contents/Frameworks/Updater"_q); QFileInfo updater(cWorkingDir() + u"tupdates/temp/Telegram.app/Contents/Frameworks/Updater"_q); -#elif defined Q_OS_UNIX // Q_OS_MAC +#else // Q_OS_MAC QString curUpdater = (cExeDir() + u"Updater"_q); QFileInfo updater(cWorkingDir() + u"tupdates/temp/Updater"_q); -#endif // Q_OS_UNIX +#endif // else for Q_OS_WIN || Q_OS_MAC if (!updater.exists()) { QFileInfo current(curUpdater); if (!current.exists()) { @@ -1576,7 +1576,7 @@ bool checkReadyUpdate() { ClearAll(); return false; } -#elif defined Q_OS_UNIX // Q_OS_MAC +#else // Q_OS_MAC // if the files in the directory are owned by user, while the directory is not, // update will still fail since it's not possible to remove files if (QFile::exists(curUpdater) @@ -1604,7 +1604,7 @@ bool checkReadyUpdate() { return false; } } -#endif // Q_OS_UNIX +#endif // else for Q_OS_WIN || Q_OS_MAC #ifdef Q_OS_MAC base::Platform::RemoveQuarantine(QFileInfo(curUpdater).absolutePath()); diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index fec69bc64..bc435194b 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 = 4009004; -constexpr auto AppVersionStr = "4.9.4"; +constexpr auto AppVersion = 4010000; +constexpr auto AppVersionStr = "4.10"; constexpr auto AppBetaVersion = false; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/SourceFiles/data/data_bot_app.h b/Telegram/SourceFiles/data/data_bot_app.h index 7de4aefe8..715fb03c0 100644 --- a/Telegram/SourceFiles/data/data_bot_app.h +++ b/Telegram/SourceFiles/data/data_bot_app.h @@ -24,4 +24,5 @@ struct BotAppData { uint64 accessHash = 0; uint64 hash = 0; + bool hasSettings = false; }; diff --git a/Telegram/SourceFiles/data/data_changes.h b/Telegram/SourceFiles/data/data_changes.h index 304749900..12cfa7353 100644 --- a/Telegram/SourceFiles/data/data_changes.h +++ b/Telegram/SourceFiles/data/data_changes.h @@ -221,14 +221,14 @@ struct StoryUpdate { enum class Flag : uint32 { None = 0, - Edited = (1U << 0), - Destroyed = (1U << 1), - NewAdded = (1U << 2), - ViewsAdded = (1U << 3), - MarkRead = (1U << 4), - Reaction = (1U << 5), + Edited = (1U << 0), + Destroyed = (1U << 1), + NewAdded = (1U << 2), + ViewsChanged = (1U << 3), + MarkRead = (1U << 4), + Reaction = (1U << 5), - LastUsedBit = (1U << 5), + LastUsedBit = (1U << 5), }; using Flags = base::flags; friend inline constexpr auto is_flag_type(Flag) { return true; } diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index dddc44116..cf266c5a2 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "data/data_chat.h" #include "data/data_session.h" +#include "data/data_stories.h" #include "data/data_folder.h" #include "data/data_forum.h" #include "data/data_forum_icons.h" @@ -530,6 +531,11 @@ bool ChannelData::canBanMembers() const { || (adminRights() & AdminRight::BanUsers); } +bool ChannelData::canPostMessages() const { + return amCreator() + || (adminRights() & AdminRight::PostMessages); +} + bool ChannelData::canEditMessages() const { return amCreator() || (adminRights() & AdminRight::EditMessages); @@ -540,6 +546,30 @@ bool ChannelData::canDeleteMessages() const { || (adminRights() & AdminRight::DeleteMessages); } +bool ChannelData::canPostStories() const { + if (!isBroadcast()) { + return false; + } + return amCreator() + || (adminRights() & AdminRight::PostStories); +} + +bool ChannelData::canEditStories() const { + if (!isBroadcast()) { + return false; + } + return amCreator() + || (adminRights() & AdminRight::EditStories); +} + +bool ChannelData::canDeleteStories() const { + if (!isBroadcast()) { + return false; + } + return amCreator() + || (adminRights() & AdminRight::DeleteStories); +} + bool ChannelData::anyoneCanAddMembers() const { return !(defaultRestrictions() & Restriction::AddParticipants); } @@ -559,11 +589,6 @@ bool ChannelData::canAddAdmins() const { || (adminRights() & AdminRight::AddAdmins); } -bool ChannelData::canPublish() const { - return amCreator() - || (adminRights() & AdminRight::PostMessages); -} - bool ChannelData::allowsForwarding() const { return !(flags() & Flag::NoForwards); } @@ -877,6 +902,38 @@ const Data::AllowedReactions &ChannelData::allowedReactions() const { return _allowedReactions; } +bool ChannelData::hasActiveStories() const { + return flags() & Flag::HasActiveStories; +} + +bool ChannelData::hasUnreadStories() const { + return flags() & Flag::HasUnreadStories; +} + +void ChannelData::setStoriesState(StoriesState state) { + Expects(state != StoriesState::Unknown); + + const auto was = flags(); + switch (state) { + case StoriesState::None: + _flags.remove(Flag::HasActiveStories | Flag::HasUnreadStories); + break; + case StoriesState::HasRead: + _flags.set( + (flags() & ~Flag::HasUnreadStories) | Flag::HasActiveStories); + break; + case StoriesState::HasUnread: + _flags.add(Flag::HasActiveStories | Flag::HasUnreadStories); + break; + } + if (flags() != was) { + if (const auto history = owner().historyLoaded(this)) { + history->updateChatListEntryPostponed(); + } + session().changes().peerUpdated(this, UpdateFlag::StoriesState); + } +} + void ChannelData::processTopics(const MTPVector &topics) { if (const auto forum = this->forum()) { forum->applyReceivedTopics(topics); @@ -1046,6 +1103,7 @@ void ApplyChannelUpdate( } else { channel->setAllowedReactions({}); } + channel->owner().stories().apply(channel, update.vstories()); channel->fullUpdated(); channel->setPendingRequestsCount( update.vrequests_pending().value_or_empty(), diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index 6c05d5c88..ee0550a6d 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -59,6 +59,9 @@ enum class ChannelDataFlag { Forum = (1 << 23), AntiSpam = (1 << 24), ParticipantsHidden = (1 << 25), + StoriesHidden = (1 << 26), + HasActiveStories = (1 << 27), + HasUnreadStories = (1 << 28), }; inline constexpr bool is_flag_type(ChannelDataFlag) { return true; }; using ChannelDataFlags = base::flags; @@ -226,6 +229,9 @@ public: [[nodiscard]] bool isFake() const { return flags() & Flag::Fake; } + [[nodiscard]] bool hasStoriesHidden() const { + return flags() & Flag::StoriesHidden; + } [[nodiscard]] static ChatRestrictionsInfo KickedRestrictedRights( not_null participant); @@ -329,10 +335,13 @@ public: [[nodiscard]] bool canBanMembers() const; [[nodiscard]] bool anyoneCanAddMembers() const; + [[nodiscard]] bool canPostMessages() const; [[nodiscard]] bool canEditMessages() const; [[nodiscard]] bool canDeleteMessages() const; + [[nodiscard]] bool canPostStories() const; + [[nodiscard]] bool canEditStories() const; + [[nodiscard]] bool canDeleteStories() const; [[nodiscard]] bool hiddenPreHistory() const; - [[nodiscard]] bool canPublish() const; [[nodiscard]] bool canViewMembers() const; [[nodiscard]] bool canViewAdmins() const; [[nodiscard]] bool canViewBanned() const; @@ -437,6 +446,10 @@ public: void setAllowedReactions(Data::AllowedReactions value); [[nodiscard]] const Data::AllowedReactions &allowedReactions() const; + [[nodiscard]] bool hasActiveStories() const; + [[nodiscard]] bool hasUnreadStories() const; + void setStoriesState(StoriesState state); + [[nodiscard]] Data::Forum *forum() const { return mgInfo ? mgInfo->forum() : nullptr; } diff --git a/Telegram/SourceFiles/data/data_chat_participant_status.cpp b/Telegram/SourceFiles/data/data_chat_participant_status.cpp index 836f13ebc..f875ccdb3 100644 --- a/Telegram/SourceFiles/data/data_chat_participant_status.cpp +++ b/Telegram/SourceFiles/data/data_chat_participant_status.cpp @@ -146,7 +146,7 @@ bool CanSendAnyOf( && !(channel->flags() & Flag::JoinToWrite)); if (!allowed || (forbidInForums && channel->isForum())) { return false; - } else if (channel->canPublish()) { + } else if (channel->canPostMessages()) { return true; } else if (channel->isBroadcast()) { return false; diff --git a/Telegram/SourceFiles/data/data_chat_participant_status.h b/Telegram/SourceFiles/data/data_chat_participant_status.h index 2227e8042..8dd7cd682 100644 --- a/Telegram/SourceFiles/data/data_chat_participant_status.h +++ b/Telegram/SourceFiles/data/data_chat_participant_status.h @@ -25,6 +25,9 @@ enum class ChatAdminRight { ManageCall = (1 << 11), Other = (1 << 12), ManageTopics = (1 << 13), + PostStories = (1 << 14), + EditStories = (1 << 15), + DeleteStories = (1 << 16), }; inline constexpr bool is_flag_type(ChatAdminRight) { return true; } using ChatAdminRights = base::flags; diff --git a/Telegram/SourceFiles/data/data_document_resolver.cpp b/Telegram/SourceFiles/data/data_document_resolver.cpp index 2839eeb76..ead282bcf 100644 --- a/Telegram/SourceFiles/data/data_document_resolver.cpp +++ b/Telegram/SourceFiles/data/data_document_resolver.cpp @@ -159,14 +159,7 @@ wv xm xml ym yuv").split(' '); bool IsExecutableName(const QString &filepath) { static const auto kExtensions = [] { const auto joined = -#ifdef Q_OS_MAC - u"\ -applescript action app bin command csh osx workflow terminal url caction \ -mpkg pkg scpt scptd xhtm webarchive"_q; -#elif defined Q_OS_UNIX // Q_OS_MAC - u"bin csh deb desktop ksh out pet pkg pup rpm run sh shar \ -slp zsh"_q; -#else // Q_OS_MAC || Q_OS_UNIX +#ifdef Q_OS_WIN u"\ ad ade adp app application appref-ms asp asx bas bat bin cab cdxml cer cfg \ chi chm cmd cnt com cpl crt csh der diagcab dll drv eml exe fon fxp gadget \ @@ -179,7 +172,14 @@ psd1 psm1 pssc pst py py3 pyc pyd pyi pyo pyw pywz pyz rb reg rgs scf scr \ sct search-ms settingcontent-ms sh shb shs slk sys t tmp u3p url vb vbe vbp \ vbs vbscript vdx vsmacros vsd vsdm vsdx vss vssm vssx vst vstm vstx vsw vsx \ vtx website ws wsc wsf wsh xbap xll xnk xs"_q; -#endif // !Q_OS_MAC && !Q_OS_UNIX +#elif defined Q_OS_MAC // Q_OS_MAC + u"\ +applescript action app bin command csh osx workflow terminal url caction \ +mpkg pkg scpt scptd xhtm webarchive"_q; +#else // Q_OS_WIN || Q_OS_MAC + u"bin csh deb desktop ksh out pet pkg pup rpm run sh shar \ +slp zsh"_q; +#endif // !Q_OS_WIN && !Q_OS_MAC const auto list = joined.split(' '); return base::flat_set(list.begin(), list.end()); }(); diff --git a/Telegram/SourceFiles/data/data_drafts.cpp b/Telegram/SourceFiles/data/data_drafts.cpp index 474822a48..0a6d64122 100644 --- a/Telegram/SourceFiles/data/data_drafts.cpp +++ b/Telegram/SourceFiles/data/data_drafts.cpp @@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_drafts.h" #include "api/api_text_entities.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "chat_helpers/message_field.h" #include "history/history.h" #include "history/history_widget.h" diff --git a/Telegram/SourceFiles/data/data_forum.cpp b/Telegram/SourceFiles/data/data_forum.cpp index 507f54478..361135d88 100644 --- a/Telegram/SourceFiles/data/data_forum.cpp +++ b/Telegram/SourceFiles/data/data_forum.cpp @@ -24,7 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "core/application.h" #include "ui/layers/generic_box.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "storage/storage_facade.h" #include "storage/storage_shared_media.h" #include "window/window_session_controller.h" diff --git a/Telegram/SourceFiles/data/data_msg_id.h b/Telegram/SourceFiles/data/data_msg_id.h index 49357d88f..6a9bc0d1c 100644 --- a/Telegram/SourceFiles/data/data_msg_id.h +++ b/Telegram/SourceFiles/data/data_msg_id.h @@ -73,7 +73,7 @@ struct FullReplyTo { FullStoryId storyId; [[nodiscard]] bool valid() const { - return msgId || storyId; + return msgId || (storyId && peerIsUser(storyId.peer)); } explicit operator bool() const { return valid(); diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index c096853c6..00c183161 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -898,6 +898,33 @@ bool PeerData::isRepliesChat() const { : kTestId) == id; } +bool PeerData::sharedMediaInfo() const { + return isSelf() || isRepliesChat(); +} + +bool PeerData::hasStoriesHidden() const { + if (const auto user = asUser()) { + return user->hasStoriesHidden(); + } else if (const auto channel = asChannel()) { + return channel->hasStoriesHidden(); + } + return false; +} + +void PeerData::setStoriesHidden(bool hidden) { + if (const auto user = asUser()) { + user->setFlags(hidden + ? (user->flags() | UserDataFlag::StoriesHidden) + : (user->flags() & ~UserDataFlag::StoriesHidden)); + } else if (const auto channel = asChannel()) { + channel->setFlags(hidden + ? (channel->flags() | ChannelDataFlag::StoriesHidden) + : (channel->flags() & ~ChannelDataFlag::StoriesHidden)); + } else { + Unexpected("PeerData::setStoriesHidden for non-user/non-channel."); + } +} + Data::Forum *PeerData::forum() const { if (const auto channel = asChannel()) { return channel->forum(); @@ -1108,6 +1135,34 @@ const Data::WallPaper *PeerData::wallPaper() const { return _wallPaper.get(); } +bool PeerData::hasActiveStories() const { + if (const auto user = asUser()) { + return user->hasActiveStories(); + } else if (const auto channel = asChannel()) { + return channel->hasActiveStories(); + } + return false; +} + +bool PeerData::hasUnreadStories() const { + if (const auto user = asUser()) { + return user->hasUnreadStories(); + } else if (const auto channel = asChannel()) { + return channel->hasUnreadStories(); + } + return false; +} + +void PeerData::setStoriesState(StoriesState state) { + if (const auto user = asUser()) { + return user->setStoriesState(state); + } else if (const auto channel = asChannel()) { + return channel->setStoriesState(state); + } else { + Unexpected("PeerData::setStoriesState for non-user/non-channel."); + } +} + void PeerData::setIsBlocked(bool is) { const auto status = is ? BlockStatus::Blocked diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index 17b19bf63..73c3d85f8 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -183,9 +183,9 @@ public: [[nodiscard]] bool isForum() const; [[nodiscard]] bool isGigagroup() const; [[nodiscard]] bool isRepliesChat() const; - [[nodiscard]] bool sharedMediaInfo() const { - return isSelf() || isRepliesChat(); - } + [[nodiscard]] bool sharedMediaInfo() const; + [[nodiscard]] bool hasStoriesHidden() const; + void setStoriesHidden(bool hidden); [[nodiscard]] bool isNotificationsUser() const { return (id == peerFromUser(333000)) @@ -407,6 +407,16 @@ public: void setWallPaper(std::optional paper); [[nodiscard]] const Data::WallPaper *wallPaper() const; + enum class StoriesState { + Unknown, + None, + HasRead, + HasUnread, + }; + [[nodiscard]] bool hasActiveStories() const; + [[nodiscard]] bool hasUnreadStories() const; + void setStoriesState(StoriesState state); + const PeerId id; MTPinputPeer input = MTP_inputPeerEmpty(); diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 207b786ef..a941f2b73 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -919,7 +919,17 @@ not_null Session::processChat(const MTPChat &data) { | Flag::NoForwards | Flag::JoinToWrite | Flag::RequestToJoin - | Flag::Forum; + | Flag::Forum + | ((!minimal && !data.is_stories_hidden_min()) + ? Flag::StoriesHidden + : Flag()); + const auto storiesState = minimal + ? std::optional() + : data.is_stories_unavailable() + ? Data::Stories::PeerSourceState() + : !data.vstories_max_id() + ? std::optional() + : stories().peerSourceState(channel, data.vstories_max_id()->v); const auto flagsSet = (data.is_broadcast() ? Flag::Broadcast : Flag()) | (data.is_verified() ? Flag::Verified : Flag()) | (data.is_scam() ? Flag::Scam : Flag()) @@ -945,8 +955,20 @@ not_null Session::processChat(const MTPChat &data) { | (data.is_join_request() ? Flag::RequestToJoin : Flag()) | ((data.is_forum() && data.is_megagroup()) ? Flag::Forum + : Flag()) + | ((!minimal + && !data.is_stories_hidden_min() + && data.is_stories_hidden()) + ? Flag::StoriesHidden : Flag()); channel->setFlags((channel->flags() & ~flagsMask) | flagsSet); + if (!minimal && storiesState) { + result->setStoriesState(!storiesState->maxId + ? UserData::StoriesState::None + : (storiesState->maxId > storiesState->readTill) + ? UserData::StoriesState::HasUnread + : UserData::StoriesState::HasRead); + } channel->setPhoto(data.vphoto()); @@ -2699,7 +2721,7 @@ void Session::checkSelfDestructItems() { auto nextDestructIn = crl::time(0); for (auto i = _selfDestructItems.begin(); i != _selfDestructItems.cend();) { if (const auto item = message(*i)) { - if (auto destructIn = item->getSelfDestructIn(now)) { + if (const auto destructIn = item->getSelfDestructIn(now)) { if (nextDestructIn > 0) { accumulate_min(nextDestructIn, destructIn); } else { @@ -3425,12 +3447,12 @@ void Session::webpageApplyFields( for (const auto &attribute : attributes->v) { attribute.match([&](const MTPDwebPageAttributeStory &data) { storyId = FullStoryId{ - peerFromUser(data.vuser_id()), + peerFromMTP(data.vpeer()), data.vid().v, }; if (const auto embed = data.vstory()) { story = stories().applyFromWebpage( - peerFromUser(data.vuser_id()), + peerFromMTP(data.vpeer()), *embed); } else if (const auto maybe = stories().lookup(storyId)) { story = *maybe; diff --git a/Telegram/SourceFiles/data/data_sponsored_messages.cpp b/Telegram/SourceFiles/data/data_sponsored_messages.cpp index 1f19c9264..d22cf976a 100644 --- a/Telegram/SourceFiles/data/data_sponsored_messages.cpp +++ b/Telegram/SourceFiles/data/data_sponsored_messages.cpp @@ -301,7 +301,7 @@ void SponsoredMessages::append( : ImageWithLocation{}; return SponsoredFrom{ .title = qs(data.vsite_name()), - .isExternalLink = true, + .externalLink = externalLink, .userpic = std::move(userpic), .isForceUserpicDisplay = message.data().is_show_peer_photo(), }; diff --git a/Telegram/SourceFiles/data/data_sponsored_messages.h b/Telegram/SourceFiles/data/data_sponsored_messages.h index e9cd46b45..2c8b1e72b 100644 --- a/Telegram/SourceFiles/data/data_sponsored_messages.h +++ b/Telegram/SourceFiles/data/data_sponsored_messages.h @@ -31,7 +31,7 @@ struct SponsoredFrom { bool isBot = false; bool isExactPost = false; bool isRecommended = false; - bool isExternalLink = false; + QString externalLink; ImageWithLocation userpic; bool isForceUserpicDisplay = false; }; diff --git a/Telegram/SourceFiles/data/data_stories.cpp b/Telegram/SourceFiles/data/data_stories.cpp index d8197de20..0f0ff8b92 100644 --- a/Telegram/SourceFiles/data/data_stories.cpp +++ b/Telegram/SourceFiles/data/data_stories.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "core/application.h" #include "data/data_changes.h" +#include "data/data_channel.h" #include "data/data_document.h" #include "data/data_folder.h" #include "data/data_photo.h" @@ -82,11 +83,11 @@ using UpdateFlag = StoryUpdate::Flag; StoriesSourceInfo StoriesSource::info() const { return { - .id = user->id, + .id = peer->id, .last = ids.empty() ? 0 : ids.back().date, .count = uint32(std::min(int(ids.size()), kMaxSegmentsCount)), .unreadCount = uint32(std::min(unreadCount(), kMaxSegmentsCount)), - .premium = user->isPremium() ? 1U : 0U, + .premium = (peer->isUser() && peer->asUser()->isPremium()) ? 1U : 0, }; } @@ -110,6 +111,31 @@ Stories::Stories(not_null owner) , _incrementViewsTimer([=] { sendIncrementViewsRequests(); }) , _pollingTimer([=] { sendPollingRequests(); }) , _pollingViewsTimer([=] { sendPollingViewsRequests(); }) { + crl::on_main(this, [=] { + session().changes().peerUpdates( + Data::PeerUpdate::Flag::Rights + ) | rpl::start_with_next([=](const Data::PeerUpdate &update) { + const auto channel = update.peer->asChannel(); + if (!channel) { + return; + } else if (!channel->canEditStories()) { + const auto peerId = channel->id; + const auto i = _peersWithDeletedStories.find(peerId); + if (i != end(_peersWithDeletedStories)) { + _peersWithDeletedStories.erase(i); + for (auto j = begin(_deleted); j != end(_deleted);) { + if (j->peer == peerId) { + j = _deleted.erase(j); + } else { + ++j; + } + } + } + } else { + clearArchive(channel); + } + }, _lifetime); + }); } Stories::~Stories() { @@ -133,10 +159,10 @@ void Stories::apply(const MTPDupdateStory &data) { return; } - const auto peerId = peerFromUser(data.vuser_id()); - const auto user = not_null(_owner->peer(peerId)->asUser()); + const auto peerId = peerFromMTP(data.vpeer()); + const auto peer = _owner->peer(peerId); const auto now = base::unixtime::now(); - const auto idDates = parseAndApply(user, data.vstory(), now); + const auto idDates = parseAndApply(peer, data.vstory(), now); if (!idDates) { return; } @@ -147,7 +173,7 @@ void Stories::apply(const MTPDupdateStory &data) { } const auto i = _all.find(peerId); if (i == end(_all)) { - requestUserStories(user); + requestPeerStories(peer); return; } else if (i->second.ids.contains(idDates)) { return; @@ -155,8 +181,8 @@ void Stories::apply(const MTPDupdateStory &data) { const auto wasInfo = i->second.info(); i->second.ids.emplace(idDates); const auto nowInfo = i->second.info(); - if (user->isSelf() && i->second.readTill < idDates.id) { - _readTill[user->id] = i->second.readTill = idDates.id; + if (peer->isSelf() && i->second.readTill < idDates.id) { + _readTill[peerId] = i->second.readTill = idDates.id; } if (wasInfo == nowInfo) { return; @@ -172,13 +198,13 @@ void Stories::apply(const MTPDupdateStory &data) { sort(list); } }; - if (user->hasStoriesHidden()) { + if (peer->hasStoriesHidden()) { refreshInList(StorySourcesList::Hidden); } else { refreshInList(StorySourcesList::NotHidden); } _sourceChanged.fire_copy(peerId); - updateUserStoriesState(user); + updatePeerStoriesState(peer); } void Stories::apply(const MTPDupdateReadStories &data) { @@ -189,7 +215,7 @@ void Stories::apply(const MTPDupdateReadStories &data) { return; } - bumpReadTill(peerFromUser(data.vuser_id()), data.vmax_id().v); + bumpReadTill(peerFromMTP(data.vpeer()), data.vmax_id().v); } void Stories::apply(const MTPStoriesStealthMode &stealthMode) { @@ -200,20 +226,19 @@ void Stories::apply(const MTPStoriesStealthMode &stealthMode) { }; } -void Stories::apply(not_null peer, const MTPUserStories *data) { +void Stories::apply(not_null peer, const MTPPeerStories *data) { // AyuGram disableStories const auto settings = &AyuSettings::getInstance(); if (settings->disableStories) { return; } - if (!data) { applyDeletedFromSources(peer->id, StorySourcesList::NotHidden); applyDeletedFromSources(peer->id, StorySourcesList::Hidden); _all.erase(peer->id); _sourceChanged.fire_copy(peer->id); - updateUserStoriesState(peer); + updatePeerStoriesState(peer); } else { parseAndApply(*data); } @@ -237,10 +262,10 @@ Story *Stories::applyFromWebpage(PeerId peerId, const MTPstoryItem &story) { return value ? value->get() : nullptr; } -void Stories::requestUserStories( - not_null user, +void Stories::requestPeerStories( + not_null peer, Fn done) { - const auto [i, ok] = _requestingUserStories.emplace(user); + const auto [i, ok] = _requestingPeerStories.emplace(peer); if (done) { i->second.push_back(std::move(done)); } @@ -248,22 +273,23 @@ void Stories::requestUserStories( return; } const auto finish = [=] { - if (const auto callbacks = _requestingUserStories.take(user)) { + if (const auto callbacks = _requestingPeerStories.take(peer)) { for (const auto &callback : *callbacks) { callback(); } } }; - _owner->session().api().request(MTPstories_GetUserStories( - user->inputUser - )).done([=](const MTPstories_UserStories &result) { + _owner->session().api().request(MTPstories_GetPeerStories( + peer->input + )).done([=](const MTPstories_PeerStories &result) { const auto &data = result.data(); _owner->processUsers(data.vusers()); + _owner->processChats(data.vchats()); parseAndApply(data.vstories()); finish(); }).fail([=] { - applyDeletedFromSources(user->id, StorySourcesList::NotHidden); - applyDeletedFromSources(user->id, StorySourcesList::Hidden); + applyDeletedFromSources(peer->id, StorySourcesList::NotHidden); + applyDeletedFromSources(peer->id, StorySourcesList::Hidden); finish(); }).send(); } @@ -323,33 +349,63 @@ void Stories::processExpired() { } } -void Stories::parseAndApply(const MTPUserStories &stories) { +Stories::Set *Stories::lookupArchive(not_null peer) { + const auto peerId = peer->id; + if (hasArchive(peer)) { + const auto i = _archive.find(peerId); + return (i != end(_archive)) + ? &i->second + : &_archive.emplace(peerId, Set()).first->second; + } + clearArchive(peer); + return nullptr; +} + +void Stories::clearArchive(not_null peer) { + const auto peerId = peer->id; + const auto i = _archive.find(peerId); + if (i == end(_archive)) { + return; + } + auto archive = base::take(i->second); + _archive.erase(i); + for (const auto &id : archive.ids.list) { + if (const auto story = lookup({ peerId, id })) { + if ((*story)->expired() && !(*story)->pinned()) { + applyDeleted(peer, id); + } + } + } + _archiveChanged.fire_copy(peerId); +} + +void Stories::parseAndApply(const MTPPeerStories &stories) { const auto &data = stories.data(); - const auto peerId = peerFromUser(data.vuser_id()); + const auto peerId = peerFromMTP(data.vpeer()); const auto already = _readTill.find(peerId); const auto readTill = std::max( data.vmax_read_id().value_or_empty(), (already != end(_readTill) ? already->second : 0)); - const auto user = _owner->peer(peerId)->asUser(); + const auto peer = _owner->peer(peerId); auto result = StoriesSource{ - .user = user, + .peer = peer, .readTill = readTill, - .hidden = user->hasStoriesHidden(), + .hidden = peer->hasStoriesHidden(), }; const auto &list = data.vstories().v; const auto now = base::unixtime::now(); result.ids.reserve(list.size()); for (const auto &story : list) { - if (const auto id = parseAndApply(result.user, story, now)) { + if (const auto id = parseAndApply(result.peer, story, now)) { result.ids.emplace(id); } } if (result.ids.empty()) { applyDeletedFromSources(peerId, StorySourcesList::NotHidden); applyDeletedFromSources(peerId, StorySourcesList::Hidden); - user->setStoriesState(UserData::StoriesState::None); + peer->setStoriesState(PeerData::StoriesState::None); return; - } else if (user->isSelf()) { + } else if (peer->isSelf()) { result.readTill = result.ids.back().id; } _readTill[peerId] = result.readTill; @@ -377,11 +433,13 @@ void Stories::parseAndApply(const MTPUserStories &stories) { } sort(list); }; - if (result.user->isSelf() - || result.user->isBot() - || result.user->isServiceUser() - || result.user->isContact()) { - const auto hidden = result.user->hasStoriesHidden(); + if (result.peer->isSelf() + || (result.peer->isChannel() && result.peer->asChannel()->amIn()) + || (result.peer->isUser() + && (result.peer->asUser()->isBot() + || result.peer->asUser()->isContact())) + || result.peer->isServiceUser()) { + const auto hidden = result.peer->hasStoriesHidden(); using List = StorySourcesList; add(hidden ? List::Hidden : List::NotHidden); applyDeletedFromSources( @@ -392,7 +450,7 @@ void Stories::parseAndApply(const MTPUserStories &stories) { applyDeletedFromSources(peerId, StorySourcesList::Hidden); } _sourceChanged.fire_copy(peerId); - updateUserStoriesState(result.user); + updatePeerStoriesState(result.peer); } Story *Stories::parseAndApply( @@ -405,7 +463,7 @@ Story *Stories::parseAndApply( } const auto expires = data.vexpire_date().v; const auto expired = (expires <= now); - if (expired && !data.is_pinned() && !peer->isSelf()) { + if (expired && !data.is_pinned() && !hasArchive(peer)) { return nullptr; } const auto id = data.vid().v; @@ -441,13 +499,13 @@ Story *Stories::parseAndApply( now )).first->second.get(); - if (peer->isSelf()) { - const auto added = _archive.list.emplace(id).second; + if (const auto archive = lookupArchive(peer)) { + const auto added = archive->ids.list.emplace(id).second; if (added) { - if (_archiveTotal >= 0 && id > _archiveLastId) { - ++_archiveTotal; + if (archive->total >= 0 && id > archive->lastId) { + ++archive->total; } - _archiveChanged.fire({}); + _archiveChanged.fire_copy(peer->id); } } @@ -473,7 +531,7 @@ StoryIdDates Stories::parseAndApply( if (const auto story = parseAndApply(peer, data, now)) { return story->idDates(); } - applyDeleted({ peer->id, data.vid().v }); + applyDeleted(peer, data.vid().v); return StoryIdDates(); }, [&](const MTPDstoryItemSkipped &data) { const auto expires = data.vexpire_date().v; @@ -481,8 +539,8 @@ StoryIdDates Stories::parseAndApply( const auto fullId = FullStoryId{ peer->id, data.vid().v }; if (!expired) { registerExpiring(expires, fullId); - } else if (!peer->isSelf()) { - applyDeleted(fullId); + } else if (!hasArchive(peer)) { + applyDeleted(peer, data.vid().v); return StoryIdDates(); } else { _expiring.remove(expires, fullId); @@ -494,7 +552,7 @@ StoryIdDates Stories::parseAndApply( data.vexpire_date().v, }; }, [&](const MTPDstoryItemDeleted &data) { - applyDeleted({ peer->id, data.vid().v }); + applyDeleted(peer, data.vid().v); return StoryIdDates(); }); } @@ -571,9 +629,10 @@ void Stories::loadMore(StorySourcesList list) { result.match([&](const MTPDstories_allStories &data) { _owner->processUsers(data.vusers()); + _owner->processChats(data.vchats()); _sourcesStates[index] = qs(data.vstate()); _sourcesLoaded[index] = !data.is_has_more(); - for (const auto &single : data.vuser_stories().v) { + for (const auto &single : data.vpeer_stories().v) { parseAndApply(single); } }, [](const MTPDstories_allStoriesNotModified &) { @@ -608,8 +667,8 @@ void Stories::preloadListsMore() { loadMore(StorySourcesList::NotHidden); } else if (!countLoaded(StorySourcesList::Hidden)) { loadMore(StorySourcesList::Hidden); - } else if (!archiveCountKnown()) { - archiveLoadMore(); + } else if (!archiveCountKnown(_owner->session().userPeerId())) { + archiveLoadMore(_owner->session().userPeerId()); } } @@ -686,18 +745,15 @@ void Stories::sendResolveRequests() { crl::on_main(&session(), [=] { sendResolveRequests(); }); } }; - const auto user = _owner->session().data().peer(peerId)->asUser(); - if (!user) { - finish(peerId); - continue; - } + const auto peer = _owner->session().data().peer(peerId); api->request(MTPstories_GetStoriesByID( - user->inputUser, + peer->input, MTP_vector(prepared) )).done([=](const MTPstories_Stories &result) { owner().processUsers(result.data().vusers()); - processResolvedStories(user, result.data().vstories().v); - finish(user->id); + owner().processChats(result.data().vchats()); + processResolvedStories(peer, result.data().vstories().v); + finish(peer->id); }).fail([=] { finish(peerId); }).send(); @@ -711,12 +767,12 @@ void Stories::processResolvedStories( for (const auto &item : list) { item.match([&](const MTPDstoryItem &data) { if (!parseAndApply(peer, data, now)) { - applyDeleted({ peer->id, data.vid().v }); + applyDeleted(peer, data.vid().v); } }, [&](const MTPDstoryItemSkipped &data) { LOG(("API Error: Unexpected storyItemSkipped in resolve.")); }, [&](const MTPDstoryItemDeleted &data) { - applyDeleted({ peer->id, data.vid().v }); + applyDeleted(peer, data.vid().v); }); } } @@ -727,19 +783,29 @@ void Stories::finalizeResolve(FullStoryId id) { LOG(("API Error: Could not resolve story %1_%2" ).arg(id.peer.value ).arg(id.story)); - applyDeleted(id); + applyDeleted(_owner->peer(id.peer), id.story); } } -void Stories::applyDeleted(FullStoryId id) { - applyRemovedFromActive(id); +void Stories::applyDeleted(not_null peer, StoryId id) { + const auto fullId = FullStoryId{ peer->id, id }; + applyRemovedFromActive(fullId); - _deleted.emplace(id); - const auto i = _stories.find(id.peer); + if (const auto channel = peer->asChannel()) { + if (!hasArchive(channel)) { + _peersWithDeletedStories.emplace(channel->id); + } + } + + _deleted.emplace(fullId); + const auto peerId = peer->id; + const auto i = _stories.find(peerId); if (i != end(_stories)) { - const auto j = i->second.find(id.story); + const auto j = i->second.find(id); if (j != end(i->second)) { - const auto &story = _deletingStories[id] = std::move(j->second); + const auto &story + = _deletingStories[fullId] + = std::move(j->second); _expiring.remove(story->expires(), story->fullId()); i->second.erase(j); @@ -747,32 +813,37 @@ void Stories::applyDeleted(FullStoryId id) { story.get(), UpdateFlag::Destroyed); removeDependencyStory(story.get()); - if (id.peer == session().userPeerId() - && _archive.list.remove(id.story)) { - if (_archiveTotal > 0) { - --_archiveTotal; - } - _archiveChanged.fire({}); - } - if (story->pinned()) { - if (const auto k = _saved.find(id.peer); k != end(_saved)) { - const auto saved = &k->second; - if (saved->ids.list.remove(id.story)) { - if (saved->total > 0) { - --saved->total; + if (hasArchive(story->peer())) { + if (const auto k = _archive.find(peerId) + ; k != end(_archive)) { + const auto archive = &k->second; + if (archive->ids.list.remove(id)) { + if (archive->total > 0) { + --archive->total; } - _savedChanged.fire_copy(id.peer); + _archiveChanged.fire_copy(peerId); } } } - if (_preloading && _preloading->id() == id) { - _preloading = nullptr; - preloadFinished(id); + if (story->pinned()) { + if (const auto k = _saved.find(peerId); k != end(_saved)) { + const auto saved = &k->second; + if (saved->ids.list.remove(id)) { + if (saved->total > 0) { + --saved->total; + } + _savedChanged.fire_copy(peerId); + } + } } - _owner->refreshStoryItemViews(id); + if (_preloading && _preloading->id() == fullId) { + _preloading = nullptr; + preloadFinished(fullId); + } + _owner->refreshStoryItemViews(fullId); Assert(!_pollingSettings.contains(story.get())); - if (const auto j = _items.find(id.peer); j != end(_items)) { - const auto k = j->second.find(id.story); + if (const auto j = _items.find(peerId); j != end(_items)) { + const auto k = j->second.find(id); if (k != end(j->second)) { Assert(!k->second.lock()); j->second.erase(k); @@ -784,7 +855,7 @@ void Stories::applyDeleted(FullStoryId id) { if (i->second.empty()) { _stories.erase(i); } - _deletingStories.remove(id); + _deletingStories.remove(fullId); } } } @@ -792,8 +863,8 @@ void Stories::applyDeleted(FullStoryId id) { void Stories::applyExpired(FullStoryId id) { if (const auto maybeStory = lookup(id)) { const auto story = *maybeStory; - if (!story->peer()->isSelf() && !story->pinned()) { - applyDeleted(id); + if (!hasArchive(story->peer()) && !story->pinned()) { + applyDeleted(story->peer(), id.story); return; } } @@ -818,14 +889,14 @@ void Stories::applyRemovedFromActive(FullStoryId id) { const auto j = i->second.ids.lower_bound(StoryIdDates{ id.story }); if (j != end(i->second.ids) && j->id == id.story) { i->second.ids.erase(j); - const auto user = i->second.user; + const auto peer = i->second.peer; if (i->second.ids.empty()) { _all.erase(i); removeFromList(StorySourcesList::NotHidden); removeFromList(StorySourcesList::Hidden); } _sourceChanged.fire_copy(id.peer); - updateUserStoriesState(user); + updatePeerStoriesState(peer); } } } @@ -913,7 +984,7 @@ void Stories::sendReaction(FullStoryId id, Data::ReactionId reaction) { const auto api = &session().api(); api->request(MTPstories_SendReaction( MTP_flags(0), - story->peer()->asUser()->inputUser, + story->peer()->input, MTP_int(id.story), ReactionToMTP(reaction) )).send(); @@ -1101,7 +1172,7 @@ bool Stories::bumpReadTill(PeerId peerId, StoryId maxReadTill) { if (till < maxReadTill) { const auto from = till; till = maxReadTill; - updateUserStoriesState(_owner->peer(peerId)); + updatePeerStoriesState(_owner->peer(peerId)); const auto i = _stories.find(peerId); if (i != end(_stories)) { refreshItems = ranges::make_subrange( @@ -1144,19 +1215,16 @@ void Stories::toggleHidden( PeerId peerId, bool hidden, std::shared_ptr show) { - const auto user = _owner->peer(peerId)->asUser(); - Assert(user != nullptr); - if (user->hasStoriesHidden() != hidden) { - user->setFlags(hidden - ? (user->flags() | UserDataFlag::StoriesHidden) - : (user->flags() & ~UserDataFlag::StoriesHidden)); - session().api().request(MTPcontacts_ToggleStoriesHidden( - user->inputUser, + const auto peer = _owner->peer(peerId); + if (peer->hasStoriesHidden() != hidden) { + peer->setStoriesHidden(hidden); + session().api().request(MTPstories_TogglePeerStoriesHidden( + peer->input, MTP_bool(hidden) )).send(); } - const auto name = user->shortName(); + const auto name = peer->shortName(); const auto guard = gsl::finally([&] { if (show) { const auto phrase = hidden @@ -1213,8 +1281,6 @@ void Stories::toggleHidden( void Stories::sendMarkAsReadRequest( not_null peer, StoryId tillId) { - Expects(peer->isUser()); - // AyuGram sendReadStories const auto settings = &AyuSettings::getInstance(); @@ -1241,7 +1307,7 @@ void Stories::sendMarkAsReadRequest( }; const auto api = &_owner->session().api(); api->request(MTPstories_ReadStories( - peer->asUser()->inputUser, + peer->input, MTP_int(tillId) )).done(finish).fail(finish).send(); } @@ -1265,7 +1331,7 @@ void Stories::sendMarkAsReadRequests() { } const auto j = _all.find(peerId); if (j != end(_all)) { - sendMarkAsReadRequest(j->second.user, j->second.readTill); + sendMarkAsReadRequest(j->second.peer, j->second.readTill); } i = _markReadPending.erase(i); } @@ -1313,7 +1379,7 @@ void Stories::sendIncrementViewsRequests() { checkQuitPreventFinished(); }; api->request(MTPstories_IncrementStoryViews( - _owner->peer(peer)->asUser()->inputUser, + _owner->peer(peer)->input, MTP_vector(std::move(ids)) )).done(finish).fail(finish).send(); _incrementViewsPending.remove(peer); @@ -1321,10 +1387,14 @@ void Stories::sendIncrementViewsRequests() { } void Stories::loadViewsSlice( + not_null peer, StoryId id, QString offset, Fn done) { - if (_viewsStoryId == id + Expects(peer->isSelf() || !done); + + if (_viewsStoryPeer == peer + && _viewsStoryId == id && _viewsOffset == offset && (!offset.isEmpty() || _viewsRequestId)) { if (_viewsRequestId) { @@ -1332,20 +1402,32 @@ void Stories::loadViewsSlice( } return; } + _viewsStoryPeer = peer; _viewsStoryId = id; _viewsOffset = offset; _viewsDone = std::move(done); - const auto api = &_owner->session().api(); - const auto perPage = _viewsDone ? kViewsPerPage : kPollingViewsPerPage; - api->request(_viewsRequestId).cancel(); + if (peer->isSelf()) { + sendViewsSliceRequest(); + } else { + sendViewsCountsRequest(); + } +} + +void Stories::sendViewsSliceRequest() { + Expects(_viewsStoryPeer != nullptr); + Expects(_viewsStoryPeer->isSelf()); + using Flag = MTPstories_GetStoryViewsList::Flag; + const auto api = &_owner->session().api(); + _owner->session().api().request(_viewsRequestId).cancel(); _viewsRequestId = api->request(MTPstories_GetStoryViewsList( MTP_flags(Flag::f_reactions_first), + _viewsStoryPeer->input, MTPstring(), // q - MTP_int(id), - MTP_string(offset), - MTP_int(perPage) + MTP_int(_viewsStoryId), + MTP_string(_viewsOffset), + MTP_int(_viewsDone ? kViewsPerPage : kPollingViewsPerPage) )).done([=](const MTPstories_StoryViewsList &result) { _viewsRequestId = 0; @@ -1384,29 +1466,72 @@ void Stories::loadViewsSlice( }).send(); } -const StoriesIds &Stories::archive() const { - return _archive; +void Stories::sendViewsCountsRequest() { + Expects(_viewsStoryPeer != nullptr); + Expects(!_viewsDone); + + const auto api = &_owner->session().api(); + _owner->session().api().request(_viewsRequestId).cancel(); + _viewsRequestId = api->request(MTPstories_GetStoriesViews( + _viewsStoryPeer->input, + MTP_vector(1, MTP_int(_viewsStoryId)) + )).done([=](const MTPstories_StoryViews &result) { + _viewsRequestId = 0; + + const auto &data = result.data(); + _owner->processUsers(data.vusers()); + if (data.vviews().v.size() == 1) { + const auto fullId = FullStoryId{ + _viewsStoryPeer->id, + _viewsStoryId, + }; + if (const auto story = lookup(fullId)) { + (*story)->applyViewsCounts(data.vviews().v.front().data()); + } + } + }).fail([=] { + _viewsRequestId = 0; + }).send(); } -rpl::producer<> Stories::archiveChanged() const { +bool Stories::hasArchive(not_null peer) const { + if (peer->isSelf()) { + return true; + } else if (const auto channel = peer->asChannel()) { + return channel->canEditStories(); + } + return false; +} + +const StoriesIds &Stories::archive(PeerId peerId) const { + static const auto empty = StoriesIds(); + const auto i = _archive.find(peerId); + return (i != end(_archive)) ? i->second.ids : empty; +} + +rpl::producer Stories::archiveChanged() const { return _archiveChanged.events(); } -int Stories::archiveCount() const { - return std::max(_archiveTotal, 0); +int Stories::archiveCount(PeerId peerId) const { + const auto i = _archive.find(peerId); + return (i != end(_archive)) ? i->second.total : 0; } -bool Stories::archiveCountKnown() const { - return _archiveTotal >= 0; +bool Stories::archiveCountKnown(PeerId peerId) const { + const auto i = _archive.find(peerId); + return (i != end(_archive)) && (i->second.total >= 0); } -bool Stories::archiveLoaded() const { - return _archiveLoaded; +bool Stories::archiveLoaded(PeerId peerId) const { + const auto i = _archive.find(peerId); + return (i != end(_archive)) && i->second.loaded; } -const StoriesIds *Stories::saved(PeerId peerId) const { +const StoriesIds &Stories::saved(PeerId peerId) const { + static const auto empty = StoriesIds(); const auto i = _saved.find(peerId); - return (i != end(_saved)) ? &i->second.ids : nullptr; + return (i != end(_saved)) ? i->second.ids : empty; } rpl::producer Stories::savedChanged() const { @@ -1428,49 +1553,57 @@ bool Stories::savedLoaded(PeerId peerId) const { return (i != end(_saved)) && i->second.loaded; } -void Stories::archiveLoadMore() { - if (_archiveRequestId || _archiveLoaded) { +void Stories::archiveLoadMore(PeerId peerId) { + const auto peer = _owner->peer(peerId); + const auto archive = lookupArchive(peer); + if (!archive || archive->requestId || archive->loaded) { return; } const auto api = &_owner->session().api(); - _archiveRequestId = api->request(MTPstories_GetStoriesArchive( - MTP_int(_archiveLastId), - MTP_int(_archiveLastId ? kArchivePerPage : kArchiveFirstPerPage) + archive->requestId = api->request(MTPstories_GetStoriesArchive( + peer->input, + MTP_int(archive->lastId), + MTP_int(archive->lastId ? kArchivePerPage : kArchiveFirstPerPage) )).done([=](const MTPstories_Stories &result) { - _archiveRequestId = 0; + const auto archive = lookupArchive(peer); + if (!archive) { + return; + } + archive->requestId = 0; const auto &data = result.data(); - const auto self = _owner->session().user(); const auto now = base::unixtime::now(); - _archiveTotal = data.vcount().v; + archive->total = data.vcount().v; for (const auto &story : data.vstories().v) { const auto id = story.match([&](const auto &id) { return id.vid().v; }); - _archive.list.emplace(id); - _archiveLastId = id; - if (!parseAndApply(self, story, now)) { - _archive.list.remove(id); - if (_archiveTotal > 0) { - --_archiveTotal; + archive->ids.list.emplace(id); + archive->lastId = id; + if (!parseAndApply(peer, story, now)) { + archive->ids.list.remove(id); + if (archive->total > 0) { + --archive->total; } } } - const auto ids = int(_archive.list.size()); - _archiveLoaded = data.vstories().v.empty(); - _archiveTotal = _archiveLoaded ? ids : std::max(_archiveTotal, ids); - _archiveChanged.fire({}); + const auto ids = int(archive->ids.list.size()); + archive->loaded = data.vstories().v.empty(); + archive->total = archive->loaded ? ids : std::max(archive->total, ids); + _archiveChanged.fire_copy(peerId); }).fail([=] { - _archiveRequestId = 0; - _archiveLoaded = true; - _archiveTotal = int(_archive.list.size()); - _archiveChanged.fire({}); + const auto archive = lookupArchive(peer); + if (!archive) { + return; + } + archive->requestId = 0; + archive->loaded = true; + archive->total = int(archive->ids.list.size()); + _archiveChanged.fire_copy(peerId); }).send(); } void Stories::savedLoadMore(PeerId peerId) { - Expects(peerIsUser(peerId)); - auto &saved = _saved[peerId]; if (saved.requestId || saved.loaded) { return; @@ -1478,7 +1611,7 @@ void Stories::savedLoadMore(PeerId peerId) { const auto api = &_owner->session().api(); const auto peer = _owner->peer(peerId); saved.requestId = api->request(MTPstories_GetPinnedStories( - peer->asUser()->inputUser, + peer->input, MTP_int(saved.lastId), MTP_int(saved.lastId ? kSavedPerPage : kSavedFirstPerPage) )).done([=](const MTPstories_Stories &result) { @@ -1515,34 +1648,39 @@ void Stories::savedLoadMore(PeerId peerId) { } void Stories::deleteList(const std::vector &ids) { + if (ids.empty()) { + return; + } + const auto peer = session().data().peer(ids.front().peer); auto list = QVector(); list.reserve(ids.size()); - const auto selfId = session().userPeerId(); for (const auto &id : ids) { - if (id.peer == selfId) { + if (id.peer == peer->id) { list.push_back(MTP_int(id.story)); } } - if (!list.empty()) { - const auto api = &_owner->session().api(); - api->request(MTPstories_DeleteStories( - MTP_vector(list) - )).done([=](const MTPVector &result) { - for (const auto &id : result.v) { - applyDeleted({ selfId, id.v }); - } - }).send(); - } + const auto api = &_owner->session().api(); + api->request(MTPstories_DeleteStories( + peer->input, + MTP_vector(list) + )).done([=](const MTPVector &result) { + for (const auto &id : result.v) { + applyDeleted(peer, id.v); + } + }).send(); } void Stories::togglePinnedList( const std::vector &ids, bool pinned) { + if (ids.empty()) { + return; + } + const auto peer = session().data().peer(ids.front().peer); auto list = QVector(); list.reserve(ids.size()); - const auto selfId = session().userPeerId(); for (const auto &id : ids) { - if (id.peer == selfId) { + if (id.peer == peer->id) { list.push_back(MTP_int(id.story)); } } @@ -1551,10 +1689,12 @@ void Stories::togglePinnedList( } const auto api = &_owner->session().api(); api->request(MTPstories_TogglePinned( + peer->input, MTP_vector(list), MTP_bool(pinned) )).done([=](const MTPVector &result) { - auto &saved = _saved[selfId]; + const auto peerId = peer->id; + auto &saved = _saved[peerId]; const auto loaded = saved.loaded; const auto lastId = !saved.ids.list.empty() ? saved.ids.list.back() @@ -1563,7 +1703,7 @@ void Stories::togglePinnedList( : std::numeric_limits::max(); auto dirty = false; for (const auto &id : result.v) { - if (const auto maybeStory = lookup({ selfId, id.v })) { + if (const auto maybeStory = lookup({ peerId, id.v })) { const auto story = *maybeStory; story->setPinned(pinned); if (pinned) { @@ -1587,9 +1727,9 @@ void Stories::togglePinnedList( } } if (dirty) { - savedLoadMore(selfId); + savedLoadMore(peerId); } else { - _savedChanged.fire_copy(selfId); + _savedChanged.fire_copy(peerId); } }).send(); } @@ -1684,7 +1824,7 @@ std::optional Stories::peerSourceState( }; } requestReadTills(); - _pendingUserStateMaxId[peer] = storyMaxId; + _pendingPeerStateMaxId[peer] = storyMaxId; return std::nullopt; } @@ -1693,12 +1833,12 @@ void Stories::requestReadTills() { return; } const auto api = &_owner->session().api(); - _readTillsRequestId = api->request(MTPstories_GetAllReadUserStories( + _readTillsRequestId = api->request(MTPstories_GetAllReadPeerStories( )).done([=](const MTPUpdates &result) { _readTillReceived = true; api->applyUpdates(result); - for (auto &[peer, maxId] : base::take(_pendingUserStateMaxId)) { - updateUserStoriesState(peer); + for (auto &[peer, maxId] : base::take(_pendingPeerStateMaxId)) { + updatePeerStoriesState(peer); } for (const auto &storyId : base::take(_pendingReadTillItems)) { _owner->refreshStoryItemViews(storyId); @@ -1723,7 +1863,7 @@ void Stories::registerPolling(not_null story, Polling polling) { case Polling::Chat: ++settings.chat; break; case Polling::Viewer: ++settings.viewer; - if (story->peer()->isSelf() + if ((story->peer()->isSelf() || story->peer()->isChannel()) && _pollingViews.emplace(story).second) { sendPollingViewsRequests(); } @@ -1816,29 +1956,28 @@ void Stories::sendPollingViewsRequests() { return; } else if (!_viewsRequestId) { Assert(_viewsDone == nullptr); - loadViewsSlice(_pollingViews.front()->id(), QString(), nullptr); + const auto story = _pollingViews.front(); + loadViewsSlice(story->peer(), story->id(), QString(), nullptr); } _pollingViewsTimer.callOnce(kPollViewsInterval); } -void Stories::updateUserStoriesState(not_null peer) { +void Stories::updatePeerStoriesState(not_null peer) { const auto till = _readTill.find(peer->id); const auto readTill = (till != end(_readTill)) ? till->second : 0; const auto pendingMaxId = [&] { - const auto j = _pendingUserStateMaxId.find(peer); - return (j != end(_pendingUserStateMaxId)) ? j->second : 0; + const auto j = _pendingPeerStateMaxId.find(peer); + return (j != end(_pendingPeerStateMaxId)) ? j->second : 0; }; const auto i = _all.find(peer->id); const auto max = (i != end(_all)) ? (i->second.ids.empty() ? 0 : i->second.ids.back().id) : pendingMaxId(); - if (const auto user = peer->asUser()) { - user->setStoriesState(!max - ? UserData::StoriesState::None - : (max <= readTill) - ? UserData::StoriesState::HasRead - : UserData::StoriesState::HasUnread); - } + peer->setStoriesState(!max + ? PeerData::StoriesState::None + : (max <= readTill) + ? PeerData::StoriesState::HasRead + : PeerData::StoriesState::HasUnread); } void Stories::preloadSourcesChanged(StorySourcesList list) { diff --git a/Telegram/SourceFiles/data/data_stories.h b/Telegram/SourceFiles/data/data_stories.h index 8d8b23da4..16cc27d38 100644 --- a/Telegram/SourceFiles/data/data_stories.h +++ b/Telegram/SourceFiles/data/data_stories.h @@ -52,7 +52,7 @@ struct StoriesSourceInfo { }; struct StoriesSource { - not_null user; + not_null peer; base::flat_set ids; StoryId readTill = 0; bool hidden = false; @@ -148,7 +148,7 @@ public: void apply(const MTPDupdateStory &data); void apply(const MTPDupdateReadStories &data); void apply(const MTPStoriesStealthMode &stealthMode); - void apply(not_null peer, const MTPUserStories *data); + void apply(not_null peer, const MTPPeerStories *data); Story *applyFromWebpage(PeerId peerId, const MTPstoryItem &story); void loadAround(FullStoryId id, StoriesContext context); @@ -178,18 +178,21 @@ public: static constexpr auto kViewsPerPage = 50; void loadViewsSlice( + not_null peer, StoryId id, QString offset, Fn done); - [[nodiscard]] const StoriesIds &archive() const; - [[nodiscard]] rpl::producer<> archiveChanged() const; - [[nodiscard]] int archiveCount() const; - [[nodiscard]] bool archiveCountKnown() const; - [[nodiscard]] bool archiveLoaded() const; - void archiveLoadMore(); + [[nodiscard]] bool hasArchive(not_null peer) const; - [[nodiscard]] const StoriesIds *saved(PeerId peerId) const; + [[nodiscard]] const StoriesIds &archive(PeerId peerId) const; + [[nodiscard]] rpl::producer archiveChanged() const; + [[nodiscard]] int archiveCount(PeerId peerId) const; + [[nodiscard]] bool archiveCountKnown(PeerId peerId) const; + [[nodiscard]] bool archiveLoaded(PeerId peerId) const; + void archiveLoadMore(PeerId peerId); + + [[nodiscard]] const StoriesIds &saved(PeerId peerId) const; [[nodiscard]] rpl::producer savedChanged() const; [[nodiscard]] int savedCount(PeerId peerId) const; [[nodiscard]] bool savedCountKnown(PeerId peerId) const; @@ -228,8 +231,8 @@ public: [[nodiscard]] bool registerPolling(FullStoryId id, Polling polling); void unregisterPolling(FullStoryId id, Polling polling); - void requestUserStories( - not_null user, + void requestPeerStories( + not_null peer, Fn done = nullptr); void savedStateChanged(not_null story); @@ -243,19 +246,20 @@ public: void sendReaction(FullStoryId id, Data::ReactionId reaction); private: - struct Saved { + struct Set { StoriesIds ids; int total = -1; StoryId lastId = 0; bool loaded = false; mtpRequestId requestId = 0; }; + struct PollingSettings { int chat = 0; int viewer = 0; }; - void parseAndApply(const MTPUserStories &stories); + void parseAndApply(const MTPPeerStories &stories); [[nodiscard]] Story *parseAndApply( not_null peer, const MTPDstoryItem &data, @@ -269,9 +273,12 @@ private: const QVector &list); void sendResolveRequests(); void finalizeResolve(FullStoryId id); - void updateUserStoriesState(not_null peer); + void updatePeerStoriesState(not_null peer); - void applyDeleted(FullStoryId id); + [[nodiscard]] Set *lookupArchive(not_null peer); + void clearArchive(not_null peer); + + void applyDeleted(not_null peer, StoryId id); void applyExpired(FullStoryId id); void applyRemovedFromActive(FullStoryId id); void applyDeletedFromSources(PeerId id, StorySourcesList list); @@ -309,6 +316,8 @@ private: TimeId now); void sendPollingRequests(); void sendPollingViewsRequests(); + void sendViewsSliceRequest(); + void sendViewsCountsRequest(); const not_null _owner; std::unordered_map< @@ -319,6 +328,7 @@ private: PeerId, base::flat_map>> _items; base::flat_multi_map _expiring; + base::flat_set _peersWithDeletedStories; base::flat_set _deleted; base::Timer _expireTimer; bool _expireSchedulePosted = false; @@ -346,27 +356,24 @@ private: rpl::event_stream _sourceChanged; rpl::event_stream _itemsChanged; - StoriesIds _archive; - int _archiveTotal = -1; - StoryId _archiveLastId = 0; - bool _archiveLoaded = false; - rpl::event_stream<> _archiveChanged; - mtpRequestId _archiveRequestId = 0; + std::unordered_map _archive; + rpl::event_stream _archiveChanged; - std::unordered_map _saved; + std::unordered_map _saved; rpl::event_stream _savedChanged; base::flat_set _markReadPending; base::Timer _markReadTimer; base::flat_set _markReadRequests; base::flat_map< - not_null, - std::vector>> _requestingUserStories; + not_null, + std::vector>> _requestingPeerStories; base::flat_map> _incrementViewsPending; base::Timer _incrementViewsTimer; base::flat_set _incrementViewsRequests; + PeerData *_viewsStoryPeer = nullptr; StoryId _viewsStoryId = 0; QString _viewsOffset; Fn _viewsDone; @@ -381,7 +388,7 @@ private: base::flat_map _readTill; base::flat_set _pendingReadTillItems; - base::flat_map, StoryId> _pendingUserStateMaxId; + base::flat_map, StoryId> _pendingPeerStateMaxId; mtpRequestId _readTillsRequestId = 0; bool _readTillReceived = false; @@ -392,6 +399,8 @@ private: rpl::variable _stealthMode; + rpl::lifetime _lifetime; + }; } // namespace Data diff --git a/Telegram/SourceFiles/data/data_stories_ids.cpp b/Telegram/SourceFiles/data/data_stories_ids.cpp index 7506abea5..f8921fe8b 100644 --- a/Telegram/SourceFiles/data/data_stories_ids.cpp +++ b/Telegram/SourceFiles/data/data_stories_ids.cpp @@ -32,19 +32,19 @@ rpl::producer SavedStoriesIds( const auto push = [=] { state->scheduled = false; + const auto peerId = peer->id; const auto stories = &peer->owner().stories(); - if (!stories->savedCountKnown(peer->id)) { + if (!stories->savedCountKnown(peerId)) { return; } - const auto saved = stories->saved(peer->id); - Assert(saved != nullptr); - const auto count = stories->savedCount(peer->id); - const auto around = saved->list.lower_bound(aroundId); - const auto hasBefore = int(around - begin(saved->list)); - const auto hasAfter = int(end(saved->list) - around); + const auto &saved = stories->saved(peerId); + const auto count = stories->savedCount(peerId); + const auto around = saved.list.lower_bound(aroundId); + const auto hasBefore = int(around - begin(saved.list)); + const auto hasAfter = int(end(saved.list) - around); if (hasAfter < limit) { - stories->savedLoadMore(peer->id); + stories->savedLoadMore(peerId); } const auto takeBefore = std::min(hasBefore, limit); const auto takeAfter = std::min(hasAfter, limit); @@ -72,14 +72,15 @@ rpl::producer SavedStoriesIds( }); }; + const auto peerId = peer->id; const auto stories = &peer->owner().stories(); stories->savedChanged( ) | rpl::filter( - rpl::mappers::_1 == peer->id + rpl::mappers::_1 == peerId ) | rpl::start_with_next(schedule, lifetime); - if (!stories->savedCountKnown(peer->id)) { - stories->savedLoadMore(peer->id); + if (!stories->savedCountKnown(peerId)) { + stories->savedLoadMore(peerId); } push(); @@ -89,7 +90,7 @@ rpl::producer SavedStoriesIds( } rpl::producer ArchiveStoriesIds( - not_null session, + not_null peer, StoryId aroundId, int limit) { return [=](auto consumer) { @@ -105,18 +106,19 @@ rpl::producer ArchiveStoriesIds( const auto push = [=] { state->scheduled = false; - const auto stories = &session->data().stories(); - if (!stories->archiveCountKnown()) { + const auto peerId = peer->id; + const auto stories = &peer->owner().stories(); + if (!stories->archiveCountKnown(peerId)) { return; } - const auto &archive = stories->archive(); - const auto count = stories->archiveCount(); + const auto &archive = stories->archive(peerId); + const auto count = stories->archiveCount(peerId); const auto i = archive.list.lower_bound(aroundId); const auto hasBefore = int(i - begin(archive.list)); const auto hasAfter = int(end(archive.list) - i); if (hasAfter < limit) { - stories->archiveLoadMore(); + stories->archiveLoadMore(peerId); } const auto takeBefore = std::min(hasBefore, limit); const auto takeAfter = std::min(hasAfter, limit); @@ -144,12 +146,13 @@ rpl::producer ArchiveStoriesIds( }); }; - const auto stories = &session->data().stories(); + const auto peerId = peer->id; + const auto stories = &peer->owner().stories(); stories->archiveChanged( ) | rpl::start_with_next(schedule, lifetime); - if (!stories->archiveCountKnown()) { - stories->archiveLoadMore(); + if (!stories->archiveCountKnown(peerId)) { + stories->archiveLoadMore(peerId); } push(); diff --git a/Telegram/SourceFiles/data/data_stories_ids.h b/Telegram/SourceFiles/data/data_stories_ids.h index 26f827f96..6c95c16a9 100644 --- a/Telegram/SourceFiles/data/data_stories_ids.h +++ b/Telegram/SourceFiles/data/data_stories_ids.h @@ -25,7 +25,7 @@ using StoriesIdsSlice = AbstractSparseIds>; int limit); [[nodiscard]] rpl::producer ArchiveStoriesIds( - not_null session, + not_null peer, StoryId aroundId, int limit); diff --git a/Telegram/SourceFiles/data/data_story.cpp b/Telegram/SourceFiles/data/data_story.cpp index d5faa4350..7500071c5 100644 --- a/Telegram/SourceFiles/data/data_story.cpp +++ b/Telegram/SourceFiles/data/data_story.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_text_entities.h" #include "data/data_document.h" #include "data/data_changes.h" +#include "data/data_channel.h" #include "data/data_file_origin.h" #include "data/data_photo.h" #include "data/data_photo_media.h" @@ -66,6 +67,25 @@ using UpdateFlag = StoryUpdate::Flag; }); }, [](const MTPDgeoPointEmpty &) { }); + }, [&](const MTPDmediaAreaSuggestedReaction &data) { + }, [&](const MTPDinputMediaAreaVenue &data) { + LOG(("API Error: Unexpected inputMediaAreaVenue in API data.")); + }); + return result; +} + +[[nodiscard]] auto ParseSuggestedReaction(const MTPMediaArea &area) +-> std::optional { + auto result = std::optional(); + area.match([&](const MTPDmediaAreaVenue &data) { + }, [&](const MTPDmediaAreaGeoPoint &data) { + }, [&](const MTPDmediaAreaSuggestedReaction &data) { + result.emplace(SuggestedReaction{ + .area = ParseArea(data.vcoordinates()), + .reaction = Data::ReactionFromMTP(data.vreaction()), + .flipped = data.is_flipped(), + .dark = data.is_dark(), + }); }, [&](const MTPDinputMediaAreaVenue &data) { LOG(("API Error: Unexpected inputMediaAreaVenue in API data.")); }); @@ -317,6 +337,10 @@ bool Story::edited() const { return _edited; } +bool Story::out() const { + return _out; +} + bool Story::canDownloadIfPremium() const { return !forbidsForward() || _peer->isSelf(); } @@ -331,6 +355,10 @@ bool Story::canShare() const { } bool Story::canDelete() const { + if (const auto channel = _peer->asChannel()) { + return channel->canDeleteStories() + || (out() && channel->canPostStories()); + } return _peer->isSelf(); } @@ -382,11 +410,32 @@ Data::ReactionId Story::sentReactionId() const { void Story::setReactionId(Data::ReactionId id) { if (_sentReactionId != id) { + const auto wasEmpty = _sentReactionId.empty(); + changeSuggestedReactionCount(_sentReactionId, -1); _sentReactionId = id; + changeSuggestedReactionCount(id, 1); + + if (_views.known && _sentReactionId.empty() != wasEmpty) { + const auto delta = wasEmpty ? 1 : -1; + if (_views.reactions + delta >= 0) { + _views.reactions += delta; + } + } session().changes().storyUpdated(this, UpdateFlag::Reaction); } } +void Story::changeSuggestedReactionCount(Data::ReactionId id, int delta) { + if (id.empty() || !_peer->isChannel()) { + return; + } + for (auto &suggested : _suggestedReactions) { + if (suggested.reaction == id && suggested.count + delta >= 0) { + suggested.count += delta; + } + } +} + const std::vector> &Story::recentViewers() const { return _recentViewers; } @@ -410,6 +459,7 @@ void Story::applyViewsSlice( || (_views.total != slice.total); _views.reactions = slice.reactions; _views.total = slice.total; + _views.known = true; if (offset.isEmpty()) { _views = slice; } else if (_views.nextOffset == offset) { @@ -440,14 +490,14 @@ void Story::applyViewsSlice( // Count not changed, but list of recent viewers changed. _peer->session().changes().storyUpdated( this, - UpdateFlag::ViewsAdded); + UpdateFlag::ViewsChanged); } } } if (changed) { _peer->session().changes().storyUpdated( this, - UpdateFlag::ViewsAdded); + UpdateFlag::ViewsChanged); } } @@ -455,6 +505,10 @@ const std::vector &Story::locations() const { return _locations; } +const std::vector &Story::suggestedReactions() const { + return _suggestedReactions; +} + void Story::applyChanges( StoryMedia media, const MTPDstoryItem &data, @@ -462,6 +516,46 @@ void Story::applyChanges( applyFields(std::move(media), data, now, false); } +Story::ViewsCounts Story::parseViewsCounts( + const MTPDstoryViews &data, + const Data::ReactionId &mine) { + auto result = ViewsCounts{ + .views = data.vviews_count().v, + .reactions = data.vreactions_count().value_or_empty(), + }; + if (const auto list = data.vrecent_viewers()) { + result.viewers.reserve(list->v.size()); + auto &owner = _peer->owner(); + auto &&cut = list->v + | ranges::views::take(kRecentViewersMax); + for (const auto &id : cut) { + result.viewers.push_back(owner.peer(peerFromUser(id))); + } + } + auto total = 0; + if (const auto list = data.vreactions() + ; list && _peer->isChannel()) { + result.reactionsCounts.reserve(list->v.size()); + for (const auto &reaction : list->v) { + const auto &data = reaction.data(); + const auto id = Data::ReactionFromMTP(data.vreaction()); + const auto count = data.vcount().v; + result.reactionsCounts[id] = count; + total += count; + } + } + if (!mine.empty()) { + if (auto &count = result.reactionsCounts[mine]; !count) { + count = 1; + ++total; + } + } + if (result.reactions < total) { + result.reactions = total; + } + return result; +} + void Story::applyFields( StoryMedia media, const MTPDstoryItem &data, @@ -469,7 +563,9 @@ void Story::applyFields( bool initial) { _lastUpdateTime = now; - const auto reaction = data.vsent_reaction() + const auto reaction = data.is_min() + ? _sentReactionId + : data.vsent_reaction() ? Data::ReactionFromMTP(*data.vsent_reaction()) : Data::ReactionId(); const auto pinned = data.is_pinned(); @@ -484,36 +580,43 @@ void Story::applyFields( ? StoryPrivacy::SelectedContacts : StoryPrivacy::Other; const auto noForwards = data.is_noforwards(); + const auto out = data.is_min() ? _out : data.is_out(); auto caption = TextWithEntities{ data.vcaption().value_or_empty(), Api::EntitiesFromMTP( &owner().session(), data.ventities().value_or_empty()), }; - auto views = _views.total; - auto reactions = _views.reactions; - auto viewers = std::vector>(); - if (!data.is_min()) { - if (const auto info = data.vviews()) { - views = info->data().vviews_count().v; - reactions = info->data().vreactions_count().v; - if (const auto list = info->data().vrecent_viewers()) { - viewers.reserve(list->v.size()); - auto &owner = _peer->owner(); - auto &&cut = list->v - | ranges::views::take(kRecentViewersMax); - for (const auto &id : cut) { - viewers.push_back(owner.peer(peerFromUser(id))); - } + auto counts = ViewsCounts(); + auto viewsKnown = _views.known; + if (const auto info = data.vviews()) { + counts = parseViewsCounts(info->data(), reaction); + viewsKnown = true; + } else { + counts.views = _views.total; + counts.reactions = _views.reactions; + counts.viewers = _recentViewers; + for (const auto &suggested : _suggestedReactions) { + if (const auto count = suggested.count) { + counts.reactionsCounts[suggested.reaction] = count; } } } auto locations = std::vector(); + auto suggestedReactions = std::vector(); if (const auto areas = data.vmedia_areas()) { locations.reserve(areas->v.size()); + suggestedReactions.reserve(areas->v.size()); for (const auto &area : areas->v) { - if (const auto parsed = ParseLocation(area)) { - locations.push_back(*parsed); + if (const auto location = ParseLocation(area)) { + locations.push_back(*location); + } else if (auto reaction = ParseSuggestedReaction(area)) { + const auto i = counts.reactionsCounts.find( + reaction->reaction); + if (i != end(counts.reactionsCounts)) { + reaction->count = i->second; + } + suggestedReactions.push_back(*reaction); } } } @@ -522,26 +625,19 @@ void Story::applyFields( const auto editedChanged = (_edited != edited); const auto mediaChanged = (_media != media); const auto captionChanged = (_caption != caption); - const auto viewsChanged = (_views.total != views) - || (_views.reactions != reactions) - || (_recentViewers != viewers); const auto locationsChanged = (_locations != locations); + const auto suggestedReactionsChanged + = (_suggestedReactions != suggestedReactions); const auto reactionChanged = (_sentReactionId != reaction); + _out = out; _privacyPublic = (privacy == StoryPrivacy::Public); _privacyCloseFriends = (privacy == StoryPrivacy::CloseFriends); _privacyContacts = (privacy == StoryPrivacy::Contacts); _privacySelectedContacts = (privacy == StoryPrivacy::SelectedContacts); - _noForwards = noForwards; _edited = edited; _pinned = pinned; _noForwards = noForwards; - if (_views.reactions != reactions || _views.total != views) { - _views = StoryViews{ .reactions = reactions, .total = views }; - } - if (viewsChanged) { - _recentViewers = std::move(viewers); - } if (mediaChanged) { _media = std::move(media); } @@ -551,19 +647,24 @@ void Story::applyFields( if (locationsChanged) { _locations = std::move(locations); } + if (suggestedReactionsChanged) { + _suggestedReactions = std::move(suggestedReactions); + } if (reactionChanged) { _sentReactionId = reaction; } + updateViewsCounts(std::move(counts), viewsKnown, initial); const auto changed = editedChanged || captionChanged || mediaChanged || locationsChanged; - if (!initial && (changed || viewsChanged || reactionChanged)) { + const auto reactionsChanged = reactionChanged + || suggestedReactionsChanged; + if (!initial && (changed || reactionsChanged)) { _peer->session().changes().storyUpdated(this, UpdateFlag() | (changed ? UpdateFlag::Edited : UpdateFlag()) - | (viewsChanged ? UpdateFlag::ViewsAdded : UpdateFlag()) - | (reactionChanged ? UpdateFlag::Reaction : UpdateFlag())); + | (reactionsChanged ? UpdateFlag::Reaction : UpdateFlag())); } if (!initial && (captionChanged || mediaChanged)) { if (const auto item = _peer->owner().stories().lookupItem(this)) { @@ -576,6 +677,44 @@ void Story::applyFields( } } +void Story::updateViewsCounts(ViewsCounts &&counts, bool known, bool initial) { + const auto viewsChanged = (_views.total != counts.views) + || (_views.reactions != counts.reactions) + || (_recentViewers != counts.viewers); + if (_views.reactions != counts.reactions + || _views.total != counts.views + || _views.known != known) { + _views = StoryViews{ + .reactions = counts.reactions, + .total = counts.views, + .known = known, + }; + } + if (viewsChanged) { + _recentViewers = std::move(counts.viewers); + _peer->session().changes().storyUpdated( + this, + UpdateFlag::ViewsChanged); + } +} + +void Story::applyViewsCounts(const MTPDstoryViews &data) { + auto counts = parseViewsCounts(data, _sentReactionId); + auto suggestedCountsChanged = false; + for (auto &suggested : _suggestedReactions) { + const auto i = counts.reactionsCounts.find(suggested.reaction); + const auto v = (i != end(counts.reactionsCounts)) ? i->second : 0; + if (suggested.count != v) { + suggested.count = v; + suggestedCountsChanged = true; + } + } + updateViewsCounts(std::move(counts), true, false); + if (suggestedCountsChanged) { + _peer->session().changes().storyUpdated(this, UpdateFlag::Reaction); + } +} + TimeId Story::lastUpdateTime() const { return _lastUpdateTime; } diff --git a/Telegram/SourceFiles/data/data_story.h b/Telegram/SourceFiles/data/data_story.h index dcfe4495c..7ebe6bbde 100644 --- a/Telegram/SourceFiles/data/data_story.h +++ b/Telegram/SourceFiles/data/data_story.h @@ -71,6 +71,7 @@ struct StoryViews { QString nextOffset; int reactions = 0; int total = 0; + bool known = false; }; struct StoryArea { @@ -96,6 +97,18 @@ struct StoryLocation { const StoryLocation &) = default; }; +struct SuggestedReaction { + StoryArea area; + Data::ReactionId reaction; + int count = 0; + bool flipped = false; + bool dark = false; + + friend inline bool operator==( + const SuggestedReaction &, + const SuggestedReaction &) = default; +}; + class Story final { public: Story( @@ -132,6 +145,7 @@ public: [[nodiscard]] StoryPrivacy privacy() const; [[nodiscard]] bool forbidsForward() const; [[nodiscard]] bool edited() const; + [[nodiscard]] bool out() const; [[nodiscard]] bool canDownloadIfPremium() const; [[nodiscard]] bool canDownloadChecked() const; @@ -157,20 +171,36 @@ public: void applyViewsSlice(const QString &offset, const StoryViews &slice); [[nodiscard]] const std::vector &locations() const; + [[nodiscard]] auto suggestedReactions() const + -> const std::vector &; void applyChanges( StoryMedia media, const MTPDstoryItem &data, TimeId now); + void applyViewsCounts(const MTPDstoryViews &data); [[nodiscard]] TimeId lastUpdateTime() const; private: + struct ViewsCounts { + int views = 0; + int reactions = 0; + base::flat_map reactionsCounts; + std::vector> viewers; + }; + + void changeSuggestedReactionCount(Data::ReactionId id, int delta); void applyFields( StoryMedia media, const MTPDstoryItem &data, TimeId now, bool initial); + void updateViewsCounts(ViewsCounts &&counts, bool known, bool initial); + [[nodiscard]] ViewsCounts parseViewsCounts( + const MTPDstoryViews &data, + const Data::ReactionId &mine); + const StoryId _id = 0; const not_null _peer; Data::ReactionId _sentReactionId; @@ -178,10 +208,12 @@ private: TextWithEntities _caption; std::vector> _recentViewers; std::vector _locations; + std::vector _suggestedReactions; StoryViews _views; const TimeId _date = 0; const TimeId _expires = 0; TimeId _lastUpdateTime = 0; + bool _out : 1 = false; bool _pinned : 1 = false; bool _privacyPublic : 1 = false; bool _privacyCloseFriends : 1 = false; diff --git a/Telegram/SourceFiles/data/data_types.cpp b/Telegram/SourceFiles/data/data_types.cpp index 0873bd7ec..ba8a44470 100644 --- a/Telegram/SourceFiles/data/data_types.cpp +++ b/Telegram/SourceFiles/data/data_types.cpp @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_types.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "storage/cache/storage_cache_types.h" #include "base/openssl_help.h" diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp index 59f7e5c87..0369774d9 100644 --- a/Telegram/SourceFiles/data/data_user.cpp +++ b/Telegram/SourceFiles/data/data_user.cpp @@ -132,18 +132,17 @@ void UserData::setPrivateForwardName(const QString &name) { } bool UserData::hasActiveStories() const { - return flags() & UserDataFlag::HasActiveStories; + return flags() & Flag::HasActiveStories; } bool UserData::hasUnreadStories() const { - return flags() & UserDataFlag::HasUnreadStories; + return flags() & Flag::HasUnreadStories; } void UserData::setStoriesState(StoriesState state) { Expects(state != StoriesState::Unknown); const auto was = flags(); - using Flag = UserDataFlag; switch (state) { case StoriesState::None: _flags.remove(Flag::HasActiveStories | Flag::HasUnreadStories); diff --git a/Telegram/SourceFiles/data/data_user.h b/Telegram/SourceFiles/data/data_user.h index ad6d0b7ae..96811051e 100644 --- a/Telegram/SourceFiles/data/data_user.h +++ b/Telegram/SourceFiles/data/data_user.h @@ -176,12 +176,6 @@ public: [[nodiscard]] QString privateForwardName() const; void setPrivateForwardName(const QString &name); - enum class StoriesState { - Unknown, - None, - HasRead, - HasUnread, - }; [[nodiscard]] bool hasActiveStories() const; [[nodiscard]] bool hasUnreadStories() const; void setStoriesState(StoriesState state); diff --git a/Telegram/SourceFiles/data/data_web_page.cpp b/Telegram/SourceFiles/data/data_web_page.cpp index 4a7b036e3..789f15ea5 100644 --- a/Telegram/SourceFiles/data/data_web_page.cpp +++ b/Telegram/SourceFiles/data/data_web_page.cpp @@ -176,6 +176,8 @@ WebPageType ParseWebPageType( return WebPageType::User; } else if (type == u"telegram_botapp"_q) { return WebPageType::BotApp; + } else if (type == u"telegram_channel_boost"_q) { + return WebPageType::ChannelBoost; } else if (hasIV) { return WebPageType::ArticleWithIV; } else { diff --git a/Telegram/SourceFiles/data/data_web_page.h b/Telegram/SourceFiles/data/data_web_page.h index 83e7bde80..94ca38358 100644 --- a/Telegram/SourceFiles/data/data_web_page.h +++ b/Telegram/SourceFiles/data/data_web_page.h @@ -23,6 +23,7 @@ enum class WebPageType { GroupWithRequest, Channel, ChannelWithRequest, + ChannelBoost, Photo, Video, diff --git a/Telegram/SourceFiles/data/notify/data_notify_settings.cpp b/Telegram/SourceFiles/data/notify/data_notify_settings.cpp index c522b8aa5..beb47698b 100644 --- a/Telegram/SourceFiles/data/notify/data_notify_settings.cpp +++ b/Telegram/SourceFiles/data/notify/data_notify_settings.cpp @@ -44,7 +44,7 @@ constexpr auto kMaxNotifyCheckDelay = 24 * 3600 * crl::time(1000); [[nodiscard]] bool SkipAddException(not_null peer) { if (const auto user = peer->asUser()) { - return user->isInaccessible(); + return user->isInaccessible() || user->isSelf(); } else if (const auto chat = peer->asChat()) { return chat->isDeactivated() || chat->isForbidden(); } else if (const auto channel = peer->asChannel()) { diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp index b9f34f030..54b16fec6 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp @@ -23,7 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ffmpeg/ffmpeg_frame_generator.h" #include "chat_helpers/stickers_lottie.h" #include "storage/file_download.h" // kMaxFileInMemory -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/text/text_custom_emoji.h" #include "ui/text/text_utilities.h" #include "ui/ui_utility.h" diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index 854faef97..b9a8c39b2 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -35,6 +35,12 @@ ForumTopicIcon { textTop: pixels; } +DialogsMiniIcon { + icon: ThreeStateIcon; + skipText: pixels; + skipMedia: pixels; +} + defaultForumTopicIcon: ForumTopicIcon { size: 21px; font: font(bold 11px); @@ -462,16 +468,24 @@ dialogsMiniPreviewSkip: 2px; dialogsMiniPreviewRight: 3px; dialogsMiniPlay: icon{{ "dialogs/dialogs_mini_play", videoPlayIconFg }}; -dialogsMiniForwardIcon: ThreeStateIcon { - icon: icon {{ "mini_forward", dialogsTextFg, point(0px, 1px) }}; - over: icon {{ "mini_forward", dialogsTextFgOver, point(0px, 1px) }}; - active: icon {{ "mini_forward", dialogsTextFgActive, point(0px, 1px) }}; +dialogsMiniForward: DialogsMiniIcon { + icon: ThreeStateIcon { + icon: icon {{ "mini_forward", dialogsTextFg, point(0px, 1px) }}; + over: icon {{ "mini_forward", dialogsTextFgOver, point(0px, 1px) }}; + active: icon {{ "mini_forward", dialogsTextFgActive, point(0px, 1px) }}; + } + skipText: 1px; + skipMedia: 2px; } -dialogsMiniIconSkip: 2px; -dialogsMiniReplyStoryIcon: ThreeStateIcon { - icon: icon {{ "mini_reply_story", dialogsTextFg, point(0px, 1px) }}; - over: icon {{ "mini_reply_story", dialogsTextFgOver, point(0px, 1px) }}; - active: icon {{ "mini_reply_story", dialogsTextFgActive, point(0px, 1px) }}; + +dialogsMiniReplyStory: DialogsMiniIcon { + icon: ThreeStateIcon { + icon: icon {{ "mini_reply_story", dialogsTextFg, point(0px, 1px) }}; + over: icon {{ "mini_reply_story", dialogsTextFgOver, point(0px, 1px) }}; + active: icon {{ "mini_reply_story", dialogsTextFgActive, point(0px, 1px) }}; + } + skipText: 4px; + skipMedia: 5px; } dialogsUnreadMention: ThreeStateIcon { diff --git a/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp index 9e643555a..9c9ad67ba 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp @@ -194,11 +194,14 @@ UnreadState MainList::unreadState() const { result.chatsMuted = result.chats; result.marksMuted = result.marks; } - volatile auto touch = _unreadState.marks + _unreadState.marksMuted +#ifdef Q_OS_WIN + [[maybe_unused]] volatile auto touch = 0 + + _unreadState.marks + _unreadState.marksMuted + _unreadState.messages + _unreadState.messagesMuted + _unreadState.chats + _unreadState.chatsMuted + _unreadState.reactions + _unreadState.reactionsMuted + _unreadState.mentions; +#endif // Q_OS_WIN return result; } diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 5d0431ce8..e273ec9b7 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "dialogs/dialogs_widget.h" +#include "base/options.h" #include "dialogs/ui/dialogs_stories_content.h" #include "dialogs/ui/dialogs_stories_list.h" #include "dialogs/dialogs_inner_widget.h" @@ -22,7 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peers/edit_peer_requests_box.h" #include "ui/widgets/buttons.h" #include "ui/widgets/elastic_scroll.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/wrap/fade_wrap.h" #include "ui/effects/radial_animation.h" #include "ui/chat/requests_bar.h" @@ -78,6 +79,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include +#include // AyuGram includes #include "ayu/ayu_settings.h" @@ -89,8 +91,16 @@ namespace { constexpr auto kSearchPerPage = 50; constexpr auto kStoriesExpandDuration = crl::time(200); +base::options::toggle OptionForumHideChatsList({ + .id = kOptionForumHideChatsList, + .name = "Hide chats list in forums", + .description = "Don't keep a narrow column of chats list.", +}); + } // namespace +const char kOptionForumHideChatsList[] = "forum-hide-chats-list"; + class Widget::BottomButton : public Ui::RippleButton { public: BottomButton( @@ -344,12 +354,12 @@ Widget::Widget( Ui::PostponeCall(this, [=] { listScrollUpdated(); }); }, lifetime()); - QObject::connect(_filter, &Ui::InputField::changed, [=] { + _filter->changes( + ) | rpl::start_with_next([=] { applyFilterUpdate(); - }); - QObject::connect(_filter, &Ui::InputField::submitted, [=] { - submit(); - }); + }, _filter->lifetime()); + _filter->submits( + ) | rpl::start_with_next([=] { submit(); }, _filter->lifetime()); QObject::connect( _filter->rawTextEdit().get(), &QTextEdit::cursorPositionChanged, @@ -2411,7 +2421,8 @@ void Widget::showForum( const Window::SectionShow ¶ms) { if (!params.childColumn || !Core::App().settings().dialogsWidthRatio() - || (_layout != Layout::Main)) { + || (_layout != Layout::Main) + || OptionForumHideChatsList.value()) { changeOpenedForum(forum, params.animated); return; } diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index e1d1a64ef..711509625 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -65,6 +65,8 @@ struct Content; namespace Dialogs { +extern const char kOptionForumHideChatsList[]; + struct RowDescriptor; class Row; class FakeRow; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp index 253bdc967..f40bf5308 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp @@ -160,9 +160,9 @@ void MessageView::prepare( options.spoilerLoginCode = true; auto preview = item->toPreview(options); _leftIcon = (preview.icon == ItemPreview::Icon::ForwardedMessage) - ? &st::dialogsMiniForwardIcon + ? &st::dialogsMiniForward : (preview.icon == ItemPreview::Icon::ReplyToStory) - ? &st::dialogsMiniReplyStoryIcon + ? &st::dialogsMiniReplyStory : nullptr; const auto hasImages = !preview.images.empty(); const auto history = item->history(); @@ -186,9 +186,12 @@ void MessageView::prepare( _senderCache = { st::dialogsTextWidthMin }; } TextUtilities::Trim(preview.text); + auto textToCache = DialogsPreviewText(std::move(preview.text)); + _hasPlainLinkAtBegin = !textToCache.entities.empty() + && (textToCache.entities.front().type() == EntityType::PlainLink); _textCache.setMarkedText( st::dialogsTextStyle, - DialogsPreviewText(std::move(preview.text)), + std::move(textToCache), DialogTextOptions(), context); _textCachedFor = item; @@ -313,7 +316,7 @@ void MessageView::paint( .elisionLines = lines, }); rect.setLeft(rect.x() + _senderCache.maxWidth()); - if (!_imagesCache.empty()) { + if (!_imagesCache.empty() && !_leftIcon) { const auto skip = st::dialogsMiniPreviewSkip + st::dialogsMiniPreviewRight; rect.setLeft(rect.x() + skip); @@ -322,14 +325,30 @@ void MessageView::paint( if (_leftIcon) { const auto &icon = ThreeStateIcon( - *_leftIcon, + _leftIcon->icon, context.active, context.selected); - icon.paint(p, rect.topLeft(), rect.width()); - rect.setLeft(rect.x() + icon.width() + st::dialogsMiniIconSkip); + const auto w = (icon.width()); + if (rect.width() > w) { + if (_hasPlainLinkAtBegin && !context.active) { + icon.paint( + p, + rect.topLeft(), + rect.width(), + palette->linkFg->c); + } else { + icon.paint(p, rect.topLeft(), rect.width()); + } + rect.setLeft(rect.x() + + w + + (_imagesCache.empty() + ? _leftIcon->skipText + : _leftIcon->skipMedia)); + } } for (const auto &image : _imagesCache) { - if (rect.width() < st::dialogsMiniPreview) { + const auto w = st::dialogsMiniPreview + st::dialogsMiniPreviewSkip; + if (rect.width() < w) { break; } const auto mini = QRect( @@ -345,14 +364,15 @@ void MessageView::paint( FillSpoilerRect(p, mini, frame); } } - rect.setLeft(rect.x() - + st::dialogsMiniPreview - + st::dialogsMiniPreviewSkip); + rect.setLeft(rect.x() + w); } if (!_imagesCache.empty()) { rect.setLeft(rect.x() + st::dialogsMiniPreviewRight); } - if (!rect.isEmpty()) { + // Style of _textCache. + static const auto ellipsisWidth = st::dialogsTextStyle.font->width( + kQEllipsis); + if (rect.width() > ellipsisWidth) { _textCache.draw(p, { .position = rect.topLeft(), .availableWidth = rect.width(), diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h index 14f677536..58c1966a8 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h @@ -15,7 +15,7 @@ enum class ImageRoundRadius; namespace style { struct DialogRow; -struct ThreeStateIcon; +struct DialogsMiniIcon; } // namespace style namespace Ui { @@ -93,7 +93,8 @@ private: mutable std::vector _imagesCache; mutable std::unique_ptr _spoiler; mutable std::unique_ptr _loadingContext; - mutable const style::ThreeStateIcon *_leftIcon = nullptr; + mutable const style::DialogsMiniIcon *_leftIcon = nullptr; + mutable bool _hasPlainLinkAtBegin = false; }; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.cpp index f737d1615..68368ebaa 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.cpp @@ -134,7 +134,7 @@ private: const not_null _data; const Data::StorySourcesList _list; base::flat_map< - not_null, + not_null, std::shared_ptr> _userpics; }; @@ -338,20 +338,20 @@ Content State::next() { Assert(source != nullptr); auto userpic = std::shared_ptr(); - const auto user = source->user; - if (const auto i = _userpics.find(user); i != end(_userpics)) { + const auto peer = source->peer; + if (const auto i = _userpics.find(peer); i != end(_userpics)) { userpic = i->second; } else { - userpic = MakeUserpicThumbnail(user); - _userpics.emplace(user, userpic); + userpic = MakeUserpicThumbnail(peer); + _userpics.emplace(peer, userpic); } result.elements.push_back({ - .id = uint64(user->id.value), - .name = user->shortName(), + .id = uint64(peer->id.value), + .name = peer->shortName(), .thumbnail = std::move(userpic), .count = info.count, .unreadCount = info.unreadCount, - .skipSmall = user->isSelf() ? 1U : 0U, + .skipSmall = peer->isSelf() ? 1U : 0U, }); } return result; @@ -512,12 +512,19 @@ void FillSourceMenu( controller->showSection(Info::Stories::Make(peer)); }, &st::menuIconStoriesSavedSection); } else { - add(tr::lng_profile_send_message(tr::now), [=] { + const auto channel = peer->isChannel(); + const auto showHistoryText = channel + ? tr::lng_context_open_channel(tr::now) + : tr::lng_profile_send_message(tr::now); + add(showHistoryText, [=] { controller->showPeerHistory(peer); - }, &st::menuIconChatBubble); - add(tr::lng_context_view_profile(tr::now), [=] { + }, channel ? &st::menuIconChannel : &st::menuIconChatBubble); + const auto viewProfileText = channel + ? tr::lng_context_view_channel(tr::now) + : tr::lng_context_view_profile(tr::now); + add(viewProfileText, [=] { controller->showPeerInfo(peer); - }, &st::menuIconProfile); + }, channel ? &st::menuIconInfo : &st::menuIconProfile); const auto in = [&](Data::StorySourcesList list) { return ranges::contains( owner->stories().sources(list), diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index 6b9fc2997..723eeae9b 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -1173,6 +1173,7 @@ ServiceAction ParseServiceAction( content.domain = ParseString(*domain); } content.attachMenu = data.is_attach_menu(); + content.fromRequest = data.is_from_request(); result.content = content; }, [&](const MTPDmessageActionSecureValuesSentMe &data) { // Should not be in user inbox. diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h index 27fb14ea1..4635ea269 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -439,6 +439,7 @@ struct ActionBotAllowed { Utf8String app; Utf8String domain; bool attachMenu = false; + bool fromRequest = false; }; struct ActionSecureValuesSent { diff --git a/Telegram/SourceFiles/export/export_api_wrap.cpp b/Telegram/SourceFiles/export/export_api_wrap.cpp index 1ff8d6774..5ed8addd9 100644 --- a/Telegram/SourceFiles/export/export_api_wrap.cpp +++ b/Telegram/SourceFiles/export/export_api_wrap.cpp @@ -504,6 +504,7 @@ void ApiWrap::requestStoriesCount() { Expects(_startProcess != nullptr); mainRequest(MTPstories_GetStoriesArchive( + MTP_inputPeerSelf(), MTP_int(0), // offset_id MTP_int(0) // limit )).done([=](const MTPstories_Stories &result) { @@ -907,6 +908,7 @@ void ApiWrap::requestStories( _storiesProcess->finish = std::move(finish); mainRequest(MTPstories_GetStoriesArchive( + MTP_inputPeerSelf(), MTP_int(_storiesProcess->offsetId), MTP_int(kStoriesSliceLimit) )).done([=](const MTPstories_Stories &result) mutable { @@ -993,6 +995,7 @@ void ApiWrap::finishStoriesSlice() { } mainRequest(MTPstories_GetStoriesArchive( + MTP_inputPeerSelf(), MTP_int(_storiesProcess->offsetId), MTP_int(kStoriesSliceLimit) )).done([=](const MTPstories_Stories &result) { @@ -2186,7 +2189,7 @@ void ApiWrap::filePartRefreshReference(int64 offset) { const auto &origin = _fileProcess->origin; if (origin.storyId) { _fileProcess->requestId = mainRequest(MTPstories_GetStoriesByID( - MTP_inputUserSelf(), + MTP_inputPeerSelf(), MTP_vector(1, MTP_int(origin.storyId)) )).fail([=](const MTP::Error &error) { _fileProcess->requestId = 0; diff --git a/Telegram/SourceFiles/export/output/export_output_abstract.cpp b/Telegram/SourceFiles/export/output/export_output_abstract.cpp index f9df0d1f7..dbdb1bf99 100644 --- a/Telegram/SourceFiles/export/output/export_output_abstract.cpp +++ b/Telegram/SourceFiles/export/output/export_output_abstract.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "export/output/export_output_abstract.h" +#include "export/output/export_output_html_and_json.h" #include "export/output/export_output_html.h" #include "export/output/export_output_json.h" #include "export/output/export_output_stats.h" @@ -50,6 +51,7 @@ std::unique_ptr CreateWriter(Format format) { switch (format) { case Format::Html: return std::make_unique(); case Format::Json: return std::make_unique(); + case Format::HtmlAndJson: return std::make_unique(); } Unexpected("Format in Export::Output::CreateWriter."); } diff --git a/Telegram/SourceFiles/export/output/export_output_abstract.h b/Telegram/SourceFiles/export/output/export_output_abstract.h index 7a2bb65ea..9ceef46eb 100644 --- a/Telegram/SourceFiles/export/output/export_output_abstract.h +++ b/Telegram/SourceFiles/export/output/export_output_abstract.h @@ -37,6 +37,7 @@ class Stats; enum class Format { Html, Json, + HtmlAndJson, }; class AbstractWriter { diff --git a/Telegram/SourceFiles/export/output/export_output_html.cpp b/Telegram/SourceFiles/export/output/export_output_html.cpp index b45f37621..4577b2f96 100644 --- a/Telegram/SourceFiles/export/output/export_output_html.cpp +++ b/Telegram/SourceFiles/export/output/export_output_html.cpp @@ -1127,6 +1127,8 @@ auto HtmlWriter::Wrap::pushMessage( return data.attachMenu ? "You allowed this bot to message you " "when you added it in the attachment menu."_q + : data.fromRequest + ? "You allowed this bot to message you in his web-app."_q : data.app.isEmpty() ? ("You allowed this bot to message you when you opened " + SerializeString(data.app)) diff --git a/Telegram/SourceFiles/export/output/export_output_html_and_json.cpp b/Telegram/SourceFiles/export/output/export_output_html_and_json.cpp new file mode 100644 index 000000000..9220147ea --- /dev/null +++ b/Telegram/SourceFiles/export/output/export_output_html_and_json.cpp @@ -0,0 +1,148 @@ +/* +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 "export/output/export_output_html_and_json.h" + +#include "export/output/export_output_html.h" +#include "export/output/export_output_json.h" +#include "export/output/export_output_result.h" + +namespace Export::Output { + +HtmlAndJsonWriter::HtmlAndJsonWriter() { + _writers.push_back(CreateWriter(Format::Html)); + _writers.push_back(CreateWriter(Format::Json)); +} + +Format HtmlAndJsonWriter::format() { + return Format::HtmlAndJson; +} + +Result HtmlAndJsonWriter::start( + const Settings &settings, + const Environment &environment, + Stats *stats) { + return invoke([&](WriterPtr w) { + return w->start(settings, environment, stats); + }); +} + +Result HtmlAndJsonWriter::writePersonal(const Data::PersonalInfo &data) { + return invoke([&](WriterPtr w) { + return w->writePersonal(data); + }); +} + +Result HtmlAndJsonWriter::writeUserpicsStart(const Data::UserpicsInfo &data) { + return invoke([&](WriterPtr w) { + return w->writeUserpicsStart(data); + }); +} + +Result HtmlAndJsonWriter::writeUserpicsSlice(const Data::UserpicsSlice &d) { + return invoke([&](WriterPtr w) { + return w->writeUserpicsSlice(d); + }); +} + +Result HtmlAndJsonWriter::writeUserpicsEnd() { + return invoke([&](WriterPtr w) { + return w->writeUserpicsEnd(); + }); +} + +Result HtmlAndJsonWriter::writeStoriesStart(const Data::StoriesInfo &data) { + return invoke([&](WriterPtr w) { + return w->writeStoriesStart(data); + }); +} + +Result HtmlAndJsonWriter::writeStoriesSlice(const Data::StoriesSlice &data) { + return invoke([&](WriterPtr w) { + return w->writeStoriesSlice(data); + }); +} + +Result HtmlAndJsonWriter::writeStoriesEnd() { + return invoke([&](WriterPtr w) { + return w->writeStoriesEnd(); + }); +} + +Result HtmlAndJsonWriter::writeContactsList(const Data::ContactsList &data) { + return invoke([&](WriterPtr w) { + return w->writeContactsList(data); + }); +} + +Result HtmlAndJsonWriter::writeSessionsList(const Data::SessionsList &data) { + return invoke([&](WriterPtr w) { + return w->writeSessionsList(data); + }); +} + +Result HtmlAndJsonWriter::writeOtherData(const Data::File &data) { + return invoke([&](WriterPtr w) { + return w->writeOtherData(data); + }); +} + +Result HtmlAndJsonWriter::writeDialogsStart(const Data::DialogsInfo &data) { + return invoke([&](WriterPtr w) { + return w->writeDialogsStart(data); + }); +} + +Result HtmlAndJsonWriter::writeDialogStart(const Data::DialogInfo &data) { + return invoke([&](WriterPtr w) { + return w->writeDialogStart(data); + }); +} + +Result HtmlAndJsonWriter::writeDialogSlice(const Data::MessagesSlice &data) { + return invoke([&](WriterPtr w) { + return w->writeDialogSlice(data); + }); +} + +Result HtmlAndJsonWriter::writeDialogEnd() { + return invoke([&](WriterPtr w) { + return w->writeDialogEnd(); + }); +} + +Result HtmlAndJsonWriter::writeDialogsEnd() { + return invoke([&](WriterPtr w) { + return w->writeDialogsEnd(); + }); +} + +Result HtmlAndJsonWriter::finish() { + return invoke([&](WriterPtr w) { + return w->finish(); + }); +} + +QString HtmlAndJsonWriter::mainFilePath() { + return _writers.front()->mainFilePath(); +} + +HtmlAndJsonWriter::~HtmlAndJsonWriter() = default; + +Result HtmlAndJsonWriter::invoke(Fn method) const { + auto result = Result(Result::Type::Success, QString()); + for (const auto &writer : _writers) { + const auto current = method(writer); + if (!current) { + result = current; + } + } + return result; +} + +} // namespace Export::Output + diff --git a/Telegram/SourceFiles/export/output/export_output_html_and_json.h b/Telegram/SourceFiles/export/output/export_output_html_and_json.h new file mode 100644 index 000000000..2a40833a8 --- /dev/null +++ b/Telegram/SourceFiles/export/output/export_output_html_and_json.h @@ -0,0 +1,65 @@ +/* +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 "export/output/export_output_abstract.h" + +namespace Export::Output { + +class HtmlWriter; +class JsonWriter; +struct Result; + +class HtmlAndJsonWriter final : public AbstractWriter { +public: + HtmlAndJsonWriter(); + + Format format() override; + + Result start( + const Settings &settings, + const Environment &environment, + Stats *stats) override; + + Result writePersonal(const Data::PersonalInfo &data) override; + + Result writeUserpicsStart(const Data::UserpicsInfo &data) override; + Result writeUserpicsSlice(const Data::UserpicsSlice &data) override; + Result writeUserpicsEnd() override; + + Result writeStoriesStart(const Data::StoriesInfo &data) override; + Result writeStoriesSlice(const Data::StoriesSlice &data) override; + Result writeStoriesEnd() override; + + Result writeContactsList(const Data::ContactsList &data) override; + + Result writeSessionsList(const Data::SessionsList &data) override; + + Result writeOtherData(const Data::File &data) override; + + Result writeDialogsStart(const Data::DialogsInfo &data) override; + Result writeDialogStart(const Data::DialogInfo &data) override; + Result writeDialogSlice(const Data::MessagesSlice &data) override; + Result writeDialogEnd() override; + Result writeDialogsEnd() override; + + Result finish() override; + + QString mainFilePath() override; + + ~HtmlAndJsonWriter(); + +private: + using WriterPtr = const std::unique_ptr &; + Result invoke(Fn method) const; + + std::vector> _writers; + +}; + +} // namespace Export::Output diff --git a/Telegram/SourceFiles/export/output/export_output_json.cpp b/Telegram/SourceFiles/export/output/export_output_json.cpp index 9dfd21620..a232b2788 100644 --- a/Telegram/SourceFiles/export/output/export_output_json.cpp +++ b/Telegram/SourceFiles/export/output/export_output_json.cpp @@ -477,6 +477,8 @@ QByteArray SerializeMessage( }, [&](const ActionBotAllowed &data) { if (data.attachMenu) { pushAction("attach_menu_bot_allowed"); + } else if (data.fromRequest) { + pushAction("web_app_bot_allowed"); } else if (data.appId) { pushAction("allow_sending_messages"); push("reason_app_id", data.appId); diff --git a/Telegram/SourceFiles/export/view/export_view_progress.cpp b/Telegram/SourceFiles/export/view/export_view_progress.cpp index f75780395..5b946428b 100644 --- a/Telegram/SourceFiles/export/view/export_view_progress.cpp +++ b/Telegram/SourceFiles/export/view/export_view_progress.cpp @@ -296,6 +296,7 @@ rpl::producer<> ProgressWidget::doneClicks() const { } void ProgressWidget::setupBottomButton(not_null button) { + button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); button->show(); sizeValue( @@ -361,7 +362,7 @@ void ProgressWidget::showDone() { tr::lng_export_done(), st::exportDoneButton); const auto desired = std::min( - st::exportDoneButton.font->width(tr::lng_export_done(tr::now).toUpper()) + st::exportDoneButton.font->width(tr::lng_export_done(tr::now)) + st::exportDoneButton.height - st::exportDoneButton.font->height, st::exportPanelSize.width() - 2 * st::exportCancelBottom); diff --git a/Telegram/SourceFiles/export/view/export_view_settings.cpp b/Telegram/SourceFiles/export/view/export_view_settings.cpp index 94e00b43a..bbc91c2dc 100644 --- a/Telegram/SourceFiles/export/view/export_view_settings.cpp +++ b/Telegram/SourceFiles/export/view/export_view_settings.cpp @@ -75,6 +75,9 @@ void ChooseFormatBox( box->setTitle(tr::lng_export_option_choose_format()); addFormatOption(tr::lng_export_option_html(tr::now), Format::Html); addFormatOption(tr::lng_export_option_json(tr::now), Format::Json); + addFormatOption( + tr::lng_export_option_html_and_json(tr::now), + Format::HtmlAndJson); box->addButton(tr::lng_settings_save(), [=] { done(group->value()); }); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); } @@ -347,7 +350,11 @@ void SettingsWidget::addFormatAndLocationLabel( return data.format; }) | rpl::distinct_until_changed( ) | rpl::map([](Format format) { - const auto text = (format == Format::Html) ? "HTML" : "JSON"; + const auto text = (format == Format::Html) + ? "HTML" + : (format == Format::Json) + ? "JSON" + : tr::lng_export_option_html_and_json(tr::now); return Ui::Text::Link(text, u"internal:edit_format"_q); }); const auto label = container->add( diff --git a/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp b/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp index a2072b065..b59bf86ba 100644 --- a/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp +++ b/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp @@ -10,10 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/algorithm.h" #include "logs.h" -#if !defined DESKTOP_APP_USE_PACKAGED && !defined Q_OS_WIN && !defined Q_OS_MAC +#if !defined TDESKTOP_USE_PACKAGED && !defined Q_OS_WIN && !defined Q_OS_MAC #include "base/platform/linux/base_linux_library.h" #include -#endif // !DESKTOP_APP_USE_PACKAGED && !Q_OS_WIN && !Q_OS_MAC +#endif // !TDESKTOP_USE_PACKAGED && !Q_OS_WIN && !Q_OS_MAC #include @@ -90,7 +90,7 @@ void PremultiplyLine(uchar *dst, const uchar *src, int intsCount) { #endif // LIB_FFMPEG_USE_QT_PRIVATE_API } -#if !defined DESKTOP_APP_USE_PACKAGED && !defined Q_OS_WIN && !defined Q_OS_MAC +#if !defined TDESKTOP_USE_PACKAGED && !defined Q_OS_WIN && !defined Q_OS_MAC [[nodiscard]] auto CheckHwLibs() { auto list = std::deque{ AV_PIX_FMT_CUDA, @@ -100,9 +100,9 @@ void PremultiplyLine(uchar *dst, const uchar *src, int intsCount) { } if ([&] { const auto list = std::array{ - "libva-drm.so.1", - "libva-x11.so.1", - "libva.so.1", + "libva-drm.so.2", + "libva-x11.so.2", + "libva.so.2", "libdrm.so.2", }; for (const auto lib : list) { @@ -116,7 +116,7 @@ void PremultiplyLine(uchar *dst, const uchar *src, int intsCount) { } return list; } -#endif // !DESKTOP_APP_USE_PACKAGED && !Q_OS_WIN && !Q_OS_MAC +#endif // !TDESKTOP_USE_PACKAGED && !Q_OS_WIN && !Q_OS_MAC [[nodiscard]] bool InitHw(AVCodecContext *context, AVHWDeviceType type) { AVCodecContext *parent = static_cast(context->opaque); @@ -158,9 +158,9 @@ void PremultiplyLine(uchar *dst, const uchar *src, int intsCount) { } return false; }; -#if !defined DESKTOP_APP_USE_PACKAGED && !defined Q_OS_WIN && !defined Q_OS_MAC +#if !defined TDESKTOP_USE_PACKAGED && !defined Q_OS_WIN && !defined Q_OS_MAC static const auto list = CheckHwLibs(); -#else // !DESKTOP_APP_USE_PACKAGED && !Q_OS_WIN && !Q_OS_MAC +#else // !TDESKTOP_USE_PACKAGED && !Q_OS_WIN && !Q_OS_MAC const auto list = std::array{ #ifdef Q_OS_WIN AV_PIX_FMT_D3D11, @@ -174,7 +174,7 @@ void PremultiplyLine(uchar *dst, const uchar *src, int intsCount) { AV_PIX_FMT_CUDA, #endif // Q_OS_WIN || Q_OS_MAC }; -#endif // DESKTOP_APP_USE_PACKAGED || Q_OS_WIN || Q_OS_MAC +#endif // TDESKTOP_USE_PACKAGED || Q_OS_WIN || Q_OS_MAC for (const auto format : list) { if (!has(format)) { continue; diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp index a64f98f47..b50db2c6b 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp @@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/scroll_area.h" #include "ui/widgets/shadow.h" #include "ui/widgets/buttons.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/ui_utility.h" #include "mainwidget.h" #include "mainwindow.h" @@ -125,9 +125,16 @@ FixedBar::FixedBar( _cancel->setClickedCallback([=] { cancelSearch(); }); _field->hide(); _filter->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); - connect(_field, &Ui::InputField::cancelled, [=] { cancelSearch(); }); - connect(_field, &Ui::InputField::changed, [=] { searchUpdated(); }); - connect(_field, &Ui::InputField::submitted, [=] { applySearch(); }); + _field->cancelled( + ) | rpl::start_with_next([=] { + cancelSearch(); + }, _field->lifetime()); + _field->changes( + ) | rpl::start_with_next([=] { + searchUpdated(); + }, _field->lifetime()); + _field->submits( + ) | rpl::start_with_next([=] { applySearch(); }, _field->lifetime()); _searchTimer.setCallback([=] { applySearch(); }); _cancel->hide(anim::type::instant); diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 400464856..318d47115 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -2445,8 +2445,8 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { : QString(); if (isUponSelected > 0) { - if (!hasCopyRestrictionForSelected() - && !getSelectedText().empty()) { + const auto selectedText = getSelectedText(); + if (!hasCopyRestrictionForSelected() && !selectedText.empty()) { _menu->addAction( ((isUponSelected > 1) ? tr::lng_context_copy_selected_items(tr::now) @@ -2454,13 +2454,14 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { [=] { copySelectedText(); }, &st::menuIconCopy); } - if (!Ui::SkipTranslate(getSelectedText().rich)) { + if (!Ui::SkipTranslate(selectedText.rich)) { + const auto peer = item->history()->peer; _menu->addAction(tr::lng_context_translate_selected({}), [=] { _controller->show(Box( Ui::TranslateBox, - item->history()->peer, + peer, MsgId(), - getSelectedText().rich, + selectedText.rich, hasCopyRestrictionForSelected())); }, &st::menuIconTranslate); } @@ -2552,7 +2553,9 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { }, &st::menuIconLink); } if (item && item->isSponsored()) { - _menu->addSeparator(&st::expandedMenuSeparator); + if (!_menu->empty()) { + _menu->addSeparator(&st::expandedMenuSeparator); + } auto item = base::make_unique_q( _menu, st::menuWithIcons, diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index dfc36d2c7..edde327a1 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -289,7 +289,7 @@ std::unique_ptr HistoryItem::CreateMedia( media.vvalue().v); }, [&](const MTPDmessageMediaStory &media) -> Result { return std::make_unique(item, FullStoryId{ - peerFromUser(media.vuser_id()), + peerFromMTP(media.vpeer()), media.vid().v, }, media.is_via_mention()); }, [](const MTPDmessageMediaEmpty &) -> Result { @@ -1230,9 +1230,15 @@ void HistoryItem::markMediaAndMentionRead() { } if (const auto selfdestruct = Get()) { - if (!selfdestruct->destructAt) { - selfdestruct->destructAt = crl::now() + selfdestruct->timeToLive; - _history->owner().selfDestructIn(this, selfdestruct->timeToLive); + if (selfdestruct->destructAt == crl::time()) { + const auto ttl = selfdestruct->timeToLive; + if (const auto maybeTime = std::get_if(&ttl)) { + const auto time = *maybeTime; + selfdestruct->destructAt = crl::now() + time; + _history->owner().selfDestructIn(this, time); + } else { + selfdestruct->destructAt = TimeToLiveSingleView(); + } } } } @@ -1993,7 +1999,7 @@ bool HistoryItem::canBeEdited() const { return true; } else if (out()) { if (isPost()) { - return channel->canPublish(); + return channel->canPostMessages(); } else if (const auto topic = this->topic()) { return Data::CanSendAnything(topic); } else { @@ -2045,9 +2051,8 @@ bool HistoryItem::canDelete() const { } if (channel->canDeleteMessages()) { return true; - } - if (out() && !isService()) { - return isPost() ? channel->canPublish() : true; + } else if (out() && !isService()) { + return isPost() ? channel->canPostMessages() : true; } return false; } @@ -3283,8 +3288,9 @@ void HistoryItem::setSponsoredFrom(const Data::SponsoredFrom &from) { from.userpic); } + sponsored->externalLink = from.externalLink; using Type = HistoryMessageSponsored::Type; - sponsored->type = from.isExternalLink + sponsored->type = (!from.externalLink.isEmpty()) ? Type::ExternalLink : from.isExactPost ? Type::Post @@ -3478,7 +3484,7 @@ void HistoryItem::createServiceFromMtp(const MTPDmessage &message) { const auto ttl = data.vttl_seconds(); Assert(ttl != nullptr); - setSelfDestruct(HistoryServiceSelfDestruct::Type::Photo, ttl->v); + setSelfDestruct(HistoryServiceSelfDestruct::Type::Photo, *ttl); if (out()) { setServiceText({ tr::lng_ttl_photo_sent(tr::now, Ui::Text::WithEntities) @@ -3503,7 +3509,7 @@ void HistoryItem::createServiceFromMtp(const MTPDmessage &message) { const auto ttl = data.vttl_seconds(); Assert(ttl != nullptr); - setSelfDestruct(HistoryServiceSelfDestruct::Type::Video, ttl->v); + setSelfDestruct(HistoryServiceSelfDestruct::Type::Video, *ttl); if (out()) { setServiceText({ tr::lng_ttl_video_sent(tr::now, Ui::Text::WithEntities) @@ -3936,6 +3942,10 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { result.text = { tr::lng_action_attach_menu_bot_allowed(tr::now) }; + } else if (action.is_from_request()) { + result.text = { + tr::lng_action_webapp_bot_allowed(tr::now) + }; } else if (const auto app = action.vapp()) { const auto bot = history()->peer->asUser(); const auto botId = bot ? bot->id : PeerId(); @@ -4602,10 +4612,16 @@ void HistoryItem::applyAction(const MTPMessageAction &action) { }); } -void HistoryItem::setSelfDestruct(HistoryServiceSelfDestruct::Type type, int ttlSeconds) { +void HistoryItem::setSelfDestruct( + HistoryServiceSelfDestruct::Type type, + MTPint mtpTTLvalue) { UpdateComponents(HistoryServiceSelfDestruct::Bit()); - auto selfdestruct = Get(); - selfdestruct->timeToLive = ttlSeconds * 1000LL; + const auto selfdestruct = Get(); + if (mtpTTLvalue.v == TimeId(0x7FFFFFFF)) { + selfdestruct->timeToLive = TimeToLiveSingleView(); + } else { + selfdestruct->timeToLive = mtpTTLvalue.v * crl::time(1000); + } selfdestruct->type = type; } @@ -4993,10 +5009,12 @@ ClickHandlerPtr HistoryItem::fromLink() const { } crl::time HistoryItem::getSelfDestructIn(crl::time now) { - if (auto selfdestruct = Get()) { - if (selfdestruct->destructAt > 0) { - if (selfdestruct->destructAt <= now) { - auto text = [selfdestruct] { + if (const auto selfdestruct = Get()) { + const auto at = std::get_if(&selfdestruct->destructAt); + if (at && (*at) > 0) { + const auto destruct = *at; + if (destruct <= now) { + auto text = [&] { switch (selfdestruct->type) { case HistoryServiceSelfDestruct::Type::Photo: return tr::lng_ttl_photo_expired(tr::now); @@ -5005,10 +5023,10 @@ crl::time HistoryItem::getSelfDestructIn(crl::time now) { } Unexpected("Type in HistoryServiceSelfDestruct::Type"); }; - setServiceText({ TextWithEntities{.text = text() } }); + setServiceText({ TextWithEntities{ .text = text() } }); return 0; } - return selfdestruct->destructAt - now; + return destruct - now; } } return 0; diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 54040551d..f4bee5394 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -576,7 +576,7 @@ private: void translationToggle( not_null translation, bool used); - void setSelfDestruct(HistorySelfDestructType type, int ttlSeconds); + void setSelfDestruct(HistorySelfDestructType type, MTPint mtpTTLvalue); TextWithEntities fromLinkText() const; ClickHandlerPtr fromLink() const; diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 59ab3396c..2ae09fd62 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -43,7 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_bot.h" #include "styles/style_widgets.h" #include "styles/style_chat.h" -#include "styles/style_dialogs.h" // dialogsMiniReplyStoryIcon. +#include "styles/style_dialogs.h" // dialogsMiniReplyStory. #include @@ -466,8 +466,8 @@ void HistoryMessageReply::updateName( w, std::min(replyToText.maxWidth(), st::maxSignatureSize)) + (storyReply - ? (st::dialogsMiniIconSkip - + st::dialogsMiniReplyStoryIcon.icon.width()) + ? (st::dialogsMiniReplyStory.skipText + + st::dialogsMiniReplyStory.icon.icon.width()) : 0); } else { maxReplyWidth = st::msgDateFont->width(statePhrase()); @@ -611,14 +611,14 @@ void HistoryMessageReply::paint( ? stm->replyTextPalette : st->imgReplyTextPalette()); if (storyReply) { - st::dialogsMiniReplyStoryIcon.icon.paint( + st::dialogsMiniReplyStory.icon.icon.paint( p, replyToTextPosition, w - st::msgReplyBarSkip - previewSkip, replyToTextPalette->linkFg->c); replyToTextPosition += QPoint( - st::dialogsMiniIconSkip - + st::dialogsMiniReplyStoryIcon.icon.width(), + st::dialogsMiniReplyStory.skipText + + st::dialogsMiniReplyStory.icon.icon.width(), 0); } replyToText.draw(p, { diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index 75581f969..d5414ba14 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -146,6 +146,7 @@ struct HistoryMessageSponsored : public RuntimeComponent( + TimeToLiveSingleView, + TimeToLiveSingleView) = default; + friend inline bool operator==( + TimeToLiveSingleView, + TimeToLiveSingleView) = default; +}; + struct HistoryServiceSelfDestruct : public RuntimeComponent { using Type = HistorySelfDestructType; Type type = Type::Photo; - crl::time timeToLive = 0; - crl::time destructAt = 0; + std::variant timeToLive = crl::time(); + std::variant destructAt = crl::time(); }; struct HistoryServiceOngoingCall diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index ea4b41222..ef957fc21 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -307,25 +307,30 @@ HistoryWidget::HistoryWidget( _joinChannel->addClickHandler([=] { joinChannel(); }); _muteUnmute->addClickHandler([=] { toggleMuteUnmute(); }); _reportMessages->addClickHandler([=] { reportSelectedMessages(); }); - connect( - _field, - &Ui::InputField::submitted, - [=](Qt::KeyboardModifiers modifiers) { sendWithModifiers(modifiers); }); - connect(_field, &Ui::InputField::cancelled, [=] { + _field->submits( + ) | rpl::start_with_next([=](Qt::KeyboardModifiers modifiers) { + sendWithModifiers(modifiers); + }, _field->lifetime()); + _field->cancelled( + ) | rpl::start_with_next([=] { escape(); - }); - connect(_field, &Ui::InputField::tabbed, [=] { + }, _field->lifetime()); + _field->tabbed( + ) | rpl::start_with_next([=] { fieldTabbed(); - }); - connect(_field, &Ui::InputField::resized, [=] { + }, _field->lifetime()); + _field->heightChanges( + ) | rpl::start_with_next([=] { fieldResized(); - }); - connect(_field, &Ui::InputField::focused, [=] { + }, _field->lifetime()); + _field->focusedChanges( + ) | rpl::filter(rpl::mappers::_1) | rpl::start_with_next([=] { fieldFocused(); - }); - connect(_field, &Ui::InputField::changed, [=] { + }, _field->lifetime()); + _field->changes( + ) | rpl::start_with_next([=] { fieldChanged(); - }); + }, _field->lifetime()); connect( controller->widget()->windowHandle(), &QWindow::visibleChanged, @@ -4343,12 +4348,14 @@ void HistoryWidget::mouseMoveEvent(QMouseEvent *e) { } void HistoryWidget::updateOverStates(QPoint pos) { + const auto isReadyToForward = readyToForward(); + const auto skip = isReadyToForward ? 0 : st::historyReplySkip; const auto replyEditForwardInfoRect = QRect( - st::historyReplySkip, + skip, _field->y() - st::historySendPadding - st::historyReplyHeight, - width() - st::historyReplySkip - _fieldBarCancel->width(), + width() - skip - _fieldBarCancel->width(), st::historyReplyHeight); - auto inReplyEditForward = (_editMsgId || replyToId() || readyToForward()) + auto inReplyEditForward = (_editMsgId || replyToId() || isReadyToForward) && replyEditForwardInfoRect.contains(pos); auto inPhotoEdit = inReplyEditForward && _photoEditMedia @@ -4587,7 +4594,7 @@ bool HistoryWidget::isChoosingTheme() const { bool HistoryWidget::isMuteUnmute() const { return _peer - && ((_peer->isBroadcast() && !_peer->asChannel()->canPublish()) + && ((_peer->isBroadcast() && !_peer->asChannel()->canPostMessages()) || (_peer->isGigagroup() && !Data::CanSendAnything(_peer)) || _peer->isRepliesChat()); } @@ -6184,16 +6191,19 @@ bool HistoryWidget::cornerButtonsHas(HistoryView::CornerButtonType type) { } void HistoryWidget::mousePressEvent(QMouseEvent *e) { + const auto isReadyToForward = readyToForward(); const auto hasSecondLayer = (_editMsgId || _replyToId - || readyToForward() + || isReadyToForward || _kbReplyTo); _replyForwardPressed = hasSecondLayer && QRect( 0, _field->y() - st::historySendPadding - st::historyReplyHeight, st::historyReplySkip, st::historyReplyHeight).contains(e->pos()); - if (_replyForwardPressed && !_fieldBarCancel->isHidden()) { + if (_replyForwardPressed + && !_fieldBarCancel->isHidden() + && !isReadyToForward) { updateField(); } else if (_inPhotoEdit && _photoEditMedia) { EditCaptionBox::StartPhotoEdit( @@ -6203,7 +6213,7 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) { _field->getTextWithTags(), crl::guard(_list, [=] { cancelEdit(); })); } else if (_inReplyEditForward) { - if (readyToForward()) { + if (isReadyToForward) { _forwardPanel->editOptions(controller()->uiShow()); } else { controller()->showPeerHistory( @@ -6616,12 +6626,21 @@ void HistoryWidget::checkPinnedBarState() { std::move(pinnedRefreshed), std::move(markupRefreshed) ) | rpl::map([=](Ui::MessageBarContent &&content, bool, HistoryItem*) { - if (!content.title.isEmpty() || !content.text.empty()) { - _list->setShownPinned( - session().data().message( - _pinnedTracker->currentMessageId().message)); - } else { - _list->setShownPinned(nullptr); + const auto id = (!content.title.isEmpty() || !content.text.empty()) + ? _pinnedTracker->currentMessageId().message + : FullMsgId(); + if (const auto list = _list.data()) { + // Sometimes we get here with non-empty content and id of + // message that is being deleted right now. We get here in + // the moment when _itemRemoved was already fired (so in + // the _list the _pinnedItem is already cleared) and the + // MessageUpdate::Flag::Destroyed being fired right now, + // so the message is still in Data::Session. So we need to + // call data().message() async, otherwise we get a nearly- + // destroyed message from it and save the pointer in _list. + crl::on_main(list, [=] { + list->setShownPinned(session().data().message(id)); + }); } return std::move(content); })); diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index ae1f9bf54..5011c52cb 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/bot_command.h" #include "chat_helpers/field_autocomplete.h" #include "window/section_widget.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "mtproto/sender.h" #include "base/flags.h" diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index e6a4717d8..cb1affa57 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -60,7 +60,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/audio/media_audio.h" #include "ui/text/text_options.h" #include "ui/ui_utility.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/dropdown_menu.h" #include "ui/text/format_values.h" #include "ui/controls/emoji_button.h" @@ -612,7 +612,7 @@ void FieldHeader::init() { const auto isLeftIcon = (pos.x() < st::historyReplySkip); const auto isLeftButton = (e->button() == Qt::LeftButton); if (type == QEvent::MouseButtonPress) { - if (isLeftButton && isLeftIcon) { + if (isLeftButton && isLeftIcon && !inPreviewRect) { *leftIconPressed = true; update(); } else if (isLeftButton && inPhotoEdit) { @@ -966,11 +966,13 @@ MsgId FieldHeader::getDraftMessageId() const { } void FieldHeader::updateControlsGeometry(QSize size) { + const auto isReadyToForward = readyToForward(); + const auto skip = isReadyToForward ? 0 : st::historyReplySkip; _cancel->moveToRight(0, 0); _clickableRect = QRect( - st::historyReplySkip, + skip, 0, - width() - st::historyReplySkip - _cancel->width(), + width() - skip - _cancel->width(), height()); _shownMessagePreviewRect = QRect( st::historyReplySkip, @@ -1000,6 +1002,7 @@ void FieldHeader::updateForwarding( if (readyToForward()) { replyToMessage({}); } + updateControlsGeometry(size()); } rpl::producer FieldHeader::editMsgIdValue() const { @@ -1250,7 +1253,7 @@ bool ComposeControls::focused() const { } rpl::producer ComposeControls::focusedValue() const { - return rpl::single(focused()) | rpl::then(_focusChanges.events()); + return rpl::single(focused()) | rpl::then(_field->focusedChanges()); } rpl::producer ComposeControls::tabbedPanelShownValue() const { @@ -1291,12 +1294,9 @@ auto ComposeControls::sendContentRequests(SendRequestType requestType) const { return (_send->type() == type) && (sendRequestType == requestType); }); auto map = rpl::map_to(Api::SendOptions()); - auto submits = base::qt_signal_producer( - _field.get(), - &Ui::InputField::submitted); return rpl::merge( _send->clicks() | filter | map, - std::move(submits) | filter | map, + _field->submits() | filter | map, _sendCustomRequests.events()); } @@ -1317,12 +1317,9 @@ rpl::producer ComposeControls::editRequests() const { auto filter = rpl::filter([=] { return _send->type() == Ui::SendButton::Type::Save; }); - auto submits = base::qt_signal_producer( - _field.get(), - &Ui::InputField::submitted); return rpl::merge( _send->clicks() | filter | toValue, - std::move(submits) | filter | toValue); + _field->submits() | filter | toValue); } rpl::producer> ComposeControls::attachRequests() const { @@ -1776,17 +1773,22 @@ void ComposeControls::initKeyHandler() { void ComposeControls::initField() { _field->setMaxHeight(st::historyComposeFieldMaxHeight); updateSubmitSettings(); - //Ui::Connect(_field, &Ui::InputField::submitted, [=] { send(); }); - Ui::Connect(_field, &Ui::InputField::cancelled, [=] { escape(); }); - Ui::Connect(_field, &Ui::InputField::tabbed, [=] { fieldTabbed(); }); - Ui::Connect(_field, &Ui::InputField::resized, [=] { updateHeight(); }); - Ui::Connect(_field, &Ui::InputField::focused, [=] { - _focusChanges.fire(true); - }); - Ui::Connect(_field, &Ui::InputField::blurred, [=] { - _focusChanges.fire(false); - }); - Ui::Connect(_field, &Ui::InputField::changed, [=] { fieldChanged(); }); + _field->cancelled( + ) | rpl::start_with_next([=] { + escape(); + }, _field->lifetime()); + _field->tabbed( + ) | rpl::start_with_next([=] { + fieldTabbed(); + }, _field->lifetime()); + _field->heightChanges( + ) | rpl::start_with_next([=] { + updateHeight(); + }, _field->lifetime()); + _field->changes( + ) | rpl::start_with_next([=] { + fieldChanged(); + }, _field->lifetime()); InitMessageField(_show, _field, [=](not_null emoji) { if (_history && Data::AllowEmojiWithoutPremium(_history->peer)) { return true; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index 06d8ae9a8..3dc5e46fa 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -17,7 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/round_rect.h" #include "ui/rp_widget.h" #include "ui/effects/animations.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" class History; class DocumentData; @@ -424,7 +424,6 @@ private: std::unique_ptr _preview; Fn _raiseEmojiSuggestions; - rpl::event_stream _focusChanges; rpl::lifetime _historyLifetime; rpl::lifetime _uploaderSubscriptions; diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 4bcea062b..2155a2b59 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -106,6 +106,99 @@ std::unique_ptr MakePathShiftGradient( st->paletteChanged()); } +bool DefaultElementDelegate::elementUnderCursor( + not_null view) { + return false; +} + +float64 DefaultElementDelegate::elementHighlightOpacity( + not_null item) const { + return 0.; +} + +bool DefaultElementDelegate::elementInSelectionMode() { + return false; +} + +bool DefaultElementDelegate::elementIntersectsRange( + not_null view, + int from, + int till) { + return true; +} + +void DefaultElementDelegate::elementStartStickerLoop( + not_null view) { +} + +void DefaultElementDelegate::elementShowPollResults( + not_null poll, + FullMsgId context) { +} + +void DefaultElementDelegate::elementOpenPhoto( + not_null photo, + FullMsgId context) { +} + +void DefaultElementDelegate::elementOpenDocument( + not_null document, + FullMsgId context, + bool showInMediaView) { +} + +void DefaultElementDelegate::elementCancelUpload(const FullMsgId &context) { +} + +void DefaultElementDelegate::elementShowTooltip( + const TextWithEntities &text, + Fn hiddenCallback) { +} + +bool DefaultElementDelegate::elementHideReply( + not_null view) { + return false; +} + +bool DefaultElementDelegate::elementShownUnread( + not_null view) { + return view->data()->unread(view->data()->history()); +} + +void DefaultElementDelegate::elementSendBotCommand( + const QString &command, + const FullMsgId &context) { +} + +void DefaultElementDelegate::elementHandleViaClick( + not_null bot) { +} + +bool DefaultElementDelegate::elementIsChatWide() { + return false; +} + +void DefaultElementDelegate::elementReplyTo(const FullMsgId &to) { +} + +void DefaultElementDelegate::elementStartInteraction( + not_null view) { +} + +void DefaultElementDelegate::elementStartPremium( + not_null view, + Element *replacing) { +} + +void DefaultElementDelegate::elementCancelPremium( + not_null view) { +} + +QString DefaultElementDelegate::elementAuthorRank( + not_null view) { + return {}; +} + SimpleElementDelegate::SimpleElementDelegate( not_null controller, Fn update) @@ -118,106 +211,15 @@ SimpleElementDelegate::SimpleElementDelegate( SimpleElementDelegate::~SimpleElementDelegate() = default; -bool SimpleElementDelegate::elementUnderCursor( - not_null view) { - return false; -} - -float64 SimpleElementDelegate::elementHighlightOpacity( - not_null item) const { - return 0.; -} - -bool SimpleElementDelegate::elementInSelectionMode() { - return false; -} - -bool SimpleElementDelegate::elementIntersectsRange( - not_null view, - int from, - int till) { - return true; -} - -void SimpleElementDelegate::elementStartStickerLoop( - not_null view) { -} - -void SimpleElementDelegate::elementShowPollResults( - not_null poll, - FullMsgId context) { -} - -void SimpleElementDelegate::elementOpenPhoto( - not_null photo, - FullMsgId context) { -} - -void SimpleElementDelegate::elementOpenDocument( - not_null document, - FullMsgId context, - bool showInMediaView) { -} - -void SimpleElementDelegate::elementCancelUpload(const FullMsgId &context) { -} - -void SimpleElementDelegate::elementShowTooltip( - const TextWithEntities &text, - Fn hiddenCallback) { -} - bool SimpleElementDelegate::elementAnimationsPaused() { return _controller->isGifPausedAtLeastFor(Window::GifPauseReason::Any); } -bool SimpleElementDelegate::elementHideReply(not_null view) { - return false; -} - -bool SimpleElementDelegate::elementShownUnread( - not_null view) { - return view->data()->unread(view->data()->history()); -} - -void SimpleElementDelegate::elementSendBotCommand( - const QString &command, - const FullMsgId &context) { -} - -void SimpleElementDelegate::elementHandleViaClick(not_null bot) { -} - -bool SimpleElementDelegate::elementIsChatWide() { - return false; -} - auto SimpleElementDelegate::elementPathShiftGradient() -> not_null { return _pathGradient.get(); } -void SimpleElementDelegate::elementReplyTo(const FullMsgId &to) { -} - -void SimpleElementDelegate::elementStartInteraction( - not_null view) { -} - -void SimpleElementDelegate::elementStartPremium( - not_null view, - Element *replacing) { -} - -void SimpleElementDelegate::elementCancelPremium( - not_null view) { -} - -QString SimpleElementDelegate::elementAuthorRank( - not_null view) { - return {}; -} - TextSelection UnshiftItemSelection( TextSelection selection, uint16 byLength) { @@ -614,7 +616,24 @@ bool Element::isHidden() const { return isHiddenByGroup(); } +void Element::overrideMedia(std::unique_ptr media) { + Expects(!history()->owner().groups().find(data())); + + _text = Ui::Text::String(st::msgMinWidth); + _textWidth = -1; + _textHeight = 0; + + _media = std::move(media); + if (!pendingResize()) { + history()->owner().requestViewResize(this); + } + _flags |= Flag::MediaOverriden; +} + void Element::refreshMedia(Element *replacing) { + if (_flags & Flag::MediaOverriden) { + return; + } _flags &= ~Flag::HiddenByGroup; const auto item = data(); diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 3faaa355b..1824f7137 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -117,13 +117,8 @@ public: not_null st, Fn update); -class SimpleElementDelegate : public ElementDelegate { +class DefaultElementDelegate : public ElementDelegate { public: - SimpleElementDelegate( - not_null controller, - Fn update); - ~SimpleElementDelegate(); - bool elementUnderCursor(not_null view) override; [[nodiscard]] float64 elementHighlightOpacity( not_null item) const override; @@ -147,7 +142,6 @@ public: void elementShowTooltip( const TextWithEntities &text, Fn hiddenCallback) override; - bool elementAnimationsPaused() override; bool elementHideReply(not_null view) override; bool elementShownUnread(not_null view) override; void elementSendBotCommand( @@ -155,7 +149,6 @@ public: const FullMsgId &context) override; void elementHandleViaClick(not_null bot) override; bool elementIsChatWide() override; - not_null elementPathShiftGradient() override; void elementReplyTo(const FullMsgId &to) override; void elementStartInteraction(not_null view) override; void elementStartPremium( @@ -164,6 +157,17 @@ public: void elementCancelPremium(not_null view) override; QString elementAuthorRank(not_null view) override; +}; + +class SimpleElementDelegate : public DefaultElementDelegate { +public: + SimpleElementDelegate( + not_null controller, + Fn update); + ~SimpleElementDelegate(); + + bool elementAnimationsPaused() override; + not_null elementPathShiftGradient() override; protected: [[nodiscard]] not_null controller() const { @@ -264,6 +268,7 @@ public: CustomEmojiRepainting = 0x0100, ScheduledUntilOnline = 0x0200, TopicRootReply = 0x0400, + MediaOverriden = 0x0800, }; using Flags = base::flags; friend inline constexpr auto is_flag_type(Flag) { return true; } @@ -480,6 +485,8 @@ public: Data::ReactionId, std::unique_ptr>; + void overrideMedia(std::unique_ptr media); + virtual ~Element(); static void Hovered(Element *view); diff --git a/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp b/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp index 95c7c6270..286edd189 100644 --- a/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp +++ b/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp @@ -14,7 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/event_filter.h" #include "base/qt/qt_key_modifiers.h" #include "base/unixtime.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/labels.h" #include "ui/widgets/buttons.h" #include "ui/widgets/popup_menu.h" diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp index 80b546b53..fc22f3de4 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -27,7 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/controls/userpic_button.h" #include "ui/wrap/fade_wrap.h" #include "ui/widgets/buttons.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/popup_menu.h" #include "ui/widgets/menu/menu_add_action_callback_factory.h" #include "ui/effects/radial_animation.h" @@ -45,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_group_call.h" // GroupCall::input. #include "data/data_folder.h" #include "data/data_session.h" +#include "data/data_stories.h" #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_user.h" @@ -766,6 +767,14 @@ void TopBarWidget::setActiveChat( updateControlsVisibility(); updateControlsGeometry(); }, _activeChatLifetime); + + if (const auto channel = peer->asChannel()) { + if (channel->canEditStories() + && !channel->owner().stories().archiveCountKnown( + channel->id)) { + channel->owner().stories().archiveLoadMore(channel->id); + } + } } if (const auto history = _activeChat.key.history()) { @@ -1237,10 +1246,12 @@ bool TopBarWidget::toggleSearch(bool shown, anim::type animated) { _searchCancel.create(this, st::dialogsCancelSearch); _searchCancel->show(anim::type::instant); _searchCancel->setClickedCallback([=] { _searchCancelled.fire({}); }); - QObject::connect(_searchField, &Ui::InputField::submitted, [=] { + _searchField->submits( + ) | rpl::start_with_next([=] { _searchSubmitted.fire({}); - }); - QObject::connect(_searchField, &Ui::InputField::changed, [=] { + }, _searchField->lifetime()); + _searchField->changes( + ) | rpl::start_with_next([=] { const auto was = _searchQuery.current(); const auto now = _searchField->getLastText(); if (_jumpToDate && was.isEmpty() != now.isEmpty()) { @@ -1255,7 +1266,7 @@ bool TopBarWidget::toggleSearch(bool shown, anim::type animated) { } } _searchQuery = now; - }); + }, _searchField->lifetime()); } else { Assert(_searchField != nullptr); } diff --git a/Telegram/SourceFiles/history/view/history_view_view_button.cpp b/Telegram/SourceFiles/history/view/history_view_view_button.cpp index 71b258ed9..197918968 100644 --- a/Telegram/SourceFiles/history/view/history_view_view_button.cpp +++ b/Telegram/SourceFiles/history/view/history_view_view_button.cpp @@ -68,6 +68,8 @@ inline auto WebPageToPhrase(not_null webpage) { : (type == WebPageType::GroupWithRequest || type == WebPageType::ChannelWithRequest) ? tr::lng_view_button_request_join(tr::now) + : (type == WebPageType::ChannelBoost) + ? tr::lng_view_button_boost(tr::now) : (type == WebPageType::VoiceChat) ? tr::lng_view_button_voice_chat(tr::now) : (type == WebPageType::Livestream) @@ -103,6 +105,46 @@ inline auto WebPageToPhrase(not_null webpage) { }); } +[[nodiscard]] ClickHandlerPtr SponsoredLink( + not_null sponsored) { + if (!sponsored->externalLink.isEmpty()) { + class ClickHandler : public UrlClickHandler { + public: + using UrlClickHandler::UrlClickHandler; + + QString copyToClipboardContextItemText() const override { + return QString(); + } + + }; + + return std::make_shared( + sponsored->externalLink, + false); + } else { + return std::make_shared([](ClickContext context) { + const auto my = context.other.value(); + const auto controller = my.sessionWindow.get(); + if (!controller) { + return; + } + const auto &data = controller->session().data(); + const auto details = data.sponsoredMessages().lookupDetails( + my.itemId); + if (!details.externalLink.isEmpty()) { + File::OpenUrl(details.externalLink); + } else if (details.hash) { + Api::CheckChatInvite(controller, *details.hash); + } else if (details.peer) { + controller->showPeerHistory( + details.peer, + Window::SectionShow::Way::Forward, + details.msgId); + } + }); + } +} + } // namespace struct ViewButton::Inner { @@ -137,6 +179,7 @@ bool ViewButton::MediaHasViewButton( return (type == WebPageType::Message) || (type == WebPageType::Group) || (type == WebPageType::Channel) + || (type == WebPageType::ChannelBoost) // || (type == WebPageType::Bot) || (type == WebPageType::User) || (type == WebPageType::VoiceChat) @@ -156,24 +199,7 @@ ViewButton::Inner::Inner( not_null sponsored, Fn updateCallback) : margins(st::historyViewButtonMargins) -, link(std::make_shared([=](ClickContext context) { - const auto my = context.other.value(); - if (const auto controller = my.sessionWindow.get()) { - const auto &data = controller->session().data(); - const auto itemId = my.itemId; - const auto details = data.sponsoredMessages().lookupDetails(itemId); - if (!details.externalLink.isEmpty()) { - File::OpenUrl(details.externalLink); - } else if (details.hash) { - Api::CheckChatInvite(controller, *details.hash); - } else if (details.peer) { - controller->showPeerHistory( - details.peer, - Window::SectionShow::Way::Forward, - details.msgId); - } - } -})) +, link(SponsoredLink(sponsored)) , updateCallback(std::move(updateCallback)) , externalLink(sponsored->type == SponsoredType::ExternalLink) , text(st::historyViewButtonTextStyle, SponsoredPhrase(sponsored->type)) { diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 074f89dab..ba5a5bbef 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -1923,6 +1923,9 @@ bool Gif::dataLoaded() const { } bool Gif::needInfoDisplay() const { + if (_parent->data()->isFakeBotAbout()) { + return false; + } return _parent->data()->isSending() || _data->uploading() || _parent->isUnderCursor() diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index a5cf0f41c..0c8849699 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -850,6 +850,9 @@ bool Photo::dataLoaded() const { } bool Photo::needInfoDisplay() const { + if (_parent->data()->isFakeBotAbout()) { + return false; + } return _parent->data()->isSending() || _parent->data()->hasFailed() || _parent->isUnderCursor() diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp index c07fc9269..13570306b 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp @@ -424,7 +424,8 @@ void Selector::paintCollapsed(QPainter &p) { } void Selector::paintExpanding(Painter &p, float64 progress) { - const auto rects = paintExpandingBg(p, progress); + const auto rects = updateExpandingRects(progress); + paintExpandingBg(p, rects); progress /= kFullDuration; if (_footer) { _footer->paintExpanding( @@ -443,8 +444,7 @@ void Selector::paintExpanding(Painter &p, float64 progress) { paintFadingExpandIcon(p, progress); } -auto Selector::paintExpandingBg(QPainter &p, float64 progress) --> ExpandingRects { +Selector::ExpandingRects Selector::updateExpandingRects(float64 progress) { progress = (progress >= kExpandDuration) ? 1. : (progress / kExpandDuration); @@ -463,22 +463,6 @@ auto Selector::paintExpandingBg(QPainter &p, float64 progress) (height() - _outer.y() - _outer.height()), expanding); const auto outer = _outer.marginsAdded({ 0, expandUp, 0, expandDown }); - if (_useTransparency) { - const auto pattern = _cachedRound.validateFrame(frame, 1., radius); - const auto fill = _cachedRound.FillWithImage(p, outer, pattern); - if (!fill.isEmpty()) { - p.fillRect(fill, _st.bg); - } - } else { - const auto inner = outer.marginsRemoved(marginsForShadow()); - p.fillRect(inner, _st.bg); - p.fillRect( - inner.x(), - inner.y() + inner.height(), - inner.width(), - st::lineWidth, - st::defaultPopupMenu.shadow.fallback); - } const auto categories = anim::interpolate( 0, extendTopForCategories(), @@ -495,9 +479,26 @@ auto Selector::paintExpandingBg(QPainter &p, float64 progress) .radius = radius, .expanding = expanding, .finalBottom = height() - margins.bottom(), + .frame = frame, + .outer = outer, }; } +void Selector::paintExpandingBg(QPainter &p, const ExpandingRects &rects) { + if (_useTransparency) { + const auto pattern = _cachedRound.validateFrame( + rects.frame, + 1., + rects.radius); + const auto fill = _cachedRound.FillWithImage(p, rects.outer, pattern); + if (!fill.isEmpty()) { + p.fillRect(fill, _st.bg); + } + } else { + paintNonTransparentExpandRect(p, rects.outer - marginsForShadow()); + } +} + void Selector::paintFadingExpandIcon(QPainter &p, float64 progress) { if (progress >= 1.) { return; @@ -514,6 +515,18 @@ void Selector::paintFadingExpandIcon(QPainter &p, float64 progress) { p.setOpacity(1.); } +void Selector::paintNonTransparentExpandRect( + QPainter &p, + const QRect &inner) const { + p.fillRect(inner, _st.bg); + p.fillRect( + inner.x(), + inner.y() + inner.height(), + inner.width(), + st::lineWidth, + st::defaultPopupMenu.shadow.fallback); +} + void Selector::paintExpanded(QPainter &p) { if (!_expandFinished) { finishExpand(); @@ -521,14 +534,7 @@ void Selector::paintExpanded(QPainter &p) { if (_useTransparency) { p.drawImage(0, 0, _paintBuffer); } else { - const auto inner = rect().marginsRemoved(marginsForShadow()); - p.fillRect(inner, _st.bg); - p.fillRect( - inner.x(), - inner.y() + inner.height(), - inner.width(), - st::lineWidth, - st::defaultPopupMenu.shadow.fallback); + paintNonTransparentExpandRect(p, rect() - marginsForShadow()); } } @@ -536,6 +542,7 @@ void Selector::finishExpand() { Expects(!_expandFinished); _expandFinished = true; + updateExpandingRects(kExpandDuration); if (_useTransparency) { auto q = QPainter(&_paintBuffer); q.setCompositionMode(QPainter::CompositionMode_Source); diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h index bf9ddb72b..f6b773dcd 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h @@ -95,6 +95,8 @@ private: float64 radius = 0.; float64 expanding = 0.; int finalBottom = 0; + int frame = 0; + QRect outer; }; Selector( @@ -117,12 +119,15 @@ private: void paintAppearing(QPainter &p); void paintCollapsed(QPainter &p); void paintExpanding(Painter &p, float64 progress); - ExpandingRects paintExpandingBg(QPainter &p, float64 progress); + void paintExpandingBg(QPainter &p, const ExpandingRects &rects); void paintFadingExpandIcon(QPainter &p, float64 progress); void paintExpanded(QPainter &p); + void paintNonTransparentExpandRect(QPainter &p, const QRect &) const; void paintBubble(QPainter &p, int innerWidth); void paintBackgroundToBuffer(); + ExpandingRects updateExpandingRects(float64 progress); + [[nodiscard]] int recentCount() const; [[nodiscard]] int countSkipLeft() const; [[nodiscard]] int lookupSelectedIndex(QPoint position) const; diff --git a/Telegram/SourceFiles/info/downloads/info_downloads_provider.cpp b/Telegram/SourceFiles/info/downloads/info_downloads_provider.cpp index 36059ac33..5dbea6fdf 100644 --- a/Telegram/SourceFiles/info/downloads/info_downloads_provider.cpp +++ b/Telegram/SourceFiles/info/downloads/info_downloads_provider.cpp @@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "storage/storage_shared_media.h" #include "layout/layout_selection.h" +#include "styles/style_overview.h" namespace Info::Downloads { namespace { diff --git a/Telegram/SourceFiles/info/info_content_widget.cpp b/Telegram/SourceFiles/info/info_content_widget.cpp index e96ba3cfc..c14624677 100644 --- a/Telegram/SourceFiles/info/info_content_widget.cpp +++ b/Telegram/SourceFiles/info/info_content_widget.cpp @@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_session_controller.h" #include "ui/widgets/scroll_area.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/wrap/padding_wrap.h" #include "ui/search_field_controller.h" #include "lang/lang_keys.h" diff --git a/Telegram/SourceFiles/info/info_top_bar.cpp b/Telegram/SourceFiles/info/info_top_bar.cpp index 9d8ee6324..c1ef0157d 100644 --- a/Telegram/SourceFiles/info/info_top_bar.cpp +++ b/Telegram/SourceFiles/info/info_top_bar.cpp @@ -23,7 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/shadow.h" #include "ui/wrap/fade_wrap.h" #include "ui/wrap/padding_wrap.h" @@ -257,7 +257,8 @@ void TopBar::createSearchView( }; cancel->addClickHandler(cancelSearch); - field->connect(field, &Ui::InputField::cancelled, cancelSearch); + field->cancelled( + ) | rpl::start_with_next(cancelSearch, field->lifetime()); wrap->widthValue( ) | rpl::start_with_next([=](int newWidth) { diff --git a/Telegram/SourceFiles/info/media/info_media_buttons.h b/Telegram/SourceFiles/info/media/info_media_buttons.h index 9d3693280..773d2719a 100644 --- a/Telegram/SourceFiles/info/media/info_media_buttons.h +++ b/Telegram/SourceFiles/info/media/info_media_buttons.h @@ -129,24 +129,27 @@ inline auto AddCommonGroupsButton( inline auto AddStoriesButton( Ui::VerticalLayout *parent, not_null navigation, - not_null user, + not_null peer, Ui::MultiSlideTracker &tracker) { auto count = rpl::single(0) | rpl::then(Data::SavedStoriesIds( - user, + peer, ServerMaxStoryId - 1, 0 ) | rpl::map([](const Data::StoriesIdsSlice &slice) { return slice.fullCount().value_or(0); })); + const auto phrase = peer->isChannel() ? (+[](int count) { + return tr::lng_profile_posts(tr::now, lt_count, count); + }) : (+[](int count) { + return tr::lng_profile_saved_stories(tr::now, lt_count, count); + }); auto result = AddCountedButton( parent, std::move(count), - [](int count) { - return tr::lng_profile_saved_stories(tr::now, lt_count, count); - }, + phrase, tracker)->entity(); result->addClickHandler([=] { - navigation->showSection(Info::Stories::Make(user)); + navigation->showSection(Info::Stories::Make(peer)); }); return result; }; diff --git a/Telegram/SourceFiles/info/media/info_media_list_section.cpp b/Telegram/SourceFiles/info/media/info_media_list_section.cpp index 2bf957192..ba207ff28 100644 --- a/Telegram/SourceFiles/info/media/info_media_list_section.cpp +++ b/Telegram/SourceFiles/info/media/info_media_list_section.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/storage_shared_media.h" #include "layout/layout_selection.h" #include "ui/painter.h" +#include "styles/style_chat_helpers.h" #include "styles/style_info.h" namespace Info::Media { diff --git a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp index 5bc2155cb..1a42e6379 100644 --- a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp +++ b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp @@ -1208,24 +1208,37 @@ void ListWidget::toggleStoryPin( list.push_back({ id.peer, StoryIdFromMsgId(id.msg) }); } } + if (list.empty()) { + return; + } + const auto channel = peerIsChannel(list.front().peer); const auto count = int(list.size()); const auto pin = (_controller->storiesTab() == Stories::Tab::Archive); const auto controller = _controller; const auto sure = [=](Fn close) { + using namespace ::Media::Stories; controller->session().data().stories().togglePinnedList(list, pin); controller->showToast( - ::Media::Stories::PrepareTogglePinnedToast(count, pin)); + PrepareTogglePinnedToast(channel, count, pin)); close(); if (confirmed) { confirmed(); } }; const auto onePhrase = pin - ? tr::lng_stories_save_sure - : tr::lng_stories_archive_sure; + ? (channel + ? tr::lng_stories_channel_save_sure + : tr::lng_stories_save_sure) + : (channel + ? tr::lng_stories_channel_archive_sure + : tr::lng_stories_archive_sure); const auto manyPhrase = pin - ? tr::lng_stories_save_sure_many - : tr::lng_stories_archive_sure_many; + ? (channel + ? tr::lng_stories_channel_save_sure_many + : tr::lng_stories_save_sure_many) + : (channel + ? tr::lng_stories_channel_archive_sure_many + : tr::lng_stories_archive_sure_many); _controller->parentController()->show(Ui::MakeConfirmBox({ .text = (count == 1 ? onePhrase() diff --git a/Telegram/SourceFiles/info/media/info_media_provider.cpp b/Telegram/SourceFiles/info/media/info_media_provider.cpp index 3e75975fa..2fc69733e 100644 --- a/Telegram/SourceFiles/info/media/info_media_provider.cpp +++ b/Telegram/SourceFiles/info/media/info_media_provider.cpp @@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_peer_values.h" #include "data/data_document.h" #include "styles/style_info.h" +#include "styles/style_overview.h" namespace Info::Media { namespace { diff --git a/Telegram/SourceFiles/info/profile/info_profile_badge.cpp b/Telegram/SourceFiles/info/profile/info_profile_badge.cpp index 61a99b336..73437e1dd 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_badge.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_badge.cpp @@ -22,6 +22,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Info::Profile { namespace { +[[nodiscard]] rpl::producer ContentForPeer( + not_null peer) { + return rpl::combine( + BadgeValue(peer), + EmojiStatusIdValue(peer) + ) | rpl::map([=](BadgeType badge, DocumentId emojiStatusId) { + return Badge::Content{ badge, emojiStatusId }; + }); +} + } // namespace Badge::Badge( @@ -32,18 +42,37 @@ Badge::Badge( Fn animationPaused, int customStatusLoopsLimit, base::flags allowed) +: Badge( + parent, + st, + &peer->session(), + ContentForPeer(peer), + emojiStatusPanel, + std::move(animationPaused), + customStatusLoopsLimit, + allowed) { +} + +Badge::Badge( + not_null parent, + const style::InfoPeerBadge &st, + not_null session, + rpl::producer content, + EmojiStatusPanel *emojiStatusPanel, + Fn animationPaused, + int customStatusLoopsLimit, + base::flags allowed) : _parent(parent) , _st(st) -, _peer(peer) +, _session(session) , _emojiStatusPanel(emojiStatusPanel) , _customStatusLoopsLimit(customStatusLoopsLimit) , _allowed(allowed) , _animationPaused(std::move(animationPaused)) { - rpl::combine( - BadgeValue(peer), - EmojiStatusIdValue(peer) - ) | rpl::start_with_next([=](BadgeType badge, DocumentId emojiStatusId) { - setBadge(badge, emojiStatusId); + std::move( + content + ) | rpl::start_with_next([=](Content content) { + setContent(content); }, _lifetime); } @@ -51,37 +80,36 @@ Ui::RpWidget *Badge::widget() const { return _view.data(); } -void Badge::setBadge(BadgeType badge, DocumentId emojiStatusId) { - if (!(_allowed & badge) - || (!_peer->session().premiumBadgesShown() - && badge == BadgeType::Premium)) { - badge = BadgeType::None; +void Badge::setContent(Content content) { + if (!(_allowed & content.badge) + || (!_session->premiumBadgesShown() + && content.badge == BadgeType::Premium)) { + content.badge = BadgeType::None; } - if (!(_allowed & badge)) { - badge = BadgeType::None; + if (!(_allowed & content.badge)) { + content.badge = BadgeType::None; } - if (badge != BadgeType::Premium) { - emojiStatusId = 0; + if (content.badge != BadgeType::Premium) { + content.emojiStatusId = 0; } - if (_badge == badge && _emojiStatusId == emojiStatusId) { + if (_content == content) { return; } - _badge = badge; - _emojiStatusId = emojiStatusId; + _content = content; _emojiStatus = nullptr; _view.destroy(); - if (_badge == BadgeType::None) { + if (_content.badge == BadgeType::None) { _updated.fire({}); return; } _view.create(_parent); _view->show(); - switch (_badge) { + switch (_content.badge) { case BadgeType::Verified: case BadgeType::Premium: { - if (_emojiStatusId) { - _emojiStatus = _peer->owner().customEmojiManager().create( - _emojiStatusId, + if (const auto id = _content.emojiStatusId) { + _emojiStatus = _session->data().customEmojiManager().create( + id, [raw = _view.data()] { raw->update(); }, sizeTag()); if (_customStatusLoopsLimit > 0) { @@ -107,7 +135,7 @@ void Badge::setBadge(BadgeType badge, DocumentId emojiStatusId) { } }, _view->lifetime()); } else { - const auto icon = (_badge == BadgeType::Verified) + const auto icon = (_content.badge == BadgeType::Verified) ? &_st.verified : &_st.premium; _view->resize(icon->size()); @@ -120,7 +148,7 @@ void Badge::setBadge(BadgeType badge, DocumentId emojiStatusId) { } break; case BadgeType::Scam: case BadgeType::Fake: { - const auto fake = (_badge == BadgeType::Fake); + const auto fake = (_content.badge == BadgeType::Fake); const auto size = Ui::ScamBadgeSize(fake); const auto skip = st::infoVerifiedCheckPosition.x(); _view->resize( @@ -139,7 +167,7 @@ void Badge::setBadge(BadgeType badge, DocumentId emojiStatusId) { } break; } - if (_badge != BadgeType::Premium || !_premiumClickCallback) { + if (_content.badge != BadgeType::Premium || !_premiumClickCallback) { _view->setAttribute(Qt::WA_TransparentForMouseEvents); } else { _view->setClickedCallback(_premiumClickCallback); @@ -150,7 +178,7 @@ void Badge::setBadge(BadgeType badge, DocumentId emojiStatusId) { void Badge::setPremiumClickCallback(Fn callback) { _premiumClickCallback = std::move(callback); - if (_view && _badge == BadgeType::Premium) { + if (_view && _content.badge == BadgeType::Premium) { if (!_premiumClickCallback) { _view->setAttribute(Qt::WA_TransparentForMouseEvents); } else { @@ -169,7 +197,8 @@ void Badge::move(int left, int top, int bottom) { return; } const auto star = !_emojiStatus - && (_badge == BadgeType::Premium || _badge == BadgeType::Verified); + && (_content.badge == BadgeType::Premium + || _content.badge == BadgeType::Verified); const auto fake = !_emojiStatus && !star; const auto skip = fake ? 0 : _st.position.x(); const auto badgeLeft = left + skip; diff --git a/Telegram/SourceFiles/info/profile/info_profile_badge.h b/Telegram/SourceFiles/info/profile/info_profile_badge.h index 9e9d01d75..daada96d1 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_badge.h +++ b/Telegram/SourceFiles/info/profile/info_profile_badge.h @@ -18,6 +18,10 @@ namespace Data { enum class CustomEmojiSizeTag : uchar; } // namespace Data +namespace Main { +class Session; +} // namespace Main + namespace Ui { class RpWidget; class AbstractButton; @@ -45,7 +49,25 @@ public: EmojiStatusPanel *emojiStatusPanel, Fn animationPaused, int customStatusLoopsLimit = 0, - base::flags allowed = base::flags::from_raw(-1)); + base::flags allowed + = base::flags::from_raw(-1)); + + struct Content { + BadgeType badge = BadgeType::None; + DocumentId emojiStatusId = 0; + + friend inline constexpr bool operator==(Content, Content) = default; + }; + Badge( + not_null parent, + const style::InfoPeerBadge &st, + not_null session, + rpl::producer content, + EmojiStatusPanel *emojiStatusPanel, + Fn animationPaused, + int customStatusLoopsLimit = 0, + base::flags allowed + = base::flags::from_raw(-1)); [[nodiscard]] Ui::RpWidget *widget() const; @@ -56,17 +78,16 @@ public: [[nodiscard]] Data::CustomEmojiSizeTag sizeTag() const; private: - void setBadge(BadgeType badge, DocumentId emojiStatusId); + void setContent(Content content); const not_null _parent; const style::InfoPeerBadge &_st; - const not_null _peer; + const not_null _session; EmojiStatusPanel *_emojiStatusPanel = nullptr; const int _customStatusLoopsLimit = 0; - DocumentId _emojiStatusId = 0; std::unique_ptr _emojiStatus; base::flags _allowed; - BadgeType _badge = BadgeType(); + Content _content; Fn _premiumClickCallback; Fn _animationPaused; object_ptr _view = { nullptr }; diff --git a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp index 642b4d422..24c59e8c2 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp @@ -187,12 +187,15 @@ object_ptr InnerWidget::setupSharedMedia( st::infoSharedMediaButtonIconPosition); }; auto addStoriesButton = [&]( - not_null user, + not_null peer, const style::icon &icon) { + if (peer->isChat()) { + return; + } auto result = Media::AddStoriesButton( content, _controller, - user, + peer, tracker); object_ptr( result, @@ -200,9 +203,7 @@ object_ptr InnerWidget::setupSharedMedia( st::infoSharedMediaButtonIconPosition); }; - if (auto user = _peer->asUser()) { - addStoriesButton(user, st::infoIconMediaStories); - } + addStoriesButton(_peer, st::infoIconMediaStories); addMediaButton(MediaType::Photo, st::infoIconMediaPhoto); addMediaButton(MediaType::Video, st::infoIconMediaVideo); addMediaButton(MediaType::File, st::infoIconMediaFile); diff --git a/Telegram/SourceFiles/info/profile/info_profile_members.cpp b/Telegram/SourceFiles/info/profile/info_profile_members.cpp index b226935c3..85e689903 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_members.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_members.cpp @@ -19,7 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/info_memento.h" #include "ui/widgets/labels.h" #include "ui/widgets/buttons.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/scroll_area.h" #include "ui/wrap/padding_wrap.h" #include "ui/text/text_utilities.h" // Ui::Text::ToUpper diff --git a/Telegram/SourceFiles/info/profile/info_profile_widget.cpp b/Telegram/SourceFiles/info/profile/info_profile_widget.cpp index 40b374ef1..d1209a246 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_widget.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_widget.cpp @@ -110,10 +110,8 @@ rpl::producer Widget::title() { rpl::producer Widget::titleStories() { const auto peer = controller()->key().peer(); - if (const auto user = peer->asUser()) { - if (!user->isBot()) { - return Dialogs::Stories::LastForPeer(user); - } + if (peer && !peer->isChat()) { + return Dialogs::Stories::LastForPeer(peer); } return nullptr; } diff --git a/Telegram/SourceFiles/info/stories/info_stories_inner_widget.cpp b/Telegram/SourceFiles/info/stories/info_stories_inner_widget.cpp index 7c572746d..a6a43432d 100644 --- a/Telegram/SourceFiles/info/stories/info_stories_inner_widget.cpp +++ b/Telegram/SourceFiles/info/stories/info_stories_inner_widget.cpp @@ -103,11 +103,11 @@ void InnerWidget::setupTop() { const auto key = _controller->key(); const auto peer = key.storiesPeer(); if (peer - && peer->isSelf() && key.storiesTab() == Stories::Tab::Saved + && peer->owner().stories().hasArchive(peer) && _isStackBottom) { createButtons(); - } else if (key.storiesTab() == Stories::Tab::Archive) { + } else if (peer && key.storiesTab() == Stories::Tab::Archive) { createAboutArchive(); } else { _top.destroy(); @@ -120,8 +120,9 @@ void InnerWidget::createButtons() { _top->show(); _topHeight = _top->heightValue(); - const auto stories = &_controller->session().data().stories(); - const auto self = _controller->session().user(); + const auto key = _controller->key(); + const auto peer = key.storiesPeer(); + const auto stories = &peer->owner().stories(); const auto archive = ::Settings::AddButton( _top, tr::lng_stories_archive_button(), @@ -133,8 +134,13 @@ void InnerWidget::createButtons() { }); auto count = rpl::single( rpl::empty - ) | rpl::then(stories->archiveChanged()) | rpl::map([=] { - const auto value = stories->archiveCount(); + ) | rpl::then( + stories->archiveChanged( + ) | rpl::filter( + rpl::mappers::_1 == peer->id + ) | rpl::to_empty + ) | rpl::map([=] { + const auto value = stories->archiveCount(peer->id); return (value > 0) ? QString::number(value) : QString(); }); ::Settings::CreateRightLabel( @@ -157,7 +163,7 @@ void InnerWidget::createButtons() { using namespace Dialogs::Stories; auto last = LastForPeer( - self + peer ) | rpl::map([=](Content &&content) { for (auto &element : content.elements) { element.unreadCount = 0; @@ -190,7 +196,7 @@ void InnerWidget::createButtons() { }, thumbs->lifetime()); thumbs->setAttribute(Qt::WA_TransparentForMouseEvents); recent->addClickHandler([=] { - _controller->parentController()->openPeerStories(self->id); + _controller->parentController()->openPeerStories(peer->id); }); object_ptr( recent, @@ -219,11 +225,14 @@ void InnerWidget::createAboutArchive() { _top->show(); _topHeight = _top->heightValue(); + const auto peer = _controller->key().storiesPeer(); _top->add(object_ptr( _top, object_ptr( _top, - tr::lng_stories_archive_about(), + (peer->isChannel() + ? tr::lng_stories_channel_archive_about + : tr::lng_stories_archive_about)(), st::infoStoriesAboutArchive), st::infoStoriesAboutArchivePadding)); diff --git a/Telegram/SourceFiles/info/stories/info_stories_provider.cpp b/Telegram/SourceFiles/info/stories/info_stories_provider.cpp index 82f0ca5f2..7fdd26a63 100644 --- a/Telegram/SourceFiles/info/stories/info_stories_provider.cpp +++ b/Telegram/SourceFiles/info/stories/info_stories_provider.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/media/info_media_list_section.h" #include "info/info_controller.h" #include "data/data_changes.h" +#include "data/data_channel.h" #include "data/data_document.h" #include "data/data_media_types.h" #include "data/data_session.h" @@ -74,6 +75,9 @@ Type Provider::type() { } bool Provider::hasSelectRestriction() { + if (const auto channel = _peer->asChannel()) { + return !channel->canEditStories() && !channel->canDeleteStories(); + } return !_peer->isSelf(); } @@ -174,10 +178,9 @@ void Provider::setSearchQuery(QString query) { void Provider::refreshViewer() { _viewerLifetime.destroy(); const auto idForViewer = _aroundId; - const auto session = &_peer->session(); auto ids = (_tab == Tab::Saved) ? Data::SavedStoriesIds(_peer, idForViewer, _idsLimit) - : Data::ArchiveStoriesIds(session, idForViewer, _idsLimit); + : Data::ArchiveStoriesIds(_peer, idForViewer, _idsLimit); std::move( ids ) | rpl::start_with_next([=](Data::StoriesIdsSlice &&slice) { @@ -346,24 +349,21 @@ ListItemSelectionData Provider::computeSelectionData( not_null item, TextSelection selection) { auto result = ListItemSelectionData(selection); + const auto id = item->id; + if (!IsStoryMsgId(id)) { + return result; + } const auto peer = item->history()->peer; - result.canDelete = peer->isSelf(); - result.canForward = [&] { - if (!peer->isSelf()) { - return false; - } - const auto id = item->id; - if (!IsStoryMsgId(id)) { - return false; - } - const auto maybeStory = peer->owner().stories().lookup( - { peer->id, StoryIdFromMsgId(id) }); - if (!maybeStory) { - return false; - } - return (*maybeStory)->canShare(); - }(); - result.canToggleStoryPin = peer->isSelf(); + const auto channel = peer->asChannel(); + const auto maybeStory = peer->owner().stories().lookup( + { peer->id, StoryIdFromMsgId(id) }); + if (maybeStory) { + const auto story = *maybeStory; + result.canForward = peer->isSelf() && story->canShare(); + result.canDelete = story->canDelete(); + } + result.canToggleStoryPin = peer->isSelf() + || (channel && channel->canEditStories()); return result; } diff --git a/Telegram/SourceFiles/info/userpic/info_userpic_emoji_builder_preview.cpp b/Telegram/SourceFiles/info/userpic/info_userpic_emoji_builder_preview.cpp index c7997381c..59e78131c 100644 --- a/Telegram/SourceFiles/info/userpic/info_userpic_emoji_builder_preview.cpp +++ b/Telegram/SourceFiles/info/userpic/info_userpic_emoji_builder_preview.cpp @@ -47,9 +47,8 @@ PreviewPainter::PreviewPainter(int size) } } -not_null PreviewPainter::document() const { - Expects(_media != nullptr); - return _media->owner(); +DocumentData *PreviewPainter::document() const { + return _media ? _media->owner().get() : nullptr; } void PreviewPainter::setPlayOnce(bool value) { @@ -183,7 +182,7 @@ void EmojiUserpic::result(int size, Fn done) { const auto painter = lifetime().make_state(size); // Reset to the first frame. const auto document = _painter.document(); - painter->setDocument(document, [=] { + const auto callback = [=] { auto background = GenerateGradient(Size(size), _colors, false); { @@ -194,12 +193,17 @@ void EmojiUserpic::result(int size, Fn done) { } } } - if (*_playOnce) { + if (*_playOnce && document) { done({ std::move(background), document->id, _colors }); } else { done({ std::move(background) }); } - }); + }; + if (document) { + painter->setDocument(document, callback); + } else { + callback(); + } } void EmojiUserpic::setGradientColors(std::vector colors) { diff --git a/Telegram/SourceFiles/info/userpic/info_userpic_emoji_builder_preview.h b/Telegram/SourceFiles/info/userpic/info_userpic_emoji_builder_preview.h index 13561ac79..9961c54a7 100644 --- a/Telegram/SourceFiles/info/userpic/info_userpic_emoji_builder_preview.h +++ b/Telegram/SourceFiles/info/userpic/info_userpic_emoji_builder_preview.h @@ -29,7 +29,7 @@ class PreviewPainter final { public: PreviewPainter(int size); - [[nodiscard]] not_null document() const; + [[nodiscard]] DocumentData *document() const; void setPlayOnce(bool value); void setDocument( diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index be7a81472..8f77bbf4b 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -7,9 +7,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "inline_bots/bot_attach_web_view.h" +#include "api/api_blocked_peers.h" #include "api/api_common.h" #include "core/click_handler_types.h" #include "data/data_bot_app.h" +#include "data/data_changes.h" #include "data/data_user.h" #include "data/data_file_origin.h" #include "data/data_document.h" @@ -46,6 +48,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "mainwidget.h" #include "styles/style_boxes.h" +#include "styles/style_layers.h" #include "styles/style_menu_icons.h" #include @@ -54,11 +57,7 @@ namespace InlineBots { namespace { constexpr auto kProlongTimeout = 60 * crl::time(1000); - -struct ParsedBot { - UserData *bot = nullptr; - bool inactive = false; -}; +constexpr auto kRefreshBotsTimeout = 60 * 60 * crl::time(1000); [[nodiscard]] DocumentData *ResolveIcon( not_null session, @@ -111,8 +110,13 @@ struct ParsedBot { .user = user, .icon = ResolveIcon(session, data), .name = qs(data.vshort_name()), - .types = ResolvePeerTypes(data.vpeer_types().v), + .types = (data.vpeer_types() + ? ResolvePeerTypes(data.vpeer_types()->v) + : PeerTypes()), .inactive = data.is_inactive(), + .inMainMenu = data.is_show_in_side_menu(), + .inAttachMenu = data.is_show_in_attach_menu(), + .disclaimerRequired = data.is_side_menu_disclaimer_needed(), .hasSettings = data.is_has_settings(), .requestWriteAccess = data.is_request_write_access(), } : std::optional(); @@ -120,6 +124,10 @@ struct ParsedBot { if (result && result->icon) { result->icon->forceToCache(true); } + if (const auto icon = result->icon) { + result->media = icon->createMediaView(); + icon->save(Data::FileOrigin(), {}); + } return result; } @@ -190,6 +198,78 @@ void ShowChooseBox( return result; } +void FillDisclaimerBox(not_null box, Fn done) { + const auto updateCheck = std::make_shared>(); + const auto validateCheck = std::make_shared>(); + + const auto callback = [=](Fn close) { + if (validateCheck && (*validateCheck)()) { + done(); + close(); + } + }; + + const auto padding = st::boxRowPadding; + Ui::ConfirmBox(box, { + .text = tr::lng_mini_apps_disclaimer_text( + tr::now, + Ui::Text::RichLangValue), + .confirmed = callback, + .confirmText = tr::lng_box_ok(), + .labelPadding = QMargins(padding.left(), 0, padding.right(), 0), + .title = tr::lng_mini_apps_disclaimer_title(), + }); + + auto checkView = std::make_unique( + st::defaultCheck, + false, + [=] { if (*updateCheck) { (*updateCheck)(); } }); + const auto check = checkView.get(); + const auto row = box->addRow( + object_ptr( + box.get(), + tr::lng_mini_apps_disclaimer_button( + lt_link, + rpl::single(Ui::Text::Link( + tr::lng_mini_apps_disclaimer_link(tr::now), + tr::lng_mini_apps_tos_url(tr::now))), + Ui::Text::WithEntities), + st::defaultBoxCheckbox, + std::move(checkView)), + { + st::boxRowPadding.left(), + st::boxRowPadding.left(), + st::boxRowPadding.right(), + 0, + }); + row->setAllowTextLines(5); + row->setClickHandlerFilter([=]( + const ClickHandlerPtr &link, + Qt::MouseButton button) { + ActivateClickHandler(row, link, ClickContext{ + .button = button, + .other = QVariant::fromValue(ClickHandlerContext{ + .show = box->uiShow(), + }) + }); + return false; + }); + + (*updateCheck) = [=] { row->update(); }; + + const auto showError = Ui::CheckView::PrepareNonToggledError( + check, + box->lifetime()); + + (*validateCheck) = [=] { + if (check->checked()) { + return true; + } + showError(); + return false; + }; +} + class BotAction final : public Ui::Menu::ItemBase { public: BotAction( @@ -214,19 +294,18 @@ private: int contentHeight() const override; void prepare(); - void validateIcon(); void paint(Painter &p); const not_null _dummyAction; const style::Menu &_st; const AttachWebViewBot _bot; + MenuBotIcon _icon; + base::unique_qptr _menu; rpl::event_stream _forceShown; Ui::Text::String _text; - QImage _mask; - QImage _icon; int _textWidth = 0; const int _height; @@ -241,6 +320,7 @@ BotAction::BotAction( , _dummyAction(new QAction(parent)) , _st(st) , _bot(bot) +, _icon(this, _bot.media) , _height(_st.itemPadding.top() + _st.itemStyle.font->height + _st.itemPadding.bottom()) { @@ -248,55 +328,19 @@ BotAction::BotAction( initResizeHook(parent->sizeValue()); setClickedCallback(std::move(callback)); + _icon.move(_st.itemIconPosition); + paintRequest( ) | rpl::start_with_next([=] { Painter p(this); paint(p); }, lifetime()); - style::PaletteChanged( - ) | rpl::start_with_next([=] { - _icon = QImage(); - update(); - }, lifetime()); - enableMouseSelecting(); prepare(); } -void BotAction::validateIcon() { - if (_mask.isNull()) { - if (!_bot.media || !_bot.media->loaded()) { - return; - } - auto icon = QSvgRenderer(_bot.media->bytes()); - if (!icon.isValid()) { - _mask = QImage( - QSize(1, 1) * style::DevicePixelRatio(), - QImage::Format_ARGB32_Premultiplied); - _mask.fill(Qt::transparent); - } else { - const auto size = style::ConvertScale(icon.defaultSize()); - _mask = QImage( - size * style::DevicePixelRatio(), - QImage::Format_ARGB32_Premultiplied); - _mask.setDevicePixelRatio(style::DevicePixelRatio()); - _mask.fill(Qt::transparent); - { - auto p = QPainter(&_mask); - icon.render(&p, QRect(QPoint(), size)); - } - _mask = Images::Colored(std::move(_mask), QColor(255, 255, 255)); - } - } - if (_icon.isNull()) { - _icon = style::colorizeImage(_mask, st::menuIconColor); - } -} - void BotAction::paint(Painter &p) { - validateIcon(); - const auto selected = isSelected(); if (selected && _st.itemBgOver->c.alpha() < 255) { p.fillRect(0, 0, width(), _height, _st.itemBg); @@ -306,10 +350,6 @@ void BotAction::paint(Painter &p) { paintRipple(p, 0, 0); } - if (!_icon.isNull()) { - p.drawImage(_st.itemIconPosition, _icon); - } - p.setPen(selected ? _st.itemFgOver : _st.itemFg); _text.drawLeftElided( p, @@ -388,6 +428,53 @@ void BotAction::handleKeyPress(not_null e) { } // namespace +MenuBotIcon::MenuBotIcon( + QWidget *parent, + std::shared_ptr media) +: RpWidget(parent) +, _media(std::move(media)) { + style::PaletteChanged( + ) | rpl::start_with_next([=] { + _image = QImage(); + update(); + }, lifetime()); + + setAttribute(Qt::WA_TransparentForMouseEvents); + resize(st::menuIconAdmin.size()); + show(); +} + +void MenuBotIcon::paintEvent(QPaintEvent *e) { + validate(); + if (!_image.isNull()) { + QPainter(this).drawImage(0, 0, _image); + } +} + +void MenuBotIcon::validate() { + const auto ratio = style::DevicePixelRatio(); + const auto wanted = size() * ratio; + if (_mask.size() != wanted) { + if (!_media || !_media->loaded()) { + return; + } + auto icon = QSvgRenderer(_media->bytes()); + _mask = QImage(wanted, QImage::Format_ARGB32_Premultiplied); + _mask.setDevicePixelRatio(style::DevicePixelRatio()); + _mask.fill(Qt::transparent); + if (icon.isValid()) { + auto p = QPainter(&_mask); + icon.render(&p, rect()); + p.end(); + + _mask = Images::Colored(std::move(_mask), Qt::white); + } + } + if (_image.isNull()) { + _image = style::colorizeImage(_mask, st::menuIconColor); + } +} + bool PeerMatchesTypes( not_null peer, not_null bot, @@ -425,11 +512,14 @@ struct AttachWebView::Context { Dialogs::EntryState dialogsEntryState; Api::SendAction action; bool fromSwitch = false; + bool fromMainMenu = false; bool fromBotApp = false; }; AttachWebView::AttachWebView(not_null session) -: _session(session) { +: _session(session) +, _refreshTimer([=] { requestBots(); }) { + _refreshTimer.callEach(kRefreshBotsTimeout); } AttachWebView::~AttachWebView() { @@ -462,6 +552,216 @@ void AttachWebView::request( resolve(); } +Webview::ThemeParams AttachWebView::botThemeParams() { + return Window::Theme::WebViewParams(); +} + +bool AttachWebView::botHandleLocalUri(QString uri) { + const auto local = Core::TryConvertUrlToLocal(uri); + if (uri == local || Core::InternalPassportLink(local)) { + return local.startsWith(u"tg://"_q); + } else if (!local.startsWith(u"tg://"_q, Qt::CaseInsensitive)) { + return false; + } + botClose(); + crl::on_main([=, shownUrl = _lastShownUrl] { + const auto variant = QVariant::fromValue(ClickHandlerContext{ + .attachBotWebviewUrl = shownUrl, + }); + UrlClickHandler::Open(local, variant); + }); + return true; +} + +void AttachWebView::botHandleInvoice(QString slug) { + Expects(_panel != nullptr); + + using Result = Payments::CheckoutResult; + const auto weak = base::make_weak(_panel.get()); + const auto reactivate = [=](Result result) { + if (const auto strong = weak.get()) { + strong->invoiceClosed(slug, [&] { + switch (result) { + case Result::Paid: return "paid"; + case Result::Failed: return "failed"; + case Result::Pending: return "pending"; + case Result::Cancelled: return "cancelled"; + } + Unexpected("Payments::CheckoutResult value."); + }()); + } + }; + _panel->hideForPayment(); + Payments::CheckoutProcess::Start(&_bot->session(), slug, reactivate); +} + +void AttachWebView::botHandleMenuButton(Ui::BotWebView::MenuButton button) { + Expects(_bot != nullptr); + Expects(_panel != nullptr); + + using Button = Ui::BotWebView::MenuButton; + const auto bot = _bot; + switch (button) { + case Button::OpenBot: + botClose(); + if (bot->session().windows().empty()) { + Core::App().domain().activate(&bot->session().account()); + } + if (!bot->session().windows().empty()) { + const auto window = bot->session().windows().front(); + window->showPeerHistory(bot); + window->window().activate(); + } + break; + case Button::RemoveFromMenu: + case Button::RemoveFromMainMenu: + const auto attached = ranges::find( + _attachBots, + not_null{ _bot }, + &AttachWebViewBot::user); + const auto name = (attached != end(_attachBots)) + ? attached->name + : _bot->name(); + const auto done = crl::guard(this, [=] { + removeFromMenu(bot); + botClose(); + if (const auto active = Core::App().activeWindow()) { + active->activate(); + } + }); + const auto main = (button == Button::RemoveFromMainMenu); + _panel->showBox(Ui::MakeConfirmBox({ + (main + ? tr::lng_bot_remove_from_side_menu_sure + : tr::lng_bot_remove_from_menu_sure)( + tr::now, + lt_bot, + Ui::Text::Bold(name), + Ui::Text::WithEntities), + done, + })); + break; + } +} + +void AttachWebView::botSendData(QByteArray data) { + if (!_context + || _context->fromSwitch + || _context->fromBotApp + || _context->fromMainMenu + || _context->action.history->peer != _bot + || _lastShownQueryId) { + return; + } + const auto randomId = base::RandomValue(); + _session->api().request(MTPmessages_SendWebViewData( + _bot->inputUser, + MTP_long(randomId), + MTP_string(_lastShownButtonText), + MTP_bytes(data) + )).done([=](const MTPUpdates &result) { + _session->api().applyUpdates(result); + }).send(); + crl::on_main(this, [=] { cancel(); }); +} + +void AttachWebView::botSwitchInlineQuery( + std::vector chatTypes, + QString query) { + const auto controller = _context + ? _context->controller.get() + : nullptr; + const auto types = PeerTypesFromNames(chatTypes); + if (!_bot + || !_bot->isBot() + || _bot->botInfo->inlinePlaceholder.isEmpty() + || !controller) { + return; + } else if (!types) { + if (_context->dialogsEntryState.key.owningHistory()) { + controller->switchInlineQuery( + _context->dialogsEntryState, + _bot, + query); + } + } else { + const auto bot = _bot; + const auto done = [=](not_null thread) { + controller->switchInlineQuery(thread, bot, query); + }; + ShowChooseBox( + controller, + types, + done, + tr::lng_inline_switch_choose()); + } + crl::on_main(this, [=] { cancel(); }); +} + +void AttachWebView::botCheckWriteAccess(Fn callback) { + _session->api().request(MTPbots_CanSendMessage( + _bot->inputUser + )).done([=](const MTPBool &result) { + callback(mtpIsTrue(result)); + }).fail([=] { + callback(false); + }).send(); +} + +void AttachWebView::botAllowWriteAccess(Fn callback) { + _session->api().request(MTPbots_AllowSendMessage( + _bot->inputUser + )).done([=](const MTPUpdates &result) { + _session->api().applyUpdates(result); + callback(true); + }).fail([=] { + callback(false); + }).send(); +} + +void AttachWebView::botSharePhone(Fn callback) { + const auto bot = _bot; + const auto history = _bot->owner().history(_bot); + if (_bot->isBlocked()) { + const auto done = [=](bool success) { + if (success && _bot == bot) { + Assert(!_bot->isBlocked()); + botSharePhone(callback); + } else { + callback(false); + } + }; + _bot->session().api().blockedPeers().unblock( + _bot, + crl::guard(this, done)); + return; + } + auto action = Api::SendAction(history); + action.clearDraft = false; + history->session().api().shareContact( + _bot->session().user(), + action, + std::move(callback)); +} + +void AttachWebView::botInvokeCustomMethod( + Ui::BotWebView::CustomMethodRequest request) { + const auto callback = request.callback; + _bot->session().api().request(MTPbots_InvokeWebViewCustomMethod( + _bot->inputUser, + MTP_string(request.method), + MTP_dataJSON(MTP_bytes(request.params)) + )).done([=](const MTPDataJSON &result) { + callback(result.data().vdata().v); + }).fail([=](const MTP::Error &error) { + callback(base::make_unexpected(error.type())); + }).send(); +} + +void AttachWebView::botClose() { + crl::on_main(this, [=] { cancel(); }); +} + AttachWebView::Context AttachWebView::LookupContext( not_null controller, const Api::SendAction &action) { @@ -480,6 +780,7 @@ bool AttachWebView::IsSame( && (a->controller == b.controller) && (a->dialogsEntryState == b.dialogsEntryState) && (a->fromSwitch == b.fromSwitch) + && (a->fromMainMenu == b.fromMainMenu) && (a->action.history == b.action.history) && (a->action.replyTo == b.action.replyTo) && (a->action.options.sendAs == b.action.options.sendAs) @@ -495,7 +796,7 @@ void AttachWebView::request( bot, button, LookupContext(controller, action), - button.fromMenu ? nullptr : controller.get()); + button.fromAttachMenu ? nullptr : controller.get()); } void AttachWebView::requestWithOptionalConfirm( @@ -555,7 +856,7 @@ void AttachWebView::request(const WebViewButton &button) { data.vquery_id().v, qs(data.vurl()), button.text, - button.fromMenu || button.url.isEmpty()); + button.fromAttachMenu || button.url.isEmpty()); }).fail([=](const MTP::Error &error) { _requestId = 0; if (error.type() == u"BOT_INVALID"_q) { @@ -568,7 +869,7 @@ void AttachWebView::cancel() { ActiveWebViews().remove(this); _session->api().request(base::take(_requestId)).cancel(); _session->api().request(base::take(_prolongId)).cancel(); - _panel = nullptr; + base::take(_panel); _lastShownContext = base::take(_context); _bot = nullptr; _app = nullptr; @@ -577,7 +878,10 @@ void AttachWebView::cancel() { _startCommand = QString(); } -void AttachWebView::requestBots() { +void AttachWebView::requestBots(Fn callback) { + if (callback) { + _botsRequestCallbacks.push_back(std::move(callback)); + } if (_botsRequestId) { return; } @@ -593,50 +897,79 @@ void AttachWebView::requestBots() { _attachBots.reserve(data.vbots().v.size()); for (const auto &bot : data.vbots().v) { if (auto parsed = ParseAttachBot(_session, bot)) { - if (!parsed->inactive) { - if (const auto icon = parsed->icon) { - parsed->media = icon->createMediaView(); - icon->save(Data::FileOrigin(), {}); - } - _attachBots.push_back(std::move(*parsed)); - } + _attachBots.push_back(std::move(*parsed)); } } _attachBotsUpdates.fire({}); }); + for (const auto &callback : base::take(_botsRequestCallbacks)) { + callback(); + } }).fail([=] { _botsRequestId = 0; + for (const auto &callback : base::take(_botsRequestCallbacks)) { + callback(); + } }).send(); } -void AttachWebView::requestAddToMenu( - not_null bot, - const QString &startCommand) { - requestAddToMenu(bot, startCommand, nullptr, std::nullopt, PeerTypes()); +bool AttachWebView::disclaimerAccepted(const AttachWebViewBot &bot) const { + return _disclaimerAccepted.contains(bot.user); +} + +bool AttachWebView::showMainMenuNewBadge( + const AttachWebViewBot &bot) const { + return bot.inMainMenu + && bot.disclaimerRequired + && !disclaimerAccepted(bot); } void AttachWebView::requestAddToMenu( not_null bot, - const QString &startCommand, + AddToMenuOpen open) { + requestAddToMenu(bot, open, nullptr, std::nullopt); +} + +void AttachWebView::requestAddToMenu( + not_null bot, + AddToMenuOpen open, Window::SessionController *controller, - std::optional action, - PeerTypes chooseTypes) { + std::optional action) { Expects(controller != nullptr || _context != nullptr); - if (!bot->isBot() || !bot->botInfo->supportsAttachMenu) { - showToast(tr::lng_bot_menu_not_supported(tr::now), controller); - return; - } const auto wasController = (controller != nullptr); _addToMenuChooseController = base::make_weak(controller); - _addToMenuStartCommand = startCommand; - _addToMenuChooseTypes = chooseTypes; + _addToMenuOpen = open; if (!controller) { _addToMenuContext = base::take(_context); } else if (action) { _addToMenuContext = std::make_unique( LookupContext(controller, *action)); } + + const auto unsupported = [=] { + auto context = base::take(_addToMenuContext); + const auto open = base::take(_addToMenuOpen); + if (const auto openApp = std::get_if(&open)) { + _app = openApp->app; + _startCommand = openApp->startCommand; + _context = std::move(context); + if (_appConfirmationRequired) { + confirmAppOpen(_appRequestWriteAccess); + } else { + requestAppView(false); + } + } else { + showToast( + tr::lng_bot_menu_not_supported(tr::now), + _addToMenuChooseController.get()); + } + }; + if (!bot->isBot() || !bot->botInfo->supportsAttachMenu) { + unsupported(); + return; + } + if (_addToMenuId) { if (_addToMenuBot == bot) { return; @@ -650,22 +983,43 @@ void AttachWebView::requestAddToMenu( _addToMenuId = 0; const auto bot = base::take(_addToMenuBot); const auto context = std::shared_ptr(base::take(_addToMenuContext)); - const auto chooseTypes = base::take(_addToMenuChooseTypes); - const auto startCommand = base::take(_addToMenuStartCommand); + const auto open = base::take(_addToMenuOpen); const auto chooseController = base::take(_addToMenuChooseController); - const auto open = [=](PeerTypes types) { + const auto launch = [=](PeerTypes types) { + const auto openAttach = v::is(open) + ? v::get(open) + : AddToMenuOpenAttach(); + const auto chooseTypes = openAttach.chooseTypes; const auto strong = chooseController.get(); - if (!strong) { - if (wasController) { + if (v::is(open)) { + if (!context) { + return false; + } + const auto &openApp = v::get(open); + _app = openApp.app; + _startCommand = openApp.startCommand; + _context = std::make_unique(*context); + requestAppView(true); + return true; + } else if (!strong) { + if (wasController || !v::is(open)) { // Just ignore the click if controller was destroyed. return true; } + } else if (v::is(open)) { + const auto &openMenu = v::get(open); + _bot = bot; + requestSimple(strong, bot, { + .startCommand = openMenu.startCommand, + .fromMainMenu = true, + }); + return true; } else if (const auto useTypes = chooseTypes & types) { const auto done = [=](not_null thread) { strong->showThread(thread); requestWithOptionalConfirm( bot, - { .startCommand = startCommand }, + { .startCommand = openAttach.startCommand }, LookupContext(strong, Api::SendAction(thread))); }; ShowChooseBox(strong, useTypes, done); @@ -676,7 +1030,7 @@ void AttachWebView::requestAddToMenu( } requestWithOptionalConfirm( bot, - { .startCommand = startCommand }, + { .startCommand = openAttach.startCommand }, *context); return true; }; @@ -684,14 +1038,22 @@ void AttachWebView::requestAddToMenu( _session->data().processUsers(data.vusers()); if (const auto parsed = ParseAttachBot(_session, data.vbot())) { if (bot == parsed->user) { + const auto i = ranges::find( + _attachBots, + not_null(bot), + &AttachWebViewBot::user); + if (i != end(_attachBots)) { + // Save flags in our list, like 'inactive'. + *i = *parsed; + } const auto types = parsed->types; if (parsed->inactive) { confirmAddToMenu(*parsed, [=] { - open(types); + launch(types); }); } else { requestBots(); - if (!open(types)) { + if (!launch(types)) { showToast( tr::lng_bot_menu_already_added(tr::now)); } @@ -702,9 +1064,7 @@ void AttachWebView::requestAddToMenu( }).fail([=] { _addToMenuId = 0; _addToMenuBot = nullptr; - _addToMenuContext = nullptr; - _addToMenuStartCommand = QString(); - showToast(tr::lng_bot_menu_not_supported(tr::now)); + unsupported(); }).send(); } @@ -723,6 +1083,8 @@ std::optional AttachWebView::lookupLastAction( } void AttachWebView::resolve() { + Expects(!_panel); + resolveUsername(_botUsername, [=](not_null bot) { if (!_context) { return; @@ -732,7 +1094,9 @@ void AttachWebView::resolve() { showToast(tr::lng_bot_menu_not_supported(tr::now)); return; } - requestAddToMenu(_bot, _startCommand); + requestAddToMenu(_bot, AddToMenuOpenAttach{ + .startCommand = _startCommand, + }); }); } @@ -774,25 +1138,42 @@ void AttachWebView::requestSimple( controller, Api::SendAction(bot->owner().history(bot)))); _context->fromSwitch = button.fromSwitch; - confirmOpen(controller, [=] { - requestSimple(button); - }); + _context->fromMainMenu = button.fromMainMenu; + if (button.fromMainMenu) { + acceptMainMenuDisclaimer(controller, button); + } else { + confirmOpen(controller, [=] { + requestSimple(button); + }); + } } void AttachWebView::requestSimple(const WebViewButton &button) { using Flag = MTPmessages_RequestSimpleWebView::Flag; _requestId = _session->api().request(MTPmessages_RequestSimpleWebView( MTP_flags(Flag::f_theme_params + | (button.fromMainMenu + ? (Flag::f_from_side_menu + | (button.startCommand.isEmpty() + ? Flag() + : Flag::f_start_param)) + : Flag::f_url) | (button.fromSwitch ? Flag::f_from_switch_webview : Flag())), _bot->inputUser, MTP_bytes(button.url), + MTP_string(button.startCommand), MTP_dataJSON(MTP_bytes(Window::Theme::WebViewParams().json)), MTP_string("tdesktop") )).done([=](const MTPSimpleWebViewResult &result) { _requestId = 0; result.match([&](const MTPDsimpleWebViewResultUrl &data) { - const auto queryId = uint64(); - show(queryId, qs(data.vurl()), button.text); + show( + uint64(), + qs(data.vurl()), + button.text, + false, + nullptr, + button.fromMainMenu); }); }).fail([=](const MTP::Error &error) { _requestId = 0; @@ -880,20 +1261,23 @@ void AttachWebView::requestApp( _bot->id, data.vapp()); _app = received ? received : already; + _app->hasSettings = data.is_has_settings(); if (!_app) { cancel(); showToast(tr::lng_username_app_not_found(tr::now)); return; } - const auto confirm = firstTime || forceConfirmation; - if (confirm) { - confirmAppOpen(result.data().is_request_write_access()); - } else { - requestAppView(false); - } + // Check if this app can be added to main menu. + // On fail it'll still be opened. + _appConfirmationRequired = firstTime || forceConfirmation; + _appRequestWriteAccess = result.data().is_request_write_access(); + requestAddToMenu(_bot, AddToMenuOpenApp{ + .app = _app, + .startCommand = _startCommand, + }); }).fail([=] { - cancel(); showToast(tr::lng_username_app_not_found(tr::now)); + cancel(); }).send(); } @@ -942,13 +1326,14 @@ void AttachWebView::requestAppView(bool allowWrite) { return; } using Flag = MTPmessages_RequestAppWebView::Flag; + const auto app = _app; const auto flags = Flag::f_theme_params | (_startCommand.isEmpty() ? Flag(0) : Flag::f_start_param) | (allowWrite ? Flag::f_write_allowed : Flag(0)); _requestId = _session->api().request(MTPmessages_RequestAppWebView( MTP_flags(flags), _context->action.history->peer->input, - MTP_inputBotAppID(MTP_long(_app->id), MTP_long(_app->accessHash)), + MTP_inputBotAppID(MTP_long(app->id), MTP_long(app->accessHash)), MTP_string(_startCommand), MTP_dataJSON(MTP_bytes(Window::Theme::WebViewParams().json)), MTP_string("tdesktop") @@ -956,7 +1341,7 @@ void AttachWebView::requestAppView(bool allowWrite) { _requestId = 0; const auto &data = result.data(); const auto queryId = uint64(); - show(queryId, qs(data.vurl())); + show(queryId, qs(data.vurl()), QString(), false, app); }).fail([=](const MTP::Error &error) { _requestId = 0; if (error.type() == u"BOT_INVALID"_q) { @@ -991,6 +1376,40 @@ void AttachWebView::confirmOpen( })); } +void AttachWebView::acceptMainMenuDisclaimer( + not_null controller, + const WebViewButton &button) { + Expects(button.fromMainMenu); + + const auto local = _bot ? &_bot->session().local() : nullptr; + if (!local) { + return; + } + const auto i = ranges::find( + _attachBots, + not_null(_bot), + &AttachWebViewBot::user); + if (i == end(_attachBots)) { + _attachBotsUpdates.fire({}); + return; + } else if (i->inactive) { + requestAddToMenu(_bot, AddToMenuOpenMenu{ + .startCommand = button.startCommand, + }, controller, {}); + return; + } else if (!i->disclaimerRequired || disclaimerAccepted(*i)) { + requestSimple(button); + return; + } + + const auto weak = base::make_weak(this); + controller->show(Box(FillDisclaimerBox, crl::guard(this, [=] { + _disclaimerAccepted.emplace(_bot); + _attachBotsUpdates.fire({}); + requestSimple(button); + }))); +} + void AttachWebView::ClearAll() { while (!ActiveWebViews().empty()) { ActiveWebViews().front()->cancel(); @@ -1001,101 +1420,11 @@ void AttachWebView::show( uint64 queryId, const QString &url, const QString &buttonText, - bool allowClipboardRead) { + bool allowClipboardRead, + const BotAppData *app, + bool fromMainMenu) { Expects(_bot != nullptr && _context != nullptr); - const auto close = crl::guard(this, [=] { - crl::on_main(this, [=] { cancel(); }); - }); - const auto sendData = crl::guard(this, [=](QByteArray data) { - if (!_context - || _context->fromSwitch - || _context->fromBotApp - || _context->action.history->peer != _bot - || queryId) { - return; - } - const auto randomId = base::RandomValue(); - _session->api().request(MTPmessages_SendWebViewData( - _bot->inputUser, - MTP_long(randomId), - MTP_string(buttonText), - MTP_bytes(data) - )).done([=](const MTPUpdates &result) { - _session->api().applyUpdates(result); - }).send(); - crl::on_main(this, [=] { cancel(); }); - }); - const auto switchInlineQuery = crl::guard(this, [=]( - std::vector typeNames, - QString query) { - const auto controller = _context - ? _context->controller.get() - : nullptr; - const auto types = PeerTypesFromNames(typeNames); - if (!_bot - || !_bot->isBot() - || _bot->botInfo->inlinePlaceholder.isEmpty() - || !controller) { - return; - } else if (!types) { - if (_context->dialogsEntryState.key.owningHistory()) { - controller->switchInlineQuery( - _context->dialogsEntryState, - _bot, - query); - } - } else { - const auto bot = _bot; - const auto done = [=](not_null thread) { - controller->switchInlineQuery(thread, bot, query); - }; - ShowChooseBox( - controller, - types, - done, - tr::lng_inline_switch_choose()); - } - crl::on_main(this, [=] { cancel(); }); - }); - const auto handleLocalUri = [close, url](QString uri) { - const auto local = Core::TryConvertUrlToLocal(uri); - if (uri == local || Core::InternalPassportLink(local)) { - return local.startsWith(u"tg://"_q); - } else if (!local.startsWith(u"tg://"_q, Qt::CaseInsensitive)) { - return false; - } - close(); - crl::on_main([=] { - const auto variant = QVariant::fromValue(ClickHandlerContext{ - .attachBotWebviewUrl = url, - }); - UrlClickHandler::Open(local, variant); - }); - return true; - }; - const auto panel = std::make_shared< - base::weak_ptr>(nullptr); - const auto handleInvoice = [=, session = _session](QString slug) { - using Result = Payments::CheckoutResult; - const auto reactivate = [=](Result result) { - if (const auto strong = panel->get()) { - strong->invoiceClosed(slug, [&] { - switch (result) { - case Result::Paid: return "paid"; - case Result::Failed: return "failed"; - case Result::Pending: return "pending"; - case Result::Cancelled: return "cancelled"; - } - Unexpected("Payments::CheckoutResult value."); - }()); - } - }; - if (const auto strong = panel->get()) { - strong->hideForPayment(); - } - Payments::CheckoutProcess::Start(session, slug, reactivate); - }; auto title = Info::Profile::NameValue(_bot); ActiveWebViews().emplace(this); @@ -1104,74 +1433,38 @@ void AttachWebView::show( _attachBots, not_null{ _bot }, &AttachWebViewBot::user); - const auto name = (attached != end(_attachBots)) - ? attached->name - : _bot->name(); - const auto hasSettings = (attached != end(_attachBots)) - && !attached->inactive - && attached->hasSettings; + const auto hasSettings = app + ? app->hasSettings + : ((attached != end(_attachBots)) + && !attached->inactive + && attached->hasSettings); const auto hasOpenBot = !_context - || (_bot != _context->action.history->peer); - const auto hasRemoveFromMenu = (attached != end(_attachBots)) - && !attached->inactive; + || (_bot != _context->action.history->peer) + || fromMainMenu; + const auto hasRemoveFromMenu = !app + && (attached != end(_attachBots)) + && (!attached->inactive || attached->inMainMenu); const auto buttons = (hasSettings ? Button::Settings : Button::None) | (hasOpenBot ? Button::OpenBot : Button::None) - | (hasRemoveFromMenu ? Button::RemoveFromMenu : Button::None); - const auto bot = _bot; - - const auto handleMenuButton = crl::guard(this, [=](Button button) { - switch (button) { - case Button::OpenBot: - close(); - if (bot->session().windows().empty()) { - Core::App().domain().activate(&bot->session().account()); - } - if (!bot->session().windows().empty()) { - const auto window = bot->session().windows().front(); - window->showPeerHistory(bot); - window->window().activate(); - } - break; - case Button::RemoveFromMenu: - if (const auto strong = panel->get()) { - const auto done = crl::guard(this, [=] { - removeFromMenu(bot); - close(); - if (const auto active = Core::App().activeWindow()) { - active->activate(); - } - }); - strong->showBox(Ui::MakeConfirmBox({ - tr::lng_bot_remove_from_menu_sure( - tr::now, - lt_bot, - Ui::Text::Bold(name), - Ui::Text::WithEntities), - done, - })); - } - break; - } - }); + | (!hasRemoveFromMenu + ? Button::None + : attached->inMainMenu + ? Button::RemoveFromMainMenu + : Button::RemoveFromMenu); _lastShownUrl = url; + _lastShownQueryId = queryId; + _lastShownButtonText = buttonText; + base::take(_panel); _panel = Ui::BotWebView::Show({ .url = url, .userDataPath = _session->domain().local().webviewDataPath(), .title = std::move(title), .bottom = rpl::single('@' + _bot->username()), - .handleLocalUri = handleLocalUri, - .handleInvoice = handleInvoice, - .sendData = sendData, - .switchInlineQuery = switchInlineQuery, - .close = close, - .phone = _session->user()->phone(), + .delegate = static_cast(this), .menuButtons = buttons, - .handleMenuButton = handleMenuButton, - .themeParams = [] { return Window::Theme::WebViewParams(); }, .allowClipboardRead = allowClipboardRead, }); - *panel = _panel.get(); started(queryId); } @@ -1245,18 +1538,31 @@ void AttachWebView::confirmAddToMenu( if (callback) { callback(); } - showToast(tr::lng_bot_add_to_menu_done(tr::now)); + showToast((bot.inMainMenu + ? tr::lng_bot_add_to_side_menu_done + : tr::lng_bot_add_to_menu_done)(tr::now)); }); close(); }; - Ui::ConfirmBox(box, { - tr::lng_bot_add_to_menu( - tr::now, - lt_bot, - Ui::Text::Bold(bot.name), - Ui::Text::WithEntities), - done, - }); + const auto disclaimer = !disclaimerAccepted(bot); + if (disclaimer) { + FillDisclaimerBox(box, [=] { + _disclaimerAccepted.emplace(bot.user); + _attachBotsUpdates.fire({}); + done([] {}); + }); + } else { + Ui::ConfirmBox(box, { + (bot.inMainMenu + ? tr::lng_bot_add_to_side_menu + : tr::lng_bot_add_to_menu)( + tr::now, + lt_bot, + Ui::Text::Bold(bot.name), + Ui::Text::WithEntities), + done, + }); + } if (bot.requestWriteAccess) { (*allowed) = box->addRow( object_ptr( @@ -1270,11 +1576,27 @@ void AttachWebView::confirmAddToMenu( st::urlAuthCheckbox), style::margins( st::boxRowPadding.left(), - st::boxPhotoCaptionSkip, + (disclaimer + ? st::boxPhotoCaptionSkip + : st::boxRowPadding.left()), st::boxRowPadding.right(), - st::boxPhotoCaptionSkip)); + st::boxRowPadding.left())); (*allowed)->setAllowTextLines(); } + if (disclaimer) { + if (!bot.requestWriteAccess) { + box->addRow(object_ptr( + box, + st::boxRowPadding.left())); + } + box->addRow(object_ptr( + box, + tr::lng_bot_will_be_added( + lt_bot, + rpl::single(Ui::Text::Bold(bot.name)), + Ui::Text::WithEntities), + st::boxLabel)); + } })); } @@ -1291,10 +1613,8 @@ void AttachWebView::toggleInMenu( MTP_bool(state != ToggledState::Removed) )).done([=] { _requestId = 0; - requestBots(); - if (callback) { - callback(); - } + _session->api().request(base::take(_botsRequestId)).cancel(); + requestBots(std::move(callback)); }).fail([=] { cancel(); }).send(); @@ -1333,7 +1653,8 @@ std::unique_ptr MakeAttachBotsMenu( }, &st::menuIconFile); } for (const auto &bot : bots->attachBots()) { - if (!PeerMatchesTypes(peer, bot.user, bot.types)) { + if (!bot.inAttachMenu + || !PeerMatchesTypes(peer, bot.user, bot.types)) { continue; } const auto callback = [=] { @@ -1341,7 +1662,7 @@ std::unique_ptr MakeAttachBotsMenu( controller, actionFactory(), bot.user, - { .fromMenu = true }); + { .fromAttachMenu = true }); }; auto action = base::make_unique_q( raw, diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h index 0cbd71ab4..04fbd06f4 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h @@ -7,9 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "mtproto/sender.h" -#include "base/weak_ptr.h" #include "base/flags.h" +#include "base/timer.h" +#include "base/weak_ptr.h" +#include "mtproto/sender.h" +#include "ui/chat/attach/attach_bot_webview.h" +#include "ui/rp_widget.h" namespace Api { struct SendAction; @@ -59,12 +62,33 @@ struct AttachWebViewBot { std::shared_ptr media; QString name; PeerTypes types = 0; - bool inactive = false; - bool hasSettings = false; - bool requestWriteAccess = false; + bool inactive : 1 = false; + bool inMainMenu : 1 = false; + bool inAttachMenu : 1 = false; + bool disclaimerRequired : 1 = false; + bool hasSettings : 1 = false; + bool requestWriteAccess : 1 = false; }; -class AttachWebView final : public base::has_weak_ptr { +struct AddToMenuOpenAttach { + QString startCommand; + PeerTypes chooseTypes; +}; +struct AddToMenuOpenMenu { + QString startCommand; +}; +struct AddToMenuOpenApp { + not_null app; + QString startCommand; +}; +using AddToMenuOpen = std::variant< + AddToMenuOpenAttach, + AddToMenuOpenMenu, + AddToMenuOpenApp>; + +class AttachWebView final + : public base::has_weak_ptr + , public Ui::BotWebView::Delegate { public: explicit AttachWebView(not_null session); ~AttachWebView(); @@ -73,7 +97,8 @@ public: QString text; QString startCommand; QByteArray url; - bool fromMenu = false; + bool fromAttachMenu = false; + bool fromMainMenu = false; bool fromSwitch = false; }; void request( @@ -103,23 +128,26 @@ public: void cancel(); - void requestBots(); + void requestBots(Fn callback = nullptr); [[nodiscard]] const std::vector &attachBots() const { return _attachBots; } [[nodiscard]] rpl::producer<> attachBotsUpdates() const { return _attachBotsUpdates.events(); } + [[nodiscard]] bool disclaimerAccepted( + const AttachWebViewBot &bot) const; + [[nodiscard]] bool showMainMenuNewBadge( + const AttachWebViewBot &bot) const; void requestAddToMenu( not_null bot, - const QString &startCommand); + AddToMenuOpen open); void requestAddToMenu( not_null bot, - const QString &startCommand, + AddToMenuOpen open, Window::SessionController *controller, - std::optional action, - PeerTypes chooseTypes); + std::optional action); void removeFromMenu(not_null bot); [[nodiscard]] std::optional lookupLastAction( @@ -130,6 +158,22 @@ public: private: struct Context; + + Webview::ThemeParams botThemeParams() override; + bool botHandleLocalUri(QString uri) override; + void botHandleInvoice(QString slug) override; + void botHandleMenuButton(Ui::BotWebView::MenuButton button) override; + void botSendData(QByteArray data) override; + void botSwitchInlineQuery( + std::vector chatTypes, + QString query) override; + void botCheckWriteAccess(Fn callback) override; + void botAllowWriteAccess(Fn callback) override; + void botSharePhone(Fn callback) override; + void botInvokeCustomMethod( + Ui::BotWebView::CustomMethodRequest request) override; + void botClose() override; + [[nodiscard]] static Context LookupContext( not_null controller, const Api::SendAction &action); @@ -153,6 +197,9 @@ private: void confirmOpen( not_null controller, Fn done); + void acceptMainMenuDisclaimer( + not_null controller, + const WebViewButton &button); enum class ToggledState { Removed, @@ -168,7 +215,9 @@ private: uint64 queryId, const QString &url, const QString &buttonText = QString(), - bool allowClipboardRead = false); + bool allowClipboardRead = false, + const BotAppData *app = nullptr, + bool fromMainMenu = false); void confirmAddToMenu( AttachWebViewBot bot, Fn callback = nullptr); @@ -182,31 +231,38 @@ private: const not_null _session; + base::Timer _refreshTimer; + std::unique_ptr _context; std::unique_ptr _lastShownContext; QString _lastShownUrl; + uint64 _lastShownQueryId = 0; + QString _lastShownButtonText; UserData *_bot = nullptr; QString _botUsername; QString _botAppName; QString _startCommand; BotAppData *_app = nullptr; QPointer _confirmAddBox; + bool _appConfirmationRequired = false; + bool _appRequestWriteAccess = false; mtpRequestId _requestId = 0; mtpRequestId _prolongId = 0; uint64 _botsHash = 0; mtpRequestId _botsRequestId = 0; + std::vector> _botsRequestCallbacks; std::unique_ptr _addToMenuContext; UserData *_addToMenuBot = nullptr; mtpRequestId _addToMenuId = 0; - QString _addToMenuStartCommand; + AddToMenuOpen _addToMenuOpen; base::weak_ptr _addToMenuChooseController; - PeerTypes _addToMenuChooseTypes; std::vector _attachBots; rpl::event_stream<> _attachBotsUpdates; + base::flat_set> _disclaimerAccepted; std::unique_ptr _panel; @@ -219,4 +275,21 @@ private: Fn actionFactory, Fn attach); +class MenuBotIcon final : public Ui::RpWidget { +public: + MenuBotIcon( + QWidget *parent, + std::shared_ptr media); + +private: + void paintEvent(QPaintEvent *e) override; + + void validate(); + + std::shared_ptr _media; + QImage _image; + QImage _mask; + +}; + } // namespace InlineBots diff --git a/Telegram/SourceFiles/intro/intro_code.cpp b/Telegram/SourceFiles/intro/intro_code.cpp index f1e165283..bdd63a7ba 100644 --- a/Telegram/SourceFiles/intro/intro_code.cpp +++ b/Telegram/SourceFiles/intro/intro_code.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/update_checker.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" +#include "ui/widgets/fields/masked_input_field.h" #include "ui/text/format_values.h" // Ui::FormatPhone #include "ui/text/text_utilities.h" #include "ui/boxes/confirm_box.h" diff --git a/Telegram/SourceFiles/intro/intro_code.h b/Telegram/SourceFiles/intro/intro_code.h index 8e0fc5743..65f39117b 100644 --- a/Telegram/SourceFiles/intro/intro_code.h +++ b/Telegram/SourceFiles/intro/intro_code.h @@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "intro/intro_step.h" #include "intro/intro_widget.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/masked_input_field.h" #include "base/timer.h" namespace Ui { diff --git a/Telegram/SourceFiles/intro/intro_password_check.cpp b/Telegram/SourceFiles/intro/intro_password_check.cpp index 935f55d01..d2bff066e 100644 --- a/Telegram/SourceFiles/intro/intro_password_check.cpp +++ b/Telegram/SourceFiles/intro/intro_password_check.cpp @@ -8,15 +8,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "intro/intro_password_check.h" #include "intro/intro_widget.h" -#include "core/file_utilities.h" #include "core/core_cloud_password.h" #include "ui/boxes/confirm_box.h" #include "boxes/passcode_box.h" #include "lang/lang_keys.h" #include "intro/intro_signup.h" #include "ui/widgets/buttons.h" -#include "ui/widgets/input_fields.h" -#include "ui/widgets/labels.h" +#include "ui/widgets/fields/input_field.h" +#include "ui/widgets/fields/password_input.h" #include "main/main_account.h" #include "base/random.h" #include "styles/style_intro.h" @@ -46,7 +45,10 @@ PasswordCheckWidget::PasswordCheckWidget( _toRecover->addClickHandler([=] { toRecover(); }); _toPassword->addClickHandler([=] { toPassword(); }); connect(_pwdField, &Ui::PasswordInput::changed, [=] { hideError(); }); - connect(_codeField, &Ui::InputField::changed, [=] { hideError(); }); + _codeField->changes( + ) | rpl::start_with_next([=] { + hideError(); + }, _codeField->lifetime()); setTitleText(tr::lng_signin_title()); updateDescriptionText(); diff --git a/Telegram/SourceFiles/intro/intro_signup.cpp b/Telegram/SourceFiles/intro/intro_signup.cpp index b11db8672..0f2f38284 100644 --- a/Telegram/SourceFiles/intro/intro_signup.cpp +++ b/Telegram/SourceFiles/intro/intro_signup.cpp @@ -13,7 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "ui/controls/userpic_button.h" #include "ui/widgets/buttons.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/labels.h" #include "styles/style_intro.h" #include "styles/style_boxes.h" diff --git a/Telegram/SourceFiles/lang/lang_tag.cpp b/Telegram/SourceFiles/lang/lang_tag.cpp index f11c00a0c..2b0f30544 100644 --- a/Telegram/SourceFiles/lang/lang_tag.cpp +++ b/Telegram/SourceFiles/lang/lang_tag.cpp @@ -937,6 +937,10 @@ ShortenedCount FormatCountToShort(int64 number) { return result; } +QString FormatCountDecimal(int64 number) { + return QString("%L1").arg(number); +} + PluralResult Plural( ushort keyBase, float64 value, @@ -973,7 +977,7 @@ PluralResult Plural( if (type == lt_count_short) { return { shift, shortened.string }; } else if (type == lt_count_decimal) { - return { shift, QString("%L1").arg(round) }; + return { shift, FormatCountDecimal(round) }; } return { shift, QString::number(round) }; } diff --git a/Telegram/SourceFiles/lang/lang_tag.h b/Telegram/SourceFiles/lang/lang_tag.h index 03368b73b..2efa0882c 100644 --- a/Telegram/SourceFiles/lang/lang_tag.h +++ b/Telegram/SourceFiles/lang/lang_tag.h @@ -14,13 +14,16 @@ namespace Lang { inline constexpr auto kTextCommandLangTag = 0x20; constexpr auto kTagReplacementSize = 4; -int FindTagReplacementPosition(const QString &original, ushort tag); +[[nodiscard]] int FindTagReplacementPosition( + const QString &original, + ushort tag); struct ShortenedCount { int64 number = 0; QString string; }; -ShortenedCount FormatCountToShort(int64 number); +[[nodiscard]] ShortenedCount FormatCountToShort(int64 number); +[[nodiscard]] QString FormatCountDecimal(int64 number); struct PluralResult { int keyShift = 0; diff --git a/Telegram/SourceFiles/layout/layout_mosaic.h b/Telegram/SourceFiles/layout/layout_mosaic.h index b8730f84c..8695d9dc1 100644 --- a/Telegram/SourceFiles/layout/layout_mosaic.h +++ b/Telegram/SourceFiles/layout/layout_mosaic.h @@ -10,8 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "layout/abstract_layout_item.h" #include "layout/layout_position.h" -#include "styles/style_chat_helpers.h" - namespace Mosaic::Layout { struct FoundItem { diff --git a/Telegram/SourceFiles/logs.cpp b/Telegram/SourceFiles/logs.cpp index b8bd88d6c..1838471bc 100644 --- a/Telegram/SourceFiles/logs.cpp +++ b/Telegram/SourceFiles/logs.cpp @@ -280,6 +280,9 @@ namespace { bool DebugModeEnabled = false; void MoveOldDataFiles(const QString &wasDir) { + if (wasDir.isEmpty()) { + return; + } QFile data(wasDir + "data"), dataConfig(wasDir + "data_config"), tdataConfig(wasDir + "tdata/config"); if (data.exists() && dataConfig.exists() && !QFileInfo::exists(cWorkingDir() + "data") && !QFileInfo::exists(cWorkingDir() + "data_config")) { // move to home dir LOG(("Copying data to home dir '%1' from '%2'").arg(cWorkingDir(), wasDir)); @@ -351,59 +354,16 @@ void start() { return; } - auto initialWorkingDir = QDir(cWorkingDir()).absolutePath() + '/'; - auto moveOldDataFrom = QString(); - auto workingDirChosen = false; - - if (cAlphaVersion()) { - workingDirChosen = true; - } else { - -#ifdef Q_OS_UNIX - - if (!cWorkingDir().isEmpty()) { - // This value must come from TelegramForcePortable - cForceWorkingDir(cWorkingDir()); - workingDirChosen = true; - } else { -#if !defined _DEBUG || defined OS_MAC_STORE - cForceWorkingDir(psAppDataPath()); - workingDirChosen = true; -#endif // !_DEBUG || OS_MAC_STORE - } - -#if !defined Q_OS_MAC && !defined _DEBUG // fix first version - moveOldDataFrom = initialWorkingDir; -#endif // !Q_OS_MAC && !_DEBUG - -#elif defined Q_OS_WINRT // Q_OS_UNIX - - cForceWorkingDir(psAppDataPath()); - workingDirChosen = true; - -#elif defined OS_WIN_STORE // Q_OS_UNIX || Q_OS_WINRT - - cForceWorkingDir(psAppDataPath()); - workingDirChosen = true; - -#elif defined Q_OS_WIN - - if (!cWorkingDir().isEmpty()) { - // This value must come from TelegramForcePortable - cForceWorkingDir(cWorkingDir()); - workingDirChosen = true; - } - -#endif // Q_OS_UNIX || Q_OS_WINRT || OS_WIN_STORE - - } - LogsData = new LogsDataFields(); - if (!workingDirChosen) { + if (cWorkingDir().isEmpty()) { +#if (!defined Q_OS_WIN && !defined _DEBUG) || defined Q_OS_WINRT || defined OS_WIN_STORE || defined OS_MAC_STORE + cForceWorkingDir(psAppDataPath()); +#else // (!Q_OS_WIN && !_DEBUG) || Q_OS_WINRT || OS_WIN_STORE || OS_MAC_STORE cForceWorkingDir(cExeDir()); if (!LogsData->openMain()) { cForceWorkingDir(psAppDataPath()); } +#endif // (!Q_OS_WIN && !_DEBUG) || Q_OS_WINRT || OS_WIN_STORE || OS_MAC_STORE } if (launcher.validateCustomWorkingDir()) { @@ -413,7 +373,7 @@ void start() { // WinRT build requires the working dir to stay the same for plugin loading. #ifndef Q_OS_WINRT - QDir().setCurrent(cWorkingDir()); + QDir::setCurrent(cWorkingDir()); #endif // !Q_OS_WINRT QDir().mkpath(cWorkingDir() + u"tdata"_q); @@ -432,7 +392,7 @@ void start() { ).arg(cAlphaVersion() ).arg(Logs::b(DebugEnabled()))); LOG(("Executable dir: %1, name: %2").arg(cExeDir(), cExeName())); - LOG(("Initial working dir: %1").arg(initialWorkingDir)); + LOG(("Initial working dir: %1").arg(launcher.initialWorkingDir())); LOG(("Working dir: %1").arg(cWorkingDir())); LOG(("Command line: %1").arg(launcher.arguments().join(' '))); @@ -444,12 +404,11 @@ void start() { #ifdef Q_OS_WIN if (cWorkingDir() == psAppDataPath()) { // fix old "Telegram Win (Unofficial)" version - moveOldDataFrom = psAppDataPathOld(); + MoveOldDataFiles(psAppDataPathOld()); } +#elif !defined Q_OS_MAC && !defined _DEBUG // fix first version + MoveOldDataFiles(launcher.initialWorkingDir()); #endif - if (!moveOldDataFrom.isEmpty()) { - MoveOldDataFiles(moveOldDataFrom); - } if (LogsInMemory) { Assert(LogsInMemory != DeletedLogsInMemory); @@ -560,7 +519,7 @@ void writeDebug(const QString &v) { //OutputDebugString(reinterpret_cast(msg.utf16())); #elif defined Q_OS_MAC //objc_outputDebugString(msg); -#elif defined Q_OS_UNIX && defined _DEBUG +#elif defined _DEBUG //std::cout << msg.toUtf8().constData(); #endif } diff --git a/Telegram/SourceFiles/main/main_session_settings.cpp b/Telegram/SourceFiles/main/main_session_settings.cpp index 261ff01ed..769cd6998 100644 --- a/Telegram/SourceFiles/main/main_session_settings.cpp +++ b/Telegram/SourceFiles/main/main_session_settings.cpp @@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session_settings.h" #include "chat_helpers/tabbed_selector.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/chat/attach/attach_send_files_way.h" #include "window/section_widget.h" #include "support/support_common.h" diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index d6d7e6a57..c6ff4d362 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -18,9 +18,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_changes.h" #include "data/data_document.h" #include "data/data_file_origin.h" +#include "data/data_peer.h" #include "data/data_session.h" #include "data/data_stories.h" -#include "data/data_user.h" #include "history/view/reactions/history_view_reactions_strip.h" #include "lang/lang_keys.h" #include "main/main_session.h" @@ -62,7 +62,7 @@ constexpr auto kSiblingMultiplierMax = 0.72; constexpr auto kSiblingOutsidePart = 0.24; constexpr auto kSiblingUserpicSize = 0.3; constexpr auto kInnerHeightMultiplier = 1.6; -constexpr auto kPreloadUsersCount = 3; +constexpr auto kPreloadPeersCount = 3; constexpr auto kPreloadStoriesCount = 5; constexpr auto kPreloadNextMediaCount = 3; constexpr auto kPreloadPreviousMediaCount = 1; @@ -140,10 +140,10 @@ private: class Controller::Unsupported final { public: - Unsupported(not_null controller, not_null user); + Unsupported(not_null controller, not_null peer); private: - void setup(not_null user); + void setup(not_null peer); const not_null _controller; std::unique_ptr _bg; @@ -206,13 +206,13 @@ void Controller::PhotoPlayback::callback() { Controller::Unsupported::Unsupported( not_null controller, - not_null user) + not_null peer) : _controller(controller) , _bgRound(st::storiesRadius, st::storiesComposeBg) { - setup(user); + setup(peer); } -void Controller::Unsupported::setup(not_null user) { +void Controller::Unsupported::setup(not_null peer) { const auto wrap = _controller->wrap(); _bg = std::make_unique(wrap); @@ -509,27 +509,27 @@ void Controller::initLayout() { .nameBoundingRect = nameBoundingRect(right, false), .nameFontSize = nameFontSize, }; - if (!_locationAreas.empty()) { - rebuildLocationAreas(layout); + if (!_areas.empty()) { + rebuildActiveAreas(layout); } return layout; }); } -void Controller::rebuildLocationAreas(const Layout &layout) const { - Expects(_locations.size() == _locationAreas.size()); - +void Controller::rebuildActiveAreas(const Layout &layout) const { const auto origin = layout.content.topLeft(); const auto scale = layout.content.size(); - for (auto i = 0, count = int(_locations.size()); i != count; ++i) { - auto &area = _locationAreas[i]; - const auto &general = _locations[i].area.geometry; + for (auto &area : _areas) { + const auto &general = area.original; area.geometry = QRect( int(base::SafeRound(general.x() * scale.width())), int(base::SafeRound(general.y() * scale.height())), int(base::SafeRound(general.width() * scale.width())), int(base::SafeRound(general.height() * scale.height())) ).translated(origin); + if (const auto reaction = area.reaction.get()) { + reaction->setAreaGeometry(area.geometry); + } } } @@ -596,8 +596,8 @@ void Controller::toggleLiked() { void Controller::reactionChosen(ReactionsMode mode, ChosenReaction chosen) { if (mode == ReactionsMode::Message) { _replyArea->sendReaction(chosen.id); - } else if (const auto user = shownUser()) { - user->owner().stories().sendReaction(_shown, chosen.id); + } else if (const auto peer = shownPeer()) { + peer->owner().stories().sendReaction(_shown, chosen.id); } unfocusReply(); } @@ -638,11 +638,11 @@ auto Controller::cachedReactionIconFactory() const } void Controller::rebuildFromContext( - not_null user, + not_null peer, FullStoryId storyId) { using namespace Data; - auto &stories = user->owner().stories(); + auto &stories = peer->owner().stories(); auto list = std::optional(); auto source = (const StoriesSource*)nullptr; const auto peerId = storyId.peer; @@ -654,41 +654,38 @@ void Controller::rebuildFromContext( hideSiblings(); }, [&](StoriesContextSaved) { if (stories.savedCountKnown(peerId)) { - if (const auto saved = stories.saved(peerId)) { - const auto &ids = saved->list; - const auto i = ids.find(id); - if (i != end(ids)) { - list = StoriesList{ - .user = user, - .ids = *saved, - .total = stories.savedCount(peerId), - }; - _index = int(i - begin(ids)); - if (ids.size() < list->total - && (end(ids) - i) < kPreloadStoriesCount) { - stories.savedLoadMore(peerId); - } + const auto &saved = stories.saved(peerId); + const auto &ids = saved.list; + const auto i = ids.find(id); + if (i != end(ids)) { + list = StoriesList{ + .peer = peer, + .ids = saved, + .total = stories.savedCount(peerId), + }; + _index = int(i - begin(ids)); + if (ids.size() < list->total + && (end(ids) - i) < kPreloadStoriesCount) { + stories.savedLoadMore(peerId); } } } hideSiblings(); }, [&](StoriesContextArchive) { - Expects(user->isSelf()); - - if (stories.archiveCountKnown()) { - const auto &archive = stories.archive(); + if (stories.archiveCountKnown(peerId)) { + const auto &archive = stories.archive(peerId); const auto &ids = archive.list; const auto i = ids.find(id); if (i != end(ids)) { list = StoriesList{ - .user = user, + .peer = peer, .ids = archive, - .total = stories.archiveCount(), + .total = stories.archiveCount(peerId), }; _index = int(i - begin(ids)); if (ids.size() < list->total && (end(ids) - i) < kPreloadStoriesCount) { - stories.archiveLoadMore(); + stories.archiveLoadMore(peerId); } } } @@ -706,8 +703,8 @@ void Controller::rebuildFromContext( } rebuildCachedSourcesList(sources, (i - begin(sources))); _cachedSourcesList[_cachedSourceIndex].shownId = storyId.story; - showSiblings(&user->session()); - if (int(sources.end() - i) < kPreloadUsersCount) { + showSiblings(&peer->session()); + if (int(sources.end() - i) < kPreloadPeersCount) { stories.loadMore(list); } } @@ -719,7 +716,7 @@ void Controller::rebuildFromContext( if (_list != list) { _list = std::move(list); } - if (const auto maybe = user->owner().stories().lookup(storyId)) { + if (const auto maybe = peer->owner().stories().lookup(storyId)) { const auto now = *maybe; const auto range = ComputeSameDayRange(now, _list->ids, _index); _sliderCount = range.till - range.from + 1; @@ -737,7 +734,7 @@ void Controller::rebuildFromContext( if (!source) { _source = std::nullopt; _list = StoriesList{ - .user = user, + .peer = peer, .ids = { { id } }, .total = 1, }; @@ -761,17 +758,17 @@ void Controller::preloadNext() { auto ids = std::vector(); ids.reserve(kPreloadPreviousMediaCount + kPreloadNextMediaCount); - const auto user = shownUser(); + const auto peer = shownPeer(); const auto count = shownCount(); const auto till = std::min(_index + kPreloadNextMediaCount, count); for (auto i = _index + 1; i != till; ++i) { - ids.push_back({ .peer = user->id, .story = shownId(i) }); + ids.push_back({ .peer = peer->id, .story = shownId(i) }); } const auto from = std::max(_index - kPreloadPreviousMediaCount, 0); for (auto i = _index; i != from;) { - ids.push_back({ .peer = user->id, .story = shownId(--i) }); + ids.push_back({ .peer = peer->id, .story = shownId(--i) }); } - user->owner().stories().setPreloadingInViewer(std::move(ids)); + peer->owner().stories().setPreloadingInViewer(std::move(ids)); } void Controller::checkMoveByDelta() { @@ -786,18 +783,18 @@ void Controller::show( Data::StoriesContext context) { auto &stories = story->owner().stories(); const auto storyId = story->fullId(); - const auto user = story->peer()->asUser(); + const auto peer = story->peer(); _context = context; _waitingForId = {}; _waitingForDelta = 0; - rebuildFromContext(user, storyId); + rebuildFromContext(peer, storyId); _contextLifetime.destroy(); const auto subscribeToSource = [&] { stories.sourceChanged() | rpl::filter( rpl::mappers::_1 == storyId.peer ) | rpl::start_with_next([=] { - rebuildFromContext(user, storyId); + rebuildFromContext(peer, storyId); }, _contextLifetime); }; v::match(_context.data, [&](Data::StoriesContextSingle) { @@ -807,13 +804,13 @@ void Controller::show( stories.savedChanged() | rpl::filter( rpl::mappers::_1 == storyId.peer ) | rpl::start_with_next([=] { - rebuildFromContext(user, storyId); + rebuildFromContext(peer, storyId); checkMoveByDelta(); }, _contextLifetime); }, [&](Data::StoriesContextArchive) { stories.archiveChanged( ) | rpl::start_with_next([=] { - rebuildFromContext(user, storyId); + rebuildFromContext(peer, storyId); checkMoveByDelta(); }, _contextLifetime); }, [&](Data::StorySourcesList) { @@ -834,7 +831,7 @@ void Controller::show( if (!unsupported) { _unsupported = nullptr; } else { - _unsupported = std::make_unique(this, user); + _unsupported = std::make_unique(this, peer); _header->raise(); _slider->raise(); } @@ -845,7 +842,7 @@ void Controller::show( _contentFadeAnimation.stop(); const auto document = story->document(); _header->show({ - .user = user, + .peer = peer, .date = story->date(), .fullIndex = _sliderCount ? _index : 0, .fullCount = _sliderCount ? shownCount() : 0, @@ -858,35 +855,46 @@ void Controller::show( if (!changeShown(story)) { return; } - _viewed = false; - invalidate_weak_ptrs(&_viewsLoadGuard); - _reactions->hide(); - if (_replyArea->focused()) { - unfocusReply(); - } _replyArea->show({ - .user = unsupported ? nullptr : user, + .peer = unsupported ? nullptr : peer.get(), .id = story->id(), }, _reactions->likedValue()); + const auto wasLikeButton = QPointer(_recentViews->likeButton()); _recentViews->show({ .list = story->recentViewers(), .reactions = story->reactions(), .total = story->views(), - .valid = user->isSelf(), - }); + .self = peer->isSelf(), + .channel = peer->isChannel(), + }, _reactions->likedValue()); + if (const auto nowLikeButton = _recentViews->likeButton()) { + if (wasLikeButton != nowLikeButton) { + _reactions->attachToReactionButton(nowLikeButton); + } + } + + if (peer->isSelf() || peer->isChannel()) { + _reactions->setReactionIconWidget(_recentViews->likeIconWidget()); + } else if (const auto like = _replyArea->likeAnimationTarget()) { + _reactions->setReactionIconWidget(like); + } + _reactions->showLikeFrom(story); stories.loadAround(storyId, context); updatePlayingAllowed(); - user->updateFull(); + peer->updateFull(); } bool Controller::changeShown(Data::Story *story) { const auto id = story ? story->fullId() : FullStoryId(); const auto session = story ? &story->session() : nullptr; const auto sessionChanged = (_session != session); + + updateAreas(story); + if (_shown == id && !sessionChanged) { return false; } @@ -909,14 +917,13 @@ bool Controller::changeShown(Data::Story *story) { story, Data::Stories::Polling::Viewer); } - _reactions->showLikeFrom(story); - const auto &locations = story - ? story->locations() - : std::vector(); - if (_locations != locations) { - _locations = locations; - _locationAreas.clear(); + _viewed = false; + invalidate_weak_ptrs(&_viewsLoadGuard); + _reactions->hide(); + _reactions->setReactionIconWidget(nullptr); + if (_replyArea->focused()) { + unfocusReply(); } return true; @@ -943,7 +950,8 @@ void Controller::subscribeToSession() { }, _sessionLifetime); _session->changes().storyUpdates( Data::StoryUpdate::Flag::Edited - | Data::StoryUpdate::Flag::ViewsAdded + | Data::StoryUpdate::Flag::ViewsChanged + | Data::StoryUpdate::Flag::Reaction ) | rpl::filter([=](const Data::StoryUpdate &update) { return (update.story == this->story()); }) | rpl::start_with_next([=](const Data::StoryUpdate &update) { @@ -955,8 +963,10 @@ void Controller::subscribeToSession() { .list = update.story->recentViewers(), .reactions = update.story->reactions(), .total = update.story->views(), - .valid = update.story->peer()->isSelf(), + .self = update.story->peer()->isSelf(), + .channel = update.story->peer()->isChannel(), }); + updateAreas(update.story); } }, _sessionLifetime); _sessionLifetime.add([=] { @@ -964,6 +974,41 @@ void Controller::subscribeToSession() { }); } +void Controller::updateAreas(Data::Story *story) { + const auto &locations = story + ? story->locations() + : std::vector(); + const auto &suggestedReactions = story + ? story->suggestedReactions() + : std::vector(); + if (_locations != locations) { + _locations = locations; + _areas.clear(); + } + const auto reactionsCount = int(suggestedReactions.size()); + if (_suggestedReactions.size() == reactionsCount && !_areas.empty()) { + for (auto i = 0; i != reactionsCount; ++i) { + const auto count = suggestedReactions[i].count; + if (_suggestedReactions[i].count != count) { + _suggestedReactions[i].count = count; + _areas[i + _locations.size()].reaction->updateCount(count); + } + if (_suggestedReactions[i] != suggestedReactions[i]) { + _suggestedReactions = suggestedReactions; + _areas.clear(); + break; + } + } + } else if (_suggestedReactions != suggestedReactions) { + _suggestedReactions = suggestedReactions; + _areas.clear(); + } + if (_areas.empty() || _suggestedReactions.empty()) { + return; + } + +} + PauseState Controller::pauseState() const { const auto inactive = !_windowActive || _replyActive @@ -1082,26 +1127,56 @@ void Controller::updatePlayback(const Player::TrackState &state) { } } -ClickHandlerPtr Controller::lookupLocationHandler(QPoint point) const { +ClickHandlerPtr Controller::lookupAreaHandler(QPoint point) const { const auto &layout = _layout.current(); - if (_locations.empty() || !layout) { + if ((_locations.empty() && _suggestedReactions.empty()) || !layout) { return nullptr; - } else if (_locationAreas.empty()) { - _locationAreas = _locations | ranges::views::transform([]( - const Data::StoryLocation &location) { - return LocationArea{ + } else if (_areas.empty()) { + _areas.reserve(_locations.size() + _suggestedReactions.size()); + for (const auto &location : _locations) { + _areas.push_back({ + .original = location.area.geometry, .rotation = location.area.rotation, .handler = std::make_shared( location.point), - }; - }) | ranges::to_vector; - rebuildLocationAreas(*layout); + }); + } + for (const auto &suggestedReaction : _suggestedReactions) { + const auto id = suggestedReaction.reaction; + auto widget = _reactions->makeSuggestedReactionWidget( + suggestedReaction); + const auto raw = widget.get(); + _areas.push_back({ + .original = suggestedReaction.area.geometry, + .rotation = suggestedReaction.area.rotation, + .handler = std::make_shared([=] { + raw->playEffect(); + if (const auto now = story()) { + if (now->sentReactionId() != id) { + now->owner().stories().sendReaction( + now->fullId(), + id); + } + } + }), + .reaction = std::move(widget), + }); + } + rebuildActiveAreas(*layout); } - for (const auto &area : _locationAreas) { + const auto circleContains = [&](QRect circle) { + const auto radius = std::min(circle.width(), circle.height()) / 2; + const auto delta = circle.center() - point; + return QPoint::dotProduct(delta, delta) < (radius * radius); + }; + for (const auto &area : _areas) { const auto center = area.geometry.center(); const auto angle = -area.rotation; - if (area.geometry.contains(Rotated(point, center, angle))) { + const auto contains = area.reaction + ? circleContains(area.geometry) + : area.geometry.contains(Rotated(point, center, angle)); + if (contains) { return area.handler; } } @@ -1129,7 +1204,7 @@ void Controller::markAsRead() { return; } _viewed = true; - shownUser()->owner().stories().markAsRead(_shown, _started); + shownPeer()->owner().stories().markAsRead(_shown, _started); } bool Controller::subjumpAvailable(int delta) const { @@ -1169,12 +1244,12 @@ void Controller::subjumpTo(int index) { Expects(shown()); Expects(index >= 0 && index < shownCount()); - const auto user = shownUser(); + const auto peer = shownPeer(); const auto id = FullStoryId{ - .peer = user->id, + .peer = peer->id, .story = shownId(index), }; - auto &stories = user->owner().stories(); + auto &stories = peer->owner().stories(); if (!id.story) { const auto delta = index - _index; if (_waitingForDelta != delta) { @@ -1183,7 +1258,7 @@ void Controller::subjumpTo(int index) { loadMoreToList(); } } else if (stories.lookup(id)) { - _delegate->storiesJumpTo(&user->session(), id, _context); + _delegate->storiesJumpTo(&peer->session(), id, _context); } else if (_waitingForId != id) { _waitingForId = id; _waitingForDelta = 0; @@ -1195,8 +1270,8 @@ void Controller::checkWaitingFor() { Expects(_waitingForId.valid()); Expects(shown()); - const auto user = shownUser(); - auto &stories = user->owner().stories(); + const auto peer = shownPeer(); + auto &stories = peer->owner().stories(); const auto maybe = stories.lookup(_waitingForId); if (!maybe) { if (maybe.error() == Data::NoStory::Deleted) { @@ -1205,7 +1280,7 @@ void Controller::checkWaitingFor() { return; } _delegate->storiesJumpTo( - &user->session(), + &peer->session(), base::take(_waitingForId), _context); } @@ -1290,9 +1365,13 @@ const Data::StoryViews &Controller::views(int limit, bool initial) { if (_viewsSlice.total > _viewsSlice.list.size() && _viewsSlice.list.size() < limit) { const auto done = viewsGotMoreCallback(); - const auto user = shownUser(); - auto &stories = user->owner().stories(); - stories.loadViewsSlice(_shown.story, _viewsSlice.nextOffset, done); + const auto peer = shownPeer(); + auto &stories = peer->owner().stories(); + stories.loadViewsSlice( + peer, + _shown.story, + _viewsSlice.nextOffset, + done); } return _viewsSlice; } @@ -1304,8 +1383,8 @@ rpl::producer<> Controller::moreViewsLoaded() const { Fn Controller::viewsGotMoreCallback() { return crl::guard(&_viewsLoadGuard, [=](Data::StoryViews result) { if (_viewsSlice.list.empty()) { - const auto user = shownUser(); - auto &stories = user->owner().stories(); + const auto peer = shownPeer(); + auto &stories = peer->owner().stories(); if (const auto maybeStory = stories.lookup(_shown)) { _viewsSlice = (*maybeStory)->viewsList(); } else { @@ -1329,11 +1408,11 @@ bool Controller::shown() const { return _source || _list; } -UserData *Controller::shownUser() const { +PeerData *Controller::shownPeer() const { return _source - ? _source->user.get() + ? _source->peer.get() : _list - ? _list->user.get() + ? _list->peer.get() : nullptr; } @@ -1356,15 +1435,13 @@ void Controller::loadMoreToList() { using namespace Data; - const auto user = shownUser(); + const auto peer = shownPeer(); const auto peerId = _shown.peer; - auto &stories = user->owner().stories(); + auto &stories = peer->owner().stories(); v::match(_context.data, [&](StoriesContextSaved) { stories.savedLoadMore(peerId); }, [&](StoriesContextArchive) { - Expects(user->isSelf()); - - stories.archiveLoadMore(); + stories.archiveLoadMore(peerId); }, [](const auto &) { }); } @@ -1462,10 +1539,10 @@ void Controller::rebuildCachedSourcesList( void Controller::refreshViewsFromData() { Expects(shown()); - const auto user = shownUser(); - auto &stories = user->owner().stories(); + const auto peer = shownPeer(); + auto &stories = peer->owner().stories(); const auto maybeStory = stories.lookup(_shown); - if (!maybeStory || !user->isSelf()) { + if (!maybeStory || !peer->isSelf()) { _viewsSlice = {}; } else { _viewsSlice = (*maybeStory)->viewsList(); @@ -1523,7 +1600,8 @@ void Controller::togglePinnedRequested(bool pinned) { moveFromShown(); } story->owner().stories().togglePinnedList({ story->fullId() }, pinned); - uiShow()->showToast(PrepareTogglePinnedToast(1, pinned)); + const auto channel = story->peer()->isChannel(); + uiShow()->showToast(PrepareTogglePinnedToast(channel, 1, pinned)); } void Controller::moveFromShown() { @@ -1574,29 +1652,41 @@ void Controller::updatePowerSaveBlocker(const Player::TrackState &state) { [=] { return _wrap->window()->windowHandle(); }); } -Ui::Toast::Config PrepareTogglePinnedToast(int count, bool pinned) { +Ui::Toast::Config PrepareTogglePinnedToast( + bool channel, + int count, + bool pinned) { return { .text = (pinned ? (count == 1 - ? tr::lng_stories_save_done( - tr::now, - Ui::Text::Bold) - : tr::lng_stories_save_done_many( - tr::now, - lt_count, - count, - Ui::Text::Bold)).append( - '\n').append( - tr::lng_stories_save_done_about(tr::now)) + ? (channel + ? tr::lng_stories_channel_save_done + : tr::lng_stories_save_done)( + tr::now, + Ui::Text::Bold) + : (channel + ? tr::lng_stories_channel_save_done_many + : tr::lng_stories_save_done_many)( + tr::now, + lt_count, + count, + Ui::Text::Bold)).append( + '\n').append((channel + ? tr::lng_stories_channel_save_done_about + : tr::lng_stories_save_done_about)(tr::now)) : (count == 1 - ? tr::lng_stories_archive_done( - tr::now, - Ui::Text::WithEntities) - : tr::lng_stories_archive_done_many( - tr::now, - lt_count, - count, - Ui::Text::WithEntities))), + ? (channel + ? tr::lng_stories_channel_archive_done + : tr::lng_stories_archive_done)( + tr::now, + Ui::Text::WithEntities) + : (channel + ? tr::lng_stories_channel_archive_done_many + : tr::lng_stories_archive_done_many)( + tr::now, + lt_count, + count, + Ui::Text::WithEntities))), .st = &st::storiesActionToast, .duration = (pinned ? Data::Stories::kPinnedToastDuration diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h index f932c40a6..88ceea064 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.h +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -68,6 +68,7 @@ enum class SiblingType; struct ContentLayout; class CaptionFullView; enum class ReactionsMode; +class SuggestedReactionView; enum class HeaderLayout { Normal, @@ -135,7 +136,7 @@ public: void ready(); void updateVideoPlayback(const Player::TrackState &state); - [[nodiscard]] ClickHandlerPtr lookupLocationHandler(QPoint point) const; + [[nodiscard]] ClickHandlerPtr lookupAreaHandler(QPoint point) const; [[nodiscard]] bool subjumpAvailable(int delta) const; [[nodiscard]] bool subjumpFor(int delta); @@ -181,7 +182,7 @@ private: class Unsupported; using ChosenReaction = HistoryView::Reactions::ChosenReaction; struct StoriesList { - not_null user; + not_null peer; Data::StoriesIds ids; int total = 0; @@ -197,10 +198,12 @@ private: return peerId != 0; } }; - struct LocationArea { + struct ActiveArea { + QRectF original; QRect geometry; float64 rotation = 0.; ClickHandlerPtr handler; + std::unique_ptr reaction; }; void initLayout(); @@ -215,7 +218,7 @@ private: void updateContentFaded(); void updatePlayingAllowed(); void setPlayingAllowed(bool allowed); - void rebuildLocationAreas(const Layout &layout) const; + void rebuildActiveAreas(const Layout &layout) const; void hideSiblings(); void showSiblings(not_null session); @@ -233,10 +236,10 @@ private: -> Fn; [[nodiscard]] bool shown() const; - [[nodiscard]] UserData *shownUser() const; + [[nodiscard]] PeerData *shownPeer() const; [[nodiscard]] int shownCount() const; [[nodiscard]] StoryId shownId(int index) const; - void rebuildFromContext(not_null user, FullStoryId storyId); + void rebuildFromContext(not_null peer, FullStoryId storyId); void checkMoveByDelta(); void loadMoreToList(); void preloadNext(); @@ -244,6 +247,7 @@ private: const std::vector &lists, int index); + void updateAreas(Data::Story *story); void reactionChosen(ReactionsMode mode, ChosenReaction chosen); const not_null _delegate; @@ -284,7 +288,8 @@ private: bool _viewed = false; std::vector _locations; - mutable std::vector _locationAreas; + std::vector _suggestedReactions; + mutable std::vector _areas; std::vector _cachedSourcesList; int _cachedSourceIndex = -1; @@ -309,6 +314,7 @@ private: }; [[nodiscard]] Ui::Toast::Config PrepareTogglePinnedToast( + bool channel, int count, bool pinned); void ReportRequested( diff --git a/Telegram/SourceFiles/media/stories/media_stories_header.cpp b/Telegram/SourceFiles/media/stories/media_stories_header.cpp index 9156ac35d..3440f4018 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_header.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_header.cpp @@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unixtime.h" #include "chat_helpers/compose/compose_show.h" -#include "data/data_user.h" +#include "data/data_peer.h" #include "media/stories/media_stories_controller.h" #include "lang/lang_keys.h" #include "ui/controls/userpic_button.h" @@ -268,7 +268,7 @@ void Header::show(HeaderData data) { if (_data == data) { return; } - const auto userChanged = !_data || (_data->user != data.user); + const auto peerChanged = !_data || (_data->peer != data.peer); _data = data; const auto updateInfoGeometry = [=] { if (_name && _date) { @@ -282,7 +282,7 @@ void Header::show(HeaderData data) { }; _tooltip = nullptr; _tooltipShown = false; - if (userChanged) { + if (peerChanged) { _volume = nullptr; _date = nullptr; _name = nullptr; @@ -298,12 +298,12 @@ void Header::show(HeaderData data) { _info = std::make_unique(raw); _info->setClickedCallback([=] { - _controller->uiShow()->show(PrepareShortInfoBox(_data->user)); + _controller->uiShow()->show(PrepareShortInfoBox(_data->peer)); }); _userpic = std::make_unique( raw, - data.user, + data.peer, st::storiesHeaderPhoto); _userpic->setAttribute(Qt::WA_TransparentForMouseEvents); _userpic->show(); @@ -313,9 +313,9 @@ void Header::show(HeaderData data) { _name = std::make_unique( raw, - rpl::single(data.user->isSelf() + rpl::single(data.peer->isSelf() ? tr::lng_stories_my_name(tr::now) - : data.user->name()), + : data.peer->name()), st::storiesHeaderName); _name->setAttribute(Qt::WA_TransparentForMouseEvents); _name->setOpacity(kNameOpacity); @@ -403,9 +403,9 @@ void Header::show(HeaderData data) { _pauseState = _controller->pauseState(); applyPauseState(); } else { - _volume = nullptr; _playPause = nullptr; _volumeToggle = nullptr; + _volume = nullptr; } rpl::combine( @@ -605,8 +605,8 @@ void Header::toggleTooltip(Tooltip type, bool show) { } const auto text = [&]() -> TextWithEntities { using Privacy = Data::StoryPrivacy; - const auto boldName = Ui::Text::Bold(_data->user->shortName()); - const auto self = _data->user->isSelf(); + const auto boldName = Ui::Text::Bold(_data->peer->shortName()); + const auto self = _data->peer->isSelf(); switch (type) { case Tooltip::SilentVideo: return { tr::lng_stories_about_silent(tr::now) }; diff --git a/Telegram/SourceFiles/media/stories/media_stories_header.h b/Telegram/SourceFiles/media/stories/media_stories_header.h index c10df3ec2..1da36174f 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_header.h +++ b/Telegram/SourceFiles/media/stories/media_stories_header.h @@ -31,7 +31,7 @@ class Controller; enum class PauseState; struct HeaderData { - not_null user; + not_null peer; TimeId date = 0; int fullIndex = 0; int fullCount = 0; diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp index 8057829e3..2d9e017d5 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/stories/media_stories_reactions.h" #include "base/event_filter.h" +#include "base/unixtime.h" #include "boxes/premium_preview_box.h" #include "chat_helpers/compose/compose_show.h" #include "data/data_changes.h" @@ -16,17 +17,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_message_reactions.h" #include "data/data_peer.h" #include "data/data_session.h" +#include "history/admin_log/history_admin_log_item.h" +#include "history/view/media/history_view_custom_emoji.h" +#include "history/view/media/history_view_media_unwrapped.h" #include "history/view/reactions/history_view_reactions_selector.h" +#include "history/view/history_view_element.h" +#include "history/history_item_reply_markup.h" +#include "history/history_item.h" +#include "history/history.h" #include "main/main_session.h" #include "media/stories/media_stories_controller.h" +#include "lang/lang_tag.h" +#include "ui/chat/chat_style.h" #include "ui/effects/emoji_fly_animation.h" +#include "ui/effects/path_shift_gradient.h" #include "ui/effects/reaction_fly_animation.h" +#include "ui/text/text_isolated_emoji.h" #include "ui/widgets/popup_menu.h" #include "ui/animated_icon.h" #include "ui/painter.h" #include "styles/style_chat_helpers.h" +#include "styles/style_chat.h" #include "styles/style_media_view.h" #include "styles/style_widgets.h" +#include "styles/style_window.h" namespace Media::Stories { namespace { @@ -34,6 +48,425 @@ namespace { constexpr auto kReactionScaleOutTarget = 0.7; constexpr auto kReactionScaleOutDuration = crl::time(1000); constexpr auto kMessageReactionScaleOutDuration = crl::time(400); +constexpr auto kSuggestedBubbleSize = 1.0; +constexpr auto kSuggestedTailBigSize = 0.264; +constexpr auto kSuggestedTailBigOffset = 0.464; +constexpr auto kSuggestedTailSmallSize = 0.110; +constexpr auto kSuggestedTailSmallOffset = 0.697; +constexpr auto kSuggestedTailBigRotation = -42.29; +constexpr auto kSuggestedTailSmallRotation = -40.87; +constexpr auto kSuggestedReactionSize = 0.7; +constexpr auto kSuggestedWithCountSize = 0.55; +constexpr auto kStoppingFadeDuration = crl::time(150); + +class ReactionView final + : public Ui::RpWidget + , public SuggestedReactionView + , public HistoryView::DefaultElementDelegate { +public: + ReactionView( + QWidget *parent, + not_null session, + const Data::SuggestedReaction &reaction); + + void setAreaGeometry(QRect geometry) override; + void updateCount(int count) override; + void playEffect() override; + +private: + using Element = HistoryView::Element; + + struct Stopping { + std::unique_ptr effect; + Ui::Animations::Simple animation; + }; + + not_null delegate(); + HistoryView::Context elementContext() override; + bool elementAnimationsPaused() override; + bool elementShownUnread(not_null view) override; + not_null elementPathShiftGradient() override; + + void paintEvent(QPaintEvent *e) override; + + void setupCustomChatStylePalette(); + void cacheBackground(); + void paintEffectFrame( + QPainter &p, + not_null effect, + crl::time now); + void updateEffectGeometry(); + void createEffectCanvas(); + void stopEffect(); + + Data::SuggestedReaction _data; + std::unique_ptr _chatStyle; + std::unique_ptr _pathGradient; + AdminLog::OwnedItem _fake; + QImage _background; + QString _countShort; + Ui::Text::String _counter; + Ui::Animations::Simple _counterAnimation; + QRectF _bubbleGeometry; + int _size = 0; + int _mediaLeft = 0; + int _mediaTop = 0; + int _mediaWidth = 0; + int _mediaHeight = 0; + float64 _bubble = 0; + float64 _bigOffset = 0; + float64 _bigSize = 0; + float64 _smallOffset = 0; + float64 _smallSize = 0; + + std::unique_ptr _effectCanvas; + std::unique_ptr _effect; + std::vector _effectStopping; + QRect _effectTarget; + +}; + +[[nodiscard]] AdminLog::OwnedItem GenerateFakeItem( + not_null delegate, + not_null history) { + Expects(history->peer->isUser()); + + const auto flags = MessageFlag::FakeHistoryItem + | MessageFlag::HasFromId; + const auto replyTo = FullReplyTo(); + const auto viaBotId = UserId(); + const auto groupedId = uint64(); + const auto item = history->makeMessage( + history->nextNonHistoryEntryId(), + flags, + replyTo, + viaBotId, + base::unixtime::now(), + peerToUser(history->peer->id), + QString(), + TextWithEntities(), + MTP_messageMediaEmpty(), + HistoryMessageMarkupData(), + groupedId); + return AdminLog::OwnedItem(delegate, item); +} + +ReactionView::ReactionView( + QWidget *parent, + not_null session, + const Data::SuggestedReaction &reaction) +: RpWidget(parent) +, _data(reaction) +, _chatStyle(std::make_unique()) +, _pathGradient( + std::make_unique( + st::shadowFg, + st::shadowFg, + [=] { update(); })) +, _fake( + GenerateFakeItem( + delegate(), + session->data().history(PeerData::kServiceNotificationsId))) { + style::PaletteChanged() | rpl::start_with_next([=] { + _background = QImage(); + }, lifetime()); + + const auto view = _fake.get(); + const auto entityData = [&] { + const auto &id = _data.reaction; + const auto reactions = &session->data().reactions(); + reactions->preloadAnimationsFor(id); + if (const auto customId = id.custom()) { + return Data::SerializeCustomEmojiId(customId); + } + const auto type = Data::Reactions::Type::All; + const auto &list = reactions->list(type); + const auto i = ranges::find(list, id, &Data::Reaction::id); + return (i != end(list)) + ? Data::SerializeCustomEmojiId(i->selectAnimation->id) + : QString(); + }(); + + const auto emoji = Ui::Text::OnlyCustomEmoji{ + { { { entityData } } } + }; + view->overrideMedia(std::make_unique( + view, + std::make_unique(view, emoji))); + view->initDimensions(); + + _mediaLeft = st::msgMargin.left(); + _mediaTop = st::msgMargin.top(); + _mediaWidth = _mediaHeight = view->resizeGetHeight(st::windowMinWidth) + - _mediaTop + - st::msgMargin.bottom(); + + session->data().viewRepaintRequest( + ) | rpl::start_with_next([=](not_null element) { + if (element == view) { + update(); + } + }, lifetime()); + + _data.count = 0; + updateCount(reaction.count); + _counterAnimation.stop(); + + setupCustomChatStylePalette(); + setAttribute(Qt::WA_TransparentForMouseEvents); + show(); +} + +void ReactionView::setupCustomChatStylePalette() { + const auto color = uchar(_data.dark ? 255 : 0); + _chatStyle->historyTextInFg().set(color, color, color, 255); + _chatStyle->applyCustomPalette(_chatStyle.get()); +} + +void ReactionView::setAreaGeometry(QRect geometry) { + _size = std::min(geometry.width(), geometry.height()); + _bubble = _size * kSuggestedBubbleSize; + _bigOffset = _bubble * kSuggestedTailBigOffset; + _bigSize = _bubble * kSuggestedTailBigSize; + _smallOffset = _bubble * kSuggestedTailSmallOffset; + _smallSize = _bubble * kSuggestedTailSmallSize; + const auto add = int(base::SafeRound(_smallOffset + _smallSize)) + - (_size / 2); + setGeometry(geometry.marginsAdded({ add, add, add, add })); + const auto sub = int(base::SafeRound( + (1. - kSuggestedReactionSize) * _size / 2)); + _effectTarget = geometry.marginsRemoved({ sub, sub, sub, sub }); + updateEffectGeometry(); +} + +void ReactionView::updateCount(int count) { + if (_data.count == count) { + return; + } + _data.count = count; + const auto countShort = count + ? Lang::FormatCountToShort(count).string + : QString(); + if (_countShort == countShort) { + return; + } + const auto was = !_countShort.isEmpty(); + _countShort = countShort; + const auto now = !_countShort.isEmpty(); + + if (!_countShort.isEmpty()) { + _counter = { st::storiesLikeCountStyle, _countShort }; + } + if (now != was) { + _counterAnimation.start( + [=] { update(); }, + was ? 1. : 0., + was ? 0. : 1., + st::fadeWrapDuration); + } + update(); +} + +void ReactionView::playEffect() { + const auto exists = (_effectCanvas != nullptr); + if (exists) { + stopEffect(); + } else { + createEffectCanvas(); + } + const auto reactions = &_fake->history()->owner().reactions(); + const auto scaleDown = _bubbleGeometry.width() / float64(_mediaWidth); + auto args = Ui::ReactionFlyAnimationArgs{ + .id = _data.reaction, + .miniCopyMultiplier = std::min(1., scaleDown), + .effectOnly = true, + }; + _effect = std::make_unique( + reactions, + std::move(args), + [=] { _effectCanvas->update(); }, + _size / 2, + Data::CustomEmojiSizeTag::Isolated); + if (exists) { + _effectStopping.back().animation.start([=] { + _effectCanvas->update(); + }, 1., 0., kStoppingFadeDuration); + } +} + +void ReactionView::paintEffectFrame( + QPainter &p, + not_null effect, + crl::time now) { + effect->paintGetArea( + p, + QPoint(), + _effectTarget.translated(-_effectCanvas->pos()), + _data.dark ? Qt::white : Qt::black, + QRect(), + now); +} + +void ReactionView::createEffectCanvas() { + _effectCanvas = std::make_unique(parentWidget()); + const auto raw = _effectCanvas.get(); + raw->setAttribute(Qt::WA_TransparentForMouseEvents); + raw->show(); + raw->paintRequest() | rpl::start_with_next([=] { + if (!_effect || _effect->finished()) { + crl::on_main(_effectCanvas.get(), [=] { + _effect = nullptr; + _effectStopping.clear(); + _effectCanvas = nullptr; + }); + return; + } + const auto now = crl::now(); + auto p = QPainter(raw); + auto hq = PainterHighQualityEnabler(p); + _effectStopping.erase(ranges::remove_if(_effectStopping, [&]( + const Stopping &stopping) { + if (!stopping.animation.animating() + || stopping.effect->finished()) { + return true; + } + p.setOpacity(stopping.animation.value(0.)); + paintEffectFrame(p, stopping.effect.get(), now); + return false; + }), end(_effectStopping)); + paintEffectFrame(p, _effect.get(), now); + }, raw->lifetime()); + updateEffectGeometry(); +} + +void ReactionView::stopEffect() { + _effectStopping.push_back({ .effect = std::move(_effect) }); + _effectStopping.back().animation.start([=] { + _effectCanvas->update(); + }, 1., 0., kStoppingFadeDuration); +} + +void ReactionView::updateEffectGeometry() { + if (!_effectCanvas) { + return; + } + const auto center = geometry().center(); + _effectCanvas->setGeometry( + center.x() - _size, + center.y() - _size, + _size * 2, + _size * 3); +} + +not_null ReactionView::delegate() { + return static_cast(this); +} + +void ReactionView::paintEvent(QPaintEvent *e) { + auto p = Painter(this); + if (!_size) { + return; + } else if (_background.size() != size() * style::DevicePixelRatio()) { + cacheBackground(); + } + p.drawImage(0, 0, _background); + + const auto counted = _counterAnimation.value(_countShort.isEmpty() + ? 0. + : 1.); + const auto scale = kSuggestedReactionSize + + (kSuggestedWithCountSize - kSuggestedReactionSize) * counted; + const auto counterSkip = (kSuggestedReactionSize - scale) * _mediaHeight / 2; + + auto hq = PainterHighQualityEnabler(p); + p.translate(_bubbleGeometry.center()); + p.scale( + scale * _bubbleGeometry.width() / _mediaWidth, + scale * _bubbleGeometry.height() / _mediaHeight); + p.rotate(_data.area.rotation); + p.translate( + -(_mediaLeft + (_mediaWidth / 2)), + -(_mediaTop + (_mediaHeight / 2) + counterSkip)); + + auto context = Ui::ChatPaintContext{ + .st = _chatStyle.get(), + .viewport = rect(), + .clip = rect(), + .now = crl::now(), + }; + _fake->draw(p, context); + + if (counted > 0.) { + p.setPen(_data.dark ? Qt::white : Qt::black); + const auto countTop = _mediaTop + _mediaHeight; + if (counted < 1.) { + const auto center = QPoint( + _mediaLeft + (_mediaWidth / 2), + countTop + st::storiesLikeCountStyle.font->height / 2); + p.translate(center); + p.scale(counted, counted); + p.translate(-center); + } + _counter.draw(p, _mediaLeft, countTop, _mediaWidth, style::al_top); + } +} + +void ReactionView::cacheBackground() { + const auto ratio = style::DevicePixelRatio(); + _background = QImage( + size() * ratio, + QImage::Format_ARGB32_Premultiplied); + _background.setDevicePixelRatio(ratio); + _background.fill(Qt::transparent); + + const auto paintShape = [&](QColor color) { + auto p = QPainter(&_background); + auto hq = PainterHighQualityEnabler(p); + p.setPen(Qt::NoPen); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.setBrush(color); + _bubbleGeometry = QRectF( + (width() - _bubble) / 2., + (height() - _bubble) / 2., + _bubble, + _bubble); + p.drawEllipse(_bubbleGeometry); + + const auto center = QPointF(width() / 2., height() / 2.); + p.translate(center); + + auto previous = 0.; + const auto rotate = [&](float64 initial) { + if (_data.flipped) { + initial = 180 - initial; + } + auto rotation = _data.area.rotation - initial; + while (rotation < 0) { + rotation += 360; + } + while (rotation >= 360) { + rotation -= 360; + } + const auto delta = rotation - previous; + previous = rotation; + p.rotate(delta); + }; + const auto paintTailPart = [&](float64 offset, float64 size) { + const auto part = QRectF(-size / 2., -size / 2., size, size); + p.drawEllipse(part.translated(offset, 0)); + }; + rotate(kSuggestedTailBigRotation); + paintTailPart(_bigOffset, _bigSize); + rotate(kSuggestedTailSmallRotation); + paintTailPart(_smallOffset, _smallSize); + }; + const auto dark = QColor(0, 0, 0, 128); + if (!_data.dark) { + paintShape(dark); + _background = Images::Blur(std::move(_background), true); + } + paintShape(_data.dark ? dark : QColor(255, 255, 255)); +} [[nodiscard]] Data::ReactionId HeartReactionId() { return { QString() + QChar(10084) }; @@ -67,6 +500,24 @@ constexpr auto kMessageReactionScaleOutDuration = crl::time(400); return result; } +HistoryView::Context ReactionView::elementContext() { + return HistoryView::Context::ContactPreview; +} + +bool ReactionView::elementAnimationsPaused() { + return false; +} + +bool ReactionView::elementShownUnread( + not_null view) { + return false; +} + +auto ReactionView::elementPathShiftGradient() +-> not_null { + return _pathGradient.get(); +} + } // namespace class Reactions::Panel final { @@ -179,7 +630,8 @@ void Reactions::Panel::collapse(Mode mode) { } } -void Reactions::Panel::attachToReactionButton(not_null button) { +void Reactions::Panel::attachToReactionButton( + not_null button) { base::install_event_filter(button, [=](not_null e) { if (e->type() == QEvent::ContextMenu && !button->isHidden()) { show(Reactions::Mode::Reaction); @@ -252,6 +704,8 @@ void Reactions::Panel::create() { _controller->layoutValue(), _shownValue.value() ) | rpl::start_with_next([=](const Layout &layout, float64 shown) { + const auto story = _controller->story(); + const auto viewsReactionsMode = story && story->peer()->isChannel(); const auto width = margins.left() + _selector->countAppearedWidth(shown) + margins.right(); @@ -259,6 +713,8 @@ void Reactions::Panel::create() { const auto shift = (width / 2); const auto right = (mode == Mode::Message) ? (layout.reactions.x() + layout.reactions.width() / 2 + shift) + : viewsReactionsMode + ? (layout.content.x() + layout.content.width()) : (layout.controlsBottomPosition.x() + layout.controlsWidth - st::storiesLikeReactionsPosition.x()); @@ -358,6 +814,15 @@ auto Reactions::chosen() const -> rpl::producer { return _chosen.events(); } +auto Reactions::makeSuggestedReactionWidget( + const Data::SuggestedReaction &reaction) +-> std::unique_ptr { + return std::make_unique( + _controller->wrap(), + &_controller->uiShow()->session(), + reaction); +} + void Reactions::setReplyFieldState( rpl::producer focused, rpl::producer hasSendText) { @@ -387,10 +852,17 @@ void Reactions::setReplyFieldState( } void Reactions::attachToReactionButton(not_null button) { - _likeButton = button; _panel->attachToReactionButton(button); } +void Reactions::setReactionIconWidget(Ui::RpWidget *widget) { + if (_likeIconWidget != widget) { + assignLikedId({}); + _likeIconWidget = widget; + _reactionAnimation = nullptr; + } +} + auto Reactions::attachToMenu( not_null menu, QPoint desiredPosition) @@ -458,9 +930,12 @@ void Reactions::outsidePressed() { void Reactions::toggleLiked() { const auto liked = !_liked.current().empty(); - const auto now = liked ? Data::ReactionId() : HeartReactionId(); - if (_liked.current() != now) { - animateAndProcess({ { .id = now }, ReactionsMode::Reaction }); + applyLike(liked ? Data::ReactionId() : HeartReactionId()); +} + +void Reactions::applyLike(Data::ReactionId id) { + if (_liked.current() != id) { + animateAndProcess({ { .id = id }, ReactionsMode::Reaction }); } } @@ -473,7 +948,7 @@ void Reactions::ready() { void Reactions::animateAndProcess(Chosen &&chosen) { const auto like = (chosen.mode == Mode::Reaction); const auto wrap = _controller->wrap(); - const auto target = like ? _likeButton : wrap.get(); + const auto target = like ? _likeIconWidget : wrap.get(); const auto story = _controller->story(); if (!story || !target) { return; @@ -518,7 +993,7 @@ Fn Reactions::setLikedIdIconInit( return nullptr; } assignLikedId(id); - if (id.empty() || !_likeButton) { + if (id.empty() || !_likeIconWidget) { return nullptr; } return crl::guard(&_likeIconGuard, [=](Ui::ReactionFlyCenter center) { @@ -534,12 +1009,12 @@ void Reactions::initLikeIcon( not_null owner, Data::ReactionId id, Ui::ReactionFlyCenter center) { - Expects(_likeButton != nullptr); + Expects(_likeIconWidget != nullptr); - _likeIcon = std::make_unique(_likeButton); + _likeIcon = std::make_unique(_likeIconWidget); const auto icon = _likeIcon.get(); icon->show(); - _likeButton->sizeValue() | rpl::start_with_next([=](QSize size) { + _likeIconWidget->sizeValue() | rpl::start_with_next([=](QSize size) { icon->setGeometry(QRect(QPoint(), size)); }, icon->lifetime()); diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.h b/Telegram/SourceFiles/media/stories/media_stories_reactions.h index fbcf34b3c..d27761f46 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.h +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.h @@ -15,6 +15,7 @@ class DocumentMedia; struct ReactionId; class Session; class Story; +struct SuggestedReaction; } // namespace Data namespace HistoryView::Reactions { @@ -40,6 +41,15 @@ enum class ReactionsMode { Reaction, }; +class SuggestedReactionView { +public: + virtual ~SuggestedReactionView() = default; + + virtual void setAreaGeometry(QRect geometry) = 0; + virtual void updateCount(int count) = 0; + virtual void playEffect() = 0; +}; + class Reactions final { public: explicit Reactions(not_null controller); @@ -64,12 +74,18 @@ public: void hide(); void outsidePressed(); void toggleLiked(); + void applyLike(Data::ReactionId id); void ready(); + [[nodiscard]] auto makeSuggestedReactionWidget( + const Data::SuggestedReaction &reaction) + -> std::unique_ptr; + void setReplyFieldState( rpl::producer focused, rpl::producer hasSendText); void attachToReactionButton(not_null button); + void setReactionIconWidget(Ui::RpWidget *widget); using AttachStripResult = HistoryView::Reactions::AttachSelectorResult; [[nodiscard]] AttachStripResult attachToMenu( @@ -110,7 +126,7 @@ private: bool _replyFocused = false; bool _hasSendText = false; - Ui::RpWidget *_likeButton = nullptr; + Ui::RpWidget *_likeIconWidget = nullptr; rpl::variable _liked; base::has_weak_ptr _likeIconGuard; std::unique_ptr _likeIcon; diff --git a/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp b/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp index 565cf5995..e2be66e08 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/group_call_userpics.h" #include "ui/controls/who_reacted_context_action.h" #include "ui/layers/box_content.h" +#include "ui/widgets/buttons.h" #include "ui/widgets/popup_menu.h" #include "ui/painter.h" #include "ui/rp_widget.h" @@ -128,7 +129,24 @@ RecentViews::RecentViews(not_null controller) RecentViews::~RecentViews() = default; -void RecentViews::show(RecentViewsData data) { +void RecentViews::show( + RecentViewsData data, + rpl::producer likedValue) { + const auto guard = gsl::finally([&] { + if (_likeIcon && likedValue) { + std::move( + likedValue + ) | rpl::map([](const Data::ReactionId &id) { + return !id.empty(); + }) | rpl::start_with_next([=](bool liked) { + const auto icon = liked + ? &st::storiesComposeControls.liked + : &st::storiesLikesIcon; + _likeIcon->setIconOverride(icon, icon); + }, _likeIcon->lifetime()); + } + }); + if (_data == data) { return; } @@ -137,27 +155,49 @@ void RecentViews::show(RecentViewsData data) { || (_data.reactions != data.reactions); const auto usersChanged = !_userpics || (_data.list != data.list); _data = data; - if (!_data.valid) { + if (!_data.self) { _text = {}; _clickHandlerLifetime.destroy(); _userpicsLifetime.destroy(); _userpics = nullptr; _widget = nullptr; - return; + } else { + if (!_widget) { + setupWidget(); + } + if (!_userpics) { + setupUserpics(); + } + if (countersChanged) { + updateText(); + } + if (usersChanged) { + updateUserpics(); + } + refreshClickHandler(); } - if (!_widget) { - setupWidget(); + + if (!_data.channel) { + _likeIcon = nullptr; + _likeWrap = nullptr; + _viewsWrap = nullptr; + } else { + _viewsCounter = Lang::FormatCountDecimal(std::max(_data.total, 1)); + _likesCounter = _data.reactions + ? Lang::FormatCountDecimal(_data.reactions) + : QString(); + if (!_likeWrap || !_likeIcon || !_viewsWrap) { + setupViewsReactions(); + } } - if (!_userpics) { - setupUserpics(); - } - if (countersChanged) { - updateText(); - } - if (usersChanged) { - updateUserpics(); - } - refreshClickHandler(); +} + +Ui::RpWidget *RecentViews::likeButton() const { + return _likeWrap.get(); +} + +Ui::RpWidget *RecentViews::likeIconWidget() const { + return _likeIcon.get(); } void RecentViews::refreshClickHandler() { @@ -236,6 +276,78 @@ void RecentViews::setupWidget() { }, raw->lifetime()); } +void RecentViews::setupViewsReactions() { + _viewsWrap = std::make_unique(_controller->wrap()); + _likeWrap = std::make_unique(_controller->wrap()); + _likeIcon = std::make_unique( + _likeWrap.get(), + st::storiesComposeControls.like); + _likeIcon->setAttribute(Qt::WA_TransparentForMouseEvents); + + _controller->layoutValue( + ) | rpl::start_with_next([=](const Layout &layout) { + _outer = QRect( + layout.content.x(), + layout.views.y(), + layout.content.width(), + layout.views.height()); + updateViewsReactionsGeometry(); + }, _likeWrap->lifetime()); + + const auto views = Ui::CreateChild( + _viewsWrap.get(), + _viewsCounter.value(), + st::storiesViewsText); + views->show(); + views->setAttribute(Qt::WA_TransparentForMouseEvents); + views->move(st::storiesViewsTextPosition); + + views->widthValue( + ) | rpl::start_with_next([=](int width) { + _viewsWrap->resize(views->x() + width, _likeIcon->height()); + updateViewsReactionsGeometry(); + }, _viewsWrap->lifetime()); + _viewsWrap->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(_viewsWrap.get()); + const auto &icon = st::storiesViewsIcon; + const auto top = (_viewsWrap->height() - icon.height()) / 2; + icon.paint(p, 0, top, _viewsWrap->width()); + }, _viewsWrap->lifetime()); + + _likeIcon->move(0, 0); + const auto likes = Ui::CreateChild( + _likeWrap.get(), + _likesCounter.value(), + st::storiesLikesText); + likes->show(); + likes->setAttribute(Qt::WA_TransparentForMouseEvents); + likes->move(st::storiesLikesTextPosition); + + likes->widthValue( + ) | rpl::start_with_next([=](int width) { + width += width + ? st::storiesLikesTextRightSkip + : st::storiesLikesEmptyRightSkip; + _likeWrap->resize(likes->x() + width, _likeIcon->height()); + updateViewsReactionsGeometry(); + }, _likeWrap->lifetime()); + + _viewsWrap->show(); + _likeIcon->show(); + _likeWrap->show(); + + _likeWrap->setClickedCallback([=] { + _controller->toggleLiked(); + }); +} + +void RecentViews::updateViewsReactionsGeometry() { + _viewsWrap->move(_outer.topLeft() + st::storiesViewsPosition); + _likeWrap->move(_outer.topLeft() + + QPoint(_outer.width() - _likeWrap->width(), 0) + + st::storiesLikesPosition); +} + void RecentViews::updatePartsGeometry() { const auto skip = st::storiesRecentViewsSkip; const auto full = _userpicsWidth + skip + _text.maxWidth(); diff --git a/Telegram/SourceFiles/media/stories/media_stories_recent_views.h b/Telegram/SourceFiles/media/stories/media_stories_recent_views.h index d469cbb5e..1e8f226fb 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_recent_views.h +++ b/Telegram/SourceFiles/media/stories/media_stories_recent_views.h @@ -13,9 +13,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Data { struct StoryView; +struct ReactionId; } // namespace Data namespace Ui { +class AbstractButton; +class IconButton; class RpWidget; class GroupCallUserpics; class PopupMenu; @@ -34,7 +37,8 @@ struct RecentViewsData { std::vector> list; int reactions = 0; int total = 0; - bool valid = false; + bool self = false; + bool channel = false; friend inline auto operator<=>( const RecentViewsData &, @@ -49,7 +53,12 @@ public: explicit RecentViews(not_null controller); ~RecentViews(); - void show(RecentViewsData data); + void show( + RecentViewsData data, + rpl::producer likedValue = nullptr); + + [[nodiscard]] Ui::RpWidget *likeButton() const; + [[nodiscard]] Ui::RpWidget *likeIconWidget() const; private: struct MenuEntry { @@ -69,6 +78,9 @@ private: void updatePartsGeometry(); void showMenu(); + void setupViewsReactions(); + void updateViewsReactionsGeometry(); + void addMenuRow(Data::StoryView entry, const QDateTime &now); void addMenuRowPlaceholder(not_null session); void rebuildMenuTail(); @@ -83,6 +95,12 @@ private: RecentViewsData _data; rpl::lifetime _userpicsLifetime; + rpl::variable _viewsCounter; + rpl::variable _likesCounter; + std::unique_ptr _viewsWrap; + std::unique_ptr _likeWrap; + std::unique_ptr _likeIcon; + base::unique_qptr _menu; rpl::lifetime _menuShortLifetime; std::vector _menuEntries; diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp index 48ee62d2e..9296a8b63 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp @@ -171,13 +171,13 @@ void ReplyArea::initGeometry() { } void ReplyArea::sendReaction(const Data::ReactionId &id) { - Expects(_data.user != nullptr); + Expects(_data.peer != nullptr); auto message = Api::MessageToSend(prepareSendAction({})); if (const auto emoji = id.emoji(); !emoji.isEmpty()) { message.textWithTags = { emoji }; } else if (const auto customId = id.custom()) { - const auto document = _data.user->owner().document(customId); + const auto document = _data.peer->owner().document(customId); if (const auto sticker = document->sticker()) { const auto text = sticker->alt; const auto id = Data::SerializeCustomEmojiId(customId); @@ -207,7 +207,7 @@ void ReplyArea::send( Api::SendOptions options, bool skipToast) { const auto error = GetErrorTextForSending( - _data.user, + _data.peer, { .topicRootId = MsgId(0), .text = &message.textWithTags, @@ -239,11 +239,11 @@ bool ReplyArea::sendExistingDocument( not_null document, Api::SendOptions options, std::optional localId) { - Expects(_data.user != nullptr); + Expects(_data.peer != nullptr); const auto show = _controller->uiShow(); const auto error = Data::RestrictionError( - _data.user, + _data.peer, ChatRestriction::SendStickers); if (error) { show->showToast(*error); @@ -269,11 +269,11 @@ void ReplyArea::sendExistingPhoto(not_null photo) { bool ReplyArea::sendExistingPhoto( not_null photo, Api::SendOptions options) { - Expects(_data.user != nullptr); + Expects(_data.peer != nullptr); const auto show = _controller->uiShow(); const auto error = Data::RestrictionError( - _data.user, + _data.peer, ChatRestriction::SendPhotos); if (error) { show->showToast(*error); @@ -348,7 +348,7 @@ bool ReplyArea::showSendingFilesError( const Ui::PreparedList &list, std::optional compress) const { const auto text = [&] { - const auto peer = _data.user; + const auto peer = _data.peer; const auto error = Data::FileRestrictionError(peer, list, compress); if (error) { return *error; @@ -383,29 +383,29 @@ bool ReplyArea::showSendingFilesError( } not_null ReplyArea::history() const { - Expects(_data.user != nullptr); + Expects(_data.peer != nullptr); - return _data.user->owner().history(_data.user); + return _data.peer->owner().history(_data.peer); } Api::SendAction ReplyArea::prepareSendAction( Api::SendOptions options) const { - Expects(_data.user != nullptr); + Expects(_data.peer != nullptr); auto result = Api::SendAction(history(), options); result.options.sendAs = _controls->sendAsPeer(); - result.replyTo.storyId = { .peer = _data.user->id, .story = _data.id }; + result.replyTo.storyId = { .peer = _data.peer->id, .story = _data.id }; return result; } void ReplyArea::chooseAttach( std::optional overrideSendImagesAsPhotos) { _chooseAttachRequest = false; - if (!_data.user) { + if (!_data.peer) { return; } - const auto user = not_null(_data.user); - if (const auto error = Data::AnyFileRestrictionError(user)) { + const auto peer = not_null(_data.peer); + if (const auto error = Data::AnyFileRestrictionError(peer)) { _controller->uiShow()->showToast(*error); return; } @@ -413,7 +413,7 @@ void ReplyArea::chooseAttach( const auto filter = (overrideSendImagesAsPhotos == true) ? FileDialog::ImagesOrAllFilter() : FileDialog::AllOrImagesFilter(); - const auto weak = make_weak(&_shownUserGuard); + const auto weak = make_weak(&_shownPeerGuard); const auto callback = [=](FileDialog::OpenResult &&result) { const auto guard = gsl::finally([&] { _choosingAttach = false; @@ -504,8 +504,8 @@ bool ReplyArea::confirmSendingFiles( .show = show, .list = std::move(list), .caption = _controls->getTextWithAppliedMarkdown(), - .limits = DefaultLimitsForPeer(_data.user), - .check = DefaultCheckForPeer(show, _data.user), + .limits = DefaultLimitsForPeer(_data.peer), + .check = DefaultCheckForPeer(show, _data.peer), .sendType = Api::SendType::Normal, .sendMenuType = SendMenu::Type::SilentOnly, .stOverride = &st::storiesComposeControls, @@ -533,7 +533,7 @@ void ReplyArea::sendingFilesConfirmed( auto groups = DivideByGroups( std::move(list), way, - _data.user->slowmodeApplied()); + _data.peer->slowmodeApplied()); const auto type = way.sendImagesAsPhotos() ? SendMediaType::Photo : SendMediaType::File; @@ -655,17 +655,17 @@ void ReplyArea::show( if (_data == data) { return; } - const auto userChanged = (_data.user != data.user); + const auto peerChanged = (_data.peer != data.peer); _data = data; - if (!userChanged) { - if (_data.user) { + if (!peerChanged) { + if (_data.peer) { _controls->clear(); } return; } - invalidate_weak_ptrs(&_shownUserGuard); - const auto user = data.user; - const auto history = user ? user->owner().history(user).get() : nullptr; + invalidate_weak_ptrs(&_shownPeerGuard); + const auto peer = data.peer; + const auto history = peer ? peer->owner().history(peer).get() : nullptr; _controls->setHistory({ .history = history, .liked = std::move( @@ -675,8 +675,8 @@ void ReplyArea::show( }), }); _controls->clear(); - const auto hidden = user && user->isSelf(); - const auto cant = !user || user->isServiceUser(); + const auto hidden = peer && (!peer->isUser() || peer->isSelf()); + const auto cant = !peer || peer->isServiceUser(); if (!hidden && !cant) { _controls->show(); } else { @@ -698,9 +698,9 @@ void ReplyArea::show( } Main::Session &ReplyArea::session() const { - Expects(_data.user != nullptr); + Expects(_data.peer != nullptr); - return _data.user->session(); + return _data.peer->session(); } bool ReplyArea::focused() const { diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.h b/Telegram/SourceFiles/media/stories/media_stories_reply.h index 34f40b856..bb5a58e70 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.h +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.h @@ -49,7 +49,7 @@ namespace Media::Stories { class Controller; struct ReplyAreaData { - UserData *user = nullptr; + PeerData *peer = nullptr; StoryId id = 0; friend inline auto operator<=>(ReplyAreaData, ReplyAreaData) = default; @@ -148,7 +148,7 @@ private: std::unique_ptr _cant; ReplyAreaData _data; - base::has_weak_ptr _shownUserGuard; + base::has_weak_ptr _shownPeerGuard; bool _chooseAttachRequest = false; rpl::variable _choosingAttach; diff --git a/Telegram/SourceFiles/media/stories/media_stories_share.cpp b/Telegram/SourceFiles/media/stories/media_stories_share.cpp index 413fbf3d1..91fc29002 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_share.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_share.cpp @@ -15,10 +15,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_chat_participant_status.h" #include "data/data_forum_topic.h" #include "data/data_histories.h" +#include "data/data_peer.h" #include "data/data_session.h" #include "data/data_stories.h" #include "data/data_thread.h" -#include "data/data_user.h" #include "history/history.h" #include "history/history_item_helpers.h" // GetErrorTextForSending. #include "history/view/history_view_context_menu.h" // CopyStoryLink. @@ -79,9 +79,7 @@ namespace Media::Stories { if (!story) { return; } - const auto user = story->peer()->asUser(); - Assert(user != nullptr); - + const auto peer = story->peer(); const auto error = [&] { for (const auto thread : result) { const auto error = GetErrorTextForSending( @@ -115,14 +113,16 @@ namespace Media::Stories { message.action.clearDraft = false; api->sendMessage(std::move(message)); } - const auto peer = thread->peer(); + const auto threadPeer = thread->peer(); const auto threadHistory = thread->owningHistory(); const auto randomId = base::RandomValue(); auto sendFlags = MTPmessages_SendMedia::Flags(0); if (action.replyTo) { sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to; } - const auto silentPost = ShouldSendSilent(peer, action.options); + const auto silentPost = ShouldSendSilent( + threadPeer, + action.options); if (silentPost) { sendFlags |= MTPmessages_SendMedia::Flag::f_silent; } @@ -140,23 +140,23 @@ namespace Media::Stories { randomId, Data::Histories::PrepareMessage( MTP_flags(sendFlags), - peer->input, + threadPeer->input, Data::Histories::ReplyToPlaceholder(), - MTP_inputMediaStory( - user->inputUser, - MTP_int(id.story)), + MTP_inputMediaStory(peer->input, MTP_int(id.story)), MTPstring(), MTP_long(randomId), MTPReplyMarkup(), MTPVector(), MTP_int(action.options.scheduled), MTP_inputPeerEmpty() - ), [=](const MTPUpdates &result, const MTP::Response &response) { - done(); - }, [=](const MTP::Error &error, const MTP::Response &response) { - api->sendMessageFail(error, peer, randomId); - done(); - }); + ), [=]( + const MTPUpdates &result, + const MTP::Response &response) { + done(); + }, [=](const MTP::Error &error, const MTP::Response &response) { + api->sendMessageFail(error, threadPeer, randomId); + done(); + }); ++state->requests; } }; diff --git a/Telegram/SourceFiles/media/stories/media_stories_sibling.cpp b/Telegram/SourceFiles/media/stories/media_stories_sibling.cpp index 791c8235f..f314a255c 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_sibling.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_sibling.cpp @@ -11,10 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_document.h" #include "data/data_document_media.h" #include "data/data_file_origin.h" +#include "data/data_peer.h" #include "data/data_photo.h" #include "data/data_photo_media.h" #include "data/data_session.h" -#include "data/data_user.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "media/stories/media_stories_controller.h" @@ -244,8 +244,8 @@ Sibling::Sibling( const Data::StoriesSource &source, StoryId suggestedId) : _controller(controller) -, _id{ source.user->id, LookupShownId(source, suggestedId) } -, _peer(source.user) { +, _id{ source.peer->id, LookupShownId(source, suggestedId) } +, _peer(source.peer) { checkStory(); _goodShown.stop(); } @@ -305,7 +305,7 @@ bool Sibling::shows( const Data::StoriesSource &source, StoryId suggestedId) const { const auto fullId = FullStoryId{ - source.user->id, + source.peer->id, LookupShownId(source, suggestedId), }; return (_id == fullId); diff --git a/Telegram/SourceFiles/media/stories/media_stories_view.cpp b/Telegram/SourceFiles/media/stories/media_stories_view.cpp index 41390c3c4..0347a5e6e 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_view.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_view.cpp @@ -59,8 +59,8 @@ void View::updatePlayback(const Player::TrackState &state) { _controller->updateVideoPlayback(state); } -ClickHandlerPtr View::lookupLocationHandler(QPoint point) const { - return _controller->lookupLocationHandler(point); +ClickHandlerPtr View::lookupAreaHandler(QPoint point) const { + return _controller->lookupAreaHandler(point); } bool View::subjumpAvailable(int delta) const { diff --git a/Telegram/SourceFiles/media/stories/media_stories_view.h b/Telegram/SourceFiles/media/stories/media_stories_view.h index 730488253..081536299 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_view.h +++ b/Telegram/SourceFiles/media/stories/media_stories_view.h @@ -81,7 +81,7 @@ public: void showFullCaption(); void updatePlayback(const Player::TrackState &state); - [[nodiscard]] ClickHandlerPtr lookupLocationHandler(QPoint point) const; + [[nodiscard]] ClickHandlerPtr lookupAreaHandler(QPoint point) const; [[nodiscard]] bool subjumpAvailable(int delta) const; [[nodiscard]] bool subjumpFor(int delta) const; diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_reader.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_reader.cpp index aa5841c53..95654e590 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_reader.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_reader.cpp @@ -903,6 +903,10 @@ void Reader::stopStreaming(bool stillActive) { _stopStreamingAsync = false; _waiting.store(nullptr, std::memory_order_release); + if (_cacheHelper && _cacheHelper->waiting != nullptr) { + QMutexLocker lock(&_cacheHelper->mutex); + _cacheHelper->waiting.store(nullptr, std::memory_order_release); + } if (!stillActive) { _streamingActive = false; refreshLoaderPriority(); @@ -1379,10 +1383,6 @@ void Reader::finalizeCache() { return; } Assert(_cache != nullptr); - if (_cacheHelper->waiting != nullptr) { - QMutexLocker lock(&_cacheHelper->mutex); - _cacheHelper->waiting.store(nullptr, std::memory_order_release); - } auto toCache = _slices.unloadToCache(); while (toCache.number >= 0) { putToCache(std::move(toCache)); diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style index 67e684764..bafcd0574 100644 --- a/Telegram/SourceFiles/media/view/media_view.style +++ b/Telegram/SourceFiles/media/view/media_view.style @@ -991,3 +991,26 @@ storiesStealthBoxBottom: 11px; storiesStealthToast: Toast(defaultMultilineToast) { maxWidth: 340px; } + +storiesViewsPosition: point(4px, 29px); +storiesViewsIcon: icon{{ "mediaview/views", storiesComposeGrayText }}; +storiesViewsText: FlatLabel(defaultFlatLabel) { + textFg: storiesComposeGrayText; +} +storiesViewsTextPosition: point(26px, 14px); + +storiesLikesPosition: point(0px, 29px); +storiesLikesIcon: icon {{ "chat/input_like", storiesComposeWhiteText }}; +storiesLikesText: FlatLabel(defaultFlatLabel) { + textFg: storiesComposeWhiteText; + style: semiboldTextStyle; +} +storiesLikesTextPosition: point(41px, 14px); +storiesLikesTextRightSkip: 8px; +storiesLikesEmptyRightSkip: 2px; + +storiesLikeCountStyle: TextStyle(defaultTextStyle) { + font: font(32px semibold); + linkFont: font(32px semibold); + linkFontOver: font(32px semibold underline); +} diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 8062ccc9f..9d32c1af7 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -3932,10 +3932,12 @@ void OverlayWidget::initThemePreview() { _themePreviewId = 0; _themePreview = std::move(result); if (_themePreview) { + using TextTransform = Ui::RoundButton::TextTransform; _themeApply.create( _body, tr::lng_theme_preview_apply(), st::themePreviewApplyButton); + _themeApply->setTextTransform(TextTransform::NoTransform); _themeApply->show(); _themeApply->setClickedCallback([=] { const auto &object = Background()->themeObject(); @@ -3952,6 +3954,7 @@ void OverlayWidget::initThemePreview() { _body, tr::lng_cancel(), st::themePreviewCancelButton); + _themeCancel->setTextTransform(TextTransform::NoTransform); _themeCancel->show(); _themeCancel->setClickedCallback([this] { close(); }); if (const auto slug = _themeCloudData.slug; !slug.isEmpty()) { @@ -3959,6 +3962,7 @@ void OverlayWidget::initThemePreview() { _body, tr::lng_theme_share(), st::themePreviewCancelButton); + _themeShare->setTextTransform(TextTransform::NoTransform); _themeShare->show(); _themeShare->setClickedCallback([=] { QGuiApplication::clipboard()->setText( @@ -5544,10 +5548,15 @@ bool OverlayWidget::handleDoubleClick( Qt::MouseButton button) { updateOver(position); - if (_over != Over::Video || !_streamed || button != Qt::LeftButton) { + if (_over != Over::Video || button != Qt::LeftButton) { return false; } else if (_stories) { + if (ClickHandler::getActive()) { + return false; + } toggleFullScreen(_windowed); + } else if (!_streamed) { + return false; } else { playbackToggleFullScreen(); playbackPauseResume(); @@ -5692,7 +5701,7 @@ void OverlayWidget::updateOver(QPoint pos) { lnk = _groupThumbs->getState(point); lnkhost = this; } else if (_stories) { - lnk = _stories->lookupLocationHandler(pos); + lnk = _stories->lookupAreaHandler(pos); lnkhost = this; } diff --git a/Telegram/SourceFiles/media/view/media_view_playback_controls.h b/Telegram/SourceFiles/media/view/media_view_playback_controls.h index 5f79cba52..ac5e15baa 100644 --- a/Telegram/SourceFiles/media/view/media_view_playback_controls.h +++ b/Telegram/SourceFiles/media/view/media_view_playback_controls.h @@ -9,8 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/rp_widget.h" #include "base/object_ptr.h" -#include "base/unique_qptr.h" -#include "styles/style_widgets.h" namespace Ui { class LabelSimple; diff --git a/Telegram/SourceFiles/menu/menu_item_download_files.cpp b/Telegram/SourceFiles/menu/menu_item_download_files.cpp index 3c4573e80..23f6c2525 100644 --- a/Telegram/SourceFiles/menu/menu_item_download_files.cpp +++ b/Telegram/SourceFiles/menu/menu_item_download_files.cpp @@ -34,9 +34,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Menu { namespace { -using DocumentViewPtr = std::shared_ptr; -using Documents = std::vector>; -using Photos = std::vector>; +using Documents = std::vector, FullMsgId>>; +using Photos = std::vector, FullMsgId>>; [[nodiscard]] bool Added( HistoryItem *item, @@ -45,19 +44,11 @@ using Photos = std::vector>; if (item && !item->forbidsForward()) { if (const auto media = item->media()) { if (const auto photo = media->photo()) { - if (const auto view = photo->activeMediaView()) { - if (view->loaded()) { - photos.push_back(view); - return true; - } - } + photos.emplace_back(photo, item->fullId()); + return true; } else if (const auto document = media->document()) { - if (const auto view = document->activeMediaView()) { - if (!view->loaded()) { - documents.emplace_back(view, item->fullId()); - return true; - } - } + documents.emplace_back(document, item->fullId()); + return true; } } } @@ -76,7 +67,7 @@ void AddAction( const auto icon = documents.empty() ? &st::menuIconSaveImage : &st::menuIconDownload; - const auto showToast = documents.empty(); + const auto shouldShowToast = documents.empty(); const auto weak = base::make_weak(controller); const auto saveImages = [=](const QString &folderPath) { @@ -96,44 +87,79 @@ void AddAction( if (path.isEmpty()) { return; } - QDir().mkpath(path); - const auto fullPath = [&](int i) { - return filedialogDefaultName( - u"photo_"_q + QString::number(i), - u".jpg"_q, - path); - }; - auto lastPath = QString(); - for (auto i = 0; i < photos.size(); i++) { - lastPath = fullPath(i + 1); - photos[i]->saveToFile(lastPath); + + const auto showToast = !shouldShowToast + ? Fn(nullptr) + : [=](const QString &lastPath) { + const auto filter = [lastPath](const auto ...) { + File::ShowInFolder(lastPath); + return false; + }; + controller->showToast({ + .text = (photos.size() > 1 + ? tr::lng_mediaview_saved_images_to + : tr::lng_mediaview_saved_to)( + tr::now, + lt_downloads, + Ui::Text::Link( + tr::lng_mediaview_downloads(tr::now), + "internal:show_saved_message"), + Ui::Text::WithEntities), + .st = &st::defaultToast, + .filter = filter, + }); + }; + + auto views = std::vector>(); + for (const auto &[photo, fullId] : photos) { + if (const auto view = photo->createMediaView()) { + view->wanted(Data::PhotoSize::Large, fullId); + views.push_back(view); + } } - if (showToast) { - const auto filter = [lastPath](const auto ...) { - File::ShowInFolder(lastPath); - return false; + const auto finalCheck = [=] { + for (const auto &[photo, _] : photos) { + if (photo->loading()) { + return false; + } + } + return true; + }; + + const auto saveToFiles = [=] { + const auto fullPath = [&](int i) { + return filedialogDefaultName( + u"photo_"_q + QString::number(i), + u".jpg"_q, + path); }; - controller->showToast({ - .text = (photos.size() > 1 - ? tr::lng_mediaview_saved_images_to - : tr::lng_mediaview_saved_to)( - tr::now, - lt_downloads, - Ui::Text::Link( - tr::lng_mediaview_downloads(tr::now), - "internal:show_saved_message"), - Ui::Text::WithEntities), - .st = &st::defaultToast, - .filter = filter, - }); + auto lastPath = QString(); + for (auto i = 0; i < views.size(); i++) { + lastPath = fullPath(i + 1); + views[i]->saveToFile(lastPath); + } + if (showToast) { + showToast(lastPath); + } + }; + + if (finalCheck()) { + saveToFiles(); + } else { + auto lifetime = std::make_shared(); + session->downloaderTaskFinished( + ) | rpl::start_with_next([=]() mutable { + if (finalCheck()) { + saveToFiles(); + base::take(lifetime)->destroy(); + } + }, *lifetime); } }; const auto saveDocuments = [=](const QString &folderPath) { - for (const auto &pair : documents) { - const auto &document = pair.first->owner(); - const auto &origin = pair.second; + for (const auto &[document, origin] : documents) { if (!folderPath.isEmpty()) { document->save(origin, folderPath + document->filename()); } else { diff --git a/Telegram/SourceFiles/mtproto/details/mtproto_dc_key_creator.h b/Telegram/SourceFiles/mtproto/details/mtproto_dc_key_creator.h index f62bb8f41..744a18ace 100644 --- a/Telegram/SourceFiles/mtproto/details/mtproto_dc_key_creator.h +++ b/Telegram/SourceFiles/mtproto/details/mtproto_dc_key_creator.h @@ -69,7 +69,7 @@ private: MTPint128 nonce, server_nonce; // 32 bytes new_nonce + 1 check byte + 8 bytes of auth_key_aux_hash. - bytes::array<41> new_nonce_buf; + bytes::array<41> new_nonce_buf{}; MTPint256 &new_nonce; MTPlong &auth_key_aux_hash; diff --git a/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp b/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp index f09fe11f9..b07d2c5e5 100644 --- a/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp +++ b/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp @@ -65,7 +65,7 @@ QByteArray DnsUserAgent() { static const auto kResult = QByteArray( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/115.0.5790.102 Safari/537.36"); + "Chrome/116.0.5845.96 Safari/537.36"); return kResult; } diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index bced7ee92..93ee43704 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -42,7 +42,7 @@ inputMediaInvoice#8eb5a6d5 flags:# title:string description:string photo:flags.0 inputMediaGeoLive#971fa843 flags:# stopped:flags.0?true geo_point:InputGeoPoint heading:flags.2?int period:flags.1?int proximity_notification_radius:flags.3?int = InputMedia; inputMediaPoll#f94e5f1 flags:# poll:Poll correct_answers:flags.0?Vector solution:flags.1?string solution_entities:flags.1?Vector = InputMedia; inputMediaDice#e66fbf7b emoticon:string = InputMedia; -inputMediaStory#9a86b58f user_id:InputUser id:int = InputMedia; +inputMediaStory#89fdd778 peer:InputPeer id:int = InputMedia; inputChatPhotoEmpty#1ca48f57 = InputChatPhoto; inputChatUploadedPhoto#bdcdaec0 flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double video_emoji_markup:flags.3?VideoSize = InputChatPhoto; @@ -96,11 +96,11 @@ userStatusLastMonth#77ebc742 = UserStatus; chatEmpty#29562865 id:long = Chat; chat#41cbf256 flags:# creator:flags.0?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true noforwards:flags.25?true id:long title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat; chatForbidden#6592a1a7 id:long title:string = Chat; -channel#83259464 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true join_to_send:flags.28?true join_request:flags.29?true forum:flags.30?true flags2:# id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int usernames:flags2.0?Vector = Chat; +channel#94f592db flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true join_to_send:flags.28?true join_request:flags.29?true forum:flags.30?true flags2:# stories_hidden:flags2.1?true stories_hidden_min:flags2.2?true stories_unavailable:flags2.3?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int usernames:flags2.0?Vector stories_max_id:flags2.4?int = Chat; channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat; chatFull#c9d31138 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true translations_disabled:flags.19?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector available_reactions:flags.18?ChatReactions = ChatFull; -channelFull#f2355507 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions = ChatFull; +channelFull#723027bd flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions stories:flags2.4?PeerStories = ChatFull; chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant; chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant; @@ -129,7 +129,7 @@ messageMediaInvoice#f6a548d3 flags:# shipping_address_requested:flags.1?true tes messageMediaGeoLive#b940c666 flags:# geo:GeoPoint heading:flags.0?int period:int proximity_notification_radius:flags.1?int = MessageMedia; messageMediaPoll#4bd6e798 poll:Poll results:PollResults = MessageMedia; messageMediaDice#3f7ee58b value:int emoticon:string = MessageMedia; -messageMediaStory#cbb20d88 flags:# via_mention:flags.1?true user_id:long id:int story:flags.0?StoryItem = MessageMedia; +messageMediaStory#68cb6283 flags:# via_mention:flags.1?true peer:Peer id:int story:flags.0?StoryItem = MessageMedia; messageActionEmpty#b6aef7b0 = MessageAction; messageActionChatCreate#bd47cbad title:string users:Vector = MessageAction; @@ -150,7 +150,7 @@ messageActionPaymentSent#96163f56 flags:# recurring_init:flags.2?true recurring_ messageActionPhoneCall#80e11a7f flags:# video:flags.2?true call_id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = MessageAction; messageActionScreenshotTaken#4792929b = MessageAction; messageActionCustomAction#fae69f56 message:string = MessageAction; -messageActionBotAllowed#c516d679 flags:# attach_menu:flags.1?true domain:flags.0?string app:flags.2?BotApp = MessageAction; +messageActionBotAllowed#c516d679 flags:# attach_menu:flags.1?true from_request:flags.3?true domain:flags.0?string app:flags.2?BotApp = MessageAction; messageActionSecureValuesSentMe#1b287353 values:Vector credentials:SecureCredentialsEncrypted = MessageAction; messageActionSecureValuesSent#d95c6154 types:Vector = MessageAction; messageActionContactSignUp#f3f25f76 = MessageAction; @@ -221,7 +221,7 @@ inputReportReasonFake#f5ddd6e7 = ReportReason; inputReportReasonIllegalDrugs#a8eb2be = ReportReason; inputReportReasonPersonalDetails#9ec7863d = ReportReason; -userFull#4fe1cc86 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true translations_disabled:flags.23?true stories_pinned_available:flags.26?true blocked_my_stories_from:flags.27?true id:long about:flags.1?string settings:PeerSettings personal_photo:flags.21?Photo profile_photo:flags.2?Photo fallback_photo:flags.22?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights premium_gifts:flags.19?Vector wallpaper:flags.24?WallPaper stories:flags.25?UserStories = UserFull; +userFull#b9b12c6c flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true translations_disabled:flags.23?true stories_pinned_available:flags.26?true blocked_my_stories_from:flags.27?true id:long about:flags.1?string settings:PeerSettings personal_photo:flags.21?Photo profile_photo:flags.2?Photo fallback_photo:flags.22?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights premium_gifts:flags.19?Vector wallpaper:flags.24?WallPaper stories:flags.25?PeerStories = UserFull; contact#145ade0b user_id:long mutual:Bool = Contact; @@ -279,6 +279,7 @@ updateChatUserTyping#83487af0 chat_id:long from_id:Peer action:SendMessageAction updateChatParticipants#7761198 participants:ChatParticipants = Update; updateUserStatus#e5bdf8de user_id:long status:UserStatus = Update; updateUserName#a7848924 user_id:long first_name:string last_name:string usernames:Vector = Update; +updateNewAuthorization#8951abef flags:# unconfirmed:flags.0?true hash:long date:flags.0?int device:flags.0?string location:flags.0?string = Update; updateNewEncryptedMessage#12bcbd9a message:EncryptedMessage qts:int = Update; updateEncryptedChatTyping#1710f156 chat_id:int = Update; updateEncryption#b4a2e88d chat:EncryptedChat date:int = Update; @@ -293,7 +294,7 @@ updateUserPhone#5492a13 user_id:long phone:string = Update; updateReadHistoryInbox#9c974fdf flags:# folder_id:flags.0?int peer:Peer max_id:int still_unread_count:int pts:int pts_count:int = Update; updateReadHistoryOutbox#2f2f21bf peer:Peer max_id:int pts:int pts_count:int = Update; updateWebPage#7f891213 webpage:WebPage pts:int pts_count:int = Update; -updateReadMessagesContents#68c13933 messages:Vector pts:int pts_count:int = Update; +updateReadMessagesContents#f8227181 flags:# messages:Vector pts:int pts_count:int date:flags.0?int = Update; updateChannelTooLong#108d941f flags:# channel_id:long pts:flags.0?int = Update; updateChannel#635b4c09 channel_id:long = Update; updateNewChannelMessage#62ba04d9 message:Message pts:int pts_count:int = Update; @@ -382,11 +383,11 @@ updateChannelPinnedTopics#fe198602 flags:# channel_id:long order:flags.0?Vector< updateUser#20529438 user_id:long = Update; updateAutoSaveSettings#ec05b097 = Update; updateGroupInvitePrivacyForbidden#ccf08ad6 user_id:long = Update; -updateStory#205a4133 user_id:long story:StoryItem = Update; -updateReadStories#feb5345a user_id:long max_id:int = Update; +updateStory#75b3b798 peer:Peer story:StoryItem = Update; +updateReadStories#f74e932b peer:Peer max_id:int = Update; updateStoryID#1bf335b9 id:int random_id:long = Update; updateStoriesStealthMode#2c084dc1 stealth_mode:StoriesStealthMode = Update; -updateSentStoryReaction#e3a73d20 user_id:long story_id:int reaction:Reaction = Update; +updateSentStoryReaction#7d627683 peer:Peer story_id:int reaction:Reaction = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -552,7 +553,7 @@ webPagePending#c586da1c id:long date:int = WebPage; webPage#e89c45b2 flags:# id:long url:string display_url:string hash:int type:flags.0?string site_name:flags.1?string title:flags.2?string description:flags.3?string photo:flags.4?Photo embed_url:flags.5?string embed_type:flags.5?string embed_width:flags.6?int embed_height:flags.6?int duration:flags.7?int author:flags.8?string document:flags.9?Document cached_page:flags.10?Page attributes:flags.12?Vector = WebPage; webPageNotModified#7311ca11 flags:# cached_page_views:flags.0?int = WebPage; -authorization#ad01d61d flags:# current:flags.0?true official_app:flags.1?true password_pending:flags.2?true encrypted_requests_disabled:flags.3?true call_requests_disabled:flags.4?true hash:long device_model:string platform:string system_version:string api_id:int app_name:string app_version:string date_created:int date_active:int ip:string country:string region:string = Authorization; +authorization#ad01d61d flags:# current:flags.0?true official_app:flags.1?true password_pending:flags.2?true encrypted_requests_disabled:flags.3?true call_requests_disabled:flags.4?true unconfirmed:flags.5?true hash:long device_model:string platform:string system_version:string api_id:int app_name:string app_version:string date_created:int date_active:int ip:string country:string region:string = Authorization; account.authorizations#4bff8ea0 authorization_ttl_days:int authorizations:Vector = account.Authorizations; @@ -570,7 +571,7 @@ chatInviteExported#ab4a819 flags:# revoked:flags.0?true permanent:flags.5?true r chatInvitePublicJoinRequests#ed107ab7 = ExportedChatInvite; chatInviteAlready#5a686d7c chat:Chat = ChatInvite; -chatInvite#300c44c1 flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true request_needed:flags.6?true title:string about:flags.5?string photo:Photo participants_count:int participants:flags.4?Vector = ChatInvite; +chatInvite#300c44c1 flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true request_needed:flags.6?true verified:flags.7?true scam:flags.8?true fake:flags.9?true title:string about:flags.5?string photo:Photo participants_count:int participants:flags.4?Vector = ChatInvite; chatInvitePeek#61695cb0 chat:Chat expires:int = ChatInvite; inputStickerSetEmpty#ffb62b95 = InputStickerSet; @@ -842,7 +843,7 @@ dataJSON#7d748d04 data:string = DataJSON; labeledPrice#cb296bf8 label:string amount:long = LabeledPrice; -invoice#3e85a91b flags:# test:flags.0?true name_requested:flags.1?true phone_requested:flags.2?true email_requested:flags.3?true shipping_address_requested:flags.4?true flexible:flags.5?true phone_to_provider:flags.6?true email_to_provider:flags.7?true recurring:flags.9?true currency:string prices:Vector max_tip_amount:flags.8?long suggested_tip_amounts:flags.8?Vector recurring_terms_url:flags.9?string = Invoice; +invoice#5db95a15 flags:# test:flags.0?true name_requested:flags.1?true phone_requested:flags.2?true email_requested:flags.3?true shipping_address_requested:flags.4?true flexible:flags.5?true phone_to_provider:flags.6?true email_to_provider:flags.7?true recurring:flags.9?true currency:string prices:Vector max_tip_amount:flags.8?long suggested_tip_amounts:flags.8?Vector terms_url:flags.10?string = Invoice; paymentCharge#ea02c27e id:string provider_charge_id:string = PaymentCharge; @@ -1122,7 +1123,7 @@ chatOnlines#f041e250 onlines:int = ChatOnlines; statsURL#47a971e0 url:string = StatsURL; -chatAdminRights#5fb224d5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true pin_messages:flags.7?true add_admins:flags.9?true anonymous:flags.10?true manage_call:flags.11?true other:flags.12?true manage_topics:flags.13?true = ChatAdminRights; +chatAdminRights#5fb224d5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true pin_messages:flags.7?true add_admins:flags.9?true anonymous:flags.10?true manage_call:flags.11?true other:flags.12?true manage_topics:flags.13?true post_stories:flags.14?true edit_stories:flags.15?true delete_stories:flags.16?true = ChatAdminRights; chatBannedRights#9f120418 flags:# view_messages:flags.0?true send_messages:flags.1?true send_media:flags.2?true send_stickers:flags.3?true send_gifs:flags.4?true send_games:flags.5?true send_inline:flags.6?true embed_links:flags.7?true send_polls:flags.8?true change_info:flags.10?true invite_users:flags.15?true pin_messages:flags.17?true manage_topics:flags.18?true send_photos:flags.19?true send_videos:flags.20?true send_roundvideos:flags.21?true send_audios:flags.22?true send_voices:flags.23?true send_docs:flags.24?true send_plain:flags.25?true until_date:int = ChatBannedRights; @@ -1197,7 +1198,7 @@ inputThemeSettings#8fde504f flags:# message_colors_animated:flags.2?true base_th themeSettings#fa58b6d4 flags:# message_colors_animated:flags.2?true base_theme:BaseTheme accent_color:int outbox_accent_color:flags.3?int message_colors:flags.0?Vector wallpaper:flags.1?WallPaper = ThemeSettings; webPageAttributeTheme#54b56617 flags:# documents:flags.0?Vector settings:flags.1?ThemeSettings = WebPageAttribute; -webPageAttributeStory#939a4671 flags:# user_id:long id:int story:flags.0?StoryItem = WebPageAttribute; +webPageAttributeStory#2e94c3e7 flags:# peer:Peer id:int story:flags.0?StoryItem = WebPageAttribute; messages.votesList#4899484e flags:# count:int votes:Vector chats:Vector users:Vector next_offset:flags.0?string = messages.VotesList; @@ -1369,7 +1370,7 @@ attachMenuBotIconColor#4576f3f0 name:string color:int = AttachMenuBotIconColor; attachMenuBotIcon#b2a7386b flags:# name:string icon:Document colors:flags.0?Vector = AttachMenuBotIcon; -attachMenuBot#c8aa2cd2 flags:# inactive:flags.0?true has_settings:flags.1?true request_write_access:flags.2?true bot_id:long short_name:string peer_types:Vector icons:Vector = AttachMenuBot; +attachMenuBot#d90d8dfe flags:# inactive:flags.0?true has_settings:flags.1?true request_write_access:flags.2?true show_in_attach_menu:flags.3?true show_in_side_menu:flags.4?true side_menu_disclaimer_needed:flags.5?true bot_id:long short_name:string peer_types:flags.3?Vector icons:Vector = AttachMenuBot; attachMenuBotsNotModified#f1d88a5c = AttachMenuBots; attachMenuBots#3c4301c0 hash:long bots:Vector users:Vector = AttachMenuBots; @@ -1499,7 +1500,7 @@ inputBotAppShortName#908c0407 bot_id:InputUser short_name:string = InputBotApp; botAppNotModified#5da674b7 = BotApp; botApp#95fcd1d6 flags:# id:long access_hash:long short_name:string title:string description:string photo:Photo document:flags.0?Document hash:long = BotApp; -messages.botApp#eb50adf5 flags:# inactive:flags.0?true request_write_access:flags.1?true app:BotApp = messages.BotApp; +messages.botApp#eb50adf5 flags:# inactive:flags.0?true request_write_access:flags.1?true has_settings:flags.2?true app:BotApp = messages.BotApp; appWebViewResultUrl#3c1b4f0d url:string = AppWebViewResult; @@ -1528,20 +1529,16 @@ messagePeerVoteMultiple#4628f6e6 peer:Peer options:Vector date:int = Mess sponsoredWebPage#3db8ec63 flags:# url:string site_name:string photo:flags.0?Photo = SponsoredWebPage; -storyViews#c64c0b97 flags:# views_count:int reactions_count:int recent_viewers:flags.0?Vector = StoryViews; +storyViews#8d595cd6 flags:# has_viewers:flags.1?true views_count:int forwards_count:flags.2?int reactions:flags.3?Vector reactions_count:flags.4?int recent_viewers:flags.0?Vector = StoryViews; storyItemDeleted#51e6ee4f id:int = StoryItem; storyItemSkipped#ffadc913 flags:# close_friends:flags.8?true id:int date:int expire_date:int = StoryItem; -storyItem#44c457ce flags:# pinned:flags.5?true public:flags.7?true close_friends:flags.8?true min:flags.9?true noforwards:flags.10?true edited:flags.11?true contacts:flags.12?true selected_contacts:flags.13?true id:int date:int expire_date:int caption:flags.0?string entities:flags.1?Vector media:MessageMedia media_areas:flags.14?Vector privacy:flags.2?Vector views:flags.3?StoryViews sent_reaction:flags.15?Reaction = StoryItem; - -userStories#8611a200 flags:# user_id:long max_read_id:flags.0?int stories:Vector = UserStories; +storyItem#44c457ce flags:# pinned:flags.5?true public:flags.7?true close_friends:flags.8?true min:flags.9?true noforwards:flags.10?true edited:flags.11?true contacts:flags.12?true selected_contacts:flags.13?true out:flags.16?true id:int date:int expire_date:int caption:flags.0?string entities:flags.1?Vector media:MessageMedia media_areas:flags.14?Vector privacy:flags.2?Vector views:flags.3?StoryViews sent_reaction:flags.15?Reaction = StoryItem; stories.allStoriesNotModified#1158fe3e flags:# state:string stealth_mode:StoriesStealthMode = stories.AllStories; -stories.allStories#519d899e flags:# has_more:flags.0?true count:int state:string user_stories:Vector users:Vector stealth_mode:StoriesStealthMode = stories.AllStories; +stories.allStories#6efc5e81 flags:# has_more:flags.0?true count:int state:string peer_stories:Vector chats:Vector users:Vector stealth_mode:StoriesStealthMode = stories.AllStories; -stories.stories#4fe57df1 count:int stories:Vector users:Vector = stories.Stories; - -stories.userStories#37a6ff5f stories:UserStories users:Vector = stories.UserStories; +stories.stories#5dd8c3c8 count:int stories:Vector chats:Vector users:Vector = stories.Stories; storyView#b0bdeac5 flags:# blocked:flags.0?true blocked_my_stories_from:flags.1?true user_id:long date:int reaction:flags.2?Reaction = StoryView; @@ -1561,6 +1558,20 @@ mediaAreaCoordinates#3d1ea4e x:double y:double w:double h:double rotation:double mediaAreaVenue#be82db9c coordinates:MediaAreaCoordinates geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string = MediaArea; inputMediaAreaVenue#b282217f coordinates:MediaAreaCoordinates query_id:long result_id:string = MediaArea; mediaAreaGeoPoint#df8b3b22 coordinates:MediaAreaCoordinates geo:GeoPoint = MediaArea; +mediaAreaSuggestedReaction#14455871 flags:# dark:flags.0?true flipped:flags.1?true coordinates:MediaAreaCoordinates reaction:Reaction = MediaArea; + +peerStories#9a35e999 flags:# peer:Peer max_read_id:flags.0?int stories:Vector = PeerStories; + +stories.peerStories#cae68768 stories:PeerStories chats:Vector users:Vector = stories.PeerStories; + +stories.boostsStatus#66ea1fef flags:# my_boost:flags.2?true level:int current_level_boosts:int boosts:int next_level_boosts:flags.0?int premium_audience:flags.1?StatsPercentValue = stories.BoostsStatus; + +stories.canApplyBoostOk#c3173587 = stories.CanApplyBoostResult; +stories.canApplyBoostReplace#712c4655 current_boost:Peer chats:Vector = stories.CanApplyBoostResult; + +booster#e9e6380 user_id:long expires:int = Booster; + +stories.boostersList#f3dd3d1d flags:# count:int boosters:Vector next_offset:flags.0?string users:Vector = stories.BoostersList; ---functions--- @@ -1667,7 +1678,7 @@ account.resetPassword#9308ce1b = account.ResetPasswordResult; account.declinePasswordReset#4c9409f6 = Bool; account.getChatThemes#d638de89 hash:long = account.Themes; account.setAuthorizationTTL#bf899aa0 authorization_ttl_days:int = Bool; -account.changeAuthorizationSettings#40f48462 flags:# hash:long encrypted_requests_disabled:flags.0?Bool call_requests_disabled:flags.1?Bool = Bool; +account.changeAuthorizationSettings#40f48462 flags:# confirmed:flags.3?true hash:long encrypted_requests_disabled:flags.0?Bool call_requests_disabled:flags.1?Bool = Bool; account.getSavedRingtones#e1902288 hash:long = account.SavedRingtones; account.saveRingtone#3dea5b03 id:InputDocument unsave:Bool = account.SavedRingtone; account.uploadRingtone#831a83a2 file:InputFile file_name:string mime_type:string = Document; @@ -1687,7 +1698,6 @@ account.invalidateSignInCodes#ca8ae8ba codes:Vector = Bool; users.getUsers#d91a548 id:Vector = Vector; users.getFullUser#b60f5918 id:InputUser = users.UserFull; users.setSecureValueErrors#90c894b5 id:InputUser errors:Vector = Bool; -users.getStoriesMaxIDs#ca1cb9ab id:Vector = Vector; contacts.getContactIDs#7adc669d hash:long = Vector; contacts.getStatuses#c4a353ee = Vector; @@ -1713,7 +1723,6 @@ contacts.resolvePhone#8af94344 phone:string = contacts.ResolvedPeer; contacts.exportContactToken#f8654027 = ExportedContactToken; contacts.importContactToken#13005788 token:string = User; contacts.editCloseFriends#ba6705f0 id:Vector = Bool; -contacts.toggleStoriesHidden#753fb865 id:InputUser hidden:Bool = Bool; contacts.setBlocked#94c65c76 flags:# my_stories_from:flags.0?true id:Vector limit:int = Bool; messages.getMessages#63c66506 id:Vector = messages.Messages; @@ -1879,7 +1888,7 @@ messages.getAttachMenuBot#77216192 bot:InputUser = AttachMenuBotsBot; messages.toggleBotInAttachMenu#69f59d69 flags:# write_allowed:flags.0?true bot:InputUser enabled:Bool = Bool; messages.requestWebView#269dc2c1 flags:# from_bot_menu:flags.4?true silent:flags.5?true peer:InputPeer bot:InputUser url:flags.1?string start_param:flags.3?string theme_params:flags.2?DataJSON platform:string reply_to:flags.0?InputReplyTo send_as:flags.13?InputPeer = WebViewResult; messages.prolongWebView#b0d81a83 flags:# silent:flags.5?true peer:InputPeer bot:InputUser query_id:long reply_to:flags.0?InputReplyTo send_as:flags.13?InputPeer = Bool; -messages.requestSimpleWebView#299bec8e flags:# from_switch_webview:flags.1?true bot:InputUser url:string theme_params:flags.0?DataJSON platform:string = SimpleWebViewResult; +messages.requestSimpleWebView#1a46500a flags:# from_switch_webview:flags.1?true from_side_menu:flags.2?true bot:InputUser url:flags.3?string start_param:flags.4?string theme_params:flags.0?DataJSON platform:string = SimpleWebViewResult; messages.sendWebViewResultMessage#a4314f5 bot_query_id:string result:InputBotInlineResult = WebViewMessageSent; messages.sendWebViewData#dc0242c8 bot:InputUser random_id:long button_text:string data:string = Updates; messages.transcribeAudio#269e9a49 peer:InputPeer msg_id:int = messages.TranscribedAudio; @@ -2017,6 +2026,9 @@ bots.setBotInfo#10cf3123 flags:# bot:flags.2?InputUser lang_code:string name:fla bots.getBotInfo#dcd914fd flags:# bot:flags.0?InputUser lang_code:string = bots.BotInfo; bots.reorderUsernames#9709b1c2 bot:InputUser order:Vector = Bool; bots.toggleUsername#53ca973 bot:InputUser username:string active:Bool = Bool; +bots.canSendMessage#1359f4e6 bot:InputUser = Bool; +bots.allowSendMessage#f132e3ef bot:InputUser = Updates; +bots.invokeWebViewCustomMethod#87fc5e7 bot:InputUser custom_method:string params:DataJSON = DataJSON; payments.getPaymentForm#37148dbb flags:# invoice:InputInvoice theme_params:flags.0?DataJSON = payments.PaymentForm; payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt; @@ -2099,22 +2111,30 @@ chatlists.hideChatlistUpdates#66e486fb chatlist:InputChatlist = Bool; chatlists.getLeaveChatlistSuggestions#fdbcd714 chatlist:InputChatlist = Vector; chatlists.leaveChatlist#74fae13a chatlist:InputChatlist peers:Vector = Updates; -stories.sendStory#d455fcec flags:# pinned:flags.2?true noforwards:flags.4?true media:InputMedia media_areas:flags.5?Vector caption:flags.0?string entities:flags.1?Vector privacy_rules:Vector random_id:long period:flags.3?int = Updates; -stories.editStory#a9b91ae4 flags:# id:int media:flags.0?InputMedia media_areas:flags.3?Vector caption:flags.1?string entities:flags.1?Vector privacy_rules:flags.2?Vector = Updates; -stories.deleteStories#b5d501d7 id:Vector = Vector; -stories.togglePinned#51602944 id:Vector pinned:Bool = Vector; +stories.canSendStory#c7dfdfdd peer:InputPeer = Bool; +stories.sendStory#bcb73644 flags:# pinned:flags.2?true noforwards:flags.4?true peer:InputPeer media:InputMedia media_areas:flags.5?Vector caption:flags.0?string entities:flags.1?Vector privacy_rules:Vector random_id:long period:flags.3?int = Updates; +stories.editStory#b583ba46 flags:# peer:InputPeer id:int media:flags.0?InputMedia media_areas:flags.3?Vector caption:flags.1?string entities:flags.1?Vector privacy_rules:flags.2?Vector = Updates; +stories.deleteStories#ae59db5f peer:InputPeer id:Vector = Vector; +stories.togglePinned#9a75a1ef peer:InputPeer id:Vector pinned:Bool = Vector; stories.getAllStories#eeb0d625 flags:# next:flags.1?true hidden:flags.2?true state:flags.0?string = stories.AllStories; -stories.getUserStories#96d528e0 user_id:InputUser = stories.UserStories; -stories.getPinnedStories#b471137 user_id:InputUser offset_id:int limit:int = stories.Stories; -stories.getStoriesArchive#1f5bc5d2 offset_id:int limit:int = stories.Stories; -stories.getStoriesByID#6a15cf46 user_id:InputUser id:Vector = stories.Stories; +stories.getPinnedStories#5821a5dc peer:InputPeer offset_id:int limit:int = stories.Stories; +stories.getStoriesArchive#b4352016 peer:InputPeer offset_id:int limit:int = stories.Stories; +stories.getStoriesByID#5774ca74 peer:InputPeer id:Vector = stories.Stories; stories.toggleAllStoriesHidden#7c2557c4 hidden:Bool = Bool; -stories.getAllReadUserStories#729c562c = Updates; -stories.readStories#edc5105b user_id:InputUser max_id:int = Vector; -stories.incrementStoryViews#22126127 user_id:InputUser id:Vector = Bool; -stories.getStoryViewsList#f95f61a4 flags:# just_contacts:flags.0?true reactions_first:flags.2?true q:flags.1?string id:int offset:string limit:int = stories.StoryViewsList; -stories.getStoriesViews#9a75d6a6 id:Vector = stories.StoryViews; -stories.exportStoryLink#16e443ce user_id:InputUser id:int = ExportedStoryLink; -stories.report#c95be06a user_id:InputUser id:Vector reason:ReportReason message:string = Bool; +stories.readStories#a556dac8 peer:InputPeer max_id:int = Vector; +stories.incrementStoryViews#b2028afb peer:InputPeer id:Vector = Bool; +stories.getStoryViewsList#7ed23c57 flags:# just_contacts:flags.0?true reactions_first:flags.2?true peer:InputPeer q:flags.1?string id:int offset:string limit:int = stories.StoryViewsList; +stories.getStoriesViews#28e16cc8 peer:InputPeer id:Vector = stories.StoryViews; +stories.exportStoryLink#7b8def20 peer:InputPeer id:int = ExportedStoryLink; +stories.report#1923fa8c peer:InputPeer id:Vector reason:ReportReason message:string = Bool; stories.activateStealthMode#57bbd166 flags:# past:flags.0?true future:flags.1?true = Updates; -stories.sendReaction#49aaa9b3 flags:# add_to_recent:flags.0?true user_id:InputUser story_id:int reaction:Reaction = Updates; +stories.sendReaction#7fd736b2 flags:# add_to_recent:flags.0?true peer:InputPeer story_id:int reaction:Reaction = Updates; +stories.getPeerStories#2c4ada50 peer:InputPeer = stories.PeerStories; +stories.getAllReadPeerStories#9b5ae7f9 = Updates; +stories.getPeerMaxIDs#535983c3 id:Vector = Vector; +stories.getChatsToSend#a56a8b60 = messages.Chats; +stories.togglePeerStoriesHidden#bd0415c4 peer:InputPeer hidden:Bool = Bool; +stories.getBoostsStatus#4c449472 peer:InputPeer = stories.BoostsStatus; +stories.getBoostersList#337ef980 peer:InputPeer offset:string limit:int = stories.BoostersList; +stories.canApplyBoost#db05c1bd peer:InputPeer = stories.CanApplyBoostResult; +stories.applyBoost#f29d7c2b peer:InputPeer = Bool; diff --git a/Telegram/SourceFiles/mtproto/scheme/layer.tl b/Telegram/SourceFiles/mtproto/scheme/layer.tl index 2f8c6ce82..dcdebd9f5 100644 --- a/Telegram/SourceFiles/mtproto/scheme/layer.tl +++ b/Telegram/SourceFiles/mtproto/scheme/layer.tl @@ -1 +1 @@ -// LAYER 161 +// LAYER 164 diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp index 9ac811f19..ea4ebd6ec 100644 --- a/Telegram/SourceFiles/overview/overview_layout.cpp +++ b/Telegram/SourceFiles/overview/overview_layout.cpp @@ -14,14 +14,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_web_page.h" #include "data/data_media_types.h" #include "data/data_peer.h" -#include "data/data_file_origin.h" #include "data/data_photo_media.h" #include "data/data_document_media.h" #include "data/data_file_click_handler.h" -#include "styles/style_overview.h" -#include "styles/style_chat.h" -#include "core/file_utilities.h" -#include "boxes/add_contact_box.h" #include "ui/boxes/confirm_box.h" #include "lang/lang_keys.h" #include "layout/layout_selection.h" @@ -50,6 +45,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/painter.h" #include "ui/power_saving.h" #include "ui/ui_utility.h" +#include "styles/style_overview.h" +#include "styles/style_chat.h" namespace Overview { namespace Layout { diff --git a/Telegram/SourceFiles/overview/overview_layout.h b/Telegram/SourceFiles/overview/overview_layout.h index e566de4a5..d17ae7f6c 100644 --- a/Telegram/SourceFiles/overview/overview_layout.h +++ b/Telegram/SourceFiles/overview/overview_layout.h @@ -13,12 +13,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/click_handler_types.h" #include "ui/effects/animations.h" #include "ui/effects/radial_animation.h" -#include "styles/style_overview.h" class Image; namespace style { struct RoundCheckbox; +struct OverviewFileLayout; } // namespace style namespace Data { @@ -367,6 +367,7 @@ struct DocumentFields { TimeId dateOverride = 0; bool forceFileLayout = false; }; + class Document final : public RadialProgressItem { public: Document( diff --git a/Telegram/SourceFiles/passport/passport_edit_identity_box.cpp b/Telegram/SourceFiles/passport/passport_edit_identity_box.cpp index b76d5bf45..68285b9b7 100644 --- a/Telegram/SourceFiles/passport/passport_edit_identity_box.cpp +++ b/Telegram/SourceFiles/passport/passport_edit_identity_box.cpp @@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "passport/passport_edit_identity_box.h" #include "passport/passport_panel_controller.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/buttons.h" #include "ui/text_options.h" #include "lang/lang_keys.h" diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_contact.cpp b/Telegram/SourceFiles/passport/passport_panel_edit_contact.cpp index 17fc1b9d0..9e1d10851 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_contact.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_edit_contact.cpp @@ -10,7 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/file_utilities.h" #include "passport/passport_panel_controller.h" #include "passport/ui/passport_details_row.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/labels.h" #include "ui/widgets/buttons.h" #include "ui/widgets/shadow.h" @@ -195,11 +195,12 @@ void VerifyBox::setupControls( if (codeLength > 0) { _code->setAutoSubmit(codeLength, _submit); } else { - connect(_code, &Ui::SentCodeField::submitted, _submit); + _code->submits() | rpl::start_with_next(_submit, _code->lifetime()); } - connect(_code, &Ui::SentCodeField::changed, [=] { + _code->changes( + ) | rpl::start_with_next([=] { problem->hide(anim::type::normal); - }); + }, _code->lifetime()); } void VerifyBox::setInnerFocus() { diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp b/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp index f9779cab6..9f71e2e3a 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_edit_document.cpp @@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "passport/passport_panel_edit_scans.h" #include "passport/ui/passport_details_row.h" #include "ui/effects/scroll_content_shadow.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/labels.h" #include "ui/widgets/buttons.h" diff --git a/Telegram/SourceFiles/passport/passport_panel_password.cpp b/Telegram/SourceFiles/passport/passport_panel_password.cpp index 0f8e8e77a..7046f14f0 100644 --- a/Telegram/SourceFiles/passport/passport_panel_password.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_password.cpp @@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/controls/userpic_button.h" #include "ui/widgets/labels.h" #include "ui/widgets/buttons.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/password_input.h" #include "ui/wrap/vertical_layout.h" #include "ui/wrap/padding_wrap.h" #include "boxes/passcode_box.h" diff --git a/Telegram/SourceFiles/passport/ui/passport_details_row.cpp b/Telegram/SourceFiles/passport/ui/passport_details_row.cpp index c567989f0..2fc7c3698 100644 --- a/Telegram/SourceFiles/passport/ui/passport_details_row.cpp +++ b/Telegram/SourceFiles/passport/ui/passport_details_row.cpp @@ -9,7 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "base/platform/base_platform_info.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" +#include "ui/widgets/fields/masked_input_field.h" #include "ui/widgets/labels.h" #include "ui/widgets/buttons.h" #include "ui/widgets/checkbox.h" @@ -268,9 +269,16 @@ AbstractTextRow::AbstractTextRow( , _field(this, st::passportDetailsField, nullptr, value) , _value(value) { _field->setMaxLength(limit); - connect(_field, &Input::changed, [=] { - _value = valueCurrent(); - }); + if constexpr (std::is_same::value) { + _field->changes( + ) | rpl::start_with_next([=] { + _value = valueCurrent(); + }, _field->lifetime()); + } else { + connect(_field, &Input::changed, [=] { + _value = valueCurrent(); + }); + } } template diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.cpp b/Telegram/SourceFiles/payments/payments_checkout_process.cpp index 6b289c67c..937358f1f 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.cpp +++ b/Telegram/SourceFiles/payments/payments_checkout_process.cpp @@ -246,17 +246,6 @@ CheckoutProcess::CheckoutProcess( showForm(); _panel->toggleProgress(true); - style::PaletteChanged( - ) | rpl::filter([=] { - return !_themeUpdateScheduled; - }) | rpl::start_with_next([=] { - _themeUpdateScheduled = true; - crl::on_main(this, [=] { - _themeUpdateScheduled = false; - _panel->updateThemeParams(Window::Theme::WebViewParams()); - }); - }, _panel->lifetime()); - if (mode == Mode::Payment) { _session->api().cloudPassword().state( ) | rpl::start_with_next([=](const Core::CloudPasswordState &state) { @@ -540,10 +529,12 @@ void CheckoutProcess::panelSubmit() { } else if (!method.newCredentials && method.savedCredentialsIndex >= method.savedCredentials.size()) { editPaymentMethod(); - } else if (invoice.isRecurring && !_form->details().termsAccepted) { + } else if (!invoice.termsUrl.isEmpty() + && !_form->details().termsAccepted) { _panel->requestTermsAcceptance( _form->details().termsBotUsername, - invoice.recurringTermsUrl); + invoice.termsUrl, + invoice.isRecurring); } else { RegisterPaymentStart(this, { _form->invoice().cover.title }); _submitState = SubmitState::Finishing; @@ -846,4 +837,8 @@ QString CheckoutProcess::panelWebviewDataPath() { return _session->domain().local().webviewDataPath(); } +Webview::ThemeParams CheckoutProcess::panelWebviewThemeParams() { + return Window::Theme::WebViewParams(); +} + } // namespace Payments diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.h b/Telegram/SourceFiles/payments/payments_checkout_process.h index 1e37c3d28..5aa050259 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.h +++ b/Telegram/SourceFiles/payments/payments_checkout_process.h @@ -150,6 +150,7 @@ private: QVariant panelClickHandlerContext() override; QString panelWebviewDataPath() override; + Webview::ThemeParams panelWebviewThemeParams() override; std::optional panelOverrideExpireDateThreshold() override; @@ -163,7 +164,6 @@ private: bool _sendFormPending = false; bool _sendFormFailed = false; - bool _themeUpdateScheduled = false; rpl::lifetime _gettingPasswordState; rpl::lifetime _lifetime; diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index b10ee9b4a..62682b9db 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -384,8 +384,7 @@ void Form::processInvoice(const MTPDinvoice &data) { .isFlexible = data.is_flexible(), .isTest = data.is_test(), - .recurringTermsUrl = qs( - data.vrecurring_terms_url().value_or_empty()), + .termsUrl = qs(data.vterms_url().value_or_empty()), .phoneSentToProvider = data.is_phone_to_provider(), .emailSentToProvider = data.is_email_to_provider(), diff --git a/Telegram/SourceFiles/payments/ui/payments_field.cpp b/Telegram/SourceFiles/payments/ui/payments_field.cpp index 914cc1ccf..73c87dff0 100644 --- a/Telegram/SourceFiles/payments/ui/payments_field.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_field.cpp @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "payments/ui/payments_field.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/boxes/country_select_box.h" #include "ui/text/format_values.h" #include "ui/ui_utility.h" @@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_payments.h" #include +#include namespace Payments::Ui { namespace { @@ -610,7 +611,8 @@ void Field::setupValidator(Fn validator) { } else { const auto raw = _input->rawTextEdit(); QObject::connect(raw, &QTextEdit::cursorPositionChanged, save); - QObject::connect(_input, &InputField::changed, validate); + _input->changes( + ) | rpl::start_with_next(validate, _input->lifetime()); } } @@ -648,7 +650,8 @@ void Field::setupSubmit() { if (_masked) { QObject::connect(_masked, &MaskedInputField::submitted, submitted); } else { - QObject::connect(_input, &InputField::submitted, submitted); + _input->submits( + ) | rpl::start_with_next(submitted, _input->lifetime()); } } diff --git a/Telegram/SourceFiles/payments/ui/payments_panel.cpp b/Telegram/SourceFiles/payments/ui/payments_panel.cpp index 0ee46d64a..cb36c5887 100644 --- a/Telegram/SourceFiles/payments/ui/payments_panel.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_panel.cpp @@ -82,6 +82,17 @@ Panel::Panel(not_null delegate) ) | rpl::start_with_next([=] { _delegate->panelCloseSure(); }, _widget->lifetime()); + + style::PaletteChanged( + ) | rpl::filter([=] { + return !_themeUpdateScheduled; + }) | rpl::start_with_next([=] { + _themeUpdateScheduled = true; + crl::on_main(_widget.get(), [=] { + _themeUpdateScheduled = false; + updateThemeParams(_delegate->panelWebviewThemeParams()); + }); + }, lifetime()); } Panel::~Panel() { @@ -483,11 +494,13 @@ bool Panel::showWebview( const QString &url, bool allowBack, rpl::producer bottomText) { - if (!_webview && !createWebview()) { + const auto params = _delegate->panelWebviewThemeParams(); + if (!_webview && !createWebview(params)) { return false; } showWebviewProgress(); _widget->hideLayer(anim::type::instant); + updateThemeParams(params); _webview->window.navigate(url); _widget->setBackAllowed(allowBack); if (bottomText) { @@ -511,7 +524,7 @@ bool Panel::showWebview( return true; } -bool Panel::createWebview() { +bool Panel::createWebview(const Webview::ThemeParams ¶ms) { auto outer = base::make_unique_q(_widget.get()); const auto container = outer.get(); _widget->showInner(std::move(outer)); @@ -532,8 +545,10 @@ bool Panel::createWebview() { _webview = std::make_unique( container, Webview::WindowConfig{ + .opaqueBg = params.opaqueBg, .userDataPath = _delegate->panelWebviewDataPath(), }); + const auto raw = &_webview->window; QObject::connect(container, &QObject::destroyed, [=] { if (_webview && &_webview->window == raw) { @@ -689,15 +704,18 @@ void Panel::showWarning(const QString &bot, const QString &provider) { void Panel::requestTermsAcceptance( const QString &username, - const QString &url) { + const QString &url, + bool recurring) { showBox(Box([=](not_null box) { box->setTitle(tr::lng_payments_terms_title()); box->addRow(object_ptr( box.get(), - tr::lng_payments_terms_text( - lt_bot, - rpl::single(Ui::Text::Bold('@' + username)), - Ui::Text::WithEntities), + (recurring + ? tr::lng_payments_terms_text + : tr::lng_payments_terms_text_once)( + lt_bot, + rpl::single(Ui::Text::Bold('@' + username)), + Ui::Text::WithEntities), st::boxLabel)); const auto update = std::make_shared>(); auto checkView = std::make_unique( @@ -735,41 +753,9 @@ void Panel::requestTermsAcceptance( (*update) = [=] { row->update(); }; - struct State { - bool error = false; - Ui::Animations::Simple errorAnimation; - }; - const auto state = box->lifetime().make_state(); - const auto showError = [=] { - const auto callback = [=] { - const auto error = state->errorAnimation.value( - state->error ? 1. : 0.); - if (error == 0.) { - check->setUntoggledOverride(std::nullopt); - } else { - const auto color = anim::color( - st::defaultCheck.untoggledFg, - st::boxTextFgError, - error); - check->setUntoggledOverride(color); - } - }; - state->error = true; - state->errorAnimation.stop(); - state->errorAnimation.start( - callback, - 0., - 1., - st::defaultCheck.duration); - }; - - row->checkedChanges( - ) | rpl::filter([=](bool checked) { - return checked; - }) | rpl::start_with_next([=] { - state->error = false; - check->setUntoggledOverride(std::nullopt); - }, row->lifetime()); + const auto showError = Ui::CheckView::PrepareNonToggledError( + check, + box->lifetime()); box->addButton(tr::lng_payments_terms_accept(), [=] { if (check->checked()) { @@ -932,6 +918,7 @@ void Panel::updateThemeParams(const Webview::ThemeParams ¶ms) { return; } _webview->window.updateTheme( + params.opaqueBg, params.scrollBg, params.scrollBgOver, params.scrollBarBg, diff --git a/Telegram/SourceFiles/payments/ui/payments_panel.h b/Telegram/SourceFiles/payments/ui/payments_panel.h index 47ec4284f..6136dace4 100644 --- a/Telegram/SourceFiles/payments/ui/payments_panel.h +++ b/Telegram/SourceFiles/payments/ui/payments_panel.h @@ -78,7 +78,10 @@ public: void askSetPassword(); void showCloseConfirm(); void showWarning(const QString &bot, const QString &provider); - void requestTermsAcceptance(const QString &username, const QString &url); + void requestTermsAcceptance( + const QString &username, + const QString &url, + bool recurring); bool showWebview( const QString &url, @@ -100,7 +103,7 @@ private: struct Progress; struct WebviewWithLifetime; - bool createWebview(); + bool createWebview(const Webview::ThemeParams ¶ms); void showWebviewProgress(); void hideWebviewProgress(); void showWebviewError( @@ -123,6 +126,7 @@ private: QPointer _weakEditInformation; QPointer _weakEditCard; rpl::event_stream _savedMethodChosen; + bool _themeUpdateScheduled = false; bool _webviewProgress = false; bool _testMode = false; diff --git a/Telegram/SourceFiles/payments/ui/payments_panel_data.h b/Telegram/SourceFiles/payments/ui/payments_panel_data.h index 40d91f44e..1021e7b56 100644 --- a/Telegram/SourceFiles/payments/ui/payments_panel_data.h +++ b/Telegram/SourceFiles/payments/ui/payments_panel_data.h @@ -56,7 +56,7 @@ struct Invoice { bool isTest = false; QString provider; - QString recurringTermsUrl; + QString termsUrl; bool phoneSentToProvider = false; bool emailSentToProvider = false; diff --git a/Telegram/SourceFiles/payments/ui/payments_panel_delegate.h b/Telegram/SourceFiles/payments/ui/payments_panel_delegate.h index ca0ed0caf..b3113d558 100644 --- a/Telegram/SourceFiles/payments/ui/payments_panel_delegate.h +++ b/Telegram/SourceFiles/payments/ui/payments_panel_delegate.h @@ -16,6 +16,10 @@ namespace Ui { class BoxContent; } // namespace Ui +namespace Webview { +struct ThemeParams; +} // namespace Webview + namespace Payments::Ui { using namespace ::Ui; @@ -56,6 +60,7 @@ public: virtual QVariant panelClickHandlerContext() = 0; virtual QString panelWebviewDataPath() = 0; + virtual Webview::ThemeParams panelWebviewThemeParams() = 0; virtual std::optional panelOverrideExpireDateThreshold() = 0; }; diff --git a/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp b/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp index 33b0ad780..027f6de91 100644 --- a/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp @@ -26,8 +26,7 @@ void UnsafeOpenUrl(const QString &url) { base::Platform::AppLaunchContext()); if (!result) { - LOG(("App Error: %1").arg( - QString::fromStdString(result.error().what()))); + LOG(("App Error: %1").arg(result.error().what())); } else if (*result) { return; } @@ -52,8 +51,7 @@ void UnsafeLaunch(const QString &filepath) { if ([&] { const auto filename = GLib::filename_to_uri(filepath.toStdString()); if (!filename) { - LOG(("App Error: %1").arg( - QString::fromStdString(filename.error().what()))); + LOG(("App Error: %1").arg(filename.error().what())); return false; } @@ -63,8 +61,7 @@ void UnsafeLaunch(const QString &filepath) { base::Platform::AppLaunchContext()); if (!result) { - LOG(("App Error: %1").arg( - QString::fromStdString(result.error().what()))); + LOG(("App Error: %1").arg(result.error().what())); return false; } @@ -96,11 +93,6 @@ bool Get( if (parent) { parent = parent->window(); } - // Workaround for sandboxed paths - static const auto docRegExp = QRegularExpression("^/run/user/\\d+/doc"); - if (cDialogLastPath().contains(docRegExp)) { - InitLastPath(); - } return ::FileDialog::internal::GetDefault( parent, files, diff --git a/Telegram/SourceFiles/platform/linux/integration_linux.cpp b/Telegram/SourceFiles/platform/linux/integration_linux.cpp index f842fd808..dbfec779d 100644 --- a/Telegram/SourceFiles/platform/linux/integration_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/integration_linux.cpp @@ -174,7 +174,7 @@ gi::ref_ptr MakeApplication() { const auto result = gi::make_ref(); if (const auto registered = result->register_(); !registered) { LOG(("App Error: Failed to register: %1").arg( - QString::fromStdString(registered.error().message_()))); + registered.error().message_().c_str())); return nullptr; } return result; @@ -207,25 +207,17 @@ LinuxIntegration::LinuxIntegration() base::Platform::XDP::kService, base::Platform::XDP::kObjectPath, nullptr)) -, _darkModeWatcher([]( - const Glib::ustring &group, - const Glib::ustring &key, - const Glib::VariantBase &value) { - if (group == "org.freedesktop.appearance" - && key == "color-scheme") { +, _darkModeWatcher( + "org.freedesktop.appearance", + "color-scheme", + [](uint value) { #if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) QWindowSystemInterface::handleThemeChange(); #else // Qt >= 6.5.0 - try { - const auto ivalue = value.get_dynamic(); - - crl::on_main([=] { - Core::App().settings().setSystemDarkMode(ivalue == 1); - }); - } catch (...) { - } + Core::Sandbox::Instance().customEnterFromEventLoop([&] { + Core::App().settings().setSystemDarkMode(value == 1); + }); #endif // Qt < 6.5.0 - } }) { LOG(("Icon theme: %1").arg(QIcon::themeName())); LOG(("Fallback icon theme: %1").arg(QIcon::fallbackThemeName())); diff --git a/Telegram/SourceFiles/platform/linux/launcher_linux.cpp b/Telegram/SourceFiles/platform/linux/launcher_linux.cpp index e5ae4efb5..1e21f57fc 100644 --- a/Telegram/SourceFiles/platform/linux/launcher_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/launcher_linux.cpp @@ -12,38 +12,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "webview/platform/linux/webview_linux_webkitgtk.h" #include +#include -#include -#include -#include -#include -#include -#include -#include +using namespace gi::repository; namespace Platform { -namespace { - -class Arguments { -public: - void push(QByteArray argument) { - argument.append(char(0)); - _argumentValues.push_back(argument); - _arguments.push_back(_argumentValues.back().data()); - } - - char **result() { - _arguments.push_back(nullptr); - return _arguments.data(); - } - -private: - std::vector _argumentValues; - std::vector _arguments; - -}; - -} // namespace Launcher::Launcher(int argc, char *argv[]) : Core::Launcher(argc, argv) { @@ -70,86 +43,94 @@ bool Launcher::launchUpdater(UpdaterLaunch action) { } const auto justRelaunch = action == UpdaterLaunch::JustRelaunch; - const auto writeProtectedUpdate = action == UpdaterLaunch::PerformUpdate - && cWriteProtected(); - const auto binaryPath = justRelaunch - ? QFile::encodeName(cExeDir() + cExeName()) - : QFile::encodeName(cWriteProtected() - ? (cWorkingDir() + u"tupdates/temp/Updater"_q) - : (cExeDir() + u"Updater"_q)); + std::vector argumentsList; - auto argumentsList = Arguments(); - if (writeProtectedUpdate) { - argumentsList.push("pkexec"); + // What we are launching. + const auto launching = justRelaunch + ? (cExeDir() + cExeName()) + : cWriteProtected() + ? u"pkexec"_q + : (cExeDir() + u"Updater"_q); + argumentsList.push_back(launching.toStdString()); + + if (justRelaunch) { + // argv[0] that is passed to what we are launching. + // It should be added explicitly in case of FILE_AND_ARGV_ZERO_. + const auto argv0 = !arguments().isEmpty() + ? arguments().first() + : launching; + argumentsList.push_back(argv0.toStdString()); + } else if (cWriteProtected()) { + // Elevated process that pkexec should launch. + const auto elevated = cWorkingDir() + u"tupdates/temp/Updater"_q; + argumentsList.push_back(elevated.toStdString()); } - argumentsList.push((justRelaunch && !arguments().isEmpty()) - ? QFile::encodeName(arguments().first()) - : binaryPath); - if (cLaunchMode() == LaunchModeAutoStart) { - argumentsList.push("-autostart"); - } if (Logs::DebugEnabled()) { - argumentsList.push("-debug"); - } - if (cStartInTray()) { - argumentsList.push("-startintray"); - } - if (cDataFile() != u"data"_q) { - argumentsList.push("-key"); - argumentsList.push(QFile::encodeName(cDataFile())); + argumentsList.push_back("-debug"); } if (justRelaunch) { - argumentsList.push("-noupdate"); - argumentsList.push("-tosettings"); + if (cLaunchMode() == LaunchModeAutoStart) { + argumentsList.push_back("-autostart"); + } + if (cStartInTray()) { + argumentsList.push_back("-startintray"); + } + if (cDataFile() != u"data"_q) { + argumentsList.push_back("-key"); + argumentsList.push_back(cDataFile().toStdString()); + } + argumentsList.push_back("-noupdate"); + argumentsList.push_back("-tosettings"); if (customWorkingDir()) { - argumentsList.push("-workdir"); - argumentsList.push(QFile::encodeName(cWorkingDir())); + argumentsList.push_back("-workdir"); + argumentsList.push_back(cWorkingDir().toStdString()); } } else { - argumentsList.push("-workpath"); - argumentsList.push(QFile::encodeName(cWorkingDir())); - argumentsList.push("-exename"); - argumentsList.push(QFile::encodeName(cExeName())); - argumentsList.push("-exepath"); - argumentsList.push(QFile::encodeName(cExeDir())); - if (!arguments().isEmpty()) { - argumentsList.push("-argv0"); - argumentsList.push(QFile::encodeName(arguments().first())); - } - if (customWorkingDir()) { - argumentsList.push("-workdir_custom"); - } + // Don't relaunch Telegram. + argumentsList.push_back("-justupdate"); + + argumentsList.push_back("-workpath"); + argumentsList.push_back(cWorkingDir().toStdString()); + argumentsList.push_back("-exename"); + argumentsList.push_back(cExeName().toStdString()); + argumentsList.push_back("-exepath"); + argumentsList.push_back(cExeDir().toStdString()); if (cWriteProtected()) { - argumentsList.push("-writeprotected"); + argumentsList.push_back("-writeprotected"); } } Logs::closeMain(); CrashReports::Finish(); - const auto args = argumentsList.result(); - - pid_t pid = fork(); - switch (pid) { - case -1: return false; - case 0: - execvp( - writeProtectedUpdate ? args[0] : binaryPath.constData(), - args); + if (justRelaunch) { + return GLib::spawn_async( + initialWorkingDir().toStdString(), + argumentsList, + std::nullopt, + GLib::SpawnFlags::FILE_AND_ARGV_ZERO_, + nullptr, + nullptr, + nullptr); + } else if (!GLib::spawn_sync( + argumentsList, + std::nullopt, + // if the spawn is sync, working directory is not set + // and GLib::SpawnFlags::LEAVE_DESCRIPTORS_OPEN_ is set, + // it goes through an optimized code path + GLib::SpawnFlags::SEARCH_PATH_ + | GLib::SpawnFlags::LEAVE_DESCRIPTORS_OPEN_, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr)) { return false; } - - // pkexec needs an alive parent - if (writeProtectedUpdate) { - waitpid(pid, nullptr, 0); - // launch new version in the same environment - return launchUpdater(UpdaterLaunch::JustRelaunch); - } - - return true; + return launchUpdater(UpdaterLaunch::JustRelaunch); } } // namespace diff --git a/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp b/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp index b6cc6c888..0c60115a5 100644 --- a/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp @@ -12,14 +12,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/qt_signal_producer.h" #include "base/flat_map.h" -#include "qwayland-wayland.h" -#include "qwayland-plasma-shell.h" - #include #include #include #include +#include +#include + using namespace QNativeInterface; using namespace QNativeInterface::Private; using namespace base::Platform::Wayland; @@ -42,7 +42,6 @@ struct WaylandIntegration::Private : public AutoDestroyer plasmaShell; - rpl::lifetime lifetime; protected: void registry_global( @@ -97,7 +96,7 @@ QtWayland::org_kde_plasma_surface WaylandIntegration::Private::plasmaSurface( if (it != plasmaShell->surfaces.cend()) { plasmaShell->surfaces.erase(it); } - }, lifetime); + }, result.first->second.lifetime()); return result.first->second; } @@ -129,7 +128,7 @@ WaylandIntegration *WaylandIntegration::Instance() { &QObject::destroyed ) | rpl::start_with_next([] { instance = std::nullopt; - }, instance->_private->lifetime); + }, instance->_private->lifetime()); return true; }(); if (!instance) return nullptr; diff --git a/Telegram/SourceFiles/platform/linux/linux_xdp_open_with_dialog.cpp b/Telegram/SourceFiles/platform/linux/linux_xdp_open_with_dialog.cpp index a425ff59f..e75bcb421 100644 --- a/Telegram/SourceFiles/platform/linux/linux_xdp_open_with_dialog.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_xdp_open_with_dialog.cpp @@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/platform/base_platform_info.h" #include "base/platform/linux/base_linux_xdp_utilities.h" -#include "base/platform/linux/base_linux_wayland_integration.h" +#include "base/platform/linux/base_linux_xdg_activation_token.h" #include "base/random.h" #include @@ -23,6 +23,7 @@ namespace { constexpr auto kXDPOpenURIInterface = "org.freedesktop.portal.OpenURI"; constexpr auto kPropertiesInterface = "org.freedesktop.DBus.Properties"; +using base::Platform::XdgActivationToken; } // namespace @@ -61,14 +62,6 @@ bool ShowXDPOpenWithDialog(const QString &filepath) { const auto handleToken = Glib::ustring("tdesktop") + std::to_string(base::RandomValue()); - const auto activationToken = []() -> Glib::ustring { - using base::Platform::WaylandIntegration; - if (const auto integration = WaylandIntegration::Instance()) { - return integration->activationToken().toStdString(); - } - return {}; - }(); - auto uniqueName = connection->get_unique_name(); uniqueName.erase(0, 1); uniqueName.replace(uniqueName.find('.'), 1, 1, '_'); @@ -118,7 +111,8 @@ bool ShowXDPOpenWithDialog(const QString &filepath) { }, { "activation_token", - Glib::create_variant(activationToken) + Glib::create_variant( + Glib::ustring(XdgActivationToken().toStdString())) }, { "ask", diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index ac74fd5c5..58c665669 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -29,7 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/event_filter.h" #include "ui/platform/ui_platform_window_title.h" #include "ui/widgets/popup_menu.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/ui_utility.h" #ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION @@ -40,6 +40,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include #include +#include +#include #include #include diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index 7448d690e..b9e7208e2 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -43,23 +43,22 @@ constexpr auto kInterface = kService; constexpr auto kPropertiesInterface = "org.freedesktop.DBus.Properties"; struct ServerInformation { - QString name; - QString vendor; + Glib::ustring name; + Glib::ustring vendor; QVersionNumber version; QVersionNumber specVersion; }; bool ServiceRegistered = false; -std::optional CurrentServerInformation; -QStringList CurrentCapabilities; +ServerInformation CurrentServerInformation; +std::vector CurrentCapabilities; void Noexcept(Fn callback, Fn failed = nullptr) noexcept { try { callback(); return; } catch (const std::exception &e) { - LOG(("Native Notification Error: %1").arg( - QString::fromStdString(e.what()))); + LOG(("Native Notification Error: %1").arg(e.what())); } if (failed) { @@ -91,15 +90,12 @@ std::unique_ptr CreateServiceWatcher() { const Glib::ustring &service, const Glib::ustring &oldOwner, const Glib::ustring &newOwner) { - if (activatable && newOwner.empty()) { - crl::on_main([] { + Core::Sandbox::Instance().customEnterFromEventLoop([&] { + if (activatable && newOwner.empty()) { Core::App().notifications().clearAll(); - }); - return; - } - - crl::on_main([] { - Core::App().notifications().createManager(); + } else { + Core::App().notifications().createManager(); + } }); }); } catch (...) { @@ -117,33 +113,35 @@ void StartServiceAsync(Fn callback) { connection, kService, [=](Fn result) { - Noexcept([&] { - try { - result(); // get the error if any - } catch (const Glib::Error &e) { - static const auto NotSupportedErrors = { - "org.freedesktop.DBus.Error.ServiceUnknown", - }; + Core::Sandbox::Instance().customEnterFromEventLoop([&] { + Noexcept([&] { + try { + result(); // get the error if any + } catch (const Glib::Error &e) { + static const auto NotSupportedErrors = { + "org.freedesktop.DBus.Error.ServiceUnknown", + }; - const auto errorName = - Gio::DBus::ErrorUtils::get_remote_error(e).raw(); + const auto errorName = + Gio::DBus::ErrorUtils::get_remote_error(e).raw(); - if (!ranges::contains( - NotSupportedErrors, - errorName)) { - throw e; + if (!ranges::contains( + NotSupportedErrors, + errorName)) { + throw e; + } } - } - }); + }); - crl::on_main(callback); + callback(); + }); }); return; } catch (...) { } - crl::on_main(callback); + callback(); } bool GetServiceRegistered() { @@ -179,8 +177,7 @@ bool GetServiceRegistered() { return false; } -void GetServerInformation( - Fn &)> callback) { +void GetServerInformation(Fn callback) { Noexcept([&] { const auto connection = Gio::DBus::Connection::get_sync( Gio::DBus::BusType::SESSION); @@ -191,46 +188,46 @@ void GetServerInformation( "GetServerInformation", {}, [=](const Glib::RefPtr &result) { - Noexcept([&] { - const auto reply = connection->call_finish(result); + Core::Sandbox::Instance().customEnterFromEventLoop([&] { + Noexcept([&] { + const auto reply = connection->call_finish(result); - const auto name = reply.get_child( - 0 - ).get_dynamic(); + const auto name = reply.get_child( + 0 + ).get_dynamic(); - const auto vendor = reply.get_child( - 1 - ).get_dynamic(); + const auto vendor = reply.get_child( + 1 + ).get_dynamic(); - const auto version = reply.get_child( - 2 - ).get_dynamic(); + const auto version = reply.get_child( + 2 + ).get_dynamic(); - const auto specVersion = reply.get_child( - 3 - ).get_dynamic(); + const auto specVersion = reply.get_child( + 3 + ).get_dynamic(); - crl::on_main([=] { callback(ServerInformation{ - QString::fromStdString(name), - QString::fromStdString(vendor), + name, + vendor, QVersionNumber::fromString( QString::fromStdString(version)), QVersionNumber::fromString( QString::fromStdString(specVersion)), }); + }, [&] { + callback({}); }); - }, [&] { - crl::on_main([=] { callback(std::nullopt); }); }); }, kService); }, [&] { - crl::on_main([=] { callback(std::nullopt); }); + callback({}); }); } -void GetCapabilities(Fn callback) { +void GetCapabilities(Fn &)> callback) { Noexcept([&] { const auto connection = Gio::DBus::Connection::get_sync( Gio::DBus::BusType::SESSION); @@ -241,25 +238,23 @@ void GetCapabilities(Fn callback) { "GetCapabilities", {}, [=](const Glib::RefPtr &result) { - Noexcept([&] { - QStringList value; - ranges::transform( - connection->call_finish( - result - ).get_child( - 0 - ).get_dynamic>(), - ranges::back_inserter(value), - QString::fromStdString); - - crl::on_main([=] { callback(value); }); - }, [&] { - crl::on_main([=] { callback({}); }); + Core::Sandbox::Instance().customEnterFromEventLoop([&] { + Noexcept([&] { + callback( + connection->call_finish( + result + ).get_child( + 0 + ).get_dynamic>() + ); + }, [&] { + callback({}); + }); }); }, kService); }, [&] { - crl::on_main([=] { callback({}); }); + callback({}); }); } @@ -277,29 +272,27 @@ void GetInhibited(Fn callback) { Glib::ustring("Inhibited"), }), [=](const Glib::RefPtr &result) { - Noexcept([&] { - const auto value = connection->call_finish( - result - ).get_child( - 0 - ).get_dynamic>( - ).get(); + Core::Sandbox::Instance().customEnterFromEventLoop([&] { + Noexcept([&] { + const auto value = connection->call_finish( + result + ).get_child( + 0 + ).get_dynamic>( + ).get(); - crl::on_main([=] { callback(value); }); - }, [&] { - crl::on_main([=] { callback(false); }); + callback(value); + }, [&] { + callback(false); + }); }); }, kService); }, [&] { - crl::on_main([=] { callback(false); }); + callback(false); }); } -ServerInformation CurrentServerInformationValue() { - return CurrentServerInformation.value_or(ServerInformation{}); -} - Glib::ustring GetImageKey(const QVersionNumber &specificationVersion) { const auto normalizedVersion = specificationVersion.normalized(); @@ -352,7 +345,7 @@ public: void show(); void close(); - void setImage(const QImage &image); + void setImage(QImage image); private: const not_null _manager; @@ -411,21 +404,11 @@ bool NotificationData::init( _notification->set_icon( Gio::ThemedIcon::create(base::IconName().toStdString())); - // glib 2.42+, we keep glib 2.40+ compatibility - static const auto set_priority = [] { - // reset dlerror after dlsym call - const auto guard = gsl::finally([] { dlerror(); }); - return reinterpret_cast( - dlsym(RTLD_DEFAULT, "g_notification_set_priority")); - }(); + // for chat messages, according to + // https://docs.gtk.org/gio/enum.NotificationPriority.html + _notification->set_priority(Gio::Notification::Priority::HIGH); - if (set_priority) { - // for chat messages, according to - // https://docs.gtk.org/gio/enum.NotificationPriority.html - set_priority(_notification->gobj(), G_NOTIFICATION_PRIORITY_HIGH); - } - - // glib 2.70+, we keep glib 2.40+ compatibility + // glib 2.70+, we keep glib 2.56+ compatibility static const auto set_category = [] { // reset dlerror after dlsym call const auto guard = gsl::finally([] { dlerror(); }); @@ -463,55 +446,57 @@ bool NotificationData::init( } const auto weak = base::make_weak(this); - const auto capabilities = CurrentCapabilities; + const auto &capabilities = CurrentCapabilities; - const auto signalEmitted = [=]( + const auto signalEmitted = crl::guard(weak, [=]( const Glib::RefPtr &connection, const Glib::ustring &sender_name, const Glib::ustring &object_path, const Glib::ustring &interface_name, const Glib::ustring &signal_name, const Glib::VariantContainerBase ¶meters) { - Noexcept([&] { - if (signal_name == "ActionInvoked") { - const auto id = parameters.get_child(0).get_dynamic(); + Core::Sandbox::Instance().customEnterFromEventLoop([&] { + Noexcept([&] { + if (signal_name == "ActionInvoked") { + const auto id = parameters.get_child(0).get_dynamic(); - const auto actionName = parameters.get_child( - 1 - ).get_dynamic(); + const auto actionName = parameters.get_child( + 1 + ).get_dynamic(); - crl::on_main(weak, [=] { actionInvoked(id, actionName); }); - } else if (signal_name == "ActivationToken") { - const auto id = parameters.get_child(0).get_dynamic(); + actionInvoked(id, actionName); + } else if (signal_name == "ActivationToken") { + const auto id = parameters.get_child(0).get_dynamic(); - const auto token = parameters.get_child( - 1 - ).get_dynamic(); + const auto token = parameters.get_child( + 1 + ).get_dynamic(); - crl::on_main(weak, [=] { activationToken(id, token); }); - } else if (signal_name == "NotificationReplied") { - const auto id = parameters.get_child(0).get_dynamic(); + activationToken(id, token); + } else if (signal_name == "NotificationReplied") { + const auto id = parameters.get_child(0).get_dynamic(); - const auto text = parameters.get_child( - 1 - ).get_dynamic(); + const auto text = parameters.get_child( + 1 + ).get_dynamic(); - crl::on_main(weak, [=] { notificationReplied(id, text); }); - } else if (signal_name == "NotificationClosed") { - const auto id = parameters.get_child(0).get_dynamic(); + notificationReplied(id, text); + } else if (signal_name == "NotificationClosed") { + const auto id = parameters.get_child(0).get_dynamic(); - const auto reason = parameters.get_child( - 1 - ).get_dynamic(); + const auto reason = parameters.get_child( + 1 + ).get_dynamic(); - crl::on_main(weak, [=] { notificationClosed(id, reason); }); - } + notificationClosed(id, reason); + } + }); }); - }; + }); - _imageKey = GetImageKey(CurrentServerInformationValue().specVersion); + _imageKey = GetImageKey(CurrentServerInformation.specVersion); - if (capabilities.contains(u"body-markup"_q)) { + if (ranges::contains(capabilities, "body-markup")) { _title = title.toStdString(); _body = subtitle.isEmpty() @@ -527,7 +512,7 @@ bool NotificationData::init( _body = msg.toStdString(); } - if (capabilities.contains("actions")) { + if (ranges::contains(capabilities, "actions")) { _actions.push_back("default"); _actions.push_back(tr::lng_open_link(tr::now).toStdString()); @@ -538,7 +523,7 @@ bool NotificationData::init( tr::lng_context_mark_read(tr::now).toStdString()); } - if (capabilities.contains("inline-reply") + if (ranges::contains(capabilities, "inline-reply") && !options.hideReplyButton) { _actions.push_back("inline-reply"); _actions.push_back( @@ -568,13 +553,13 @@ bool NotificationData::init( kObjectPath); } - if (capabilities.contains("action-icons")) { + if (ranges::contains(capabilities, "action-icons")) { _hints["action-icons"] = Glib::create_variant(true); } // suppress system sound if telegram sound activated, // otherwise use system sound - if (capabilities.contains("sound")) { + if (ranges::contains(capabilities, "sound")) { if (Core::App().settings().soundNotify()) { _hints["suppress-sound"] = Glib::create_variant(true); } else { @@ -584,7 +569,7 @@ bool NotificationData::init( } } - if (capabilities.contains("x-canonical-append")) { + if (ranges::contains(capabilities, "x-canonical-append")) { _hints["x-canonical-append"] = Glib::create_variant( Glib::ustring("true")); } @@ -652,23 +637,19 @@ void NotificationData::show() { _hints, -1, }), - [=](const Glib::RefPtr &result) { - Noexcept([&] { - const auto notificationId = connection->call_finish( - result - ).get_child( - 0 - ).get_dynamic(); - - crl::on_main(weak, [=] { - _notificationId = notificationId; - }); - }, [&] { - crl::on_main(weak, [=] { + crl::guard(weak, [=](const Glib::RefPtr &result) { + Core::Sandbox::Instance().customEnterFromEventLoop([&] { + Noexcept([&] { + _notificationId = connection->call_finish( + result + ).get_child( + 0 + ).get_dynamic(); + }, [&] { _manager->clearNotification(_id); }); }); - }, + }), kService); })); } @@ -694,7 +675,7 @@ void NotificationData::close() { _manager->clearNotification(_id); } -void NotificationData::setImage(const QImage &image) { +void NotificationData::setImage(QImage image) { if (_notification) { const auto imageData = [&] { QByteArray ba; @@ -717,20 +698,22 @@ void NotificationData::setImage(const QImage &image) { return; } - const auto convertedImage = image.hasAlphaChannel() - ? image.convertToFormat(QImage::Format_RGBA8888) - : image.convertToFormat(QImage::Format_RGB888); + if (image.hasAlphaChannel()) { + image.convertTo(QImage::Format_RGBA8888); + } else { + image.convertTo(QImage::Format_RGB888); + } _hints[_imageKey] = Glib::create_variant(std::tuple{ - convertedImage.width(), - convertedImage.height(), - int(convertedImage.bytesPerLine()), - convertedImage.hasAlphaChannel(), + image.width(), + image.height(), + int(image.bytesPerLine()), + image.hasAlphaChannel(), 8, - convertedImage.hasAlphaChannel() ? 4 : 3, + image.hasAlphaChannel() ? 4 : 3, std::vector( - convertedImage.constBits(), - convertedImage.constBits() + convertedImage.sizeInBytes()), + image.constBits(), + image.constBits() + image.sizeInBytes()), }); } @@ -822,20 +805,18 @@ bool ByDefault() { // A list of capabilities that offer feature parity // with custom notifications - static const auto NeededCapabilities = { + return ranges::all_of(std::initializer_list{ // To show message content - u"body"_q, + "body", // To have buttons on notifications - u"actions"_q, + "actions", // To have quick reply - u"inline-reply"_q, + "inline-reply", // To not to play sound with Don't Disturb activated // (no, using sound capability is not a way) - u"inhibitions"_q, - }; - - return ranges::all_of(NeededCapabilities, [&](const auto &capability) { - return CurrentCapabilities.contains(capability); + "inhibitions", + }, [](const auto *capability) { + return ranges::contains(CurrentCapabilities, capability); }); } @@ -871,19 +852,18 @@ void Create(Window::Notifications::System *system) { ServiceRegistered = GetServiceRegistered(); if (!ServiceRegistered) { - CurrentServerInformation = std::nullopt; - CurrentCapabilities = QStringList{}; + CurrentServerInformation = {}; + CurrentCapabilities = {}; managerSetter(); return; } - GetServerInformation([=]( - const std::optional &result) { + GetServerInformation([=](const ServerInformation &result) { CurrentServerInformation = result; oneReady(); }); - GetCapabilities([=](const QStringList &result) { + GetCapabilities([=](const std::vector &result) { CurrentCapabilities = result; oneReady(); }); @@ -928,29 +908,40 @@ private: Manager::Private::Private(not_null manager) : _manager(manager) { - const auto serverInformation = CurrentServerInformation; - const auto capabilities = CurrentCapabilities; + const auto &serverInformation = CurrentServerInformation; + const auto &capabilities = CurrentCapabilities; - if (serverInformation.has_value()) { + if (!serverInformation.name.empty()) { LOG(("Notification daemon product name: %1") - .arg(serverInformation->name)); + .arg(serverInformation.name.c_str())); + } + if (!serverInformation.vendor.empty()) { LOG(("Notification daemon vendor name: %1") - .arg(serverInformation->vendor)); + .arg(serverInformation.vendor.c_str())); + } + if (!serverInformation.version.isNull()) { LOG(("Notification daemon version: %1") - .arg(serverInformation->version.toString())); + .arg(serverInformation.version.toString())); + } + if (!serverInformation.specVersion.isNull()) { LOG(("Notification daemon specification version: %1") - .arg(serverInformation->specVersion.toString())); + .arg(serverInformation.specVersion.toString())); } - if (!capabilities.isEmpty()) { - LOG(("Notification daemon capabilities: %1") - .arg(capabilities.join(", "))); + if (!capabilities.empty()) { + LOG(("Notification daemon capabilities: %1").arg( + ranges::fold_left( + capabilities, + "", + [](const Glib::ustring &a, const Glib::ustring &b) { + return a + (a.empty() ? "" : ", ") + b; + }).c_str())); } - if (capabilities.contains(u"inhibitions"_q)) { + if (ranges::contains(capabilities, "inhibitions")) { Noexcept([&] { _dbusConnection = Gio::DBus::Connection::get_sync( Gio::DBus::BusType::SESSION); @@ -966,34 +957,32 @@ Manager::Private::Private(not_null manager) })); _inhibitedSignalId = _dbusConnection->signal_subscribe( - [=]( + crl::guard(weak, [=]( const Glib::RefPtr &connection, const Glib::ustring &sender_name, const Glib::ustring &object_path, const Glib::ustring &interface_name, const Glib::ustring &signal_name, const Glib::VariantContainerBase ¶meters) { - Noexcept([&] { - const auto interface = parameters.get_child( - 0 - ).get_dynamic(); + Core::Sandbox::Instance().customEnterFromEventLoop([&] { + Noexcept([&] { + const auto interface = parameters.get_child( + 0 + ).get_dynamic(); - if (interface != kInterface) { - return; - } + if (interface != kInterface) { + return; + } - const auto inhibited = parameters.get_child( - 1 - ).get_dynamic>( - ).at( - "Inhibited" - ).get_dynamic(); - - crl::on_main(weak, [=] { - _inhibited = inhibited; + _inhibited = parameters.get_child( + 1 + ).get_dynamic>( + ).at( + "Inhibited" + ).get_dynamic(); }); }); - }, + }), kService, kPropertiesInterface, "PropertiesChanged", diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index b3b7a50e5..f7d5169ee 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -11,12 +11,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/platform/base_platform_info.h" #include "base/platform/linux/base_linux_dbus_utilities.h" #include "base/platform/linux/base_linux_xdp_utilities.h" +#include "ui/platform/ui_platform_window_title.h" #include "platform/linux/linux_desktop_environment.h" #include "platform/linux/linux_wayland_integration.h" #include "lang/lang_keys.h" #include "mainwindow.h" #include "storage/localstorage.h" #include "core/launcher.h" +#include "core/sandbox.h" #include "core/core_settings.h" #include "core/update_checker.h" #include "webview/platform/linux/webview_linux_webkitgtk.h" @@ -52,115 +54,135 @@ using Platform::internal::WaylandIntegration; namespace Platform { namespace { -bool PortalAutostart(bool start, bool silent) { +void PortalAutostart(bool enabled, Fn done) { if (cExeName().isEmpty()) { - return false; + if (done) { + done(false); + } + return; } - auto error = false; - - try { - const auto connection = Gio::DBus::Connection::get_sync( - Gio::DBus::BusType::SESSION); - - const auto handleToken = Glib::ustring("tdesktop") - + std::to_string(base::RandomValue()); - - std::vector commandline; - commandline.push_back(cExeName().toStdString()); - if (Core::Launcher::Instance().customWorkingDir()) { - commandline.push_back("-workdir"); - commandline.push_back(cWorkingDir().toStdString()); + const auto connection = [&] { + try { + return Gio::DBus::Connection::get_sync( + Gio::DBus::BusType::SESSION); + } catch (const std::exception &e) { + if (done) { + LOG(("Portal Autostart Error: %1").arg(e.what())); + } + return Glib::RefPtr(); } - commandline.push_back("-autostart"); + }(); - std::map options; - options["handle_token"] = Glib::create_variant(handleToken); - options["reason"] = Glib::create_variant( - Glib::ustring( - tr::lng_settings_auto_start(tr::now).toStdString())); - options["autostart"] = Glib::create_variant(start); - options["commandline"] = Glib::create_variant(commandline); - options["dbus-activatable"] = Glib::create_variant(false); + if (!connection) { + if (done) { + done(false); + } + return; + } - auto uniqueName = connection->get_unique_name(); - uniqueName.erase(0, 1); - uniqueName.replace(uniqueName.find('.'), 1, 1, '_'); + const auto handleToken = Glib::ustring("tdesktop") + + std::to_string(base::RandomValue()); - const auto requestPath = Glib::ustring( - "/org/freedesktop/portal/desktop/request/") - + uniqueName - + '/' - + handleToken; + std::vector commandline; + commandline.push_back(cExeName().toStdString()); + if (Core::Launcher::Instance().customWorkingDir()) { + commandline.push_back("-workdir"); + commandline.push_back(cWorkingDir().toStdString()); + } + commandline.push_back("-autostart"); - const auto loop = Glib::MainLoop::create(); + std::map options; + options["handle_token"] = Glib::create_variant(handleToken); + options["reason"] = Glib::create_variant( + Glib::ustring( + tr::lng_settings_auto_start(tr::now).toStdString())); + options["autostart"] = Glib::create_variant(enabled); + options["commandline"] = Glib::create_variant(commandline); + options["dbus-activatable"] = Glib::create_variant(false); + + auto uniqueName = connection->get_unique_name(); + uniqueName.erase(0, 1); + uniqueName.replace(uniqueName.find('.'), 1, 1, '_'); + + const auto requestPath = Glib::ustring( + "/org/freedesktop/portal/desktop/request/") + + uniqueName + + '/' + + handleToken; + + const auto window = std::make_shared(); + window->setAttribute(Qt::WA_DontShowOnScreen); + window->setWindowModality(Qt::ApplicationModal); + window->show(); + + const auto signalId = std::make_shared(); + *signalId = connection->signal_subscribe( + [=]( + const Glib::RefPtr &connection, + const Glib::ustring &sender_name, + const Glib::ustring &object_path, + const Glib::ustring &interface_name, + const Glib::ustring &signal_name, + const Glib::VariantContainerBase ¶meters) { + Core::Sandbox::Instance().customEnterFromEventLoop([&] { + (void)window; // don't destroy until finish - const auto signalId = connection->signal_subscribe( - [&]( - const Glib::RefPtr &connection, - const Glib::ustring &sender_name, - const Glib::ustring &object_path, - const Glib::ustring &interface_name, - const Glib::ustring &signal_name, - const Glib::VariantContainerBase ¶meters) { try { const auto response = parameters.get_child( 0 ).get_dynamic(); if (response) { - if (!silent) { + if (done) { LOG(("Portal Autostart Error: Request denied")); + done(false); } - error = true; + } else if (done) { + done(enabled); } } catch (const std::exception &e) { - if (!silent) { - LOG(("Portal Autostart Error: %1").arg( - QString::fromStdString(e.what()))); + if (done) { + LOG(("Portal Autostart Error: %1").arg(e.what())); + done(false); } - error = true; } - loop->quit(); - }, - base::Platform::XDP::kService, - base::Platform::XDP::kRequestInterface, - "Response", - requestPath); + if (*signalId) { + connection->signal_unsubscribe(*signalId); + } + }); + }, + base::Platform::XDP::kService, + base::Platform::XDP::kRequestInterface, + "Response", + requestPath); - const auto signalGuard = gsl::finally([&] { - if (signalId != 0) { - connection->signal_unsubscribe(signalId); - } - }); + connection->call( + base::Platform::XDP::kObjectPath, + "org.freedesktop.portal.Background", + "RequestBackground", + Glib::create_variant(std::tuple{ + base::Platform::XDP::ParentWindowID(), + options, + }), + [=](const Glib::RefPtr &result) { + Core::Sandbox::Instance().customEnterFromEventLoop([&] { + try { + connection->call_finish(result); + } catch (const std::exception &e) { + if (done) { + LOG(("Portal Autostart Error: %1").arg(e.what())); + done(false); + } - connection->call_sync( - base::Platform::XDP::kObjectPath, - "org.freedesktop.portal.Background", - "RequestBackground", - Glib::create_variant(std::tuple{ - base::Platform::XDP::ParentWindowID(), - options, - }), - base::Platform::XDP::kService); - - if (signalId != 0) { - QWidget window; - window.setAttribute(Qt::WA_DontShowOnScreen); - window.setWindowModality(Qt::ApplicationModal); - window.show(); - loop->run(); - } - } catch (const std::exception &e) { - if (!silent) { - LOG(("Portal Autostart Error: %1").arg( - QString::fromStdString(e.what()))); - } - error = true; - } - - return !error; + if (*signalId) { + connection->signal_unsubscribe(*signalId); + } + } + }); + }, + base::Platform::XDP::kService); } bool GenerateDesktopFile( @@ -261,7 +283,7 @@ bool GenerateDesktopFile( target->save_to_file(targetFile.toStdString()); } catch (const std::exception &e) { if (!silent) { - LOG(("App Error: %1").arg(QString::fromStdString(e.what()))); + LOG(("App Error: %1").arg(e.what())); } return false; } @@ -353,7 +375,7 @@ bool GenerateServiceFile(bool silent = false) { target->save_to_file(targetFile.toStdString()); } catch (const std::exception &e) { if (!silent) { - LOG(("App Error: %1").arg(QString::fromStdString(e.what()))); + LOG(("App Error: %1").arg(e.what())); } return false; } @@ -371,15 +393,16 @@ bool GenerateServiceFile(bool silent = false) { } try { - const auto connection = Gio::DBus::Connection::get_sync( - Gio::DBus::BusType::SESSION); - - connection->call_sync( + Gio::DBus::Connection::get_sync( + Gio::DBus::BusType::SESSION + )->call( base::Platform::DBus::kObjectPath, base::Platform::DBus::kInterface, "ReloadConfig", {}, - base::Platform::DBus::kService); + {}, + base::Platform::DBus::kService + ); } catch (...) { } @@ -426,7 +449,10 @@ void SetApplicationIcon(const QIcon &icon) { QString SingleInstanceLocalServerName(const QString &hash) { #if defined Q_OS_LINUX && QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) if (KSandbox::isSnap()) { - return u"snap.telegram-desktop."_q + hash; + return u"snap."_q + + qEnvironmentVariable("SNAP_INSTANCE_NAME") + + '.' + + hash; } return hash + '-' + cGUIDStr(); #else // Q_OS_LINUX && Qt >= 6.2.0 @@ -436,18 +462,13 @@ QString SingleInstanceLocalServerName(const QString &hash) { #if QT_VERSION < QT_VERSION_CHECK(6, 5, 0) std::optional IsDarkMode() { - try { - const auto result = base::Platform::XDP::ReadSetting( - "org.freedesktop.appearance", - "color-scheme"); + const auto result = base::Platform::XDP::ReadSetting( + "org.freedesktop.appearance", + "color-scheme"); - if (result.has_value()) { - return result->get_dynamic() == 1; - } - } catch (...) { - } - - return std::nullopt; + return result.has_value() + ? std::make_optional(*result == 1) + : std::nullopt; } #endif // Qt < 6.5.0 @@ -456,13 +477,12 @@ bool AutostartSupported() { } void AutostartToggle(bool enabled, Fn done) { + if (KSandbox::isFlatpak()) { + PortalAutostart(enabled, done); + return; + } + const auto success = [&] { - const auto silent = !done; - - if (KSandbox::isFlatpak()) { - return PortalAutostart(enabled, silent); - } - const auto autostart = QStandardPaths::writableLocation( QStandardPaths::GenericConfigLocation) + u"/autostart/"_q; @@ -478,7 +498,7 @@ void AutostartToggle(bool enabled, Fn done) { autostart, { u"-autostart"_q }, true, - silent); + !done); }(); if (done) { @@ -510,6 +530,15 @@ bool SkipTaskbarSupported() { return false; } +bool RunInBackground() { + using Ui::Platform::TitleControl; + const auto layout = Ui::Platform::TitleControlsLayout(); + return (ranges::contains(layout.left, TitleControl::Close) + || ranges::contains(layout.right, TitleControl::Close)) + && !ranges::contains(layout.left, TitleControl::Minimize) + && !ranges::contains(layout.right, TitleControl::Minimize); +} + QString ExecutablePathForShortcuts() { if (Core::UpdaterDisabled()) { const auto &arguments = Core::Launcher::Instance().arguments(); @@ -616,18 +645,6 @@ void start() { Glib::init(); Gio::init(); -#ifdef DESKTOP_APP_USE_PACKAGED_RLOTTIE - g_warning( - "Application has been built with foreign rlottie, " - "animated emojis won't be colored to the selected pack."); -#endif // DESKTOP_APP_USE_PACKAGED_RLOTTIE - -#ifdef DESKTOP_APP_USE_PACKAGED_FONTS - g_warning( - "Application was built without embedded fonts, " - "this may lead to font issues."); -#endif // DESKTOP_APP_USE_PACKAGED_FONTS - Webview::WebKitGTK::SetSocketPath(u"%1/%2-%3-webview-%4"_q.arg( QDir::tempPath(), h, diff --git a/Telegram/SourceFiles/platform/mac/file_utilities_mac.mm b/Telegram/SourceFiles/platform/mac/file_utilities_mac.mm index a3fe3a63f..1447454de 100644 --- a/Telegram/SourceFiles/platform/mac/file_utilities_mac.mm +++ b/Telegram/SourceFiles/platform/mac/file_utilities_mac.mm @@ -514,7 +514,7 @@ bool UnsafeShowOpenWith(const QString &filepath) { NSArray *appsPaths = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationDirectory inDomains:NSLocalDomainMask]; if ([appsPaths count]) [openPanel setDirectoryURL:[appsPaths firstObject]]; [openPanel beginWithCompletionHandler:^(NSInteger result){ - if (result == NSFileHandlingPanelOKButton) { + if (result == NSModalResponseOK) { if ([[openPanel URLs] count] > 0) { NSURL *app = [[openPanel URLs] objectAtIndex:0]; NSString *path = [app path]; diff --git a/Telegram/SourceFiles/platform/mac/main_window_mac.mm b/Telegram/SourceFiles/platform/mac/main_window_mac.mm index fe3d70c76..95043e270 100644 --- a/Telegram/SourceFiles/platform/mac/main_window_mac.mm +++ b/Telegram/SourceFiles/platform/mac/main_window_mac.mm @@ -33,10 +33,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/about_box.h" #include "lang/lang_keys.h" #include "base/platform/mac/base_utilities_mac.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/ui_utility.h" #include +#include +#include #include #include diff --git a/Telegram/SourceFiles/platform/mac/specific_mac.h b/Telegram/SourceFiles/platform/mac/specific_mac.h index 12cc59024..187ceb7b1 100644 --- a/Telegram/SourceFiles/platform/mac/specific_mac.h +++ b/Telegram/SourceFiles/platform/mac/specific_mac.h @@ -31,6 +31,10 @@ inline bool SkipTaskbarSupported() { return false; } +inline bool RunInBackground() { + return true; +} + void ActivateThisProcess(); inline uint64 ActivationWindowId(not_null window) { diff --git a/Telegram/SourceFiles/platform/mac/specific_mac.mm b/Telegram/SourceFiles/platform/mac/specific_mac.mm index d795a1c24..8a7e3c713 100644 --- a/Telegram/SourceFiles/platform/mac/specific_mac.mm +++ b/Telegram/SourceFiles/platform/mac/specific_mac.mm @@ -155,10 +155,10 @@ std::optional IsDarkMode() { #endif // Qt < 6.5.0 void WriteCrashDumpDetails() { -#ifndef DESKTOP_APP_DISABLE_CRASH_REPORTS +#ifndef TDESKTOP_DISABLE_CRASH_REPORTS double v = objc_appkitVersion(); CrashReports::dump() << "OS-Version: " << v; -#endif // DESKTOP_APP_DISABLE_CRASH_REPORTS +#endif // TDESKTOP_DISABLE_CRASH_REPORTS } // I do check for availability, just not in the exact way clang is content with diff --git a/Telegram/SourceFiles/platform/mac/touchbar/items/mac_scrubber_item.mm b/Telegram/SourceFiles/platform/mac/touchbar/items/mac_scrubber_item.mm index 0f851a03f..7ec68e337 100644 --- a/Telegram/SourceFiles/platform/mac/touchbar/items/mac_scrubber_item.mm +++ b/Telegram/SourceFiles/platform/mac/touchbar/items/mac_scrubber_item.mm @@ -30,7 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/mac/touchbar/mac_touchbar_common.h" #include "styles/style_basic.h" #include "styles/style_settings.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "window/section_widget.h" #include "window/window_controller.h" #include "window/window_session_controller.h" @@ -47,6 +47,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #import #import +#include + using TouchBar::kCircleDiameter; using TouchBar::CreateNSImageFromStyleIcon; diff --git a/Telegram/SourceFiles/platform/platform_file_utilities.h b/Telegram/SourceFiles/platform/platform_file_utilities.h index 91ef52aae..b28b821bc 100644 --- a/Telegram/SourceFiles/platform/platform_file_utilities.h +++ b/Telegram/SourceFiles/platform/platform_file_utilities.h @@ -43,10 +43,10 @@ bool Get( // Platform dependent implementations. -#ifdef Q_OS_MAC -#include "platform/mac/file_utilities_mac.h" -#elif defined Q_OS_UNIX // Q_OS_MAC -#include "platform/linux/file_utilities_linux.h" -#elif defined Q_OS_WINRT || defined Q_OS_WIN // Q_OS_MAC || Q_OS_UNIX +#if defined Q_OS_WINRT || defined Q_OS_WIN #include "platform/win/file_utilities_win.h" -#endif // Q_OS_MAC || Q_OS_UNIX || Q_OS_WINRT || Q_OS_WIN +#elif defined Q_OS_MAC // Q_OS_WINRT || Q_OS_WIN +#include "platform/mac/file_utilities_mac.h" +#else // Q_OS_WINRT || Q_OS_WIN || Q_OS_MAC +#include "platform/linux/file_utilities_linux.h" +#endif // else for Q_OS_WINRT || Q_OS_WIN || Q_OS_MAC diff --git a/Telegram/SourceFiles/platform/platform_integration.cpp b/Telegram/SourceFiles/platform/platform_integration.cpp index 021024c6f..d2f832eeb 100644 --- a/Telegram/SourceFiles/platform/platform_integration.cpp +++ b/Telegram/SourceFiles/platform/platform_integration.cpp @@ -7,13 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/platform_integration.h" -#ifdef Q_OS_MAC -#include "platform/mac/integration_mac.h" -#elif defined Q_OS_UNIX // Q_OS_MAC -#include "platform/linux/integration_linux.h" -#elif defined Q_OS_WINRT || defined Q_OS_WIN // Q_OS_MAC || Q_OS_UNIX +#if defined Q_OS_WINRT || defined Q_OS_WIN #include "platform/win/integration_win.h" -#endif // Q_OS_MAC || Q_OS_UNIX || Q_OS_WINRT || Q_OS_WIN +#elif defined Q_OS_MAC // Q_OS_WINRT || Q_OS_WIN +#include "platform/mac/integration_mac.h" +#else // Q_OS_WINRT || Q_OS_WIN || Q_OS_MAC +#include "platform/linux/integration_linux.h" +#endif // else Q_OS_WINRT || Q_OS_WIN || Q_OS_MAC namespace Platform { namespace { diff --git a/Telegram/SourceFiles/platform/platform_launcher.h b/Telegram/SourceFiles/platform/platform_launcher.h index d79aea65c..172730e67 100644 --- a/Telegram/SourceFiles/platform/platform_launcher.h +++ b/Telegram/SourceFiles/platform/platform_launcher.h @@ -21,10 +21,10 @@ namespace Platform { // Platform dependent implementations. -#ifdef Q_OS_MAC -#include "platform/mac/launcher_mac.h" -#elif defined Q_OS_UNIX // Q_OS_MAC -#include "platform/linux/launcher_linux.h" -#elif defined Q_OS_WINRT || defined Q_OS_WIN // Q_OS_MAC || Q_OS_UNIX +#if defined Q_OS_WINRT || defined Q_OS_WIN #include "platform/win/launcher_win.h" -#endif // Q_OS_MAC || Q_OS_UNIX || Q_OS_WINRT || Q_OS_WIN +#elif defined Q_OS_MAC // Q_OS_WINRT || Q_OS_WIN +#include "platform/mac/launcher_mac.h" +#else // Q_OS_WINRT || Q_OS_WIN || Q_OS_MAC +#include "platform/linux/launcher_linux.h" +#endif // else for Q_OS_WINRT || Q_OS_WIN || Q_OS_MAC diff --git a/Telegram/SourceFiles/platform/platform_main_window.h b/Telegram/SourceFiles/platform/platform_main_window.h index 4bdb93981..e52de1edc 100644 --- a/Telegram/SourceFiles/platform/platform_main_window.h +++ b/Telegram/SourceFiles/platform/platform_main_window.h @@ -17,10 +17,10 @@ class MainWindow; // Platform dependent implementations. -#ifdef Q_OS_MAC -#include "platform/mac/main_window_mac.h" -#elif defined Q_OS_UNIX // Q_OS_MAC -#include "platform/linux/main_window_linux.h" -#elif defined Q_OS_WIN // Q_OS_MAC || Q_OS_UNIX +#ifdef Q_OS_WIN #include "platform/win/main_window_win.h" -#endif // Q_OS_MAC || Q_OS_UNIX || Q_OS_WIN +#elif defined Q_OS_MAC // Q_OS_WIN +#include "platform/mac/main_window_mac.h" +#else // Q_OS_WIN || Q_OS_MAC +#include "platform/linux/main_window_linux.h" +#endif // else Q_OS_WIN || Q_OS_MAC diff --git a/Telegram/SourceFiles/platform/platform_notifications_manager.h b/Telegram/SourceFiles/platform/platform_notifications_manager.h index 62db3378a..3ddb70a54 100644 --- a/Telegram/SourceFiles/platform/platform_notifications_manager.h +++ b/Telegram/SourceFiles/platform/platform_notifications_manager.h @@ -27,10 +27,10 @@ void Create(Window::Notifications::System *system); // Platform dependent implementations. -#ifdef Q_OS_MAC -#include "platform/mac/notifications_manager_mac.h" -#elif defined Q_OS_UNIX // Q_OS_MAC -#include "platform/linux/notifications_manager_linux.h" -#elif defined Q_OS_WIN // Q_OS_MAC || Q_OS_UNIX +#ifdef Q_OS_WIN #include "platform/win/notifications_manager_win.h" -#endif // Q_OS_MAC || Q_OS_UNIX || Q_OS_WIN +#elif defined Q_OS_MAC // Q_OS_MAC +#include "platform/mac/notifications_manager_mac.h" +#else // Q_OS_WIN || Q_OS_MAC +#include "platform/linux/notifications_manager_linux.h" +#endif // else for Q_OS_WIN || Q_OS_MAC diff --git a/Telegram/SourceFiles/platform/platform_overlay_widget.h b/Telegram/SourceFiles/platform/platform_overlay_widget.h index 2de3687d0..5b599858a 100644 --- a/Telegram/SourceFiles/platform/platform_overlay_widget.h +++ b/Telegram/SourceFiles/platform/platform_overlay_widget.h @@ -95,10 +95,10 @@ private: // Platform dependent implementations. -#ifdef Q_OS_MAC -#include "platform/mac/overlay_widget_mac.h" -#elif defined Q_OS_UNIX // Q_OS_MAC -#include "platform/linux/overlay_widget_linux.h" -#elif defined Q_OS_WIN // Q_OS_MAC || Q_OS_UNIX +#ifdef Q_OS_WIN #include "platform/win/overlay_widget_win.h" -#endif // Q_OS_MAC || Q_OS_UNIX || Q_OS_WIN +#elif defined Q_OS_MAC // Q_OS_WIN +#include "platform/mac/overlay_widget_mac.h" +#else // Q_OS_WIN || Q_OS_MAC +#include "platform/linux/overlay_widget_linux.h" +#endif // else for Q_OS_WIN || Q_OS_MAC diff --git a/Telegram/SourceFiles/platform/platform_specific.h b/Telegram/SourceFiles/platform/platform_specific.h index 604676615..ce839beb1 100644 --- a/Telegram/SourceFiles/platform/platform_specific.h +++ b/Telegram/SourceFiles/platform/platform_specific.h @@ -32,8 +32,8 @@ enum class SystemSettingsType { }; void SetApplicationIcon(const QIcon &icon); -QString SingleInstanceLocalServerName(const QString &hash); -PermissionStatus GetPermissionStatus(PermissionType type); +[[nodiscard]] QString SingleInstanceLocalServerName(const QString &hash); +[[nodiscard]] PermissionStatus GetPermissionStatus(PermissionType type); void RequestPermission(PermissionType type, Fn resultCallback); void OpenSystemSettingsForPermission(PermissionType type); bool OpenSystemSettings(SystemSettingsType type); @@ -42,8 +42,9 @@ void IgnoreApplicationActivationRightNow(); void AutostartRequestStateFromSystem(Fn callback); void AutostartToggle(bool enabled, Fn done = nullptr); [[nodiscard]] bool AutostartSkip(); -bool TrayIconSupported(); -bool SkipTaskbarSupported(); +[[nodiscard]] bool TrayIconSupported(); +[[nodiscard]] bool SkipTaskbarSupported(); +[[nodiscard]] bool RunInBackground(); void WriteCrashDumpDetails(); void NewVersionLaunched(int oldVersion); [[nodiscard]] QImage DefaultApplicationIcon(); @@ -62,10 +63,10 @@ void finish(); } // namespace ThirdParty } // namespace Platform -#ifdef Q_OS_MAC -#include "platform/mac/specific_mac.h" -#elif defined Q_OS_UNIX // Q_OS_MAC -#include "platform/linux/specific_linux.h" -#elif defined Q_OS_WIN // Q_OS_MAC || Q_OS_UNIX +#ifdef Q_OS_WIN #include "platform/win/specific_win.h" -#endif // Q_OS_MAC || Q_OS_UNIX || Q_OS_WIN +#elif defined Q_OS_MAC // Q_OS_WIN +#include "platform/mac/specific_mac.h" +#else // Q_OS_WIN || Q_OS_MAC +#include "platform/linux/specific_linux.h" +#endif // else for Q_OS_WIN || Q_OS_MAC diff --git a/Telegram/SourceFiles/platform/platform_tray.h b/Telegram/SourceFiles/platform/platform_tray.h index 3d6b90724..0838b52e4 100644 --- a/Telegram/SourceFiles/platform/platform_tray.h +++ b/Telegram/SourceFiles/platform/platform_tray.h @@ -15,10 +15,10 @@ class Tray; // Platform dependent implementations. -#ifdef Q_OS_MAC -#include "platform/mac/tray_mac.h" -#elif defined Q_OS_UNIX // Q_OS_MAC -#include "platform/linux/tray_linux.h" -#elif defined Q_OS_WIN // Q_OS_MAC || Q_OS_UNIX +#ifdef Q_OS_WIN #include "platform/win/tray_win.h" -#endif // Q_OS_MAC || Q_OS_UNIX || Q_OS_WIN +#elif defined Q_OS_MAC // Q_OS_WIN +#include "platform/mac/tray_mac.h" +#else // Q_OS_WIN || Q_OS_MAC +#include "platform/linux/tray_linux.h" +#endif // else for Q_OS_WIN || Q_OS_MAC diff --git a/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp b/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp index 83c543fe0..41bf5f786 100644 --- a/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp +++ b/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp @@ -107,11 +107,7 @@ crl::time LastSettingsQueryMs/* = 0*/; } bool init() { - if (!IsWindows8OrGreater()) { - return false; - } - if ((Dlls::SetCurrentProcessExplicitAppUserModelID == nullptr) - || !base::WinRT::Supported()) { + if (!IsWindows8OrGreater() || !base::WinRT::Supported()) { return false; } @@ -122,13 +118,21 @@ bool init() { LOG(("App Error: Object registration failed.")); } } - if (!AppUserModelId::validateShortcut()) { + if (!AppUserModelId::ValidateShortcut()) { LOG(("App Error: Shortcut validation failed.")); return false; } - auto appUserModelId = AppUserModelId::getId(); - if (!SUCCEEDED(Dlls::SetCurrentProcessExplicitAppUserModelID(appUserModelId))) { + PWSTR appUserModelId = {}; + if (!SUCCEEDED(GetCurrentProcessExplicitAppUserModelID(&appUserModelId))) { + return false; + } + + const auto appUserModelIdGuard = gsl::finally([&] { + CoTaskMemFree(appUserModelId); + }); + + if (AppUserModelId::Id() != appUserModelId) { return false; } return true; @@ -304,7 +308,7 @@ void QueryFocusAssist() { } return; } - const auto appUserModelId = std::wstring(AppUserModelId::getId()); + const auto appUserModelId = AppUserModelId::Id(); auto blocked = true; const auto guard = gsl::finally([&] { if (FocusAssistBlocks != blocked) { @@ -496,7 +500,7 @@ Manager::Private::Private(Manager *instance) bool Manager::Private::init() { return base::WinRT::Try([&] { _notifier = ToastNotificationManager::CreateToastNotifier( - AppUserModelId::getId()); + AppUserModelId::Id()); }); } diff --git a/Telegram/SourceFiles/platform/win/specific_win.cpp b/Telegram/SourceFiles/platform/win/specific_win.cpp index aa9dc99f6..75f0944f7 100644 --- a/Telegram/SourceFiles/platform/win/specific_win.cpp +++ b/Telegram/SourceFiles/platform/win/specific_win.cpp @@ -209,9 +209,9 @@ bool ManageAppLink( if (const auto propertyStore = shellLink.try_as()) { PROPVARIANT appIdPropVar; - hr = InitPropVariantFromString(AppUserModelId::getId(), &appIdPropVar); + hr = InitPropVariantFromString(AppUserModelId::Id().c_str(), &appIdPropVar); if (SUCCEEDED(hr)) { - hr = propertyStore->SetValue(AppUserModelId::getKey(), appIdPropVar); + hr = propertyStore->SetValue(AppUserModelId::Key(), appIdPropVar); PropVariantClear(&appIdPropVar); if (SUCCEEDED(hr)) { hr = propertyStore->Commit(); @@ -262,7 +262,7 @@ void psDoCleanup() { try { Platform::AutostartToggle(false); psSendToMenu(false, true); - AppUserModelId::cleanupShortcut(); + AppUserModelId::CleanupShortcut(); DeleteMyModules(); } catch (...) { } @@ -370,6 +370,10 @@ void start() { void start() { // https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/setlocale-wsetlocale#utf-8-support setlocale(LC_ALL, ".UTF8"); + + const auto appUserModelId = AppUserModelId::Id(); + SetCurrentProcessExplicitAppUserModelID(appUserModelId.c_str()); + LOG(("AppUserModelID: %1").arg(appUserModelId)); } void finish() { @@ -478,7 +482,7 @@ bool AutostartSkip() { } void WriteCrashDumpDetails() { -#ifndef DESKTOP_APP_DISABLE_CRASH_REPORTS +#ifndef TDESKTOP_DISABLE_CRASH_REPORTS PROCESS_MEMORY_COUNTERS data = { 0 }; if (Dlls::GetProcessMemoryInfo && Dlls::GetProcessMemoryInfo( @@ -499,7 +503,7 @@ void WriteCrashDumpDetails() { << (data.PagefileUsage / mb) << " MB (current)\n"; } -#endif // DESKTOP_APP_DISABLE_CRASH_REPORTS +#endif // TDESKTOP_DISABLE_CRASH_REPORTS } void SetWindowPriority(not_null window, uint32 priority) { @@ -638,8 +642,8 @@ bool OpenSystemSettings(SystemSettingsType type) { } void NewVersionLaunched(int oldVersion) { - if (oldVersion < 8051) { - AppUserModelId::checkPinned(); + if (oldVersion <= 4009009) { + AppUserModelId::CheckPinned(); } if (oldVersion > 0 && oldVersion < 2008012) { // Reset icons cache, because we've changed the application icon. diff --git a/Telegram/SourceFiles/platform/win/specific_win.h b/Telegram/SourceFiles/platform/win/specific_win.h index 0b0945dd5..acb2eda92 100644 --- a/Telegram/SourceFiles/platform/win/specific_win.h +++ b/Telegram/SourceFiles/platform/win/specific_win.h @@ -27,6 +27,10 @@ inline bool SkipTaskbarSupported() { return true; } +inline bool RunInBackground() { + return false; +} + inline bool PreventsQuit(Core::QuitReason reason) { return false; } diff --git a/Telegram/SourceFiles/platform/win/windows_app_user_model_id.cpp b/Telegram/SourceFiles/platform/win/windows_app_user_model_id.cpp index 2e749d4e1..e6bc3d2a1 100644 --- a/Telegram/SourceFiles/platform/win/windows_app_user_model_id.cpp +++ b/Telegram/SourceFiles/platform/win/windows_app_user_model_id.cpp @@ -9,34 +9,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/win/windows_dlls.h" #include "platform/win/windows_toast_activator.h" -#include "base/platform/win/base_windows_wrl.h" +#include "base/platform/win/base_windows_winrt.h" +#include "core/launcher.h" #include #include -using namespace Microsoft::WRL; - namespace Platform { namespace AppUserModelId { namespace { +constexpr auto kMaxFileLen = MAX_PATH * 2; + const PROPERTYKEY pkey_AppUserModel_ID = { { 0x9F4C2666, 0x9F79, 0x4B39, { 0xA8, 0xD0, 0xE1, 0xD4, 0x2D, 0xE1, 0xD5, 0xF3 } }, 5 }; const PROPERTYKEY pkey_AppUserModel_StartPinOption = { { 0x9F4C2666, 0x9F79, 0x4B39, { 0xA8, 0xD0, 0xE1, 0xD4, 0x2D, 0xE1, 0xD5, 0xF3 } }, 12 }; const PROPERTYKEY pkey_AppUserModel_ToastActivator = { { 0x9F4C2666, 0x9F79, 0x4B39, { 0xA8, 0xD0, 0xE1, 0xD4, 0x2D, 0xE1, 0xD5, 0xF3 } }, 26 }; #ifdef OS_WIN_STORE -const WCHAR AppUserModelIdRelease[] = L"Telegram.TelegramDesktop.Store"; +const WCHAR AppUserModelIdBase[] = L"Telegram.TelegramDesktop.Store"; #else // OS_WIN_STORE -const WCHAR AppUserModelIdRelease[] = L"Telegram.TelegramDesktop"; +const WCHAR AppUserModelIdBase[] = L"Telegram.TelegramDesktop"; #endif // OS_WIN_STORE -const WCHAR AppUserModelIdAlpha[] = L"Telegram.TelegramDesktop.Alpha"; -} // namespace - -QString pinnedPath() { - static const int maxFileLen = MAX_PATH * 10; - WCHAR wstrPath[maxFileLen]; - if (GetEnvironmentVariable(L"APPDATA", wstrPath, maxFileLen)) { +[[nodiscard]] QString PinnedIconsPath() { + WCHAR wstrPath[kMaxFileLen] = {}; + if (GetEnvironmentVariable(L"APPDATA", wstrPath, kMaxFileLen)) { auto appData = QDir(QString::fromStdWString(std::wstring(wstrPath))); return appData.absolutePath() + u"/Microsoft/Internet Explorer/Quick Launch/User Pinned/TaskBar/"_q; @@ -44,34 +41,74 @@ QString pinnedPath() { return QString(); } -void checkPinned() { - static const int maxFileLen = MAX_PATH * 10; +} // namespace - HRESULT hr = CoInitialize(0); - if (!SUCCEEDED(hr)) return; +const std::wstring &MyExecutablePath() { + static const auto Path = [&] { + auto result = std::wstring(kMaxFileLen, 0); + const auto length = GetModuleFileName( + GetModuleHandle(nullptr), + result.data(), + kMaxFileLen); + if (!length || length == kMaxFileLen) { + result.clear(); + } else { + result.resize(length + 1); + } + return result; + }(); + return Path; +} - QString path = pinnedPath(); - std::wstring p = QDir::toNativeSeparators(path).toStdWString(); +UniqueFileId MyExecutablePathId() { + return GetUniqueFileId(MyExecutablePath().c_str()); +} - WCHAR src[MAX_PATH]; - GetModuleFileName(GetModuleHandle(0), src, MAX_PATH); - BY_HANDLE_FILE_INFORMATION srcinfo = { 0 }; - HANDLE srcfile = CreateFile( - src, - 0x00, - 0x00, - NULL, +UniqueFileId GetUniqueFileId(LPCWSTR path) { + auto info = BY_HANDLE_FILE_INFORMATION{}; + const auto file = CreateFile( + path, + 0, + 0, + nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, - NULL); - if (srcfile == INVALID_HANDLE_VALUE) return; - BOOL srcres = GetFileInformationByHandle(srcfile, &srcinfo); - CloseHandle(srcfile); - if (!srcres) return; + nullptr); + if (file == INVALID_HANDLE_VALUE) { + return {}; + } + const auto result = GetFileInformationByHandle(file, &info); + CloseHandle(file); + if (!result) { + return {}; + } + return { + .part1 = info.dwVolumeSerialNumber, + .part2 = ((std::uint64_t(info.nFileIndexLow) << 32) + | std::uint64_t(info.nFileIndexHigh)), + }; +} + +void CheckPinned() { + if (!SUCCEEDED(CoInitialize(0))) { + return; + } + const auto coGuard = gsl::finally([] { + CoUninitialize(); + }); + + const auto path = PinnedIconsPath(); + const auto native = QDir::toNativeSeparators(path).toStdWString(); + + const auto srcid = MyExecutablePathId(); + if (!srcid) { + return; + } + LOG(("Checking...")); WIN32_FIND_DATA findData; HANDLE findHandle = FindFirstFileEx( - (p + L"*").c_str(), + (native + L"*").c_str(), FindExInfoStandard, &findData, FindExSearchNameMatch, @@ -82,62 +119,48 @@ void checkPinned() { return; } do { - std::wstring fname = p + findData.cFileName; + std::wstring fname = native + findData.cFileName; LOG(("Checking %1").arg(QString::fromStdWString(fname))); if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { continue; } else { DWORD attributes = GetFileAttributes(fname.c_str()); - if (attributes >= 0xFFFFFFF) continue; // file does not exist + if (attributes >= 0xFFFFFFF) { + continue; // file does not exist + } - ComPtr shellLink; - HRESULT hr = CoCreateInstance( - CLSID_ShellLink, - nullptr, - CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(&shellLink)); + auto shellLink = base::WinRT::TryCreateInstance( + CLSID_ShellLink); + if (!shellLink) { + continue; + } + + auto persistFile = shellLink.try_as(); + if (!persistFile) { + continue; + } + + auto hr = persistFile->Load(fname.c_str(), STGM_READWRITE); if (!SUCCEEDED(hr)) continue; - ComPtr persistFile; - hr = shellLink.As(&persistFile); + WCHAR dst[MAX_PATH] = { 0 }; + hr = shellLink->GetPath(dst, MAX_PATH, nullptr, 0); if (!SUCCEEDED(hr)) continue; - hr = persistFile->Load(fname.c_str(), STGM_READWRITE); - if (!SUCCEEDED(hr)) continue; - - WCHAR dst[MAX_PATH]; - hr = shellLink->GetPath(dst, MAX_PATH, 0, 0); - if (!SUCCEEDED(hr)) continue; - - BY_HANDLE_FILE_INFORMATION dstinfo = { 0 }; - HANDLE dstfile = CreateFile( - dst, - 0x00, - 0x00, - NULL, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, - NULL); - if (dstfile == INVALID_HANDLE_VALUE) continue; - BOOL dstres = GetFileInformationByHandle(dstfile, &dstinfo); - CloseHandle(dstfile); - if (!dstres) continue; - - if (srcinfo.dwVolumeSerialNumber == dstinfo.dwVolumeSerialNumber - && srcinfo.nFileIndexLow == dstinfo.nFileIndexLow - && srcinfo.nFileIndexHigh == dstinfo.nFileIndexHigh) { - ComPtr propertyStore; - hr = shellLink.As(&propertyStore); - if (!SUCCEEDED(hr)) return; + if (GetUniqueFileId(dst) == srcid) { + auto propertyStore = shellLink.try_as(); + if (!propertyStore) { + return; + } PROPVARIANT appIdPropVar; - hr = propertyStore->GetValue(getKey(), &appIdPropVar); + hr = propertyStore->GetValue(Key(), &appIdPropVar); if (!SUCCEEDED(hr)) return; LOG(("Reading...")); WCHAR already[MAX_PATH]; hr = PropVariantToString(appIdPropVar, already, MAX_PATH); if (SUCCEEDED(hr)) { - if (std::wstring(getId()) == already) { + if (Id() == already) { LOG(("Already!")); PropVariantClear(&appIdPropVar); return; @@ -149,10 +172,10 @@ void checkPinned() { } PropVariantClear(&appIdPropVar); - hr = InitPropVariantFromString(getId(), &appIdPropVar); + hr = InitPropVariantFromString(Id().c_str(), &appIdPropVar); if (!SUCCEEDED(hr)) return; - hr = propertyStore->SetValue(getKey(), appIdPropVar); + hr = propertyStore->SetValue(Key(), appIdPropVar); PropVariantClear(&appIdPropVar); if (!SUCCEEDED(hr)) return; @@ -175,9 +198,8 @@ void checkPinned() { } QString systemShortcutPath() { - static const int maxFileLen = MAX_PATH * 10; - WCHAR wstrPath[maxFileLen]; - if (GetEnvironmentVariable(L"APPDATA", wstrPath, maxFileLen)) { + WCHAR wstrPath[kMaxFileLen] = {}; + if (GetEnvironmentVariable(L"APPDATA", wstrPath, kMaxFileLen)) { auto appData = QDir(QString::fromStdWString(std::wstring(wstrPath))); const auto path = appData.absolutePath(); return path + u"/Microsoft/Windows/Start Menu/Programs/"_q; @@ -185,8 +207,11 @@ QString systemShortcutPath() { return QString(); } -void cleanupShortcut() { - static const int maxFileLen = MAX_PATH * 10; +void CleanupShortcut() { + const auto myid = MyExecutablePathId(); + if (!myid) { + return; + } QString path = systemShortcutPath() + u"AyuGram.lnk"_q; std::wstring p = QDir::toNativeSeparators(path).toStdWString(); @@ -194,66 +219,69 @@ void cleanupShortcut() { DWORD attributes = GetFileAttributes(p.c_str()); if (attributes >= 0xFFFFFFF) return; // file does not exist - ComPtr shellLink; - HRESULT hr = CoCreateInstance( - CLSID_ShellLink, - nullptr, - CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(&shellLink)); - if (!SUCCEEDED(hr)) return; + auto shellLink = base::WinRT::TryCreateInstance( + CLSID_ShellLink); + if (!shellLink) { + return; + } - ComPtr persistFile; - hr = shellLink.As(&persistFile); - if (!SUCCEEDED(hr)) return; + auto persistFile = shellLink.try_as(); + if (!persistFile) { + return; + } - hr = persistFile->Load(p.c_str(), STGM_READWRITE); + auto hr = persistFile->Load(p.c_str(), STGM_READWRITE); if (!SUCCEEDED(hr)) return; WCHAR szGotPath[MAX_PATH]; - WIN32_FIND_DATA wfd; - hr = shellLink->GetPath( - szGotPath, - MAX_PATH, - (WIN32_FIND_DATA*)&wfd, - SLGP_SHORTPATH); + hr = shellLink->GetPath(szGotPath, MAX_PATH, nullptr, 0); if (!SUCCEEDED(hr)) return; - const auto full = cExeDir() + cExeName(); - if (QDir::toNativeSeparators(full).toStdWString() == szGotPath) { + if (GetUniqueFileId(szGotPath) == myid) { QFile().remove(path); } } bool validateShortcutAt(const QString &path) { - static const int maxFileLen = MAX_PATH * 10; + const auto native = QDir::toNativeSeparators(path).toStdWString(); - std::wstring p = QDir::toNativeSeparators(path).toStdWString(); + DWORD attributes = GetFileAttributes(native.c_str()); + if (attributes >= 0xFFFFFFF) { + return false; // file does not exist + } - DWORD attributes = GetFileAttributes(p.c_str()); - if (attributes >= 0xFFFFFFF) return false; // file does not exist + auto shellLink = base::WinRT::TryCreateInstance( + CLSID_ShellLink); + if (!shellLink) { + return false; + } - ComPtr shellLink; - HRESULT hr = CoCreateInstance( - CLSID_ShellLink, - nullptr, - CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(&shellLink)); + auto persistFile = shellLink.try_as(); + if (!persistFile) { + return false; + } + + auto hr = persistFile->Load(native.c_str(), STGM_READWRITE); if (!SUCCEEDED(hr)) return false; - ComPtr persistFile; - hr = shellLink.As(&persistFile); - if (!SUCCEEDED(hr)) return false; + WCHAR szGotPath[kMaxFileLen] = { 0 }; + hr = shellLink->GetPath(szGotPath, kMaxFileLen, nullptr, 0); + if (!SUCCEEDED(hr)) { + return false; + } - hr = persistFile->Load(p.c_str(), STGM_READWRITE); - if (!SUCCEEDED(hr)) return false; + if (GetUniqueFileId(szGotPath) != MyExecutablePathId()) { + return false; + } - ComPtr propertyStore; - hr = shellLink.As(&propertyStore); - if (!SUCCEEDED(hr)) return false; + auto propertyStore = shellLink.try_as(); + if (!propertyStore) { + return false; + } PROPVARIANT appIdPropVar; PROPVARIANT toastActivatorPropVar; - hr = propertyStore->GetValue(getKey(), &appIdPropVar); + hr = propertyStore->GetValue(Key(), &appIdPropVar); if (!SUCCEEDED(hr)) return false; hr = propertyStore->GetValue( @@ -263,7 +291,7 @@ bool validateShortcutAt(const QString &path) { WCHAR already[MAX_PATH]; hr = PropVariantToString(appIdPropVar, already, MAX_PATH); - const auto good1 = SUCCEEDED(hr) && (std::wstring(getId()) == already); + const auto good1 = SUCCEEDED(hr) && (Id() == already); const auto bad1 = !good1 && (appIdPropVar.vt != VT_EMPTY); PropVariantClear(&appIdPropVar); @@ -279,10 +307,10 @@ bool validateShortcutAt(const QString &path) { return false; } - hr = InitPropVariantFromString(getId(), &appIdPropVar); + hr = InitPropVariantFromString(Id().c_str(), &appIdPropVar); if (!SUCCEEDED(hr)) return false; - hr = propertyStore->SetValue(getKey(), appIdPropVar); + hr = propertyStore->SetValue(Key(), appIdPropVar); PropVariantClear(&appIdPropVar); if (!SUCCEEDED(hr)) return false; @@ -301,7 +329,7 @@ bool validateShortcutAt(const QString &path) { if (!SUCCEEDED(hr)) return false; if (persistFile->IsDirty() == S_OK) { - hr = persistFile->Save(p.c_str(), TRUE); + hr = persistFile->Save(native.c_str(), TRUE); if (!SUCCEEDED(hr)) return false; } @@ -309,7 +337,21 @@ bool validateShortcutAt(const QString &path) { return true; } -bool validateShortcut() { +bool checkInstalled(QString path = {}) { + if (path.isEmpty()) { + path = systemShortcutPath(); + if (path.isEmpty()) { + return false; + } + } + + const auto installed = u"AyuGram Desktop/AyuGram.lnk"_q; + const auto old = u"AyuGram for Windows/AyuGram.lnk"_q; + return validateShortcutAt(path + installed) + || validateShortcutAt(path + old); +} + +bool ValidateShortcut() { QString path = systemShortcutPath(); if (path.isEmpty() || cExeName().isEmpty()) { return false; @@ -321,10 +363,7 @@ bool validateShortcut() { return true; } } else { - const auto installed = u"AyuGram Desktop/AyuGram.lnk"_q; - const auto old = u"AyuGram for Windows/AyuGram.lnk"_q; - if (validateShortcutAt(path + installed) - || validateShortcutAt(path + old)) { + if (checkInstalled(path)) { return true; } @@ -334,84 +373,140 @@ bool validateShortcut() { } } - ComPtr shellLink; - HRESULT hr = CoCreateInstance( - CLSID_ShellLink, - nullptr, - CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(&shellLink)); - if (!SUCCEEDED(hr)) return false; + auto shellLink = base::WinRT::TryCreateInstance( + CLSID_ShellLink); + if (!shellLink) { + return false; + } - hr = shellLink->SetPath( - QDir::toNativeSeparators( - cExeDir() + cExeName()).toStdWString().c_str()); - if (!SUCCEEDED(hr)) return false; + auto hr = shellLink->SetPath(MyExecutablePath().c_str()); + if (!SUCCEEDED(hr)) { + return false; + } hr = shellLink->SetArguments(L""); - if (!SUCCEEDED(hr)) return false; + if (!SUCCEEDED(hr)) { + return false; + } hr = shellLink->SetWorkingDirectory( QDir::toNativeSeparators( QDir(cWorkingDir()).absolutePath()).toStdWString().c_str()); - if (!SUCCEEDED(hr)) return false; + if (!SUCCEEDED(hr)) { + return false; + } - ComPtr propertyStore; - hr = shellLink.As(&propertyStore); - if (!SUCCEEDED(hr)) return false; + auto propertyStore = shellLink.try_as(); + if (!propertyStore) { + return false; + } PROPVARIANT appIdPropVar; - hr = InitPropVariantFromString(getId(), &appIdPropVar); - if (!SUCCEEDED(hr)) return false; + hr = InitPropVariantFromString(Id().c_str(), &appIdPropVar); + if (!SUCCEEDED(hr)) { + return false; + } - hr = propertyStore->SetValue(getKey(), appIdPropVar); + hr = propertyStore->SetValue(Key(), appIdPropVar); PropVariantClear(&appIdPropVar); - if (!SUCCEEDED(hr)) return false; + if (!SUCCEEDED(hr)) { + return false; + } PROPVARIANT startPinPropVar; hr = InitPropVariantFromUInt32( APPUSERMODEL_STARTPINOPTION_NOPINONINSTALL, &startPinPropVar); - if (!SUCCEEDED(hr)) return false; + if (!SUCCEEDED(hr)) { + return false; + } hr = propertyStore->SetValue( pkey_AppUserModel_StartPinOption, startPinPropVar); PropVariantClear(&startPinPropVar); - if (!SUCCEEDED(hr)) return false; + if (!SUCCEEDED(hr)) { + return false; + } PROPVARIANT toastActivatorPropVar{}; hr = InitPropVariantFromCLSID( __uuidof(ToastActivator), &toastActivatorPropVar); - if (!SUCCEEDED(hr)) return false; + if (!SUCCEEDED(hr)) { + return false; + } hr = propertyStore->SetValue( pkey_AppUserModel_ToastActivator, toastActivatorPropVar); PropVariantClear(&toastActivatorPropVar); - if (!SUCCEEDED(hr)) return false; + if (!SUCCEEDED(hr)) { + return false; + } hr = propertyStore->Commit(); - if (!SUCCEEDED(hr)) return false; + if (!SUCCEEDED(hr)) { + return false; + } - ComPtr persistFile; - hr = shellLink.As(&persistFile); - if (!SUCCEEDED(hr)) return false; + auto persistFile = shellLink.try_as(); + if (!persistFile) { + return false; + } hr = persistFile->Save( QDir::toNativeSeparators(path).toStdWString().c_str(), TRUE); - if (!SUCCEEDED(hr)) return false; + if (!SUCCEEDED(hr)) { + return false; + } LOG(("App Info: Shortcut created and validated at \"%1\"").arg(path)); return true; } -const WCHAR *getId() { - return cAlphaVersion() ? AppUserModelIdAlpha : AppUserModelIdRelease; +const std::wstring &Id() { + static const auto BaseId = std::wstring(AppUserModelIdBase); + static auto CheckingInstalled = false; + if (CheckingInstalled) { + return BaseId; + } + static const auto Installed = [] { +#ifdef OS_WIN_STORE + return true; +#else // OS_WIN_STORE + CheckingInstalled = true; + const auto guard = gsl::finally([] { + CheckingInstalled = false; + }); + if (!SUCCEEDED(CoInitialize(nullptr))) { + return false; + } + const auto coGuard = gsl::finally([] { + CoUninitialize(); + }); + return checkInstalled(); +#endif + }(); + if (Installed) { + return BaseId; + } + static const auto PortableId = [] { + std::string h(32, 0); + if (Core::Launcher::Instance().customWorkingDir()) { + const auto d = QFile::encodeName(QDir(cWorkingDir()).absolutePath()); + hashMd5Hex(d.constData(), d.size(), h.data()); + } else { + const auto exePath = QFile::encodeName(cExeDir() + cExeName()); + hashMd5Hex(exePath.constData(), exePath.size(), h.data()); + } + return BaseId + L'.' + std::wstring(h.begin(), h.end()); + }(); + return PortableId; } -const PROPERTYKEY &getKey() { +const PROPERTYKEY &Key() { return pkey_AppUserModel_ID; } diff --git a/Telegram/SourceFiles/platform/win/windows_app_user_model_id.h b/Telegram/SourceFiles/platform/win/windows_app_user_model_id.h index 464e44041..92d6c2b7d 100644 --- a/Telegram/SourceFiles/platform/win/windows_app_user_model_id.h +++ b/Telegram/SourceFiles/platform/win/windows_app_user_model_id.h @@ -12,13 +12,37 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Platform { namespace AppUserModelId { -void cleanupShortcut(); -void checkPinned(); +void CleanupShortcut(); +void CheckPinned(); -const WCHAR *getId(); -bool validateShortcut(); +[[nodiscard]] const std::wstring &Id(); +bool ValidateShortcut(); -const PROPERTYKEY &getKey(); +[[nodiscard]] const PROPERTYKEY &Key(); + +[[nodiscard]] const std::wstring &MyExecutablePath(); + +struct UniqueFileId { + std::uint64_t part1 = 0; + std::uint64_t part2 = 0; + + [[nodiscard]] bool valid() const { + return part1 || part2; + } + [[nodiscard]] explicit operator bool() const { + return valid(); + } + + [[nodiscard]] friend inline auto operator<=>( + UniqueFileId a, + UniqueFileId b) = default; + [[nodiscard]] friend inline bool operator==( + UniqueFileId a, + UniqueFileId b) = default; +}; + +[[nodiscard]] UniqueFileId GetUniqueFileId(LPCWSTR path); +[[nodiscard]] UniqueFileId MyExecutablePathId(); } // namespace AppUserModelId } // namespace Platform diff --git a/Telegram/SourceFiles/platform/win/windows_dlls.cpp b/Telegram/SourceFiles/platform/win/windows_dlls.cpp index f728fcce2..7184b2f4b 100644 --- a/Telegram/SourceFiles/platform/win/windows_dlls.cpp +++ b/Telegram/SourceFiles/platform/win/windows_dlls.cpp @@ -34,7 +34,6 @@ SafeIniter::SafeIniter() { LOAD_SYMBOL(LibShell32, OpenAs_RunDLL); LOAD_SYMBOL(LibShell32, SHQueryUserNotificationState); LOAD_SYMBOL(LibShell32, SHChangeNotify); - LOAD_SYMBOL(LibShell32, SetCurrentProcessExplicitAppUserModelID); //if (IsWindows10OrGreater()) { // static const auto kSystemVersion = QOperatingSystemVersion::current(); diff --git a/Telegram/SourceFiles/platform/win/windows_dlls.h b/Telegram/SourceFiles/platform/win/windows_dlls.h index b3695f269..eef6e9bc7 100644 --- a/Telegram/SourceFiles/platform/win/windows_dlls.h +++ b/Telegram/SourceFiles/platform/win/windows_dlls.h @@ -65,8 +65,6 @@ inline void(__stdcall *SHChangeNotify)( UINT uFlags, __in_opt LPCVOID dwItem1, __in_opt LPCVOID dwItem2); -inline HRESULT(__stdcall *SetCurrentProcessExplicitAppUserModelID)( - __in PCWSTR AppID); // PROPSYS.DLL diff --git a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_common.cpp b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_common.cpp index 42eb5b782..4a0ba8d68 100644 --- a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_common.cpp +++ b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_common.cpp @@ -13,7 +13,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "lottie/lottie_icon.h" #include "main/main_session.h" -#include "settings/cloud_password/settings_cloud_password_common.h" #include "settings/cloud_password/settings_cloud_password_email.h" #include "settings/cloud_password/settings_cloud_password_email_confirm.h" #include "settings/cloud_password/settings_cloud_password_hint.h" @@ -23,7 +22,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_common.h" #include "ui/boxes/confirm_box.h" #include "ui/widgets/buttons.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" +#include "ui/widgets/fields/password_input.h" #include "ui/widgets/labels.h" #include "ui/wrap/vertical_layout.h" #include "window/window_session_controller.h" diff --git a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_email.cpp b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_email.cpp index 1e13fae55..0fedabd93 100644 --- a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_email.cpp +++ b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_email.cpp @@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/cloud_password/settings_cloud_password_manage.h" #include "ui/boxes/confirm_box.h" #include "ui/widgets/buttons.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/wrap/padding_wrap.h" #include "ui/wrap/vertical_layout.h" #include "window/window_session_controller.h" @@ -90,9 +90,10 @@ void Email::setupContent() { currentStepDataEmail); const auto newInput = wrap->entity(); const auto error = AddError(content, nullptr); - QObject::connect(newInput, &Ui::InputField::changed, [=] { + newInput->changes( + ) | rpl::start_with_next([=] { error->hide(); - }); + }, newInput->lifetime()); AddSkipInsteadOfField(content); const auto send = [=](Fn close) { @@ -189,7 +190,7 @@ void Email::setupContent() { }); const auto submit = [=] { button->clicked({}, Qt::LeftButton); }; - QObject::connect(newInput, &Ui::InputField::submitted, submit); + newInput->submits() | rpl::start_with_next(submit, newInput->lifetime()); setFocusCallback([=] { newInput->setFocus(); }); diff --git a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_email_confirm.cpp b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_email_confirm.cpp index 09fc68aa0..24d7fb2ac 100644 --- a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_email_confirm.cpp +++ b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_email_confirm.cpp @@ -136,9 +136,10 @@ void EmailConfirm::setupContent() { std::move(objectInput))); const auto error = AddError(content, nullptr); - QObject::connect(newInput, &Ui::InputField::changed, [=] { + newInput->changes( + ) | rpl::start_with_next([=] { error->hide(); - }); + }, newInput->lifetime()); AddSkipInsteadOfField(content); const auto resendInfo = Ui::CreateChild( @@ -326,7 +327,7 @@ void EmailConfirm::setupContent() { const auto submit = [=] { button->clicked({}, Qt::LeftButton); }; newInput->setAutoSubmit(currentStepDataCodeLength, submit); - QObject::connect(newInput, &Ui::InputField::submitted, submit); + newInput->submits() | rpl::start_with_next(submit, newInput->lifetime()); setFocusCallback([=] { newInput->setFocus(); }); diff --git a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_hint.cpp b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_hint.cpp index e7451ab83..f2a89376e 100644 --- a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_hint.cpp +++ b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_hint.cpp @@ -13,7 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/cloud_password/settings_cloud_password_email.h" #include "settings/cloud_password/settings_cloud_password_manage.h" #include "ui/widgets/buttons.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/labels.h" #include "ui/wrap/padding_wrap.h" #include "ui/wrap/vertical_layout.h" @@ -79,9 +79,10 @@ void Hint::setupContent() { currentStepDataHint); const auto newInput = wrap->entity(); const auto error = AddError(content, nullptr); - QObject::connect(newInput, &Ui::InputField::changed, [=] { + newInput->changes( + ) | rpl::start_with_next([=] { error->hide(); - }); + }, newInput->lifetime()); AddSkipInsteadOfField(content); const auto save = [=](const QString &hint) { @@ -142,7 +143,7 @@ void Hint::setupContent() { }); const auto submit = [=] { button->clicked({}, Qt::LeftButton); }; - QObject::connect(newInput, &Ui::InputField::submitted, submit); + newInput->submits() | rpl::start_with_next(submit, newInput->lifetime()); setFocusCallback([=] { newInput->setFocus(); }); diff --git a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_input.cpp b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_input.cpp index 49af9c6a1..e532117b7 100644 --- a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_input.cpp +++ b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_input.cpp @@ -21,7 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/boxes/confirm_box.h" #include "ui/text/format_values.h" #include "ui/widgets/buttons.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/password_input.h" #include "ui/widgets/labels.h" #include "ui/wrap/vertical_layout.h" #include "window/window_session_controller.h" diff --git a/Telegram/SourceFiles/settings/settings_advanced.cpp b/Telegram/SourceFiles/settings/settings_advanced.cpp index 8d64aad20..60c6ed1e3 100644 --- a/Telegram/SourceFiles/settings/settings_advanced.cpp +++ b/Telegram/SourceFiles/settings/settings_advanced.cpp @@ -409,7 +409,9 @@ void SetupWindowTitleContent( if (Ui::Platform::NativeWindowFrameSupported()) { const auto nativeFrame = addCheckbox( - tr::lng_settings_native_frame(), + Platform::IsWayland() + ? tr::lng_settings_qt_frame() + : tr::lng_settings_native_frame(), Core::App().settings().nativeWindowFrame()); nativeFrame->checkedChanges( @@ -550,28 +552,29 @@ void SetupSystemIntegrationContent( Core::App().saveSettings(); }, roundIcon->lifetime()); #endif // OS_MAC_STORE - -#else // Q_OS_MAC - const auto closeToTaskbar = addSlidingCheckbox( - tr::lng_settings_close_to_taskbar(), - Core::App().settings().closeToTaskbar()); - - const auto closeToTaskbarShown = std::make_shared>(false); - Core::App().settings().workModeValue( - ) | rpl::start_with_next([=](WorkMode workMode) { - *closeToTaskbarShown = !Core::App().tray().has(); - }, closeToTaskbar->lifetime()); - - closeToTaskbar->toggleOn(closeToTaskbarShown->value()); - closeToTaskbar->entity()->checkedChanges( - ) | rpl::filter([=](bool checked) { - return (checked != Core::App().settings().closeToTaskbar()); - }) | rpl::start_with_next([=](bool checked) { - Core::App().settings().setCloseToTaskbar(checked); - Local::writeSettings(); - }, closeToTaskbar->lifetime()); #endif // Q_OS_MAC + if (!Platform::RunInBackground()) { + const auto closeToTaskbar = addSlidingCheckbox( + tr::lng_settings_close_to_taskbar(), + Core::App().settings().closeToTaskbar()); + + const auto closeToTaskbarShown = std::make_shared>(false); + Core::App().settings().workModeValue( + ) | rpl::start_with_next([=](WorkMode workMode) { + *closeToTaskbarShown = !Core::App().tray().has(); + }, closeToTaskbar->lifetime()); + + closeToTaskbar->toggleOn(closeToTaskbarShown->value()); + closeToTaskbar->entity()->checkedChanges( + ) | rpl::filter([=](bool checked) { + return (checked != Core::App().settings().closeToTaskbar()); + }) | rpl::start_with_next([=](bool checked) { + Core::App().settings().setCloseToTaskbar(checked); + Local::writeSettings(); + }, closeToTaskbar->lifetime()); + } + if (Platform::AutostartSupported() && controller) { const auto minimizedToggled = [=] { return cStartMinimized() diff --git a/Telegram/SourceFiles/settings/settings_chat.cpp b/Telegram/SourceFiles/settings/settings_chat.cpp index 6b353438b..01b6437f3 100644 --- a/Telegram/SourceFiles/settings/settings_chat.cpp +++ b/Telegram/SourceFiles/settings/settings_chat.cpp @@ -21,7 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/local_storage_box.h" #include "ui/wrap/vertical_layout.h" #include "ui/wrap/slide_wrap.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/color_editor.h" #include "ui/widgets/buttons.h" @@ -1016,6 +1016,23 @@ void SetupMessages( AddSkip(inner, st::settingsCheckboxesSkip); } +void SetupArchive( + not_null controller, + not_null container) { + AddDivider(container); + AddSkip(container); + + PreloadArchiveSettings(&controller->session()); + AddButton( + container, + tr::lng_context_archive_settings(), + st::settingsButton, + { &st::menuIconArchive } + )->addClickHandler([=] { + controller->show(Box(Settings::ArchiveSettingsBox, controller)); + }); +} + void SetupExport( not_null controller, not_null container, @@ -1737,6 +1754,7 @@ void Chat::setupContent(not_null controller) { SetupChatBackground(controller, content); SetupStickersEmoji(controller, content); SetupMessages(controller, content); + SetupArchive(controller, content); Ui::ResizeFitChild(this, content); } diff --git a/Telegram/SourceFiles/settings/settings_experimental.cpp b/Telegram/SourceFiles/settings/settings_experimental.cpp index 74af35edf..97447adf1 100644 --- a/Telegram/SourceFiles/settings/settings_experimental.cpp +++ b/Telegram/SourceFiles/settings/settings_experimental.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "core/launcher.h" #include "chat_helpers/tabbed_panel.h" +#include "dialogs/dialogs_widget.h" #include "lang/lang_keys.h" #include "mainwindow.h" #include "media/player/media_player_instance.h" @@ -137,6 +138,7 @@ void SetupExperimental( }; addToggle(ChatHelpers::kOptionTabbedPanelShowOnClick); + addToggle(Dialogs::kOptionForumHideChatsList); addToggle(Core::kOptionFractionalScalingEnabled); addToggle(Window::kOptionViewProfileInChatsListContextMenu); addToggle(Ui::GL::kOptionAllowLinuxNvidiaOpenGL); diff --git a/Telegram/SourceFiles/settings/settings_folders.cpp b/Telegram/SourceFiles/settings/settings_folders.cpp index 18a3630a7..c3ef96f03 100644 --- a/Telegram/SourceFiles/settings/settings_folders.cpp +++ b/Telegram/SourceFiles/settings/settings_folders.cpp @@ -30,7 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_utilities.h" #include "ui/widgets/box_content_divider.h" #include "ui/widgets/buttons.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/labels.h" #include "ui/wrap/slide_wrap.h" #include "window/window_controller.h" @@ -362,10 +362,11 @@ void FilterRowButton::paintEvent(QPaintEvent *e) { const auto removed = ranges::count_if( state->rows, &FilterRow::removed); - if (state->rows.size() < limit() + removed) { + const auto count = int(state->rows.size() - removed); + if (count < limit()) { return false; } - controller->show(Box(FiltersLimitBox, session)); + controller->show(Box(FiltersLimitBox, session, count)); return true; }; const auto markForRemovalSure = [=](not_null button) { diff --git a/Telegram/SourceFiles/settings/settings_information.cpp b/Telegram/SourceFiles/settings/settings_information.cpp index da3f97859..9e9fedbb1 100644 --- a/Telegram/SourceFiles/settings/settings_information.cpp +++ b/Telegram/SourceFiles/settings/settings_information.cpp @@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/slide_wrap.h" #include "ui/widgets/labels.h" #include "ui/widgets/buttons.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/popup_menu.h" #include "ui/widgets/box_content_divider.h" #include "ui/widgets/menu/menu_add_action_callback_factory.h" @@ -538,10 +538,8 @@ void SetupBio( auto cursor = bio->textCursor(); cursor.setPosition(bio->getLastText().size()); bio->setTextCursor(cursor); - QObject::connect(bio, &Ui::InputField::submitted, [=] { - save(); - }); - QObject::connect(bio, &Ui::InputField::changed, updated); + bio->submits() | rpl::start_with_next([=] { save(); }, bio->lifetime()); + bio->changes() | rpl::start_with_next(updated, bio->lifetime()); bio->setInstantReplaces(Ui::InstantReplaces::Default()); bio->setInstantReplacesEnabled( Core::App().settings().replaceEmojiValue()); diff --git a/Telegram/SourceFiles/settings/settings_local_passcode.cpp b/Telegram/SourceFiles/settings/settings_local_passcode.cpp index e1c5d56d8..49e8ba019 100644 --- a/Telegram/SourceFiles/settings/settings_local_passcode.cpp +++ b/Telegram/SourceFiles/settings/settings_local_passcode.cpp @@ -20,7 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/storage_domain.h" #include "ui/boxes/confirm_box.h" #include "ui/widgets/buttons.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/password_input.h" #include "ui/widgets/labels.h" #include "ui/wrap/vertical_layout.h" #include "window/window_session_controller.h" diff --git a/Telegram/SourceFiles/settings/settings_notifications.cpp b/Telegram/SourceFiles/settings/settings_notifications.cpp index 02240041f..fb8397903 100644 --- a/Telegram/SourceFiles/settings/settings_notifications.cpp +++ b/Telegram/SourceFiles/settings/settings_notifications.cpp @@ -28,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/platform_specific.h" #include "platform/platform_notifications_manager.h" #include "base/platform/base_platform_info.h" +#include "base/call_delayed.h" #include "mainwindow.h" #include "core/application.h" #include "main/main_session.h" @@ -49,7 +50,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_dialogs.h" #include -#include namespace Settings { namespace { @@ -704,11 +704,11 @@ void NotificationsCount::SampleWidget::destroyDelayed() { _deleted = true; // Ubuntu has a lag if deleteLater() called immediately. -#if defined Q_OS_UNIX && !defined Q_OS_MAC - QTimer::singleShot(1000, [this] { delete this; }); -#else // Q_OS_UNIX && !Q_OS_MAC - deleteLater(); -#endif // Q_OS_UNIX && !Q_OS_MAC + if constexpr (Platform::IsLinux()) { + base::call_delayed(1000, this, [this] { delete this; }); + } else { + deleteLater(); + } } class NotifyPreview final { diff --git a/Telegram/SourceFiles/stdafx.h b/Telegram/SourceFiles/stdafx.h index 795adbddd..153aa953f 100644 --- a/Telegram/SourceFiles/stdafx.h +++ b/Telegram/SourceFiles/stdafx.h @@ -76,9 +76,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include // Fix Google Breakpad build for Mac App Store and Linux version -#ifdef Q_OS_UNIX +#ifndef Q_OS_WIN #define __STDC_FORMAT_MACROS -#endif // Q_OS_UNIX +#endif // !Q_OS_WIN // Remove 'small' macro definition. #ifdef Q_OS_WIN diff --git a/Telegram/SourceFiles/storage/details/storage_settings_scheme.cpp b/Telegram/SourceFiles/storage/details/storage_settings_scheme.cpp index 6940e8139..a1474b712 100644 --- a/Telegram/SourceFiles/storage/details/storage_settings_scheme.cpp +++ b/Telegram/SourceFiles/storage/details/storage_settings_scheme.cpp @@ -13,7 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "core/core_settings.h" #include "mtproto/mtproto_config.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/chat/attach/attach_send_files_way.h" #include "ui/power_saving.h" #include "window/themes/window_theme.h" diff --git a/Telegram/SourceFiles/storage/storage_account.cpp b/Telegram/SourceFiles/storage/storage_account.cpp index f0bda9aa6..60fb45382 100644 --- a/Telegram/SourceFiles/storage/storage_account.cpp +++ b/Telegram/SourceFiles/storage/storage_account.cpp @@ -2808,7 +2808,13 @@ void Account::writeTrustedBots() { } void Account::readTrustedBots() { - if (!_trustedBotsKey) return; + if (_trustedBotsRead) { + return; + } + _trustedBotsRead = true; + if (!_trustedBotsKey) { + return; + } FileReadDescriptor trusted; if (!ReadEncryptedFile(trusted, _trustedBotsKey, _basePath, _localKey)) { @@ -2845,10 +2851,7 @@ void Account::markBotTrustedOpenGame(PeerId botId) { } bool Account::isBotTrustedOpenGame(PeerId botId) { - if (!_trustedBotsRead) { - readTrustedBots(); - _trustedBotsRead = true; - } + readTrustedBots(); const auto i = _trustedBots.find(botId); return (i != end(_trustedBots)) && ((i->second & BotTrustFlag::NoOpenGame) == 0); @@ -2870,10 +2873,7 @@ void Account::markBotTrustedPayment(PeerId botId) { } bool Account::isBotTrustedPayment(PeerId botId) { - if (!_trustedBotsRead) { - readTrustedBots(); - _trustedBotsRead = true; - } + readTrustedBots(); const auto i = _trustedBots.find(botId); return (i != end(_trustedBots)) && ((i->second & BotTrustFlag::Payment) != 0); @@ -2895,10 +2895,7 @@ void Account::markBotTrustedOpenWebView(PeerId botId) { } bool Account::isBotTrustedOpenWebView(PeerId botId) { - if (!_trustedBotsRead) { - readTrustedBots(); - _trustedBotsRead = true; - } + readTrustedBots(); const auto i = _trustedBots.find(botId); return (i != end(_trustedBots)) && ((i->second & BotTrustFlag::OpenWebView) != 0); diff --git a/Telegram/SourceFiles/storage/storage_account.h b/Telegram/SourceFiles/storage/storage_account.h index c96ba84f8..3c1525ed7 100644 --- a/Telegram/SourceFiles/storage/storage_account.h +++ b/Telegram/SourceFiles/storage/storage_account.h @@ -184,9 +184,9 @@ private: Failed, }; enum class BotTrustFlag : uchar { - NoOpenGame = (1 << 0), - Payment = (1 << 1), - OpenWebView = (1 << 2), + NoOpenGame = (1 << 0), + Payment = (1 << 1), + OpenWebView = (1 << 2), }; friend inline constexpr bool is_flag_type(BotTrustFlag) { return true; }; diff --git a/Telegram/SourceFiles/support/support_autocomplete.cpp b/Telegram/SourceFiles/support/support_autocomplete.cpp index aba4ee89a..aaf9fec54 100644 --- a/Telegram/SourceFiles/support/support_autocomplete.cpp +++ b/Telegram/SourceFiles/support/support_autocomplete.cpp @@ -10,7 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/chat_theme.h" #include "ui/chat/chat_style.h" #include "ui/widgets/scroll_area.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/buttons.h" #include "ui/wrap/padding_wrap.h" #include "ui/painter.h" @@ -418,16 +418,20 @@ void Autocomplete::setupContent() { }; inner->activated() | rpl::start_with_next(submit, lifetime()); - connect(input, &Ui::InputField::blurred, [=] { + input->focusedChanges( + ) | rpl::filter(!rpl::mappers::_1) | rpl::start_with_next([=] { base::call_delayed(10, this, [=] { if (!input->hasFocus()) { deactivate(); } }); - }); - connect(input, &Ui::InputField::cancelled, [=] { deactivate(); }); - connect(input, &Ui::InputField::changed, refresh); - connect(input, &Ui::InputField::submitted, submit); + }, input->lifetime()); + input->cancelled( + ) | rpl::start_with_next([=] { + deactivate(); + }, input->lifetime()); + input->changes() | rpl::start_with_next(refresh, input->lifetime()); + input->submits() | rpl::start_with_next(submit, input->lifetime()); input->customUpDown(true); _activate = [=] { diff --git a/Telegram/SourceFiles/support/support_helper.cpp b/Telegram/SourceFiles/support/support_helper.cpp index cb8fe49c5..fd273c09f 100644 --- a/Telegram/SourceFiles/support/support_helper.cpp +++ b/Telegram/SourceFiles/support/support_helper.cpp @@ -18,7 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "boxes/abstract_box.h" #include "ui/toast/toast.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/chat/attach/attach_prepare.h" #include "ui/text/format_values.h" #include "ui/text/text_entity.h" @@ -109,8 +109,11 @@ void EditInfoBox::prepare() { addButton(tr::lng_settings_save(), save); addButton(tr::lng_cancel(), [=] { closeBox(); }); - connect(_field, &Ui::InputField::submitted, save); - connect(_field, &Ui::InputField::cancelled, [=] { closeBox(); }); + _field->submits() | rpl::start_with_next(save, _field->lifetime()); + _field->cancelled( + ) | rpl::start_with_next([=] { + closeBox(); + }, _field->lifetime()); Ui::Emoji::SuggestionsController::Init( getDelegate()->outerContainer(), _field, diff --git a/Telegram/SourceFiles/ui/boxes/boost_box.cpp b/Telegram/SourceFiles/ui/boxes/boost_box.cpp new file mode 100644 index 000000000..4e611e635 --- /dev/null +++ b/Telegram/SourceFiles/ui/boxes/boost_box.cpp @@ -0,0 +1,278 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/boxes/boost_box.h" + +#include "lang/lang_keys.h" +#include "ui/effects/fireworks_animation.h" +#include "ui/effects/premium_graphics.h" +#include "ui/layers/generic_box.h" +#include "ui/text/text_utilities.h" +#include "ui/widgets/buttons.h" +#include "styles/style_layers.h" +#include "styles/style_premium.h" + +namespace Ui { +namespace { + +void StartFireworks(not_null parent) { + const auto result = Ui::CreateChild(parent.get()); + result->setAttribute(Qt::WA_TransparentForMouseEvents); + result->setGeometry(parent->rect()); + result->show(); + + auto &lifetime = result->lifetime(); + const auto animation = lifetime.make_state([=] { + result->update(); + }); + result->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(result); + if (!animation->paint(p, result->rect())) { + crl::on_main(result, [=] { delete result; }); + } + }, lifetime); +} + +} // namespace + +void BoostBox( + not_null box, + BoostBoxData data, + Fn)> boost) { + box->setWidth(st::boxWideWidth); + box->setStyle(st::boostBox); + + const auto full = !data.boost.nextLevelBoosts; + + if (data.boost.mine && data.boost.boosts > 0) { + --data.boost.boosts; + } + + if (full) { + data.boost.nextLevelBoosts = data.boost.boosts + + (data.boost.mine ? 1 : 0); + data.boost.thisLevelBoosts = 0; + if (data.boost.level > 0) { + --data.boost.level; + } + } else if (data.boost.mine + && data.boost.level > 0 + && data.boost.boosts < data.boost.thisLevelBoosts) { + --data.boost.level; + data.boost.nextLevelBoosts = data.boost.thisLevelBoosts; + data.boost.thisLevelBoosts = 0; + } + + struct State { + rpl::variable you = false; + bool submitted = false; + }; + const auto state = box->lifetime().make_state(State{ + .you = data.boost.mine, + }); + box->addTopButton(st::boxTitleClose, [=] { + box->closeBox(); + }); + + const auto addSkip = [&](int skip) { + box->addRow(object_ptr(box, skip)); + }; + + addSkip(st::boostSkipTop); + + const auto levelWidth = [&](int add) { + return st::normalFont->width( + tr::lng_boost_level(tr::now, lt_count, data.boost.level + add)); + }; + const auto paddings = 2 * st::premiumLineTextSkip; + const auto labelLeftWidth = paddings + levelWidth(0); + const auto labelRightWidth = paddings + levelWidth(1); + const auto ratio = [=](int boosts) { + const auto min = std::min( + data.boost.boosts, + data.boost.thisLevelBoosts); + const auto max = std::max({ + data.boost.boosts, + data.boost.nextLevelBoosts, + 1, + }); + Assert(boosts >= min && boosts <= max); + const auto count = (max - min); + const auto index = (boosts - min); + if (!index) { + return 0.; + } else if (index == count) { + return 1.; + } else if (count == 2) { + return 0.5; + } + const auto available = st::boxWideWidth + - st::boxPadding.left() + - st::boxPadding.right(); + const auto average = available / float64(count); + const auto first = std::max(average, labelLeftWidth * 1.); + const auto last = std::max(average, labelRightWidth * 1.); + const auto other = (available - first - last) / (count - 2); + return (first + (index - 1) * other) / available; + }; + + const auto min = std::min( + data.boost.boosts, + data.boost.thisLevelBoosts); + const auto now = data.boost.boosts; + const auto max = (data.boost.nextLevelBoosts > min) + ? (data.boost.nextLevelBoosts) + : (data.boost.boosts > 0) + ? data.boost.boosts + : 1; + auto bubbleRowState = state->you.value( + ) | rpl::map([=](bool mine) { + const auto index = mine ? (now + 1) : now; + return Premium::BubbleRowState{ + .counter = index, + .ratio = ratio(index), + .dynamic = true, + }; + }); + Premium::AddBubbleRow( + box->verticalLayout(), + st::boostBubble, + BoxShowFinishes(box), + rpl::duplicate(bubbleRowState), + max, + true, + nullptr, + &st::premiumIconBoost); + addSkip(st::premiumLineTextSkip); + + const auto level = [](int level) { + return tr::lng_boost_level(tr::now, lt_count, level); + }; + auto ratioValue = std::move( + bubbleRowState + ) | rpl::map([](const Premium::BubbleRowState &state) { + return state.ratio; + }); + Premium::AddLimitRow( + box->verticalLayout(), + st::boostLimits, + Premium::LimitRowLabels{ + .leftLabel = level(data.boost.level), + .rightLabel = level(data.boost.level + 1), + .dynamic = true, + }, + std::move(ratioValue)); + + const auto name = data.name; + auto title = state->you.value() | rpl::map([=](bool your) { + return your + ? tr::lng_boost_channel_you_title( + lt_channel, + rpl::single(data.name)) + : full + ? tr::lng_boost_channel_title_max() + : !data.boost.level + ? tr::lng_boost_channel_title_first() + : tr::lng_boost_channel_title_more(); + }) | rpl::flatten_latest(); + auto text = state->you.value() | rpl::map([=](bool your) { + const auto bold = Ui::Text::Bold(data.name); + const auto now = data.boost.boosts + (your ? 1 : 0); + const auto left = (data.boost.nextLevelBoosts > now) + ? (data.boost.nextLevelBoosts - now) + : 0; + auto post = tr::lng_boost_channel_post_stories( + lt_count, + rpl::single(float64(data.boost.level + 1)), + Ui::Text::RichLangValue); + return (your || full) + ? ((!full && left > 0) + ? (!data.boost.level + ? tr::lng_boost_channel_you_first( + lt_count, + rpl::single(float64(left)), + Ui::Text::RichLangValue) + : tr::lng_boost_channel_you_more( + lt_count, + rpl::single(float64(left)), + lt_post, + std::move(post), + Ui::Text::RichLangValue)) + : (!data.boost.level + ? tr::lng_boost_channel_reached_first( + Ui::Text::RichLangValue) + : tr::lng_boost_channel_reached_more( + lt_count, + rpl::single(float64(data.boost.level + 1)), + lt_post, + std::move(post), + Ui::Text::RichLangValue))) + : !data.boost.level + ? tr::lng_boost_channel_needs_first( + lt_count, + rpl::single(float64(left)), + lt_channel, + rpl::single(bold), + Ui::Text::RichLangValue) + : tr::lng_boost_channel_needs_more( + lt_count, + rpl::single(float64(left)), + lt_channel, + rpl::single(bold), + lt_post, + std::move(post), + Ui::Text::RichLangValue); + }) | rpl::flatten_latest(); + box->addRow( + object_ptr( + box, + std::move(title), + st::boostTitle), + st::boxRowPadding + QMargins(0, st::boostTitleSkip, 0, 0)); + box->addRow( + object_ptr( + box, + std::move(text), + st::boostText), + (st::boxRowPadding + + QMargins(0, st::boostTextSkip, 0, st::boostBottomSkip))); + + auto submit = full + ? (tr::lng_box_ok() | rpl::type_erased()) + : state->you.value( + ) | rpl::map([](bool mine) { + return mine ? tr::lng_box_ok() : tr::lng_boost_channel_button(); + }) | rpl::flatten_latest(); + const auto button = box->addButton(rpl::duplicate(submit), [=] { + if (state->submitted) { + return; + } else if (!full && !state->you.current()) { + state->submitted = true; + boost(crl::guard(box, [=](bool success) { + state->submitted = false; + if (success) { + StartFireworks(box->parentWidget()); + state->you = true; + } + })); + } else { + box->closeBox(); + } + }); + rpl::combine( + std::move(submit), + box->widthValue() + ) | rpl::start_with_next([=](const QString &, int width) { + const auto &padding = st::boostBox.buttonPadding; + button->resizeToWidth(width + - padding.left() + - padding.right()); + button->moveToLeft(padding.left(), button->y()); + }, button->lifetime()); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/boxes/boost_box.h b/Telegram/SourceFiles/ui/boxes/boost_box.h new file mode 100644 index 000000000..2036cfb06 --- /dev/null +++ b/Telegram/SourceFiles/ui/boxes/boost_box.h @@ -0,0 +1,32 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace Ui { + +class GenericBox; + +struct BoostCounters { + int level = 0; + int boosts = 0; + int thisLevelBoosts = 0; + int nextLevelBoosts = 0; // Zero means no next level is available. + bool mine = false; +}; + +struct BoostBoxData { + QString name; + BoostCounters boost; +}; + +void BoostBox( + not_null box, + BoostBoxData data, + Fn)> boost); + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/boxes/choose_date_time.cpp b/Telegram/SourceFiles/ui/boxes/choose_date_time.cpp index a56957bc4..e4d8092a5 100644 --- a/Telegram/SourceFiles/ui/boxes/choose_date_time.cpp +++ b/Telegram/SourceFiles/ui/boxes/choose_date_time.cpp @@ -11,13 +11,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/event_filter.h" #include "ui/boxes/calendar_box.h" #include "ui/widgets/buttons.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/time_input.h" #include "ui/ui_utility.h" #include "lang/lang_keys.h" #include "styles/style_layers.h" #include "styles/style_boxes.h" +#include + namespace Ui { namespace { @@ -147,8 +149,9 @@ ChooseDateTimeBoxDescriptor ChooseDateTimeBox( const auto calendar = content->lifetime().make_state>(); const auto calendarStyle = args.style.calendarStyle; - QObject::connect(state->day, &InputField::focused, [=] { - if (*calendar) { + state->day->focusedChanges( + ) | rpl::start_with_next([=](bool focused) { + if (*calendar || !focused) { return; } *calendar = box->getDelegate()->show( @@ -167,7 +170,7 @@ ChooseDateTimeBoxDescriptor ChooseDateTimeBox( ) | rpl::start_with_next(crl::guard(state->time, [=] { state->time->setFocusFast(); }), (*calendar)->lifetime()); - }); + }, state->day->lifetime()); const auto collect = [=] { const auto timeValue = state->time->valueCurrent().split(':'); diff --git a/Telegram/SourceFiles/ui/boxes/confirm_box.cpp b/Telegram/SourceFiles/ui/boxes/confirm_box.cpp index 070f84b29..22b2b3987 100644 --- a/Telegram/SourceFiles/ui/boxes/confirm_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/confirm_box.cpp @@ -24,7 +24,9 @@ void ConfirmBox(not_null box, ConfirmBoxArgs &&args) { if (!v::is_null(args.text)) { const auto padding = st::boxPadding; - const auto use = withTitle + const auto use = args.labelPadding + ? *args.labelPadding + : withTitle ? QMargins(padding.left(), 0, padding.right(), padding.bottom()) : padding; const auto label = box->addRow( diff --git a/Telegram/SourceFiles/ui/boxes/confirm_box.h b/Telegram/SourceFiles/ui/boxes/confirm_box.h index b66be3d3a..b65a04361 100644 --- a/Telegram/SourceFiles/ui/boxes/confirm_box.h +++ b/Telegram/SourceFiles/ui/boxes/confirm_box.h @@ -30,6 +30,7 @@ struct ConfirmBoxArgs { const style::FlatLabel *labelStyle = nullptr; Fn labelFilter; + std::optional labelPadding; v::text::data title = v::null; diff --git a/Telegram/SourceFiles/ui/boxes/confirm_phone_box.cpp b/Telegram/SourceFiles/ui/boxes/confirm_phone_box.cpp index 9fab7f4a5..e69cd3e5f 100644 --- a/Telegram/SourceFiles/ui/boxes/confirm_phone_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/confirm_phone_box.cpp @@ -73,7 +73,8 @@ void ConfirmPhoneBox::prepare() { + st::usernameSkip + (_fragment ? (_fragment->height() + fragmentSkip()) : 0)); - connect(_code, &Ui::InputField::submitted, [=] { sendCode(); }); + _code->submits( + ) | rpl::start_with_next([=] { sendCode(); }, _code->lifetime()); showChildren(); } diff --git a/Telegram/SourceFiles/ui/boxes/country_select_box.h b/Telegram/SourceFiles/ui/boxes/country_select_box.h index 507e478e9..553fd39e2 100644 --- a/Telegram/SourceFiles/ui/boxes/country_select_box.h +++ b/Telegram/SourceFiles/ui/boxes/country_select_box.h @@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "ui/layers/box_content.h" -#include "styles/style_widgets.h" namespace Countries { struct Info; diff --git a/Telegram/SourceFiles/ui/boxes/edit_invite_link.cpp b/Telegram/SourceFiles/ui/boxes/edit_invite_link.cpp index a27a18859..b7805d503 100644 --- a/Telegram/SourceFiles/ui/boxes/edit_invite_link.cpp +++ b/Telegram/SourceFiles/ui/boxes/edit_invite_link.cpp @@ -10,7 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "ui/boxes/choose_date_time.h" #include "ui/widgets/labels.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" +#include "ui/widgets/fields/number_input.h" #include "ui/widgets/checkbox.h" #include "ui/wrap/slide_wrap.h" #include "base/unixtime.h" @@ -212,7 +213,7 @@ void EditInviteLinkBox( ? tr::lng_group_invite_usage_any(tr::now) : !limit ? tr::lng_group_invite_usage_custom(tr::now) - : QString("%L1").arg(limit); + : Lang::FormatCountDecimal(limit); state->usageButtons.emplace( limit, addButton(usagesWrap, usageGroup, limit, text)); diff --git a/Telegram/SourceFiles/ui/boxes/rate_call_box.cpp b/Telegram/SourceFiles/ui/boxes/rate_call_box.cpp index 8d88bde67..d56889f0b 100644 --- a/Telegram/SourceFiles/ui/boxes/rate_call_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/rate_call_box.cpp @@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "ui/widgets/buttons.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "styles/style_layers.h" #include "styles/style_calls.h" @@ -92,11 +92,16 @@ void RateCallBox::ratingChanged(int value) { _comment->height()); updateMaxHeight(); - connect(_comment, &InputField::resized, [=] { + _comment->heightChanges( + ) | rpl::start_with_next([=] { commentResized(); - }); - connect(_comment, &InputField::submitted, [=] { send(); }); - connect(_comment, &InputField::cancelled, [=] { closeBox(); }); + }, _comment->lifetime()); + _comment->submits( + ) | rpl::start_with_next([=] { send(); }, _comment->lifetime()); + _comment->cancelled( + ) | rpl::start_with_next([=] { + closeBox(); + }, _comment->lifetime()); } _comment->setFocusFast(); } else if (_comment) { diff --git a/Telegram/SourceFiles/ui/boxes/report_box.cpp b/Telegram/SourceFiles/ui/boxes/report_box.cpp index fa0ac22ff..34c678bb4 100644 --- a/Telegram/SourceFiles/ui/boxes/report_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/report_box.cpp @@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/layers/generic_box.h" #include "ui/wrap/vertical_layout.h" #include "ui/widgets/buttons.h" -#include "ui/widgets/input_fields.h" +#include "ui/widgets/fields/input_field.h" #include "ui/toast/toast.h" #include "info/profile/info_profile_icon.h" #include "styles/style_chat_helpers.h" @@ -148,7 +148,8 @@ void ReportDetailsBox( const auto text = details->getLastText(); done(text); }; - QObject::connect(details, &InputField::submitted, submit); + details->submits( + ) | rpl::start_with_next(submit, details->lifetime()); box->addButton(tr::lng_report_button(), submit); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); } diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp index 1930c5484..96546ae00 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp @@ -315,25 +315,12 @@ Panel::Progress::Progress(QWidget *parent, Fn rect) Panel::Panel( const QString &userDataPath, rpl::producer title, - Fn handleLocalUri, - Fn handleInvoice, - Fn sendData, - Fn, QString)> switchInlineQuery, - Fn close, - QString phone, + not_null delegate, MenuButtons menuButtons, - Fn handleMenuButton, - Fn themeParams, bool allowClipboardRead) : _userDataPath(userDataPath) -, _handleLocalUri(std::move(handleLocalUri)) -, _handleInvoice(std::move(handleInvoice)) -, _sendData(std::move(sendData)) -, _switchInlineQuery(std::move(switchInlineQuery)) -, _close(std::move(close)) -, _phone(phone) +, _delegate(delegate) , _menuButtons(menuButtons) -, _handleMenuButton(std::move(handleMenuButton)) , _widget(std::make_unique()) , _allowClipboardRead(allowClipboardRead) { _widget->setInnerSize(st::paymentsPanelSize); @@ -344,14 +331,16 @@ Panel::Panel( if (_closeNeedConfirmation) { scheduleCloseWithConfirmation(); } else { - _close(); + _delegate->botClose(); } }, _widget->lifetime()); _widget->closeEvents( ) | rpl::filter([=] { return !_hiddenForPayment; - }) | rpl::start_with_next(_close, _widget->lifetime()); + }) | rpl::start_with_next([=] { + _delegate->botClose(); + }, _widget->lifetime()); _widget->backRequests( ) | rpl::start_with_next([=] { @@ -367,7 +356,7 @@ Panel::Panel( _themeUpdateScheduled = true; crl::on_main(_widget.get(), [=] { _themeUpdateScheduled = false; - updateThemeParams(themeParams()); + updateThemeParams(_delegate->botThemeParams()); }); }, _widget->lifetime()); @@ -505,7 +494,7 @@ bool Panel::showWebview( const QString &url, const Webview::ThemeParams ¶ms, rpl::producer bottomText) { - if (!_webview && !createWebview()) { + if (!_webview && !createWebview(params)) { return false; } const auto allowBack = false; @@ -540,18 +529,23 @@ bool Panel::showWebview( } if (_menuButtons & MenuButton::OpenBot) { callback(tr::lng_bot_open(tr::now), [=] { - _handleMenuButton(MenuButton::OpenBot); + _delegate->botHandleMenuButton(MenuButton::OpenBot); }, &st::menuIconLeave); } callback(tr::lng_bot_reload_page(tr::now), [=] { _webview->window.reload(); }, &st::menuIconRestore); - if (_menuButtons & MenuButton::RemoveFromMenu) { + const auto main = (_menuButtons & MenuButton::RemoveFromMainMenu); + if (main || (_menuButtons & MenuButton::RemoveFromMenu)) { const auto handler = [=] { - _handleMenuButton(MenuButton::RemoveFromMenu); + _delegate->botHandleMenuButton(main + ? MenuButton::RemoveFromMainMenu + : MenuButton::RemoveFromMenu); }; callback({ - .text = tr::lng_bot_remove_from_menu(tr::now), + .text = (main + ? tr::lng_bot_remove_from_side_menu + : tr::lng_bot_remove_from_menu)(tr::now), .handler = handler, .icon = &st::menuIconDeleteAttention, .isAttention = true, @@ -561,7 +555,7 @@ bool Panel::showWebview( return true; } -bool Panel::createWebview() { +bool Panel::createWebview(const Webview::ThemeParams ¶ms) { auto outer = base::make_unique_q(_widget.get()); const auto container = outer.get(); _widget->showInner(std::move(outer)); @@ -586,6 +580,7 @@ bool Panel::createWebview() { _webview = std::make_unique( container, Webview::WindowConfig{ + .opaqueBg = params.opaqueBg, .userDataPath = _userDataPath, }); const auto raw = &_webview->window; @@ -624,7 +619,7 @@ bool Panel::createWebview() { const auto command = list.at(0).toString(); const auto arguments = ParseMethodArgs(list.at(1).toString()); if (command == "web_app_close") { - _close(); + _delegate->botClose(); } else if (command == "web_app_data_send") { sendDataMessage(arguments); } else if (command == "web_app_switch_inline_query") { @@ -645,17 +640,23 @@ bool Panel::createWebview() { openInvoice(arguments); } else if (command == "web_app_open_popup") { openPopup(arguments); + } else if (command == "web_app_request_write_access") { + requestWriteAccess(); } else if (command == "web_app_request_phone") { requestPhone(); + } else if (command == "web_app_invoke_custom_method") { + invokeCustomMethod(arguments); } else if (command == "web_app_setup_closing_behavior") { setupClosingBehaviour(arguments); } else if (command == "web_app_read_text_from_clipboard") { requestClipboardText(arguments); + } else if (command == "web_app_set_header_color") { + processHeaderColor(arguments); } }); raw->setNavigationStartHandler([=](const QString &uri, bool newWindow) { - if (_handleLocalUri(uri)) { + if (_delegate->botHandleLocalUri(uri)) { return false; } else if (newWindow) { return true; @@ -694,27 +695,27 @@ void Panel::setTitle(rpl::producer title) { void Panel::sendDataMessage(const QJsonObject &args) { if (args.isEmpty()) { - _close(); + _delegate->botClose(); return; } const auto data = args["data"].toString(); if (data.isEmpty()) { LOG(("BotWebView Error: Bad 'data' in sendDataMessage.")); - _close(); + _delegate->botClose(); return; } - _sendData(data.toUtf8()); + _delegate->botSendData(data.toUtf8()); } void Panel::switchInlineQueryMessage(const QJsonObject &args) { if (args.isEmpty()) { - _close(); + _delegate->botClose(); return; } const auto query = args["query"].toString(); if (query.isEmpty()) { LOG(("BotWebView Error: Bad 'query' in switchInlineQueryMessage.")); - _close(); + _delegate->botClose(); return; } const auto valid = base::flat_set{ @@ -735,26 +736,26 @@ void Panel::switchInlineQueryMessage(const QJsonObject &args) { break; } } - _switchInlineQuery(types, query); + _delegate->botSwitchInlineQuery(types, query); } void Panel::openTgLink(const QJsonObject &args) { if (args.isEmpty()) { - _close(); + _delegate->botClose(); return; } const auto path = args["path_full"].toString(); if (path.isEmpty()) { LOG(("BotWebView Error: Bad 'path_full' in openTgLink.")); - _close(); + _delegate->botClose(); return; } - _handleLocalUri("https://t.me" + path); + _delegate->botHandleLocalUri("https://t.me" + path); } void Panel::openExternalLink(const QJsonObject &args) { if (args.isEmpty()) { - _close(); + _delegate->botClose(); return; } const auto url = args["url"].toString(); @@ -762,7 +763,7 @@ void Panel::openExternalLink(const QJsonObject &args) { if (url.isEmpty() || (!lower.startsWith("http://") && !lower.startsWith("https://"))) { LOG(("BotWebView Error: Bad 'url' in openExternalLink.")); - _close(); + _delegate->botClose(); return; } else if (!allowOpenLink()) { return; @@ -772,21 +773,21 @@ void Panel::openExternalLink(const QJsonObject &args) { void Panel::openInvoice(const QJsonObject &args) { if (args.isEmpty()) { - _close(); + _delegate->botClose(); return; } const auto slug = args["slug"].toString(); if (slug.isEmpty()) { LOG(("BotWebView Error: Bad 'slug' in openInvoice.")); - _close(); + _delegate->botClose(); return; } - _handleInvoice(slug); + _delegate->botHandleInvoice(slug); } void Panel::openPopup(const QJsonObject &args) { if (args.isEmpty()) { - _close(); + _delegate->botClose(); return; } using Button = Webview::PopupArgs::Button; @@ -805,7 +806,7 @@ void Panel::openPopup(const QJsonObject &args) { const auto i = types.find(fields["type"].toString()); if (i == end(types)) { LOG(("BotWebView Error: Bad 'type' in openPopup buttons.")); - _close(); + _delegate->botClose(); return; } buttons.push_back({ @@ -816,11 +817,11 @@ void Panel::openPopup(const QJsonObject &args) { } if (message.isEmpty()) { LOG(("BotWebView Error: Bad 'message' in openPopup.")); - _close(); + _delegate->botClose(); return; } else if (buttons.empty()) { LOG(("BotWebView Error: Bad 'buttons' in openPopup.")); - _close(); + _delegate->botClose(); return; } const auto widget = _webview->window.widget(); @@ -838,8 +839,65 @@ void Panel::openPopup(const QJsonObject &args) { } } +void Panel::requestWriteAccess() { + if (_inBlockingRequest) { + replyRequestWriteAccess(false); + return; + } + _inBlockingRequest = true; + const auto finish = [=](bool allowed) { + _inBlockingRequest = false; + replyRequestWriteAccess(allowed); + }; + const auto weak = base::make_weak(this); + _delegate->botCheckWriteAccess([=](bool allowed) { + if (!weak) { + return; + } else if (allowed) { + finish(true); + return; + } + using Button = Webview::PopupArgs::Button; + const auto widget = _webview->window.widget(); + const auto integration = &Ui::Integration::Instance(); + const auto result = Webview::ShowBlockingPopup({ + .parent = widget ? widget->window() : nullptr, + .title = integration->phraseBotAllowWriteTitle(), + .text = integration->phraseBotAllowWrite(), + .buttons = { + { + .id = "allow", + .text = integration->phraseBotAllowWriteConfirm(), + }, + { .id = "cancel", .type = Button::Type::Cancel }, + }, + }); + if (!weak) { + return; + } else if (result.id == "allow") { + _delegate->botAllowWriteAccess(crl::guard(this, finish)); + } else { + finish(false); + } + }); +} + +void Panel::replyRequestWriteAccess(bool allowed) { + postEvent("write_access_requested", QJsonObject{ + { u"status"_q, allowed ? u"allowed"_q : u"cancelled"_q } + }); +} + void Panel::requestPhone() { -#if 0 // disabled for now + if (_inBlockingRequest) { + replyRequestPhone(false); + return; + } + _inBlockingRequest = true; + const auto finish = [=](bool shared) { + _inBlockingRequest = false; + replyRequestPhone(shared); + }; using Button = Webview::PopupArgs::Button; const auto widget = _webview->window.widget(); const auto weak = base::make_weak(this); @@ -853,15 +911,62 @@ void Panel::requestPhone() { .id = "share", .text = integration->phraseBotSharePhoneConfirm(), }, - {.id = "cancel", .type = Button::Type::Cancel }, + { .id = "cancel", .type = Button::Type::Cancel }, }, }); - if (weak) { - postEvent("phone_requested", (result.id == "share") - ? QJsonObject{ { u"phone_number"_q, _phone } } - : EventData()); + if (!weak) { + return; + } else if (result.id == "share") { + _delegate->botSharePhone(crl::guard(this, finish)); + } else { + finish(false); } -#endif +} + +void Panel::replyRequestPhone(bool shared) { + postEvent("phone_requested", QJsonObject{ + { u"status"_q, shared ? u"sent"_q : u"cancelled"_q } + }); +} + +void Panel::invokeCustomMethod(const QJsonObject &args) { + const auto requestId = args["req_id"]; + if (requestId.isUndefined()) { + return; + } + const auto finish = [=](QJsonObject response) { + replyCustomMethod(requestId, std::move(response)); + }; + auto callback = crl::guard(this, [=](CustomMethodResult result) { + if (result) { + auto error = QJsonParseError(); + const auto parsed = QJsonDocument::fromJson( + "{ \"result\": " + *result + '}', + &error); + if (error.error != QJsonParseError::NoError + || !parsed.isObject() + || parsed.object().size() != 1) { + finish({ { u"error"_q, u"Could not parse response."_q } }); + } else { + finish(parsed.object()); + } + } else { + finish({ { u"error"_q, result.error() } }); + } + }); + const auto params = QJsonDocument( + args["params"].toObject() + ).toJson(QJsonDocument::Compact); + _delegate->botInvokeCustomMethod({ + .method = args["method"].toString(), + .params = params, + .callback = std::move(callback), + }); +} + +void Panel::replyCustomMethod(QJsonValue requestId, QJsonObject response) { + response["req_id"] = requestId; + postEvent(u"custom_method_invoked"_q, response); } void Panel::requestClipboardText(const QJsonObject &args) { @@ -929,7 +1034,7 @@ void Panel::closeWithConfirmation() { if (!weak) { return; } else if (result.id != "cancel") { - _close(); + _delegate->botClose(); } else { _closeWithConfirmationScheduled = false; } @@ -941,10 +1046,22 @@ void Panel::setupClosingBehaviour(const QJsonObject &args) { void Panel::processMainButtonMessage(const QJsonObject &args) { if (args.isEmpty()) { - _close(); + _delegate->botClose(); return; } + const auto shown = [&] { + return _mainButton && !_mainButton->isHidden(); + }; + const auto wasShown = shown(); + const auto guard = gsl::finally([&] { + if (shown() != wasShown) { + crl::on_main(this, [=] { + sendViewport(); + }); + } + }); + if (!_mainButton) { if (args["is_visible"].toBool()) { createMainButton(); @@ -987,6 +1104,22 @@ void Panel::processBackButtonMessage(const QJsonObject &args) { _widget->setBackAllowed(args["is_visible"].toBool()); } +void Panel::processHeaderColor(const QJsonObject &args) { + if (const auto color = ParseColor(args["color"].toString())) { + _widget->overrideTitleColor(color); + _headerColorLifetime.destroy(); + } else if (args["color_key"].toString() == u"secondary_bg_color"_q) { + _widget->overrideTitleColor(st::boxDividerBg->c); + _headerColorLifetime = style::PaletteChanged( + ) | rpl::start_with_next([=] { + _widget->overrideTitleColor(st::boxDividerBg->c); + }); + } else { + _widget->overrideTitleColor(std::nullopt); + _headerColorLifetime.destroy(); + } +} + void Panel::createMainButton() { _mainButton = std::make_unique