diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index f0f9e1d3e..25d6f2fd7 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -59,28 +59,34 @@ jobs: - "" env: SDK: "10.0.18362.0" - VC: "call vcvars32.bat && cd Libraries" GIT: "https://github.com" - QT: "5_15_2" QT_VER: "5.15.2" OPENSSL_VER: "1_1_1" UPLOAD_ARTIFACT: "true" ONLY_CACHE: "false" - MANUAL_CACHING: "2" + MANUAL_CACHING: "0" DOC_PATH: "docs/building-win.md" AUTO_CACHING: "1" defaults: run: shell: cmd + working-directory: Libraries steps: - name: Get repository name. shell: bash + working-directory: ${{ github.workspace }} run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV + - uses: ilammy/msvc-dev-cmd@v1.9.0 + name: x86 Native Tools Command Prompt. + with: + arch: win32 + - name: Set up environment paths. shell: bash + working-directory: ${{ github.workspace }} run: | echo "C:\\Strawberry\\perl\\bin\\" >> $GITHUB_PATH echo "C:\\Program Files\\NASM\\" >> $GITHUB_PATH @@ -91,6 +97,8 @@ jobs: p=`pwd | sed 's#^/[d]#d:#g' |sed 's#/#\\\\#g'` echo "LibrariesPath=$p" >> $GITHUB_ENV + echo "QT=${QT_VER//./_}" >> $GITHUB_ENV + - name: Save msbuild version. run: | call vcvars32.bat @@ -104,6 +112,7 @@ jobs: - name: Generate cache key. shell: bash + working-directory: ${{ github.workspace }} run: | curl -o $LibrariesPath/tg_owt-version.json https://api.github.com/repos/desktop-app/tg_owt/git/refs/heads/master curl -o $LibrariesPath/tg_angle-version.json https://api.github.com/repos/desktop-app/tg_angle/git/refs/heads/master @@ -115,7 +124,10 @@ jobs: echo "CACHE_KEY=`md5sum CACHE_KEY.txt | awk '{ print $1 }'`" >> $GITHUB_ENV - name: Choco installs. - run: choco install --no-progress -y nasm yasm jom ninja + run: | + choco install --allow-empty-checksums --no-progress -y yasm + choco install --no-progress -y nasm jom ninja + python -m pip install pywin32 - name: NuGet sources. run: | @@ -124,6 +136,7 @@ jobs: - name: Patches. shell: bash + working-directory: ${{ github.workspace }} run: | echo "Find necessary commit from doc." checkoutCommit=$(grep -A 1 "cd patches" $REPO_NAME/$DOC_PATH | sed -n 2p) @@ -146,8 +159,6 @@ jobs: - name: LZMA. run: | - %VC% - git clone %GIT%/telegramdesktop/lzma.git cd lzma cd C\Util\LzmaLib @@ -162,8 +173,6 @@ jobs: - name: OpenSSL. if: steps.cache-openssl.outputs.cache-hit != 'true' run: | - %VC% - git clone %GIT%/openssl/openssl.git openssl_%OPENSSL_VER% cd openssl_%OPENSSL_VER% git checkout OpenSSL_%OPENSSL_VER%-stable @@ -187,8 +196,6 @@ jobs: - name: Zlib. run: | - %VC% - git clone %GIT%/telegramdesktop/zlib.git cd zlib git checkout tdesktop @@ -198,8 +205,6 @@ jobs: - name: MozJPEG. shell: cmd run: | - %VC% - git clone -b v4.0.1-rc2 %GIT%/mozilla/mozjpeg.git cd mozjpeg cmake . ^ @@ -218,8 +223,6 @@ jobs: - name: OpenAL Soft. if: steps.cache-openal.outputs.cache-hit != 'true' run: | - %VC% - git clone -b openal-soft-1.21.0 --depth=1 %GIT%/kcat/openal-soft.git cd openal-soft\build cmake .. ^ @@ -243,8 +246,6 @@ jobs: GYP_MSVS_VERSION: 2019 if: steps.cache-breakpad.outputs.cache-hit != 'true' run: | - cd %LibrariesPath% - git clone %GIT%/telegramdesktop/gyp.git cd gyp SET PATH=%PY2%;%cd%;%PATH% @@ -278,8 +279,6 @@ jobs: - name: Opus. if: steps.cache-opus.outputs.cache-hit != 'true' run: | - %VC% - git clone %GIT%/telegramdesktop/opus.git cd opus git checkout tdesktop @@ -288,10 +287,7 @@ jobs: msbuild -m opus.sln /property:Configuration=Release /property:Platform="Win32" - name: Rnnoise. - shell: cmd run: | - %VC% - git clone %GIT%/desktop-app/rnnoise.git mkdir rnnoise\out cd rnnoise\out @@ -307,7 +303,6 @@ jobs: - name: FFmpeg. if: steps.cache-ffmpeg.outputs.cache-hit != 'true' run: | - %VC% choco install --no-progress -y msys2 git clone %GIT%/FFmpeg/FFmpeg.git ffmpeg @@ -328,8 +323,6 @@ jobs: - name: Angle. if: steps.cache-angle.outputs.cache-hit != 'true' run: | - %VC% - git clone --recursive %GIT%/desktop-app/tg_angle.git mkdir tg_angle\out\Debug cd tg_angle\out\Debug @@ -355,8 +348,6 @@ jobs: - name: Configure Qt 5.15.2. if: steps.cache-qt.outputs.cache-hit != 'true' run: | - %VC% - git clone git://code.qt.io/qt/qt5.git qt_%QT% cd qt_%QT% perl init-repository --module-subset=qtbase,qtimageformats @@ -408,7 +399,6 @@ jobs: - name: Qt 5.15.2 build. if: steps.cache-qt.outputs.cache-hit != 'true' run: | - %VC% cd qt_%QT% jom -j%NUMBER_OF_PROCESSORS% @@ -426,8 +416,6 @@ jobs: - name: WebRTC. if: steps.cache-webrtc.outputs.cache-hit != 'true' run: | - %VC% - git clone --recursive %GIT%/desktop-app/tg_owt.git mkdir tg_owt\out\Debug cd tg_owt\out\Debug @@ -464,10 +452,12 @@ jobs: echo "TDESKTOP_BUILD_DEFINE=$DEFINE" >> $GITHUB_ENV - name: Free up some disk space. + working-directory: ${{ github.workspace }} run: del /S *.pdb - name: Kotatogram Desktop build. if: env.ONLY_CACHE == 'false' + working-directory: ${{ github.workspace }} run: | cd %REPO_NAME% call vcvarsall.bat x86 %SDK% @@ -490,6 +480,7 @@ jobs: - name: Move artifact. if: env.UPLOAD_ARTIFACT == 'true' + working-directory: ${{ github.workspace }} run: | cd %REPO_NAME%\build mkdir artifact diff --git a/.gitmodules b/.gitmodules index 3076c4da0..c7b202e09 100644 --- a/.gitmodules +++ b/.gitmodules @@ -91,3 +91,6 @@ [submodule "Telegram/lib_waylandshells"] path = Telegram/lib_waylandshells url = https://github.com/desktop-app/lib_waylandshells.git +[submodule "Telegram/ThirdParty/jemalloc"] + path = Telegram/ThirdParty/jemalloc + url = https://github.com/jemalloc/jemalloc diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ecc42a98..f54eae136 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,7 @@ set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT T get_filename_component(third_party_loc "Telegram/ThirdParty" REALPATH) get_filename_component(submodules_loc "Telegram" REALPATH) +get_filename_component(cmake_helpers_loc "cmake" REALPATH) include(cmake/variables.cmake) include(cmake/nice_target_sources.cmake) @@ -37,6 +38,7 @@ include(cmake/target_link_frameworks.cmake) include(cmake/init_target.cmake) include(cmake/generate_target.cmake) include(cmake/nuget.cmake) +include(cmake/validate_d3d_compiler.cmake) include(cmake/options.cmake) diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index d8a19a8f2..2f5caf17f 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -82,50 +82,6 @@ PRIVATE desktop-app::external_xxhash ) -if (LINUX) - target_link_libraries(Telegram - PRIVATE - desktop-app::external_glibmm - desktop-app::external_glib - ) - - if (NOT DESKTOP_APP_DISABLE_DBUS_INTEGRATION) - target_link_libraries(Telegram - PRIVATE - desktop-app::external_statusnotifieritem - desktop-app::external_dbusmenu_qt - ) - endif() - - if (NOT DESKTOP_APP_DISABLE_X11_INTEGRATION) - target_link_libraries(Telegram - PRIVATE - desktop-app::external_xcb - ) - endif() - - if (NOT DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION) - target_link_libraries(Telegram - PRIVATE - desktop-app::lib_waylandshells - desktop-app::external_kwayland - ) - endif() - - if (NOT DESKTOP_APP_DISABLE_GTK_INTEGRATION) - find_package(PkgConfig REQUIRED) - - pkg_check_modules(GTK REQUIRED gtk+-3.0) - target_include_directories(Telegram SYSTEM PRIVATE ${GTK_INCLUDE_DIRS}) - - if (NOT DESKTOP_APP_DISABLE_X11_INTEGRATION) - target_link_libraries(Telegram PRIVATE X11) - endif() - - target_link_libraries(Telegram PRIVATE rt) - endif() -endif() - target_precompile_headers(Telegram PRIVATE ${src_loc}/stdafx.h) nice_target_sources(Telegram ${src_loc} PRIVATE @@ -135,12 +91,16 @@ PRIVATE api/api_attached_stickers.h api/api_authorizations.cpp api/api_authorizations.h + api/api_blocked_peers.cpp + api/api_blocked_peers.h api/api_bot.cpp api/api_bot.h api/api_chat_filters.cpp api/api_chat_filters.h api/api_chat_invite.cpp api/api_chat_invite.h + api/api_cloud_password.cpp + api/api_cloud_password.h api/api_common.h api/api_editing.cpp api/api_editing.h @@ -168,6 +128,8 @@ PRIVATE api/api_toggling_media.h api/api_updates.cpp api/api_updates.h + api/api_user_privacy.cpp + api/api_user_privacy.h boxes/filters/edit_filter_box.cpp boxes/filters/edit_filter_box.h boxes/filters/edit_filter_chats_list.cpp @@ -264,6 +226,7 @@ PRIVATE calls/group/calls_choose_join_as.h calls/group/calls_group_call.cpp calls/group/calls_group_call.h + calls/group/calls_group_common.cpp calls/group/calls_group_common.h calls/group/calls_group_invite_controller.cpp calls/group/calls_group_invite_controller.h @@ -309,6 +272,8 @@ PRIVATE calls/calls_video_bubble.h calls/calls_video_incoming.cpp calls/calls_video_incoming.h + chat_helpers/bot_command.cpp + chat_helpers/bot_command.h chat_helpers/bot_keyboard.cpp chat_helpers/bot_keyboard.h chat_helpers/emoji_keywords.cpp @@ -608,6 +573,8 @@ PRIVATE history/view/history_view_cursor_state.h history/view/history_view_element.cpp history/view/history_view_element.h + history/view/history_view_empty_list_bubble.cpp + history/view/history_view_empty_list_bubble.h history/view/history_view_group_call_tracker.cpp history/view/history_view_group_call_tracker.h history/view/history_view_list_widget.cpp @@ -761,6 +728,10 @@ PRIVATE lang/lang_numbers_animation.h lang/lang_translator.cpp lang/lang_translator.h + layout/layout_document_generic_preview.cpp + layout/layout_document_generic_preview.h + layout/layout_item_base.cpp + layout/layout_item_base.h main/main_account.cpp main/main_account.h main/main_app_config.cpp @@ -926,7 +897,6 @@ PRIVATE platform/linux/notifications_manager_linux.h platform/linux/specific_linux.cpp platform/linux/specific_linux.h - platform/linux/window_title_linux.h platform/mac/file_utilities_mac.mm platform/mac/file_utilities_mac.h platform/mac/launcher_mac.mm @@ -941,7 +911,6 @@ PRIVATE platform/mac/specific_mac_p.mm platform/mac/specific_mac_p.h platform/mac/window_title_mac.mm - platform/mac/window_title_mac.h platform/mac/touchbar/items/mac_formatter_item.h platform/mac/touchbar/items/mac_formatter_item.mm platform/mac/touchbar/items/mac_pinned_chats_item.h @@ -972,8 +941,6 @@ PRIVATE platform/win/notifications_manager_win.h platform/win/specific_win.cpp platform/win/specific_win.h - platform/win/window_title_win.cpp - platform/win/window_title_win.h platform/win/windows_app_user_model_id.cpp platform/win/windows_app_user_model_id.h platform/win/windows_dlls.cpp @@ -1147,13 +1114,11 @@ PRIVATE window/window_outdated_bar.h window/window_peer_menu.cpp window/window_peer_menu.h + window/window_section_common.h window/window_session_controller.cpp window/window_session_controller.h window/window_slide_animation.cpp window/window_slide_animation.h - window/window_title_qt.cpp - window/window_title_qt.h - window/window_title.h window/window_top_bar_wrap.h window/themes/window_theme.cpp window/themes/window_theme.h @@ -1180,8 +1145,6 @@ PRIVATE config.h facades.cpp facades.h - layout.cpp - layout.h logs.cpp logs.h main.cpp @@ -1194,13 +1157,6 @@ PRIVATE stdafx.h ) -if (NOT LINUX) - remove_target_sources(Telegram ${src_loc} - window/window_title_qt.cpp - window/window_title_qt.h - ) -endif() - if (DESKTOP_APP_DISABLE_DBUS_INTEGRATION) remove_target_sources(Telegram ${src_loc} platform/linux/linux_xdp_file_dialog.cpp @@ -1331,6 +1287,48 @@ elseif (APPLE) ) endif() endif() +else() + target_link_libraries(Telegram + PRIVATE + desktop-app::external_glibmm + desktop-app::external_glib + ) + + if (NOT DESKTOP_APP_DISABLE_DBUS_INTEGRATION) + target_link_libraries(Telegram + PRIVATE + desktop-app::external_statusnotifieritem + desktop-app::external_dbusmenu_qt + ) + endif() + + if (NOT DESKTOP_APP_DISABLE_X11_INTEGRATION) + target_link_libraries(Telegram + PRIVATE + desktop-app::external_xcb + ) + endif() + + if (NOT DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION) + target_link_libraries(Telegram + PRIVATE + desktop-app::lib_waylandshells + desktop-app::external_kwayland + ) + endif() + + if (NOT DESKTOP_APP_DISABLE_GTK_INTEGRATION) + find_package(PkgConfig REQUIRED) + + pkg_check_modules(GTK REQUIRED gtk+-3.0) + target_include_directories(Telegram SYSTEM PRIVATE ${GTK_INCLUDE_DIRS}) + + if (NOT DESKTOP_APP_DISABLE_X11_INTEGRATION) + target_link_libraries(Telegram PRIVATE X11) + endif() + + target_link_libraries(Telegram PRIVATE rt) + endif() endif() set(bundle_identifier "io.github.kotatogram") @@ -1481,7 +1479,7 @@ if ((NOT DESKTOP_APP_DISABLE_AUTOUPDATE OR APPLE) AND NOT build_macstore AND NOT endif() if (DESKTOP_APP_SPECIAL_TARGET OR KTGDESKTOP_ENABLE_PACKER) - add_executable(Packer WIN32) + add_executable(Packer) init_target(Packer) add_dependencies(Telegram Packer) diff --git a/Telegram/Resources/art/background.jpg b/Telegram/Resources/art/background.jpg new file mode 100644 index 000000000..04d57e4db Binary files /dev/null and b/Telegram/Resources/art/background.jpg differ diff --git a/Telegram/Resources/art/bg.jpg b/Telegram/Resources/art/bg.jpg deleted file mode 100644 index 7fa05b3ed..000000000 Binary files a/Telegram/Resources/art/bg.jpg and /dev/null differ diff --git a/Telegram/Resources/icons/calls/video_mini_invited.png b/Telegram/Resources/icons/calls/video_mini_invited.png new file mode 100644 index 000000000..bd4cc48a8 Binary files /dev/null and b/Telegram/Resources/icons/calls/video_mini_invited.png differ diff --git a/Telegram/Resources/icons/calls/video_mini_invited@2x.png b/Telegram/Resources/icons/calls/video_mini_invited@2x.png new file mode 100644 index 000000000..cab3af0b1 Binary files /dev/null and b/Telegram/Resources/icons/calls/video_mini_invited@2x.png differ diff --git a/Telegram/Resources/icons/calls/video_mini_invited@3x.png b/Telegram/Resources/icons/calls/video_mini_invited@3x.png new file mode 100644 index 000000000..2a3d0be00 Binary files /dev/null and b/Telegram/Resources/icons/calls/video_mini_invited@3x.png differ diff --git a/Telegram/Resources/icons/info_media_gif.png b/Telegram/Resources/icons/info_media_gif.png index 4451f2ffd..3b44ac891 100644 Binary files a/Telegram/Resources/icons/info_media_gif.png and b/Telegram/Resources/icons/info_media_gif.png differ diff --git a/Telegram/Resources/icons/info_media_gif@2x.png b/Telegram/Resources/icons/info_media_gif@2x.png index 91adfd386..7a458e125 100644 Binary files a/Telegram/Resources/icons/info_media_gif@2x.png and b/Telegram/Resources/icons/info_media_gif@2x.png differ diff --git a/Telegram/Resources/icons/info_media_gif@3x.png b/Telegram/Resources/icons/info_media_gif@3x.png index 784c99723..304ecd2c3 100644 Binary files a/Telegram/Resources/icons/info_media_gif@3x.png and b/Telegram/Resources/icons/info_media_gif@3x.png differ diff --git a/Telegram/Resources/langs/download.sh b/Telegram/Resources/langs/download.sh deleted file mode 100755 index a4da69404..000000000 --- a/Telegram/Resources/langs/download.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env bash -set -e -FullExecPath=$PWD -pushd `dirname $0` > /dev/null -FullScriptPath=`pwd` -popd > /dev/null - -if [ ! -d "$FullScriptPath/../../../../DesktopPrivate" ]; then - echo "" - echo "This script is for building the production version of Telegram Desktop." - echo "" - echo "For building custom versions please visit the build instructions page at:" - echo "https://github.com/telegramdesktop/tdesktop/#build-instructions" - exit -fi - -Error () { - cd $FullExecPath - echo "$1" - exit 1 -} - -cd $FullScriptPath/../../../../ -while IFS='' read -r line || [[ -n "$line" ]]; do - tx pull -f -l $line --minimum-perc=100 -done < tdesktop/Telegram/Resources/langs/list -cd translations/telegram-desktop.langstrings/ -for file in *.strings; do - iconv -f "UTF-16LE" -t "UTF-8" "$file" > "../../tdesktop/Telegram/Resources/langs/lang_$file.tmp" - awk '{ if (NR==1) sub(/^\xef\xbb\xbf/,""); sub(/ /,""); print }' "../../tdesktop/Telegram/Resources/langs/lang_$file.tmp" > "../../tdesktop/Telegram/Resources/langs/lang_$file" - rm "../../tdesktop/Telegram/Resources/langs/lang_$file.tmp" -done - -cd $FullExecPath diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 65659e47b..dc1517cab 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -82,6 +82,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_box_ok" = "OK"; "lng_box_done" = "Done"; +"lng_box_yes" = "Yes"; +"lng_box_no" = "No"; "lng_cancel" = "Cancel"; "lng_continue" = "Continue"; @@ -596,6 +598,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_cloud_password_remove" = "Remove cloud password"; "lng_cloud_password_set" = "Enable two-step verification"; "lng_cloud_password_edit" = "Change cloud password"; +"lng_cloud_password_reset_in" = "Reset password in"; +"lng_cloud_password_reset_ready" = "Reset password"; +"lng_cloud_password_reset_cancel" = "Cancel password reset"; "lng_cloud_password_enter_old" = "Enter current password"; "lng_cloud_password_enter_first" = "Enter a password"; "lng_cloud_password_enter_new" = "Enter new password"; @@ -618,6 +623,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_cloud_password_passport_losing" = "Warning! All data saved in your Telegram Passport will be lost!"; "lng_cloud_password_resend" = "Resend code"; "lng_cloud_password_resent" = "Code was resent."; +"lng_cloud_password_reset_title" = "Reset password"; +"lng_cloud_password_reset_no_email" = "Since you didn't provide a recovery email when setting up your password, your remaining options are either to remember your password or wait 7 days until your password is reset."; +"lng_cloud_password_reset_with_email" = "If you don't have access to your recovery email, your remaining options are either to remember your password or wait 7 days until your password resets."; +"lng_cloud_password_reset_ok" = "Reset"; +"lng_cloud_password_reset_cancel_title" = "Cancel reset"; +"lng_cloud_password_reset_cancel_sure" = "Cancel the password reset process? If you request a new reset later, it will take another 7 days."; +"lng_cloud_password_reset_later" = "You recently requested a password reset that was cancelled. Please wait {duration} before making a new request."; "lng_connection_auto_connecting" = "Default (connecting...)"; "lng_connection_auto" = "Default ({transport} used)"; @@ -862,6 +874,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_profile_photos#one" = "{count} photo"; "lng_profile_photos#other" = "{count} photos"; "lng_profile_photos_header" = "Photos"; +"lng_profile_gifs#one" = "{count} GIF"; +"lng_profile_gifs#other" = "{count} GIFs"; +"lng_profile_gifs_header" = "GIFs"; "lng_profile_videos#one" = "{count} video"; "lng_profile_videos#other" = "{count} videos"; "lng_profile_videos_header" = "Videos"; @@ -904,6 +919,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_info_add_as_contact" = "Add to contacts"; "lng_profile_shared_media" = "Shared media"; "lng_media_type_photos" = "Photos"; +"lng_media_type_gifs" = "GIFs"; "lng_media_type_videos" = "Videos"; "lng_media_type_songs" = "Audio files"; "lng_media_type_files" = "Files"; @@ -923,6 +939,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_profile_export_channel" = "Export channel history"; "lng_media_selected_photo#one" = "{count} Photo"; "lng_media_selected_photo#other" = "{count} Photos"; +"lng_media_selected_gif#one" = "{count} GIF"; +"lng_media_selected_gif#other" = "{count} GIFs"; "lng_media_selected_video#one" = "{count} Video"; "lng_media_selected_video#other" = "{count} Videos"; "lng_media_selected_song#one" = "{count} Audio file"; @@ -936,6 +954,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_media_selected_link#one" = "{count} Shared link"; "lng_media_selected_link#other" = "{count} Shared links"; "lng_media_photo_empty" = "No photos here yet"; +"lng_media_gif_empty" = "No GIFs here yet"; "lng_media_video_empty" = "No videos here yet"; "lng_media_song_empty" = "No music files here yet"; "lng_media_file_empty" = "No files here yet"; @@ -1498,6 +1517,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_saved_forward_here" = "Forward messages here for quick access"; "lng_scheduled_messages" = "Scheduled Messages"; +"lng_scheduled_messages_empty" = "No scheduled messages here yet..."; "lng_reminder_messages" = "Reminders"; "lng_scheduled_date" = "Scheduled for {date}"; "lng_scheduled_date_until_online" = "Scheduled until online"; @@ -2005,6 +2025,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_call_start_video" = "Start Video"; "lng_call_stop_video" = "Stop Video"; +"lng_call_screencast" = "Screencast"; "lng_call_end_call" = "End Call"; "lng_call_mute_audio" = "Mute"; "lng_call_unmute_audio" = "Unmute"; @@ -2084,6 +2105,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_call_noise_suppression" = "Enable Noise Suppression"; "lng_group_call_limit#one" = "Video is only available\nfor the first {count} member"; "lng_group_call_limit#other" = "Video is only available\nfor the first {count} members"; +"lng_group_call_over_limit#one" = "The voice chat is over {count} member.\nNew participants only have access to audio stream."; +"lng_group_call_over_limit#other" = "The voice chat is over {count} members.\nNew participants only have access to audio stream."; "lng_group_call_video_paused" = "Video is paused"; "lng_group_call_share_speaker" = "Users with this link can speak"; "lng_group_call_copy_speaker_link" = "Copy Speaker Link"; diff --git a/Telegram/Resources/langs/list b/Telegram/Resources/langs/list deleted file mode 100644 index 658122760..000000000 --- a/Telegram/Resources/langs/list +++ /dev/null @@ -1 +0,0 @@ -de,es,it,ko,nl,pt_BR diff --git a/Telegram/Resources/langs/rewrites/en.json b/Telegram/Resources/langs/rewrites/en.json index befc59600..5eee92ced 100644 --- a/Telegram/Resources/langs/rewrites/en.json +++ b/Telegram/Resources/langs/rewrites/en.json @@ -159,24 +159,6 @@ "ktg_group_status_admin": "is admin", "ktg_too_many_accounts_warning": "Warning! Using too many accounts at the same time is not recommended due to higher memory comsumption and possible crashes because of it.\n\nYou sure you want to add a new account?", "ktg_account_add_anyway": "Add anyway", - "ktg_media_selected_gif": { - "zero": "{count} GIFs", - "one": "{count} GIF", - "two": "{count} GIFs", - "few": "{count} GIFs", - "many": "{count} GIFs", - "other": "{count} GIFs" - }, - "ktg_media_type_gif": "GIFs", - "ktg_profile_gif": { - "zero": "{count} GIFs", - "one": "{count} GIF", - "two": "{count} GIFs", - "few": "{count} GIFs", - "many": "{count} GIFs", - "other": "{count} GIFs" - }, - "ktg_media_gif_empty": "No GIFs here yet", "ktg_rights_chat_send_stickers": "Send stickers", "ktg_rights_chat_send_gif": "Send GIFs", "ktg_rights_chat_send_games": "Send games", diff --git a/Telegram/Resources/langs/upload.sh b/Telegram/Resources/langs/upload.sh deleted file mode 100755 index fcbf4f2a0..000000000 --- a/Telegram/Resources/langs/upload.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash -set -e -FullExecPath=$PWD -pushd `dirname $0` > /dev/null -FullScriptPath=`pwd` -popd > /dev/null - -if [ ! -d "$FullScriptPath/../../../../DesktopPrivate" ]; then - echo "" - echo "This script is for building the production version of Telegram Desktop." - echo "" - echo "For building custom versions please visit the build instructions page at:" - echo "https://github.com/telegramdesktop/tdesktop/#build-instructions" - exit -fi - -Error () { - cd $FullExecPath - echo "$1" - exit 1 -} - -cd $FullScriptPath/../../../../ -while IFS='' read -r line || [[ -n "$line" ]]; do - tx pull -f -l $line -done < tdesktop/Telegram/Resources/langs/list -tx push -s - -cd $FullExecPath diff --git a/Telegram/Resources/qrc/telegram/telegram.qrc b/Telegram/Resources/qrc/telegram/telegram.qrc index bd5e298fe..bb6e245b7 100644 --- a/Telegram/Resources/qrc/telegram/telegram.qrc +++ b/Telegram/Resources/qrc/telegram/telegram.qrc @@ -42,7 +42,7 @@ ../../export_html/js/script.js - ../../art/bg.jpg + ../../art/background.jpg ../../art/bg_initial.jpg ../../art/logo_256.png ../../art/logo_256_blue.png diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 0bb3bb4cd..0e531cf95 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -9,7 +9,7 @@ + Version="2.9.3.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index f186a115f..4b9e417d8 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 2,8,11,0 - PRODUCTVERSION 2,8,11,0 + FILEVERSION 2,9,3,0 + PRODUCTVERSION 2,9,3,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "2.8.11.0" + VALUE "FileVersion", "2.9.3.0" VALUE "LegalCopyright", "Copyright (C) 2014-2021" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "2.8.11.0" + VALUE "ProductVersion", "2.9.3.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 318d96f42..270c9ff3c 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 2,8,11,0 - PRODUCTVERSION 2,8,11,0 + FILEVERSION 2,9,3,0 + PRODUCTVERSION 2,9,3,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop Updater" - VALUE "FileVersion", "2.8.11.0" + VALUE "FileVersion", "2.9.3.0" VALUE "LegalCopyright", "Copyright (C) 2014-2021" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "2.8.11.0" + VALUE "ProductVersion", "2.9.3.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/_other/packer.cpp b/Telegram/SourceFiles/_other/packer.cpp index 1b8789ecc..edd77f2b5 100644 --- a/Telegram/SourceFiles/_other/packer.cpp +++ b/Telegram/SourceFiles/_other/packer.cpp @@ -160,8 +160,7 @@ int main(int argc, char *argv[]) QString remove; int version = 0; - bool targetosx = false; - bool targetwin64 = false; + [[maybe_unused]] bool targetwin64 = false; QFileInfoList files; for (int i = 0; i < argc; ++i) { if (string("-path") == argv[i] && i + 1 < argc) { @@ -170,7 +169,6 @@ int main(int argc, char *argv[]) files.push_back(info); if (remove.isEmpty()) remove = info.canonicalPath() + "/"; } else if (string("-target") == argv[i] && i + 1 < argc) { - targetosx = (string("osx") == argv[i + 1]); targetwin64 = (string("win64") == argv[i + 1]); } else if (string("-version") == argv[i] && i + 1 < argc) { version = QString(argv[i + 1]).toInt(); @@ -498,7 +496,7 @@ int main(int argc, char *argv[]) #ifdef Q_OS_WIN QString outName((targetwin64 ? QString("tx64upd%1") : QString("tupdate%1")).arg(AlphaVersion ? AlphaVersion : version)); #elif defined Q_OS_MAC - QString outName((targetosx ? QString("tosxupd%1") : QString("tmacupd%1")).arg(AlphaVersion ? AlphaVersion : version)); + QString outName(QString("tmacupd%1").arg(AlphaVersion ? AlphaVersion : version)); #elif defined Q_OS_UNIX #ifndef _LP64 QString outName(QString("tlinux32upd%1").arg(AlphaVersion ? AlphaVersion : version)); diff --git a/Telegram/SourceFiles/api/api_blocked_peers.cpp b/Telegram/SourceFiles/api/api_blocked_peers.cpp new file mode 100644 index 000000000..823147894 --- /dev/null +++ b/Telegram/SourceFiles/api/api_blocked_peers.cpp @@ -0,0 +1,173 @@ +/* +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 "api/api_blocked_peers.h" + +#include "apiwrap.h" +#include "base/unixtime.h" +#include "data/data_changes.h" +#include "data/data_peer.h" +#include "data/data_peer_id.h" +#include "data/data_session.h" +#include "main/main_session.h" + +namespace Api { +namespace { + +constexpr auto kBlockedFirstSlice = 16; +constexpr auto kBlockedPerPage = 40; + +BlockedPeers::Slice TLToSlice( + const MTPcontacts_Blocked &blocked, + Data::Session &owner) { + const auto create = [&](int count, const QVector &list) { + auto slice = BlockedPeers::Slice(); + slice.total = std::max(count, list.size()); + slice.list.reserve(list.size()); + for (const auto &contact : list) { + contact.match([&](const MTPDpeerBlocked &data) { + slice.list.push_back({ + .id = peerFromMTP(data.vpeer_id()), + .date = data.vdate().v, + }); + }); + } + return slice; + }; + return blocked.match([&](const MTPDcontacts_blockedSlice &data) { + owner.processUsers(data.vusers()); + owner.processChats(data.vchats()); + return create(data.vcount().v, data.vblocked().v); + }, [&](const MTPDcontacts_blocked &data) { + owner.processUsers(data.vusers()); + owner.processChats(data.vchats()); + return create(0, data.vblocked().v); + }); +} + +} // namespace + +BlockedPeers::BlockedPeers(not_null api) +: _session(&api->session()) +, _api(&api->instance()) { +} + +bool BlockedPeers::Slice::Item::operator==(const Item &other) const { + return (id == other.id) && (date == other.date); +} + +bool BlockedPeers::Slice::Item::operator!=(const Item &other) const { + return !(*this == other); +} + +bool BlockedPeers::Slice::operator==(const BlockedPeers::Slice &other) const { + return (total == other.total) && (list == other.list); +} + +bool BlockedPeers::Slice::operator!=(const BlockedPeers::Slice &other) const { + return !(*this == other); +} + +void BlockedPeers::block(not_null peer) { + if (peer->isBlocked()) { + _session->changes().peerUpdated( + peer, + Data::PeerUpdate::Flag::IsBlocked); + } else if (_blockRequests.find(peer) == end(_blockRequests)) { + const auto requestId = _api.request(MTPcontacts_Block( + peer->input + )).done([=](const MTPBool &result) { + _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([=](const MTP::Error &error) { + _blockRequests.erase(peer); + }).send(); + + _blockRequests.emplace(peer, requestId); + } +} + +void BlockedPeers::unblock(not_null peer, Fn onDone) { + if (!peer->isBlocked()) { + _session->changes().peerUpdated( + peer, + Data::PeerUpdate::Flag::IsBlocked); + return; + } else if (_blockRequests.find(peer) != end(_blockRequests)) { + return; + } + const auto requestId = _api.request(MTPcontacts_Unblock( + peer->input + )).done([=](const MTPBool &result) { + _blockRequests.erase(peer); + peer->setIsBlocked(false); + if (_slice) { + auto &list = _slice->list; + for (auto i = list.begin(); i != list.end(); ++i) { + if (i->id == peer->id) { + list.erase(i); + break; + } + } + if (_slice->total > list.size()) { + --_slice->total; + } + _changes.fire_copy(*_slice); + } + if (onDone) { + onDone(); + } + }).fail([=](const MTP::Error &error) { + _blockRequests.erase(peer); + }).send(); + _blockRequests.emplace(peer, requestId); +} + +void BlockedPeers::reload() { + if (_requestId) { + return; + } + request(0, [=](Slice &&slice) { + if (!_slice || *_slice != slice) { + _slice = slice; + _changes.fire(std::move(slice)); + } + }); +} + +auto BlockedPeers::slice() -> rpl::producer { + if (!_slice) { + reload(); + } + return _slice + ? _changes.events_starting_with_copy(*_slice) + : (_changes.events() | rpl::type_erased()); +} + +void BlockedPeers::request(int offset, Fn onDone) { + if (_requestId) { + return; + } + _requestId = _api.request(MTPcontacts_GetBlocked( + MTP_int(offset), + MTP_int(offset ? kBlockedPerPage : kBlockedFirstSlice) + )).done([=](const MTPcontacts_Blocked &result) { + _requestId = 0; + onDone(TLToSlice(result, _session->data())); + }).fail([=](const MTP::Error &error) { + _requestId = 0; + }).send(); +} + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_blocked_peers.h b/Telegram/SourceFiles/api/api_blocked_peers.h new file mode 100644 index 000000000..ae506bbc9 --- /dev/null +++ b/Telegram/SourceFiles/api/api_blocked_peers.h @@ -0,0 +1,60 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "mtproto/sender.h" + +class ApiWrap; + +namespace Main { +class Session; +} // namespace Main + +namespace Api { + +class BlockedPeers final { +public: + struct Slice { + struct Item { + PeerId id; + TimeId date = 0; + + bool operator==(const Item &other) const; + bool operator!=(const Item &other) const; + }; + + QVector list; + int total = 0; + + bool operator==(const Slice &other) const; + bool operator!=(const Slice &other) const; + }; + + explicit BlockedPeers(not_null api); + + void reload(); + rpl::producer slice(); + void request(int offset, Fn onDone); + + void block(not_null peer); + void unblock(not_null peer, Fn onDone = nullptr); + +private: + const not_null _session; + + MTP::Sender _api; + + base::flat_map, mtpRequestId> _blockRequests; + mtpRequestId _requestId = 0; + std::optional _slice; + rpl::event_stream _changes; + + +}; + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_bot.cpp b/Telegram/SourceFiles/api/api_bot.cpp index 74704f27e..b10883016 100644 --- a/Telegram/SourceFiles/api/api_bot.cpp +++ b/Telegram/SourceFiles/api/api_bot.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_bot.h" #include "apiwrap.h" +#include "api/api_cloud_password.h" #include "core/core_cloud_password.h" #include "api/api_send_progress.h" #include "boxes/confirm_box.h" @@ -169,7 +170,7 @@ void SendBotCallbackDataWithPassword( if (!button || button->requestId) { return; } - api->reloadPasswordState(); + api->cloudPassword().reload(); SendBotCallbackData(item, row, column, MTP_inputCheckPasswordEmpty(), [=](const MTP::Error &error) { auto box = PrePasswordErrorBox( error, @@ -182,7 +183,7 @@ void SendBotCallbackDataWithPassword( } else { auto lifetime = std::make_shared(); button->requestId = -1; - api->passwordState( + api->cloudPassword().state( ) | rpl::take( 1 ) | rpl::start_with_next([=](const Core::CloudPasswordState &state) mutable { diff --git a/Telegram/SourceFiles/api/api_cloud_password.cpp b/Telegram/SourceFiles/api/api_cloud_password.cpp new file mode 100644 index 000000000..a8a14fd1d --- /dev/null +++ b/Telegram/SourceFiles/api/api_cloud_password.cpp @@ -0,0 +1,86 @@ +/* +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 "api/api_cloud_password.h" + +#include "base/openssl_help.h" +#include "core/core_cloud_password.h" +#include "apiwrap.h" + +namespace Api { + +// #TODO Add ability to set recovery email separately. + +CloudPassword::CloudPassword(not_null api) +: _api(&api->instance()) { +} + +void CloudPassword::reload() { + if (_requestId) { + return; + } + _requestId = _api.request(MTPaccount_GetPassword( + )).done([=](const MTPaccount_Password &result) { + _requestId = 0; + result.match([&](const MTPDaccount_password &data) { + openssl::AddRandomSeed(bytes::make_span(data.vsecure_random().v)); + if (_state) { + *_state = Core::ParseCloudPasswordState(data); + } else { + _state = std::make_unique( + Core::ParseCloudPasswordState(data)); + } + _stateChanges.fire_copy(*_state); + }); + }).fail([=](const MTP::Error &error) { + _requestId = 0; + }).send(); +} + +void CloudPassword::applyPendingReset( + const MTPaccount_ResetPasswordResult &data) { + if (!_state) { + reload(); + return; + } + data.match([&](const MTPDaccount_resetPasswordOk &data) { + reload(); + }, [&](const MTPDaccount_resetPasswordRequestedWait &data) { + const auto until = data.vuntil_date().v; + if (_state->pendingResetDate != until) { + _state->pendingResetDate = until; + _stateChanges.fire_copy(*_state); + } + }, [&](const MTPDaccount_resetPasswordFailedWait &data) { + }); +} + +void CloudPassword::clearUnconfirmedPassword() { + _requestId = _api.request(MTPaccount_CancelPasswordEmail( + )).done([=](const MTPBool &result) { + _requestId = 0; + reload(); + }).fail([=](const MTP::Error &error) { + _requestId = 0; + reload(); + }).send(); +} + +rpl::producer CloudPassword::state() const { + return _state + ? _stateChanges.events_starting_with_copy(*_state) + : (_stateChanges.events() | rpl::type_erased()); +} + +auto CloudPassword::stateCurrent() const +-> std::optional { + return _state + ? base::make_optional(*_state) + : std::nullopt; +} + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_cloud_password.h b/Telegram/SourceFiles/api/api_cloud_password.h new file mode 100644 index 000000000..2fdd7394c --- /dev/null +++ b/Telegram/SourceFiles/api/api_cloud_password.h @@ -0,0 +1,42 @@ +/* +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 "mtproto/sender.h" + +namespace Core { +struct CloudPasswordState; +} // namespace Core + +class ApiWrap; + +namespace Main { +class Session; +} // namespace Main + +namespace Api { + +class CloudPassword final { +public: + explicit CloudPassword(not_null api); + + void reload(); + void applyPendingReset(const MTPaccount_ResetPasswordResult &data); + void clearUnconfirmedPassword(); + rpl::producer state() const; + std::optional stateCurrent() const; + +private: + MTP::Sender _api; + mtpRequestId _requestId = 0; + std::unique_ptr _state; + rpl::event_stream _stateChanges; + +}; + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index 1997e4c4f..6e6586a4f 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -41,22 +41,22 @@ namespace { void InnerFillMessagePostFlags( const Api::SendOptions &options, not_null peer, - MTPDmessage::Flags &flags) { + MessageFlags &flags) { const auto anonymousPost = peer->amAnonymous(); if (!anonymousPost) { - flags |= MTPDmessage::Flag::f_from_id; + flags |= MessageFlag::HasFromId; return; } else if (peer->asMegagroup()) { return; } - flags |= MTPDmessage::Flag::f_post; + flags |= MessageFlag::Post; // Don't display views and author of a new post when it's scheduled. if (options.scheduled) { return; } - flags |= MTPDmessage::Flag::f_views; + flags |= MessageFlag::HasViews; if (peer->asChannel()->addsSignature()) { - flags |= MTPDmessage::Flag::f_post_author; + flags |= MessageFlag::HasPostAuthor; } } @@ -82,11 +82,10 @@ void SendExistingMedia( session->data().nextLocalMessageId()); const auto randomId = openssl::RandomValue(); - auto flags = NewMessageFlags(peer) | MTPDmessage::Flag::f_media; - auto clientFlags = NewMessageClientFlags(); + auto flags = NewMessageFlags(peer); auto sendFlags = MTPmessages_SendMedia::Flags(0); if (message.action.replyTo) { - flags |= MTPDmessage::Flag::f_reply_to; + flags |= MessageFlag::HasReplyInfo; sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id; } const auto anonymousPost = peer->amAnonymous(); @@ -114,19 +113,19 @@ void SendExistingMedia( const auto captionText = caption.text; if (message.action.options.scheduled) { - flags |= MTPDmessage::Flag::f_from_scheduled; + flags |= MessageFlag::IsOrWasScheduled; sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date; } else { - clientFlags |= MTPDmessage_ClientFlag::f_local_history_entry; + flags |= MessageFlag::LocalHistoryEntry; } session->data().registerMessageRandomId(randomId, newId); + const auto viaBotId = UserId(); history->addNewLocalMessage( newId.msg, flags, - clientFlags, - 0, + viaBotId, replyTo, HistoryItem::NewMessageDate(message.action.options.scheduled), messageFromId, @@ -292,11 +291,10 @@ bool SendDice( const auto randomId = openssl::RandomValue(); auto &histories = history->owner().histories(); - auto flags = NewMessageFlags(peer) | MTPDmessage::Flag::f_media; - auto clientFlags = NewMessageClientFlags(); + auto flags = NewMessageFlags(peer); auto sendFlags = MTPmessages_SendMedia::Flags(0); if (message.action.replyTo) { - flags |= MTPDmessage::Flag::f_reply_to; + flags |= MessageFlag::HasReplyInfo; sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id; } const auto replyHeader = NewMessageReplyHeader(message.action); @@ -311,42 +309,26 @@ bool SendDice( const auto replyTo = message.action.replyTo; if (message.action.options.scheduled) { - flags |= MTPDmessage::Flag::f_from_scheduled; + flags |= MessageFlag::IsOrWasScheduled; sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date; } else { - clientFlags |= MTPDmessage_ClientFlag::f_local_history_entry; + flags |= MessageFlag::LocalHistoryEntry; } session->data().registerMessageRandomId(randomId, newId); - const auto views = 1; - const auto forwards = 0; - history->addNewMessage( - MTP_message( - MTP_flags(flags), - MTP_int(newId.msg), - peerToMTP(messageFromId), - peerToMTP(history->peer->id), - MTPMessageFwdHeader(), - MTPint(), // via_bot_id - replyHeader, - MTP_int(HistoryItem::NewMessageDate( - message.action.options.scheduled)), - MTP_string(), - MTP_messageMediaDice(MTP_int(0), MTP_string(emoji)), - MTPReplyMarkup(), - MTP_vector(), - MTP_int(views), - MTP_int(forwards), - MTPMessageReplies(), - MTPint(), // edit_date - MTP_string(messagePostAuthor), - MTPlong(), - //MTPMessageReactions(), - MTPVector(), - MTPint()), // ttl_period - clientFlags, - NewMessageType::Unread); + const auto viaBotId = UserId(); + history->addNewLocalMessage( + newId.msg, + flags, + viaBotId, + message.action.replyTo, + HistoryItem::NewMessageDate(message.action.options.scheduled), + messageFromId, + messagePostAuthor, + TextWithEntities(), + MTP_messageMediaDice(MTP_int(0), MTP_string(emoji)), + MTPReplyMarkup()); const auto requestType = Data::Histories::RequestType::Send; histories.sendRequest(history, requestType, [=](Fn finish) { @@ -382,14 +364,15 @@ bool SendDice( void FillMessagePostFlags( const Api::SendAction &action, not_null peer, - MTPDmessage::Flags &flags) { + MessageFlags &flags) { InnerFillMessagePostFlags(action.options, peer, flags); } void SendConfirmedFile( not_null session, const std::shared_ptr &file) { - const auto isEditing = file->to.replaceMediaOf != 0; + const auto isEditing = (file->type != SendMediaType::Audio) + && (file->to.replaceMediaOf != 0); const auto channelId = peerToChannel(file->to.peer); const auto newId = FullMsgId( @@ -440,29 +423,24 @@ void SendConfirmedFile( } } - auto flags = (isEditing ? MTPDmessage::Flags() : NewMessageFlags(peer)) - | MTPDmessage::Flag::f_entities - | MTPDmessage::Flag::f_media; - auto clientFlags = NewMessageClientFlags(); + auto flags = isEditing ? MessageFlags() : NewMessageFlags(peer); if (file->to.replyTo) { - flags |= MTPDmessage::Flag::f_reply_to; + flags |= MessageFlag::HasReplyInfo; } const auto replyHeader = NewMessageReplyHeader(action); const auto anonymousPost = peer->amAnonymous(); const auto silentPost = ShouldSendSilent(peer, file->to.options); - Api::FillMessagePostFlags(action, peer, flags); + FillMessagePostFlags(action, peer, flags); if (silentPost) { - flags |= MTPDmessage::Flag::f_silent; - } - if (groupId) { - flags |= MTPDmessage::Flag::f_grouped_id; + flags |= MessageFlag::Silent; } if (file->to.options.scheduled) { - flags |= MTPDmessage::Flag::f_from_scheduled; + flags |= MessageFlag::IsOrWasScheduled; + // Scheduled messages have no the 'edited' badge. - flags |= MTPDmessage::Flag::f_edit_hide; + flags |= MessageFlag::HideEdited; } else { - clientFlags |= MTPDmessage_ClientFlag::f_local_history_entry; + flags |= MessageFlag::LocalHistoryEntry; } const auto messageFromId = anonymousPost ? 0 : session->userPeerId(); @@ -470,17 +448,37 @@ void SendConfirmedFile( ? session->user()->name : QString(); - const auto views = 1; - const auto forwards = 0; - if (file->type == SendMediaType::Photo) { - const auto photoFlags = MTPDmessageMediaPhoto::Flag::f_photo | 0; - const auto photo = MTP_messageMediaPhoto( - MTP_flags(photoFlags), - file->photo, - MTPint()); + const auto media = [&] { + if (file->type == SendMediaType::Photo) { + return MTP_messageMediaPhoto( + MTP_flags(MTPDmessageMediaPhoto::Flag::f_photo), + file->photo, + MTPint()); + } else if (file->type == SendMediaType::File) { + return MTP_messageMediaDocument( + MTP_flags(MTPDmessageMediaDocument::Flag::f_document), + file->document, + MTPint()); + } else if (file->type == SendMediaType::Audio) { + return MTP_messageMediaDocument( + MTP_flags(MTPDmessageMediaDocument::Flag::f_document), + file->document, + MTPint()); + } else { + Unexpected("Type in sendFilesConfirmed."); + } + }(); - const auto mtpMessage = MTP_message( - MTP_flags(flags), + if (itemToEdit) { + itemToEdit->savePreviousMedia(); + itemToEdit->applyEdition(MTP_message( + MTP_flags(MTPDmessage::Flag::f_media + | ((flags & MessageFlag::HideEdited) + ? MTPDmessage::Flag::f_edit_hide + : MTPDmessage::Flag()) + | (localEntities.v.isEmpty() + ? MTPDmessage::Flag() + : MTPDmessage::Flag::f_entities)), MTP_int(newId.msg), peerToMTP(messageFromId), peerToMTP(file->to.peer), @@ -489,105 +487,32 @@ void SendConfirmedFile( replyHeader, MTP_int(HistoryItem::NewMessageDate(file->to.options.scheduled)), MTP_string(caption.text), - photo, + media, MTPReplyMarkup(), localEntities, - MTP_int(views), - MTP_int(forwards), + MTPint(), // views + MTPint(), // forwards MTPMessageReplies(), MTPint(), // edit_date MTP_string(messagePostAuthor), MTP_long(groupId), //MTPMessageReactions(), MTPVector(), - MTPint()); // ttl_period - - if (itemToEdit) { - itemToEdit->savePreviousMedia(); - itemToEdit->applyEdition(mtpMessage.c_message()); - } else { - history->addNewMessage( - mtpMessage, - clientFlags, - NewMessageType::Unread); - } - } else if (file->type == SendMediaType::File) { - const auto documentFlags = MTPDmessageMediaDocument::Flag::f_document | 0; - const auto document = MTP_messageMediaDocument( - MTP_flags(documentFlags), - file->document, - MTPint()); - - const auto mtpMessage = MTP_message( - MTP_flags(flags), - MTP_int(newId.msg), - peerToMTP(messageFromId), - peerToMTP(file->to.peer), - MTPMessageFwdHeader(), - MTPint(), - replyHeader, - MTP_int(HistoryItem::NewMessageDate(file->to.options.scheduled)), - MTP_string(caption.text), - document, - MTPReplyMarkup(), - localEntities, - MTP_int(views), - MTP_int(forwards), - MTPMessageReplies(), - MTPint(), // edit_date - MTP_string(messagePostAuthor), - MTP_long(groupId), - //MTPMessageReactions(), - MTPVector(), - MTPint()); // ttl_period - - if (itemToEdit) { - itemToEdit->savePreviousMedia(); - itemToEdit->applyEdition(mtpMessage.c_message()); - } else { - history->addNewMessage( - mtpMessage, - clientFlags, - NewMessageType::Unread); - } - } else if (file->type == SendMediaType::Audio) { - if (!peer->isChannel() || peer->isMegagroup()) { - flags |= MTPDmessage::Flag::f_media_unread; - } - const auto documentFlags = MTPDmessageMediaDocument::Flag::f_document | 0; - const auto document = MTP_messageMediaDocument( - MTP_flags(documentFlags), - file->document, - MTPint()); - history->addNewMessage( - MTP_message( - MTP_flags(flags), - MTP_int(newId.msg), - peerToMTP(messageFromId), - peerToMTP(file->to.peer), - MTPMessageFwdHeader(), - MTPint(), - replyHeader, - MTP_int( - HistoryItem::NewMessageDate(file->to.options.scheduled)), - MTP_string(caption.text), - document, - MTPReplyMarkup(), - localEntities, - MTP_int(views), - MTP_int(forwards), - MTPMessageReplies(), - MTPint(), // edit_date - MTP_string(messagePostAuthor), - MTP_long(groupId), - //MTPMessageReactions(), - MTPVector(), - MTPint()), // ttl_period - clientFlags, - NewMessageType::Unread); - // Voices can't be edited. + MTPint()).c_message()); } else { - Unexpected("Type in sendFilesConfirmed."); + const auto viaBotId = UserId(); + history->addNewLocalMessage( + newId.msg, + flags, + viaBotId, + file->to.replyTo, + HistoryItem::NewMessageDate(file->to.options.scheduled), + messageFromId, + messagePostAuthor, + caption, + media, + MTPReplyMarkup(), + groupId); } if (isEditing) { diff --git a/Telegram/SourceFiles/api/api_sending.h b/Telegram/SourceFiles/api/api_sending.h index 28a9431ee..1bd1b6f70 100644 --- a/Telegram/SourceFiles/api/api_sending.h +++ b/Telegram/SourceFiles/api/api_sending.h @@ -51,7 +51,7 @@ bool SendDice( void FillMessagePostFlags( const SendAction &action, not_null peer, - MTPDmessage::Flags &flags); + MessageFlags &flags); void SendConfirmedFile( not_null session, diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 314f65298..edd4ab362 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_authorizations.h" #include "api/api_text_entities.h" +#include "api/api_user_privacy.h" #include "main/main_session.h" #include "main/main_account.h" #include "mtproto/mtp_instance.h" @@ -41,7 +42,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_controller.h" #include "boxes/confirm_box.h" #include "apiwrap.h" -#include "app.h" // App::formatPhone +#include "ui/text/format_values.h" // Ui::FormatPhone +#include "app.h" // App::quitting namespace Api { namespace { @@ -1054,7 +1056,7 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) { //MTPMessageReactions(), MTPVector(), MTP_int(d.vttl_period().value_or_empty())), - MTPDmessage_ClientFlags(), + MessageFlags(), NewMessageType::Unread); } break; @@ -1085,7 +1087,7 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) { //MTPMessageReactions(), MTPVector(), MTP_int(d.vttl_period().value_or_empty())), - MTPDmessage_ClientFlags(), + MessageFlags(), NewMessageType::Unread); } break; @@ -1114,7 +1116,7 @@ void Updates::applyUpdateNoPtsCheck(const MTPUpdate &update) { if (needToAdd) { _session->data().addNewMessage( d.vmessage(), - MTPDmessage_ClientFlags(), + MessageFlags(), NewMessageType::Unread); } } break; @@ -1208,7 +1210,7 @@ void Updates::applyUpdateNoPtsCheck(const MTPUpdate &update) { if (needToAdd) { _session->data().addNewMessage( d.vmessage(), - MTPDmessage_ClientFlags(), + MessageFlags(), NewMessageType::Unread); } } break; @@ -1844,7 +1846,7 @@ void Updates::feedUpdate(const MTPUpdate &update) { || user->isSelf() || user->phone().isEmpty()) ? QString() - : App::formatPhone(user->phone())), + : Ui::FormatPhone(user->phone())), user->username); session().changes().peerUpdated( @@ -1953,13 +1955,10 @@ void Updates::feedUpdate(const MTPUpdate &update) { } return true; }; - if (const auto key = ApiWrap::Privacy::KeyFromMTP(d.vkey().type())) { - if (allLoaded()) { - session().api().handlePrivacyChange(*key, d.vrules()); - } else { - session().api().reloadPrivacy(*key); - } - } + session().api().userPrivacy().apply( + d.vkey().type(), + d.vrules(), + allLoaded()); } break; case mtpc_updatePinnedDialogs: { diff --git a/Telegram/SourceFiles/api/api_user_privacy.cpp b/Telegram/SourceFiles/api/api_user_privacy.cpp new file mode 100644 index 000000000..da5f9d190 --- /dev/null +++ b/Telegram/SourceFiles/api/api_user_privacy.cpp @@ -0,0 +1,303 @@ +/* +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 "api/api_user_privacy.h" + +#include "apiwrap.h" +#include "data/data_chat.h" +#include "data/data_channel.h" +#include "data/data_peer.h" +#include "data/data_peer_id.h" +#include "data/data_session.h" +#include "data/data_user.h" +#include "main/main_session.h" + +namespace Api { +namespace { + +constexpr auto kMaxRules = 3; // Allow users, disallow users, Option. + +using TLInputRules = MTPVector; +using TLRules = MTPVector; + +TLInputRules RulesToTL(const UserPrivacy::Rule &rule) { + const auto collectInputUsers = [](const auto &peers) { + auto result = QVector(); + result.reserve(peers.size()); + for (const auto peer : peers) { + if (const auto user = peer->asUser()) { + result.push_back(user->inputUser); + } + } + return result; + }; + const auto collectInputChats = [](const auto &peers) { + auto result = QVector(); // #TODO ids + result.reserve(peers.size()); + for (const auto peer : peers) { + if (!peer->isUser()) { + result.push_back(peerToBareMTPInt(peer->id)); + } + } + return result; + }; + + auto result = QVector(); + result.reserve(kMaxRules); + if (!rule.ignoreAlways) { + const auto users = collectInputUsers(rule.always); + const auto chats = collectInputChats(rule.always); + if (!users.empty()) { + result.push_back( + MTP_inputPrivacyValueAllowUsers( + MTP_vector(users))); + } + if (!chats.empty()) { + result.push_back( + MTP_inputPrivacyValueAllowChatParticipants( + MTP_vector(chats))); + } + } + if (!rule.ignoreNever) { + const auto users = collectInputUsers(rule.never); + const auto chats = collectInputChats(rule.never); + if (!users.empty()) { + result.push_back( + MTP_inputPrivacyValueDisallowUsers( + MTP_vector(users))); + } + if (!chats.empty()) { + result.push_back( + MTP_inputPrivacyValueDisallowChatParticipants( + MTP_vector(chats))); + } + } + result.push_back([&] { + using Option = UserPrivacy::Option; + switch (rule.option) { + case Option::Everyone: return MTP_inputPrivacyValueAllowAll(); + case Option::Contacts: return MTP_inputPrivacyValueAllowContacts(); + case Option::Nobody: return MTP_inputPrivacyValueDisallowAll(); + } + Unexpected("Option value in Api::UserPrivacy::RulesToTL."); + }()); + + + return MTP_vector(std::move(result)); +} + +UserPrivacy::Rule TLToRules(const TLRules &rules, Data::Session &owner) { + // This is simplified version of privacy rules interpretation. + // But it should be fine for all the apps + // that use the same subset of features. + using Option = UserPrivacy::Option; + auto result = UserPrivacy::Rule(); + auto optionSet = false; + const auto setOption = [&](Option option) { + if (optionSet) return; + optionSet = true; + result.option = option; + }; + auto &always = result.always; + auto &never = result.never; + const auto feed = [&](const MTPPrivacyRule &rule) { + rule.match([&](const MTPDprivacyValueAllowAll &) { + setOption(Option::Everyone); + }, [&](const MTPDprivacyValueAllowContacts &) { + setOption(Option::Contacts); + }, [&](const MTPDprivacyValueAllowUsers &data) { + const auto &users = data.vusers().v; + always.reserve(always.size() + users.size()); + for (const auto userId : users) { + const auto user = owner.user(UserId(userId.v)); + if (!base::contains(never, user) + && !base::contains(always, user)) { + always.emplace_back(user); + } + } + }, [&](const MTPDprivacyValueAllowChatParticipants &data) { + const auto &chats = data.vchats().v; + always.reserve(always.size() + chats.size()); + for (const auto &chatId : chats) { + const auto chat = owner.chatLoaded(chatId); + const auto peer = chat + ? static_cast(chat) + : owner.channelLoaded(chatId); + if (peer + && !base::contains(never, peer) + && !base::contains(always, peer)) { + always.emplace_back(peer); + } + } + }, [&](const MTPDprivacyValueDisallowContacts &) { + // Not supported + }, [&](const MTPDprivacyValueDisallowAll &) { + setOption(Option::Nobody); + }, [&](const MTPDprivacyValueDisallowUsers &data) { + const auto &users = data.vusers().v; + never.reserve(never.size() + users.size()); + for (const auto userId : users) { + const auto user = owner.user(UserId(userId.v)); + if (!base::contains(always, user) + && !base::contains(never, user)) { + never.emplace_back(user); + } + } + }, [&](const MTPDprivacyValueDisallowChatParticipants &data) { + const auto &chats = data.vchats().v; + never.reserve(never.size() + chats.size()); + for (const auto &chatId : chats) { + const auto chat = owner.chatLoaded(chatId); + const auto peer = chat + ? static_cast(chat) + : owner.channelLoaded(chatId); + if (peer + && !base::contains(always, peer) + && !base::contains(never, peer)) { + never.emplace_back(peer); + } + } + }); + }; + for (const auto &rule : rules.v) { + feed(rule); + } + feed(MTP_privacyValueDisallowAll()); // Disallow by default. + return result; +} + +MTPInputPrivacyKey KeyToTL(UserPrivacy::Key key) { + using Key = UserPrivacy::Key; + switch (key) { + case Key::Calls: return MTP_inputPrivacyKeyPhoneCall(); + case Key::Invites: return MTP_inputPrivacyKeyChatInvite(); + case Key::PhoneNumber: return MTP_inputPrivacyKeyPhoneNumber(); + case Key::AddedByPhone: + return MTP_inputPrivacyKeyAddedByPhone(); + case Key::LastSeen: + return MTP_inputPrivacyKeyStatusTimestamp(); + case Key::CallsPeer2Peer: + return MTP_inputPrivacyKeyPhoneP2P(); + case Key::Forwards: + return MTP_inputPrivacyKeyForwards(); + case Key::ProfilePhoto: + return MTP_inputPrivacyKeyProfilePhoto(); + } + Unexpected("Key in Api::UserPrivacy::KetToTL."); +} + +std::optional TLToKey(mtpTypeId type) { + using Key = UserPrivacy::Key; + switch (type) { + case mtpc_privacyKeyPhoneNumber: + case mtpc_inputPrivacyKeyPhoneNumber: return Key::PhoneNumber; + case mtpc_privacyKeyAddedByPhone: + case mtpc_inputPrivacyKeyAddedByPhone: return Key::AddedByPhone; + case mtpc_privacyKeyStatusTimestamp: + case mtpc_inputPrivacyKeyStatusTimestamp: return Key::LastSeen; + case mtpc_privacyKeyChatInvite: + case mtpc_inputPrivacyKeyChatInvite: return Key::Invites; + case mtpc_privacyKeyPhoneCall: + case mtpc_inputPrivacyKeyPhoneCall: return Key::Calls; + case mtpc_privacyKeyPhoneP2P: + case mtpc_inputPrivacyKeyPhoneP2P: return Key::CallsPeer2Peer; + case mtpc_privacyKeyForwards: + case mtpc_inputPrivacyKeyForwards: return Key::Forwards; + case mtpc_privacyKeyProfilePhoto: + case mtpc_inputPrivacyKeyProfilePhoto: return Key::ProfilePhoto; + } + return std::nullopt; +} + +} // namespace + +UserPrivacy::UserPrivacy(not_null api) +: _session(&api->session()) +, _api(&api->instance()) { +} + +void UserPrivacy::save( + Key key, + const UserPrivacy::Rule &rule) { + const auto tlKey = KeyToTL(key); + const auto keyTypeId = tlKey.type(); + const auto it = _privacySaveRequests.find(keyTypeId); + if (it != _privacySaveRequests.cend()) { + _api.request(it->second).cancel(); + _privacySaveRequests.erase(it); + } + + const auto requestId = _api.request(MTPaccount_SetPrivacy( + tlKey, + RulesToTL(rule) + )).done([=](const MTPaccount_PrivacyRules &result) { + result.match([&](const MTPDaccount_privacyRules &data) { + _session->data().processUsers(data.vusers()); + _session->data().processChats(data.vchats()); + _privacySaveRequests.remove(keyTypeId); + apply(keyTypeId, data.vrules(), true); + }); + }).fail([=](const MTP::Error &error) { + _privacySaveRequests.remove(keyTypeId); + }).send(); + + _privacySaveRequests.emplace(keyTypeId, requestId); +} + +void UserPrivacy::apply( + mtpTypeId type, + const TLRules &rules, + bool allLoaded) { + if (const auto key = TLToKey(type)) { + if (!allLoaded) { + reload(*key); + return; + } + pushPrivacy(*key, rules); + if ((*key) == Key::LastSeen) { + _session->api().updatePrivacyLastSeens(); + } + } +} + +void UserPrivacy::reload(Key key) { + if (_privacyRequestIds.contains(key)) { + return; + } + const auto requestId = _api.request(MTPaccount_GetPrivacy( + KeyToTL(key) + )).done([=](const MTPaccount_PrivacyRules &result) { + _privacyRequestIds.erase(key); + result.match([&](const MTPDaccount_privacyRules &data) { + _session->data().processUsers(data.vusers()); + _session->data().processChats(data.vchats()); + pushPrivacy(key, data.vrules()); + }); + }).fail([=](const MTP::Error &error) { + _privacyRequestIds.erase(key); + }).send(); + _privacyRequestIds.emplace(key, requestId); +} + +void UserPrivacy::pushPrivacy(Key key, const TLRules &rules) { + const auto &saved = (_privacyValues[key] = + TLToRules(rules, _session->data())); + const auto i = _privacyChanges.find(key); + if (i != end(_privacyChanges)) { + i->second.fire_copy(saved); + } +} + +auto UserPrivacy::value(Key key) -> rpl::producer { + if (const auto i = _privacyValues.find(key); i != end(_privacyValues)) { + return _privacyChanges[key].events_starting_with_copy(i->second); + } else { + return _privacyChanges[key].events(); + } +} + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_user_privacy.h b/Telegram/SourceFiles/api/api_user_privacy.h new file mode 100644 index 000000000..a706cea02 --- /dev/null +++ b/Telegram/SourceFiles/api/api_user_privacy.h @@ -0,0 +1,73 @@ +/* +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 "mtproto/sender.h" + +class ApiWrap; + +namespace Main { +class Session; +} // namespace Main + +namespace Api { + +class UserPrivacy final { +public: + enum class Key { + PhoneNumber, + AddedByPhone, + LastSeen, + Calls, + Invites, + CallsPeer2Peer, + Forwards, + ProfilePhoto, + }; + enum class Option { + Everyone, + Contacts, + Nobody, + }; + struct Rule { + Option option = Option::Everyone; + std::vector> always; + std::vector> never; + bool ignoreAlways = false; + bool ignoreNever = false; + }; + + explicit UserPrivacy(not_null api); + + void save( + Key key, + const UserPrivacy::Rule &rule); + void apply( + mtpTypeId type, + const MTPVector &rules, + bool allLoaded); + + void reload(Key key); + rpl::producer value(Key key); + +private: + const not_null _session; + + void pushPrivacy(Key key, const MTPVector &rules); + + base::flat_map _privacySaveRequests; + + base::flat_map _privacyRequestIds; + base::flat_map _privacyValues; + std::map> _privacyChanges; + + MTP::Sender _api; + +}; + +} // namespace Api diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index e80affd1f..abdae2379 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -9,6 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_authorizations.h" #include "api/api_attached_stickers.h" +#include "api/api_blocked_peers.h" +#include "api/api_cloud_password.h" #include "api/api_hash.h" #include "api/api_invite_links.h" #include "api/api_media.h" @@ -18,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_sensitive_content.h" #include "api/api_global_privacy.h" #include "api/api_updates.h" +#include "api/api_user_privacy.h" #include "data/stickers/data_stickers.h" #include "data/data_drafts.h" #include "data/data_changes.h" @@ -42,7 +45,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/dialogs_key.h" #include "core/core_cloud_password.h" #include "core/application.h" -#include "base/openssl_help.h" #include "base/unixtime.h" #include "base/qt_adapters.h" #include "base/call_delayed.h" @@ -108,7 +110,6 @@ constexpr auto kStickersByEmojiInvalidateTimeout = crl::time(60 * 60 * 1000); constexpr auto kNotifySettingSaveTimeout = crl::time(1000); constexpr auto kDialogsFirstLoad = 20; constexpr auto kDialogsPerPage = 500; -constexpr auto kBlockedFirstSlice = 16; using PhotoFileLocationId = Data::PhotoFileLocationId; using DocumentFileLocationId = Data::DocumentFileLocationId; @@ -120,65 +121,6 @@ using UpdatedFileReferences = Data::UpdatedFileReferences; } // namespace -MTPInputPrivacyKey ApiWrap::Privacy::Input(Key key) { - switch (key) { - case Privacy::Key::Calls: return MTP_inputPrivacyKeyPhoneCall(); - case Privacy::Key::Invites: return MTP_inputPrivacyKeyChatInvite(); - case Privacy::Key::PhoneNumber: return MTP_inputPrivacyKeyPhoneNumber(); - case Privacy::Key::AddedByPhone: - return MTP_inputPrivacyKeyAddedByPhone(); - case Privacy::Key::LastSeen: - return MTP_inputPrivacyKeyStatusTimestamp(); - case Privacy::Key::CallsPeer2Peer: - return MTP_inputPrivacyKeyPhoneP2P(); - case Privacy::Key::Forwards: - return MTP_inputPrivacyKeyForwards(); - case Privacy::Key::ProfilePhoto: - return MTP_inputPrivacyKeyProfilePhoto(); - } - Unexpected("Key in ApiWrap::Privacy::Input."); -} - -std::optional ApiWrap::Privacy::KeyFromMTP( - mtpTypeId type) { - using Key = Privacy::Key; - switch (type) { - case mtpc_privacyKeyPhoneNumber: - case mtpc_inputPrivacyKeyPhoneNumber: return Key::PhoneNumber; - case mtpc_privacyKeyAddedByPhone: - case mtpc_inputPrivacyKeyAddedByPhone: return Key::AddedByPhone; - case mtpc_privacyKeyStatusTimestamp: - case mtpc_inputPrivacyKeyStatusTimestamp: return Key::LastSeen; - case mtpc_privacyKeyChatInvite: - case mtpc_inputPrivacyKeyChatInvite: return Key::Invites; - case mtpc_privacyKeyPhoneCall: - case mtpc_inputPrivacyKeyPhoneCall: return Key::Calls; - case mtpc_privacyKeyPhoneP2P: - case mtpc_inputPrivacyKeyPhoneP2P: return Key::CallsPeer2Peer; - case mtpc_privacyKeyForwards: - case mtpc_inputPrivacyKeyForwards: return Key::Forwards; - case mtpc_privacyKeyProfilePhoto: - case mtpc_inputPrivacyKeyProfilePhoto: return Key::ProfilePhoto; - } - return std::nullopt; -} - -bool ApiWrap::BlockedPeersSlice::Item::operator==(const Item &other) const { - return (peer == other.peer) && (date == other.date); -} - -bool ApiWrap::BlockedPeersSlice::Item::operator!=(const Item &other) const { - return !(*this == other); -} - -bool ApiWrap::BlockedPeersSlice::operator==(const BlockedPeersSlice &other) const { - return (total == other.total) && (list == other.list); -} - -bool ApiWrap::BlockedPeersSlice::operator!=(const BlockedPeersSlice &other) const { - return !(*this == other); -} - ApiWrap::ApiWrap(not_null session) : MTP::Sender(&session->account().mtp()) , _session(session) @@ -192,9 +134,12 @@ ApiWrap::ApiWrap(not_null session) , _updateNotifySettingsTimer([=] { sendNotifySettingsUpdates(); }) , _authorizations(std::make_unique(this)) , _attachedStickers(std::make_unique(this)) +, _blockedPeers(std::make_unique(this)) +, _cloudPassword(std::make_unique(this)) , _selfDestruct(std::make_unique(this)) , _sensitiveContent(std::make_unique(this)) , _globalPrivacy(std::make_unique(this)) +, _userPrivacy(std::make_unique(this)) , _inviteLinks(std::make_unique(this)) { crl::on_main(session, [=] { // You can't use _session->lifetime() in the constructor, @@ -2175,68 +2120,6 @@ void ApiWrap::leaveChannel(not_null channel) { } } -void ApiWrap::blockPeer(not_null peer) { - if (peer->isBlocked()) { - session().changes().peerUpdated( - peer, - Data::PeerUpdate::Flag::IsBlocked); - } else if (_blockRequests.find(peer) == end(_blockRequests)) { - const auto requestId = request(MTPcontacts_Block( - peer->input - )).done([=](const MTPBool &result) { - _blockRequests.erase(peer); - peer->setIsBlocked(true); - if (_blockedPeersSlice) { - _blockedPeersSlice->list.insert( - _blockedPeersSlice->list.begin(), - { peer, base::unixtime::now() }); - ++_blockedPeersSlice->total; - _blockedPeersChanges.fire_copy(*_blockedPeersSlice); - } - }).fail([=](const MTP::Error &error) { - _blockRequests.erase(peer); - }).send(); - - _blockRequests.emplace(peer, requestId); - } -} - -void ApiWrap::unblockPeer(not_null peer, Fn onDone) { - if (!peer->isBlocked()) { - session().changes().peerUpdated( - peer, - Data::PeerUpdate::Flag::IsBlocked); - return; - } else if (_blockRequests.find(peer) != end(_blockRequests)) { - return; - } - const auto requestId = request(MTPcontacts_Unblock( - peer->input - )).done([=](const MTPBool &result) { - _blockRequests.erase(peer); - peer->setIsBlocked(false); - if (_blockedPeersSlice) { - auto &list = _blockedPeersSlice->list; - for (auto i = list.begin(); i != list.end(); ++i) { - if (i->peer == peer) { - list.erase(i); - break; - } - } - if (_blockedPeersSlice->total > list.size()) { - --_blockedPeersSlice->total; - } - _blockedPeersChanges.fire_copy(*_blockedPeersSlice); - } - if (onDone) { - onDone(); - } - }).fail([=](const MTP::Error &error) { - _blockRequests.erase(peer); - }).send(); - _blockRequests.emplace(peer, requestId); -} - void ApiWrap::requestNotifySettings(const MTPInputNotifyPeer &peer) { const auto key = [&] { switch (peer.type()) { @@ -2309,45 +2192,7 @@ void ApiWrap::saveDraftToCloudDelayed(not_null history) { } } -void ApiWrap::savePrivacy( - const MTPInputPrivacyKey &key, - QVector &&rules) { - const auto keyTypeId = key.type(); - const auto it = _privacySaveRequests.find(keyTypeId); - if (it != _privacySaveRequests.cend()) { - request(it->second).cancel(); - _privacySaveRequests.erase(it); - } - - const auto requestId = request(MTPaccount_SetPrivacy( - key, - MTP_vector(std::move(rules)) - )).done([=](const MTPaccount_PrivacyRules &result) { - result.match([&](const MTPDaccount_privacyRules &data) { - _session->data().processUsers(data.vusers()); - _session->data().processChats(data.vchats()); - _privacySaveRequests.remove(keyTypeId); - if (const auto key = Privacy::KeyFromMTP(keyTypeId)) { - handlePrivacyChange(*key, data.vrules()); - } - }); - }).fail([=](const MTP::Error &error) { - _privacySaveRequests.remove(keyTypeId); - }).send(); - - _privacySaveRequests.emplace(keyTypeId, requestId); -} - -void ApiWrap::handlePrivacyChange( - Privacy::Key key, - const MTPVector &rules) { - pushPrivacy(key, rules.v); - if (key == Privacy::Key::LastSeen) { - updatePrivacyLastSeens(rules.v); - } -} - -void ApiWrap::updatePrivacyLastSeens(const QVector &rules) { +void ApiWrap::updatePrivacyLastSeens() { const auto now = base::unixtime::now(); _session->data().enumerateUsers([&](UserData *user) { if (user->isSelf() || !user->isFullLoaded()) { @@ -3787,18 +3632,17 @@ void ApiWrap::forwardMessages( const auto anonymousPost = peer->amAnonymous(); const auto silentPost = ShouldSendSilent(peer, action.options); - auto flags = MTPDmessage::Flags(0); - auto clientFlags = MTPDmessage_ClientFlags(); + auto flags = MessageFlags(); auto sendFlags = MTPmessages_ForwardMessages::Flags(0); FillMessagePostFlags(action, peer, flags); if (silentPost) { sendFlags |= MTPmessages_ForwardMessages::Flag::f_silent; } if (action.options.scheduled) { - flags |= MTPDmessage::Flag::f_from_scheduled; + flags |= MessageFlag::IsOrWasScheduled; sendFlags |= MTPmessages_ForwardMessages::Flag::f_schedule_date; } else { - clientFlags |= MTPDmessage_ClientFlag::f_local_history_entry; + flags |= MessageFlag::LocalHistoryEntry; } auto forwardFrom = items.front()->history()->peer; @@ -3866,7 +3710,6 @@ void ApiWrap::forwardMessages( history->addNewLocalMessage( newId.msg, flags, - clientFlags, HistoryItem::NewMessageDate(action.options.scheduled), messageFromId, messagePostAuthor, @@ -3930,18 +3773,17 @@ void ApiWrap::forwardMessagesUnquoted( const auto anonymousPost = peer->amAnonymous(); const auto silentPost = ShouldSendSilent(peer, action.options); - auto flags = MTPDmessage::Flags(0); - auto clientFlags = MTPDmessage_ClientFlags(); + auto flags = MessageFlags(); auto sendFlags = MTPmessages_ForwardMessages::Flags(0); FillMessagePostFlags(action, peer, flags); if (silentPost) { sendFlags |= MTPmessages_ForwardMessages::Flag::f_silent; } if (action.options.scheduled) { - flags |= MTPDmessage::Flag::f_from_scheduled; + flags |= MessageFlag::IsOrWasScheduled; sendFlags |= MTPmessages_ForwardMessages::Flag::f_schedule_date; } else { - clientFlags |= MTPDmessage_ClientFlag::f_local_history_entry; + flags |= MessageFlag::LocalHistoryEntry; } auto forwardFrom = items.front()->history()->peer; @@ -4044,15 +3886,14 @@ void ApiWrap::forwardMessagesUnquoted( const auto forwards = 0; const auto newGroupId = openssl::RandomValue(); - auto msgFlags = NewMessageFlags(peer) | MTPDmessage::Flag::f_media; - auto clientFlags = NewMessageClientFlags(); + auto msgFlags = NewMessageFlags(peer); FillMessagePostFlags(action, peer, msgFlags); if (action.options.scheduled) { - msgFlags |= MTPDmessage::Flag::f_from_scheduled; + msgFlags |= MessageFlag::IsOrWasScheduled; } else { - clientFlags |= MTPDmessage_ClientFlag::f_local_history_entry; + msgFlags |= MessageFlag::LocalHistoryEntry; } for (auto i = fromIter, e = toIter; i != e; i++) { @@ -4093,7 +3934,6 @@ void ApiWrap::forwardMessagesUnquoted( history->addNewLocalMessage( newId.msg, msgFlags, - clientFlags, 0, // viaBotId 0, // replyTo HistoryItem::NewMessageDate(action.options.scheduled), @@ -4107,7 +3947,6 @@ void ApiWrap::forwardMessagesUnquoted( history->addNewLocalMessage( newId.msg, msgFlags, - clientFlags, 0, // viaBotId 0, // replyTo HistoryItem::NewMessageDate(action.options.scheduled), @@ -4415,61 +4254,44 @@ void ApiWrap::sendSharedContact( _session->data().nextLocalMessageId()); const auto anonymousPost = peer->amAnonymous(); - auto flags = NewMessageFlags(peer) | MTPDmessage::Flag::f_media; - auto clientFlags = NewMessageClientFlags(); + auto flags = NewMessageFlags(peer); if (action.replyTo) { - flags |= MTPDmessage::Flag::f_reply_to; + flags |= MessageFlag::HasReplyInfo; } const auto replyHeader = NewMessageReplyHeader(action); FillMessagePostFlags(action, peer, flags); if (action.options.scheduled) { - flags |= MTPDmessage::Flag::f_from_scheduled; + flags |= MessageFlag::IsOrWasScheduled; } else { - clientFlags |= MTPDmessage_ClientFlag::f_local_history_entry; + flags |= MessageFlag::LocalHistoryEntry; } const auto messageFromId = anonymousPost ? 0 : _session->userPeerId(); const auto messagePostAuthor = peer->isBroadcast() ? _session->user()->name : QString(); - const auto vcard = QString(); - const auto views = 1; - const auto forwards = 0; - const auto item = history->addNewMessage( - MTP_message( - MTP_flags(flags), - MTP_int(newId.msg), - peerToMTP(messageFromId), - peerToMTP(peer->id), - MTPMessageFwdHeader(), - MTPint(), // via_bot_id - replyHeader, - MTP_int(HistoryItem::NewMessageDate(action.options.scheduled)), - MTP_string(), - MTP_messageMediaContact( - MTP_string(phone), - MTP_string(firstName), - MTP_string(lastName), - MTP_string(vcard), - MTP_int(userId.bare)), // #TODO ids - MTPReplyMarkup(), - MTPVector(), - MTP_int(views), - MTP_int(forwards), - MTPMessageReplies(), - MTPint(), // edit_date - MTP_string(messagePostAuthor), - MTPlong(), - //MTPMessageReactions(), - MTPVector(), - MTPint()), // ttl_period - clientFlags, - NewMessageType::Unread); + const auto viaBotId = UserId(); + const auto item = history->addNewLocalMessage( + newId.msg, + flags, + viaBotId, + action.replyTo, + HistoryItem::NewMessageDate(action.options.scheduled), + messageFromId, + messagePostAuthor, + TextWithEntities(), + MTP_messageMediaContact( + MTP_string(phone), + MTP_string(firstName), + MTP_string(lastName), + MTP_string(), // vcard + MTP_int(userId.bare)), // #TODO ids + MTPReplyMarkup()); const auto media = MTP_inputMediaContact( MTP_string(phone), MTP_string(firstName), MTP_string(lastName), - MTP_string(vcard)); + MTP_string()); // vcard sendMedia(item, media, action.options); _session->data().sendHistoryChangeNotifications(); @@ -4678,11 +4500,10 @@ void ApiWrap::sendMessage( _session->data().registerMessageSentData(randomId, peer->id, sending.text); MTPstring msgText(MTP_string(sending.text)); - auto flags = NewMessageFlags(peer) | MTPDmessage::Flag::f_entities; - auto clientFlags = NewMessageClientFlags(); + auto flags = NewMessageFlags(peer); auto sendFlags = MTPmessages_SendMessage::Flags(0); if (action.replyTo) { - flags |= MTPDmessage::Flag::f_reply_to; + flags |= MessageFlag::HasReplyInfo; sendFlags |= MTPmessages_SendMessage::Flag::f_reply_to_msg_id; } const auto replyHeader = NewMessageReplyHeader(action); @@ -4695,7 +4516,6 @@ void ApiWrap::sendMessage( MTP_webPagePending( MTP_long(page->id), MTP_int(page->pendingTill))); - flags |= MTPDmessage::Flag::f_media; } const auto anonymousPost = peer->amAnonymous(); const auto silentPost = ShouldSendSilent(peer, action.options); @@ -4703,10 +4523,7 @@ void ApiWrap::sendMessage( if (silentPost) { sendFlags |= MTPmessages_SendMessage::Flag::f_silent; } - auto localEntities = Api::EntitiesToMTP( - _session, - sending.entities); - auto sentEntities = Api::EntitiesToMTP( + const auto sentEntities = Api::EntitiesToMTP( _session, sending.entities, Api::ConvertOption::SkipLocal); @@ -4724,39 +4541,23 @@ void ApiWrap::sendMessage( ? _session->user()->name : QString(); if (action.options.scheduled) { - flags |= MTPDmessage::Flag::f_from_scheduled; + flags |= MessageFlag::IsOrWasScheduled; sendFlags |= MTPmessages_SendMessage::Flag::f_schedule_date; } else { - clientFlags |= MTPDmessage_ClientFlag::f_local_history_entry; - } - const auto views = 1; - const auto forwards = 0; - lastMessage = history->addNewMessage( - MTP_message( - MTP_flags(flags), - MTP_int(newId.msg), - peerToMTP(messageFromId), - peerToMTP(peer->id), - MTPMessageFwdHeader(), - MTPint(), // via_bot_id - replyHeader, - MTP_int( - HistoryItem::NewMessageDate(action.options.scheduled)), - msgText, - media, - MTPReplyMarkup(), - localEntities, - MTP_int(views), - MTP_int(forwards), - MTPMessageReplies(), - MTPint(), // edit_date - MTP_string(messagePostAuthor), - MTPlong(), - //MTPMessageReactions(), - MTPVector(), - MTPint()), // ttl_period - clientFlags, - NewMessageType::Unread); + flags |= MessageFlag::LocalHistoryEntry; + } + const auto viaBotId = UserId(); + lastMessage = history->addNewLocalMessage( + newId.msg, + flags, + viaBotId, + action.replyTo, + HistoryItem::NewMessageDate(action.options.scheduled), + messageFromId, + messagePostAuthor, + sending, + media, + MTPReplyMarkup()); histories.sendRequest(history, requestType, [=](Fn finish) { history->sendRequestId = request(MTPmessages_SendMessage( MTP_flags(sendFlags), @@ -4848,11 +4649,10 @@ void ApiWrap::sendInlineResult( _session->data().nextLocalMessageId()); const auto randomId = openssl::RandomValue(); - auto flags = NewMessageFlags(peer) | MTPDmessage::Flag::f_media; - auto clientFlags = NewMessageClientFlags(); + auto flags = NewMessageFlags(peer); auto sendFlags = MTPmessages_SendInlineBotResult::Flag::f_clear_draft | 0; if (action.replyTo) { - flags |= MTPDmessage::Flag::f_reply_to; + flags |= MessageFlag::HasReplyInfo; sendFlags |= MTPmessages_SendInlineBotResult::Flag::f_reply_to_msg_id; } const auto anonymousPost = peer->amAnonymous(); @@ -4865,14 +4665,14 @@ void ApiWrap::sendInlineResult( if (action.options.hideVia) { sendFlags |= MTPmessages_SendInlineBotResult::Flag::f_hide_via; } else { - flags |= MTPDmessage::Flag::f_via_bot_id; + flags |= MessageFlag::HasViaBot; } } if (action.options.scheduled) { - flags |= MTPDmessage::Flag::f_from_scheduled; + flags |= MessageFlag::IsOrWasScheduled; sendFlags |= MTPmessages_SendInlineBotResult::Flag::f_schedule_date; } else { - clientFlags |= MTPDmessage_ClientFlag::f_local_history_entry; + flags |= MessageFlag::LocalHistoryEntry; } const auto messageFromId = anonymousPost ? 0 : _session->userPeerId(); @@ -4885,10 +4685,9 @@ void ApiWrap::sendInlineResult( data->addToHistory( history, flags, - clientFlags, newId.msg, messageFromId, - MTP_int(HistoryItem::NewMessageDate(action.options.scheduled)), + HistoryItem::NewMessageDate(action.options.scheduled), bot && !action.options.hideVia ? peerToUser(bot->id) : 0, action.replyTo, messagePostAuthor); @@ -5290,52 +5089,6 @@ void ApiWrap::clearPeerPhoto(not_null photo) { } } -void ApiWrap::reloadPasswordState() { - if (_passwordRequestId) { - return; - } - _passwordRequestId = request(MTPaccount_GetPassword( - )).done([=](const MTPaccount_Password &result) { - _passwordRequestId = 0; - result.match([&](const MTPDaccount_password &data) { - openssl::AddRandomSeed(bytes::make_span(data.vsecure_random().v)); - if (_passwordState) { - *_passwordState = Core::ParseCloudPasswordState(data); - } else { - _passwordState = std::make_unique( - Core::ParseCloudPasswordState(data)); - } - _passwordStateChanges.fire_copy(*_passwordState); - }); - }).fail([=](const MTP::Error &error) { - _passwordRequestId = 0; - }).send(); -} - -void ApiWrap::clearUnconfirmedPassword() { - _passwordRequestId = request(MTPaccount_CancelPasswordEmail( - )).done([=](const MTPBool &result) { - _passwordRequestId = 0; - reloadPasswordState(); - }).fail([=](const MTP::Error &error) { - _passwordRequestId = 0; - reloadPasswordState(); - }).send(); -} - -rpl::producer ApiWrap::passwordState() const { - return _passwordState - ? _passwordStateChanges.events_starting_with_copy(*_passwordState) - : (_passwordStateChanges.events() | rpl::type_erased()); -} - -auto ApiWrap::passwordStateCurrent() const --> std::optional { - return _passwordState - ? base::make_optional(*_passwordState) - : std::nullopt; -} - void ApiWrap::reloadContactSignupSilent() { if (_contactSignupSilentRequestId) { return; @@ -5409,176 +5162,6 @@ void ApiWrap::saveSelfBio(const QString &text, FnMut done) { }).send(); } -void ApiWrap::reloadPrivacy(Privacy::Key key) { - if (_privacyRequestIds.contains(key)) { - return; - } - const auto requestId = request(MTPaccount_GetPrivacy( - Privacy::Input(key) - )).done([=](const MTPaccount_PrivacyRules &result) { - _privacyRequestIds.erase(key); - result.match([&](const MTPDaccount_privacyRules &data) { - _session->data().processUsers(data.vusers()); - _session->data().processChats(data.vchats()); - pushPrivacy(key, data.vrules().v); - }); - }).fail([=](const MTP::Error &error) { - _privacyRequestIds.erase(key); - }).send(); - _privacyRequestIds.emplace(key, requestId); -} - -auto ApiWrap::parsePrivacy(const QVector &rules) --> Privacy { - using Option = Privacy::Option; - - // This is simplified version of privacy rules interpretation. - // But it should be fine for all the apps - // that use the same subset of features. - auto result = Privacy(); - auto optionSet = false; - const auto SetOption = [&](Option option) { - if (optionSet) return; - optionSet = true; - result.option = option; - }; - auto &always = result.always; - auto &never = result.never; - const auto Feed = [&](const MTPPrivacyRule &rule) { - rule.match([&](const MTPDprivacyValueAllowAll &) { - SetOption(Option::Everyone); - }, [&](const MTPDprivacyValueAllowContacts &) { - SetOption(Option::Contacts); - }, [&](const MTPDprivacyValueAllowUsers &data) { - const auto &users = data.vusers().v; - always.reserve(always.size() + users.size()); - for (const auto userId : users) { - const auto user = _session->data().user(UserId(userId.v)); - if (!base::contains(never, user) - && !base::contains(always, user)) { - always.emplace_back(user); - } - } - }, [&](const MTPDprivacyValueAllowChatParticipants &data) { - const auto &chats = data.vchats().v; - always.reserve(always.size() + chats.size()); - for (const auto &chatId : chats) { - const auto chat = _session->data().chatLoaded(chatId); - const auto peer = chat - ? static_cast(chat) - : _session->data().channelLoaded(chatId); - if (peer - && !base::contains(never, peer) - && !base::contains(always, peer)) { - always.emplace_back(peer); - } - } - }, [&](const MTPDprivacyValueDisallowContacts &) { - // not supported - }, [&](const MTPDprivacyValueDisallowAll &) { - SetOption(Option::Nobody); - }, [&](const MTPDprivacyValueDisallowUsers &data) { - const auto &users = data.vusers().v; - never.reserve(never.size() + users.size()); - for (const auto userId : users) { - const auto user = _session->data().user(UserId(userId.v)); - if (!base::contains(always, user) - && !base::contains(never, user)) { - never.emplace_back(user); - } - } - }, [&](const MTPDprivacyValueDisallowChatParticipants &data) { - const auto &chats = data.vchats().v; - never.reserve(never.size() + chats.size()); - for (const auto &chatId : chats) { - const auto chat = _session->data().chatLoaded(chatId); - const auto peer = chat - ? static_cast(chat) - : _session->data().channelLoaded(chatId); - if (peer - && !base::contains(always, peer) - && !base::contains(never, peer)) { - never.emplace_back(peer); - } - } - }); - }; - for (const auto &rule : rules) { - Feed(rule); - } - Feed(MTP_privacyValueDisallowAll()); // disallow by default. - return result; -} - -void ApiWrap::pushPrivacy( - Privacy::Key key, - const QVector &rules) { - const auto &saved = (_privacyValues[key] = parsePrivacy(rules)); - const auto i = _privacyChanges.find(key); - if (i != end(_privacyChanges)) { - i->second.fire_copy(saved); - } -} - -auto ApiWrap::privacyValue(Privacy::Key key) -> rpl::producer { - if (const auto i = _privacyValues.find(key); i != end(_privacyValues)) { - return _privacyChanges[key].events_starting_with_copy(i->second); - } else { - return _privacyChanges[key].events(); - } -} - -void ApiWrap::reloadBlockedPeers() { - if (_blockedPeersRequestId) { - return; - } - _blockedPeersRequestId = request(MTPcontacts_GetBlocked( - MTP_int(0), - MTP_int(kBlockedFirstSlice) - )).done([=](const MTPcontacts_Blocked &result) { - _blockedPeersRequestId = 0; - const auto push = [&]( - int count, - const QVector &list) { - auto slice = BlockedPeersSlice(); - slice.total = std::max(count, list.size()); - slice.list.reserve(list.size()); - for (const auto &contact : list) { - contact.match([&](const MTPDpeerBlocked &data) { - const auto peer = _session->data().peerLoaded( - peerFromMTP(data.vpeer_id())); - if (peer) { - peer->setIsBlocked(true); - slice.list.push_back({ peer, data.vdate().v }); - } - }); - } - if (!_blockedPeersSlice || *_blockedPeersSlice != slice) { - _blockedPeersSlice = slice; - _blockedPeersChanges.fire(std::move(slice)); - } - }; - result.match([&](const MTPDcontacts_blockedSlice &data) { - _session->data().processUsers(data.vusers()); - push(data.vcount().v, data.vblocked().v); - }, [&](const MTPDcontacts_blocked &data) { - _session->data().processUsers(data.vusers()); - push(0, data.vblocked().v); - }); - }).fail([=](const MTP::Error &error) { - _blockedPeersRequestId = 0; - }).send(); -} - -auto ApiWrap::blockedPeersSlice() -> rpl::producer { - if (!_blockedPeersSlice) { - reloadBlockedPeers(); - } - return _blockedPeersSlice - ? _blockedPeersChanges.events_starting_with_copy(*_blockedPeersSlice) - : (_blockedPeersChanges.events() | rpl::type_erased()); -} - Api::Authorizations &ApiWrap::authorizations() { return *_authorizations; } @@ -5587,6 +5170,14 @@ Api::AttachedStickers &ApiWrap::attachedStickers() { return *_attachedStickers; } +Api::BlockedPeers &ApiWrap::blockedPeers() { + return *_blockedPeers; +} + +Api::CloudPassword &ApiWrap::cloudPassword() { + return *_cloudPassword; +} + Api::SelfDestruct &ApiWrap::selfDestruct() { return *_selfDestruct; } @@ -5599,6 +5190,10 @@ Api::GlobalPrivacy &ApiWrap::globalPrivacy() { return *_globalPrivacy; } +Api::UserPrivacy &ApiWrap::userPrivacy() { + return *_userPrivacy; +} + Api::InviteLinks &ApiWrap::inviteLinks() { return *_inviteLinks; } diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index c6a084cfc..011541a4d 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -45,10 +45,6 @@ namespace Dialogs { class Key; } // namespace Dialogs -namespace Core { -struct CloudPasswordState; -} // namespace Core - namespace Ui { struct PreparedList; } // namespace Ui @@ -58,9 +54,12 @@ namespace Api { class Updates; class Authorizations; class AttachedStickers; +class BlockedPeers; +class CloudPassword; class SelfDestruct; class SensitiveContent; class GlobalPrivacy; +class UserPrivacy; class InviteLinks; namespace details { @@ -113,46 +112,6 @@ public: using SendAction = Api::SendAction; using MessageToSend = Api::MessageToSend; - struct Privacy { - enum class Key { - PhoneNumber, - AddedByPhone, - LastSeen, - Calls, - Invites, - CallsPeer2Peer, - Forwards, - ProfilePhoto, - }; - enum class Option { - Everyone, - Contacts, - Nobody, - }; - Option option = Option::Everyone; - std::vector> always; - std::vector> never; - - static MTPInputPrivacyKey Input(Key key); - static std::optional KeyFromMTP(mtpTypeId type); - }; - - struct BlockedPeersSlice { - struct Item { - PeerData *peer = nullptr; - TimeId date = 0; - - bool operator==(const Item &other) const; - bool operator!=(const Item &other) const; - }; - - QVector list; - int total = 0; - - bool operator==(const BlockedPeersSlice &other) const; - bool operator!=(const BlockedPeersSlice &other) const; - }; - explicit ApiWrap(not_null session); ~ApiWrap(); @@ -295,19 +254,10 @@ public: void joinChannel(not_null channel); void leaveChannel(not_null channel); - void blockPeer(not_null peer); - void unblockPeer(not_null peer, Fn onDone = nullptr); - void requestNotifySettings(const MTPInputNotifyPeer &peer); void updateNotifySettingsDelayed(not_null peer); void saveDraftToCloudDelayed(not_null history); - void savePrivacy( - const MTPInputPrivacyKey &key, - QVector &&rules); - void handlePrivacyChange( - Privacy::Key key, - const MTPVector &rules); static int OnlineTillFromStatus( const MTPUserStatus &status, int currentOnlineTill); @@ -440,11 +390,6 @@ public: void uploadPeerPhoto(not_null peer, QImage &&image); void clearPeerPhoto(not_null photo); - void reloadPasswordState(); - void clearUnconfirmedPassword(); - rpl::producer passwordState() const; - std::optional passwordStateCurrent() const; - void reloadContactSignupSilent(); rpl::producer contactSignupSilent() const; std::optional contactSignupSilentCurrent() const; @@ -452,17 +397,14 @@ public: void saveSelfBio(const QString &text, FnMut done); - void reloadPrivacy(Privacy::Key key); - rpl::producer privacyValue(Privacy::Key key); - - void reloadBlockedPeers(); - rpl::producer blockedPeersSlice(); - [[nodiscard]] Api::Authorizations &authorizations(); [[nodiscard]] Api::AttachedStickers &attachedStickers(); + [[nodiscard]] Api::BlockedPeers &blockedPeers(); + [[nodiscard]] Api::CloudPassword &cloudPassword(); [[nodiscard]] Api::SelfDestruct &selfDestruct(); [[nodiscard]] Api::SensitiveContent &sensitiveContent(); [[nodiscard]] Api::GlobalPrivacy &globalPrivacy(); + [[nodiscard]] Api::UserPrivacy &userPrivacy(); [[nodiscard]] Api::InviteLinks &inviteLinks(); void createPoll( @@ -476,6 +418,8 @@ public: void closePoll(not_null item); void reloadPollResults(not_null item); + void updatePrivacyLastSeens(); + private: struct MessageDataRequest { using Callbacks = QList; @@ -632,12 +576,6 @@ private: void photoUploadReady(const FullMsgId &msgId, const MTPInputFile &file); - Privacy parsePrivacy(const QVector &rules); - void pushPrivacy( - Privacy::Key key, - const QVector &rules); - void updatePrivacyLastSeens(const QVector &rules); - void migrateDone( not_null peer, not_null channel); @@ -682,7 +620,6 @@ private: QMap > _stickerSetRequests; QMap _channelAmInRequests; - base::flat_map, mtpRequestId> _blockRequests; base::flat_map _notifySettingRequests; base::flat_map, mtpRequestId> _draftsSaveRequestIds; base::Timer _draftsSaveTimer; @@ -707,8 +644,6 @@ private: base::flat_map, StickersByEmoji> _stickersByEmoji; - base::flat_map _privacySaveRequests; - mtpRequestId _contactsRequestId = 0; mtpRequestId _contactsStatusesRequestId = 0; @@ -771,27 +706,18 @@ private: base::flat_map> _peerPhotoUploads; - mtpRequestId _passwordRequestId = 0; - std::unique_ptr _passwordState; - rpl::event_stream _passwordStateChanges; - mtpRequestId _saveBioRequestId = 0; FnMut _saveBioDone; QString _saveBioText; - base::flat_map _privacyRequestIds; - base::flat_map _privacyValues; - std::map> _privacyChanges; - - mtpRequestId _blockedPeersRequestId = 0; - std::optional _blockedPeersSlice; - rpl::event_stream _blockedPeersChanges; - const std::unique_ptr _authorizations; const std::unique_ptr _attachedStickers; + const std::unique_ptr _blockedPeers; + const std::unique_ptr _cloudPassword; const std::unique_ptr _selfDestruct; const std::unique_ptr _sensitiveContent; const std::unique_ptr _globalPrivacy; + const std::unique_ptr _userPrivacy; const std::unique_ptr _inviteLinks; base::flat_map _pollVotesRequestIds; diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 70c0fc178..ee0ef90cb 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -38,7 +38,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mainwindow.h" #include "mainwidget.h" #include "apiwrap.h" -#include "numbers.h" #include "main/main_session.h" #include "styles/style_boxes.h" #include "styles/style_overview.h" @@ -70,32 +69,6 @@ HistoryView::Element *hoveredItem = nullptr, namespace App { - QString formatPhone(QString phone) { - if (phone.isEmpty()) return QString(); - if (phone.at(0) == '0') return phone; - - QString number = phone; - for (const QChar *ch = phone.constData(), *e = ch + phone.size(); ch != e; ++ch) { - if (ch->unicode() < '0' || ch->unicode() > '9') { - number = phone.replace(QRegularExpression(qsl("[^\\d]")), QString()); - } - } - QVector groups = phoneNumberParse(number); - if (groups.isEmpty()) return '+' + number; - - QString result; - result.reserve(number.size() + groups.size() + 1); - result.append('+'); - int32 sum = 0; - for (int32 i = 0, l = groups.size(); i < l; ++i) { - result.append(number.midRef(sum, groups.at(i))); - sum += groups.at(i); - if (sum < number.size()) result.append(' '); - } - if (sum < number.size()) result.append(number.midRef(sum)); - return result; - } - void initMedia() { Ui::StartCachedCorners(); } diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h index 9b82f25aa..6e581251b 100644 --- a/Telegram/SourceFiles/app.h +++ b/Telegram/SourceFiles/app.h @@ -14,8 +14,6 @@ class Element; } // namespace HistoryView namespace App { - QString formatPhone(QString phone); - void hoveredItem(HistoryView::Element *item); HistoryView::Element *hoveredItem(); void pressedItem(HistoryView::Element *item); diff --git a/Telegram/SourceFiles/boxes/background_box.cpp b/Telegram/SourceFiles/boxes/background_box.cpp index 43d06d53a..dbd2a1822 100644 --- a/Telegram/SourceFiles/boxes/background_box.cpp +++ b/Telegram/SourceFiles/boxes/background_box.cpp @@ -239,8 +239,10 @@ void BackgroundBox::Inner::sortPapers() { return std::make_tuple( data.id() == current, night ? data.isDark() : !data.isDark(), - !data.isDefault() && !data.isLocal(), - !data.isDefault() && data.isLocal()); + Data::IsDefaultWallPaper(data), + !data.isDefault() && !Data::IsLegacy1DefaultWallPaper(data), + Data::IsLegacy2DefaultWallPaper(data), + Data::IsLegacy1DefaultWallPaper(data)); }); if (!_papers.empty() && _papers.front().data.id() == current) { _papers.front().data = _papers.front().data.withParamsFrom( @@ -366,6 +368,7 @@ void BackgroundBox::Inner::paintPaper( _check->paint(p, checkLeft, checkTop, width()); } else if (Data::IsCloudWallPaper(paper.data) && !Data::IsDefaultWallPaper(paper.data) + && !Data::IsLegacy2DefaultWallPaper(paper.data) && !v::is_null(over) && (&paper == &_papers[getSelectionIndex(over)])) { const auto deleteSelected = v::is(over); @@ -395,6 +398,7 @@ void BackgroundBox::Inner::mouseMoveEvent(QMouseEvent *e) { } else if (result >= _papers.size()) { return Selection(); } + auto &data = _papers[result].data; const auto deleteLeft = (column + 1) * (width + skip) - st::stickerPanDeleteIconBg.width(); const auto deleteBottom = row * (height + skip) + skip @@ -402,9 +406,10 @@ void BackgroundBox::Inner::mouseMoveEvent(QMouseEvent *e) { const auto currentId = Window::Theme::Background()->id(); const auto inDelete = (x >= deleteLeft) && (y < deleteBottom) - && Data::IsCloudWallPaper(_papers[result].data) - && !Data::IsDefaultWallPaper(_papers[result].data) - && (currentId != _papers[result].data.id()); + && Data::IsCloudWallPaper(data) + && !Data::IsDefaultWallPaper(data) + && !Data::IsLegacy2DefaultWallPaper(data) + && (currentId != data.id()); return (result >= _papers.size()) ? Selection() : inDelete diff --git a/Telegram/SourceFiles/boxes/background_preview_box.cpp b/Telegram/SourceFiles/boxes/background_preview_box.cpp index a85eb58e8..59d0759e1 100644 --- a/Telegram/SourceFiles/boxes/background_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/background_preview_box.cpp @@ -286,24 +286,25 @@ AdminLog::OwnedItem GenerateTextItem( bool out) { Expects(history->peer->isUser()); - using Flag = MTPDmessage::Flag; static auto id = ServerMaxMsgId + (ServerMaxMsgId / 3); - const auto flags = Flag::f_entities - | Flag::f_from_id - | (out ? Flag::f_out : Flag(0)); - const auto clientFlags = MTPDmessage_ClientFlag::f_fake_history_item; - const auto replyTo = 0; - const auto viaBotId = UserId(0); + const auto flags = MessageFlag::FakeHistoryItem + | MessageFlag::HasFromId + | (out ? MessageFlag::Outgoing : MessageFlag(0)); + const auto replyTo = MsgId(); + const auto viaBotId = UserId(); + const auto groupedId = uint64(); const auto item = history->makeMessage( ++id, flags, - clientFlags, replyTo, viaBotId, base::unixtime::now(), out ? history->session().userId() : peerToUser(history->peer->id), QString(), - TextWithEntities{ TextUtilities::Clean(text) }); + TextWithEntities{ TextUtilities::Clean(text) }, + MTP_messageMediaEmpty(), + MTPReplyMarkup(), + groupedId); return AdminLog::OwnedItem(delegate, item); } @@ -622,6 +623,7 @@ void BackgroundPreviewBox::paintDate(Painter &p) { if (!date || !_serviceBg) { return; } + auto hq = PainterHighQualityEnabler(p); const auto text = date->text; const auto bubbleHeight = st::msgServicePadding.top() + st::msgServiceFont->height + st::msgServicePadding.bottom(); const auto bubbleTop = st::msgServiceMargin.top(); @@ -758,7 +760,7 @@ void BackgroundPreviewBox::checkLoadedDocument() { }; _generating = Data::ReadImageAsync( _media.get(), - Window::Theme::ProcessBackgroundImage, + Window::Theme::PreprocessBackgroundImage, generateCallback); } diff --git a/Telegram/SourceFiles/boxes/change_phone_box.cpp b/Telegram/SourceFiles/boxes/change_phone_box.cpp index 199ebeab5..5de47051d 100644 --- a/Telegram/SourceFiles/boxes/change_phone_box.cpp +++ b/Telegram/SourceFiles/boxes/change_phone_box.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/input_fields.h" #include "ui/wrap/fade_wrap.h" #include "ui/toast/toast.h" +#include "ui/text/format_values.h" // Ui::FormatPhone #include "ui/text/text_utilities.h" #include "ui/special_fields.h" #include "boxes/confirm_phone_box.h" @@ -21,7 +22,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "mtproto/sender.h" #include "apiwrap.h" -#include "app.h" #include "styles/style_layers.h" #include "styles/style_boxes.h" @@ -235,7 +235,7 @@ void ChangePhoneBox::EnterPhone::sendPhoneFail(const MTP::Error &error, const QS tr::lng_change_phone_occupied( tr::now, lt_phone, - App::formatPhone(phoneNumber)), + Ui::FormatPhone(phoneNumber)), tr::lng_box_ok(tr::now))); } else { showError(Lang::Hard::ServerError()); @@ -271,7 +271,7 @@ void ChangePhoneBox::EnterCode::prepare() { auto descriptionText = tr::lng_change_phone_code_description( tr::now, lt_phone, - Ui::Text::Bold(App::formatPhone(_phone)), + Ui::Text::Bold(Ui::FormatPhone(_phone)), Ui::Text::WithEntities); auto description = object_ptr(this, rpl::single(descriptionText), st::changePhoneLabel); description->moveToLeft(st::boxPadding.left(), 0); diff --git a/Telegram/SourceFiles/boxes/confirm_phone_box.cpp b/Telegram/SourceFiles/boxes/confirm_phone_box.cpp index ca8d00aaa..51741745a 100644 --- a/Telegram/SourceFiles/boxes/confirm_phone_box.cpp +++ b/Telegram/SourceFiles/boxes/confirm_phone_box.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" #include "ui/widgets/labels.h" +#include "ui/text/format_values.h" // Ui::FormatPhone #include "ui/text/text_utilities.h" #include "core/click_handler_types.h" // UrlClickHandler #include "base/qthelp_url.h" // qthelp::url_encode @@ -19,7 +20,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "mainwidget.h" #include "numbers.h" -#include "app.h" #include "lang/lang_keys.h" #include "mtproto/facade.h" #include "styles/style_layers.h" @@ -297,7 +297,7 @@ void ConfirmPhoneBox::prepare() { this, tr::lng_confirm_phone_about( lt_phone, - rpl::single(Ui::Text::Bold(App::formatPhone(_phone))), + rpl::single(Ui::Text::Bold(Ui::FormatPhone(_phone))), Ui::Text::WithEntities), st::confirmPhoneAboutLabel); @@ -348,7 +348,7 @@ void ConfirmPhoneBox::sendCode() { void ConfirmPhoneBox::confirmDone(const MTPBool &result) { _sendCodeRequestId = 0; - Ui::show(Box(tr::lng_confirm_phone_success(tr::now, lt_phone, App::formatPhone(_phone)))); + Ui::show(Box(tr::lng_confirm_phone_success(tr::now, lt_phone, Ui::FormatPhone(_phone)))); } void ConfirmPhoneBox::confirmFail(const MTP::Error &error) { diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp index 9d8f90517..3b198bb4c 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp @@ -101,6 +101,11 @@ Ui::AlbumType ComputeAlbumType(not_null item) { return Ui::AlbumType(); } +bool CanBeCompressed(Ui::AlbumType type) { + return (type == Ui::AlbumType::None) + || (type == Ui::AlbumType::PhotoVideo); +} + } // namespace EditCaptionBox::EditCaptionBox( @@ -170,7 +175,7 @@ void EditCaptionBox::rebuildPreview() { const auto photo = media->photo(); const auto document = media->document(); if (photo || document->isVideoFile() || document->isAnimation()) { - _isPhoto = true; + _isPhoto = (photo != nullptr); const auto media = Ui::CreateChild( this, gifPaused, @@ -230,6 +235,8 @@ void EditCaptionBox::rebuildPreview() { _scroll->setOwnedWidget( object_ptr::fromRaw(_content.get())); + _previewRebuilds.fire({}); + captionResized(); } @@ -302,10 +309,11 @@ void EditCaptionBox::setupShadows() { } void EditCaptionBox::setupControls() { - auto hintLabelToggleOn = _isPhoto.value( - ) | rpl::map([=](bool value) { + auto hintLabelToggleOn = _previewRebuilds.events_starting_with( + rpl::empty_value() + ) | rpl::map([=] { return _controller->session().settings().photoEditorHintShown() - ? value + ? _isPhoto : false; }); @@ -327,9 +335,12 @@ void EditCaptionBox::setupControls() { st::defaultBoxCheckbox), st::editMediaCheckboxMargins) )->toggleOn( - _isPhoto.value( - ) | rpl::map([=](bool value) { - return value && (_albumType == Ui::AlbumType::None); + _previewRebuilds.events_starting_with( + rpl::empty_value() + ) | rpl::map([=] { + return _isPhoto + && CanBeCompressed(_albumType) + && !_preparedList.files.empty(); }), anim::type::instant )->entity()->checkedChanges( @@ -610,6 +621,7 @@ void EditCaptionBox::resizeEvent(QResizeEvent *e) { _emojiToggle->update(); if (!_controls->isHidden()) { + _controls->resizeToWidth(width()); _controls->moveToLeft( st::boxPhotoPadding.left(), bottom - _controls->heightNoMargins()); @@ -661,7 +673,7 @@ void EditCaptionBox::save() { _controller->session().api().editMedia( std::move(_preparedList), - (!_asFile && _isPhoto.current()) + (!_asFile && _isPhoto && CanBeCompressed(_albumType)) ? SendMediaType::Photo : SendMediaType::File, _field->getTextWithAppliedMarkdown(), diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.h b/Telegram/SourceFiles/boxes/edit_caption_box.h index c145f285c..02b364881 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.h +++ b/Telegram/SourceFiles/boxes/edit_caption_box.h @@ -74,14 +74,14 @@ private: const not_null _controller; const not_null _historyItem; - const bool _isAllowedEditMedia = false; + const bool _isAllowedEditMedia; const Ui::AlbumType _albumType; const base::unique_qptr _controls; const base::unique_qptr _scroll; const base::unique_qptr _field; const base::unique_qptr _emojiToggle; - const base::unique_qptr _topShadow,_bottomShadow; + const base::unique_qptr _topShadow, _bottomShadow; base::unique_qptr _content; base::unique_qptr _emojiPanel; @@ -93,15 +93,16 @@ private: mtpRequestId _saveRequestId = 0; + bool _isPhoto = false; bool _asFile = false; QString _error; - rpl::variable _isPhoto = false; rpl::variable _footerHeight = 0; rpl::event_stream<> _editMediaClicks; rpl::event_stream<> _photoEditorOpens; + rpl::event_stream<> _previewRebuilds; rpl::event_stream _contentHeight; }; diff --git a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp index 1102f5fa1..c223e51c5 100644 --- a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp @@ -170,63 +170,6 @@ void EditPrivacyBox::editExceptions( Ui::LayerOption::KeepOther); } -QVector EditPrivacyBox::collectResult() { - const auto collectInputUsers = [](const auto &peers) { - auto result = QVector(); - result.reserve(peers.size()); - for (const auto peer : peers) { - if (const auto user = peer->asUser()) { - result.push_back(user->inputUser); - } - } - return result; - }; - const auto collectInputChats = [](const auto &peers) { - auto result = QVector(); // #TODO ids - result.reserve(peers.size()); - for (const auto peer : peers) { - if (!peer->isUser()) { - result.push_back(peerToBareMTPInt(peer->id)); - } - } - return result; - }; - - constexpr auto kMaxRules = 3; // allow users, disallow users, option - auto result = QVector(); - result.reserve(kMaxRules); - if (showExceptionLink(Exception::Always)) { - const auto users = collectInputUsers(_value.always); - const auto chats = collectInputChats(_value.always); - if (!users.empty()) { - result.push_back(MTP_inputPrivacyValueAllowUsers(MTP_vector(users))); - } - if (!chats.empty()) { - result.push_back(MTP_inputPrivacyValueAllowChatParticipants(MTP_vector(chats))); - } - } - if (showExceptionLink(Exception::Never)) { - const auto users = collectInputUsers(_value.never); - const auto chats = collectInputChats(_value.never); - if (!users.empty()) { - result.push_back(MTP_inputPrivacyValueDisallowUsers(MTP_vector(users))); - } - if (!chats.empty()) { - result.push_back(MTP_inputPrivacyValueDisallowChatParticipants(MTP_vector(chats))); - } - } - result.push_back([&] { - switch (_value.option) { - case Option::Everyone: return MTP_inputPrivacyValueAllowAll(); - case Option::Contacts: return MTP_inputPrivacyValueAllowContacts(); - case Option::Nobody: return MTP_inputPrivacyValueDisallowAll(); - } - Unexpected("Option value in EditPrivacyBox::collectResult."); - }()); - - return result; -} - std::vector> &EditPrivacyBox::exceptions(Exception exception) { switch (exception) { case Exception::Always: return _value.always; @@ -379,10 +322,13 @@ void EditPrivacyBox::setupContent() { const auto someAreDisallowed = (_value.option != Option::Everyone) || !_value.never.empty(); _controller->confirmSave(someAreDisallowed, crl::guard(this, [=] { + _value.ignoreAlways = !showExceptionLink(Exception::Always); + _value.ignoreNever = !showExceptionLink(Exception::Never); + _controller->saveAdditional(); - _window->session().api().savePrivacy( - _controller->apiKey(), - collectResult()); + _window->session().api().userPrivacy().save( + _controller->key(), + _value); closeBox(); })); }); diff --git a/Telegram/SourceFiles/boxes/edit_privacy_box.h b/Telegram/SourceFiles/boxes/edit_privacy_box.h index 4310d1b51..d414c9318 100644 --- a/Telegram/SourceFiles/boxes/edit_privacy_box.h +++ b/Telegram/SourceFiles/boxes/edit_privacy_box.h @@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/abstract_box.h" #include "mtproto/sender.h" -#include "apiwrap.h" +#include "api/api_user_privacy.h" namespace Ui { class VerticalLayout; @@ -31,15 +31,14 @@ class EditPrivacyBox; class EditPrivacyController { public: - using Key = ApiWrap::Privacy::Key; - using Option = ApiWrap::Privacy::Option; + using Key = Api::UserPrivacy::Key; + using Option = Api::UserPrivacy::Option; enum class Exception { Always, Never, }; [[nodiscard]] virtual Key key() = 0; - [[nodiscard]] virtual MTPInputPrivacyKey apiKey() = 0; [[nodiscard]] virtual rpl::producer title() = 0; [[nodiscard]] virtual bool hasOption(Option option) { @@ -102,8 +101,8 @@ private: class EditPrivacyBox : public Ui::BoxContent { public: - using Value = ApiWrap::Privacy; - using Option = Value::Option; + using Value = Api::UserPrivacy::Rule; + using Option = Api::UserPrivacy::Option; using Exception = EditPrivacyController::Exception; EditPrivacyBox( @@ -124,7 +123,6 @@ protected: private: bool showExceptionLink(Exception exception) const; void setupContent(); - QVector collectResult(); Ui::FlatLabel *addLabel( not_null container, diff --git a/Telegram/SourceFiles/boxes/passcode_box.cpp b/Telegram/SourceFiles/boxes/passcode_box.cpp index f5aa0a7e3..18545d2d1 100644 --- a/Telegram/SourceFiles/boxes/passcode_box.cpp +++ b/Telegram/SourceFiles/boxes/passcode_box.cpp @@ -12,8 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "boxes/confirm_box.h" #include "boxes/confirm_phone_box.h" +#include "base/unixtime.h" #include "mainwindow.h" #include "apiwrap.h" +#include "api/api_cloud_password.h" #include "main/main_session.h" #include "main/main_domain.h" #include "core/application.h" @@ -43,7 +45,7 @@ enum class PasswordErrorType { void SetCloudPassword( not_null box, not_null session) { - session->api().passwordState( + session->api().cloudPassword().state( ) | rpl::start_with_next([=] { using namespace Settings; const auto weak = Ui::MakeWeak(box); @@ -96,6 +98,56 @@ void TransferPasswordError( } } +void StartPendingReset( + not_null session, + not_null context, + Fn close) { + const auto weak = Ui::MakeWeak(context.get()); + session->api().request(MTPaccount_ResetPassword( + )).done([=](const MTPaccount_ResetPasswordResult &result) { + session->api().cloudPassword().applyPendingReset(result); + result.match([&](const MTPDaccount_resetPasswordOk &data) { + }, [&](const MTPDaccount_resetPasswordRequestedWait &data) { + }, [&](const MTPDaccount_resetPasswordFailedWait &data) { + constexpr auto kMinute = 60; + constexpr auto kHour = 3600; + constexpr auto kDay = 86400; + const auto left = std::max( + data.vretry_date().v - base::unixtime::now(), + kMinute); + const auto days = (left / kDay); + const auto hours = (left / kHour); + const auto minutes = (left / kMinute); + const auto duration = days + ? tr::lng_group_call_duration_days(tr::now, lt_count, days) + : hours + ? tr::lng_group_call_duration_hours(tr::now, lt_count, hours) + : tr::lng_group_call_duration_minutes( + tr::now, + lt_count, + minutes); + if (const auto strong = weak.data()) { + strong->getDelegate()->show(Box( + tr::lng_cloud_password_reset_later( + tr::now, + lt_duration, + duration))); + } + }); + if (const auto strong = weak.data()) { + strong->closeBox(); + } + close(); + }).fail([=](const MTP::Error &error) { + if (const auto strong = weak.data()) { + strong->getDelegate()->show( + Box("Error: " + error.type())); + strong->closeBox(); + } + close(); + }).send(); +} + } // namespace PasscodeBox::CloudFields PasscodeBox::CloudFields::From( @@ -107,6 +159,7 @@ PasscodeBox::CloudFields PasscodeBox::CloudFields::From( result.hasRecovery = current.hasRecovery; result.notEmptyPassport = current.notEmptyPassport; result.hint = current.hint; + result.pendingResetDate = current.pendingResetDate; return result; } @@ -146,7 +199,8 @@ PasscodeBox::PasscodeBox( , _reenterPasscode(this, st::defaultInputField, tr::lng_cloud_password_confirm_new()) , _passwordHint(this, st::defaultInputField, fields.curRequest ? tr::lng_cloud_password_change_hint() : tr::lng_cloud_password_hint()) , _recoverEmail(this, st::defaultInputField, tr::lng_cloud_password_email()) -, _recover(this, tr::lng_signin_recover(tr::now)) { +, _recover(this, tr::lng_signin_recover(tr::now)) +, _showRecoverLink(_cloudFields.hasRecovery || !_cloudFields.pendingResetDate) { Expects(!_turningOff || _cloudFields.curRequest); if (!_cloudFields.hint.isEmpty()) { @@ -204,14 +258,14 @@ void PasscodeBox::prepare() { : _cloudPwd ? tr::lng_cloud_password_remove() : tr::lng_passcode_remove()); - setDimensions(st::boxWidth, st::passcodePadding.top() + _oldPasscode->height() + st::passcodeTextLine + ((_cloudFields.hasRecovery && !_hintText.isEmpty()) ? st::passcodeTextLine : 0) + st::passcodeAboutSkip + _aboutHeight + st::passcodePadding.bottom()); + setDimensions(st::boxWidth, st::passcodePadding.top() + _oldPasscode->height() + st::passcodeTextLine + ((_showRecoverLink && !_hintText.isEmpty()) ? st::passcodeTextLine : 0) + st::passcodeAboutSkip + _aboutHeight + st::passcodePadding.bottom()); } else { if (currentlyHave()) { _oldPasscode->show(); setTitle(_cloudPwd ? tr::lng_cloud_password_change() : tr::lng_passcode_change()); - setDimensions(st::boxWidth, st::passcodePadding.top() + _oldPasscode->height() + st::passcodeTextLine + ((_cloudFields.hasRecovery && !_hintText.isEmpty()) ? st::passcodeTextLine : 0) + _newPasscode->height() + st::passcodeLittleSkip + _reenterPasscode->height() + st::passcodeSkip + (_cloudPwd ? _passwordHint->height() + st::passcodeLittleSkip : 0) + st::passcodeAboutSkip + _aboutHeight + st::passcodePadding.bottom()); + setDimensions(st::boxWidth, st::passcodePadding.top() + _oldPasscode->height() + st::passcodeTextLine + ((_showRecoverLink && !_hintText.isEmpty()) ? st::passcodeTextLine : 0) + _newPasscode->height() + st::passcodeLittleSkip + _reenterPasscode->height() + st::passcodeSkip + (_cloudPwd ? _passwordHint->height() + st::passcodeLittleSkip : 0) + st::passcodeAboutSkip + _aboutHeight + st::passcodePadding.bottom()); } else { _oldPasscode->hide(); setTitle(_cloudPwd @@ -238,7 +292,9 @@ void PasscodeBox::prepare() { const auto has = currentlyHave(); _oldPasscode->setVisible(onlyCheck || has); - _recover->setVisible((onlyCheck || has) && _cloudPwd && _cloudFields.hasRecovery); + _recover->setVisible((onlyCheck || has) + && _cloudPwd + && _showRecoverLink); _newPasscode->setVisible(!onlyCheck); _reenterPasscode->setVisible(!onlyCheck); _passwordHint->setVisible(!onlyCheck && _cloudPwd); @@ -286,7 +342,7 @@ void PasscodeBox::paintEvent(QPaintEvent *e) { Painter p(this); int32 w = st::boxWidth - st::boxPadding.left() * 1.5; - int32 abouty = (_passwordHint->isHidden() ? ((_reenterPasscode->isHidden() ? (_oldPasscode->y() + (_cloudFields.hasRecovery && !_hintText.isEmpty() ? st::passcodeTextLine : 0)) : _reenterPasscode->y()) + st::passcodeSkip) : _passwordHint->y()) + _oldPasscode->height() + st::passcodeLittleSkip + st::passcodeAboutSkip; + int32 abouty = (_passwordHint->isHidden() ? ((_reenterPasscode->isHidden() ? (_oldPasscode->y() + (_showRecoverLink && !_hintText.isEmpty() ? st::passcodeTextLine : 0)) : _reenterPasscode->y()) + st::passcodeSkip) : _passwordHint->y()) + _oldPasscode->height() + st::passcodeLittleSkip + st::passcodeAboutSkip; p.setPen(st::boxTextFg); _about.drawLeft(p, st::boxPadding.left(), abouty, w, width()); @@ -318,7 +374,7 @@ void PasscodeBox::resizeEvent(QResizeEvent *e) { _oldPasscode->resize(w, _oldPasscode->height()); _oldPasscode->moveToLeft(st::boxPadding.left(), st::passcodePadding.top()); _newPasscode->resize(w, _newPasscode->height()); - _newPasscode->moveToLeft(st::boxPadding.left(), _oldPasscode->y() + ((_turningOff || has) ? (_oldPasscode->height() + st::passcodeTextLine + ((_cloudFields.hasRecovery && !_hintText.isEmpty()) ? st::passcodeTextLine : 0)) : 0)); + _newPasscode->moveToLeft(st::boxPadding.left(), _oldPasscode->y() + ((_turningOff || has) ? (_oldPasscode->height() + st::passcodeTextLine + ((_showRecoverLink && !_hintText.isEmpty()) ? st::passcodeTextLine : 0)) : 0)); _reenterPasscode->resize(w, _reenterPasscode->height()); _reenterPasscode->moveToLeft(st::boxPadding.left(), _newPasscode->y() + _newPasscode->height() + st::passcodeLittleSkip); _passwordHint->resize(w, _passwordHint->height()); @@ -380,7 +436,7 @@ void PasscodeBox::setPasswordFail(const QString &type) { _oldPasscode->setFocus(); _oldPasscode->showError(); _oldError = tr::lng_flood_error(tr::now); - if (_cloudFields.hasRecovery && _hintText.isEmpty()) { + if (_showRecoverLink && _hintText.isEmpty()) { _recover->hide(); } update(); @@ -914,7 +970,7 @@ void PasscodeBox::badOldPasscode() { _oldError = _cloudPwd ? tr::lng_cloud_password_wrong(tr::now) : tr::lng_passcode_wrong(tr::now); - if (_cloudFields.hasRecovery && _hintText.isEmpty()) { + if (_showRecoverLink && _hintText.isEmpty()) { _recover->hide(); } update(); @@ -923,7 +979,7 @@ void PasscodeBox::badOldPasscode() { void PasscodeBox::oldChanged() { if (!_oldError.isEmpty()) { _oldError = QString(); - if (_cloudFields.hasRecovery && _hintText.isEmpty()) { + if (_showRecoverLink && _hintText.isEmpty()) { _recover->show(); } update(); @@ -945,7 +1001,21 @@ void PasscodeBox::emailChanged() { } void PasscodeBox::recoverByEmail() { - if (_pattern.isEmpty()) { + if (!_cloudFields.hasRecovery) { + const auto session = _session; + const auto confirmBox = std::make_shared>(); + const auto reset = crl::guard(this, [=] { + StartPendingReset(session, this, [=] { + if (const auto box = *confirmBox) { + box->closeBox(); + } + }); + }); + *confirmBox = getDelegate()->show(Box( + tr::lng_cloud_password_reset_no_email(tr::now), + tr::lng_cloud_password_reset_ok(tr::now), + reset)); + } else if (_pattern.isEmpty()) { _pattern = "-"; _api.request(MTPauth_RequestPasswordRecovery( )).done([=](const MTPauth_PasswordRecovery &result) { @@ -965,10 +1035,13 @@ void PasscodeBox::recoverExpired() { void PasscodeBox::recover() { if (_pattern == "-") return; + const auto weak = Ui::MakeWeak(this); const auto box = getDelegate()->show(Box( _session, _pattern, - _cloudFields.notEmptyPassport)); + _cloudFields.notEmptyPassport, + _cloudFields.pendingResetDate != 0, + [weak] { if (weak) { weak->closeBox(); } })); box->passwordCleared( ) | rpl::map_to( @@ -997,11 +1070,37 @@ RecoverBox::RecoverBox( QWidget*, not_null session, const QString &pattern, - bool notEmptyPassport) + bool notEmptyPassport, + bool hasPendingReset, + Fn closeParent) : _api(&session->mtp()) , _pattern(st::normalFont->elided(tr::lng_signin_recover_hint(tr::now, lt_recover_email, pattern), st::boxWidth - st::boxPadding.left() * 1.5)) , _notEmptyPassport(notEmptyPassport) -, _recoverCode(this, st::defaultInputField, tr::lng_signin_code()) { +, _recoverCode(this, st::defaultInputField, tr::lng_signin_code()) +, _noEmailAccess(this, tr::lng_signin_try_password(tr::now)) +, _closeParent(std::move(closeParent)) { + if (hasPendingReset) { + _noEmailAccess.destroy(); + } else { + _noEmailAccess->setClickedCallback([=] { + const auto confirmBox = std::make_shared>(); + const auto reset = crl::guard(this, [=] { + const auto closeParent = _closeParent; + StartPendingReset(session, this, [=] { + if (closeParent) { + closeParent(); + } + if (const auto box = *confirmBox) { + box->closeBox(); + } + }); + }); + *confirmBox = getDelegate()->show(Box( + tr::lng_cloud_password_reset_with_email(tr::now), + tr::lng_cloud_password_reset_ok(tr::now), + reset)); + }); + } } rpl::producer<> RecoverBox::passwordCleared() const { @@ -1018,7 +1117,13 @@ void RecoverBox::prepare() { addButton(tr::lng_passcode_submit(), [=] { submit(); }); addButton(tr::lng_cancel(), [=] { closeBox(); }); - setDimensions(st::boxWidth, st::passcodePadding.top() + st::passcodePadding.bottom() + st::passcodeTextLine + _recoverCode->height() + st::passcodeTextLine); + setDimensions( + st::boxWidth, + (st::passcodePadding.top() + + st::passcodePadding.bottom() + + st::passcodeTextLine + + _recoverCode->height() + + st::passcodeTextLine)); connect(_recoverCode, &Ui::InputField::changed, [=] { codeChanged(); }); connect(_recoverCode, &Ui::InputField::submitted, [=] { submit(); }); @@ -1045,6 +1150,9 @@ void RecoverBox::resizeEvent(QResizeEvent *e) { _recoverCode->resize(st::boxWidth - st::boxPadding.left() - st::boxPadding.right(), _recoverCode->height()); _recoverCode->moveToLeft(st::boxPadding.left(), st::passcodePadding.top() + st::passcodePadding.bottom() + st::passcodeTextLine); + if (_noEmailAccess) { + _noEmailAccess->moveToLeft(st::boxPadding.left(), _recoverCode->y() + _recoverCode->height() + (st::passcodeTextLine - _noEmailAccess->height()) / 2); + } } void RecoverBox::setInnerFocus() { @@ -1086,11 +1194,18 @@ void RecoverBox::submit() { } } -void RecoverBox::codeChanged() { - _error = QString(); +void RecoverBox::setError(const QString &error) { + _error = error; + if (_noEmailAccess) { + _noEmailAccess->setVisible(error.isEmpty()); + } update(); } +void RecoverBox::codeChanged() { + setError(QString()); +} + void RecoverBox::codeSubmitDone(const MTPauth_Authorization &result) { _submitRequest = 0; @@ -1103,8 +1218,7 @@ void RecoverBox::codeSubmitDone(const MTPauth_Authorization &result) { void RecoverBox::codeSubmitFail(const MTP::Error &error) { if (MTP::IsFloodError(error)) { _submitRequest = 0; - _error = tr::lng_flood_error(tr::now); - update(); + setError(tr::lng_flood_error(tr::now)); _recoverCode->showError(); return; } @@ -1122,18 +1236,14 @@ void RecoverBox::codeSubmitFail(const MTP::Error &error) { _recoveryExpired.fire({}); closeBox(); } else if (err == qstr("CODE_INVALID")) { - _error = tr::lng_signin_wrong_code(tr::now); - update(); + setError(tr::lng_signin_wrong_code(tr::now)); _recoverCode->selectAll(); _recoverCode->setFocus(); _recoverCode->showError(); } else { - if (Logs::DebugEnabled()) { // internal server error - _error = err + ": " + error.description(); - } else { - _error = Lang::Hard::ServerError(); - } - update(); + setError(Logs::DebugEnabled() // internal server error + ? (err + ": " + error.description()) + : Lang::Hard::ServerError()); _recoverCode->setFocus(); } } diff --git a/Telegram/SourceFiles/boxes/passcode_box.h b/Telegram/SourceFiles/boxes/passcode_box.h index 875bed383..1d17f5fab 100644 --- a/Telegram/SourceFiles/boxes/passcode_box.h +++ b/Telegram/SourceFiles/boxes/passcode_box.h @@ -39,6 +39,7 @@ public: QString hint; Core::SecureSecretAlgo newSecureSecretAlgo; bool turningOff = false; + TimeId pendingResetDate = 0; // Check cloud password for some action. Fn customCheckCallback; @@ -157,6 +158,7 @@ private: object_ptr _passwordHint; object_ptr _recoverEmail; object_ptr _recover; + bool _showRecoverLink = false; QString _oldError, _newError, _emailError; @@ -172,7 +174,9 @@ public: QWidget*, not_null session, const QString &pattern, - bool notEmptyPassport); + bool notEmptyPassport, + bool hasPendingReset, + Fn closeParent = nullptr); rpl::producer<> passwordCleared() const; rpl::producer<> recoveryExpired() const; @@ -192,6 +196,7 @@ private: void codeChanged(); void codeSubmitDone(const MTPauth_Authorization &result); void codeSubmitFail(const MTP::Error &error); + void setError(const QString &error); MTP::Sender _api; mtpRequestId _submitRequest = 0; @@ -200,6 +205,8 @@ private: bool _notEmptyPassport = false; object_ptr _recoverCode; + object_ptr _noEmailAccess; + Fn _closeParent; QString _error; diff --git a/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp index 8b6eb1404..9bb82ae36 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/vertical_layout.h" #include "ui/widgets/labels.h" #include "ui/widgets/input_fields.h" +#include "ui/text/format_values.h" // Ui::FormatPhone #include "ui/text/text_utilities.h" #include "info/profile/info_profile_cover.h" #include "lang/lang_keys.h" @@ -19,7 +20,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/toast/toast.h" #include "main/main_session.h" #include "apiwrap.h" -#include "app.h" #include "styles/style_layers.h" #include "styles/style_boxes.h" #include "styles/style_info.h" @@ -145,7 +145,7 @@ void Controller::setupCover() { _window, (_phone.isEmpty() ? tr::lng_contact_mobile_hidden() - : rpl::single(App::formatPhone(_phone)))), + : rpl::single(Ui::FormatPhone(_phone)))), style::margins())->setAttribute(Qt::WA_TransparentForMouseEvents); } diff --git a/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp index 808dbd228..f7fb6778a 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp @@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unixtime.h" #include "base/qt_adapters.h" #include "apiwrap.h" +#include "api/api_cloud_password.h" #include "main/main_session.h" #include "styles/style_layers.h" #include "styles/style_boxes.h" @@ -462,7 +463,7 @@ void EditAdminBox::transferOwnership() { ? peer()->asChannel()->inputChannel : MTP_inputChannelEmpty(); const auto api = &peer()->session().api(); - api->reloadPasswordState(); + api->cloudPassword().reload(); _checkTransferRequestId = api->request(MTPchannels_EditCreator( channel, MTP_inputUserEmpty(), @@ -513,7 +514,7 @@ void EditAdminBox::transferOwnershipChecked() { } void EditAdminBox::requestTransferPassword(not_null channel) { - peer()->session().api().passwordState( + peer()->session().api().cloudPassword().state( ) | rpl::take( 1 ) | rpl::start_with_next([=](const Core::CloudPasswordState &state) { diff --git a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp index c68f9c1ea..480c37fad 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp @@ -1472,9 +1472,27 @@ base::unique_qptr ParticipantsBoxController::rowContextMenu( crl::guard(this, [=] { _navigation->showPeerInfo(participant); })); } - result->addAction( - ktr("ktg_context_show_messages_from"), - crl::guard(this, [=] { App::searchByHashtag(QString(), _peer, user); })); + if (const auto window = App::wnd()) { + if (const auto mainwidget = window->sessionContent()) { + result->addAction( + ktr("ktg_context_show_messages_from"), + crl::guard(this, [=] { + mainwidget->searchMessages( + " ", + (_peer && !_peer->isUser()) + ? _peer->owner().history(_peer).get() + : Dialogs::Key(), + user); + })); + if (const auto openedPeer = mainwidget->peer()) { + if (openedPeer->canWrite()) { + result->addAction( + ktr("ktg_profile_mention_user"), + crl::guard(this, [=] { mainwidget->mentionUser(user); })); + } + } + } + } if (_role == Role::Kicked) { if (_peer->isMegagroup() && _additional.canRestrictParticipant(participant)) { @@ -1489,17 +1507,6 @@ base::unique_qptr ParticipantsBoxController::rowContextMenu( } return result; } - if (const auto window = App::wnd()) { - if (const auto mainwidget = window->sessionContent()) { - if (const auto openedPeer = mainwidget->peer()) { - if (openedPeer->canWrite()) { - result->addAction( - ktr("ktg_profile_mention_user"), - crl::guard(this, [=] { mainwidget->mentionUser(user); })); - } - } - } - } if (user && _additional.canAddOrEditAdmin(user)) { const auto isAdmin = _additional.isCreator(user) || _additional.adminRights(user).has_value(); diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index eaa14ba75..ddbc02737 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -173,6 +173,24 @@ callCameraUnmute: CallButton(callMicrophoneUnmute) { } } } +callScreencastOn: CallButton(callMicrophoneMute) { + button: IconButton(callButton) { + icon: icon {{ "calls/calls_present", callIconFg }}; + iconPosition: point(-1px, 22px); + ripple: RippleAnimation(defaultRippleAnimation) { + color: callMuteRipple; + } + } +} +callScreencastOff: CallButton(callMicrophoneUnmute) { + button: IconButton(callButton) { + icon: icon {{ "calls/calls_present", callIconFgActive }}; + iconPosition: point(-1px, 22px); + ripple: RippleAnimation(defaultRippleAnimation) { + color: callIconActiveRipple; + } + } +} callBottomShadowSize: 124px; CallMuteButton { @@ -724,6 +742,12 @@ groupCallTitleLabel: FlatLabel(groupCallSubtitleLabel) { linkFontOver: font(semibold 14px); } } +groupCallVideoLimitLabel: FlatLabel(defaultFlatLabel) { + align: align(top); + textFg: groupCallMemberNotJoinedStatus; + style: semiboldTextStyle; + minWidth: 96px; +} groupCallAddButtonPosition: point(10px, 7px); groupCallMembersWidthMax: 480px; groupCallRecordingMark: 6px; @@ -1215,6 +1239,7 @@ groupCallNarrowColoredCrossLine: CrossLineAnimation(groupCallNarrowInactiveCross groupCallNarrowRaisedHand: icon {{ "calls/video_mini_speak", groupCallMemberInactiveStatus }}; groupCallNarrowCameraIcon: icon {{ "calls/video_mini_video", groupCallMemberNotJoinedStatus }}; groupCallNarrowScreenIcon: icon {{ "calls/video_mini_screencast", groupCallMemberNotJoinedStatus }}; +groupCallNarrowInvitedIcon: icon {{ "calls/video_mini_invited", groupCallMemberNotJoinedStatus }}; groupCallNarrowIconPosition: point(-4px, 2px); groupCallNarrowIconSkip: 15px; groupCallOutline: 2px; diff --git a/Telegram/SourceFiles/calls/calls_box_controller.cpp b/Telegram/SourceFiles/calls/calls_box_controller.cpp index 6e828bb23..a992ed546 100644 --- a/Telegram/SourceFiles/calls/calls_box_controller.cpp +++ b/Telegram/SourceFiles/calls/calls_box_controller.cpp @@ -406,7 +406,7 @@ void BoxController::receivedCalls(const QVector &result) { if (const auto peer = session().data().peerLoaded(peerId)) { const auto item = session().data().addNewMessage( message, - MTPDmessage_ClientFlags(), + MessageFlags(), NewMessageType::Existing); insertRow(item, InsertWay::Append); } else { diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp index 41b5beb92..af7922578 100644 --- a/Telegram/SourceFiles/calls/calls_call.cpp +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -290,16 +290,8 @@ void Call::startIncoming() { }).send(); } -void Call::switchVideoOutgoing() { - const auto video = _videoOutgoing->state() == Webrtc::VideoState::Active; - _delegate->callRequestPermissionsOrFail(crl::guard(this, [=] { - videoOutgoing()->setState(StartVideoState(!video)); - }), true); - -} - void Call::answer() { - const auto video = _videoOutgoing->state() == Webrtc::VideoState::Active; + const auto video = isSharingVideo(); _delegate->callRequestPermissionsOrFail(crl::guard(this, [=] { actuallyAnswer(); }), video); @@ -366,7 +358,9 @@ void Call::setupOutgoingVideo() { } _videoOutgoing->stateValue( ) | rpl::start_with_next([=](Webrtc::VideoState state) { - if (state != Webrtc::VideoState::Inactive && !hasDevices()) { + if (state != Webrtc::VideoState::Inactive + && !hasDevices() + && !_videoCaptureIsScreencast) { _errors.fire({ ErrorType::NoCamera }); _videoOutgoing->setState(Webrtc::VideoState::Inactive); } else if (_state.current() != State::Established @@ -383,7 +377,9 @@ void Call::setupOutgoingVideo() { // Paused not supported right now. Assert(state == Webrtc::VideoState::Active); if (!_videoCapture) { - _videoCapture = _delegate->callGetVideoCapture(); + _videoCapture = _delegate->callGetVideoCapture( + _videoCaptureDeviceId, + _videoCaptureIsScreencast); _videoCapture->setOutput(_videoOutgoing->sink()); } if (_instance) { @@ -986,9 +982,15 @@ void Call::setCurrentAudioDevice(bool input, const QString &deviceId) { } } -void Call::setCurrentVideoDevice(const QString &deviceId) { - if (_videoCapture) { - _videoCapture->switchToDevice(deviceId.toStdString()); +void Call::setCurrentCameraDevice(const QString &deviceId) { + if (!_videoCaptureIsScreencast) { + _videoCaptureDeviceId = deviceId; + if (_videoCapture) { + _videoCapture->switchToDevice(deviceId.toStdString(), false); + if (_instance) { + _instance->sendVideoDeviceUpdated(); + } + } } } @@ -1008,6 +1010,77 @@ void Call::setAudioDuckingEnabled(bool enabled) { } } +bool Call::isSharingVideo() const { + return (_videoOutgoing->state() != Webrtc::VideoState::Inactive); +} + +bool Call::isSharingCamera() const { + return !_videoCaptureIsScreencast && isSharingVideo(); +} + +bool Call::isSharingScreen() const { + return _videoCaptureIsScreencast && isSharingVideo(); +} + +QString Call::cameraSharingDeviceId() const { + return isSharingCamera() ? _videoCaptureDeviceId : QString(); +} + +QString Call::screenSharingDeviceId() const { + return isSharingScreen() ? _videoCaptureDeviceId : QString(); +} + +void Call::toggleCameraSharing(bool enabled) { + if (isSharingCamera() == enabled) { + return; + } else if (!enabled) { + if (_videoCapture) { + _videoCapture->setState(tgcalls::VideoState::Inactive); + } + _videoOutgoing->setState(Webrtc::VideoState::Inactive); + _videoCaptureDeviceId = QString(); + return; + } + _delegate->callRequestPermissionsOrFail(crl::guard(this, [=] { + toggleScreenSharing(std::nullopt); + const auto deviceId = Core::App().settings().callVideoInputDeviceId(); + _videoCaptureDeviceId = deviceId; + if (_videoCapture) { + _videoCapture->switchToDevice(deviceId.toStdString(), false); + if (_instance) { + _instance->sendVideoDeviceUpdated(); + } + } + _videoOutgoing->setState(Webrtc::VideoState::Active); + }), true); +} + +void Call::toggleScreenSharing(std::optional uniqueId) { + if (!uniqueId) { + if (isSharingScreen()) { + if (_videoCapture) { + _videoCapture->setState(tgcalls::VideoState::Inactive); + } + _videoOutgoing->setState(Webrtc::VideoState::Inactive); + } + _videoCaptureDeviceId = QString(); + _videoCaptureIsScreencast = false; + return; + } else if (screenSharingDeviceId() == *uniqueId) { + return; + } + toggleCameraSharing(false); + _videoCaptureIsScreencast = true; + _videoCaptureDeviceId = *uniqueId; + if (_videoCapture) { + _videoCapture->switchToDevice(uniqueId->toStdString(), true); + if (_instance) { + _instance->sendVideoDeviceUpdated(); + } + } + _videoOutgoing->setState(Webrtc::VideoState::Active); +} + void Call::finish(FinishType type, const MTPPhoneCallDiscardReason &reason) { Expects(type != FinishType::None); diff --git a/Telegram/SourceFiles/calls/calls_call.h b/Telegram/SourceFiles/calls/calls_call.h index fec90fc4a..1478a8ed8 100644 --- a/Telegram/SourceFiles/calls/calls_call.h +++ b/Telegram/SourceFiles/calls/calls_call.h @@ -77,8 +77,10 @@ public: Fn onSuccess, bool video) = 0; - virtual auto callGetVideoCapture() - -> std::shared_ptr = 0; + virtual auto callGetVideoCapture( + const QString &deviceId, + bool isScreenCapture) + -> std::shared_ptr = 0; virtual ~Delegate() = default; @@ -174,7 +176,6 @@ public: crl::time getDurationMs() const; float64 getWaitingSoundPeakValue() const; - void switchVideoOutgoing(); void answer(); void hangup(); void redial(); @@ -185,10 +186,22 @@ public: QString getDebugLog() const; void setCurrentAudioDevice(bool input, const QString &deviceId); - void setCurrentVideoDevice(const QString &deviceId); //void setAudioVolume(bool input, float level); void setAudioDuckingEnabled(bool enabled); + void setCurrentCameraDevice(const QString &deviceId); + [[nodiscard]] QString videoDeviceId() const { + return _videoCaptureDeviceId; + } + + [[nodiscard]] bool isSharingVideo() const; + [[nodiscard]] bool isSharingCamera() const; + [[nodiscard]] bool isSharingScreen() const; + [[nodiscard]] QString cameraSharingDeviceId() const; + [[nodiscard]] QString screenSharingDeviceId() const; + void toggleCameraSharing(bool enabled); + void toggleScreenSharing(std::optional uniqueId); + [[nodiscard]] rpl::lifetime &lifetime() { return _lifetime; } @@ -268,6 +281,8 @@ private: std::unique_ptr _instance; std::shared_ptr _videoCapture; + QString _videoCaptureDeviceId; + bool _videoCaptureIsScreencast = false; const std::unique_ptr _videoIncoming; const std::unique_ptr _videoOutgoing; diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index 56fd9a6d1..d23c625ab 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -65,8 +65,10 @@ public: Fn onSuccess, bool video) override; void callPlaySound(CallSound sound) override; - auto callGetVideoCapture() - -> std::shared_ptr override; + auto callGetVideoCapture( + const QString &deviceId, + bool isScreenCapture) + -> std::shared_ptr override; void groupCallFinished(not_null call) override; void groupCallFailed(not_null call) override; @@ -124,9 +126,11 @@ void Instance::Delegate::callPlaySound(CallSound sound) { }()); } -auto Instance::Delegate::callGetVideoCapture() +auto Instance::Delegate::callGetVideoCapture( + const QString &deviceId, + bool isScreenCapture) -> std::shared_ptr { - return _instance->getVideoCapture(); + return _instance->getVideoCapture(deviceId, isScreenCapture); } void Instance::Delegate::groupCallFinished(not_null call) { @@ -160,7 +164,7 @@ void Instance::Delegate::groupCallPlaySound(GroupCallSound sound) { auto Instance::Delegate::groupCallGetVideoCapture(const QString &deviceId) -> std::shared_ptr { - return _instance->getVideoCapture(deviceId); + return _instance->getVideoCapture(deviceId, false); } FnMut Instance::Delegate::groupCallAddAsyncWaiter() { @@ -700,18 +704,25 @@ void Instance::requestPermissionOrFail(Platform::PermissionType type, Fn } std::shared_ptr Instance::getVideoCapture( - QString deviceId) { - if (deviceId.isEmpty()) { - deviceId = Core::App().settings().callVideoInputDeviceId(); - } + std::optional deviceId, + bool isScreenCapture) { if (auto result = _videoCapture.lock()) { - result->switchToDevice(deviceId.toStdString()); + if (deviceId) { + result->switchToDevice( + (deviceId->isEmpty() + ? Core::App().settings().callVideoInputDeviceId() + : *deviceId).toStdString(), + isScreenCapture); + } return result; } + const auto startDeviceId = (deviceId && !deviceId->isEmpty()) + ? *deviceId + : Core::App().settings().callVideoInputDeviceId(); auto result = std::shared_ptr( tgcalls::VideoCaptureInterface::Create( tgcalls::StaticThreads::getThreads(), - deviceId.toStdString())); + startDeviceId.toStdString())); _videoCapture = result; return result; } diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h index 6ab7e937c..c403ef4eb 100644 --- a/Telegram/SourceFiles/calls/calls_instance.h +++ b/Telegram/SourceFiles/calls/calls_instance.h @@ -75,7 +75,9 @@ public: bool activateCurrentCall(const QString &joinHash = QString()); bool minimizeCurrentActiveCall(); bool closeCurrentActiveCall(); - [[nodiscard]] auto getVideoCapture(QString deviceId = QString()) + [[nodiscard]] auto getVideoCapture( + std::optional deviceId = std::nullopt, + bool isScreenCapture = false) -> std::shared_ptr; void requestPermissionsOrFail(Fn onSuccess, bool video = true); diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index 71e875584..f66ede2c8 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_photo_media.h" #include "data/data_cloud_file.h" #include "data/data_changes.h" +#include "calls/group/calls_group_common.h" #include "calls/calls_emoji_fingerprint.h" #include "calls/calls_signal_bars.h" #include "calls/calls_userpic.h" @@ -24,7 +25,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/widgets/shadow.h" -#include "ui/widgets/window.h" +#include "ui/widgets/rp_window.h" +#include "ui/layers/layer_manager.h" +#include "ui/layers/generic_box.h" #include "ui/image/image.h" #include "ui/text/format_values.h" #include "ui/wrap/fade_wrap.h" @@ -44,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/main_window.h" #include "app.h" #include "webrtc/webrtc_video_track.h" +#include "webrtc/webrtc_media_devices.h" #include "styles/style_calls.h" #include "styles/style_chat.h" @@ -57,6 +61,7 @@ namespace Calls { Panel::Panel(not_null call) : _call(call) , _user(call->user()) +, _layerBg(std::make_unique(widget())) #ifndef Q_OS_MAC , _controls(std::make_unique( widget(), @@ -67,10 +72,14 @@ Panel::Panel(not_null call) , _answerHangupRedial(widget(), st::callAnswer, &st::callHangup) , _decline(widget(), object_ptr(widget(), st::callHangup)) , _cancel(widget(), object_ptr(widget(), st::callCancel)) +, _screencast(widget(), st::callScreencastOn, &st::callScreencastOff) , _camera(widget(), st::callCameraMute, &st::callCameraUnmute) , _mute(widget(), st::callMicrophoneMute, &st::callMicrophoneUnmute) , _name(widget(), st::callName) , _status(widget(), st::callStatus) { + _layerBg->setStyleOverrides(&st::groupCallBox, &st::groupCallLayerBox); + _layerBg->setHideByBackgroundClick(true); + _decline->setDuration(st::callPanelDuration); _decline->entity()->setText(tr::lng_call_decline()); _cancel->setDuration(st::callPanelDuration); @@ -152,9 +161,13 @@ void Panel::initWindow() { _answerHangupRedial->height()).contains(widgetPoint) || (!_outgoingPreviewInBody && _outgoingVideoBubble->geometry().contains(widgetPoint)); - return inControls - ? Flag::None - : (Flag::Move | Flag::FullScreen); + if (inControls) { + return Flag::None | Flag(0); + } + const auto shown = _layerBg->topShownLayer(); + return (!shown || !shown->geometry().contains(widgetPoint)) + ? (Flag::Move | Flag::FullScreen) + : Flag::None; }); // Don't do that, it looks awful :( @@ -205,9 +218,28 @@ void Panel::initControls() { _call->setMuted(!_call->muted()); } }); + _screencast->setClickedCallback([=] { + if (!_call) { + return; + } else if (!Webrtc::DesktopCaptureAllowed()) { + if (auto box = Group::ScreenSharingPrivacyRequestBox()) { + _layerBg->showBox(std::move(box)); + } + } else if (const auto source = Webrtc::UniqueDesktopCaptureSource()) { + if (_call->isSharingScreen()) { + _call->toggleScreenSharing(std::nullopt); + } else { + chooseSourceAccepted(*source, false); + } + } else { + Group::Ui::DesktopCapture::ChooseSource(this); + } + }); _camera->setClickedCallback([=] { - if (_call) { - _call->switchVideoOutgoing(); + if (!_call) { + return; + } else { + _call->toggleCameraSharing(!_call->isSharingCamera()); } }); @@ -218,7 +250,8 @@ void Panel::initControls() { }); _updateOuterRippleTimer.setCallback([this] { if (_call) { - _answerHangupRedial->setOuterValue(_call->getWaitingSoundPeakValue()); + _answerHangupRedial->setOuterValue( + _call->getWaitingSoundPeakValue()); } else { _answerHangupRedial->setOuterValue(0.); _updateOuterRippleTimer.cancel(); @@ -260,6 +293,40 @@ void Panel::setIncomingSize(QSize size) { showControls(); } +QWidget *Panel::chooseSourceParent() { + return window().get(); +} + +QString Panel::chooseSourceActiveDeviceId() { + return _call->screenSharingDeviceId(); +} + +bool Panel::chooseSourceActiveWithAudio() { + return false;// _call->screenSharingWithAudio(); +} + +bool Panel::chooseSourceWithAudioSupported() { +//#ifdef Q_OS_WIN +// return true; +//#else // Q_OS_WIN + return false; +//#endif // Q_OS_WIN +} + +rpl::lifetime &Panel::chooseSourceInstanceLifetime() { + return lifetime(); +} + +void Panel::chooseSourceAccepted( + const QString &deviceId, + bool withAudio) { + _call->toggleScreenSharing(deviceId/*, withAudio*/); +} + +void Panel::chooseSourceStop() { + _call->toggleScreenSharing(std::nullopt); +} + void Panel::refreshIncomingGeometry() { Expects(_call != nullptr); Expects(_incoming != nullptr); @@ -332,12 +399,19 @@ void Panel::reinitWithCall(Call *call) { }, _callLifetime); _call->videoOutgoing()->stateValue( - ) | rpl::start_with_next([=](Webrtc::VideoState state) { - const auto active = (state == Webrtc::VideoState::Active); - _camera->setProgress(active ? 0. : 1.); - _camera->setText(active - ? tr::lng_call_stop_video() - : tr::lng_call_start_video()); + ) | rpl::start_with_next([=] { + { + const auto active = _call->isSharingCamera(); + _camera->setProgress(active ? 0. : 1.); + _camera->setText(active + ? tr::lng_call_stop_video() + : tr::lng_call_start_video()); + } + { + const auto active = _call->isSharingScreen(); + _screencast->setProgress(active ? 0. : 1.); + _screencast->setText(tr::lng_call_screencast()); + } }, _callLifetime); _call->stateValue( @@ -646,9 +720,11 @@ void Panel::updateControlsGeometry() { updateOutgoingVideoBubbleGeometry(); } - auto bothWidth = _answerHangupRedial->width() + st::callCancel.button.width; - _decline->moveToLeft((widget()->width() - bothWidth) / 2, _buttonsTop); - _cancel->moveToLeft((widget()->width() - bothWidth) / 2, _buttonsTop); + auto threeWidth = _answerHangupRedial->width() + + st::callCancel.button.width + - _screencast->width(); + _decline->moveToLeft((widget()->width() - threeWidth) / 2, _buttonsTop); + _cancel->moveToLeft((widget()->width() - threeWidth) / 2, _buttonsTop); updateHangupGeometry(); } @@ -670,16 +746,19 @@ void Panel::updateOutgoingVideoBubbleGeometry() { } void Panel::updateHangupGeometry() { - auto singleWidth = _answerHangupRedial->width(); - auto bothWidth = singleWidth + st::callCancel.button.width; - auto rightFrom = (widget()->width() - bothWidth) / 2; - auto rightTo = (widget()->width() - singleWidth) / 2; + auto twoWidth = _answerHangupRedial->width() + _screencast->width(); + auto threeWidth = twoWidth + st::callCancel.button.width; + auto rightFrom = (widget()->width() - threeWidth) / 2; + auto rightTo = (widget()->width() - twoWidth) / 2; auto hangupProgress = _hangupShownProgress.value(_hangupShown ? 1. : 0.); auto hangupRight = anim::interpolate(rightFrom, rightTo, hangupProgress); _answerHangupRedial->moveToRight(hangupRight, _buttonsTop); _answerHangupRedial->setProgress(hangupProgress); _mute->moveToRight(hangupRight - _mute->width(), _buttonsTop); - _camera->moveToLeft(hangupRight - _mute->width(), _buttonsTop); + _screencast->moveToLeft(hangupRight - _mute->width(), _buttonsTop); + _camera->moveToLeft( + hangupRight - _mute->width() + _screencast->width(), + _buttonsTop); } void Panel::updateStatusGeometry() { @@ -709,7 +788,7 @@ void Panel::handleClose() { } } -not_null Panel::window() const { +not_null Panel::window() const { return _window.window(); } diff --git a/Telegram/SourceFiles/calls/calls_panel.h b/Telegram/SourceFiles/calls/calls_panel.h index e7f7861d9..adff466b9 100644 --- a/Telegram/SourceFiles/calls/calls_panel.h +++ b/Telegram/SourceFiles/calls/calls_panel.h @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/timer.h" #include "base/object_ptr.h" #include "calls/calls_call.h" +#include "calls/group/ui/desktop_capture_choose_source.h" #include "ui/effects/animations.h" #include "ui/gl/gl_window.h" #include "ui/rp_widget.h" @@ -25,12 +26,13 @@ class CloudImageView; namespace Ui { class IconButton; class CallButton; +class LayerManager; class FlatLabel; template class FadeWrap; template class PaddingWrap; -class Window; +class RpWindow; namespace GL { enum class Backend; } // namespace GL @@ -50,7 +52,7 @@ class Userpic; class SignalBars; class VideoBubble; -class Panel final { +class Panel final : private Group::Ui::DesktopCapture::ChooseSourceDelegate { public: Panel(not_null call); ~Panel(); @@ -61,7 +63,17 @@ public: void replaceCall(not_null call); void closeBeforeDestroy(); - rpl::lifetime &lifetime(); + QWidget *chooseSourceParent() override; + QString chooseSourceActiveDeviceId() override; + bool chooseSourceActiveWithAudio() override; + bool chooseSourceWithAudioSupported() override; + rpl::lifetime &chooseSourceInstanceLifetime() override; + void chooseSourceAccepted( + const QString &deviceId, + bool withAudio) override; + void chooseSourceStop() override; + + [[nodiscard]] rpl::lifetime &lifetime(); private: class Incoming; @@ -73,7 +85,7 @@ private: Redial, }; - [[nodiscard]] not_null window() const; + [[nodiscard]] not_null window() const; [[nodiscard]] not_null widget() const; void paint(QRect clip); @@ -110,6 +122,7 @@ private: not_null _user; Ui::GL::Window _window; + const std::unique_ptr _layerBg; std::unique_ptr _incoming; #ifndef Q_OS_MAC @@ -128,6 +141,7 @@ private: bool _outgoingPreviewInBody = false; std::optional _answerHangupRedialState; Ui::Animations::Simple _hangupShownProgress; + object_ptr _screencast; object_ptr _camera; object_ptr _mute; object_ptr _name; diff --git a/Telegram/SourceFiles/calls/calls_video_incoming.cpp b/Telegram/SourceFiles/calls/calls_video_incoming.cpp index 56cb34762..bfd3fe7e7 100644 --- a/Telegram/SourceFiles/calls/calls_video_incoming.cpp +++ b/Telegram/SourceFiles/calls/calls_video_incoming.cpp @@ -223,7 +223,6 @@ void Panel::Incoming::RendererGL::paint( Assert(data.format == Webrtc::FrameFormat::YUV420); Assert(!data.yuv420->size.isEmpty()); const auto yuv = data.yuv420; - const auto format = Ui::GL::CurrentSingleComponentFormat(); f.glActiveTexture(GL_TEXTURE0); _textures.bind(f, 1); @@ -231,8 +230,8 @@ void Panel::Incoming::RendererGL::paint( f.glPixelStorei(GL_UNPACK_ALIGNMENT, 1); uploadTexture( f, - format, - format, + GL_ALPHA, + GL_ALPHA, yuv->size, _lumaSize, yuv->y.stride, @@ -244,8 +243,8 @@ void Panel::Incoming::RendererGL::paint( if (upload) { uploadTexture( f, - format, - format, + GL_ALPHA, + GL_ALPHA, yuv->chromaSize, _chromaSize, yuv->u.stride, @@ -256,8 +255,8 @@ void Panel::Incoming::RendererGL::paint( if (upload) { uploadTexture( f, - format, - format, + GL_ALPHA, + GL_ALPHA, yuv->chromaSize, _chromaSize, yuv->v.stride, diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 9b9e9a5ad..7f38ec867 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -654,7 +654,7 @@ void GroupCall::toggleScreenSharing( _screenWithAudio = withAudio; _screenState = Webrtc::VideoState::Active; if (changed && wasSharing && isSharingScreen()) { - _screenCapture->switchToDevice(uniqueId->toStdString()); + _screenCapture->switchToDevice(uniqueId->toStdString(), true); } if (_screenInstance) { _screenInstance->setIsMuted(!withAudio); @@ -1951,7 +1951,7 @@ void GroupCall::setupMediaDevices() { ) | rpl::start_with_next([=](QString id) { _cameraInputId = id; if (_cameraCapture) { - _cameraCapture->switchToDevice(id.toStdString()); + _cameraCapture->switchToDevice(id.toStdString(), false); } }, _lifetime); } @@ -2064,7 +2064,9 @@ void GroupCall::setupOutgoingVideo() { }); }); } else { - _cameraCapture->switchToDevice(_cameraInputId.toStdString()); + _cameraCapture->switchToDevice( + _cameraInputId.toStdString(), + false); } if (_instance) { _instance->setVideoCapture(_cameraCapture); @@ -2131,7 +2133,8 @@ void GroupCall::setupOutgoingVideo() { }); } else { _screenCapture->switchToDevice( - _screenDeviceId.toStdString()); + _screenDeviceId.toStdString(), + true); } if (_screenInstance) { _screenInstance->setVideoCapture(_screenCapture); @@ -3049,10 +3052,6 @@ void GroupCall::setCurrentAudioDevice(bool input, const QString &deviceId) { } } -void GroupCall::setCurrentVideoDevice(const QString &deviceId) { - _mediaDevices->switchToVideoInput(deviceId); -} - void GroupCall::toggleMute(const Group::MuteRequest &data) { if (data.locallyOnly) { applyParticipantLocally(data.peer, data.mute, std::nullopt); diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index a4a06923b..4e8f59f71 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -370,7 +370,6 @@ public: } void setCurrentAudioDevice(bool input, const QString &deviceId); - void setCurrentVideoDevice(const QString &deviceId); [[nodiscard]] bool isSharingScreen() const; [[nodiscard]] rpl::producer isSharingScreenValue() const; [[nodiscard]] bool isScreenPaused() const; diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.cpp b/Telegram/SourceFiles/calls/group/calls_group_common.cpp new file mode 100644 index 000000000..487629893 --- /dev/null +++ b/Telegram/SourceFiles/calls/group/calls_group_common.cpp @@ -0,0 +1,53 @@ +/* +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 "calls/group/calls_group_common.h" + +#include "base/platform/base_platform_info.h" +#include "ui/widgets/labels.h" +#include "ui/layers/generic_box.h" +#include "ui/text/text_utilities.h" +#include "lang/lang_keys.h" +#include "styles/style_layers.h" +#include "styles/style_calls.h" + +namespace Calls::Group { + +object_ptr ScreenSharingPrivacyRequestBox() { +#ifdef Q_OS_MAC + if (!Platform::IsMac10_15OrGreater()) { + return { nullptr }; + } + return Box([=](not_null box) { + box->addRow( + object_ptr( + box.get(), + rpl::combine( + tr::lng_group_call_mac_screencast_access(), + tr::lng_group_call_mac_recording() + ) | rpl::map([](QString a, QString b) { + auto result = Ui::Text::RichLangValue(a); + result.append("\n\n").append(Ui::Text::RichLangValue(b)); + return result; + }), + st::groupCallBoxLabel), + style::margins( + st::boxRowPadding.left(), + st::boxPadding.top(), + st::boxRowPadding.right(), + st::boxPadding.bottom())); + box->addButton(tr::lng_group_call_mac_settings(), [=] { + Platform::OpenDesktopCapturePrivacySettings(); + }); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); + }); +#else // Q_OS_MAC + return { nullptr }; +#endif // Q_OS_MAC +} + +} // namespace Calls::Group diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.h b/Telegram/SourceFiles/calls/group/calls_group_common.h index ce84f94ef..8ab37b978 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.h +++ b/Telegram/SourceFiles/calls/group/calls_group_common.h @@ -7,8 +7,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "base/object_ptr.h" + class UserData; +namespace Ui { +class GenericBox; +} // namespace Ui + namespace Calls::Group { constexpr auto kDefaultVolume = 10000; @@ -78,4 +84,6 @@ constexpr inline bool is_flag_type(StickedTooltip) { } using StickedTooltips = base::flags; +[[nodiscard]] object_ptr ScreenSharingPrivacyRequestBox(); + } // namespace Calls::Group diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index c82fbb403..2604783bd 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -46,9 +46,16 @@ constexpr auto kUserpicBlurRadius = 8; using Row = MembersRow; +[[nodiscard]] int VideoParticipantsLimit(not_null session) { + return int(session->account().appConfig().get( + "groupcall_video_participants_max", + 30.)); +} + void SetupVideoPlaceholder( not_null widget, - not_null chat) { + not_null chat, + int limit) { struct State { QImage blurred; QImage rounded; @@ -128,9 +135,6 @@ void SetupVideoPlaceholder( size.width()); const auto skip = st::groupCallVideoLargeSkip; - const auto limit = chat->session().account().appConfig().get( - "groupcall_video_participants_max", - 30.); p.setPen(st::groupCallVideoTextFg); const auto text = QRect( skip, @@ -145,6 +149,22 @@ void SetupVideoPlaceholder( }, widget->lifetime()); } +void SetupVideoAboutLimit( + not_null widget, + not_null session, + int limit) { + const auto label = Ui::CreateChild( + widget.get(), + tr::lng_group_call_over_limit(lt_count, rpl::single(limit * 1.)), + st::groupCallVideoLimitLabel); + widget->widthValue( + ) | rpl::start_with_next([=](int width) { + label->resizeToWidth(width); + label->moveToLeft(0, st::normalFont->height / 3); + widget->resize(width, label->height() + st::normalFont->height); + }, label->lifetime()); +} + } // namespace class Members::Controller final @@ -1036,12 +1056,16 @@ void Members::Controller::rowPaintIcon( return; } const auto narrow = (state.style == MembersRowStyle::Narrow); - if (!narrow && state.invited) { - st::groupCallMemberInvited.paintInCenter( - p, - QRect( - rect.topLeft() + st::groupCallMemberInvitedPosition, - st::groupCallMemberInvited.size())); + if (state.invited) { + if (narrow) { + st::groupCallNarrowInvitedIcon.paintInCenter(p, rect); + } else { + st::groupCallMemberInvited.paintInCenter( + p, + QRect( + rect.topLeft() + st::groupCallMemberInvitedPosition, + st::groupCallMemberInvited.size())); + } return; } const auto video = (state.style == MembersRowStyle::Video); @@ -1601,6 +1625,7 @@ Members::Members( object_ptr(_scroll.data()))) , _videoWrap(_layout->add(object_ptr(_layout.get()))) , _videoPlaceholder(std::make_unique(_videoWrap.get())) +, _videoAboutLimit(std::make_unique(_videoWrap.get())) , _viewport( std::make_unique( _videoWrap.get(), @@ -1838,6 +1863,7 @@ void Members::trackViewportGeometry() { _scroll->scrollTopValue( ) | rpl::skip(1) | rpl::start_with_next(move, _viewport->lifetime()); + const auto videoLimit = VideoParticipantsLimit(&_call->peer()->session()); rpl::combine( _layout->widthValue(), _call->hasNotShownVideoValue() @@ -1846,15 +1872,52 @@ void Members::trackViewportGeometry() { _videoPlaceholder->setGeometry(0, 0, width, height); }, _videoPlaceholder->lifetime()); - SetupVideoPlaceholder(_videoPlaceholder.get(), _call->peer()); + SetupVideoPlaceholder(_videoPlaceholder.get(), _call->peer(), videoLimit); + + _layout->widthValue( + ) | rpl::start_with_next([=](int width) { + _videoAboutLimit->resizeToWidth(width); + }, _videoAboutLimit->lifetime()); + + using namespace rpl::mappers; + auto aboutLimitRelevant = fullCountValue( + ) | rpl::map( + _1 > videoLimit + ) | rpl::distinct_until_changed(); + auto aboutLimitShown = rpl::combine( + std::move(aboutLimitRelevant), + _call->canManageValue(), + _1 && _2); + + SetupVideoAboutLimit( + _videoAboutLimit.get(), + &_call->peer()->session(), + videoLimit); rpl::combine( _videoPlaceholder->heightValue(), - _viewport->fullHeightValue() - ) | rpl::start_with_next([=](int placeholder, int viewport) { + _viewport->fullHeightValue(), + _videoAboutLimit->heightValue(), + std::move(aboutLimitShown) + ) | rpl::start_with_next([=]( + int placeholder, + int viewport, + int aboutLimit, + bool aboutLimitShown) { + if (placeholder > 0 || viewport <= 0 || !aboutLimitShown) { + aboutLimitShown = false; + } + + // This call may update _videoAboutLimit->height() :( + _videoAboutLimit->setVisible(aboutLimitShown); + + _videoAboutLimit->move(0, viewport); _videoWrap->resize( _videoWrap->width(), - std::max(placeholder, viewport)); + std::max( + placeholder, + (viewport + + (aboutLimitShown ? _videoAboutLimit->height() : 0)))); if (viewport > 0) { move(); resize(); diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.h b/Telegram/SourceFiles/calls/group/calls_group_members.h index 198fdcaad..bb8c2a813 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.h +++ b/Telegram/SourceFiles/calls/group/calls_group_members.h @@ -102,6 +102,7 @@ private: not_null _layout; const not_null _videoWrap; const std::unique_ptr _videoPlaceholder; + const std::unique_ptr _videoAboutLimit; std::unique_ptr _viewport; rpl::variable _addMemberButton = nullptr; RpWidget *_topSkip = nullptr; diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 33ca08c28..6a5a1a2f9 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -25,7 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/dropdown_menu.h" #include "ui/widgets/input_fields.h" #include "ui/widgets/tooltip.h" -#include "ui/widgets/window.h" +#include "ui/widgets/rp_window.h" #include "ui/chat/group_call_bar.h" #include "ui/layers/layer_manager.h" #include "ui/layers/generic_box.h" @@ -70,10 +70,6 @@ constexpr auto kRecordingOpacity = 0.6; constexpr auto kStartNoConfirmation = TimeId(10); constexpr auto kControlsBackgroundOpacity = 0.8; constexpr auto kOverrideActiveColorBgAlpha = 172; -constexpr auto kMicrophoneTooltipAfterLoudCount = 3; -constexpr auto kDropLoudAfterQuietCount = 5; -constexpr auto kMicrophoneTooltipLevelThreshold = 0.2; -constexpr auto kMicrophoneTooltipCheckInterval = crl::time(500); } // namespace @@ -87,49 +83,6 @@ struct Panel::ControlsBackgroundNarrow { Ui::RpWidget blocker; }; -class Panel::MicLevelTester final { -public: - explicit MicLevelTester(Fn show); - - [[nodiscard]] bool showTooltip() const; - -private: - void check(); - - Fn _show; - base::Timer _timer; - Webrtc::AudioInputTester _tester; - int _loudCount = 0; - int _quietCount = 0; - -}; - -Panel::MicLevelTester::MicLevelTester(Fn show) -: _show(std::move(show)) -, _timer([=] { check(); }) -, _tester( - Core::App().settings().callAudioBackend(), - Core::App().settings().callInputDeviceId()) { - _timer.callEach(kMicrophoneTooltipCheckInterval); -} - -bool Panel::MicLevelTester::showTooltip() const { - return (_loudCount >= kMicrophoneTooltipAfterLoudCount); -} - -void Panel::MicLevelTester::check() { - const auto level = _tester.getAndResetLevel(); - if (level >= kMicrophoneTooltipLevelThreshold) { - _quietCount = 0; - if (++_loudCount >= kMicrophoneTooltipAfterLoudCount) { - _show(); - } - } else if (_loudCount > 0 && ++_quietCount >= kDropLoudAfterQuietCount) { - _quietCount = 0; - _loudCount = 0; - } -} - Panel::Panel(not_null call) : _call(call) , _peer(call->peer()) @@ -315,11 +268,16 @@ void Panel::initWindow() { 0, widget()->width(), st::groupCallMembersTop); - return (titleRect.contains(widgetPoint) + const auto moveable = (titleRect.contains(widgetPoint) && (!_menuToggle || !_menuToggle->geometry().contains(widgetPoint)) && (!_menu || !_menu->geometry().contains(widgetPoint)) && (!_recordingMark || !_recordingMark->geometry().contains(widgetPoint)) - && (!_joinAsToggle || !_joinAsToggle->geometry().contains(widgetPoint))) + && (!_joinAsToggle || !_joinAsToggle->geometry().contains(widgetPoint))); + if (!moveable) { + return (Flag::None | Flag(0)); + } + const auto shown = _layerBg->topShownLayer(); + return (!shown || !shown->geometry().contains(widgetPoint)) ? (Flag::Move | Flag::Maximize) : Flag::None; }); @@ -1155,35 +1113,9 @@ void Panel::refreshTopButton() { } void Panel::screenSharingPrivacyRequest() { -#ifdef Q_OS_MAC - if (!Platform::IsMac10_15OrGreater()) { - return; + if (auto box = ScreenSharingPrivacyRequestBox()) { + _layerBg->showBox(std::move(box)); } - const auto requestInputMonitoring = Platform::IsMac10_15OrGreater(); - _layerBg->showBox(Box([=](not_null box) { - box->addRow( - object_ptr( - box.get(), - rpl::combine( - tr::lng_group_call_mac_screencast_access(), - tr::lng_group_call_mac_recording() - ) | rpl::map([](QString a, QString b) { - auto result = Ui::Text::RichLangValue(a); - result.append("\n\n").append(Ui::Text::RichLangValue(b)); - return result; - }), - st::groupCallBoxLabel), - style::margins( - st::boxRowPadding.left(), - st::boxPadding.top(), - st::boxRowPadding.right(), - st::boxPadding.bottom())); - box->addButton(tr::lng_group_call_mac_settings(), [=] { - Platform::OpenDesktopCapturePrivacySettings(); - }); - box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); - })); -#endif // Q_OS_MAC } void Panel::chooseShareScreenSource() { @@ -2221,7 +2153,7 @@ bool Panel::handleClose() { return false; } -not_null Panel::window() const { +not_null Panel::window() const { return _window.window(); } diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.h b/Telegram/SourceFiles/calls/group/calls_group_panel.h index 4cfc3bbcb..75bac4aa0 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.h +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.h @@ -63,6 +63,7 @@ class Members; class Viewport; enum class PanelMode; enum class StickedTooltip; +class MicLevelTester; class Panel final : private Ui::DesktopCapture::ChooseSourceDelegate { public: @@ -94,9 +95,8 @@ private: Activated, Discarded, }; - class MicLevelTester; - [[nodiscard]] not_null window() const; + [[nodiscard]] not_null window() const; [[nodiscard]] not_null widget() const; [[nodiscard]] PanelMode mode() const; diff --git a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp index 19f3f37bb..62cac5a79 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp @@ -53,6 +53,10 @@ namespace Calls::Group { namespace { constexpr auto kDelaysCount = 201; +constexpr auto kMicrophoneTooltipAfterLoudCount = 3; +constexpr auto kDropLoudAfterQuietCount = 5; +constexpr auto kMicrophoneTooltipLevelThreshold = 0.2; +constexpr auto kMicrophoneTooltipCheckInterval = crl::time(500); #ifdef Q_OS_MAC constexpr auto kCheckAccessibilityInterval = crl::time(500); @@ -735,4 +739,31 @@ std::pair, rpl::lifetime> ShareInviteLinkAction( return { std::move(callback), std::move(lifetime) }; } +MicLevelTester::MicLevelTester(Fn show) +: _show(std::move(show)) +, _timer([=] { check(); }) +, _tester( + std::make_unique( + Core::App().settings().callAudioBackend(), + Core::App().settings().callInputDeviceId())) { + _timer.callEach(kMicrophoneTooltipCheckInterval); +} + +bool MicLevelTester::showTooltip() const { + return (_loudCount >= kMicrophoneTooltipAfterLoudCount); +} + +void MicLevelTester::check() { + const auto level = _tester->getAndResetLevel(); + if (level >= kMicrophoneTooltipLevelThreshold) { + _quietCount = 0; + if (++_loudCount >= kMicrophoneTooltipAfterLoudCount) { + _show(); + } + } else if (_loudCount > 0 && ++_quietCount >= kDropLoudAfterQuietCount) { + _quietCount = 0; + _loudCount = 0; + } +} + } // namespace Calls::Group diff --git a/Telegram/SourceFiles/calls/group/calls_group_settings.h b/Telegram/SourceFiles/calls/group/calls_group_settings.h index 744b8fc50..a4a829e90 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_settings.h +++ b/Telegram/SourceFiles/calls/group/calls_group_settings.h @@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/layers/generic_box.h" +namespace Webrtc { +class AudioInputTester; +} // namespace Webrtc + namespace Calls { class GroupCall; } // namespace Calls @@ -24,4 +28,21 @@ void SettingsBox( Fn)> showBox, Fn showToast); +class MicLevelTester final { +public: + explicit MicLevelTester(Fn show); + + [[nodiscard]] bool showTooltip() const; + +private: + void check(); + + Fn _show; + base::Timer _timer; + std::unique_ptr _tester; + int _loudCount = 0; + int _quietCount = 0; + +}; + } // namespace Calls::Group diff --git a/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp b/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp index cfc3b49e7..7ca3a5024 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_viewport_opengl.cpp @@ -985,7 +985,6 @@ void Viewport::RendererGL::bindFrame( program.argb32->setUniformValue("s_texture", GLint(0)); } else { const auto yuv = data.yuv420; - const auto format = Ui::GL::CurrentSingleComponentFormat(); program.yuv420->bind(); f.glActiveTexture(GL_TEXTURE0); tileData.textures.bind(f, 0); @@ -993,8 +992,8 @@ void Viewport::RendererGL::bindFrame( f.glPixelStorei(GL_UNPACK_ALIGNMENT, 1); uploadTexture( f, - format, - format, + GL_ALPHA, + GL_ALPHA, yuv->size, tileData.textureSize, yuv->y.stride, @@ -1007,8 +1006,8 @@ void Viewport::RendererGL::bindFrame( if (upload) { uploadTexture( f, - format, - format, + GL_ALPHA, + GL_ALPHA, yuv->chromaSize, tileData.textureChromaSize, yuv->u.stride, @@ -1019,8 +1018,8 @@ void Viewport::RendererGL::bindFrame( if (upload) { uploadTexture( f, - format, - format, + GL_ALPHA, + GL_ALPHA, yuv->chromaSize, tileData.textureChromaSize, yuv->v.stride, @@ -1365,19 +1364,33 @@ void Viewport::RendererGL::validateNoiseTexture( if (_noiseTexture.created()) { return; } - const auto format = Ui::GL::CurrentSingleComponentFormat(); _noiseTexture.ensureCreated(f, GL_NEAREST, GL_REPEAT); _noiseTexture.bind(f, 0); + + // Rendering to GL_ALPHA is not supported. f.glTexImage2D( GL_TEXTURE_2D, 0, - format, + GL_R8, kNoiseTextureSize, kNoiseTextureSize, 0, - format, + GL_RED, GL_UNSIGNED_BYTE, nullptr); + if (f.glGetError() != GL_NO_ERROR) { + // Direct3D 9 doesn't support GL_R8 textures. + f.glTexImage2D( + GL_TEXTURE_2D, + 0, + GL_RGB, + kNoiseTextureSize, + kNoiseTextureSize, + 0, + GL_RGB, + GL_UNSIGNED_BYTE, + nullptr); + } _noiseFramebuffer.ensureCreated(f); _noiseFramebuffer.bind(f, 0); @@ -1459,5 +1472,4 @@ void Viewport::RendererGL::validatePausedAnimation( st::fadeWrapDuration); } - } // namespace Calls::Group diff --git a/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp b/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp index 63ef43423..a39e5ca45 100644 --- a/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp +++ b/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "calls/group/ui/desktop_capture_choose_source.h" -#include "ui/widgets/window.h" +#include "ui/widgets/rp_window.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/labels.h" #include "ui/widgets/buttons.h" @@ -63,7 +63,6 @@ public: [[nodiscard]] rpl::producer<> activations() const; void setActive(bool active); - [[nodiscard]] bool isWindow() const; [[nodiscard]] QString deviceIdKey() const; [[nodiscard]] rpl::lifetime &lifetime(); @@ -105,7 +104,7 @@ private: std::unique_ptr> &Map(); const not_null _delegate; - const std::unique_ptr _window; + const std::unique_ptr _window; const std::unique_ptr _scroll; const not_null _inner; const not_null _bottom; @@ -171,10 +170,6 @@ rpl::producer<> Source::activations() const { return _activations.events(); } -bool Source::isWindow() const { - return _source.isWindow(); -} - QString Source::deviceIdKey() const { return QString::fromStdString(_source.deviceIdKey()); } @@ -255,7 +250,7 @@ rpl::lifetime &Source::lifetime() { ChooseSourceProcess::ChooseSourceProcess( not_null delegate) : _delegate(delegate) -, _window(std::make_unique()) +, _window(std::make_unique()) , _scroll(std::make_unique(_window->body())) , _inner(_scroll->setOwnedWidget(object_ptr(_scroll.get()))) , _bottom(CreateChild(_window->body().get())) @@ -428,7 +423,7 @@ void ChooseSourceProcess::setupPanel() { + rows * st::desktopCaptureSourceSize.height() + (rows - 1) * skips.height() + margins.bottom(); - _inner->resize(width, std::max(height, innerHeight)); + _inner->resize(width, innerHeight); }, _inner->lifetime()); if (const auto parent = _delegate->chooseSourceParent()) { diff --git a/Telegram/SourceFiles/chat_helpers/bot_command.cpp b/Telegram/SourceFiles/chat_helpers/bot_command.cpp new file mode 100644 index 000000000..64be1e4b8 --- /dev/null +++ b/Telegram/SourceFiles/chat_helpers/bot_command.cpp @@ -0,0 +1,49 @@ +/* +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 "chat_helpers/bot_command.h" + +#include "data/data_channel.h" +#include "data/data_chat.h" +#include "data/data_peer.h" +#include "data/data_user.h" +#include "data/data_session.h" +#include "history/history_item.h" + +namespace Bot { + +QString WrapCommandInChat( + not_null peer, + const QString &command, + const FullMsgId &context) { + auto result = command; + if (const auto item = peer->owner().message(context)) { + if (const auto user = item->fromOriginal()->asUser()) { + return WrapCommandInChat(peer, command, user); + } + } + return result; +} + +QString WrapCommandInChat( + not_null peer, + const QString &command, + not_null bot) { + if (!bot->isBot() || bot->username.isEmpty()) { + return command; + } + const auto botStatus = peer->isChat() + ? peer->asChat()->botStatus + : peer->isMegagroup() + ? peer->asChannel()->mgInfo->botStatus + : -1; + return ((command.indexOf('@') < 2) && (botStatus == 0 || botStatus == 2)) + ? command + '@' + bot->username + : command; +} + +} // namespace Bot diff --git a/Telegram/SourceFiles/chat_helpers/bot_command.h b/Telegram/SourceFiles/chat_helpers/bot_command.h new file mode 100644 index 000000000..79f79f9bb --- /dev/null +++ b/Telegram/SourceFiles/chat_helpers/bot_command.h @@ -0,0 +1,31 @@ +/* +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 + +class PeerData; +class UserData; + +namespace Bot { + +struct SendCommandRequest { + not_null peer; + QString command; + FullMsgId context; + int replyTo = 0; +}; + +[[nodiscard]] QString WrapCommandInChat( + not_null peer, + const QString &command, + const FullMsgId &context); +[[nodiscard]] QString WrapCommandInChat( + not_null peer, + const QString &command, + not_null bot); + +} // namespace Bot diff --git a/Telegram/SourceFiles/chat_helpers/bot_keyboard.cpp b/Telegram/SourceFiles/chat_helpers/bot_keyboard.cpp index 1a9d80fd8..5bd32636f 100644 --- a/Telegram/SourceFiles/chat_helpers/bot_keyboard.cpp +++ b/Telegram/SourceFiles/chat_helpers/bot_keyboard.cpp @@ -7,11 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "chat_helpers/bot_keyboard.h" +#include "core/click_handler_types.h" #include "history/history.h" #include "history/history_item_components.h" #include "data/data_user.h" #include "data/data_session.h" #include "main/main_session.h" +#include "window/window_session_controller.h" #include "ui/cached_round_corners.h" #include "facades.h" #include "styles/style_widgets.h" @@ -98,9 +100,11 @@ int Style::minButtonWidth(HistoryMessageMarkupButton::Type type) const { } // namespace -BotKeyboard::BotKeyboard(not_null session, QWidget *parent) +BotKeyboard::BotKeyboard( + not_null controller, + QWidget *parent) : TWidget(parent) -, _session(session) +, _controller(controller) , _st(&st::botKbButton) { setGeometry(0, 0, _st->margin, st::botKbScroll.deltat); _height = st::botKbScroll.deltat; @@ -137,7 +141,12 @@ void BotKeyboard::mouseReleaseEvent(QMouseEvent *e) { updateSelected(); if (ClickHandlerPtr activated = ClickHandler::unpressed()) { - ActivateClickHandler(window(), activated, e->button()); + ActivateClickHandler(window(), activated, { + e->button(), + QVariant::fromValue(ClickHandlerContext{ + .sessionWindow = base::make_weak(_controller.get()), + }) + }); } } @@ -151,32 +160,50 @@ void BotKeyboard::leaveEventHook(QEvent *e) { } bool BotKeyboard::moderateKeyActivate(int key) { - if (const auto item = _session->data().message(_wasForMsgId)) { + const auto &data = _controller->session().data(); + + const auto botCommand = [](int key) { + if (key == Qt::Key_Q || key == Qt::Key_6) { + return u"/translate"_q; + } else if (key == Qt::Key_W || key == Qt::Key_5) { + return u"/eng"_q; + } else if (key == Qt::Key_3) { + return u"/pattern"_q; + } else if (key == Qt::Key_4) { + return u"/abuse"_q; + } else if (key == Qt::Key_0 || key == Qt::Key_E || key == Qt::Key_9) { + return u"/undo"_q; + } else if (key == Qt::Key_Plus + || key == Qt::Key_QuoteLeft + || key == Qt::Key_7) { + return u"/next"_q; + } else if (key == Qt::Key_Period + || key == Qt::Key_S + || key == Qt::Key_8) { + return u"/stats"_q; + } + return QString(); + }; + + if (const auto item = data.message(_wasForMsgId)) { if (const auto markup = item->Get()) { if (key >= Qt::Key_1 && key <= Qt::Key_2) { const auto index = int(key - Qt::Key_1); if (!markup->rows.empty() && index >= 0 && index < int(markup->rows.front().size())) { - App::activateBotCommand(item, 0, index); + App::activateBotCommand(_controller, item, 0, index); return true; } } else if (const auto user = item->history()->peer->asUser()) { if (user->isBot() && item->from() == user) { - if (key == Qt::Key_Q || key == Qt::Key_6) { - App::sendBotCommand(user, user, qsl("/translate")); - } else if (key == Qt::Key_W || key == Qt::Key_5) { - App::sendBotCommand(user, user, qsl("/eng")); - } else if (key == Qt::Key_3) { - App::sendBotCommand(user, user, qsl("/pattern")); - } else if (key == Qt::Key_4) { - App::sendBotCommand(user, user, qsl("/abuse")); - } else if (key == Qt::Key_0 || key == Qt::Key_E || key == Qt::Key_9) { - App::sendBotCommand(user, user, qsl("/undo")); - } else if (key == Qt::Key_Plus || key == Qt::Key_QuoteLeft || key == Qt::Key_7) { - App::sendBotCommand(user, user, qsl("/next")); - } else if (key == Qt::Key_Period || key == Qt::Key_S || key == Qt::Key_8) { - App::sendBotCommand(user, user, qsl("/stats")); + const auto command = botCommand(key); + if (!command.isEmpty()) { + _sendCommandRequests.fire({ + .peer = user, + .command = command, + .context = item->fullId(), + }); } return true; } @@ -215,9 +242,9 @@ bool BotKeyboard::updateMarkup(HistoryItem *to, bool force) { _wasForMsgId = FullMsgId(to->channelId(), to->id); auto markupFlags = to->replyKeyboardFlags(); - _forceReply = markupFlags & MTPDreplyKeyboardMarkup_ClientFlag::f_force_reply; - _maximizeSize = !(markupFlags & MTPDreplyKeyboardMarkup::Flag::f_resize); - _singleUse = _forceReply || (markupFlags & MTPDreplyKeyboardMarkup::Flag::f_single_use); + _forceReply = markupFlags & ReplyMarkupFlag::ForceReply; + _maximizeSize = !(markupFlags & ReplyMarkupFlag::Resize); + _singleUse = _forceReply || (markupFlags & ReplyMarkupFlag::SingleUse); if (const auto markup = to->Get()) { _placeholder = markup->placeholder; @@ -317,4 +344,9 @@ void BotKeyboard::updateSelected() { } } +auto BotKeyboard::sendCommandRequests() const +-> rpl::producer { + return _sendCommandRequests.events(); +} + BotKeyboard::~BotKeyboard() = default; diff --git a/Telegram/SourceFiles/chat_helpers/bot_keyboard.h b/Telegram/SourceFiles/chat_helpers/bot_keyboard.h index 8a15d2c63..209623461 100644 --- a/Telegram/SourceFiles/chat_helpers/bot_keyboard.h +++ b/Telegram/SourceFiles/chat_helpers/bot_keyboard.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "ui/widgets/tooltip.h" +#include "chat_helpers/bot_command.h" class ReplyKeyboard; @@ -15,16 +16,18 @@ namespace style { struct BotKeyboardButton; } // namespace style -namespace Main { -class Session; -} // namespace Main +namespace Window { +class SessionController; +} // namespace Window class BotKeyboard : public TWidget , public Ui::AbstractTooltipShower , public ClickHandlerHost { public: - BotKeyboard(not_null session, QWidget *parent); + BotKeyboard( + not_null controller, + QWidget *parent); bool moderateKeyActivate(int index); @@ -60,6 +63,8 @@ public: void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; + rpl::producer sendCommandRequests() const; + ~BotKeyboard(); protected: @@ -78,7 +83,7 @@ private: void updateStyle(int newWidth); void clearSelection(); - const not_null _session; + const not_null _controller; FullMsgId _wasForMsgId; QString _placeholder; int _height = 0; @@ -90,6 +95,8 @@ private: QPoint _lastMousePos; std::unique_ptr _impl; + rpl::event_stream _sendCommandRequests; + const style::BotKeyboardButton *_st = nullptr; }; diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp index c7da2d589..5dab2b723 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/ui_utility.h" #include "ui/cached_round_corners.h" #include "lang/lang_keys.h" +#include "layout/layout_position.h" #include "emoji_suggestions_data.h" #include "emoji_suggestions_helper.h" #include "main/main_session.h" @@ -553,7 +554,12 @@ void EmojiListWidget::paintEvent(QPaintEvent *e) { auto index = i * _columnCount + j; if (index >= info.count) break; - auto selected = (!_picker->isHidden() && info.section * MatrixRowShift + index == _pickerSel) || (info.section * MatrixRowShift + index == _selected); + const auto selectedIndex = Layout::PositionToIndex( + info.section, + index); + auto selected = (selectedIndex == _selected) + || (!_picker->isHidden() + && selectedIndex == _pickerSel); auto w = QPoint(_rowsLeft + j * _singleSize.width(), info.rowsTop + i * _singleSize.height()); if (selected) { @@ -593,8 +599,7 @@ void EmojiListWidget::mousePressEvent(QMouseEvent *e) { _pressedSel = _selected; if (_selected >= 0) { - auto section = (_selected / MatrixRowShift); - auto sel = _selected % MatrixRowShift; + const auto &[section, sel] = Layout::IndexToPosition(_selected); if (section < kEmojiSectionCount && sel < _emoji[section].size() && _emoji[section][sel]->hasVariants()) { @@ -619,8 +624,7 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) { if (_picker->rect().contains(_picker->mapFromGlobal(_lastMousePos))) { return _picker->handleMouseRelease(QCursor::pos()); } else if (_pickerSel >= 0) { - auto section = (_pickerSel / MatrixRowShift); - auto sel = _pickerSel % MatrixRowShift; + const auto &[section, sel] = Layout::IndexToPosition(_pickerSel); if (section < kEmojiSectionCount && sel < _emoji[section].size() && _emoji[section][sel]->hasVariants()) { @@ -642,12 +646,11 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) { if (_selected < 0 || _selected != pressed) return; - if (_selected >= kEmojiSectionCount * MatrixRowShift) { + if (_selected >= Layout::PositionToIndex(kEmojiSectionCount, 0)) { return; } - auto section = (_selected / MatrixRowShift); - auto sel = _selected % MatrixRowShift; + const auto &[section, sel] = Layout::IndexToPosition(_selected); if (sel < _emoji[section].size()) { auto emoji = _emoji[section][sel]; if (emoji->hasVariants() && !_picker->isHidden()) return; @@ -664,8 +667,7 @@ void EmojiListWidget::selectEmoji(EmojiPtr emoji) { void EmojiListWidget::showPicker() { if (_pickerSel < 0) return; - auto section = (_pickerSel / MatrixRowShift); - auto sel = _pickerSel % MatrixRowShift; + const auto &[section, sel] = Layout::IndexToPosition(_pickerSel); if (section < kEmojiSectionCount && sel < _emoji[section].size() && _emoji[section][sel]->hasVariants()) { _picker->showEmoji(_emoji[section][sel]); @@ -708,8 +710,7 @@ void EmojiListWidget::colorChosen(EmojiPtr emoji) { Core::App().settings().saveEmojiVariant(emoji); } if (_pickerSel >= 0) { - auto section = (_pickerSel / MatrixRowShift); - auto sel = _pickerSel % MatrixRowShift; + const auto &[section, sel] = Layout::IndexToPosition(_pickerSel); if (section >= 0 && section < kEmojiSectionCount) { _emoji[section][sel] = emoji; rtlupdate(emojiRect(section, sel)); @@ -756,8 +757,7 @@ Ui::Emoji::Section EmojiListWidget::currentSection(int yOffset) const { QString EmojiListWidget::tooltipText() const { const auto &replacements = Ui::Emoji::internal::GetAllReplacements(); - const auto section = (_selected / MatrixRowShift); - const auto sel = _selected % MatrixRowShift; + const auto &[section, sel] = Layout::IndexToPosition(_selected); if (_selected >= 0 && section < kEmojiSectionCount && sel < _emoji[section].size()) { const auto emoji = _emoji[section][sel]->original(); const auto text = emoji->text(); @@ -822,7 +822,7 @@ void EmojiListWidget::updateSelected() { if (newSelected >= _emoji[info.section].size()) { newSelected = -1; } else { - newSelected += info.section * MatrixRowShift; + newSelected += Layout::PositionToIndex(info.section, 0); } } } @@ -835,8 +835,11 @@ void EmojiListWidget::setSelected(int newSelected) { return; } auto updateSelected = [this]() { - if (_selected < 0) return; - rtlupdate(emojiRect(_selected / MatrixRowShift, _selected % MatrixRowShift)); + if (_selected < 0) { + return; + } + const auto &[section, sel] = Layout::IndexToPosition(_selected); + rtlupdate(emojiRect(section, sel)); }; updateSelected(); _selected = newSelected; diff --git a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp index c56bc1b08..b68c8ad5b 100644 --- a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_document_media.h" #include "data/stickers/data_stickers.h" #include "chat_helpers/send_context_menu.h" // SendMenu::FillSendMenu +#include "core/click_handler_types.h" #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" #include "ui/widgets/popup_menu.h" @@ -27,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "inline_bots/inline_bot_result.h" #include "storage/localstorage.h" #include "lang/lang_keys.h" +#include "layout/layout_position.h" #include "mainwindow.h" #include "main/main_session.h" #include "window/window_session_controller.h" @@ -167,6 +169,7 @@ GifsListWidget::GifsListWidget( , _api(&controller->session().mtp()) , _section(Section::Gifs) , _updateInlineItems([=] { updateInlineItems(); }) +, _mosaic(st::emojiPanWidth - st::inlineResultsLeft) , _previewTimer([=] { showPreview(); }) { setMouseTracking(true); setAttribute(Qt::WA_OpaquePaintEvent); @@ -195,6 +198,16 @@ GifsListWidget::GifsListWidget( update(); } }, lifetime()); + + sizeValue( + ) | rpl::start_with_next([=](const QSize &s) { + _mosaic.setFullWidth(s.width()); + }, lifetime()); + + _mosaic.setOffset( + st::inlineResultsLeft - st::roundRadiusSmall, + st::stickerPanPadding); + _mosaic.setRightSkip(st::inlineResultsSkip); } rpl::producer GifsListWidget::fileChosen() const { @@ -238,12 +251,7 @@ void GifsListWidget::checkLoadMore() { } int GifsListWidget::countDesiredHeight(int newWidth) { - auto result = st::stickerPanPadding; - for (int i = 0, l = _rows.count(); i < l; ++i) { - layoutInlineRow(_rows[i], newWidth); - result += _rows[i].height; - } - return result + st::stickerPanPadding; + return _mosaic.countDesiredHeight(newWidth) + st::stickerPanPadding * 2; } GifsListWidget::~GifsListWidget() { @@ -321,7 +329,7 @@ void GifsListWidget::paintEvent(QPaintEvent *e) { } void GifsListWidget::paintInlineItems(Painter &p, QRect clip) { - if (_rows.isEmpty()) { + if (_mosaic.empty()) { p.setFont(st::normalFont); p.setPen(st::noContactsColor); auto text = _inlineQuery.isEmpty() @@ -330,38 +338,20 @@ void GifsListWidget::paintInlineItems(Painter &p, QRect clip) { p.drawText(QRect(0, 0, width(), (height() / 3) * 2 + st::normalFont->height), text, style::al_center); return; } - auto gifPaused = controller()->isGifPausedAtLeastFor(Window::GifPauseReason::SavedGifs); - InlineBots::Layout::PaintContext context(crl::now(), false, gifPaused, false); + const auto gifPaused = controller()->isGifPausedAtLeastFor( + Window::GifPauseReason::SavedGifs); + using namespace InlineBots::Layout; + PaintContext context(crl::now(), false, gifPaused, false); - auto top = st::stickerPanPadding; - auto fromx = rtl() ? (width() - clip.x() - clip.width()) : clip.x(); - auto tox = rtl() ? (width() - clip.x()) : (clip.x() + clip.width()); - for (auto row = 0, rows = _rows.size(); row != rows; ++row) { - auto &inlineRow = _rows[row]; - if (top >= clip.top() + clip.height()) { - break; - } - if (top + inlineRow.height > clip.top()) { - auto left = st::inlineResultsLeft - st::roundRadiusSmall; - if (row == rows - 1) context.lastRow = true; - for (int col = 0, cols = inlineRow.items.size(); col < cols; ++col) { - if (left >= tox) break; - - auto item = inlineRow.items.at(col); - auto w = item->width(); - if (left + w > fromx) { - p.translate(left, top); - item->paint(p, clip.translated(-left, -top), &context); - p.translate(-left, -top); - } - left += w; - if (item->hasRightSkip()) { - left += st::inlineResultsSkip; - } - } - } - top += inlineRow.height; - } + auto paintItem = [&](not_null item, QPoint point) { + p.translate(point.x(), point.y()); + item->paint( + p, + clip.translated(-point), + &context); + p.translate(-point.x(), -point.y()); + }; + _mosaic.paint(std::move(paintItem), clip); } void GifsListWidget::mousePressEvent(QMouseEvent *e) { @@ -382,11 +372,9 @@ void GifsListWidget::fillContextMenu( if (_selected < 0 || _pressed >= 0) { return; } - const auto row = _selected / MatrixRowShift; - const auto column = _selected % MatrixRowShift; - const auto send = [=](Api::SendOptions options) { - selectInlineResult(row, column, options, true); + const auto send = [=, selected = _selected](Api::SendOptions options) { + selectInlineResult(selected, options, true); }; SendMenu::FillSendMenu( menu, @@ -394,8 +382,7 @@ void GifsListWidget::fillContextMenu( SendMenu::DefaultSilentCallback(send), SendMenu::DefaultScheduleCallback(this, type, send)); - if (!(row >= _rows.size() || column >= _rows[row].items.size())) { - const auto item = _rows[row].items[column]; + if (const auto item = _mosaic.maybeItemAt(_selected)) { const auto document = item->getDocument() ? item->getDocument() // Saved GIF. : item->getPreviewDocument(); // Searched GIF. @@ -427,30 +414,29 @@ void GifsListWidget::mouseReleaseEvent(QMouseEvent *e) { } if (dynamic_cast(activated.get())) { - int row = _selected / MatrixRowShift, column = _selected % MatrixRowShift; - selectInlineResult(row, column); + selectInlineResult(_selected, {}); } else { - ActivateClickHandler(window(), activated, e->button()); + ActivateClickHandler(window(), activated, { + e->button(), + QVariant::fromValue(ClickHandlerContext{ + .sessionWindow = base::make_weak(controller().get()), + }) + }); } } -void GifsListWidget::selectInlineResult(int row, int column) { - selectInlineResult(row, column, Api::SendOptions()); -} - void GifsListWidget::selectInlineResult( - int row, - int column, + int index, Api::SendOptions options, bool forceSend) { - if (row >= _rows.size() || column >= _rows[row].items.size()) { + const auto item = _mosaic.maybeItemAt(index); + if (!item) { return; } forceSend |= (QGuiApplication::keyboardModifiers() == Qt::ControlModifier); options.hideVia = true; - auto item = _rows[row].items[column]; if (const auto photo = item->getPhoto()) { using Data::PhotoSize; const auto media = photo->activeMediaView(); @@ -506,9 +492,7 @@ void GifsListWidget::enterFromChildEvent(QEvent *e, QWidget *child) { void GifsListWidget::clearSelection() { if (_selected >= 0) { - int srow = _selected / MatrixRowShift, scol = _selected % MatrixRowShift; - Assert(srow >= 0 && srow < _rows.size() && scol >= 0 && scol < _rows[srow].items.size()); - ClickHandler::clearActive(_rows[srow].items[scol]); + ClickHandler::clearActive(_mosaic.itemAt(_selected)); setCursor(style::cur_default); } _selected = _pressed = -1; @@ -539,65 +523,21 @@ void GifsListWidget::clearHeavyData() { } } -bool GifsListWidget::inlineRowsAddItem(DocumentData *savedGif, InlineResult *result, Row &row, int32 &sumWidth) { - LayoutItem *layout = nullptr; - if (savedGif) { - layout = layoutPrepareSavedGif(savedGif, (_rows.size() * MatrixRowShift) + row.items.size()); - } else if (result) { - layout = layoutPrepareInlineResult(result, (_rows.size() * MatrixRowShift) + row.items.size()); - } - if (!layout) return false; - - layout->preload(); - if (inlineRowFinalize(row, sumWidth, layout->isFullLine())) { - layout->setPosition(_rows.size() * MatrixRowShift); - } - - sumWidth += layout->maxWidth(); - if (!row.items.isEmpty() && row.items.back()->hasRightSkip()) { - sumWidth += st::inlineResultsSkip; - } - - row.items.push_back(layout); - return true; -} - -bool GifsListWidget::inlineRowFinalize(Row &row, int32 &sumWidth, bool force) { - if (row.items.isEmpty()) return false; - - auto full = (row.items.size() >= kInlineItemsMaxPerRow); - - // Currently use the same GIFs layout for all widget sizes. -// auto big = (sumWidth >= st::roundRadiusSmall + width() - st::inlineResultsLeft); - auto big = (sumWidth >= st::emojiPanWidth - st::inlineResultsLeft); - if (full || big || force) { - row.maxWidth = (full || big) ? sumWidth : 0; - layoutInlineRow( - row, - width()); - _rows.push_back(row); - row = Row(); - row.items.reserve(kInlineItemsMaxPerRow); - sumWidth = 0; - return true; - } - return false; -} - void GifsListWidget::refreshSavedGifs() { if (_section == Section::Gifs) { clearInlineRows(false); const auto &saved = controller()->session().data().stickers().savedGifs(); if (!saved.isEmpty()) { - _rows.reserve(saved.size()); - auto row = Row(); - row.items.reserve(kInlineItemsMaxPerRow); - auto sumWidth = 0; - for (const auto &gif : saved) { - inlineRowsAddItem(gif, 0, row, sumWidth); - } - inlineRowFinalize(row, sumWidth, true); + const auto layouts = ranges::views::all( + saved + ) | ranges::views::transform([&](not_null gif) { + return layoutPrepareSavedGif(gif); + }) | ranges::views::filter([](const LayoutItem *item) { + return item != nullptr; + }) | ranges::to>>; + + _mosaic.addItems(layouts); } deleteUnusedGifLayouts(); @@ -617,18 +557,12 @@ void GifsListWidget::clearInlineRows(bool resultsDeleted) { _selected = _pressed = -1; } else { clearSelection(); - for (const auto &row : std::as_const(_rows)) { - for (const auto &item : std::as_const(row.items)) { - item->setPosition(-1); - } - } } - _rows.clear(); + _mosaic.clearRows(resultsDeleted); } GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareSavedGif( - not_null document, - int32 position) { + not_null document) { auto it = _gifLayouts.find(document); if (it == _gifLayouts.cend()) { if (auto layout = LayoutItem::createLayoutGif(this, document)) { @@ -640,13 +574,11 @@ GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareSavedGif( } if (!it->second->maxWidth()) return nullptr; - it->second->setPosition(position); return it->second.get(); } GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareInlineResult( - not_null result, - int32 position) { + not_null result) { auto it = _inlineLayouts.find(result); if (it == _inlineLayouts.cend()) { if (auto layout = LayoutItem::createLayout( @@ -661,12 +593,11 @@ GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareInlineResult( } if (!it->second->maxWidth()) return nullptr; - it->second->setPosition(position); return it->second.get(); } void GifsListWidget::deleteUnusedGifLayouts() { - if (_rows.isEmpty() || _section != Section::Gifs) { // delete all + if (_mosaic.empty() || _section != Section::Gifs) { // delete all _gifLayouts.clear(); } else { for (auto i = _gifLayouts.begin(); i != _gifLayouts.cend();) { @@ -680,7 +611,7 @@ void GifsListWidget::deleteUnusedGifLayouts() { } void GifsListWidget::deleteUnusedInlineLayouts() { - if (_rows.isEmpty() || _section == Section::Gifs) { // delete all + if (_mosaic.empty() || _section == Section::Gifs) { // delete all _inlineLayouts.clear(); } else { for (auto i = _inlineLayouts.begin(); i != _inlineLayouts.cend();) { @@ -693,49 +624,10 @@ void GifsListWidget::deleteUnusedInlineLayouts() { } } -void GifsListWidget::layoutInlineRow(Row &row, int fullWidth) { - auto count = int(row.items.size()); - Assert(count <= kInlineItemsMaxPerRow); - - // enumerate items in the order of growing maxWidth() - // for that sort item indices by maxWidth() - int indices[kInlineItemsMaxPerRow]; - for (auto i = 0; i != count; ++i) { - indices[i] = i; - } - std::sort(indices, indices + count, [&](int a, int b) { - return row.items[a]->maxWidth() - < row.items[b]->maxWidth(); - }); - - auto desiredWidth = row.maxWidth; - row.height = 0; - int availw = fullWidth - (st::inlineResultsLeft - st::roundRadiusSmall); - for (int i = 0; i < count; ++i) { - const auto index = indices[i]; - const auto &item = row.items[index]; - const auto w = desiredWidth - ? (item->maxWidth() * availw / desiredWidth) - : item->maxWidth(); - auto actualw = qMax(w, st::inlineResultsMinWidth); - row.height = qMax(row.height, item->resizeGetHeight(actualw)); - if (desiredWidth) { - availw -= actualw; - desiredWidth -= row.items[index]->maxWidth(); - if (index > 0 && row.items[index - 1]->hasRightSkip()) { - availw -= st::inlineResultsSkip; - desiredWidth -= st::inlineResultsSkip; - } - } - } -} - void GifsListWidget::preloadImages() { - for (auto row = 0, rows = _rows.size(); row != rows; ++row) { - for (auto col = 0, cols = _rows[row].items.size(); col != cols; ++col) { - _rows[row].items[col]->preload(); - } - } + _mosaic.forEach([](not_null item) { + item->preload(); + }); } void GifsListWidget::switchToSavedGifs() { @@ -758,20 +650,23 @@ int GifsListWidget::refreshInlineRows(const InlineCacheEntry *entry, bool result clearSelection(); _section = Section::Inlines; - auto count = int(entry->results.size()); - auto from = validateExistingInlineRows(entry->results); + const auto count = int(entry->results.size()); + const auto from = validateExistingInlineRows(entry->results); auto added = 0; if (count) { - _rows.reserve(count); - auto row = Row(); - row.items.reserve(kInlineItemsMaxPerRow); - auto sumWidth = 0; - for (auto i = from; i != count; ++i) { - if (inlineRowsAddItem(0, entry->results[i].get(), row, sumWidth)) { - ++added; - } - } - inlineRowFinalize(row, sumWidth, true); + const auto resultLayouts = entry->results | ranges::views::slice( + from, + count + ) | ranges::views::transform([&]( + const std::unique_ptr &r) { + return layoutPrepareInlineResult(r.get()); + }) | ranges::views::filter([](const LayoutItem *item) { + return item != nullptr; + }) | ranges::to>>; + + _mosaic.addItems(resultLayouts); + added = resultLayouts.size(); + preloadImages(); } resizeToWidth(width()); @@ -784,61 +679,15 @@ int GifsListWidget::refreshInlineRows(const InlineCacheEntry *entry, bool result } int GifsListWidget::validateExistingInlineRows(const InlineResults &results) { - int count = results.size(), until = 0, untilrow = 0, untilcol = 0; - for (; until < count;) { - if (untilrow >= _rows.size() || _rows[untilrow].items[untilcol]->getResult() != results[until].get()) { - break; - } - ++until; - if (++untilcol == _rows[untilrow].items.size()) { - ++untilrow; - untilcol = 0; - } - } - if (until == count) { // all items are layed out - if (untilrow == _rows.size()) { // nothing changed - return until; - } + const auto until = _mosaic.validateExistingRows([&]( + not_null item, + int untilIndex) { + return item->getResult() != results[untilIndex].get(); + }, results.size()); - for (int i = untilrow, l = _rows.size(), skip = untilcol; i < l; ++i) { - for (int j = 0, s = _rows[i].items.size(); j < s; ++j) { - if (skip) { - --skip; - } else { - _rows[i].items[j]->setPosition(-1); - } - } - } - if (!untilcol) { // all good rows are filled - _rows.resize(untilrow); - return until; - } - _rows.resize(untilrow + 1); - _rows[untilrow].items.resize(untilcol); - _rows[untilrow].maxWidth = std::accumulate( - _rows[untilrow].items.begin(), - _rows[untilrow].items.end(), - 0, - [](int w, auto &row) { return w + row->maxWidth(); }); - layoutInlineRow(_rows[untilrow], width()); - return until; - } - if (untilrow && !untilcol) { // remove last row, maybe it is not full - --untilrow; - untilcol = _rows[untilrow].items.size(); - } - until -= untilcol; - - for (int i = untilrow, l = _rows.size(); i < l; ++i) { - for (int j = 0, s = _rows[i].items.size(); j < s; ++j) { - _rows[i].items[j]->setPosition(-1); - } - } - _rows.resize(untilrow); - - if (_rows.isEmpty()) { + if (_mosaic.empty()) { _inlineWithThumb = false; - for (int i = until; i < count; ++i) { + for (int i = until; i < results.size(); ++i) { if (results.at(i)->hasThumbDisplay()) { _inlineWithThumb = true; break; @@ -853,9 +702,8 @@ void GifsListWidget::inlineItemLayoutChanged(const InlineBots::Layout::ItemBase return; } - int row = _selected / MatrixRowShift, col = _selected % MatrixRowShift; - if (row < _rows.size() && col < _rows[row].items.size()) { - if (layout == _rows[row].items[col]) { + if (const auto item = _mosaic.maybeItemAt(_selected)) { + if (layout == item) { updateSelected(); } } @@ -876,16 +724,14 @@ bool GifsListWidget::inlineItemVisible(const InlineBots::Layout::ItemBase *layou return false; } - auto row = position / MatrixRowShift; - auto col = position % MatrixRowShift; - Assert((row < _rows.size()) && (col < _rows[row].items.size())); - + const auto &[row, column] = Layout::IndexToPosition(position); auto top = 0; for (auto i = 0; i != row; ++i) { - top += _rows[i].height; + top += _mosaic.rowHeightAt(i); } - return (top < getVisibleBottom()) && (top + _rows[row].items[col]->height() > getVisibleTop()); + return (top < getVisibleBottom()) + && (top + _mosaic.itemAt(row, column)->height() > getVisibleTop()); } Data::FileOrigin GifsListWidget::inlineItemFileOrigin() { @@ -1028,77 +874,39 @@ void GifsListWidget::updateSelected() { return; } - auto p = mapFromGlobal(_lastMousePos); - int sx = (rtl() ? width() - p.x() : p.x()) - (st::inlineResultsLeft - st::roundRadiusSmall); - int sy = p.y() - st::stickerPanPadding; - int row = -1, col = -1, sel = -1; - ClickHandlerPtr lnk; - ClickHandlerHost *lnkhost = nullptr; - if (sy >= 0) { - row = 0; - for (int rows = _rows.size(); row < rows; ++row) { - if (sy < _rows[row].height) { - break; - } - sy -= _rows[row].height; + const auto p = mapFromGlobal(_lastMousePos); + const auto sx = rtl() ? (width() - p.x()) : p.x(); + const auto sy = p.y(); + const auto &[index, exact, relative] = _mosaic.findByPoint({ sx, sy }); + const auto selected = exact ? index : -1; + const auto item = exact ? _mosaic.itemAt(selected).get() : nullptr; + const auto link = exact ? item->getState(relative, {}).link : nullptr; + + if (_selected != selected) { + if (const auto s = _mosaic.maybeItemAt(_selected)) { + s->update(); } - } - if (sx >= 0 && row >= 0 && row < _rows.size()) { - auto &inlineItems = _rows[row].items; - col = 0; - for (int cols = inlineItems.size(); col < cols; ++col) { - int width = inlineItems[col]->width(); - if (sx < width) { - break; - } - sx -= width; - if (inlineItems[col]->hasRightSkip()) { - sx -= st::inlineResultsSkip; - } - } - if (col < inlineItems.size()) { - sel = row * MatrixRowShift + col; - auto result = inlineItems[col]->getState( - QPoint(sx, sy), - HistoryView::StateRequest()); - lnk = result.link; - lnkhost = inlineItems[col]; - } else { - row = col = -1; - } - } else { - row = col = -1; - } - int srow = (_selected >= 0) ? (_selected / MatrixRowShift) : -1; - int scol = (_selected >= 0) ? (_selected % MatrixRowShift) : -1; - if (_selected != sel) { - if (srow >= 0 && scol >= 0) { - Assert(srow >= 0 && srow < _rows.size() && scol >= 0 && scol < _rows[srow].items.size()); - _rows[srow].items[scol]->update(); - } - _selected = sel; - if (row >= 0 && col >= 0) { - Assert(row >= 0 && row < _rows.size() && col >= 0 && col < _rows[row].items.size()); - _rows[row].items[col]->update(); + _selected = selected; + if (item) { + item->update(); } if (_previewShown && _selected >= 0 && _pressed != _selected) { _pressed = _selected; - if (row >= 0 && col >= 0) { - auto layout = _rows[row].items[col]; - if (const auto previewDocument = layout->getPreviewDocument()) { + if (item) { + if (const auto preview = item->getPreviewDocument()) { controller()->widget()->showMediaPreview( Data::FileOriginSavedGifs(), - previewDocument); - } else if (const auto previewPhoto = layout->getPreviewPhoto()) { + preview); + } else if (const auto preview = item->getPreviewPhoto()) { controller()->widget()->showMediaPreview( Data::FileOrigin(), - previewPhoto); + preview); } } } } - if (ClickHandler::setActive(lnk, lnkhost)) { - setCursor(lnk ? style::cur_pointer : style::cur_default); + if (ClickHandler::setActive(link, item)) { + setCursor(link ? style::cur_pointer : style::cur_default); } } @@ -1106,9 +914,7 @@ void GifsListWidget::showPreview() { if (_pressed < 0) { return; } - int row = _pressed / MatrixRowShift, col = _pressed % MatrixRowShift; - if (row < _rows.size() && col < _rows[row].items.size()) { - auto layout = _rows[row].items[col]; + if (const auto layout = _mosaic.maybeItemAt(_pressed)) { if (const auto previewDocument = layout->getPreviewDocument()) { _previewShown = controller()->widget()->showMediaPreview( Data::FileOriginSavedGifs(), diff --git a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.h b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.h index 279934ea6..fe83d2630 100644 --- a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/tabbed_selector.h" #include "base/timer.h" #include "inline_bots/inline_bot_layout_item.h" +#include "layout/layout_mosaic.h" #include "app.h" #include @@ -142,46 +143,32 @@ private: base::Timer _updateInlineItems; bool _inlineWithThumb = false; - struct Row { - int maxWidth = 0; - int height = 0; - QVector items; - }; - QVector _rows; void clearInlineRows(bool resultsDeleted); std::map< not_null, std::unique_ptr> _gifLayouts; - LayoutItem *layoutPrepareSavedGif( - not_null document, - int32 position); + LayoutItem *layoutPrepareSavedGif(not_null document); std::map< not_null, std::unique_ptr> _inlineLayouts; - LayoutItem *layoutPrepareInlineResult( - not_null result, - int32 position); + LayoutItem *layoutPrepareInlineResult(not_null result); - bool inlineRowsAddItem(DocumentData *savedGif, InlineResult *result, Row &row, int32 &sumWidth); - bool inlineRowFinalize(Row &row, int32 &sumWidth, bool force = false); - - void layoutInlineRow(Row &row, int fullWidth); void deleteUnusedGifLayouts(); void deleteUnusedInlineLayouts(); int validateExistingInlineRows(const InlineResults &results); - void selectInlineResult(int row, int column); void selectInlineResult( - int row, - int column, + int index, Api::SendOptions options, bool forceSend = false); Footer *_footer = nullptr; + Mosaic::Layout::MosaicLayout _mosaic; + int _selected = -1; int _pressed = -1; QPoint _lastMousePos; diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp index 633aa4fd2..8516fe027 100644 --- a/Telegram/SourceFiles/core/application.cpp +++ b/Telegram/SourceFiles/core/application.cpp @@ -295,7 +295,7 @@ void Application::run() { const auto currentGeometry = _window->widget()->geometry(); _mediaView = std::make_unique(); - _window->widget()->setGeometry(currentGeometry); + _window->widget()->Ui::RpWidget::setGeometry(currentGeometry); DEBUG_LOG(("Application Info: showing.")); _window->finishFirstShow(); @@ -418,15 +418,6 @@ bool Application::hideMediaView() { return false; } -PeerData *Application::ui_getPeerForMouseAction() { - if (_mediaView && !_mediaView->isHidden()) { - return _mediaView->ui_getPeerForMouseAction(); - } else if (const auto m = App::main()) { // multi good - return m->ui_getPeerForMouseAction(); - } - return nullptr; -} - bool Application::eventFilter(QObject *object, QEvent *e) { switch (e->type()) { case QEvent::KeyPress: diff --git a/Telegram/SourceFiles/core/application.h b/Telegram/SourceFiles/core/application.h index 939b2d0e4..c3eca0c8c 100644 --- a/Telegram/SourceFiles/core/application.h +++ b/Telegram/SourceFiles/core/application.h @@ -143,7 +143,6 @@ public: // Media view interface. void checkMediaViewActivation(); bool hideMediaView(); - [[nodiscard]] PeerData *ui_getPeerForMouseAction(); [[nodiscard]] QPoint getPointForCallPanelCenter() const; [[nodiscard]] QImage logo(int variant = 0) const { diff --git a/Telegram/SourceFiles/core/changelogs.cpp b/Telegram/SourceFiles/core/changelogs.cpp index 2eac72601..5dafd0c44 100644 --- a/Telegram/SourceFiles/core/changelogs.cpp +++ b/Telegram/SourceFiles/core/changelogs.cpp @@ -25,78 +25,6 @@ namespace { std::map BetaLogs() { return { - { - 2005002, - "- Fix possible crash in video calls.\n" - - "- Fix possible crash in connecting to voice chats.\n" - - "- Use different audio module code on Windows in calls.\n" - }, - { - 2005003, - "- Allow using mouse buttons in Push-to-Talk shortcut.\n" - - "- Fix blurred thumbnails in Shared Links section.\n" - }, - { - 2005004, - "- Implement new audio module code for calls and voice chats.\n" - - "- Allow retracting votes from polls in comments to channel posts.\n" - - "- Show small voice chat button for empty voice chats.\n" - - "- Fix media viewer updating when screen resolution is changed.\n" - }, - { - 2005005, - "- Fix recording of audio in voice chats.\n" - - "- Fix media viewer zoom and crashing.\n" - }, - { - 2005006, - "- Press Up arrow to edit your last sent comment.\n" - - "- Add more information to date tooltips " - "in Recent Actions and channel comments.\n" - - "- Bug and crash fixes.\n" - }, - { - 2006002, - "- Fix text disappearing because of cloud drafts sync.\n" - }, - { - 2006003, - "- Fix audio device selection in voice chats.\n" - - "- Fix blinking self profile photo " - "in case the profile photo privacy is used.\n" - - "- Fix voice chat admin menu on macOS.\n" - }, - { - 2006004, - "- Fix freeze in voice chats.\n" - - "- Make default interface scale 110% on macOS Retina screens.\n" - }, - { - 2006005, - "- Improvements and fixes in new voice chat features.\n" - }, - { - 2006007, - "- Improve voice chat participants list updating.\n" - }, - { - 2006008, - "- Fix connecting and getting allowed to speak on voice chats.\n" - - "- MPRIS support on Linux.\n" - }, { 2007005, "- Add \"Voice chats\" filter in \"Recent actions\" for channels.\n" @@ -150,6 +78,20 @@ std::map BetaLogs() { "- Use GTK from a child process (Linux).\n" }, + { + 2008012, + "- Change the default chat background.\n" + + "- Add GIFs overview section in chats.\n" + + "- Add a simple new messages animation.\n" + + "- Use modern Telegram application icon.\n" + + "- Use Direct3D 11 by default on Windows.\n" + + "- Fix Direct3D acceleration on basic Windows 7 setup.\n" + }, }; }; diff --git a/Telegram/SourceFiles/core/click_handler_types.cpp b/Telegram/SourceFiles/core/click_handler_types.cpp index a02326148..88a555843 100644 --- a/Telegram/SourceFiles/core/click_handler_types.cpp +++ b/Telegram/SourceFiles/core/click_handler_types.cpp @@ -11,11 +11,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "core/local_url_handlers.h" #include "mainwidget.h" +#include "mainwindow.h" #include "main/main_session.h" #include "boxes/confirm_box.h" #include "kotato/boxes/kotato_confirm_box.h" #include "base/qthelp_regex.h" #include "storage/storage_account.h" +#include "history/history.h" #include "history/view/history_view_element.h" #include "history/history_item.h" #include "data/data_user.h" @@ -26,6 +28,36 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include +namespace { + +void SearchByHashtag(ClickContext context, const QString &tag) { + const auto my = context.other.value(); + const auto controller = my.sessionWindow.get(); + if (!controller) { + return; + } + if (controller->openedFolder().current()) { + controller->closeFolder(); + } + + controller->widget()->ui_hideSettingsAndLayer(anim::type::normal); + Core::App().hideMediaView(); + + auto &data = controller->session().data(); + const auto inPeer = my.peer + ? my.peer + : my.itemId + ? data.message(my.itemId)->history()->peer.get() + : nullptr; + controller->content()->searchMessages( + tag + ' ', + (inPeer && !inPeer->isUser()) + ? data.history(inPeer).get() + : Dialogs::Key()); +} + +} // namespace + bool UrlRequiresConfirmation(const QUrl &url) { using namespace qthelp; @@ -160,7 +192,7 @@ QString HashtagClickHandler::copyToClipboardContextItemText() const { void HashtagClickHandler::onClick(ClickContext context) const { const auto button = context.button; if (button == Qt::LeftButton || button == Qt::MiddleButton) { - App::searchByHashtag(_tag, Ui::getPeerForMouseAction()); + SearchByHashtag(context, _tag); } } @@ -175,7 +207,7 @@ QString CashtagClickHandler::copyToClipboardContextItemText() const { void CashtagClickHandler::onClick(ClickContext context) const { const auto button = context.button; if (button == Qt::LeftButton || button == Qt::MiddleButton) { - App::searchByHashtag(_tag, Ui::getPeerForMouseAction()); + SearchByHashtag(context, _tag); } } @@ -185,24 +217,31 @@ auto CashtagClickHandler::getTextEntity() const -> TextEntity { void BotCommandClickHandler::onClick(ClickContext context) const { const auto button = context.button; - if (button == Qt::LeftButton || button == Qt::MiddleButton) { - const auto my = context.other.value(); - if (const auto delegate = my.elementDelegate ? my.elementDelegate() : nullptr) { - delegate->elementSendBotCommand(_cmd, my.itemId); + if (button != Qt::LeftButton && button != Qt::MiddleButton) { + return; + } + const auto my = context.other.value(); + if (const auto delegate = my.elementDelegate ? my.elementDelegate() : nullptr) { + delegate->elementSendBotCommand(_cmd, my.itemId); + } else if (const auto controller = my.sessionWindow.get()) { + auto &data = controller->session().data(); + const auto peer = my.peer + ? my.peer + : my.itemId + ? data.message(my.itemId)->history()->peer.get() + : nullptr; + // Can't find context. + if (!peer) { return; - } else if (auto peer = Ui::getPeerForMouseAction()) { // old way - auto bot = peer->isUser() ? peer->asUser() : nullptr; - if (!bot) { - if (const auto view = App::hoveredLinkItem()) { - // may return nullptr - bot = view->data()->fromOriginal()->asUser(); - } - } - Ui::showPeerHistory(peer, ShowAtTheEndMsgId); - App::sendBotCommand(peer, bot, _cmd); - } else { - App::insertBotCommand(_cmd); } + controller->widget()->ui_hideSettingsAndLayer(anim::type::normal); + Core::App().hideMediaView(); + controller->content()->sendBotCommand({ + .peer = peer, + .command = _cmd, + .context = my.itemId, + .replyTo = 0, + }); } } diff --git a/Telegram/SourceFiles/core/click_handler_types.h b/Telegram/SourceFiles/core/click_handler_types.h index 74ff76e51..a4ff0f8b1 100644 --- a/Telegram/SourceFiles/core/click_handler_types.h +++ b/Telegram/SourceFiles/core/click_handler_types.h @@ -23,11 +23,15 @@ class SessionController; [[nodiscard]] bool UrlRequiresConfirmation(const QUrl &url); +class PeerData; struct ClickHandlerContext { FullMsgId itemId; + // Is filled from sections. Fn elementDelegate; base::weak_ptr sessionWindow; bool skipBotAutoLogin = false; + // Is filled from peer info. + PeerData *peer = nullptr; }; Q_DECLARE_METATYPE(ClickHandlerContext); @@ -169,7 +173,6 @@ private: }; -class PeerData; class BotCommandClickHandler : public TextClickHandler { public: BotCommandClickHandler(const QString &cmd) : _cmd(cmd) { diff --git a/Telegram/SourceFiles/core/core_cloud_password.cpp b/Telegram/SourceFiles/core/core_cloud_password.cpp index 576285f13..f01b48087 100644 --- a/Telegram/SourceFiles/core/core_cloud_password.cpp +++ b/Telegram/SourceFiles/core/core_cloud_password.cpp @@ -314,6 +314,7 @@ CloudPasswordState ParseCloudPasswordState( ParseSecureSecretAlgo(data.vnew_secure_algo())); result.unconfirmedPattern = qs(data.vemail_unconfirmed_pattern().value_or_empty()); + result.pendingResetDate = data.vpending_reset_date().value_or_empty(); return result; } diff --git a/Telegram/SourceFiles/core/core_cloud_password.h b/Telegram/SourceFiles/core/core_cloud_password.h index 33f9894ef..78e319e7b 100644 --- a/Telegram/SourceFiles/core/core_cloud_password.h +++ b/Telegram/SourceFiles/core/core_cloud_password.h @@ -130,6 +130,7 @@ struct CloudPasswordState { CloudPasswordAlgo newPassword; SecureSecretAlgo newSecureSecret; QString unconfirmedPattern; + TimeId pendingResetDate = 0; }; CloudPasswordState ParseCloudPasswordState( diff --git a/Telegram/SourceFiles/core/core_settings.cpp b/Telegram/SourceFiles/core/core_settings.cpp index 83ceb0803..64aeb2a3f 100644 --- a/Telegram/SourceFiles/core/core_settings.cpp +++ b/Telegram/SourceFiles/core/core_settings.cpp @@ -112,8 +112,7 @@ QByteArray Settings::serialize() const { + sizeof(qint64) + sizeof(qint32) * 2 + Serialize::bytearraySize(windowPosition) - + sizeof(qint32) - + Serialize::bytearraySize(_photoEditorBrush); + + sizeof(qint32); for (const auto &[id, rating] : recentEmojiPreloadData) { size += Serialize::stringSize(id) + sizeof(quint16); } @@ -123,7 +122,9 @@ QByteArray Settings::serialize() const { } size += sizeof(qint32) * 3 + Serialize::bytearraySize(proxy) - + sizeof(qint32) * 2; + + sizeof(qint32) * 2 + + Serialize::bytearraySize(_photoEditorBrush) + + sizeof(qint32); auto result = QByteArray(); result.reserve(size); @@ -212,12 +213,13 @@ QByteArray Settings::serialize() const { } stream << qint32(0) // Old Disable OpenGL - << qint32(_groupCallNoiseSuppression ? 1 : 0) + << qint32(0) // Old Noise Suppression << qint32(_workMode.current()) << proxy << qint32(_hiddenGroupCallTooltips.value()) << qint32(_disableOpenGL ? 1 : 0) - << _photoEditorBrush; + << _photoEditorBrush + << qint32(_groupCallNoiseSuppression ? 1 : 0); } return result; } @@ -432,7 +434,8 @@ void Settings::addFromSerialized(const QByteArray &serialized) { stream >> disableOpenGLOld; } if (!stream.atEnd()) { - stream >> groupCallNoiseSuppression; + qint32 groupCallNoiseSuppressionOld; + stream >> groupCallNoiseSuppressionOld; } if (!stream.atEnd()) { stream >> workMode; @@ -449,6 +452,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) { if (!stream.atEnd()) { stream >> photoEditorBrush; } + if (!stream.atEnd()) { + stream >> groupCallNoiseSuppression; + } if (stream.status() != QDataStream::Ok) { LOG(("App Error: " "Bad data for Core::Settings::constructFromSerialized()")); @@ -802,7 +808,7 @@ void Settings::resetOnLastLogout() { _groupCallPushToTalkShortcut = QByteArray(); _groupCallPushToTalkDelay = 20; - _groupCallNoiseSuppression = true; + _groupCallNoiseSuppression = false; //_themesAccentColors = Window::Theme::AccentColors(); diff --git a/Telegram/SourceFiles/core/core_settings.h b/Telegram/SourceFiles/core/core_settings.h index 32920bc8a..0b22cf0d8 100644 --- a/Telegram/SourceFiles/core/core_settings.h +++ b/Telegram/SourceFiles/core/core_settings.h @@ -654,7 +654,7 @@ private: bool _callAudioDuckingEnabled = true; bool _disableCalls = false; bool _groupCallPushToTalk = false; - bool _groupCallNoiseSuppression = true; + bool _groupCallNoiseSuppression = false; QByteArray _groupCallPushToTalkShortcut; crl::time _groupCallPushToTalkDelay = 20; Window::Theme::AccentColors _themesAccentColors; diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index 9a8e1d6f4..8ca89dff6 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -268,7 +268,9 @@ bool ResolveUsername( } else if (!valid(domain)) { const auto searchParam = params.value(qsl("query")); if (!searchParam.isEmpty()) { - App::searchByHashtag(searchParam, nullptr); + controller->content()->searchMessages( + searchParam + ' ', + Dialogs::Key()); } return false; } diff --git a/Telegram/SourceFiles/core/utils.h b/Telegram/SourceFiles/core/utils.h index 3f932644e..2d73b601f 100644 --- a/Telegram/SourceFiles/core/utils.h +++ b/Telegram/SourceFiles/core/utils.h @@ -121,8 +121,6 @@ void memset_rand(void *data, uint32 len); QString translitRusEng(const QString &rus); QString rusKeyboardLayoutSwitch(const QString &from); -static const int MatrixRowShift = 40000; - inline int rowscount(int fullCount, int countPerRow) { return (fullCount + countPerRow - 1) / countPerRow; } diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 7edada1f6..4ea72daef 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -22,7 +22,7 @@ constexpr auto AppId = "{C4A4AE8F-B9F7-4CC7-8A6C-BF7EEE87ACA5}"_cs; constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs; constexpr auto AppName = "Kotatogram Desktop"_cs; constexpr auto AppFile = "Kotatogram"_cs; -constexpr auto AppVersion = 2008011; -constexpr auto AppVersionStr = "2.8.11"; +constexpr auto AppVersion = 2009003; +constexpr auto AppVersionStr = "2.9.3"; constexpr auto AppBetaVersion = false; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/SourceFiles/data/data_replies_list.cpp b/Telegram/SourceFiles/data/data_replies_list.cpp index d45fe7560..8127ef7ca 100644 --- a/Telegram/SourceFiles/data/data_replies_list.cpp +++ b/Telegram/SourceFiles/data/data_replies_list.cpp @@ -30,10 +30,9 @@ constexpr auto kMessagesPerPage = 50; const QString &text) { return history->makeServiceMessage( history->session().data().nextNonHistoryEntryId(), - MTPDmessage_ClientFlag::f_fake_history_item, + MessageFlag::FakeHistoryItem, date, HistoryService::PreparedText{ text }, - MTPDmessage::Flags(0), PeerId(0), nullptr, false); @@ -543,7 +542,7 @@ bool RepliesList::processMessagesIsEmpty(const MTPmessages_Messages &result) { const auto maxId = IdFromMessage(list.front()); const auto wasSize = int(_list.size()); const auto toFront = (wasSize > 0) && (maxId > _list.front()); - const auto clientFlags = MTPDmessage_ClientFlags(); + const auto localFlags = MessageFlags(); const auto type = NewMessageType::Existing; auto refreshed = std::vector(); if (toFront) { @@ -551,7 +550,7 @@ bool RepliesList::processMessagesIsEmpty(const MTPmessages_Messages &result) { } auto skipped = 0; for (const auto &message : list) { - if (const auto item = owner.addNewMessage(message, clientFlags, type)) { + if (const auto item = owner.addNewMessage(message, localFlags, type)) { if (item->replyToTop() == _rootId) { if (toFront) { refreshed.push_back(item->id); diff --git a/Telegram/SourceFiles/data/data_scheduled_messages.cpp b/Telegram/SourceFiles/data/data_scheduled_messages.cpp index 9a7647058..52df0b32c 100644 --- a/Telegram/SourceFiles/data/data_scheduled_messages.cpp +++ b/Telegram/SourceFiles/data/data_scheduled_messages.cpp @@ -158,6 +158,7 @@ void ScheduledMessages::sendNowSimpleMessage( not_null local) { Expects(local->isSending()); Expects(local->isScheduled()); + if (HasScheduledDate(local)) { LOG(("Error: trying to put to history a new local message, " "that has scheduled date.")); @@ -175,17 +176,19 @@ void ScheduledMessages::sendNowSimpleMessage( auto action = Api::SendAction(history); action.replyTo = local->replyToId(); const auto replyHeader = NewMessageReplyHeader(action); - auto flags = NewMessageFlags(history->peer) - | MTPDmessage::Flag::f_entities + const auto localFlags = NewMessageFlags(history->peer) + | MessageFlag::LocalHistoryEntry; + const auto flags = MTPDmessage::Flag::f_entities | MTPDmessage::Flag::f_from_id | (local->replyToId() ? MTPDmessage::Flag::f_reply_to : MTPDmessage::Flag(0)) | (update.vttl_period() ? MTPDmessage::Flag::f_ttl_period + : MTPDmessage::Flag(0)) + | ((localFlags & MessageFlag::Outgoing) + ? MTPDmessage::Flag::f_out : MTPDmessage::Flag(0)); - auto clientFlags = NewMessageClientFlags() - | MTPDmessage_ClientFlag::f_local_history_entry; const auto views = 1; const auto forwards = 0; history->addNewMessage( @@ -213,7 +216,7 @@ void ScheduledMessages::sendNowSimpleMessage( //MTPMessageReactions(), MTPVector(), MTP_int(update.vttl_period().value_or_empty())), - clientFlags, + localFlags, NewMessageType::Unread); local->destroy(); @@ -459,7 +462,7 @@ HistoryItem *ScheduledMessages::append( const auto item = _session->data().addNewMessage( PrepareMessage(message, history->nextNonHistoryEntryId()), - MTPDmessage_ClientFlags(), + MessageFlags(), // localFlags NewMessageType::Existing); if (!item || item->history() != history) { LOG(("API Error: Bad data received in scheduled messages.")); diff --git a/Telegram/SourceFiles/data/data_search_controller.cpp b/Telegram/SourceFiles/data/data_search_controller.cpp index ab53a656b..bc544c8a4 100644 --- a/Telegram/SourceFiles/data/data_search_controller.cpp +++ b/Telegram/SourceFiles/data/data_search_controller.cpp @@ -161,7 +161,7 @@ SearchResult ParseSearchResult( for (const auto &message : *messages) { const auto item = peer->owner().addNewMessage( message, - MTPDmessage_ClientFlags(), + MessageFlags(), addType); if (item) { const auto itemId = item->id; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 3c8699836..df99bc097 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/crash_reports.h" // CrashReports::SetAnnotation #include "ui/image/image.h" #include "ui/image/image_location_factory.h" // Images::FromPhotoSize +#include "ui/text/format_values.h" // Ui::FormatPhone #include "export/export_manager.h" #include "export/view/export_view_panel_controller.h" #include "mtproto/mtproto_config.h" @@ -506,7 +507,7 @@ not_null Session::processUser(const MTPUser &data) { const auto pname = (showPhoneChanged || phoneChanged || nameChanged) ? ((showPhone && !phone.isEmpty()) - ? App::formatPhone(phone) + ? Ui::FormatPhone(phone) : QString()) : result->nameOrPhone; @@ -1966,7 +1967,7 @@ void Session::processMessages( for (const auto &[position, index] : indices) { addNewMessage( data[index], - MTPDmessage_ClientFlags(), + MessageFlags(), type); } } @@ -2286,7 +2287,7 @@ void Session::unmuteByFinished() { HistoryItem *Session::addNewMessage( const MTPMessage &data, - MTPDmessage_ClientFlags clientFlags, + MessageFlags localFlags, NewMessageType type) { const auto peerId = PeerFromMessage(data); if (!peerId) { @@ -2295,7 +2296,7 @@ HistoryItem *Session::addNewMessage( const auto result = history(peerId)->addNewMessage( data, - clientFlags, + localFlags, type); if (result && type == NewMessageType::Unread) { CheckForSwitchInlineButton(result); @@ -3593,7 +3594,7 @@ QString Session::findContactPhone(not_null contact) const { const auto result = contact->phone(); return result.isEmpty() ? findContactPhone(peerToUser(contact->id)) - : App::formatPhone(result); + : Ui::FormatPhone(result); } QString Session::findContactPhone(UserId contactId) const { @@ -4067,8 +4068,8 @@ void Session::insertCheckedServiceNotification( const auto flags = MTPDmessage::Flag::f_entities | MTPDmessage::Flag::f_from_id | MTPDmessage::Flag::f_media; - const auto clientFlags = MTPDmessage_ClientFlag::f_clientside_unread - | MTPDmessage_ClientFlag::f_local_history_entry; + const auto localFlags = MessageFlag::ClientSideUnread + | MessageFlag::LocalHistoryEntry; auto sending = TextWithEntities(), left = message; while (TextUtilities::CutPart(sending, left, MaxMessageSize)) { addNewMessage( @@ -4094,7 +4095,7 @@ void Session::insertCheckedServiceNotification( //MTPMessageReactions(), MTPVector(), MTPint()), // ttl_period - clientFlags, + localFlags, NewMessageType::Unread); } sendHistoryChangeNotifications(); @@ -4161,10 +4162,19 @@ void Session::setWallpapers(const QVector &data, int32 hash) { _wallpapers.push_back(*parsed); } } + + // Put the legacy2 (flowers) wallpaper to the front of the list. + const auto legacy2 = ranges::find_if( + _wallpapers, + Data::IsLegacy2DefaultWallPaper); + if (legacy2 != end(_wallpapers)) { + ranges::rotate(begin(_wallpapers), legacy2, legacy2 + 1); + } + if (ranges::none_of(_wallpapers, Data::IsDefaultWallPaper)) { _wallpapers.push_back(Data::DefaultWallPaper()); _wallpapers.back().setLocalImageAsThumbnail(std::make_shared( - u":/gui/arg/bg.jpg"_q)); + u":/gui/art/background.jpg"_q)); } } diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index fa3c50409..bb4fe354e 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -400,7 +400,7 @@ public: HistoryItem *addNewMessage( const MTPMessage &data, - MTPDmessage_ClientFlags flags, + MessageFlags localFlags, NewMessageType type); struct SendActionAnimationUpdate { diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h index 2745afd0b..40ea359c6 100644 --- a/Telegram/SourceFiles/data/data_types.h +++ b/Telegram/SourceFiles/data/data_types.h @@ -364,3 +364,61 @@ struct StickerSetIdentifier { return !empty(); } }; + +enum class MessageFlag : uint32 { + HideEdited = (1U << 0), + Legacy = (1U << 1), + HasReplyMarkup = (1U << 2), + HasFromId = (1U << 3), + HasPostAuthor = (1U << 4), + HasViews = (1U << 5), + HasReplyInfo = (1U << 6), + HasViaBot = (1U << 7), + AdminLogEntry = (1U << 8), + Post = (1U << 9), + Silent = (1U << 10), + Outgoing = (1U << 11), + Pinned = (1U << 12), + MediaIsUnread = (1U << 13), + MentionsMe = (1U << 14), + IsOrWasScheduled = (1U << 15), + + // Needs to return back to inline mode. + HasSwitchInlineButton = (1U << 16), + + // For "shared links" indexing. + HasTextLinks = (1U << 17), + + // Group / channel create or migrate service message. + IsGroupEssential = (1U << 18), + + // Edited media is generated on the client + // and should not update media from server. + IsLocalUpdateMedia = (1U << 19), + + // Sent from inline bot, need to re-set media when sent. + FromInlineBot = (1U << 20), + + // Generated on the client side and should be unread. + ClientSideUnread = (1U << 21), + + // In a supergroup. + HasAdminBadge = (1U << 22), + + // Outgoing message that is being sent. + BeingSent = (1U << 23), + + // Outgoing message and failed to be sent. + SendingFailed = (1U << 24), + + // No media and only a several emoji text. + IsolatedEmoji = (1U << 25), + + // Local message existing in the message history. + LocalHistoryEntry = (1U << 26), + + // Fake message for some UI element. + FakeHistoryItem = (1U << 27), +}; +inline constexpr bool is_flag_type(MessageFlag) { return true; } +using MessageFlags = base::flags; diff --git a/Telegram/SourceFiles/data/data_wall_paper.cpp b/Telegram/SourceFiles/data/data_wall_paper.cpp index 49391bf7a..f7e6d203d 100644 --- a/Telegram/SourceFiles/data/data_wall_paper.cpp +++ b/Telegram/SourceFiles/data/data_wall_paper.cpp @@ -28,7 +28,8 @@ constexpr auto kTestingEditorBackground = FromLegacyBackgroundId(-664); constexpr auto kThemeBackground = FromLegacyBackgroundId(-2); constexpr auto kCustomBackground = FromLegacyBackgroundId(-1); constexpr auto kLegacy1DefaultBackground = FromLegacyBackgroundId(0); -constexpr auto kDefaultBackground = 5947530738516623361; +constexpr auto kLegacy2DefaultBackground = 5947530738516623361; +constexpr auto kDefaultBackground = 5778236420632084488; constexpr auto kIncorrectDefaultBackground = FromLegacyBackgroundId(105); quint32 SerializeMaybeColor(std::optional color) { @@ -497,13 +498,17 @@ bool IsLegacy1DefaultWallPaper(const WallPaper &paper) { return (paper.id() == kLegacy1DefaultBackground); } +bool IsLegacy2DefaultWallPaper(const WallPaper &paper) { + return (paper.id() == kLegacy2DefaultBackground) + || (paper.id() == kIncorrectDefaultBackground); +} + WallPaper DefaultWallPaper() { return WallPaper(kDefaultBackground); } bool IsDefaultWallPaper(const WallPaper &paper) { - return (paper.id() == kDefaultBackground) - || (paper.id() == kIncorrectDefaultBackground); + return (paper.id() == kDefaultBackground); } bool IsCloudWallPaper(const WallPaper &paper) { diff --git a/Telegram/SourceFiles/data/data_wall_paper.h b/Telegram/SourceFiles/data/data_wall_paper.h index 72147d02b..994d478b6 100644 --- a/Telegram/SourceFiles/data/data_wall_paper.h +++ b/Telegram/SourceFiles/data/data_wall_paper.h @@ -98,6 +98,7 @@ private: [[nodiscard]] bool IsCustomWallPaper(const WallPaper &paper); [[nodiscard]] WallPaper Legacy1DefaultWallPaper(); [[nodiscard]] bool IsLegacy1DefaultWallPaper(const WallPaper &paper); +[[nodiscard]] bool IsLegacy2DefaultWallPaper(const WallPaper &paper); [[nodiscard]] WallPaper DefaultWallPaper(); [[nodiscard]] bool IsDefaultWallPaper(const WallPaper &paper); [[nodiscard]] bool IsCloudWallPaper(const WallPaper &paper); diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 4e83c74c7..f3dbaf822 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -2078,7 +2078,7 @@ bool InnerWidget::searchReceived( if (lastDate) { const auto item = session().data().addNewMessage( message, - MTPDmessage_ClientFlags(), + MessageFlags(), NewMessageType::Existing); const auto history = item->history(); if (!uniquePeers || !hasHistoryInResults(history)) { diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index b2f45cf7c..421b02d67 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -21,10 +21,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include -namespace App { // Hackish.. -QString formatPhone(QString phone); -} // namespace App - namespace Export { namespace Data { namespace { @@ -1795,7 +1791,7 @@ bool SkipMessageByDate(const Message &message, const Settings &settings) { Utf8String FormatPhoneNumber(const Utf8String &phoneNumber) { return phoneNumber.isEmpty() ? Utf8String() - : App::formatPhone(QString::fromUtf8(phoneNumber)).toUtf8(); + : Ui::FormatPhone(QString::fromUtf8(phoneNumber)).toUtf8(); } Utf8String FormatDateTime( diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index 6dceb378f..2ca908051 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -59,15 +59,6 @@ namespace { namespace App { -void sendBotCommand( - not_null peer, - UserData *bot, - const QString &cmd, MsgId replyTo) { - if (const auto m = CheckMainWidget(&peer->session())) { - m->sendBotCommand(peer, bot, cmd, replyTo); - } -} - void hideSingleUseKeyboard(not_null message) { if (const auto m = CheckMainWidget(&message->history()->session())) { m->hideSingleUseKeyboard(message->history()->peer, message->id); @@ -82,6 +73,7 @@ bool insertBotCommand(const QString &cmd) { } void activateBotCommand( + Window::SessionController *sessionController, not_null msg, int row, int column) { @@ -99,12 +91,15 @@ void activateBotCommand( case ButtonType::Default: { // Copy string before passing it to the sending method // because the original button can be destroyed inside. - MsgId replyTo = (msg->id > 0) ? msg->id : 0; - sendBotCommand( - msg->history()->peer, - msg->fromOriginal()->asUser(), - QString(button->text), - replyTo); + if (sessionController) { + MsgId replyTo = (msg->id > 0) ? msg->id : 0; + sessionController->content()->sendBotCommand({ + .peer = msg->history()->peer, + .command = QString(button->text), + .context = msg->fullId(), + .replyTo = replyTo, + }); + } } break; case ButtonType::Callback: @@ -216,25 +211,6 @@ void activateBotCommand( } } -void searchByHashtag(const QString &tag, PeerData *inPeer, UserData *from) { - const auto m = inPeer - ? CheckMainWidget(&inPeer->session()) - : App::main(); // multi good - if (m) { - if (m->controller()->openedFolder().current()) { - m->controller()->closeFolder(); - } - Ui::hideSettingsAndLayer(); - Core::App().hideMediaView(); - m->searchMessages( - tag + ' ', - (inPeer && !inPeer->isUser()) - ? inPeer->owner().history(inPeer).get() - : Dialogs::Key(), - from); - } -} - } // namespace App namespace Ui { @@ -284,10 +260,6 @@ void showPeerHistory(not_null peer, MsgId msgId) { } } -PeerData *getPeerForMouseAction() { - return Core::App().ui_getPeerForMouseAction(); -} - bool skipPaintEvent(QWidget *widget, QPaintEvent *event) { if (auto w = App::wnd()) { if (w->contentOverlapped(widget, event)) { diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index 8f0f5d42a..77521a90a 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -14,15 +14,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class History; -namespace Data { -struct FileOrigin; -} // namespace Data - -namespace InlineBots { -namespace Layout { -class ItemBase; -} // namespace Layout -} // namespace InlineBots +namespace Window { +class SessionController; +} // namespace Window namespace App { @@ -37,17 +31,12 @@ template }; } -void sendBotCommand( - not_null peer, - UserData *bot, - const QString &cmd, - MsgId replyTo = 0); bool insertBotCommand(const QString &cmd); void activateBotCommand( + Window::SessionController *sessionController, not_null msg, int row, int column); -void searchByHashtag(const QString &tag, PeerData *inPeer, UserData *from = nullptr); } // namespace App diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp index 8c098b001..4e848ce38 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -27,7 +27,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "apiwrap.h" #include "api/api_attached_stickers.h" -#include "layout.h" #include "window/window_session_controller.h" #include "main/main_session.h" #include "main/main_session_settings.h" @@ -36,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_utilities.h" #include "ui/inactive_press.h" #include "ui/effects/path_shift_gradient.h" +#include "core/click_handler_types.h" #include "core/file_utilities.h" #include "lang/lang_keys.h" #include "boxes/peers/edit_participant_box.h" @@ -658,6 +658,9 @@ not_null InnerWidget::elementPathShiftGradient() { return _pathGradient.get(); } +void InnerWidget::elementReplyTo(const FullMsgId &to) { +} + void InnerWidget::saveState(not_null memento) { memento->setFilter(std::move(_filter)); memento->setAdmins(std::move(_admins)); @@ -1610,7 +1613,17 @@ void InnerWidget::mouseActionFinish(const QPoint &screenPos, Qt::MouseButton but if (activated) { mouseActionCancel(); - ActivateClickHandler(window(), activated, button); + ActivateClickHandler(window(), activated, { + button, + QVariant::fromValue(ClickHandlerContext{ + .elementDelegate = [weak = Ui::MakeWeak(this)] { + return weak + ? (ElementDelegate*)weak + : nullptr; + }, + .sessionWindow = base::make_weak(_controller.get()), + }) + }); return; } if (_mouseAction == MouseAction::PrepareDrag && !_pressWasInactive && button != Qt::RightButton) { diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h index af0698c25..79cdcc806 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h @@ -132,6 +132,7 @@ public: void elementHandleViaClick(not_null bot) override; bool elementIsChatWide() override; not_null elementPathShiftGradient() override; + void elementReplyTo(const FullMsgId &to) override; ~InnerWidget(); diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp index a3047515e..24d575f8b 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp @@ -533,7 +533,6 @@ void GenerateItems( return callback(OwnedItem(delegate, item), sentDate); }; - using Flag = MTPDmessage::Flag; const auto fromName = from->name; const auto fromLink = from->createOpenLink(); const auto fromLinkText = textcmdLink(1, fromName); @@ -543,10 +542,9 @@ void GenerateItems( message.links.push_back(fromLink); addPart(history->makeServiceMessage( history->nextNonHistoryEntryId(), - MTPDmessage_ClientFlag::f_admin_log_entry, + MessageFlag::AdminLogEntry, date, message, - MTPDmessage::Flags(0), peerToUser(from->id), photo, showTime)); @@ -564,6 +562,29 @@ void GenerateItems( addSimpleServiceMessage(text); }; + auto makeSimpleTextMessage = [&](TextWithEntities &&text) { + auto bodyFlags = MessageFlag::HasFromId | MessageFlag::AdminLogEntry; + auto bodyReplyTo = MsgId(); + auto bodyViaBotId = UserId(); + auto bodyGroupedId = uint64(); + return history->makeMessage( + history->nextNonHistoryEntryId(), + bodyFlags, + bodyReplyTo, + bodyViaBotId, + date, + peerToUser(from->id), + QString(), + std::move(text), + MTP_messageMediaEmpty(), + MTPReplyMarkup(), + bodyGroupedId); + }; + + auto addSimpleTextMessage = [&](TextWithEntities &&text) { + addPart(makeSimpleTextMessage(std::move(text))); + }; + auto createChangeAbout = [&](const MTPDchannelAdminLogEventActionChangeAbout &action) { auto newValue = qs(action.vnew_value()); auto oldValue = qs(action.vprev_value()); @@ -577,21 +598,8 @@ void GenerateItems( )(tr::now, lt_from, fromLinkText); addSimpleServiceMessage(text, nullptr, false); - auto bodyFlags = Flag::f_entities | Flag::f_from_id; - auto bodyClientFlags = MTPDmessage_ClientFlag::f_admin_log_entry; - auto bodyReplyTo = 0; - auto bodyViaBotId = 0; auto newDescription = PrepareText(newValue, QString()); - auto body = history->makeMessage( - history->nextNonHistoryEntryId(), - bodyFlags, - bodyClientFlags, - bodyReplyTo, - bodyViaBotId, - date, - peerToUser(from->id), - QString(), - newDescription); + auto body = makeSimpleTextMessage(PrepareText(newValue, QString())); if (!oldValue.isEmpty()) { auto oldDescription = PrepareText(oldValue, QString()); body->addLogEntryOriginal(id, tr::lng_admin_log_previous_description(tr::now), oldDescription); @@ -612,25 +620,16 @@ void GenerateItems( )(tr::now, lt_from, fromLinkText); addSimpleServiceMessage(text, nullptr, false); - auto bodyFlags = Flag::f_entities | Flag::f_from_id; - auto bodyClientFlags = MTPDmessage_ClientFlag::f_admin_log_entry; - auto bodyReplyTo = 0; - auto bodyViaBotId = 0; auto newLink = newValue.isEmpty() ? TextWithEntities() : PrepareText( history->session().createInternalLinkFull(newValue), QString()); - auto body = history->makeMessage( - history->nextNonHistoryEntryId(), - bodyFlags, - bodyClientFlags, - bodyReplyTo, - bodyViaBotId, - date, - peerToUser(from->id), - QString(), - newLink); + auto body = makeSimpleTextMessage(newValue.isEmpty() + ? TextWithEntities() + : PrepareText( + history->session().createInternalLinkFull(newValue), + QString())); if (!oldValue.isEmpty()) { auto oldLink = PrepareText( history->session().createInternalLinkFull(oldValue), @@ -692,7 +691,7 @@ void GenerateItems( action.vmessage(), history->nextNonHistoryEntryId(), date), - MTPDmessage_ClientFlag::f_admin_log_entry, + MessageFlag::AdminLogEntry, detachExistingItem), ExtractSentDate(action.vmessage())); }, [&](const auto &) { @@ -721,7 +720,7 @@ void GenerateItems( action.vnew_message(), history->nextNonHistoryEntryId(), date), - MTPDmessage_ClientFlag::f_admin_log_entry, + MessageFlag::AdminLogEntry, detachExistingItem); if (oldValue.text.isEmpty()) { oldValue = PrepareText(QString(), tr::lng_admin_log_empty_text(tr::now)); @@ -747,7 +746,7 @@ void GenerateItems( action.vmessage(), history->nextNonHistoryEntryId(), date), - MTPDmessage_ClientFlag::f_admin_log_entry, + MessageFlag::AdminLogEntry, detachExistingItem), ExtractSentDate(action.vmessage())); }; @@ -767,39 +766,13 @@ void GenerateItems( }; auto createParticipantInvite = [&](const MTPDchannelAdminLogEventActionParticipantInvite &action) { - auto bodyFlags = Flag::f_entities | Flag::f_from_id; - auto bodyClientFlags = MTPDmessage_ClientFlag::f_admin_log_entry; - auto bodyReplyTo = 0; - auto bodyViaBotId = 0; - auto bodyText = GenerateParticipantChangeText(channel, action.vparticipant()); - addPart(history->makeMessage( - history->nextNonHistoryEntryId(), - bodyFlags, - bodyClientFlags, - bodyReplyTo, - bodyViaBotId, - date, - peerToUser(from->id), - QString(), - bodyText)); + addSimpleTextMessage( + GenerateParticipantChangeText(channel, action.vparticipant())); }; auto createParticipantToggleBan = [&](const MTPDchannelAdminLogEventActionParticipantToggleBan &action) { - auto bodyFlags = Flag::f_entities | Flag::f_from_id; - auto bodyClientFlags = MTPDmessage_ClientFlag::f_admin_log_entry; - auto bodyReplyTo = 0; - auto bodyViaBotId = 0; - auto bodyText = GenerateParticipantChangeText(channel, action.vnew_participant(), &action.vprev_participant()); - addPart(history->makeMessage( - history->nextNonHistoryEntryId(), - bodyFlags, - bodyClientFlags, - bodyReplyTo, - bodyViaBotId, - date, - peerToUser(from->id), - QString(), - bodyText)); + addSimpleTextMessage( + GenerateParticipantChangeText(channel, action.vnew_participant(), &action.vprev_participant())); }; auto createParticipantToggleAdmin = [&](const MTPDchannelAdminLogEventActionParticipantToggleAdmin &action) { @@ -809,21 +782,8 @@ void GenerateItems( // the "User > Creator" part and skip the "Creator > Admin" part. return; } - auto bodyFlags = Flag::f_entities | Flag::f_from_id; - auto bodyClientFlags = MTPDmessage_ClientFlag::f_admin_log_entry; - auto bodyReplyTo = 0; - auto bodyViaBotId = 0; - auto bodyText = GenerateParticipantChangeText(channel, action.vnew_participant(), &action.vprev_participant()); - addPart(history->makeMessage( - history->nextNonHistoryEntryId(), - bodyFlags, - bodyClientFlags, - bodyReplyTo, - bodyViaBotId, - date, - peerToUser(from->id), - QString(), - bodyText)); + addSimpleTextMessage( + GenerateParticipantChangeText(channel, action.vnew_participant(), &action.vprev_participant())); }; auto createChangeStickerSet = [&](const MTPDchannelAdminLogEventActionChangeStickerSet &action) { @@ -849,10 +809,9 @@ void GenerateItems( message.links.push_back(setLink); addPart(history->makeServiceMessage( history->nextNonHistoryEntryId(), - MTPDmessage_ClientFlag::f_admin_log_entry, + MessageFlag::AdminLogEntry, date, message, - MTPDmessage::Flags(0), peerToUser(from->id))); } }; @@ -866,24 +825,11 @@ void GenerateItems( }; auto createDefaultBannedRights = [&](const MTPDchannelAdminLogEventActionDefaultBannedRights &action) { - auto bodyFlags = Flag::f_entities | Flag::f_from_id; - auto bodyClientFlags = MTPDmessage_ClientFlag::f_admin_log_entry; - auto bodyReplyTo = 0; - auto bodyViaBotId = 0; - auto bodyText = GenerateDefaultBannedRightsChangeText( - channel, - ChatRestrictionsInfo(action.vnew_banned_rights()), - ChatRestrictionsInfo(action.vprev_banned_rights())); - addPart(history->makeMessage( - history->nextNonHistoryEntryId(), - bodyFlags, - bodyClientFlags, - bodyReplyTo, - bodyViaBotId, - date, - peerToUser(from->id), - QString(), - bodyText)); + addSimpleTextMessage( + GenerateDefaultBannedRightsChangeText( + channel, + ChatRestrictionsInfo(action.vnew_banned_rights()), + ChatRestrictionsInfo(action.vprev_banned_rights()))); }; auto createStopPoll = [&](const MTPDchannelAdminLogEventActionStopPoll &action) { @@ -897,7 +843,7 @@ void GenerateItems( action.vmessage(), history->nextNonHistoryEntryId(), date), - MTPDmessage_ClientFlag::f_admin_log_entry, + MessageFlag::AdminLogEntry, detachExistingItem), ExtractSentDate(action.vmessage())); }; @@ -930,10 +876,9 @@ void GenerateItems( message.links.push_back(chatLink); addPart(history->makeServiceMessage( history->nextNonHistoryEntryId(), - MTPDmessage_ClientFlag::f_admin_log_entry, + MessageFlag::AdminLogEntry, date, message, - MTPDmessage::Flags(0), peerToUser(from->id))); } }; @@ -1001,10 +946,9 @@ void GenerateItems( message.links.push_back(link); addPart(history->makeServiceMessage( history->nextNonHistoryEntryId(), - MTPDmessage_ClientFlag::f_admin_log_entry, + MessageFlag::AdminLogEntry, date, message, - MTPDmessage::Flags(0), peerToUser(from->id))); }; @@ -1049,10 +993,9 @@ void GenerateItems( } addPart(history->makeServiceMessage( history->nextNonHistoryEntryId(), - MTPDmessage_ClientFlag::f_admin_log_entry, + MessageFlag::AdminLogEntry, date, message, - MTPDmessage::Flags(0), peerToUser(from->id), nullptr)); }; @@ -1094,21 +1037,8 @@ void GenerateItems( }; auto createExportedInviteEdit = [&](const MTPDchannelAdminLogEventActionExportedInviteEdit &data) { - auto bodyFlags = Flag::f_entities | Flag::f_from_id; - auto bodyClientFlags = MTPDmessage_ClientFlag::f_admin_log_entry; - auto bodyReplyTo = 0; - auto bodyViaBotId = 0; - auto bodyText = GenerateInviteLinkChangeText(data.vnew_invite(), data.vprev_invite()); - addPart(history->makeMessage( - history->nextNonHistoryEntryId(), - bodyFlags, - bodyClientFlags, - bodyReplyTo, - bodyViaBotId, - date, - peerToUser(from->id), - QString(), - bodyText)); + addSimpleTextMessage( + GenerateInviteLinkChangeText(data.vnew_invite(), data.vprev_invite())); }; auto createParticipantVolume = [&](const MTPDchannelAdminLogEventActionParticipantVolume &data) { diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 6f6f6478f..a59d8c7c0 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -349,7 +349,7 @@ void History::setForwardDraft(MessageIdsList &&items) { HistoryItem *History::createItem( const MTPMessage &message, - MTPDmessage_ClientFlags clientFlags, + MessageFlags localFlags, bool detachExistingItem) { const auto messageId = IdFromMessage(message); if (!messageId) { @@ -362,17 +362,17 @@ HistoryItem *History::createItem( } return result; } - return HistoryItem::Create(this, message, clientFlags); + return HistoryItem::Create(this, message, localFlags); } std::vector> History::createItems( const QVector &data) { auto result = std::vector>(); result.reserve(data.size()); - const auto clientFlags = MTPDmessage_ClientFlags(); + const auto localFlags = MessageFlags(); for (auto i = data.cend(), e = data.cbegin(); i != e;) { const auto detachExistingItem = true; - const auto item = createItem(*--i, clientFlags, detachExistingItem); + const auto item = createItem(*--i, localFlags, detachExistingItem); if (item) { result.emplace_back(item); } @@ -382,10 +382,10 @@ std::vector> History::createItems( HistoryItem *History::addNewMessage( const MTPMessage &msg, - MTPDmessage_ClientFlags clientFlags, + MessageFlags localFlags, NewMessageType type) { const auto detachExistingItem = (type == NewMessageType::Unread); - const auto item = createItem(msg, clientFlags, detachExistingItem); + const auto item = createItem(msg, localFlags, detachExistingItem); if (!item) { return nullptr; } @@ -515,8 +515,35 @@ void History::checkForLoadedAtTop(not_null added) { not_null History::addNewLocalMessage( MsgId id, - MTPDmessage::Flags flags, - MTPDmessage_ClientFlags clientFlags, + MessageFlags flags, + UserId viaBotId, + MsgId replyTo, + TimeId date, + PeerId from, + const QString &postAuthor, + const TextWithEntities &text, + const MTPMessageMedia &media, + const MTPReplyMarkup &markup, + uint64 groupedId) { + return addNewItem( + makeMessage( + id, + flags, + replyTo, + viaBotId, + date, + from, + postAuthor, + text, + media, + markup, + groupedId), + true); +} + +not_null History::addNewLocalMessage( + MsgId id, + MessageFlags flags, TimeId date, PeerId from, const QString &postAuthor, @@ -525,7 +552,6 @@ not_null History::addNewLocalMessage( makeMessage( id, flags, - clientFlags, date, from, postAuthor, @@ -535,8 +561,7 @@ not_null History::addNewLocalMessage( not_null History::addNewLocalMessage( MsgId id, - MTPDmessage::Flags flags, - MTPDmessage_ClientFlags clientFlags, + MessageFlags flags, UserId viaBotId, MsgId replyTo, TimeId date, @@ -550,7 +575,6 @@ not_null History::addNewLocalMessage( makeMessage( id, flags, - clientFlags, replyTo, viaBotId, date, @@ -565,8 +589,7 @@ not_null History::addNewLocalMessage( not_null History::addNewLocalMessage( MsgId id, - MTPDmessage::Flags flags, - MTPDmessage_ClientFlags clientFlags, + MessageFlags flags, UserId viaBotId, MsgId replyTo, TimeId date, @@ -580,7 +603,6 @@ not_null History::addNewLocalMessage( makeMessage( id, flags, - clientFlags, replyTo, viaBotId, date, @@ -595,8 +617,7 @@ not_null History::addNewLocalMessage( not_null History::addNewLocalMessage( MsgId id, - MTPDmessage::Flags flags, - MTPDmessage_ClientFlags clientFlags, + MessageFlags flags, UserId viaBotId, MsgId replyTo, TimeId date, @@ -608,7 +629,6 @@ not_null History::addNewLocalMessage( makeMessage( id, flags, - clientFlags, replyTo, viaBotId, date, @@ -700,10 +720,10 @@ void History::addUnreadMentionsSlice(const MTPmessages_Messages &result) { auto added = false; if (messages) { - const auto clientFlags = MTPDmessage_ClientFlags(); + const auto localFlags = MessageFlags(); const auto type = NewMessageType::Existing; for (const auto &message : *messages) { - if (const auto item = addNewMessage(message, clientFlags, type)) { + if (const auto item = addNewMessage(message, localFlags, type)) { if (item->isUnreadMention()) { _unreadMentions.insert(item->id); added = true; @@ -785,7 +805,7 @@ not_null History::addNewToBack( } if (item->definesReplyKeyboard()) { auto markupFlags = item->replyKeyboardFlags(); - if (!(markupFlags & MTPDreplyKeyboardMarkup::Flag::f_selective) + if (!(markupFlags & ReplyMarkupFlag::Selective) || item->mentionsMe()) { auto getMarkupSenders = [this]() -> base::flat_set>* { if (auto chat = peer->asChat()) { @@ -798,7 +818,8 @@ not_null History::addNewToBack( if (auto markupSenders = getMarkupSenders()) { markupSenders->insert(item->from()); } - if (markupFlags & MTPDreplyKeyboardMarkup_ClientFlag::f_zero) { // zero markup means replyKeyboardHide + if (markupFlags & ReplyMarkupFlag::None) { + // None markup means replyKeyboardHide. if (lastKeyboardFrom == item->from()->id || (!lastKeyboardInited && !peer->isChat() @@ -1298,13 +1319,13 @@ void History::addItemsToLists( if (item->author()->id) { if (markupSenders) { // chats with bots if (!lastKeyboardInited && item->definesReplyKeyboard() && !item->out()) { - auto markupFlags = item->replyKeyboardFlags(); - if (!(markupFlags & MTPDreplyKeyboardMarkup::Flag::f_selective) || item->mentionsMe()) { + const auto markupFlags = item->replyKeyboardFlags(); + if (!(markupFlags & ReplyMarkupFlag::Selective) || item->mentionsMe()) { bool wasKeyboardHide = markupSenders->contains(item->author()); if (!wasKeyboardHide) { markupSenders->insert(item->author()); } - if (!(markupFlags & MTPDreplyKeyboardMarkup_ClientFlag::f_zero)) { + if (!(markupFlags & ReplyMarkupFlag::None)) { if (!lastKeyboardInited) { bool botNotInChat = false; if (peer->isChat()) { @@ -1325,9 +1346,9 @@ void History::addItemsToLists( } } } else if (!lastKeyboardInited && item->definesReplyKeyboard() && !item->out()) { // conversations with bots - MTPDreplyKeyboardMarkup::Flags markupFlags = item->replyKeyboardFlags(); - if (!(markupFlags & MTPDreplyKeyboardMarkup::Flag::f_selective) || item->mentionsMe()) { - if (markupFlags & MTPDreplyKeyboardMarkup_ClientFlag::f_zero) { + const auto markupFlags = item->replyKeyboardFlags(); + if (!(markupFlags & ReplyMarkupFlag::Selective) || item->mentionsMe()) { + if (markupFlags & ReplyMarkupFlag::None) { clearLastKeyboard(); } else { lastKeyboardInited = true; @@ -2355,7 +2376,7 @@ void History::setFakeChatListMessageFrom(const MTPmessages_Messages &data) { } const auto item = owner().addNewMessage( *other, - MTPDmessage_ClientFlags(), + MessageFlags(), NewMessageType::Existing); if (!item || item->isGroupMigrate()) { // Not better than the last one. @@ -2788,9 +2809,8 @@ HistoryService *History::insertJoinedMessage() { return nullptr; } - const auto flags = MTPDmessage::Flags(); const auto inviteDate = peer->asChannel()->inviteDate; - _joinedMessage = GenerateJoinedMessage(this, inviteDate, inviter, flags); + _joinedMessage = GenerateJoinedMessage(this, inviteDate, inviter); insertLocalMessage(_joinedMessage); return _joinedMessage; } diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index b068377fb..32d15edc9 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -118,23 +118,33 @@ public: HistoryItem *addNewMessage( const MTPMessage &msg, - MTPDmessage_ClientFlags clientFlags, + MessageFlags localFlags, NewMessageType type); HistoryItem *addToHistory( const MTPMessage &msg, - MTPDmessage_ClientFlags clientFlags); + MessageFlags localFlags); not_null addNewLocalMessage( MsgId id, - MTPDmessage::Flags flags, - MTPDmessage_ClientFlags clientFlags, + MessageFlags flags, + UserId viaBotId, + MsgId replyTo, + TimeId date, + PeerId from, + const QString &postAuthor, + const TextWithEntities &text, + const MTPMessageMedia &media, + const MTPReplyMarkup &markup, + uint64 groupedId = 0); + not_null addNewLocalMessage( + MsgId id, + MessageFlags flags, TimeId date, PeerId from, const QString &postAuthor, not_null forwardOriginal); not_null addNewLocalMessage( MsgId id, - MTPDmessage::Flags flags, - MTPDmessage_ClientFlags clientFlags, + MessageFlags flags, UserId viaBotId, MsgId replyTo, TimeId date, @@ -146,8 +156,7 @@ public: uint64 newGroupId = 0); not_null addNewLocalMessage( MsgId id, - MTPDmessage::Flags flags, - MTPDmessage_ClientFlags clientFlags, + MessageFlags flags, UserId viaBotId, MsgId replyTo, TimeId date, @@ -159,8 +168,7 @@ public: uint64 newGroupId = 0); not_null addNewLocalMessage( MsgId id, - MTPDmessage::Flags flags, - MTPDmessage_ClientFlags clientFlags, + MessageFlags flags, UserId viaBotId, MsgId replyTo, TimeId date, @@ -172,7 +180,7 @@ public: // Used only internally and for channel admin log. HistoryItem *createItem( const MTPMessage &message, - MTPDmessage_ClientFlags clientFlags, + MessageFlags localFlags, bool detachExistingItem); std::vector> createItems( const QVector &data); diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 29c6aa425..988efd1ab 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -47,7 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unixtime.h" #include "mainwindow.h" #include "mainwidget.h" -#include "layout.h" +#include "layout/layout_selection.h" #include "main/main_session.h" #include "main/main_session_settings.h" #include "core/application.h" @@ -1596,7 +1596,12 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { const auto peer = item->history()->peer; if (peer->isChat() || peer->isMegagroup()) { _menu->addAction(ktr("ktg_context_show_messages_from"), [=] { - App::searchByHashtag(QString(), peer, item->from()->asUser()); + controller->content()->searchMessages( + " ", + (peer && !peer->isUser()) + ? peer->owner().history(peer).get() + : Dialogs::Key(), + item->from()->asUser()); }); } }; @@ -2627,19 +2632,7 @@ bool HistoryInner::elementIsGifPaused() { void HistoryInner::elementSendBotCommand( const QString &command, const FullMsgId &context) { - if (auto peer = Ui::getPeerForMouseAction()) { // old way - auto bot = peer->isUser() ? peer->asUser() : nullptr; - if (!bot) { - if (const auto view = App::hoveredLinkItem()) { - // may return nullptr - bot = view->data()->fromOriginal()->asUser(); - } - } - Ui::showPeerHistory(peer, ShowAtTheEndMsgId); - App::sendBotCommand(peer, bot, command); - } else { - App::insertBotCommand(command); - } + _widget->sendBotCommand({ _history->peer, command, context }); } void HistoryInner::elementHandleViaClick(not_null bot) { @@ -2654,6 +2647,10 @@ not_null HistoryInner::elementPathShiftGradient() { return _pathGradient.get(); } +void HistoryInner::elementReplyTo(const FullMsgId &to) { + return _widget->replyToMessage(to); +} + auto HistoryInner::getSelectionState() const -> HistoryView::TopBarWidget::SelectedState { auto result = HistoryView::TopBarWidget::SelectedState {}; @@ -3557,6 +3554,11 @@ not_null HistoryInner::ElementDelegate() { return Instance->elementPathShiftGradient(); } + void elementReplyTo(const FullMsgId &to) override { + if (Instance) { + Instance->elementReplyTo(to); + } + } }; static Result result; diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index e5304749c..03d455123 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -109,6 +109,7 @@ public: void elementHandleViaClick(not_null bot); bool elementIsChatWide(); not_null elementPathShiftGradient(); + void elementReplyTo(const FullMsgId &to); void updateBotInfo(bool recount = true); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 3d91090d1..f498c8018 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -10,7 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "kotato/kotato_lang.h" #include "lang/lang_keys.h" #include "mainwidget.h" -#include "layout.h" #include "history/view/history_view_element.h" #include "history/view/history_view_service_message.h" #include "history/history_item_components.h" @@ -61,8 +60,7 @@ enum class MediaCheckResult { not_null CreateUnsupportedMessage( not_null history, MsgId msgId, - MTPDmessage::Flags flags, - MTPDmessage_ClientFlags clientFlags, + MessageFlags flags, MsgId replyTo, UserId viaBotId, TimeId date, @@ -74,18 +72,21 @@ not_null CreateUnsupportedMessage( TextUtilities::ParseEntities(text, Ui::ItemTextNoMonoOptions().flags); text.entities.push_front( EntityInText(EntityType::Italic, 0, text.text.size())); - flags &= ~MTPDmessage::Flag::f_post_author; - flags |= MTPDmessage::Flag::f_legacy; + flags &= ~MessageFlag::HasPostAuthor; + flags |= MessageFlag::Legacy; + const auto groupedId = uint64(); return history->makeMessage( msgId, flags, - clientFlags, replyTo, viaBotId, date, from, QString(), - text); + text, + MTP_messageMediaEmpty(), + MTPReplyMarkup(), + groupedId); } MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) { @@ -172,15 +173,13 @@ void HistoryItem::HistoryItem::Destroyer::operator()(HistoryItem *value) { HistoryItem::HistoryItem( not_null history, MsgId id, - MTPDmessage::Flags flags, - MTPDmessage_ClientFlags clientFlags, + MessageFlags flags, TimeId date, PeerId from) : id(id) , _history(history) , _from(from ? history->owner().peer(from) : history->peer) , _flags(flags) -, _clientFlags(clientFlags) , _date(date) { if (isHistoryEntry() && IsClientMsgId(id)) { _history->registerLocalMessage(this); @@ -233,7 +232,7 @@ void HistoryItem::setGroupId(MessageGroupId groupId) { HistoryMessageReplyMarkup *HistoryItem::inlineReplyMarkup() { if (const auto markup = Get()) { - if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_inline) { + if (markup->flags & ReplyMarkupFlag::Inline) { return markup; } } @@ -320,11 +319,11 @@ bool HistoryItem::hasUnreadMediaFlag() const { return false; } } - return _flags & MTPDmessage::Flag::f_media_unread; + return _flags & MessageFlag::MediaIsUnread; } bool HistoryItem::isUnreadMention() const { - return mentionsMe() && (_flags & MTPDmessage::Flag::f_media_unread); + return mentionsMe() && (_flags & MessageFlag::MediaIsUnread); } bool HistoryItem::mentionsMe() const { @@ -332,7 +331,7 @@ bool HistoryItem::mentionsMe() const { && !Core::App().settings().notifyAboutPinned()) { return false; } - return _flags & MTPDmessage::Flag::f_mentioned; + return _flags & MessageFlag::MentionsMe; } bool HistoryItem::isUnreadMedia() const { @@ -349,7 +348,7 @@ bool HistoryItem::isUnreadMedia() const { } void HistoryItem::markMediaRead() { - _flags &= ~MTPDmessage::Flag::f_media_unread; + _flags &= ~MessageFlag::MediaIsUnread; if (mentionsMe()) { history()->updateChatListEntry(); @@ -360,7 +359,7 @@ void HistoryItem::markMediaRead() { void HistoryItem::setIsPinned(bool pinned) { const auto changed = (isPinned() != pinned); if (pinned) { - _flags |= MTPDmessage::Flag::f_pinned; + _flags |= MessageFlag::Pinned; history()->session().storage().add(Storage::SharedMediaAddExisting( history()->peer->id, Storage::SharedMediaType::Pinned, @@ -368,7 +367,7 @@ void HistoryItem::setIsPinned(bool pinned) { { id, id })); history()->peer->setHasPinnedMessages(true); } else { - _flags &= ~MTPDmessage::Flag::f_pinned; + _flags &= ~MessageFlag::Pinned; history()->session().storage().remove(Storage::SharedMediaRemoveOne( history()->peer->id, Storage::SharedMediaType::Pinned, @@ -381,7 +380,7 @@ void HistoryItem::setIsPinned(bool pinned) { bool HistoryItem::definesReplyKeyboard() const { if (const auto markup = Get()) { - if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_inline) { + if (markup->flags & ReplyMarkupFlag::Inline) { return false; } return true; @@ -389,10 +388,10 @@ bool HistoryItem::definesReplyKeyboard() const { // optimization: don't create markup component for the case // MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag - return (_flags & MTPDmessage::Flag::f_reply_markup); + return (_flags & MessageFlag::HasReplyMarkup); } -MTPDreplyKeyboardMarkup::Flags HistoryItem::replyKeyboardFlags() const { +ReplyMarkupFlags HistoryItem::replyKeyboardFlags() const { Expects(definesReplyKeyboard()); if (const auto markup = Get()) { @@ -401,7 +400,7 @@ MTPDreplyKeyboardMarkup::Flags HistoryItem::replyKeyboardFlags() const { // optimization: don't create markup component for the case // MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag - return MTPDreplyKeyboardMarkup_ClientFlag::f_zero | 0; + return ReplyMarkupFlag::None; } void HistoryItem::addLogEntryOriginal( @@ -443,22 +442,22 @@ UserData *HistoryItem::getMessageBot() const { bool HistoryItem::isHistoryEntry() const { return IsServerMsgId(id) - || (_clientFlags & MTPDmessage_ClientFlag::f_local_history_entry); + || (_flags & MessageFlag::LocalHistoryEntry); } bool HistoryItem::isAdminLogEntry() const { - return (_clientFlags & MTPDmessage_ClientFlag::f_admin_log_entry); + return (_flags & MessageFlag::AdminLogEntry); } bool HistoryItem::isFromScheduled() const { return isHistoryEntry() - && (_flags & MTPDmessage::Flag::f_from_scheduled); + && (_flags & MessageFlag::IsOrWasScheduled); } bool HistoryItem::isScheduled() const { return !isHistoryEntry() && !isAdminLogEntry() - && (_flags & MTPDmessage::Flag::f_from_scheduled); + && (_flags & MessageFlag::IsOrWasScheduled); } void HistoryItem::destroy() { @@ -562,11 +561,11 @@ void HistoryItem::indexAsNewItem() { } void HistoryItem::setRealId(MsgId newId) { - Expects(_clientFlags & MTPDmessage_ClientFlag::f_sending); + Expects(_flags & MessageFlag::BeingSent); Expects(IsClientMsgId(id)); const auto oldId = std::exchange(id, newId); - _clientFlags &= ~MTPDmessage_ClientFlag::f_sending; + _flags &= ~MessageFlag::BeingSent; if (IsServerMsgId(id)) { _history->unregisterLocalMessage(this); } @@ -848,11 +847,10 @@ void HistoryItem::applyTTL(TimeId destroyAt) { } void HistoryItem::sendFailed() { - Expects(_clientFlags & MTPDmessage_ClientFlag::f_sending); - Expects(!(_clientFlags & MTPDmessage_ClientFlag::f_failed)); + Expects(_flags & MessageFlag::BeingSent); + Expects(!(_flags & MessageFlag::SendingFailed)); - _clientFlags = (_clientFlags | MTPDmessage_ClientFlag::f_failed) - & ~MTPDmessage_ClientFlag::f_sending; + _flags = (_flags | MessageFlag::SendingFailed) & ~MessageFlag::BeingSent; history()->session().changes().historyUpdated( history(), Data::HistoryUpdate::Flag::LocalMessages); @@ -897,7 +895,7 @@ bool HistoryItem::unread() const { } return true; } - return (_clientFlags & MTPDmessage_ClientFlag::f_clientside_unread); + return (_flags & MessageFlag::ClientSideUnread); } bool HistoryItem::showNotification() const { @@ -911,7 +909,7 @@ bool HistoryItem::showNotification() const { } void HistoryItem::markClientSideAsRead() { - _clientFlags &= ~MTPDmessage_ClientFlag::f_clientside_unread; + _flags &= ~MessageFlag::ClientSideUnread; } MessageGroupId HistoryItem::groupId() const { @@ -1046,10 +1044,44 @@ ClickHandlerPtr goToMessageClickHandler( }); } +MessageFlags FlagsFromMTP(MTPDmessage::Flags flags) { + using Flag = MessageFlag; + using MTP = MTPDmessage::Flag; + return Flag() + | ((flags & MTP::f_out) ? Flag::Outgoing : Flag()) + | ((flags & MTP::f_mentioned) ? Flag::MentionsMe : Flag()) + | ((flags & MTP::f_media_unread) ? Flag::MediaIsUnread : Flag()) + | ((flags & MTP::f_silent) ? Flag::Silent : Flag()) + | ((flags & MTP::f_post) ? Flag::Post : Flag()) + | ((flags & MTP::f_legacy) ? Flag::Legacy : Flag()) + | ((flags & MTP::f_edit_hide) ? Flag::HideEdited : Flag()) + | ((flags & MTP::f_pinned) ? Flag::Pinned : Flag()) + | ((flags & MTP::f_from_id) ? Flag::HasFromId : Flag()) + | ((flags & MTP::f_via_bot_id) ? Flag::HasViaBot : Flag()) + | ((flags & MTP::f_reply_to) ? Flag::HasReplyInfo : Flag()) + | ((flags & MTP::f_reply_markup) ? Flag::HasReplyMarkup : Flag()) + | ((flags & MTP::f_from_scheduled) ? Flag::IsOrWasScheduled : Flag()) + | ((flags & MTP::f_views) ? Flag::HasViews : Flag()); +} + +MessageFlags FlagsFromMTP(MTPDmessageService::Flags flags) { + using Flag = MessageFlag; + using MTP = MTPDmessageService::Flag; + return Flag() + | ((flags & MTP::f_out) ? Flag::Outgoing : Flag()) + | ((flags & MTP::f_mentioned) ? Flag::MentionsMe : Flag()) + | ((flags & MTP::f_media_unread) ? Flag::MediaIsUnread : Flag()) + | ((flags & MTP::f_silent) ? Flag::Silent : Flag()) + | ((flags & MTP::f_post) ? Flag::Post : Flag()) + | ((flags & MTP::f_legacy) ? Flag::Legacy : Flag()) + | ((flags & MTP::f_from_id) ? Flag::HasFromId : Flag()) + | ((flags & MTP::f_reply_to) ? Flag::HasReplyInfo : Flag()); +} + not_null HistoryItem::Create( not_null history, const MTPMessage &message, - MTPDmessage_ClientFlags clientFlags) { + MessageFlags localFlags) { return message.match([&](const MTPDmessage &data) -> HistoryItem* { const auto media = data.vmedia(); const auto checked = media @@ -1059,8 +1091,7 @@ not_null HistoryItem::Create( return CreateUnsupportedMessage( history, data.vid().v, - data.vflags().v, - clientFlags, + FlagsFromMTP(data.vflags().v) | localFlags, MsgId(0), // No need to pass reply_to data here. data.vvia_bot_id().value_or_empty(), data.vdate().v, @@ -1071,27 +1102,26 @@ not_null HistoryItem::Create( }; return history->makeServiceMessage( data.vid().v, - clientFlags, + FlagsFromMTP(data.vflags().v) | localFlags, data.vdate().v, text, - data.vflags().v, data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0)); } else if (checked == MediaCheckResult::HasTimeToLive) { - return history->makeServiceMessage(data, clientFlags); + return history->makeServiceMessage(data, localFlags); } - return history->makeMessage(data, clientFlags); + return history->makeMessage(data, localFlags); }, [&](const MTPDmessageService &data) -> HistoryItem* { if (data.vaction().type() == mtpc_messageActionPhoneCall) { - return history->makeMessage(data, clientFlags); + return history->makeMessage(data, localFlags); } - return history->makeServiceMessage(data, clientFlags); + return history->makeServiceMessage(data, localFlags); }, [&](const MTPDmessageEmpty &data) -> HistoryItem* { const auto text = HistoryService::PreparedText{ tr::lng_message_empty(tr::now) }; return history->makeServiceMessage( data.vid().v, - clientFlags, + localFlags, TimeId(0), text); }); diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index b9517f2ae..7bd594be4 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -57,12 +57,27 @@ class ElementDelegate; struct HiddenSenderInfo; class History; +enum class ReplyMarkupFlag : uint32 { + None = (1U << 0), + ForceReply = (1U << 1), + HasSwitchInlineButton = (1U << 2), + Inline = (1U << 3), + Resize = (1U << 4), + SingleUse = (1U << 5), + Selective = (1U << 6), +}; +inline constexpr bool is_flag_type(ReplyMarkupFlag) { return true; } +using ReplyMarkupFlags = base::flags; + +[[nodiscard]] MessageFlags FlagsFromMTP(MTPDmessage::Flags flags); +[[nodiscard]] MessageFlags FlagsFromMTP(MTPDmessageService::Flags flags); + class HistoryItem : public RuntimeComposer { public: static not_null Create( not_null history, const MTPMessage &message, - MTPDmessage_ClientFlags clientFlags); + MessageFlags localFlags); struct Destroyer { void operator()(HistoryItem *value); @@ -113,10 +128,10 @@ public: void destroy(); [[nodiscard]] bool out() const { - return _flags & MTPDmessage::Flag::f_out; + return _flags & MessageFlag::Outgoing; } [[nodiscard]] bool isPinned() const { - return _flags & MTPDmessage::Flag::f_pinned; + return _flags & MessageFlag::Pinned; } [[nodiscard]] bool unread() const; [[nodiscard]] bool showNotification() const; @@ -149,47 +164,47 @@ public: } [[nodiscard]] bool definesReplyKeyboard() const; - [[nodiscard]] MTPDreplyKeyboardMarkup::Flags replyKeyboardFlags() const; + [[nodiscard]] ReplyMarkupFlags replyKeyboardFlags() const; [[nodiscard]] bool hasSwitchInlineButton() const { - return _clientFlags & MTPDmessage_ClientFlag::f_has_switch_inline_button; + return _flags & MessageFlag::HasSwitchInlineButton; } [[nodiscard]] bool hasTextLinks() const { - return _clientFlags & MTPDmessage_ClientFlag::f_has_text_links; + return _flags & MessageFlag::HasTextLinks; } [[nodiscard]] bool isGroupEssential() const { - return _clientFlags & MTPDmessage_ClientFlag::f_is_group_essential; + return _flags & MessageFlag::IsGroupEssential; } [[nodiscard]] bool isLocalUpdateMedia() const { - return _clientFlags & MTPDmessage_ClientFlag::f_is_local_update_media; + return _flags & MessageFlag::IsLocalUpdateMedia; } void setIsLocalUpdateMedia(bool flag) { if (flag) { - _clientFlags |= MTPDmessage_ClientFlag::f_is_local_update_media; + _flags |= MessageFlag::IsLocalUpdateMedia; } else { - _clientFlags &= ~MTPDmessage_ClientFlag::f_is_local_update_media; + _flags &= ~MessageFlag::IsLocalUpdateMedia; } } [[nodiscard]] bool isGroupMigrate() const { return isGroupEssential() && isEmpty(); } [[nodiscard]] bool isIsolatedEmoji() const { - return _clientFlags & MTPDmessage_ClientFlag::f_isolated_emoji; + return _flags & MessageFlag::IsolatedEmoji; } [[nodiscard]] bool hasViews() const { - return _flags & MTPDmessage::Flag::f_views; + return _flags & MessageFlag::HasViews; } [[nodiscard]] bool isPost() const { - return _flags & MTPDmessage::Flag::f_post; + return _flags & MessageFlag::Post; } [[nodiscard]] bool isSilent() const { - return _flags & MTPDmessage::Flag::f_silent; + return _flags & MessageFlag::Silent; } [[nodiscard]] bool isSending() const { - return _clientFlags & MTPDmessage_ClientFlag::f_sending; + return _flags & MessageFlag::BeingSent; } [[nodiscard]] bool hasFailed() const { - return _clientFlags & MTPDmessage_ClientFlag::f_failed; + return _flags & MessageFlag::SendingFailed; } void sendFailed(); [[nodiscard]] virtual int viewsCount() const { @@ -410,8 +425,7 @@ protected: HistoryItem( not_null history, MsgId id, - MTPDmessage::Flags flags, - MTPDmessage_ClientFlags clientFlags, + MessageFlags flags, TimeId date, PeerId from); @@ -424,8 +438,7 @@ protected: const not_null _history; not_null _from; - MTPDmessage::Flags _flags = 0; - MTPDmessage_ClientFlags _clientFlags = 0; + MessageFlags _flags = 0; void invalidateChatListEntry(); diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 56b3fafb7..2cc6551a3 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_service_message.h" #include "history/view/media/history_view_document.h" #include "core/click_handler_types.h" +#include "layout/layout_position.h" #include "mainwindow.h" #include "media/audio/media_audio.h" #include "media/player/media_player_instance.h" @@ -455,9 +456,13 @@ auto ReplyMarkupClickHandler::getUrlButton() const return nullptr; } -void ReplyMarkupClickHandler::onClickImpl() const { +void ReplyMarkupClickHandler::onClick(ClickContext context) const { + if (context.button != Qt::LeftButton) { + return; + } if (const auto item = _owner->message(_itemId)) { - App::activateBotCommand(item, _row, _column); + const auto my = context.other.value(); + App::activateBotCommand(my.sessionWindow.get(), item, _row, _column); } } @@ -732,7 +737,7 @@ void ReplyKeyboard::clickHandlerPressedChanged( void ReplyKeyboard::startAnimation(int i, int j, int direction) { auto notStarted = _animations.empty(); - int indexForAnimation = (i * MatrixRowShift + j + 1) * direction; + int indexForAnimation = Layout::PositionToIndex(i, j + 1) * direction; _animations.remove(-indexForAnimation); if (!_animations.contains(indexForAnimation)) { @@ -750,8 +755,7 @@ bool ReplyKeyboard::selectedAnimationCallback(crl::time now) { } for (auto i = _animations.begin(); i != _animations.end();) { const auto index = std::abs(i->first) - 1; - const auto row = (index / MatrixRowShift); - const auto col = index % MatrixRowShift; + const auto &[row, col] = Layout::IndexToPosition(index); const auto dt = float64(now - i->second) / st::botKbDuration; if (dt >= 1) { _rows[row][col].howMuchOver = (i->first > 0) ? 1 : 0; @@ -768,8 +772,7 @@ bool ReplyKeyboard::selectedAnimationCallback(crl::time now) { void ReplyKeyboard::clearSelection() { for (const auto &[relativeIndex, time] : _animations) { const auto index = std::abs(relativeIndex) - 1; - const auto row = (index / MatrixRowShift); - const auto col = index % MatrixRowShift; + const auto &[row, col] = Layout::IndexToPosition(index); _rows[row][col].howMuchOver = 0; } _animations.clear(); @@ -893,7 +896,7 @@ void HistoryMessageReplyMarkup::createFromButtonRows( if (type == Type::SwitchInline) { // Optimization flag. // Fast check on all new messages if there is a switch button to auto-click it. - flags |= MTPDreplyKeyboardMarkup_ClientFlag::f_has_switch_inline_button; + flags |= ReplyMarkupFlag::HasSwitchInlineButton; } }, [&](const MTPDkeyboardButtonGame &data) { row.emplace_back(Type::Game, qs(data.vtext())); @@ -938,35 +941,26 @@ void HistoryMessageReplyMarkup::create(const MTPReplyMarkup &markup) { rows.clear(); inlineKeyboard = nullptr; - switch (markup.type()) { - case mtpc_replyKeyboardMarkup: { - auto &d = markup.c_replyKeyboardMarkup(); - flags = d.vflags().v; - placeholder = d.vplaceholder() ? qs(*d.vplaceholder()) : QString(); - - createFromButtonRows(d.vrows().v); - } break; - - case mtpc_replyInlineMarkup: { - auto &d = markup.c_replyInlineMarkup(); - flags = MTPDreplyKeyboardMarkup::Flags(0) | MTPDreplyKeyboardMarkup_ClientFlag::f_inline; + using Flag = ReplyMarkupFlag; + markup.match([&](const MTPDreplyKeyboardMarkup &data) { + flags = (data.is_resize() ? Flag::Resize : Flag()) + | (data.is_selective() ? Flag::Selective : Flag()) + | (data.is_single_use() ? Flag::SingleUse : Flag()); + placeholder = qs(data.vplaceholder().value_or_empty()); + createFromButtonRows(data.vrows().v); + }, [&](const MTPDreplyInlineMarkup &data) { + flags = Flag::Inline; placeholder = QString(); - - createFromButtonRows(d.vrows().v); - } break; - - case mtpc_replyKeyboardHide: { - auto &d = markup.c_replyKeyboardHide(); - flags = mtpCastFlags(d.vflags()) | MTPDreplyKeyboardMarkup_ClientFlag::f_zero; + createFromButtonRows(data.vrows().v); + }, [&](const MTPDreplyKeyboardHide &data) { + flags = Flag::None | (data.is_selective() ? Flag::Selective : Flag()); placeholder = QString(); - } break; - - case mtpc_replyKeyboardForceReply: { - auto &d = markup.c_replyKeyboardForceReply(); - flags = mtpCastFlags(d.vflags()) | MTPDreplyKeyboardMarkup_ClientFlag::f_force_reply; - placeholder = d.vplaceholder() ? qs(*d.vplaceholder()) : QString(); - } break; - } + }, [&](const MTPDreplyKeyboardForceReply &data) { + flags = Flag::ForceReply + | (data.is_selective() ? Flag::Selective : Flag()) + | (data.is_single_use() ? Flag::SingleUse : Flag()); + placeholder = qs(data.vplaceholder().value_or_empty()); + }); } void HistoryMessageReplyMarkup::create( diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index d8076a189..2173a1afe 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -225,18 +225,19 @@ struct HistoryMessageMarkupButton { }; -struct HistoryMessageReplyMarkup : public RuntimeComponent { +struct HistoryMessageReplyMarkup + : public RuntimeComponent { using Button = HistoryMessageMarkupButton; HistoryMessageReplyMarkup() = default; - HistoryMessageReplyMarkup(MTPDreplyKeyboardMarkup::Flags f) : flags(f) { + HistoryMessageReplyMarkup(ReplyMarkupFlags flags) : flags(flags) { } void create(const MTPReplyMarkup &markup); void create(const HistoryMessageReplyMarkup &markup); std::vector> rows; - MTPDreplyKeyboardMarkup::Flags flags = 0; + ReplyMarkupFlags flags = 0; QString placeholder; std::unique_ptr inlineKeyboard; @@ -246,7 +247,7 @@ private: }; -class ReplyMarkupClickHandler : public LeftButtonClickHandler { +class ReplyMarkupClickHandler : public ClickHandler { public: ReplyMarkupClickHandler( not_null owner, @@ -278,8 +279,7 @@ public: _itemId = msgId; } -protected: - void onClickImpl() const override; + void onClick(ClickContext context) const override; private: const not_null _owner; diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index 2a6bab92d..453d1f8b6 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -57,39 +57,29 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace { -[[nodiscard]] MTPDmessage::Flags NewForwardedFlags( +[[nodiscard]] MessageFlags NewForwardedFlags( not_null peer, PeerId from, not_null fwd) { - auto result = NewMessageFlags(peer) | MTPDmessage::Flag::f_fwd_from; + auto result = NewMessageFlags(peer); if (from) { - result |= MTPDmessage::Flag::f_from_id; + result |= MessageFlag::HasFromId; } if (fwd->Has()) { - result |= MTPDmessage::Flag::f_via_bot_id; + result |= MessageFlag::HasViaBot; } if (const auto media = fwd->media()) { - if (dynamic_cast(media)) { - // Drop web page if we're not allowed to send it. - if (peer->amRestricted(ChatRestriction::EmbedLinks)) { - result &= ~MTPDmessage::Flag::f_media; - } - } if ((!peer->isChannel() || peer->isMegagroup()) && media->forwardedBecomesUnread()) { - result |= MTPDmessage::Flag::f_media_unread; + result |= MessageFlag::MediaIsUnread; } } if (fwd->hasViews()) { - result |= MTPDmessage::Flag::f_views; + result |= MessageFlag::HasViews; } return result; } -[[nodiscard]] MTPDmessage_ClientFlags NewForwardedClientFlags() { - return NewMessageClientFlags(); -} - [[nodiscard]] bool CopyMarkupToForward(not_null item) { auto mediaOriginal = item->media(); if (mediaOriginal && mediaOriginal->game()) { @@ -375,15 +365,9 @@ Fn HistoryDependentItemCallback( }; } -MTPDmessage::Flags NewMessageFlags(not_null peer) { - MTPDmessage::Flags result = 0; - if (!peer->isSelf()) { - result |= MTPDmessage::Flag::f_out; - //if (p->isChat() || (p->isUser() && !p->asUser()->isBot())) { - // result |= MTPDmessage::Flag::f_unread; - //} - } - return result; +MessageFlags NewMessageFlags(not_null peer) { + return MessageFlag::BeingSent + | (peer->isSelf() ? MessageFlag() : MessageFlag::Outgoing); } bool ShouldSendSilent( @@ -421,10 +405,6 @@ MTPMessageReplyHeader NewMessageReplyHeader(const Api::SendAction &action) { return MTPMessageReplyHeader(); } -MTPDmessage_ClientFlags NewMessageClientFlags() { - return MTPDmessage_ClientFlag::f_sending; -} - QString GetErrorTextForSending( not_null peer, const HistoryItemsList &items, @@ -481,12 +461,11 @@ void HistoryMessage::FillForwardedInfo( HistoryMessage::HistoryMessage( not_null history, const MTPDmessage &data, - MTPDmessage_ClientFlags clientFlags) + MessageFlags localFlags) : HistoryItem( history, data.vid().v, - data.vflags().v, - clientFlags, + FlagsFromMTP(data.vflags().v) | localFlags, data.vdate().v, data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0)) { auto config = CreateConfig(); @@ -538,12 +517,11 @@ HistoryMessage::HistoryMessage( HistoryMessage::HistoryMessage( not_null history, const MTPDmessageService &data, - MTPDmessage_ClientFlags clientFlags) + MessageFlags localFlags) : HistoryItem( history, data.vid().v, - mtpCastFlags(data.vflags().v), - clientFlags, + FlagsFromMTP(data.vflags().v) | localFlags, data.vdate().v, data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0)) { auto config = CreateConfig(); @@ -576,8 +554,7 @@ HistoryMessage::HistoryMessage( HistoryMessage::HistoryMessage( not_null history, MsgId id, - MTPDmessage::Flags flags, - MTPDmessage_ClientFlags clientFlags, + MessageFlags flags, TimeId date, PeerId from, const QString &postAuthor, @@ -586,7 +563,6 @@ HistoryMessage::HistoryMessage( history, id, NewForwardedFlags(history->peer, from, original) | flags, - NewForwardedClientFlags() | clientFlags, date, from) { const auto peer = history->peer; @@ -621,7 +597,7 @@ HistoryMessage::HistoryMessage( config.savedFromMsgId = original->id; //} } - if (flags & MTPDmessage::Flag::f_post_author) { + if (flags & MessageFlag::HasPostAuthor) { config.author = postAuthor; } if (const auto fwdViaBot = original->viaBot()) { @@ -660,36 +636,34 @@ HistoryMessage::HistoryMessage( HistoryMessage::HistoryMessage( not_null history, MsgId id, - MTPDmessage::Flags flags, - MTPDmessage_ClientFlags clientFlags, + MessageFlags flags, MsgId replyTo, UserId viaBotId, TimeId date, PeerId from, const QString &postAuthor, - const TextWithEntities &textWithEntities) + const TextWithEntities &textWithEntities, + const MTPMessageMedia &media, + const MTPReplyMarkup &markup, + uint64 groupedId) : HistoryItem( history, id, - flags & ~MTPDmessage::Flag::f_reply_markup, - clientFlags, + flags, date, - (flags & MTPDmessage::Flag::f_from_id) ? from : 0) { - createComponentsHelper( - flags & ~MTPDmessage::Flag::f_reply_markup, - replyTo, - viaBotId, - postAuthor, - MTPReplyMarkup()); - + (flags & MessageFlag::HasFromId) ? from : 0) { + createComponentsHelper(flags, replyTo, viaBotId, postAuthor, markup); + setMedia(media); setText(textWithEntities); + if (groupedId) { + setGroupId(MessageGroupId::FromRaw(history->peer->id, groupedId)); + } } HistoryMessage::HistoryMessage( not_null history, MsgId id, - MTPDmessage::Flags flags, - MTPDmessage_ClientFlags clientFlags, + MessageFlags flags, MsgId replyTo, UserId viaBotId, TimeId date, @@ -703,9 +677,8 @@ HistoryMessage::HistoryMessage( history, id, flags, - clientFlags, date, - (flags & MTPDmessage::Flag::f_from_id) ? from : 0) { + (flags & MessageFlag::HasFromId) ? from : 0) { createComponentsHelper(flags, replyTo, viaBotId, postAuthor, markup); _media = std::make_unique(this, document); @@ -720,8 +693,7 @@ HistoryMessage::HistoryMessage( HistoryMessage::HistoryMessage( not_null history, MsgId id, - MTPDmessage::Flags flags, - MTPDmessage_ClientFlags clientFlags, + MessageFlags flags, MsgId replyTo, UserId viaBotId, TimeId date, @@ -735,9 +707,8 @@ HistoryMessage::HistoryMessage( history, id, flags, - clientFlags, date, - (flags & MTPDmessage::Flag::f_from_id) ? from : 0) { + (flags & MessageFlag::HasFromId) ? from : 0) { createComponentsHelper(flags, replyTo, viaBotId, postAuthor, markup); _media = std::make_unique(this, photo); @@ -752,8 +723,7 @@ HistoryMessage::HistoryMessage( HistoryMessage::HistoryMessage( not_null history, MsgId id, - MTPDmessage::Flags flags, - MTPDmessage_ClientFlags clientFlags, + MessageFlags flags, MsgId replyTo, UserId viaBotId, TimeId date, @@ -765,9 +735,8 @@ HistoryMessage::HistoryMessage( history, id, flags, - clientFlags, date, - (flags & MTPDmessage::Flag::f_from_id) ? from : 0) { + (flags & MessageFlag::HasFromId) ? from : 0) { createComponentsHelper(flags, replyTo, viaBotId, postAuthor, markup); _media = std::make_unique(this, game); @@ -775,22 +744,22 @@ HistoryMessage::HistoryMessage( } void HistoryMessage::createComponentsHelper( - MTPDmessage::Flags flags, + MessageFlags flags, MsgId replyTo, UserId viaBotId, const QString &postAuthor, const MTPReplyMarkup &markup) { auto config = CreateConfig(); - if (flags & MTPDmessage::Flag::f_via_bot_id) config.viaBotId = viaBotId; - if (flags & MTPDmessage::Flag::f_reply_to) { + if (flags & MessageFlag::HasViaBot) config.viaBotId = viaBotId; + if (flags & MessageFlag::HasReplyInfo) { config.replyTo = replyTo; const auto replyToTop = LookupReplyToTop(history(), replyTo); config.replyToTop = replyToTop ? replyToTop : replyTo; } - if (flags & MTPDmessage::Flag::f_reply_markup) config.mtpMarkup = &markup; - if (flags & MTPDmessage::Flag::f_post_author) config.author = postAuthor; - if (flags & MTPDmessage::Flag::f_views) config.viewsCount = 1; + if (flags & MessageFlag::HasReplyMarkup) config.mtpMarkup = &markup; + if (flags & MessageFlag::HasPostAuthor) config.author = postAuthor; + if (flags & MessageFlag::HasViews) config.viewsCount = 1; createComponents(config); } @@ -1150,8 +1119,8 @@ void HistoryMessage::createComponents(const CreateConfig &config) { } else if (config.inlineMarkup) { markup->create(*config.inlineMarkup); } - if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_has_switch_inline_button) { - _clientFlags |= MTPDmessage_ClientFlag::f_has_switch_inline_button; + if (markup->flags & ReplyMarkupFlag::HasSwitchInlineButton) { + _flags |= MessageFlag::HasSwitchInlineButton; } } const auto from = displayFrom(); @@ -1359,7 +1328,6 @@ std::unique_ptr HistoryMessage::CreateMedia( }, [](const MTPDmessageMediaUnsupported &) -> Result { return nullptr; }); - return nullptr; } void HistoryMessage::replaceBuyWithReceiptInMarkup() { @@ -1390,11 +1358,14 @@ void HistoryMessage::applyEdition(const MTPDmessage &message) { // } //} - const auto copyFlags = MTPDmessage::Flag::f_edit_hide; - _flags = (_flags & ~copyFlags) | (message.vflags().v & copyFlags); + if (message.is_edit_hide()) { + _flags |= MessageFlag::HideEdited; + } else { + _flags &= ~MessageFlag::HideEdited; + } if (const auto editDate = message.vedit_date()) { - _flags |= MTPDmessage::Flag::f_edit_date; + //_flags |= MTPDmessage::Flag::f_edit_date; if (!Has()) { AddComponents(HistoryMessageEdited::Bit()); } @@ -1452,11 +1423,11 @@ void HistoryMessage::updateSentContent( const MTPMessageMedia *media) { const auto isolated = isolatedEmoji(); setText(textWithEntities); - if (_clientFlags & MTPDmessage_ClientFlag::f_from_inline_bot) { + if (_flags & MessageFlag::FromInlineBot) { if (!media || !_media || !_media->updateInlineResultMedia(*media)) { refreshSentMedia(media); } - _clientFlags &= ~MTPDmessage_ClientFlag::f_from_inline_bot; + _flags &= ~MessageFlag::FromInlineBot; } else if (media || _media || !isolated || isolated != isolatedEmoji()) { if (!media || !_media || !_media->updateSentMedia(*media)) { refreshSentMedia(media); @@ -1566,7 +1537,7 @@ void HistoryMessage::setText(const TextWithEntities &textWithEntities) { if (type == EntityType::Url || type == EntityType::CustomUrl || type == EntityType::Email) { - _clientFlags |= MTPDmessage_ClientFlag::f_has_text_links; + _flags |= MessageFlag::HasTextLinks; break; } } @@ -1617,16 +1588,16 @@ void HistoryMessage::setEmptyText() { } void HistoryMessage::clearIsolatedEmoji() { - if (!(_clientFlags & MTPDmessage_ClientFlag::f_isolated_emoji)) { + if (!(_flags & MessageFlag::IsolatedEmoji)) { return; } history()->session().emojiStickersPack().remove(this); - _clientFlags &= ~MTPDmessage_ClientFlag::f_isolated_emoji; + _flags &= ~MessageFlag::IsolatedEmoji; } void HistoryMessage::checkIsolatedEmoji() { if (history()->session().emojiStickersPack().add(this)) { - _clientFlags |= MTPDmessage_ClientFlag::f_isolated_emoji; + _flags |= MessageFlag::IsolatedEmoji; } } @@ -1638,8 +1609,8 @@ void HistoryMessage::setReplyMarkup(const MTPReplyMarkup *markup) { Data::MessageUpdate::Flag::ReplyMarkup); }; if (!markup) { - if (_flags & MTPDmessage::Flag::f_reply_markup) { - _flags &= ~MTPDmessage::Flag::f_reply_markup; + if (_flags & MessageFlag::HasReplyMarkup) { + _flags &= ~MessageFlag::HasReplyMarkup; if (Has()) { RemoveComponents(HistoryMessageReplyMarkup::Bit()); } @@ -1650,22 +1621,23 @@ void HistoryMessage::setReplyMarkup(const MTPReplyMarkup *markup) { // optimization: don't create markup component for the case // MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag - if (markup->type() == mtpc_replyKeyboardHide && markup->c_replyKeyboardHide().vflags().v == 0) { + if (markup->type() == mtpc_replyKeyboardHide + && markup->c_replyKeyboardHide().vflags().v == 0) { bool changed = false; if (Has()) { RemoveComponents(HistoryMessageReplyMarkup::Bit()); changed = true; } - if (!(_flags & MTPDmessage::Flag::f_reply_markup)) { - _flags |= MTPDmessage::Flag::f_reply_markup; + if (!(_flags & MessageFlag::HasReplyMarkup)) { + _flags |= MessageFlag::HasReplyMarkup; changed = true; } if (changed) { requestUpdate(); } } else { - if (!(_flags & MTPDmessage::Flag::f_reply_markup)) { - _flags |= MTPDmessage::Flag::f_reply_markup; + if (!(_flags & MessageFlag::HasReplyMarkup)) { + _flags |= MessageFlag::HasReplyMarkup; } if (!Has()) { AddComponents(HistoryMessageReplyMarkup::Bit()); diff --git a/Telegram/SourceFiles/history/history_message.h b/Telegram/SourceFiles/history/history_message.h index 3a660aefb..6331fdefe 100644 --- a/Telegram/SourceFiles/history/history_message.h +++ b/Telegram/SourceFiles/history/history_message.h @@ -24,11 +24,10 @@ struct HistoryMessageViews; [[nodiscard]] Fn HistoryDependentItemCallback( not_null item); -[[nodiscard]] MTPDmessage::Flags NewMessageFlags(not_null peer); +[[nodiscard]] MessageFlags NewMessageFlags(not_null peer); [[nodiscard]] bool ShouldSendSilent( not_null peer, const Api::SendOptions &options); -[[nodiscard]] MTPDmessage_ClientFlags NewMessageClientFlags(); [[nodiscard]] MsgId LookupReplyToTop( not_null history, MsgId replyToId); @@ -50,16 +49,15 @@ public: HistoryMessage( not_null history, const MTPDmessage &data, - MTPDmessage_ClientFlags clientFlags); + MessageFlags localFlags); HistoryMessage( not_null history, const MTPDmessageService &data, - MTPDmessage_ClientFlags clientFlags); + MessageFlags localFlags); HistoryMessage( not_null history, MsgId id, - MTPDmessage::Flags flags, - MTPDmessage_ClientFlags clientFlags, + MessageFlags flags, TimeId date, PeerId from, const QString &postAuthor, @@ -67,19 +65,20 @@ public: HistoryMessage( not_null history, MsgId id, - MTPDmessage::Flags flags, - MTPDmessage_ClientFlags clientFlags, + MessageFlags flags, MsgId replyTo, UserId viaBotId, TimeId date, PeerId from, const QString &postAuthor, - const TextWithEntities &textWithEntities); // local message + const TextWithEntities &textWithEntities, + const MTPMessageMedia &media, + const MTPReplyMarkup &markup, + uint64 groupedId); // local message HistoryMessage( not_null history, MsgId id, - MTPDmessage::Flags flags, - MTPDmessage_ClientFlags clientFlags, + MessageFlags flags, MsgId replyTo, UserId viaBotId, TimeId date, @@ -92,8 +91,7 @@ public: HistoryMessage( not_null history, MsgId id, - MTPDmessage::Flags flags, - MTPDmessage_ClientFlags clientFlags, + MessageFlags flags, MsgId replyTo, UserId viaBotId, TimeId date, @@ -106,8 +104,7 @@ public: HistoryMessage( not_null history, MsgId id, - MTPDmessage::Flags flags, - MTPDmessage_ClientFlags clientFlags, + MessageFlags flags, MsgId replyTo, UserId viaBotId, TimeId date, @@ -131,7 +128,7 @@ public: [[nodiscard]] bool uploading() const; [[nodiscard]] bool hideEditedBadge() const { - return (_flags & MTPDmessage::Flag::f_edit_hide); + return (_flags & MessageFlag::HideEdited); } void setViewsCount(int count) override; @@ -148,7 +145,18 @@ public: [[nodiscard]] QString notificationHeader() const override; + // Looks on: + // f_edit_hide + // f_edit_date + // f_entities + // f_reply_markup + // f_media + // f_views + // f_forwards + // f_replies + // f_ttl_period void applyEdition(const MTPDmessage &message) override; + void applyEdition(const MTPDmessageService &message) override; void updateSentContent( const TextWithEntities &textWithEntities, @@ -215,7 +223,7 @@ private: void setEmptyText(); [[nodiscard]] bool isTooOldForEdit(TimeId now) const; [[nodiscard]] bool isLegacyMessage() const { - return _flags & MTPDmessage::Flag::f_legacy; + return _flags & MessageFlag::Legacy; } [[nodiscard]] bool checkCommentsLinkedChat(ChannelId id) const; @@ -230,7 +238,12 @@ private: void setReplyMarkup(const MTPReplyMarkup *markup); struct CreateConfig; - void createComponentsHelper(MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, const QString &postAuthor, const MTPReplyMarkup &markup); + void createComponentsHelper( + MessageFlags flags, + MsgId replyTo, + UserId viaBotId, + const QString &postAuthor, + const MTPReplyMarkup &markup); void createComponents(const CreateConfig &config); void setupForwardedComponent(const CreateConfig &config); void changeReplyToTopCounter( diff --git a/Telegram/SourceFiles/history/history_service.cpp b/Telegram/SourceFiles/history/history_service.cpp index 261c87f36..3b3b803b1 100644 --- a/Telegram/SourceFiles/history/history_service.cpp +++ b/Telegram/SourceFiles/history/history_service.cpp @@ -12,7 +12,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "main/main_domain.h" // Core::App().domain().activate(). #include "apiwrap.h" -#include "layout.h" #include "history/history.h" #include "history/view/media/history_view_invoice.h" #include "history/history_message.h" @@ -531,13 +530,13 @@ void HistoryService::applyAction(const MTPMessageAction &action) { }, [](const MTPDphotoEmpty &) { }); }, [&](const MTPDmessageActionChatCreate &) { - _clientFlags |= MTPDmessage_ClientFlag::f_is_group_essential; + _flags |= MessageFlag::IsGroupEssential; }, [&](const MTPDmessageActionChannelCreate &) { - _clientFlags |= MTPDmessage_ClientFlag::f_is_group_essential; + _flags |= MessageFlag::IsGroupEssential; }, [&](const MTPDmessageActionChatMigrateTo &) { - _clientFlags |= MTPDmessage_ClientFlag::f_is_group_essential; + _flags |= MessageFlag::IsGroupEssential; }, [&](const MTPDmessageActionChannelMigrateFrom &) { - _clientFlags |= MTPDmessage_ClientFlag::f_is_group_essential; + _flags |= MessageFlag::IsGroupEssential; }, [](const auto &) { }); } @@ -849,12 +848,11 @@ HistoryService::PreparedText HistoryService::prepareCallScheduledText( HistoryService::HistoryService( not_null history, const MTPDmessage &data, - MTPDmessage_ClientFlags clientFlags) + MessageFlags localFlags) : HistoryItem( history, data.vid().v, - data.vflags().v, - clientFlags, + FlagsFromMTP(data.vflags().v) | localFlags, data.vdate().v, data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0)) { createFromMtp(data); @@ -864,12 +862,11 @@ HistoryService::HistoryService( HistoryService::HistoryService( not_null history, const MTPDmessageService &data, - MTPDmessage_ClientFlags clientFlags) + MessageFlags localFlags) : HistoryItem( history, data.vid().v, - mtpCastFlags(data.vflags().v), - clientFlags, + FlagsFromMTP(data.vflags().v) | localFlags, data.vdate().v, data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0)) { createFromMtp(data); @@ -879,14 +876,13 @@ HistoryService::HistoryService( HistoryService::HistoryService( not_null history, MsgId id, - MTPDmessage_ClientFlags clientFlags, + MessageFlags flags, TimeId date, const PreparedText &message, - MTPDmessage::Flags flags, PeerId from, PhotoData *photo, bool showTime) -: HistoryItem(history, id, flags, clientFlags, date, from) { +: HistoryItem(history, id, flags, date, from) { setNeedTime(showTime); setServiceText(message); if (photo) { @@ -1260,14 +1256,12 @@ HistoryService::PreparedText GenerateJoinedText( not_null GenerateJoinedMessage( not_null history, TimeId inviteDate, - not_null inviter, - MTPDmessage::Flags flags) { + not_null inviter) { return history->makeServiceMessage( history->owner().nextLocalMessageId(), - MTPDmessage_ClientFlag::f_local_history_entry, + MessageFlag::LocalHistoryEntry, inviteDate, - GenerateJoinedText(history, inviter), - flags); + GenerateJoinedText(history, inviter)); } std::optional PeerHasThisCall( diff --git a/Telegram/SourceFiles/history/history_service.h b/Telegram/SourceFiles/history/history_service.h index 9c27f038e..dea413047 100644 --- a/Telegram/SourceFiles/history/history_service.h +++ b/Telegram/SourceFiles/history/history_service.h @@ -70,18 +70,17 @@ public: HistoryService( not_null history, const MTPDmessage &data, - MTPDmessage_ClientFlags clientFlags); + MessageFlags localFlags); HistoryService( not_null history, const MTPDmessageService &data, - MTPDmessage_ClientFlags clientFlags); + MessageFlags localFlags); HistoryService( not_null history, MsgId id, - MTPDmessage_ClientFlags clientFlags, + MessageFlags flags, TimeId date, const PreparedText &message, - MTPDmessage::Flags flags = 0, PeerId from = 0, PhotoData *photo = nullptr, bool showTime = true); @@ -187,8 +186,7 @@ private: [[nodiscard]] not_null GenerateJoinedMessage( not_null history, TimeId inviteDate, - not_null inviter, - MTPDmessage::Flags flags); + not_null inviter); [[nodiscard]] std::optional PeerHasThisCall( not_null peer, uint64 id); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 2b3fbc8e4..bb1b82acd 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -315,7 +315,7 @@ HistoryWidget::HistoryWidget( tr::lng_message_ph()) , _kbScroll(this, st::botKbScroll) , _keyboard(_kbScroll->setOwnedWidget(object_ptr( - &session(), + controller, this))) , _membersDropdownShowTimer([=] { showMembersDropdown(); }) , _scrollTimer([=] { scrollByTimer(); }) @@ -413,6 +413,11 @@ HistoryWidget::HistoryWidget( InitMessageField(controller, _field); + _keyboard->sendCommandRequests( + ) | rpl::start_with_next([=](Bot::SendCommandRequest r) { + sendBotCommand(r); + }, lifetime()); + _fieldAutocomplete->mentionChosen( ) | rpl::start_with_next([=](FieldAutocomplete::MentionChosen data) { insertMention(data.user, data.method); @@ -1370,7 +1375,7 @@ void HistoryWidget::insertHashtagOrBotCommand( // Send bot command at once, if it was not inserted by pressing Tab. if (str.at(0) == '/' && method != FieldAutocomplete::ChooseMethod::ByTab) { - App::sendBotCommand(_peer, nullptr, str, replyToId()); + sendBotCommand({ _peer, str, FullMsgId(), replyToId() }); session().api().finishForwarding(Api::SendAction(_history)); setFieldText(_field->getTextWithTagsPart(_field->textCursor().position())); } else { @@ -2122,6 +2127,8 @@ void HistoryWidget::showHistory( noSelectingScroll(); _nonEmptySelection = false; _itemRevealPending.clear(); + _itemRevealAnimations.clear(); + _itemsRevealHeight = 0; if (_peer) { _history = _peer->owner().history(_peer); @@ -3847,34 +3854,33 @@ void HistoryWidget::mouseReleaseEvent(QMouseEvent *e) { } } -void HistoryWidget::sendBotCommand( - not_null peer, - UserData *bot, - const QString &cmd, - MsgId replyTo) { // replyTo != 0 from ReplyKeyboardMarkup, == 0 from cmd links - if (_peer != peer.get()) { +void HistoryWidget::sendBotCommand(const Bot::SendCommandRequest &request) { +// replyTo != 0 from ReplyKeyboardMarkup, == 0 from command links + if (_peer != request.peer.get()) { return; } else if (showSlowmodeError()) { return; } - bool lastKeyboardUsed = (_keyboard->forMsgId() == FullMsgId(_channel, _history->lastKeyboardId)) && (_keyboard->forMsgId() == FullMsgId(_channel, replyTo)); + const auto lastKeyboardUsed = (_keyboard->forMsgId() + == FullMsgId(_channel, _history->lastKeyboardId)) + && (_keyboard->forMsgId() == FullMsgId(_channel, request.replyTo)); // 'bot' may be nullptr in case of sending from FieldAutocomplete. - const auto toSend = (replyTo || !bot) - ? cmd - : HistoryView::WrapBotCommandInChat(_peer, cmd, bot); + const auto toSend = (request.replyTo/* || !bot*/) + ? request.command + : Bot::WrapCommandInChat(_peer, request.command, request.context); auto message = ApiWrap::MessageToSend(_history); message.textWithTags = { toSend, TextWithTags::Tags() }; - message.action.replyTo = replyTo + message.action.replyTo = request.replyTo ? ((!_peer->isUser()/* && (botStatus == 0 || botStatus == 2)*/) - ? replyTo + ? request.replyTo : replyToId()) : 0; session().api().sendMessage(std::move(message)); - if (replyTo) { - if (_replyToId == replyTo) { + if (request.replyTo) { + if (_replyToId == request.replyTo) { cancelReply(); saveCloudDraft(); } @@ -4862,10 +4868,6 @@ void HistoryWidget::updateHistoryItemsByTimer() { } } -PeerData *HistoryWidget::ui_getPeerForMouseAction() { - return _peer; -} - void HistoryWidget::handlePendingHistoryUpdate() { if (hasPendingResizedItems() || _updateHistoryGeometryRequired) { updateHistoryGeometry(); diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 8b8f88e83..f0c899e93 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_drag_area.h" #include "ui/widgets/tooltip.h" #include "mainwidget.h" +#include "chat_helpers/bot_command.h" #include "chat_helpers/field_autocomplete.h" #include "window/section_widget.h" #include "ui/widgets/input_fields.h" @@ -214,11 +215,7 @@ public: void escape(); - void sendBotCommand( - not_null peer, - UserData *bot, - const QString &cmd, - MsgId replyTo); + void sendBotCommand(const Bot::SendCommandRequest &request); void hideSingleUseKeyboard(PeerData *peer, MsgId replyTo); bool insertBotCommand(const QString &cmd); @@ -290,8 +287,6 @@ public: bool floatPlayerHandleWheelEvent(QEvent *e) override; QRect floatPlayerAvailableRect() override; - PeerData *ui_getPeerForMouseAction(); - bool notify_switchInlineBotButtonReceived(const QString &query, UserData *samePeerBot, MsgId samePeerReplyTo); void notify_showScheduledButtonChanged(); 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 de88d8ae5..b535b88be 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -1095,7 +1095,9 @@ void ComposeControls::initKeyHandler() { auto keyEvent = static_cast(e.get()); const auto key = keyEvent->key(); const auto isCtrl = keyEvent->modifiers() == Qt::ControlModifier; - const auto hasModifiers = keyEvent->modifiers() != Qt::NoModifier; + const auto hasModifiers = (Qt::NoModifier != + (keyEvent->modifiers() + & ~(Qt::KeypadModifier | Qt::GroupSwitchModifier))); if (key == Qt::Key_O && isCtrl) { _attachRequests.fire({}); return; diff --git a/Telegram/SourceFiles/history/view/history_view_contact_status.cpp b/Telegram/SourceFiles/history/view/history_view_contact_status.cpp index a259a9a59..1df574ee6 100644 --- a/Telegram/SourceFiles/history/view/history_view_contact_status.cpp +++ b/Telegram/SourceFiles/history/view/history_view_contact_status.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/labels.h" #include "ui/layers/generic_box.h" #include "ui/toast/toast.h" +#include "ui/text/format_values.h" // Ui::FormatPhone #include "ui/text/text_utilities.h" #include "data/data_peer.h" #include "data/data_user.h" @@ -23,10 +24,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_controller.h" #include "window/window_session_controller.h" #include "apiwrap.h" +#include "api/api_blocked_peers.h" #include "main/main_session.h" #include "boxes/confirm_box.h" #include "boxes/peers/edit_contact_box.h" -#include "app.h" #include "styles/style_chat.h" #include "styles/style_layers.h" @@ -345,7 +346,7 @@ void ContactStatus::setupShareHandler(not_null user) { tr::now, lt_phone, Ui::Text::WithEntities( - App::formatPhone(user->session().user()->phone())), + Ui::FormatPhone(user->session().user()->phone())), lt_user, Ui::Text::Bold(user->name), Ui::Text::WithEntities), @@ -393,7 +394,7 @@ void ContactStatus::setupReportHandler(not_null peer) { _controller->showBackFromStack(); }); if (const auto user = peer->asUser()) { - peer->session().api().blockPeer(user); + peer->session().api().blockedPeers().block(user); } const auto text = ((peer->isChat() || peer->isMegagroup()) ? tr::lng_report_spam_sure_group diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index ff3c5d221..a832e4926 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -156,6 +156,18 @@ void AddPhotoActions( } } +void SaveGif( + not_null controller, + FullMsgId itemId) { + if (const auto item = controller->session().data().message(itemId)) { + if (const auto media = item->media()) { + if (const auto document = media->document()) { + Api::ToggleSavedGif(document, item->fullId(), true); + } + } + } +} + void OpenGif( not_null controller, FullMsgId itemId) { @@ -227,6 +239,11 @@ void AddDocumentActions( OpenGif(list->controller(), contextId); }); } + if (document->isGifv()) { + menu->addAction(tr::lng_context_save_gif(tr::now), [=] { + SaveGif(list->controller(), contextId); + }); + } } if (document->sticker() && document->sticker()->set) { menu->addAction( @@ -420,16 +437,12 @@ bool AddSendNowMessageAction( const auto itemId = item->fullId(); menu->addAction(tr::lng_context_send_now_msg(tr::now), [=] { if (const auto item = owner->message(itemId)) { - const auto callback = [=] { - request.navigation->showBackFromStack(); - }; Window::ShowSendNowMessagesBox( request.navigation, item->history(), (asGroup ? owner->itemOrItsGroup(item) - : MessageIdsList{ 1, itemId }), - callback); + : MessageIdsList{ 1, itemId })); } }); return true; diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 3c1e11fdd..106b20641 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -30,7 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_groups.h" #include "data/data_media_types.h" #include "lang/lang_keys.h" -#include "layout.h" +#include "layout/layout_selection.h" #include "app.h" #include "styles/style_chat.h" @@ -170,6 +170,9 @@ auto SimpleElementDelegate::elementPathShiftGradient() return _pathGradient.get(); } +void SimpleElementDelegate::elementReplyTo(const FullMsgId &to) { +} + TextSelection UnshiftItemSelection( TextSelection selection, uint16 byLength) { diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index f1f7377b0..355283f67 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -84,6 +84,7 @@ public: virtual void elementHandleViaClick(not_null bot) = 0; virtual bool elementIsChatWide() = 0; virtual not_null elementPathShiftGradient() = 0; + virtual void elementReplyTo(const FullMsgId &to) = 0; virtual ~ElementDelegate() { } @@ -138,6 +139,7 @@ public: void elementHandleViaClick(not_null bot) override; bool elementIsChatWide() override; not_null elementPathShiftGradient() override; + void elementReplyTo(const FullMsgId &to) override; private: const not_null _controller; @@ -384,7 +386,7 @@ private: // This should be called only from previousInBlocksChanged() or when // DateBadge or UnreadBar bit is changed in the Composer mask // then the result should be cached in a client side flag - // MTPDmessage_ClientFlag::f_attach_to_previous. + // HistoryView::Element::Flag::AttachedToPrevious. void recountAttachToPreviousInBlocks(); QSize countOptimalSize() final override; diff --git a/Telegram/SourceFiles/history/view/history_view_empty_list_bubble.cpp b/Telegram/SourceFiles/history/view/history_view_empty_list_bubble.cpp new file mode 100644 index 000000000..5e189791b --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_empty_list_bubble.cpp @@ -0,0 +1,76 @@ +/* +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 "history/view/history_view_empty_list_bubble.h" + +#include "history/view/history_view_list_widget.h" +#include "history/view/history_view_service_message.h" + +namespace HistoryView { + +EmptyListBubbleWidget::EmptyListBubbleWidget( + not_null parent, + const style::margins &padding) +: RpWidget(parent) +, _padding(padding) { + + parent->sizeValue( + ) | rpl::start_with_next([=](const QSize &s) { + updateGeometry(s); + }, lifetime()); +} + +void EmptyListBubbleWidget::updateGeometry(const QSize &size) { + const auto w = _forceWidth + ? _forceWidth + : std::min( + _text.maxWidth() + _padding.left() + _padding.right(), + size.width()); + _innerWidth = w - _padding.left() - _padding.right(); + const auto h = _padding.top() + + _text.countHeight(_innerWidth) + + _padding.bottom(); + resize(w, h); + move((size.width() - w) / 2, (size.height() - h) / 3); +} + +void EmptyListBubbleWidget::paintEvent(QPaintEvent *e) { + Painter p(this); + + const auto r = rect(); + HistoryView::ServiceMessagePainter::paintBubble( + p, + r.x(), + r.y(), + r.width(), + r.height()); + + p.setPen(st::msgServiceFg); + _text.draw( + p, + r.x() + _padding.left(), + r.y() + _padding.top(), + _innerWidth, + style::al_top); +} + +void EmptyListBubbleWidget::setText( + const TextWithEntities &textWithEntities) { + auto options = _defaultOptions; + options.flags |= TextParseMarkdown; + _text.setMarkedText(st::defaultTextStyle, textWithEntities, options); + updateGeometry(size()); +} + +void EmptyListBubbleWidget::setForceWidth(int width) { + if (_forceWidth != width) { + _forceWidth = width; + updateGeometry(size()); + } +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_empty_list_bubble.h b/Telegram/SourceFiles/history/view/history_view_empty_list_bubble.h new file mode 100644 index 000000000..9a789b828 --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_empty_list_bubble.h @@ -0,0 +1,38 @@ +/* +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 "ui/rp_widget.h" + +namespace HistoryView { + +class ListWidget; + +class EmptyListBubbleWidget : public Ui::RpWidget { +public: + EmptyListBubbleWidget( + not_null parent, + const style::margins &padding); + + void setText(const TextWithEntities &textWithEntities); + void setForceWidth(int width); + +protected: + void paintEvent(QPaintEvent *e) override; + +private: + void updateGeometry(const QSize &size); + + const style::margins &_padding; + Ui::Text::String _text; + int _innerWidth = 0; + int _forceWidth = 0; + +}; + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 29318093b..07d32f3af 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -23,7 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mainwidget.h" #include "core/click_handler_types.h" #include "apiwrap.h" -#include "layout.h" +#include "layout/layout_selection.h" #include "window/window_adaptive.h" #include "window/window_session_controller.h" #include "window/window_peer_menu.h" @@ -382,6 +382,9 @@ void ListWidget::refreshRows(const Data::MessagesSlice &old) { if (!_itemsRevealHeight) { mouseActionUpdate(QCursor::pos()); } + if (_emptyInfo) { + _emptyInfo->setVisible(isEmpty()); + } _delegate->listContentRefreshed(); } @@ -1376,6 +1379,10 @@ not_null ListWidget::elementPathShiftGradient() { return _pathGradient.get(); } +void ListWidget::elementReplyTo(const FullMsgId &to) { + replyToMessageRequestNotify(to); +} + void ListWidget::saveState(not_null memento) { memento->setAroundPosition(_aroundPosition); auto state = countScrollState(); @@ -1470,7 +1477,9 @@ void ListWidget::revealItemsCallback() { ? (_minHeight - _itemsHeight - st::historyPaddingBottom) : 0; const auto wasHeight = height(); - const auto nowHeight = std::max(_minHeight, wasHeight + delta); + const auto nowHeight = _itemsTop + + _itemsHeight + + st::historyPaddingBottom; if (wasHeight != nowHeight) { resize(width(), nowHeight); } @@ -2938,6 +2947,10 @@ void ListWidget::replyNextMessage(FullMsgId fullId, bool next) { } } +void ListWidget::setEmptyInfoWidget(base::unique_qptr &&w) { + _emptyInfo = std::move(w); +} + ListWidget::~ListWidget() = default; void ConfirmDeleteSelectedItems(not_null widget) { @@ -3004,41 +3017,16 @@ void ConfirmSendNowSelectedItems(not_null widget) { if (!history) { return; } + const auto clearSelection = [weak = Ui::MakeWeak(widget)] { + if (const auto strong = weak.data()) { + strong->cancelSelection(); + } + }; Window::ShowSendNowMessagesBox( navigation, history, widget->getSelectedIds(), - [=] { navigation->showBackFromStack(); }); -} - -QString WrapBotCommandInChat( - not_null peer, - const QString &command, - const FullMsgId &context) { - auto result = command; - if (const auto item = peer->owner().message(context)) { - if (const auto user = item->fromOriginal()->asUser()) { - return WrapBotCommandInChat(peer, command, user); - } - } - return result; -} - -QString WrapBotCommandInChat( - not_null peer, - const QString &command, - not_null bot) { - if (!bot->isBot() || bot->username.isEmpty()) { - return command; - } - const auto botStatus = peer->isChat() - ? peer->asChat()->botStatus - : peer->isMegagroup() - ? peer->asChannel()->mgInfo->botStatus - : -1; - return ((command.indexOf('@') < 2) && (botStatus == 0 || botStatus == 2)) - ? command + '@' + bot->username - : command; + clearSelection); } } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index 67c256cde..5d0910985 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -256,6 +256,9 @@ public: void elementHandleViaClick(not_null bot) override; bool elementIsChatWide() override; not_null elementPathShiftGradient() override; + void elementReplyTo(const FullMsgId &to) override; + + void setEmptyInfoWidget(base::unique_qptr &&w); ~ListWidget(); @@ -524,6 +527,8 @@ private: const std::unique_ptr _pathGradient; + base::unique_qptr _emptyInfo = nullptr; + int _minHeight = 0; int _visibleTop = 0; int _visibleBottom = 0; @@ -594,13 +599,4 @@ void ConfirmDeleteSelectedItems(not_null widget); void ConfirmForwardSelectedItems(not_null widget); void ConfirmSendNowSelectedItems(not_null widget); -[[nodiscard]] QString WrapBotCommandInChat( - not_null peer, - const QString &command, - const FullMsgId &context); -[[nodiscard]] QString WrapBotCommandInChat( - not_null peer, - const QString &command, - not_null bot); - } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 3c7ca8a86..7498255d2 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/history_view_message.h" +#include "core/click_handler_types.h" // ClickHandlerContext #include "history/view/history_view_cursor_state.h" #include "history/history_item_components.h" #include "history/history_message.h" @@ -25,12 +26,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "lang/lang_keys.h" #include "mainwidget.h" -#include "mainwindow.h" #include "main/main_session.h" #include "window/window_session_controller.h" #include "apiwrap.h" -#include "layout.h" -#include "facades.h" +#include "layout/layout_selection.h" + #include "styles/style_widgets.h" #include "styles/style_chat.h" #include "styles/style_dialogs.h" @@ -40,6 +40,15 @@ namespace { const auto kPsaTooltipPrefix = "cloud_lng_tooltip_psa_"; +std::optional ExtractController( + const ClickContext &context) { + const auto my = context.other.value(); + if (const auto controller = my.sessionWindow.get()) { + return controller; + } + return std::nullopt; +} + class KeyboardStyle : public ReplyKeyboard::Style { public: using ReplyKeyboard::Style::Style; @@ -1396,21 +1405,25 @@ bool Message::getStateCommentsButton( ClickHandlerPtr Message::createGoToCommentsLink() const { const auto fullId = data()->fullId(); - return std::make_shared([=] { - if (const auto window = App::wnd()) { - if (const auto controller = window->sessionController()) { - if (const auto item = controller->session().data().message(fullId)) { - const auto history = item->history(); - if (const auto channel = history->peer->asChannel()) { - if (channel->invitePeekExpires()) { - Ui::Toast::Show( - tr::lng_channel_invite_private(tr::now)); - return; - } - } - controller->showRepliesForMessage(history, item->id); + const auto sessionId = data()->history()->session().uniqueId(); + return std::make_shared([=](ClickContext context) { + const auto controller = ExtractController(context).value_or(nullptr); + if (!controller) { + return; + } + if (controller->session().uniqueId() != sessionId) { + return; + } + if (const auto item = controller->session().data().message(fullId)) { + const auto history = item->history(); + if (const auto channel = history->peer->asChannel()) { + if (channel->invitePeekExpires()) { + Ui::Toast::Show( + tr::lng_channel_invite_private(tr::now)); + return; } } + controller->showRepliesForMessage(history, item->id); } }); } @@ -2334,80 +2347,96 @@ void Message::drawRightAction( } ClickHandlerPtr Message::rightActionLink() const { - if (!_rightActionLink) { - if (isPinnedContext()) { - _rightActionLink = goToMessageClickHandler(data()); - return _rightActionLink; - } else if (displayRightActionComments()) { - _rightActionLink = createGoToCommentsLink(); - return _rightActionLink; - } - const auto owner = &data()->history()->owner(); - const auto itemId = data()->fullId(); - const auto forwarded = data()->Get(); - const auto savedFromPeer = forwarded ? forwarded->savedFromPeer : nullptr; - const auto savedFromMsgId = forwarded ? forwarded->savedFromMsgId : 0; - const auto showByThread = std::make_shared>(); - const auto showByThreadWeak = std::weak_ptr>(showByThread); - if (data()->externalReply()) { - *showByThread = [=, requested = 0]() mutable { - const auto original = savedFromPeer->owner().message(savedFromPeer->asChannel(), savedFromMsgId); - if (original && original->replyToTop()) { - App::wnd()->sessionController()->showRepliesForMessage( - original->history(), - original->replyToTop(), - original->id, - Window::SectionShow::Way::Forward); - } else if (!requested) { - const auto channel = savedFromPeer->asChannel(); - const auto prequested = &requested; - requested = 1; - channel->session().api().requestMessageData(channel, savedFromMsgId, [=](ChannelData *gotChannel, MsgId gotId) { + if (_rightActionLink) { + return _rightActionLink; + } + if (isPinnedContext()) { + _rightActionLink = goToMessageClickHandler(data()); + return _rightActionLink; + } else if (displayRightActionComments()) { + _rightActionLink = createGoToCommentsLink(); + return _rightActionLink; + } + const auto sessionId = data()->history()->session().uniqueId(); + const auto owner = &data()->history()->owner(); + const auto itemId = data()->fullId(); + const auto forwarded = data()->Get(); + const auto savedFromPeer = forwarded ? forwarded->savedFromPeer : nullptr; + const auto savedFromMsgId = forwarded ? forwarded->savedFromMsgId : 0; + + using Callback = FnMut)>; + const auto showByThread = std::make_shared(); + const auto showByThreadWeak = std::weak_ptr(showByThread); + if (data()->externalReply()) { + *showByThread = [=, requested = 0]( + not_null controller) mutable { + const auto original = savedFromPeer->owner().message( + savedFromPeer->asChannel(), + savedFromMsgId); + if (original && original->replyToTop()) { + controller->showRepliesForMessage( + original->history(), + original->replyToTop(), + original->id, + Window::SectionShow::Way::Forward); + } else if (!requested) { + const auto channel = savedFromPeer->asChannel(); + const auto prequested = &requested; + requested = 1; + channel->session().api().requestMessageData( + channel, + savedFromMsgId, + [=, weak = base::make_weak(controller.get())]( + ChannelData *gotChannel, MsgId gotId) { if (const auto strong = showByThreadWeak.lock()) { - *prequested = 2; - (*strong)(); + if (const auto strongController = weak.get()) { + *prequested = 2; + (*strong)(strongController); + } } }); - } else if (requested == 2) { - App::wnd()->sessionController()->showPeerHistory( - savedFromPeer, - Window::SectionShow::Way::Forward, - savedFromMsgId); - } - }; - }; - _rightActionLink = std::make_shared([=] { - if (const auto item = owner->message(itemId)) { - if (*showByThread) { - (*showByThread)(); - } else if (savedFromPeer && savedFromMsgId) { - App::wnd()->sessionController()->showPeerHistory( - savedFromPeer, - Window::SectionShow::Way::Forward, - savedFromMsgId); - } else { - FastShareMessage(item); - } + } else if (requested == 2) { + controller->showPeerHistory( + savedFromPeer, + Window::SectionShow::Way::Forward, + savedFromMsgId); } - }); - } + }; + }; + _rightActionLink = std::make_shared([=]( + ClickContext context) { + const auto controller = ExtractController(context).value_or(nullptr); + if (!controller) { + return; + } + if (controller->session().uniqueId() != sessionId) { + return; + } + + if (const auto item = owner->message(itemId)) { + if (*showByThread) { + (*showByThread)(controller); + } else if (savedFromPeer && savedFromMsgId) { + controller->showPeerHistory( + savedFromPeer, + Window::SectionShow::Way::Forward, + savedFromMsgId); + } else { + FastShareMessage(item); + } + } + }); return _rightActionLink; } ClickHandlerPtr Message::fastReplyLink() const { - if (!_fastReplyLink) { - const auto owner = &data()->history()->owner(); - const auto itemId = data()->fullId(); - _fastReplyLink = std::make_shared([=] { - if (const auto item = owner->message(itemId)) { - if (const auto main = App::main()) { // multi good - if (&main->session() == &owner->session()) { - main->replyToItem(item); - } - } - } - }); + if (_fastReplyLink) { + return _fastReplyLink; } + const auto itemId = data()->fullId(); + _fastReplyLink = std::make_shared([=] { + delegate()->elementReplyTo(itemId); + }); return _fastReplyLink; } diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.h b/Telegram/SourceFiles/history/view/history_view_pinned_section.h index 0420b4d70..9757935d1 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_section.h +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.h @@ -66,6 +66,11 @@ public: const QRect &geometry, not_null memento); + Window::SectionActionResult sendBotCommand( + Bot::SendCommandRequest request) override { + return Window::SectionActionResult::Fallback; + } + // Float player interface. bool floatPlayerHandleWheelEvent(QEvent *e) override; QRect floatPlayerAvailableRect() override; diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 4cc8c5e76..87f00d26e 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -1417,15 +1417,17 @@ bool RepliesWidget::showMessage( return true; } -bool RepliesWidget::replyToMessage(not_null item) { - if (item->history() != _history || item->replyToTop() != _rootId) { - return false; +Window::SectionActionResult RepliesWidget::sendBotCommand( + Bot::SendCommandRequest request) { + if (request.peer != _history->peer) { + return Window::SectionActionResult::Ignore; } - replyToMessage(item->fullId()); - return true; + listSendBotCommand(request.command, request.context); + return Window::SectionActionResult::Handle; } void RepliesWidget::replyToMessage(FullMsgId itemId) { + // if (item->history() != _history || item->replyToTop() != _rootId) { _composeControls->replyToMessage(itemId); refreshTopBarActiveChat(); } @@ -1796,7 +1798,10 @@ bool RepliesWidget::listIsGoodForAroundPosition( void RepliesWidget::listSendBotCommand( const QString &command, const FullMsgId &context) { - const auto text = WrapBotCommandInChat(_history->peer, command, context); + const auto text = Bot::WrapCommandInChat( + _history->peer, + command, + context); auto message = ApiWrap::MessageToSend(_history); message.textWithTags = { text }; message.action.replyTo = replyToId(); diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index d451dba1e..f3cc3111e 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -92,7 +92,9 @@ public: PeerId peerId, const Window::SectionShow ¶ms, MsgId messageId) override; - bool replyToMessage(not_null item) override; + + Window::SectionActionResult sendBotCommand( + Bot::SendCommandRequest request) override; void setInternalState( const QRect &geometry, diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 3a3b1a975..4a61472df 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_scheduled_section.h" #include "history/view/controls/history_view_compose_controls.h" +#include "history/view/history_view_empty_list_bubble.h" #include "history/view/history_view_top_bar_widget.h" #include "history/view/history_view_list_widget.h" #include "history/view/history_view_schedule_box.h" @@ -24,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/attach/attach_send_files_way.h" #include "ui/special_buttons.h" #include "ui/ui_utility.h" +#include "ui/text/text_utilities.h" #include "ui/toasts/common_toasts.h" #include "api/api_common.h" #include "api/api_editing.h" @@ -156,6 +158,16 @@ ScheduledWidget::ScheduledWidget( } }, _inner->lifetime()); + { + auto emptyInfo = base::make_unique_q( + _inner, + st::msgServicePadding); + const auto emptyText = Ui::Text::Semibold( + tr::lng_scheduled_messages_empty(tr::now)); + emptyInfo->setText(emptyText); + _inner->setEmptyInfoWidget(std::move(emptyInfo)); + } + setupScrollDownButton(); setupComposeControls(); } @@ -1186,11 +1198,23 @@ bool ScheduledWidget::listIsGoodForAroundPosition( return true; } +Window::SectionActionResult ScheduledWidget::sendBotCommand( + Bot::SendCommandRequest request) { + if (request.peer != _history->peer) { + return Window::SectionActionResult::Ignore; + } + listSendBotCommand(request.command, request.context); + return Window::SectionActionResult::Handle; +} + void ScheduledWidget::listSendBotCommand( const QString &command, const FullMsgId &context) { const auto callback = [=](Api::SendOptions options) { - const auto text = WrapBotCommandInChat(_history->peer, command, context); + const auto text = Bot::WrapCommandInChat( + _history->peer, + command, + context); auto message = ApiWrap::MessageToSend(_history); message.textWithTags = { text }; message.action.options = options; diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h index c63928234..3091c8212 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h @@ -74,6 +74,9 @@ public: const Window::SectionShow ¶ms) override; std::shared_ptr createMemento() override; + Window::SectionActionResult sendBotCommand( + Bot::SendCommandRequest request) override; + void setInternalState( const QRect &geometry, not_null memento); diff --git a/Telegram/SourceFiles/history/view/history_view_service_message.cpp b/Telegram/SourceFiles/history/view/history_view_service_message.cpp index a30cb28fa..122946199 100644 --- a/Telegram/SourceFiles/history/view/history_view_service_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_service_message.cpp @@ -18,7 +18,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_options.h" #include "ui/ui_utility.h" #include "mainwidget.h" -#include "layout.h" #include "lang/lang_keys.h" #include "styles/style_chat.h" diff --git a/Telegram/SourceFiles/history/view/media/history_view_call.cpp b/Telegram/SourceFiles/history/view/media/history_view_call.cpp index 3f5d3ed05..10d555762 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_call.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_call.cpp @@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/confirm_box.h" #include "lang/lang_keys.h" #include "ui/text/format_values.h" -#include "layout.h" // FullSelection +#include "layout/layout_selection.h" // FullSelection #include "history/history.h" #include "history/history_item.h" #include "history/view/history_view_element.h" diff --git a/Telegram/SourceFiles/history/view/media/history_view_contact.cpp b/Telegram/SourceFiles/history/view/media/history_view_contact.cpp index 50c576807..4d6c9dd27 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_contact.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_contact.cpp @@ -7,8 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/media/history_view_contact.h" +#include "core/click_handler_types.h" // ClickHandlerContext #include "lang/lang_keys.h" -#include "layout.h" +#include "layout/layout_selection.h" #include "mainwindow.h" #include "boxes/add_contact_box.h" #include "history/history_item_components.h" @@ -18,38 +19,51 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_cursor_state.h" #include "window/window_session_controller.h" #include "ui/empty_userpic.h" +#include "ui/text/format_values.h" // Ui::FormatPhone #include "ui/text/text_options.h" #include "data/data_session.h" #include "data/data_user.h" #include "data/data_media_types.h" #include "data/data_cloud_file.h" #include "main/main_session.h" -#include "app.h" #include "styles/style_chat.h" namespace HistoryView { namespace { -ClickHandlerPtr sendMessageClickHandler(PeerData *peer) { - return std::make_shared([peer] { - App::wnd()->sessionController()->showPeerHistory( - peer->id, - Window::SectionShow::Way::Forward); +ClickHandlerPtr SendMessageClickHandler(PeerData *peer) { + return std::make_shared([peer](ClickContext context) { + const auto my = context.other.value(); + if (const auto controller = my.sessionWindow.get()) { + if (controller->session().uniqueId() + != peer->session().uniqueId()) { + return; + } + controller->showPeerHistory( + peer->id, + Window::SectionShow::Way::Forward); + } }); } -ClickHandlerPtr addContactClickHandler(not_null item) { +ClickHandlerPtr AddContactClickHandler(not_null item) { const auto session = &item->history()->session(); const auto fullId = item->fullId(); - return std::make_shared([=] { - if (const auto item = session->data().message(fullId)) { - if (const auto media = item->media()) { - if (const auto contact = media->sharedContact()) { - Ui::show(Box( - session, - contact->firstName, - contact->lastName, - contact->phoneNumber)); + return std::make_shared([=](ClickContext context) { + const auto my = context.other.value(); + if (const auto controller = my.sessionWindow.get()) { + if (controller->session().uniqueId() != session->uniqueId()) { + return; + } + if (const auto item = session->data().message(fullId)) { + if (const auto media = item->media()) { + if (const auto contact = media->sharedContact()) { + controller->show(Box( + session, + contact->firstName, + contact->lastName, + contact->phoneNumber)); + } } } } @@ -68,7 +82,7 @@ Contact::Contact( , _userId(userId) , _fname(first) , _lname(last) -, _phone(App::formatPhone(phone)) { +, _phone(Ui::FormatPhone(phone)) { history()->owner().registerContactView(userId, parent); _name.setText( @@ -112,10 +126,10 @@ QSize Contact::countOptimalSize() { full); } if (_contact && _contact->isContact()) { - _linkl = sendMessageClickHandler(_contact); + _linkl = SendMessageClickHandler(_contact); _link = tr::lng_profile_send_message(tr::now).toUpper(); } else if (_userId) { - _linkl = addContactClickHandler(_parent->data()); + _linkl = AddContactClickHandler(_parent->data()); _link = tr::lng_profile_add_contact(tr::now).toUpper(); } _linkw = _link.isEmpty() ? 0 : st::semiboldFont->width(_link); diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp index efb597694..7cdc530ed 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp @@ -21,7 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/format_song_document_name.h" #include "ui/cached_round_corners.h" #include "ui/ui_utility.h" -#include "layout.h" // FullSelection +#include "layout/layout_selection.h" // FullSelection #include "data/data_session.h" #include "data/data_document.h" #include "data/data_document_media.h" diff --git a/Telegram/SourceFiles/history/view/media/history_view_game.cpp b/Telegram/SourceFiles/history/view/media/history_view_game.cpp index e881ca698..84cdfc312 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_game.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_game.cpp @@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_game.h" #include "lang/lang_keys.h" -#include "layout.h" +#include "layout/layout_selection.h" #include "history/history_item_components.h" #include "history/history.h" #include "history/view/history_view_element.h" diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index c66b21761..08a71d7c1 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -36,7 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_file_click_handler.h" #include "data/data_file_origin.h" #include "data/data_document_media.h" -#include "layout.h" // FullSelection +#include "layout/layout_selection.h" // FullSelection #include "styles/style_chat.h" namespace HistoryView { diff --git a/Telegram/SourceFiles/history/view/media/history_view_invoice.cpp b/Telegram/SourceFiles/history/view/media/history_view_invoice.cpp index 45261fe6e..f01458629 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_invoice.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_invoice.cpp @@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_invoice.h" #include "lang/lang_keys.h" -#include "layout.h" +#include "layout/layout_selection.h" #include "history/view/history_view_element.h" #include "history/view/history_view_cursor_state.h" #include "history/view/media/history_view_photo.h" diff --git a/Telegram/SourceFiles/history/view/media/history_view_large_emoji.cpp b/Telegram/SourceFiles/history/view/media/history_view_large_emoji.cpp index 881233ffc..d2e2042dd 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_large_emoji.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_large_emoji.cpp @@ -14,7 +14,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "ui/image/image.h" #include "data/data_file_origin.h" -#include "layout.h" #include "styles/style_chat.h" namespace HistoryView { diff --git a/Telegram/SourceFiles/history/view/media/history_view_location.cpp b/Telegram/SourceFiles/history/view/media/history_view_location.cpp index 6e340b030..4b27b84bb 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_location.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_location.cpp @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/media/history_view_location.h" -#include "layout.h" +#include "layout/layout_selection.h" #include "history/history.h" #include "history/history_item_components.h" #include "history/history_item.h" diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp index 3fbd02191..f5cd5fd95 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -18,7 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "ui/grouped_layout.h" #include "ui/text/text_options.h" -#include "layout.h" +#include "layout/layout_selection.h" #include "styles/style_chat.h" namespace HistoryView { diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp index 8964ab143..7f29a36bd 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp @@ -16,7 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lottie/lottie_single_player.h" #include "data/data_session.h" #include "ui/cached_round_corners.h" -#include "layout.h" +#include "layout/layout_selection.h" #include "styles/style_chat.h" namespace HistoryView { diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index 79e65a481..497c1d8e4 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/media/history_view_photo.h" -#include "layout.h" +#include "layout/layout_selection.h" #include "history/history_item_components.h" #include "history/history_item.h" #include "history/history.h" diff --git a/Telegram/SourceFiles/history/view/media/history_view_poll.cpp b/Telegram/SourceFiles/history/view/media/history_view_poll.cpp index 5673955af..69da60c3f 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_poll.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_poll.cpp @@ -29,7 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unixtime.h" #include "base/timer.h" #include "main/main_session.h" -#include "layout.h" // FullSelection +#include "layout/layout_selection.h" // FullSelection #include "apiwrap.h" #include "styles/style_chat.h" #include "styles/style_widgets.h" diff --git a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp index c6473f1a1..92dd66760 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp @@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/media/history_view_sticker.h" -#include "layout.h" #include "boxes/sticker_set_box.h" #include "history/history.h" #include "history/history_item_components.h" diff --git a/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp index 2f19bf157..f670d25ac 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp @@ -21,7 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/format_values.h" #include "ui/cached_round_corners.h" #include "ui/ui_utility.h" -#include "layout.h" // FullSelection +#include "layout/layout_selection.h" // FullSelection #include "styles/style_chat.h" namespace HistoryView { diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp index 4b227c54c..ccb548840 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -20,7 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_options.h" #include "ui/text/format_values.h" #include "ui/cached_round_corners.h" -#include "layout.h" // FullSelection +#include "layout/layout_selection.h" // FullSelection #include "data/data_session.h" #include "data/data_media_types.h" #include "data/data_web_page.h" diff --git a/Telegram/SourceFiles/info/info_top_bar.cpp b/Telegram/SourceFiles/info/info_top_bar.cpp index 873f236ae..304cf3d6f 100644 --- a/Telegram/SourceFiles/info/info_top_bar.cpp +++ b/Telegram/SourceFiles/info/info_top_bar.cpp @@ -484,16 +484,10 @@ bool TopBar::computeCanDelete() const { Ui::StringWithNumbers TopBar::generateSelectedText() const { using Type = Storage::SharedMediaType; - if (_selectedItems.type == Type::GIF) { - return Ui::StringWithNumbers::FromString( - ktr("ktg_media_selected_gif", - _selectedItems.list.size(), - { "count", QString::number(_selectedItems.list.size()) })); - } - const auto phrase = [&] { switch (_selectedItems.type) { case Type::Photo: return tr::lng_media_selected_photo; + case Type::GIF: return tr::lng_media_selected_gif; case Type::Video: return tr::lng_media_selected_video; case Type::File: return tr::lng_media_selected_file; case Type::MusicFile: return tr::lng_media_selected_song; @@ -588,10 +582,10 @@ rpl::producer TitleValue( switch (section.mediaType()) { case Section::MediaType::Photo: return tr::lng_media_type_photos(); + case Section::MediaType::GIF: + return tr::lng_media_type_gifs(); case Section::MediaType::Video: return tr::lng_media_type_videos(); - case Section::MediaType::GIF: - return rktr("ktg_media_type_gif"); case Section::MediaType::MusicFile: return tr::lng_media_type_songs(); case Section::MediaType::File: diff --git a/Telegram/SourceFiles/info/media/info_media_buttons.h b/Telegram/SourceFiles/info/media/info_media_buttons.h index 93d25f7ad..4d0f0ed47 100644 --- a/Telegram/SourceFiles/info/media/info_media_buttons.h +++ b/Telegram/SourceFiles/info/media/info_media_buttons.h @@ -31,6 +31,7 @@ using Type = Storage::SharedMediaType; inline tr::phrase MediaTextPhrase(Type type) { switch (type) { case Type::Photo: return tr::lng_profile_photos; + case Type::GIF: return tr::lng_profile_gifs; case Type::Video: return tr::lng_profile_videos; case Type::File: return tr::lng_profile_files; case Type::MusicFile: return tr::lng_profile_songs; @@ -41,12 +42,8 @@ inline tr::phrase MediaTextPhrase(Type type) { }; inline auto MediaText(Type type) { - return [type](int count) { - if (type == Type::GIF) { - return ktr("ktg_profile_gif", count, { "count", QString::number(count) }); - } else { - return MediaTextPhrase(type)(tr::now, lt_count, count); - } + return [phrase = MediaTextPhrase(type)](int count) { + return phrase(tr::now, lt_count, count); }; } diff --git a/Telegram/SourceFiles/info/media/info_media_empty_widget.cpp b/Telegram/SourceFiles/info/media/info_media_empty_widget.cpp index 6ade371ba..51c68a3ec 100644 --- a/Telegram/SourceFiles/info/media/info_media_empty_widget.cpp +++ b/Telegram/SourceFiles/info/media/info_media_empty_widget.cpp @@ -37,9 +37,9 @@ void EmptyWidget::setType(Type type) { _type = type; _icon = [&] { switch (_type) { - case Type::Photo: return &st::infoEmptyPhoto; + case Type::Photo: + case Type::GIF: return &st::infoEmptyPhoto; case Type::Video: return &st::infoEmptyVideo; - case Type::GIF: return &st::infoEmptyVideo; case Type::MusicFile: return &st::infoEmptyAudio; case Type::File: return &st::infoEmptyFile; case Type::Link: return &st::infoEmptyLink; @@ -55,10 +55,10 @@ void EmptyWidget::setSearchQuery(const QString &query) { switch (_type) { case Type::Photo: return tr::lng_media_photo_empty(tr::now); + case Type::GIF: + return tr::lng_media_gif_empty(tr::now); case Type::Video: return tr::lng_media_video_empty(tr::now); - case Type::GIF: - return ktr("ktg_media_gif_empty"); case Type::MusicFile: return query.isEmpty() ? tr::lng_media_song_empty(tr::now) diff --git a/Telegram/SourceFiles/info/media/info_media_inner_widget.cpp b/Telegram/SourceFiles/info/media/info_media_inner_widget.cpp index 99f357012..6d95f0ea6 100644 --- a/Telegram/SourceFiles/info/media/info_media_inner_widget.cpp +++ b/Telegram/SourceFiles/info/media/info_media_inner_widget.cpp @@ -126,6 +126,7 @@ void InnerWidget::createTypeButtons() { addMediaButton(Type::MusicFile, st::infoIconMediaAudio); addMediaButton(Type::Link, st::infoIconMediaLink); addMediaButton(Type::RoundVoiceFile, st::infoIconMediaVoice); + addMediaButton(Type::GIF, st::infoIconMediaGif); // if (auto user = _controller->key().peer()->asUser()) { // addCommonGroupsButton(user, st::infoIconMediaGroup); // } diff --git a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp index 4f889c798..85cd6dafa 100644 --- a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp +++ b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp @@ -9,6 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/info_controller.h" #include "overview/overview_layout.h" +#include "layout/layout_mosaic.h" +#include "layout/layout_selection.h" #include "data/data_media_types.h" #include "data/data_photo.h" #include "data/data_document.h" @@ -32,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_overview.h" #include "styles/style_info.h" #include "base/platform/base_platform_info.h" +#include "base/weak_ptr.h" #include "media/player/media_player_instance.h" #include "boxes/peer_list_controllers.h" #include "boxes/confirm_box.h" @@ -41,8 +44,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include -namespace Layout = Overview::Layout; - namespace Info { namespace Media { namespace { @@ -71,8 +72,8 @@ UniversalMsgId GetUniversalId(not_null layout) { bool HasFloatingHeader(Type type) { switch (type) { case Type::Photo: - case Type::Video: case Type::GIF: + case Type::Video: case Type::RoundFile: case Type::RoundVoiceFile: case Type::MusicFile: @@ -87,7 +88,7 @@ bool HasFloatingHeader(Type type) { } // namespace struct ListWidget::Context { - Layout::PaintContext layoutContext; + Overview::Layout::PaintContext layoutContext; not_null selected; not_null dragSelected; DragSelectAction dragSelectAction; @@ -97,10 +98,13 @@ class ListWidget::Section { public: Section(Type type) : _type(type) - , _hasFloatingHeader(HasFloatingHeader(type)) { + , _hasFloatingHeader(HasFloatingHeader(type)) + , _mosaic(st::emojiPanWidth - st::inlineResultsLeft) { } bool addItem(not_null item); + void finishSection(); + bool empty() const { return _items.empty(); } @@ -168,7 +172,7 @@ private: not_null item, const Context &context) const; - int recountHeight() const; + int recountHeight(); void refreshHeight(); Type _type = Type::Photo; @@ -183,6 +187,8 @@ private: int _top = 0; int _height = 0; + Mosaic::Layout::MosaicLayout _mosaic; + }; bool ListWidget::IsAfter( @@ -231,13 +237,22 @@ bool ListWidget::Section::addItem(not_null item) { return false; } +void ListWidget::Section::finishSection() { + if (_type == Type::GIF) { + _mosaic.setOffset(st::infoMediaSkip, headerHeight()); + _mosaic.setRightSkip(st::infoMediaSkip); + const auto items = ranges::views::values(_items) | ranges::to_vector; + _mosaic.addItems(items); + } +} + void ListWidget::Section::setHeader(not_null item) { auto text = [&] { auto date = item->dateTime().date(); switch (_type) { case Type::Photo: - case Type::Video: case Type::GIF: + case Type::Video: case Type::RoundFile: case Type::RoundVoiceFile: case Type::File: @@ -263,8 +278,8 @@ bool ListWidget::Section::belongsHere( switch (_type) { case Type::Photo: - case Type::Video: case Type::GIF: + case Type::Video: case Type::RoundFile: case Type::RoundVoiceFile: case Type::File: @@ -298,6 +313,9 @@ bool ListWidget::Section::removeItem(UniversalMsgId universalId) { QRect ListWidget::Section::findItemRect( not_null item) const { auto position = item->position(); + if (!_mosaic.empty()) { + return _mosaic.findRect(position); + } auto top = position / _itemsInRow; auto indexInRow = position % _itemsInRow; auto left = _itemsLeft @@ -314,6 +332,13 @@ auto ListWidget::Section::completeResult( auto ListWidget::Section::findItemByPoint( QPoint point) const -> FoundItem { Expects(!_items.empty()); + if (!_mosaic.empty()) { + const auto found = _mosaic.findByPoint(point); + Assert(found.index != -1); + const auto item = _mosaic.itemAt(found.index); + const auto rect = findItemRect(item); + return { item, rect, found.exact }; + } auto itemIt = findItemAfterTop(point.y()); if (itemIt == _items.end()) { --itemIt; @@ -362,6 +387,7 @@ auto ListWidget::Section::findItemDetails(not_null item) const auto ListWidget::Section::findItemAfterTop( int top) -> Items::iterator { + Expects(_mosaic.empty()); return ranges::lower_bound( _items, top, @@ -374,6 +400,7 @@ auto ListWidget::Section::findItemAfterTop( auto ListWidget::Section::findItemAfterTop( int top) const -> Items::const_iterator { + Expects(_mosaic.empty()); return ranges::lower_bound( _items, top, @@ -387,6 +414,7 @@ auto ListWidget::Section::findItemAfterTop( auto ListWidget::Section::findItemAfterBottom( Items::const_iterator from, int bottom) const -> Items::const_iterator { + Expects(_mosaic.empty()); return ranges::lower_bound( from, _items.end(), @@ -416,6 +444,20 @@ void ListWidget::Section::paint( auto localContext = context.layoutContext; localContext.isAfterDate = (header > 0); + if (!_mosaic.empty()) { + auto paintItem = [&](not_null item, QPoint point) { + p.translate(point.x(), point.y()); + item->paint( + p, + clip.translated(-point), + itemSelection(item, context), + &localContext); + p.translate(-point.x(), -point.y()); + }; + _mosaic.paint(std::move(paintItem), clip); + return; + } + auto fromIt = findItemAfterTop(clip.y()); auto tillIt = findItemAfterBottom( fromIt, @@ -509,7 +551,6 @@ void ListWidget::Section::resizeToWidth(int newWidth) { switch (_type) { case Type::Photo: case Type::Video: - case Type::GIF: case Type::RoundFile: { _itemsLeft = st::infoMediaSkip; _itemsTop = st::infoMediaSkip; @@ -522,6 +563,10 @@ void ListWidget::Section::resizeToWidth(int newWidth) { } } break; + case Type::GIF: { + _mosaic.setFullWidth(newWidth - st::infoMediaSkip); + } break; + case Type::RoundVoiceFile: case Type::MusicFile: resizeOneColumn(0, newWidth); @@ -541,8 +586,8 @@ int ListWidget::Section::MinItemHeight(Type type, int width) { auto &songSt = st::overviewFileLayout; switch (type) { case Type::Photo: - case Type::Video: case Type::GIF: + case Type::Video: case Type::RoundFile: { auto itemsLeft = st::infoMediaSkip; auto itemsInRow = (width - itemsLeft) @@ -562,13 +607,12 @@ int ListWidget::Section::MinItemHeight(Type type, int width) { Unexpected("Type in ListWidget::Section::MinItemHeight()"); } -int ListWidget::Section::recountHeight() const { +int ListWidget::Section::recountHeight() { auto result = headerHeight(); switch (_type) { case Type::Photo: case Type::Video: - case Type::GIF: case Type::RoundFile: { auto itemHeight = _itemWidth + st::infoMediaSkip; auto index = 0; @@ -588,6 +632,10 @@ int ListWidget::Section::recountHeight() const { } } break; + case Type::GIF: { + return _mosaic.countDesiredHeight(0) + result; + } break; + case Type::RoundVoiceFile: case Type::File: case Type::MusicFile: @@ -619,7 +667,9 @@ ListWidget::ListWidget( , _dateBadge(DateBadge{ .check = SingleQueuedInvokation([=] { scrollDateCheck(); }), .hideTimer = base::Timer([=] { scrollDateHide(); }), - .goodType = (_type == Type::Photo || _type == Type::Video), + .goodType = (_type == Type::Photo + || _type == Type::Video + || _type == Type::GIF), }) { setMouseTracking(true); start(); @@ -835,6 +885,10 @@ void ListWidget::repaintItem(const BaseLayout *item) { } } +void ListWidget::repaintItem(not_null item) { + repaintItem(GetUniversalId(item)); +} + void ListWidget::repaintItem(QRect itemGeometry) { rtlupdate(itemGeometry); } @@ -877,14 +931,27 @@ void ListWidget::unregisterHeavyItem(not_null item) { } } +bool ListWidget::itemVisible(not_null item) { + if (const auto &found = findItemById(GetUniversalId(item))) { + const auto geometry = found->geometry; + return (geometry.top() < _visibleBottom) + && (geometry.top() + geometry.height() > _visibleTop); + } + return true; +} + void ListWidget::openPhoto(not_null photo, FullMsgId id) { _controller->parentController()->openPhoto(photo, id); } void ListWidget::openDocument( not_null document, - FullMsgId id) { - _controller->parentController()->openDocument(document, id); + FullMsgId id, + bool showInMediaView) { + _controller->parentController()->openDocument( + document, + id, + showInMediaView); } SparseIdsMergedSlice::Key ListWidget::sliceKey( @@ -965,15 +1032,19 @@ std::unique_ptr ListWidget::createLayout( }; auto &songSt = st::overviewFileLayout; - using namespace Layout; + using namespace Overview::Layout; switch (type) { case Type::Photo: if (const auto photo = getPhoto()) { return std::make_unique(this, item, photo); } return nullptr; - case Type::Video: case Type::GIF: + if (const auto file = getFile()) { + return std::make_unique(this, item, file); + } + return nullptr; + case Type::Video: if (const auto file = getFile()) { return std::make_unique