diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 78896d035..a8855805d 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -60,7 +60,7 @@ jobs: defaults: run: - shell: scl enable llvm-toolset-7.0 -- scl enable devtoolset-9 -- bash --noprofile --norc -eo pipefail {0} + shell: scl enable llvm-toolset-7.0 -- scl enable devtoolset-10 -- bash --noprofile --norc -eo pipefail {0} strategy: matrix: @@ -110,8 +110,7 @@ jobs: -D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \ $DEFINE - cd ../out/Debug - make -j$(nproc) + cmake --build ../out --config Debug --parallel - name: Check. run: | diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index 11e0eef67..562159d0d 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -8,8 +8,8 @@ jobs: lock: runs-on: ubuntu-latest steps: - - uses: dessant/lock-threads@v2 + - uses: dessant/lock-threads@v3 with: github-token: ${{ github.token }} - issue-lock-inactive-days: 45 - pr-lock-inactive-days: 45 + issue-inactive-days: 45 + pr-inactive-days: 45 diff --git a/.github/workflows/no-response.yml b/.github/workflows/no-response.yml index 4b201b453..4d19e1048 100644 --- a/.github/workflows/no-response.yml +++ b/.github/workflows/no-response.yml @@ -15,14 +15,5 @@ jobs: - uses: lee-dohm/no-response@v0.5.0 with: token: ${{ github.token }} - # Number of days of inactivity before an Issue is closed for lack of response - daysUntilClose: 30 # Label requiring a response responseRequiredLabel: waiting for answer - # Comment to post when closing an Issue for lack of response. Set to `false` to disable - closeComment: > - This issue has been automatically closed because there has been no response - to our request for more information from the original author. With only the - information that is currently in the issue, we don't have enough information - to take action. Please reach out if you have or find the answers we need so - that we can investigate further. diff --git a/.github/workflows/tarball-release.yml b/.github/workflows/tarball-release.yml index 9ee679327..05669af43 100644 --- a/.github/workflows/tarball-release.yml +++ b/.github/workflows/tarball-release.yml @@ -2,7 +2,7 @@ name: Release Tarball on: release: - types: [created] + types: [published] jobs: diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 17402679a..3f7b78c79 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -112,6 +112,8 @@ PRIVATE api/api_chat_filters.h api/api_chat_invite.cpp api/api_chat_invite.h + api/api_chat_participants.cpp + api/api_chat_participants.h api/api_cloud_password.cpp api/api_cloud_password.h api/api_common.h @@ -400,6 +402,8 @@ PRIVATE data/data_chat.h data/data_chat_filters.cpp data/data_chat_filters.h + data/data_chat_participant_status.cpp + data/data_chat_participant_status.h data/data_changes.cpp data/data_changes.h data/data_channel.cpp @@ -802,6 +806,8 @@ PRIVATE main/main_session.h main/main_session_settings.cpp main/main_session_settings.h + main/session/send_as_peers.cpp + main/session/send_as_peers.h media/system_media_controls_manager.h media/system_media_controls_manager.cpp media/audio/media_audio.cpp @@ -1100,6 +1106,8 @@ PRIVATE ui/chat/attach/attach_item_single_file_preview.h ui/chat/attach/attach_item_single_media_preview.cpp ui/chat/attach/attach_item_single_media_preview.h + ui/chat/choose_send_as.cpp + ui/chat/choose_send_as.h ui/chat/choose_theme_controller.cpp ui/chat/choose_theme_controller.h ui/effects/fireworks_animation.cpp @@ -1427,12 +1435,13 @@ PRIVATE G_LOG_DOMAIN="Kotatogram" ) -if (APPLE OR NOT CMAKE_EXECUTABLE_SUFFIX STREQUAL "" OR NOT "${output_name}" STREQUAL "Kotatogram") +if ("${CMAKE_GENERATOR}" STREQUAL "Xcode" + OR "${CMAKE_GENERATOR}" STREQUAL "Ninja Multi-Config" + OR NOT CMAKE_EXECUTABLE_SUFFIX STREQUAL "" + OR NOT "${output_name}" STREQUAL "Kotatogram") set(output_folder ${CMAKE_BINARY_DIR}) -elseif (DESKTOP_APP_SPECIAL_TARGET STREQUAL "") - set(output_folder ${CMAKE_BINARY_DIR}/bin) else() - set(output_folder ${CMAKE_BINARY_DIR}/$,Debug,Release>) + set(output_folder ${CMAKE_BINARY_DIR}/bin) endif() set_target_properties(Telegram PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_folder}) @@ -1558,8 +1567,8 @@ endif() if (LINUX AND DESKTOP_APP_USE_PACKAGED) include(GNUInstallDirs) - configure_file("../lib/xdg/kotatogramdesktop.appdata.xml.in" "${CMAKE_CURRENT_BINARY_DIR}/kotatogramdesktop.appdata.xml" @ONLY) - # generate_appdata_changelog(Telegram "${CMAKE_SOURCE_DIR}/changelog.txt" "${CMAKE_CURRENT_BINARY_DIR}/kotatogramdesktop.appdata.xml") + configure_file("../lib/xdg/kotatogramdesktop.metainfo.xml.in" "${CMAKE_CURRENT_BINARY_DIR}/kotatogramdesktop.metainfo.xml" @ONLY) + # generate_appdata_changelog(Telegram "${CMAKE_SOURCE_DIR}/changelog.txt" "${CMAKE_CURRENT_BINARY_DIR}/kotatogramdesktop.metainfo.xml") install(TARGETS Telegram RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" BUNDLE DESTINATION "${CMAKE_INSTALL_BINDIR}") install(FILES "Resources/art/icon16.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/16x16/apps" RENAME "kotatogram.png") install(FILES "Resources/art/icon32.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/32x32/apps" RENAME "kotatogram.png") @@ -1569,5 +1578,5 @@ if (LINUX AND DESKTOP_APP_USE_PACKAGED) install(FILES "Resources/art/icon256.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/256x256/apps" RENAME "kotatogram.png") install(FILES "Resources/art/icon512.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/512x512/apps" RENAME "kotatogram.png") install(FILES "../lib/xdg/kotatogramdesktop.desktop" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications" RENAME "${TDESKTOP_LAUNCHER_BASENAME}.desktop") - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/kotatogramdesktop.appdata.xml" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo" RENAME "${TDESKTOP_LAUNCHER_BASENAME}.appdata.xml") + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/kotatogramdesktop.metainfo.xml" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo" RENAME "${TDESKTOP_LAUNCHER_BASENAME}.metainfo.xml") endif() diff --git a/Telegram/Resources/icons/calendar_down.png b/Telegram/Resources/icons/calendar_down.png new file mode 100644 index 000000000..e7213ec36 Binary files /dev/null and b/Telegram/Resources/icons/calendar_down.png differ diff --git a/Telegram/Resources/icons/calendar_down@2x.png b/Telegram/Resources/icons/calendar_down@2x.png new file mode 100644 index 000000000..8ee33ea61 Binary files /dev/null and b/Telegram/Resources/icons/calendar_down@2x.png differ diff --git a/Telegram/Resources/icons/calendar_down@3x.png b/Telegram/Resources/icons/calendar_down@3x.png new file mode 100644 index 000000000..f48c3def0 Binary files /dev/null and b/Telegram/Resources/icons/calendar_down@3x.png differ diff --git a/Telegram/Resources/icons/dialogs_bot.png b/Telegram/Resources/icons/dialogs/dialogs_bot.png similarity index 100% rename from Telegram/Resources/icons/dialogs_bot.png rename to Telegram/Resources/icons/dialogs/dialogs_bot.png diff --git a/Telegram/Resources/icons/dialogs_bot@2x.png b/Telegram/Resources/icons/dialogs/dialogs_bot@2x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_bot@2x.png rename to Telegram/Resources/icons/dialogs/dialogs_bot@2x.png diff --git a/Telegram/Resources/icons/dialogs_bot@3x.png b/Telegram/Resources/icons/dialogs/dialogs_bot@3x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_bot@3x.png rename to Telegram/Resources/icons/dialogs/dialogs_bot@3x.png diff --git a/Telegram/Resources/icons/dialogs_calendar.png b/Telegram/Resources/icons/dialogs/dialogs_calendar.png similarity index 100% rename from Telegram/Resources/icons/dialogs_calendar.png rename to Telegram/Resources/icons/dialogs/dialogs_calendar.png diff --git a/Telegram/Resources/icons/dialogs_calendar@2x.png b/Telegram/Resources/icons/dialogs/dialogs_calendar@2x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_calendar@2x.png rename to Telegram/Resources/icons/dialogs/dialogs_calendar@2x.png diff --git a/Telegram/Resources/icons/dialogs_calendar@3x.png b/Telegram/Resources/icons/dialogs/dialogs_calendar@3x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_calendar@3x.png rename to Telegram/Resources/icons/dialogs/dialogs_calendar@3x.png diff --git a/Telegram/Resources/icons/dialogs_cancel_search.png b/Telegram/Resources/icons/dialogs/dialogs_cancel_search.png similarity index 100% rename from Telegram/Resources/icons/dialogs_cancel_search.png rename to Telegram/Resources/icons/dialogs/dialogs_cancel_search.png diff --git a/Telegram/Resources/icons/dialogs_cancel_search@2x.png b/Telegram/Resources/icons/dialogs/dialogs_cancel_search@2x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_cancel_search@2x.png rename to Telegram/Resources/icons/dialogs/dialogs_cancel_search@2x.png diff --git a/Telegram/Resources/icons/dialogs_cancel_search@3x.png b/Telegram/Resources/icons/dialogs/dialogs_cancel_search@3x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_cancel_search@3x.png rename to Telegram/Resources/icons/dialogs/dialogs_cancel_search@3x.png diff --git a/Telegram/Resources/icons/dialogs_channel.png b/Telegram/Resources/icons/dialogs/dialogs_channel.png similarity index 100% rename from Telegram/Resources/icons/dialogs_channel.png rename to Telegram/Resources/icons/dialogs/dialogs_channel.png diff --git a/Telegram/Resources/icons/dialogs_channel@2x.png b/Telegram/Resources/icons/dialogs/dialogs_channel@2x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_channel@2x.png rename to Telegram/Resources/icons/dialogs/dialogs_channel@2x.png diff --git a/Telegram/Resources/icons/dialogs_channel@3x.png b/Telegram/Resources/icons/dialogs/dialogs_channel@3x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_channel@3x.png rename to Telegram/Resources/icons/dialogs/dialogs_channel@3x.png diff --git a/Telegram/Resources/icons/dialogs_chat.png b/Telegram/Resources/icons/dialogs/dialogs_chat.png similarity index 100% rename from Telegram/Resources/icons/dialogs_chat.png rename to Telegram/Resources/icons/dialogs/dialogs_chat.png diff --git a/Telegram/Resources/icons/dialogs_chat@2x.png b/Telegram/Resources/icons/dialogs/dialogs_chat@2x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_chat@2x.png rename to Telegram/Resources/icons/dialogs/dialogs_chat@2x.png diff --git a/Telegram/Resources/icons/dialogs_chat@3x.png b/Telegram/Resources/icons/dialogs/dialogs_chat@3x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_chat@3x.png rename to Telegram/Resources/icons/dialogs/dialogs_chat@3x.png diff --git a/Telegram/Resources/icons/dialogs_deleted.png b/Telegram/Resources/icons/dialogs/dialogs_deleted.png similarity index 100% rename from Telegram/Resources/icons/dialogs_deleted.png rename to Telegram/Resources/icons/dialogs/dialogs_deleted.png diff --git a/Telegram/Resources/icons/dialogs_deleted@2x.png b/Telegram/Resources/icons/dialogs/dialogs_deleted@2x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_deleted@2x.png rename to Telegram/Resources/icons/dialogs/dialogs_deleted@2x.png diff --git a/Telegram/Resources/icons/dialogs_deleted@3x.png b/Telegram/Resources/icons/dialogs/dialogs_deleted@3x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_deleted@3x.png rename to Telegram/Resources/icons/dialogs/dialogs_deleted@3x.png diff --git a/Telegram/Resources/icons/dialogs_lock.png b/Telegram/Resources/icons/dialogs/dialogs_lock.png similarity index 100% rename from Telegram/Resources/icons/dialogs_lock.png rename to Telegram/Resources/icons/dialogs/dialogs_lock.png diff --git a/Telegram/Resources/icons/dialogs_lock@2x.png b/Telegram/Resources/icons/dialogs/dialogs_lock@2x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_lock@2x.png rename to Telegram/Resources/icons/dialogs/dialogs_lock@2x.png diff --git a/Telegram/Resources/icons/dialogs_lock@3x.png b/Telegram/Resources/icons/dialogs/dialogs_lock@3x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_lock@3x.png rename to Telegram/Resources/icons/dialogs/dialogs_lock@3x.png diff --git a/Telegram/Resources/icons/dialogs_menu.png b/Telegram/Resources/icons/dialogs/dialogs_menu.png similarity index 100% rename from Telegram/Resources/icons/dialogs_menu.png rename to Telegram/Resources/icons/dialogs/dialogs_menu.png diff --git a/Telegram/Resources/icons/dialogs_menu@2x.png b/Telegram/Resources/icons/dialogs/dialogs_menu@2x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_menu@2x.png rename to Telegram/Resources/icons/dialogs/dialogs_menu@2x.png diff --git a/Telegram/Resources/icons/dialogs_menu@3x.png b/Telegram/Resources/icons/dialogs/dialogs_menu@3x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_menu@3x.png rename to Telegram/Resources/icons/dialogs/dialogs_menu@3x.png diff --git a/Telegram/Resources/icons/dialogs_menu_unread.png b/Telegram/Resources/icons/dialogs/dialogs_menu_unread.png similarity index 100% rename from Telegram/Resources/icons/dialogs_menu_unread.png rename to Telegram/Resources/icons/dialogs/dialogs_menu_unread.png diff --git a/Telegram/Resources/icons/dialogs_menu_unread@2x.png b/Telegram/Resources/icons/dialogs/dialogs_menu_unread@2x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_menu_unread@2x.png rename to Telegram/Resources/icons/dialogs/dialogs_menu_unread@2x.png diff --git a/Telegram/Resources/icons/dialogs_menu_unread@3x.png b/Telegram/Resources/icons/dialogs/dialogs_menu_unread@3x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_menu_unread@3x.png rename to Telegram/Resources/icons/dialogs/dialogs_menu_unread@3x.png diff --git a/Telegram/Resources/icons/dialogs_menu_unread_dot.png b/Telegram/Resources/icons/dialogs/dialogs_menu_unread_dot.png similarity index 100% rename from Telegram/Resources/icons/dialogs_menu_unread_dot.png rename to Telegram/Resources/icons/dialogs/dialogs_menu_unread_dot.png diff --git a/Telegram/Resources/icons/dialogs_menu_unread_dot@2x.png b/Telegram/Resources/icons/dialogs/dialogs_menu_unread_dot@2x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_menu_unread_dot@2x.png rename to Telegram/Resources/icons/dialogs/dialogs_menu_unread_dot@2x.png diff --git a/Telegram/Resources/icons/dialogs_menu_unread_dot@3x.png b/Telegram/Resources/icons/dialogs/dialogs_menu_unread_dot@3x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_menu_unread_dot@3x.png rename to Telegram/Resources/icons/dialogs/dialogs_menu_unread_dot@3x.png diff --git a/Telegram/Resources/icons/dialogs_pinned.png b/Telegram/Resources/icons/dialogs/dialogs_pinned.png similarity index 100% rename from Telegram/Resources/icons/dialogs_pinned.png rename to Telegram/Resources/icons/dialogs/dialogs_pinned.png diff --git a/Telegram/Resources/icons/dialogs_pinned@2x.png b/Telegram/Resources/icons/dialogs/dialogs_pinned@2x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_pinned@2x.png rename to Telegram/Resources/icons/dialogs/dialogs_pinned@2x.png diff --git a/Telegram/Resources/icons/dialogs_pinned@3x.png b/Telegram/Resources/icons/dialogs/dialogs_pinned@3x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_pinned@3x.png rename to Telegram/Resources/icons/dialogs/dialogs_pinned@3x.png diff --git a/Telegram/Resources/icons/dialogs_received.png b/Telegram/Resources/icons/dialogs/dialogs_received.png similarity index 100% rename from Telegram/Resources/icons/dialogs_received.png rename to Telegram/Resources/icons/dialogs/dialogs_received.png diff --git a/Telegram/Resources/icons/dialogs_received@2x.png b/Telegram/Resources/icons/dialogs/dialogs_received@2x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_received@2x.png rename to Telegram/Resources/icons/dialogs/dialogs_received@2x.png diff --git a/Telegram/Resources/icons/dialogs_received@3x.png b/Telegram/Resources/icons/dialogs/dialogs_received@3x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_received@3x.png rename to Telegram/Resources/icons/dialogs/dialogs_received@3x.png diff --git a/Telegram/Resources/icons/dialogs_search_from.png b/Telegram/Resources/icons/dialogs/dialogs_search_from.png similarity index 100% rename from Telegram/Resources/icons/dialogs_search_from.png rename to Telegram/Resources/icons/dialogs/dialogs_search_from.png diff --git a/Telegram/Resources/icons/dialogs_search_from@2x.png b/Telegram/Resources/icons/dialogs/dialogs_search_from@2x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_search_from@2x.png rename to Telegram/Resources/icons/dialogs/dialogs_search_from@2x.png diff --git a/Telegram/Resources/icons/dialogs_search_from@3x.png b/Telegram/Resources/icons/dialogs/dialogs_search_from@3x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_search_from@3x.png rename to Telegram/Resources/icons/dialogs/dialogs_search_from@3x.png diff --git a/Telegram/Resources/icons/dialogs_sending.png b/Telegram/Resources/icons/dialogs/dialogs_sending.png similarity index 100% rename from Telegram/Resources/icons/dialogs_sending.png rename to Telegram/Resources/icons/dialogs/dialogs_sending.png diff --git a/Telegram/Resources/icons/dialogs_sending@2x.png b/Telegram/Resources/icons/dialogs/dialogs_sending@2x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_sending@2x.png rename to Telegram/Resources/icons/dialogs/dialogs_sending@2x.png diff --git a/Telegram/Resources/icons/dialogs_sending@3x.png b/Telegram/Resources/icons/dialogs/dialogs_sending@3x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_sending@3x.png rename to Telegram/Resources/icons/dialogs/dialogs_sending@3x.png diff --git a/Telegram/Resources/icons/dialogs_sent.png b/Telegram/Resources/icons/dialogs/dialogs_sent.png similarity index 100% rename from Telegram/Resources/icons/dialogs_sent.png rename to Telegram/Resources/icons/dialogs/dialogs_sent.png diff --git a/Telegram/Resources/icons/dialogs_sent@2x.png b/Telegram/Resources/icons/dialogs/dialogs_sent@2x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_sent@2x.png rename to Telegram/Resources/icons/dialogs/dialogs_sent@2x.png diff --git a/Telegram/Resources/icons/dialogs_sent@3x.png b/Telegram/Resources/icons/dialogs/dialogs_sent@3x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_sent@3x.png rename to Telegram/Resources/icons/dialogs/dialogs_sent@3x.png diff --git a/Telegram/Resources/icons/dialogs_unlock.png b/Telegram/Resources/icons/dialogs/dialogs_unlock.png similarity index 100% rename from Telegram/Resources/icons/dialogs_unlock.png rename to Telegram/Resources/icons/dialogs/dialogs_unlock.png diff --git a/Telegram/Resources/icons/dialogs_unlock@2x.png b/Telegram/Resources/icons/dialogs/dialogs_unlock@2x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_unlock@2x.png rename to Telegram/Resources/icons/dialogs/dialogs_unlock@2x.png diff --git a/Telegram/Resources/icons/dialogs_unlock@3x.png b/Telegram/Resources/icons/dialogs/dialogs_unlock@3x.png similarity index 100% rename from Telegram/Resources/icons/dialogs_unlock@3x.png rename to Telegram/Resources/icons/dialogs/dialogs_unlock@3x.png diff --git a/Telegram/Resources/icons/dialogs/dialogs_verified_check.png b/Telegram/Resources/icons/dialogs/dialogs_verified_check.png new file mode 100644 index 000000000..14dfa4ee6 Binary files /dev/null and b/Telegram/Resources/icons/dialogs/dialogs_verified_check.png differ diff --git a/Telegram/Resources/icons/dialogs/dialogs_verified_check@2x.png b/Telegram/Resources/icons/dialogs/dialogs_verified_check@2x.png new file mode 100644 index 000000000..1caf9a685 Binary files /dev/null and b/Telegram/Resources/icons/dialogs/dialogs_verified_check@2x.png differ diff --git a/Telegram/Resources/icons/dialogs/dialogs_verified_check@3x.png b/Telegram/Resources/icons/dialogs/dialogs_verified_check@3x.png new file mode 100644 index 000000000..f2b4cea40 Binary files /dev/null and b/Telegram/Resources/icons/dialogs/dialogs_verified_check@3x.png differ diff --git a/Telegram/Resources/icons/dialogs/dialogs_verified_star.png b/Telegram/Resources/icons/dialogs/dialogs_verified_star.png new file mode 100644 index 000000000..334e70cdf Binary files /dev/null and b/Telegram/Resources/icons/dialogs/dialogs_verified_star.png differ diff --git a/Telegram/Resources/icons/dialogs/dialogs_verified_star@2x.png b/Telegram/Resources/icons/dialogs/dialogs_verified_star@2x.png new file mode 100644 index 000000000..9bfd666d5 Binary files /dev/null and b/Telegram/Resources/icons/dialogs/dialogs_verified_star@2x.png differ diff --git a/Telegram/Resources/icons/dialogs/dialogs_verified_star@3x.png b/Telegram/Resources/icons/dialogs/dialogs_verified_star@3x.png new file mode 100644 index 000000000..bcfd5eeb7 Binary files /dev/null and b/Telegram/Resources/icons/dialogs/dialogs_verified_star@3x.png differ diff --git a/Telegram/Resources/icons/dialogs_verified_check.png b/Telegram/Resources/icons/dialogs_verified_check.png deleted file mode 100644 index 67b732e6d..000000000 Binary files a/Telegram/Resources/icons/dialogs_verified_check.png and /dev/null differ diff --git a/Telegram/Resources/icons/dialogs_verified_check@2x.png b/Telegram/Resources/icons/dialogs_verified_check@2x.png deleted file mode 100644 index b98a405e0..000000000 Binary files a/Telegram/Resources/icons/dialogs_verified_check@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/dialogs_verified_check@3x.png b/Telegram/Resources/icons/dialogs_verified_check@3x.png deleted file mode 100644 index ce4145c47..000000000 Binary files a/Telegram/Resources/icons/dialogs_verified_check@3x.png and /dev/null differ diff --git a/Telegram/Resources/icons/dialogs_verified_star.png b/Telegram/Resources/icons/dialogs_verified_star.png deleted file mode 100644 index 9a4b8467c..000000000 Binary files a/Telegram/Resources/icons/dialogs_verified_star.png and /dev/null differ diff --git a/Telegram/Resources/icons/dialogs_verified_star@2x.png b/Telegram/Resources/icons/dialogs_verified_star@2x.png deleted file mode 100644 index de6571852..000000000 Binary files a/Telegram/Resources/icons/dialogs_verified_star@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/dialogs_verified_star@3x.png b/Telegram/Resources/icons/dialogs_verified_star@3x.png deleted file mode 100644 index 22b94f483..000000000 Binary files a/Telegram/Resources/icons/dialogs_verified_star@3x.png and /dev/null differ diff --git a/Telegram/Resources/icons/player/panel_close.png b/Telegram/Resources/icons/player/panel_close.png new file mode 100644 index 000000000..2e333de42 Binary files /dev/null and b/Telegram/Resources/icons/player/panel_close.png differ diff --git a/Telegram/Resources/icons/player/panel_close@2x.png b/Telegram/Resources/icons/player/panel_close@2x.png new file mode 100644 index 000000000..1a70b887f Binary files /dev/null and b/Telegram/Resources/icons/player/panel_close@2x.png differ diff --git a/Telegram/Resources/icons/player/panel_close@3x.png b/Telegram/Resources/icons/player/panel_close@3x.png new file mode 100644 index 000000000..1ac24a1c6 Binary files /dev/null and b/Telegram/Resources/icons/player/panel_close@3x.png differ diff --git a/Telegram/Resources/icons/player/player_backward.png b/Telegram/Resources/icons/player/player_backward.png new file mode 100644 index 000000000..d2cac60d1 Binary files /dev/null and b/Telegram/Resources/icons/player/player_backward.png differ diff --git a/Telegram/Resources/icons/player/player_backward@2x.png b/Telegram/Resources/icons/player/player_backward@2x.png new file mode 100644 index 000000000..e7afc81f3 Binary files /dev/null and b/Telegram/Resources/icons/player/player_backward@2x.png differ diff --git a/Telegram/Resources/icons/player/player_backward@3x.png b/Telegram/Resources/icons/player/player_backward@3x.png new file mode 100644 index 000000000..e83867321 Binary files /dev/null and b/Telegram/Resources/icons/player/player_backward@3x.png differ diff --git a/Telegram/Resources/icons/player_check.png b/Telegram/Resources/icons/player/player_check.png similarity index 100% rename from Telegram/Resources/icons/player_check.png rename to Telegram/Resources/icons/player/player_check.png diff --git a/Telegram/Resources/icons/player_check@2x.png b/Telegram/Resources/icons/player/player_check@2x.png similarity index 100% rename from Telegram/Resources/icons/player_check@2x.png rename to Telegram/Resources/icons/player/player_check@2x.png diff --git a/Telegram/Resources/icons/player_check@3x.png b/Telegram/Resources/icons/player/player_check@3x.png similarity index 100% rename from Telegram/Resources/icons/player_check@3x.png rename to Telegram/Resources/icons/player/player_check@3x.png diff --git a/Telegram/Resources/icons/player/player_forward.png b/Telegram/Resources/icons/player/player_forward.png new file mode 100644 index 000000000..9a28263a7 Binary files /dev/null and b/Telegram/Resources/icons/player/player_forward.png differ diff --git a/Telegram/Resources/icons/player/player_forward@2x.png b/Telegram/Resources/icons/player/player_forward@2x.png new file mode 100644 index 000000000..d1de2c67a Binary files /dev/null and b/Telegram/Resources/icons/player/player_forward@2x.png differ diff --git a/Telegram/Resources/icons/player/player_forward@3x.png b/Telegram/Resources/icons/player/player_forward@3x.png new file mode 100644 index 000000000..c59050a84 Binary files /dev/null and b/Telegram/Resources/icons/player/player_forward@3x.png differ diff --git a/Telegram/Resources/icons/player_fullscreen.png b/Telegram/Resources/icons/player/player_fullscreen.png similarity index 100% rename from Telegram/Resources/icons/player_fullscreen.png rename to Telegram/Resources/icons/player/player_fullscreen.png diff --git a/Telegram/Resources/icons/player_fullscreen@2x.png b/Telegram/Resources/icons/player/player_fullscreen@2x.png similarity index 100% rename from Telegram/Resources/icons/player_fullscreen@2x.png rename to Telegram/Resources/icons/player/player_fullscreen@2x.png diff --git a/Telegram/Resources/icons/player_fullscreen@3x.png b/Telegram/Resources/icons/player/player_fullscreen@3x.png similarity index 100% rename from Telegram/Resources/icons/player_fullscreen@3x.png rename to Telegram/Resources/icons/player/player_fullscreen@3x.png diff --git a/Telegram/Resources/icons/player/player_mini_full.png b/Telegram/Resources/icons/player/player_mini_full.png new file mode 100644 index 000000000..a56beec23 Binary files /dev/null and b/Telegram/Resources/icons/player/player_mini_full.png differ diff --git a/Telegram/Resources/icons/player/player_mini_full@2x.png b/Telegram/Resources/icons/player/player_mini_full@2x.png new file mode 100644 index 000000000..919fa5588 Binary files /dev/null and b/Telegram/Resources/icons/player/player_mini_full@2x.png differ diff --git a/Telegram/Resources/icons/player/player_mini_full@3x.png b/Telegram/Resources/icons/player/player_mini_full@3x.png new file mode 100644 index 000000000..554968fce Binary files /dev/null and b/Telegram/Resources/icons/player/player_mini_full@3x.png differ diff --git a/Telegram/Resources/icons/player/player_mini_half.png b/Telegram/Resources/icons/player/player_mini_half.png new file mode 100644 index 000000000..b73618710 Binary files /dev/null and b/Telegram/Resources/icons/player/player_mini_half.png differ diff --git a/Telegram/Resources/icons/player/player_mini_half@2x.png b/Telegram/Resources/icons/player/player_mini_half@2x.png new file mode 100644 index 000000000..34c4263dc Binary files /dev/null and b/Telegram/Resources/icons/player/player_mini_half@2x.png differ diff --git a/Telegram/Resources/icons/player/player_mini_half@3x.png b/Telegram/Resources/icons/player/player_mini_half@3x.png new file mode 100644 index 000000000..308a16a04 Binary files /dev/null and b/Telegram/Resources/icons/player/player_mini_half@3x.png differ diff --git a/Telegram/Resources/icons/player/player_mini_off.png b/Telegram/Resources/icons/player/player_mini_off.png new file mode 100644 index 000000000..a650f55d2 Binary files /dev/null and b/Telegram/Resources/icons/player/player_mini_off.png differ diff --git a/Telegram/Resources/icons/player/player_mini_off@2x.png b/Telegram/Resources/icons/player/player_mini_off@2x.png new file mode 100644 index 000000000..c3150639b Binary files /dev/null and b/Telegram/Resources/icons/player/player_mini_off@2x.png differ diff --git a/Telegram/Resources/icons/player/player_mini_off@3x.png b/Telegram/Resources/icons/player/player_mini_off@3x.png new file mode 100644 index 000000000..f4792b216 Binary files /dev/null and b/Telegram/Resources/icons/player/player_mini_off@3x.png differ diff --git a/Telegram/Resources/icons/player_minimize.png b/Telegram/Resources/icons/player/player_minimize.png similarity index 100% rename from Telegram/Resources/icons/player_minimize.png rename to Telegram/Resources/icons/player/player_minimize.png diff --git a/Telegram/Resources/icons/player_minimize@2x.png b/Telegram/Resources/icons/player/player_minimize@2x.png similarity index 100% rename from Telegram/Resources/icons/player_minimize@2x.png rename to Telegram/Resources/icons/player/player_minimize@2x.png diff --git a/Telegram/Resources/icons/player_minimize@3x.png b/Telegram/Resources/icons/player/player_minimize@3x.png similarity index 100% rename from Telegram/Resources/icons/player_minimize@3x.png rename to Telegram/Resources/icons/player/player_minimize@3x.png diff --git a/Telegram/Resources/icons/player_more.png b/Telegram/Resources/icons/player/player_more.png similarity index 100% rename from Telegram/Resources/icons/player_more.png rename to Telegram/Resources/icons/player/player_more.png diff --git a/Telegram/Resources/icons/player_more@2x.png b/Telegram/Resources/icons/player/player_more@2x.png similarity index 100% rename from Telegram/Resources/icons/player_more@2x.png rename to Telegram/Resources/icons/player/player_more@2x.png diff --git a/Telegram/Resources/icons/player_more@3x.png b/Telegram/Resources/icons/player/player_more@3x.png similarity index 100% rename from Telegram/Resources/icons/player_more@3x.png rename to Telegram/Resources/icons/player/player_more@3x.png diff --git a/Telegram/Resources/icons/player/player_order.png b/Telegram/Resources/icons/player/player_order.png new file mode 100644 index 000000000..ed2b10e71 Binary files /dev/null and b/Telegram/Resources/icons/player/player_order.png differ diff --git a/Telegram/Resources/icons/player/player_order@2x.png b/Telegram/Resources/icons/player/player_order@2x.png new file mode 100644 index 000000000..2ae4fc734 Binary files /dev/null and b/Telegram/Resources/icons/player/player_order@2x.png differ diff --git a/Telegram/Resources/icons/player/player_order@3x.png b/Telegram/Resources/icons/player/player_order@3x.png new file mode 100644 index 000000000..a372bff44 Binary files /dev/null and b/Telegram/Resources/icons/player/player_order@3x.png differ diff --git a/Telegram/Resources/icons/player/player_pause.png b/Telegram/Resources/icons/player/player_pause.png new file mode 100644 index 000000000..f7001be9d Binary files /dev/null and b/Telegram/Resources/icons/player/player_pause.png differ diff --git a/Telegram/Resources/icons/player/player_pause@2x.png b/Telegram/Resources/icons/player/player_pause@2x.png new file mode 100644 index 000000000..61e268caf Binary files /dev/null and b/Telegram/Resources/icons/player/player_pause@2x.png differ diff --git a/Telegram/Resources/icons/player/player_pause@3x.png b/Telegram/Resources/icons/player/player_pause@3x.png new file mode 100644 index 000000000..8cc548561 Binary files /dev/null and b/Telegram/Resources/icons/player/player_pause@3x.png differ diff --git a/Telegram/Resources/icons/player_pip.png b/Telegram/Resources/icons/player/player_pip.png similarity index 100% rename from Telegram/Resources/icons/player_pip.png rename to Telegram/Resources/icons/player/player_pip.png diff --git a/Telegram/Resources/icons/player_pip@2x.png b/Telegram/Resources/icons/player/player_pip@2x.png similarity index 100% rename from Telegram/Resources/icons/player_pip@2x.png rename to Telegram/Resources/icons/player/player_pip@2x.png diff --git a/Telegram/Resources/icons/player_pip@3x.png b/Telegram/Resources/icons/player/player_pip@3x.png similarity index 100% rename from Telegram/Resources/icons/player_pip@3x.png rename to Telegram/Resources/icons/player/player_pip@3x.png diff --git a/Telegram/Resources/icons/player_pip_close.png b/Telegram/Resources/icons/player/player_pip_close.png similarity index 100% rename from Telegram/Resources/icons/player_pip_close.png rename to Telegram/Resources/icons/player/player_pip_close.png diff --git a/Telegram/Resources/icons/player_pip_close@2x.png b/Telegram/Resources/icons/player/player_pip_close@2x.png similarity index 100% rename from Telegram/Resources/icons/player_pip_close@2x.png rename to Telegram/Resources/icons/player/player_pip_close@2x.png diff --git a/Telegram/Resources/icons/player_pip_close@3x.png b/Telegram/Resources/icons/player/player_pip_close@3x.png similarity index 100% rename from Telegram/Resources/icons/player_pip_close@3x.png rename to Telegram/Resources/icons/player/player_pip_close@3x.png diff --git a/Telegram/Resources/icons/player_pip_enlarge.png b/Telegram/Resources/icons/player/player_pip_enlarge.png similarity index 100% rename from Telegram/Resources/icons/player_pip_enlarge.png rename to Telegram/Resources/icons/player/player_pip_enlarge.png diff --git a/Telegram/Resources/icons/player_pip_enlarge@2x.png b/Telegram/Resources/icons/player/player_pip_enlarge@2x.png similarity index 100% rename from Telegram/Resources/icons/player_pip_enlarge@2x.png rename to Telegram/Resources/icons/player/player_pip_enlarge@2x.png diff --git a/Telegram/Resources/icons/player_pip_enlarge@3x.png b/Telegram/Resources/icons/player/player_pip_enlarge@3x.png similarity index 100% rename from Telegram/Resources/icons/player_pip_enlarge@3x.png rename to Telegram/Resources/icons/player/player_pip_enlarge@3x.png diff --git a/Telegram/Resources/icons/player_pip_pause.png b/Telegram/Resources/icons/player/player_pip_pause.png similarity index 100% rename from Telegram/Resources/icons/player_pip_pause.png rename to Telegram/Resources/icons/player/player_pip_pause.png diff --git a/Telegram/Resources/icons/player_pip_pause@2x.png b/Telegram/Resources/icons/player/player_pip_pause@2x.png similarity index 100% rename from Telegram/Resources/icons/player_pip_pause@2x.png rename to Telegram/Resources/icons/player/player_pip_pause@2x.png diff --git a/Telegram/Resources/icons/player_pip_pause@3x.png b/Telegram/Resources/icons/player/player_pip_pause@3x.png similarity index 100% rename from Telegram/Resources/icons/player_pip_pause@3x.png rename to Telegram/Resources/icons/player/player_pip_pause@3x.png diff --git a/Telegram/Resources/icons/player_pip_play.png b/Telegram/Resources/icons/player/player_pip_play.png similarity index 100% rename from Telegram/Resources/icons/player_pip_play.png rename to Telegram/Resources/icons/player/player_pip_play.png diff --git a/Telegram/Resources/icons/player_pip_play@2x.png b/Telegram/Resources/icons/player/player_pip_play@2x.png similarity index 100% rename from Telegram/Resources/icons/player_pip_play@2x.png rename to Telegram/Resources/icons/player/player_pip_play@2x.png diff --git a/Telegram/Resources/icons/player_pip_play@3x.png b/Telegram/Resources/icons/player/player_pip_play@3x.png similarity index 100% rename from Telegram/Resources/icons/player_pip_play@3x.png rename to Telegram/Resources/icons/player/player_pip_play@3x.png diff --git a/Telegram/Resources/icons/player/player_play.png b/Telegram/Resources/icons/player/player_play.png new file mode 100644 index 000000000..3488f2692 Binary files /dev/null and b/Telegram/Resources/icons/player/player_play.png differ diff --git a/Telegram/Resources/icons/player/player_play@2x.png b/Telegram/Resources/icons/player/player_play@2x.png new file mode 100644 index 000000000..7c283f331 Binary files /dev/null and b/Telegram/Resources/icons/player/player_play@2x.png differ diff --git a/Telegram/Resources/icons/player/player_play@3x.png b/Telegram/Resources/icons/player/player_play@3x.png new file mode 100644 index 000000000..aa280b9a2 Binary files /dev/null and b/Telegram/Resources/icons/player/player_play@3x.png differ diff --git a/Telegram/Resources/icons/player/player_repeat.png b/Telegram/Resources/icons/player/player_repeat.png new file mode 100644 index 000000000..1667941e7 Binary files /dev/null and b/Telegram/Resources/icons/player/player_repeat.png differ diff --git a/Telegram/Resources/icons/player/player_repeat@2x.png b/Telegram/Resources/icons/player/player_repeat@2x.png new file mode 100644 index 000000000..ae686e152 Binary files /dev/null and b/Telegram/Resources/icons/player/player_repeat@2x.png differ diff --git a/Telegram/Resources/icons/player/player_repeat@3x.png b/Telegram/Resources/icons/player/player_repeat@3x.png new file mode 100644 index 000000000..01afab9b3 Binary files /dev/null and b/Telegram/Resources/icons/player/player_repeat@3x.png differ diff --git a/Telegram/Resources/icons/player/player_repeat_single.png b/Telegram/Resources/icons/player/player_repeat_single.png new file mode 100644 index 000000000..d3d81db04 Binary files /dev/null and b/Telegram/Resources/icons/player/player_repeat_single.png differ diff --git a/Telegram/Resources/icons/player/player_repeat_single@2x.png b/Telegram/Resources/icons/player/player_repeat_single@2x.png new file mode 100644 index 000000000..0bb671ed1 Binary files /dev/null and b/Telegram/Resources/icons/player/player_repeat_single@2x.png differ diff --git a/Telegram/Resources/icons/player/player_repeat_single@3x.png b/Telegram/Resources/icons/player/player_repeat_single@3x.png new file mode 100644 index 000000000..a6aa0c883 Binary files /dev/null and b/Telegram/Resources/icons/player/player_repeat_single@3x.png differ diff --git a/Telegram/Resources/icons/player/player_reverse.png b/Telegram/Resources/icons/player/player_reverse.png new file mode 100644 index 000000000..dea0be2df Binary files /dev/null and b/Telegram/Resources/icons/player/player_reverse.png differ diff --git a/Telegram/Resources/icons/player/player_reverse@2x.png b/Telegram/Resources/icons/player/player_reverse@2x.png new file mode 100644 index 000000000..d3af5baf6 Binary files /dev/null and b/Telegram/Resources/icons/player/player_reverse@2x.png differ diff --git a/Telegram/Resources/icons/player/player_reverse@3x.png b/Telegram/Resources/icons/player/player_reverse@3x.png new file mode 100644 index 000000000..9b01d7a1b Binary files /dev/null and b/Telegram/Resources/icons/player/player_reverse@3x.png differ diff --git a/Telegram/Resources/icons/player/player_shuffle.png b/Telegram/Resources/icons/player/player_shuffle.png new file mode 100644 index 000000000..1986e7329 Binary files /dev/null and b/Telegram/Resources/icons/player/player_shuffle.png differ diff --git a/Telegram/Resources/icons/player/player_shuffle@2x.png b/Telegram/Resources/icons/player/player_shuffle@2x.png new file mode 100644 index 000000000..3c2751ac8 Binary files /dev/null and b/Telegram/Resources/icons/player/player_shuffle@2x.png differ diff --git a/Telegram/Resources/icons/player/player_shuffle@3x.png b/Telegram/Resources/icons/player/player_shuffle@3x.png new file mode 100644 index 000000000..f743ba906 Binary files /dev/null and b/Telegram/Resources/icons/player/player_shuffle@3x.png differ diff --git a/Telegram/Resources/icons/player_volume_off.png b/Telegram/Resources/icons/player/player_volume_off.png similarity index 100% rename from Telegram/Resources/icons/player_volume_off.png rename to Telegram/Resources/icons/player/player_volume_off.png diff --git a/Telegram/Resources/icons/player_volume_off@2x.png b/Telegram/Resources/icons/player/player_volume_off@2x.png similarity index 100% rename from Telegram/Resources/icons/player_volume_off@2x.png rename to Telegram/Resources/icons/player/player_volume_off@2x.png diff --git a/Telegram/Resources/icons/player_volume_off@3x.png b/Telegram/Resources/icons/player/player_volume_off@3x.png similarity index 100% rename from Telegram/Resources/icons/player_volume_off@3x.png rename to Telegram/Resources/icons/player/player_volume_off@3x.png diff --git a/Telegram/Resources/icons/player_volume_on.png b/Telegram/Resources/icons/player/player_volume_on.png similarity index 100% rename from Telegram/Resources/icons/player_volume_on.png rename to Telegram/Resources/icons/player/player_volume_on.png diff --git a/Telegram/Resources/icons/player_volume_on@2x.png b/Telegram/Resources/icons/player/player_volume_on@2x.png similarity index 100% rename from Telegram/Resources/icons/player_volume_on@2x.png rename to Telegram/Resources/icons/player/player_volume_on@2x.png diff --git a/Telegram/Resources/icons/player_volume_on@3x.png b/Telegram/Resources/icons/player/player_volume_on@3x.png similarity index 100% rename from Telegram/Resources/icons/player_volume_on@3x.png rename to Telegram/Resources/icons/player/player_volume_on@3x.png diff --git a/Telegram/Resources/icons/player_volume_small.png b/Telegram/Resources/icons/player/player_volume_small.png similarity index 100% rename from Telegram/Resources/icons/player_volume_small.png rename to Telegram/Resources/icons/player/player_volume_small.png diff --git a/Telegram/Resources/icons/player_volume_small@2x.png b/Telegram/Resources/icons/player/player_volume_small@2x.png similarity index 100% rename from Telegram/Resources/icons/player_volume_small@2x.png rename to Telegram/Resources/icons/player/player_volume_small@2x.png diff --git a/Telegram/Resources/icons/player_volume_small@3x.png b/Telegram/Resources/icons/player/player_volume_small@3x.png similarity index 100% rename from Telegram/Resources/icons/player_volume_small@3x.png rename to Telegram/Resources/icons/player/player_volume_small@3x.png diff --git a/Telegram/Resources/icons/playlist_cancel.png b/Telegram/Resources/icons/player/playlist_cancel.png similarity index 100% rename from Telegram/Resources/icons/playlist_cancel.png rename to Telegram/Resources/icons/player/playlist_cancel.png diff --git a/Telegram/Resources/icons/playlist_cancel@2x.png b/Telegram/Resources/icons/player/playlist_cancel@2x.png similarity index 100% rename from Telegram/Resources/icons/playlist_cancel@2x.png rename to Telegram/Resources/icons/player/playlist_cancel@2x.png diff --git a/Telegram/Resources/icons/playlist_cancel@3x.png b/Telegram/Resources/icons/player/playlist_cancel@3x.png similarity index 100% rename from Telegram/Resources/icons/playlist_cancel@3x.png rename to Telegram/Resources/icons/player/playlist_cancel@3x.png diff --git a/Telegram/Resources/icons/playlist_download.png b/Telegram/Resources/icons/player/playlist_download.png similarity index 100% rename from Telegram/Resources/icons/playlist_download.png rename to Telegram/Resources/icons/player/playlist_download.png diff --git a/Telegram/Resources/icons/playlist_download@2x.png b/Telegram/Resources/icons/player/playlist_download@2x.png similarity index 100% rename from Telegram/Resources/icons/playlist_download@2x.png rename to Telegram/Resources/icons/player/playlist_download@2x.png diff --git a/Telegram/Resources/icons/playlist_download@3x.png b/Telegram/Resources/icons/player/playlist_download@3x.png similarity index 100% rename from Telegram/Resources/icons/playlist_download@3x.png rename to Telegram/Resources/icons/player/playlist_download@3x.png diff --git a/Telegram/Resources/icons/playlist_pause.png b/Telegram/Resources/icons/player/playlist_pause.png similarity index 100% rename from Telegram/Resources/icons/playlist_pause.png rename to Telegram/Resources/icons/player/playlist_pause.png diff --git a/Telegram/Resources/icons/playlist_pause@2x.png b/Telegram/Resources/icons/player/playlist_pause@2x.png similarity index 100% rename from Telegram/Resources/icons/playlist_pause@2x.png rename to Telegram/Resources/icons/player/playlist_pause@2x.png diff --git a/Telegram/Resources/icons/playlist_pause@3x.png b/Telegram/Resources/icons/player/playlist_pause@3x.png similarity index 100% rename from Telegram/Resources/icons/playlist_pause@3x.png rename to Telegram/Resources/icons/player/playlist_pause@3x.png diff --git a/Telegram/Resources/icons/playlist_play.png b/Telegram/Resources/icons/player/playlist_play.png similarity index 100% rename from Telegram/Resources/icons/playlist_play.png rename to Telegram/Resources/icons/player/playlist_play.png diff --git a/Telegram/Resources/icons/playlist_play@2x.png b/Telegram/Resources/icons/player/playlist_play@2x.png similarity index 100% rename from Telegram/Resources/icons/playlist_play@2x.png rename to Telegram/Resources/icons/player/playlist_play@2x.png diff --git a/Telegram/Resources/icons/playlist_play@3x.png b/Telegram/Resources/icons/player/playlist_play@3x.png similarity index 100% rename from Telegram/Resources/icons/playlist_play@3x.png rename to Telegram/Resources/icons/player/playlist_play@3x.png diff --git a/Telegram/Resources/icons/playlist_shadow.png b/Telegram/Resources/icons/player/playlist_shadow.png similarity index 100% rename from Telegram/Resources/icons/playlist_shadow.png rename to Telegram/Resources/icons/player/playlist_shadow.png diff --git a/Telegram/Resources/icons/playlist_shadow@2x.png b/Telegram/Resources/icons/player/playlist_shadow@2x.png similarity index 100% rename from Telegram/Resources/icons/playlist_shadow@2x.png rename to Telegram/Resources/icons/player/playlist_shadow@2x.png diff --git a/Telegram/Resources/icons/playlist_shadow@3x.png b/Telegram/Resources/icons/player/playlist_shadow@3x.png similarity index 100% rename from Telegram/Resources/icons/playlist_shadow@3x.png rename to Telegram/Resources/icons/player/playlist_shadow@3x.png diff --git a/Telegram/Resources/icons/voice_speed/voice_speed0.5.png b/Telegram/Resources/icons/player/voice_speed/voice_speed0.5.png similarity index 100% rename from Telegram/Resources/icons/voice_speed/voice_speed0.5.png rename to Telegram/Resources/icons/player/voice_speed/voice_speed0.5.png diff --git a/Telegram/Resources/icons/voice_speed/voice_speed0.5@2x.png b/Telegram/Resources/icons/player/voice_speed/voice_speed0.5@2x.png similarity index 100% rename from Telegram/Resources/icons/voice_speed/voice_speed0.5@2x.png rename to Telegram/Resources/icons/player/voice_speed/voice_speed0.5@2x.png diff --git a/Telegram/Resources/icons/voice_speed/voice_speed0.5@3x.png b/Telegram/Resources/icons/player/voice_speed/voice_speed0.5@3x.png similarity index 100% rename from Telegram/Resources/icons/voice_speed/voice_speed0.5@3x.png rename to Telegram/Resources/icons/player/voice_speed/voice_speed0.5@3x.png diff --git a/Telegram/Resources/icons/voice_speed/voice_speed1.5.png b/Telegram/Resources/icons/player/voice_speed/voice_speed1.5.png similarity index 100% rename from Telegram/Resources/icons/voice_speed/voice_speed1.5.png rename to Telegram/Resources/icons/player/voice_speed/voice_speed1.5.png diff --git a/Telegram/Resources/icons/voice_speed/voice_speed1.5@2x.png b/Telegram/Resources/icons/player/voice_speed/voice_speed1.5@2x.png similarity index 100% rename from Telegram/Resources/icons/voice_speed/voice_speed1.5@2x.png rename to Telegram/Resources/icons/player/voice_speed/voice_speed1.5@2x.png diff --git a/Telegram/Resources/icons/voice_speed/voice_speed1.5@3x.png b/Telegram/Resources/icons/player/voice_speed/voice_speed1.5@3x.png similarity index 100% rename from Telegram/Resources/icons/voice_speed/voice_speed1.5@3x.png rename to Telegram/Resources/icons/player/voice_speed/voice_speed1.5@3x.png diff --git a/Telegram/Resources/icons/voice_speed/voice_speed2.png b/Telegram/Resources/icons/player/voice_speed/voice_speed2.png similarity index 100% rename from Telegram/Resources/icons/voice_speed/voice_speed2.png rename to Telegram/Resources/icons/player/voice_speed/voice_speed2.png diff --git a/Telegram/Resources/icons/voice_speed/voice_speed2@2x.png b/Telegram/Resources/icons/player/voice_speed/voice_speed2@2x.png similarity index 100% rename from Telegram/Resources/icons/voice_speed/voice_speed2@2x.png rename to Telegram/Resources/icons/player/voice_speed/voice_speed2@2x.png diff --git a/Telegram/Resources/icons/voice_speed/voice_speed2@3x.png b/Telegram/Resources/icons/player/voice_speed/voice_speed2@3x.png similarity index 100% rename from Telegram/Resources/icons/voice_speed/voice_speed2@3x.png rename to Telegram/Resources/icons/player/voice_speed/voice_speed2@3x.png diff --git a/Telegram/Resources/icons/player_close.png b/Telegram/Resources/icons/player_close.png deleted file mode 100644 index d27dd6b70..000000000 Binary files a/Telegram/Resources/icons/player_close.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_close@2x.png b/Telegram/Resources/icons/player_close@2x.png deleted file mode 100644 index cf0e108d8..000000000 Binary files a/Telegram/Resources/icons/player_close@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_close@3x.png b/Telegram/Resources/icons/player_close@3x.png deleted file mode 100644 index c6d8846fa..000000000 Binary files a/Telegram/Resources/icons/player_close@3x.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_next.png b/Telegram/Resources/icons/player_next.png deleted file mode 100644 index cdcf1f3da..000000000 Binary files a/Telegram/Resources/icons/player_next.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_next@2x.png b/Telegram/Resources/icons/player_next@2x.png deleted file mode 100644 index b646b8472..000000000 Binary files a/Telegram/Resources/icons/player_next@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_next@3x.png b/Telegram/Resources/icons/player_next@3x.png deleted file mode 100644 index 7650bfdb7..000000000 Binary files a/Telegram/Resources/icons/player_next@3x.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_panel_next.png b/Telegram/Resources/icons/player_panel_next.png deleted file mode 100644 index 2649ce5d7..000000000 Binary files a/Telegram/Resources/icons/player_panel_next.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_panel_next@2x.png b/Telegram/Resources/icons/player_panel_next@2x.png deleted file mode 100644 index 2c93ed70e..000000000 Binary files a/Telegram/Resources/icons/player_panel_next@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_panel_next@3x.png b/Telegram/Resources/icons/player_panel_next@3x.png deleted file mode 100644 index 05ac52690..000000000 Binary files a/Telegram/Resources/icons/player_panel_next@3x.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_panel_pin.png b/Telegram/Resources/icons/player_panel_pin.png deleted file mode 100644 index 809ba4327..000000000 Binary files a/Telegram/Resources/icons/player_panel_pin.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_panel_pin@2x.png b/Telegram/Resources/icons/player_panel_pin@2x.png deleted file mode 100644 index 07e07315c..000000000 Binary files a/Telegram/Resources/icons/player_panel_pin@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_panel_pin@3x.png b/Telegram/Resources/icons/player_panel_pin@3x.png deleted file mode 100644 index 7ad80b27f..000000000 Binary files a/Telegram/Resources/icons/player_panel_pin@3x.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_pause.png b/Telegram/Resources/icons/player_pause.png deleted file mode 100644 index a09d1730a..000000000 Binary files a/Telegram/Resources/icons/player_pause.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_pause@2x.png b/Telegram/Resources/icons/player_pause@2x.png deleted file mode 100644 index ef66eea10..000000000 Binary files a/Telegram/Resources/icons/player_pause@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_pause@3x.png b/Telegram/Resources/icons/player_pause@3x.png deleted file mode 100644 index 5392c68d8..000000000 Binary files a/Telegram/Resources/icons/player_pause@3x.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_play.png b/Telegram/Resources/icons/player_play.png deleted file mode 100644 index 4dc3dab18..000000000 Binary files a/Telegram/Resources/icons/player_play.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_play@2x.png b/Telegram/Resources/icons/player_play@2x.png deleted file mode 100644 index e4a2e5186..000000000 Binary files a/Telegram/Resources/icons/player_play@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_play@3x.png b/Telegram/Resources/icons/player_play@3x.png deleted file mode 100644 index 8a14c8cea..000000000 Binary files a/Telegram/Resources/icons/player_play@3x.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_repeat.png b/Telegram/Resources/icons/player_repeat.png deleted file mode 100644 index 99b52c3ef..000000000 Binary files a/Telegram/Resources/icons/player_repeat.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_repeat@2x.png b/Telegram/Resources/icons/player_repeat@2x.png deleted file mode 100644 index 870d4ff84..000000000 Binary files a/Telegram/Resources/icons/player_repeat@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_repeat@3x.png b/Telegram/Resources/icons/player_repeat@3x.png deleted file mode 100644 index d37882c23..000000000 Binary files a/Telegram/Resources/icons/player_repeat@3x.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_volume0.png b/Telegram/Resources/icons/player_volume0.png deleted file mode 100644 index 9a531489b..000000000 Binary files a/Telegram/Resources/icons/player_volume0.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_volume0@2x.png b/Telegram/Resources/icons/player_volume0@2x.png deleted file mode 100644 index dbaec7557..000000000 Binary files a/Telegram/Resources/icons/player_volume0@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_volume0@3x.png b/Telegram/Resources/icons/player_volume0@3x.png deleted file mode 100644 index 5b7750508..000000000 Binary files a/Telegram/Resources/icons/player_volume0@3x.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_volume1.png b/Telegram/Resources/icons/player_volume1.png deleted file mode 100644 index e45f35612..000000000 Binary files a/Telegram/Resources/icons/player_volume1.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_volume1@2x.png b/Telegram/Resources/icons/player_volume1@2x.png deleted file mode 100644 index 933e8ca99..000000000 Binary files a/Telegram/Resources/icons/player_volume1@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_volume1@3x.png b/Telegram/Resources/icons/player_volume1@3x.png deleted file mode 100644 index 1e9daea63..000000000 Binary files a/Telegram/Resources/icons/player_volume1@3x.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_volume2.png b/Telegram/Resources/icons/player_volume2.png deleted file mode 100644 index 70faf1133..000000000 Binary files a/Telegram/Resources/icons/player_volume2.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_volume2@2x.png b/Telegram/Resources/icons/player_volume2@2x.png deleted file mode 100644 index f7a65f540..000000000 Binary files a/Telegram/Resources/icons/player_volume2@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_volume2@3x.png b/Telegram/Resources/icons/player_volume2@3x.png deleted file mode 100644 index 824386b9d..000000000 Binary files a/Telegram/Resources/icons/player_volume2@3x.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_volume3.png b/Telegram/Resources/icons/player_volume3.png deleted file mode 100644 index 7d27dc27a..000000000 Binary files a/Telegram/Resources/icons/player_volume3.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_volume3@2x.png b/Telegram/Resources/icons/player_volume3@2x.png deleted file mode 100644 index 501e14e14..000000000 Binary files a/Telegram/Resources/icons/player_volume3@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/player_volume3@3x.png b/Telegram/Resources/icons/player_volume3@3x.png deleted file mode 100644 index 24ff5175c..000000000 Binary files a/Telegram/Resources/icons/player_volume3@3x.png and /dev/null differ diff --git a/Telegram/Resources/icons/settings/devices/device_desktop_mac.lottie b/Telegram/Resources/icons/settings/devices/device_desktop_mac.lottie new file mode 100644 index 000000000..8185f1dd6 --- /dev/null +++ b/Telegram/Resources/icons/settings/devices/device_desktop_mac.lottie @@ -0,0 +1 @@ +{"v":"5.7.4","fr":60,"ip":0,"op":120,"w":30,"h":30,"nm":"mac_30","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Rectangle 21","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[15,20.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[16.667,16.667,100],"ix":6,"l":2}},"ao":0,"shapes":[],"ip":0,"op":120,"st":-120,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Rectangle 23","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[15,22,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[16.667,16.667,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,-0.276],[-0.828,0],[0,0],[0,0.828],[0.276,0]],"o":[[-0.276,0],[0,0.828],[0,0],[0.828,0],[0,-0.276],[0,0]],"v":[[-12.5,-1],[-13,-0.5],[-11.5,1],[11.5,1],[13,-0.5],[12.5,-1]],"c":true},"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Rectangle 22","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-120,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Rectangle 22","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[15,22,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[16.667,16.667,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,-0.276],[-0.828,0],[0,0],[0,0.828],[0.276,0]],"o":[[-0.276,0],[0,0.828],[0,0],[0.828,0],[0,-0.276],[0,0]],"v":[[-12.5,-17.667],[-13,-0.5],[-11.5,1],[11.5,1],[13,-0.5],[12.5,-17.667]],"c":true},"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Rectangle 22","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-120,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Rectangle 21","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[15,15,0],"to":[0,1.083,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":1,"y":0},"t":20,"s":[15,21.5,0],"to":[0,0,0],"ti":[0,1.083,0]},{"t":40,"s":[15,15,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[16.667,16.667,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0.192,-0.376],[0,-1.12],[0,0],[0,0],[0,0],[0.218,0.428],[0.376,0.192],[1.12,0],[0,0],[0.428,-0.218]],"o":[[-0.218,0.428],[0,0],[0,0],[0,0],[0,-1.12],[-0.192,-0.376],[-0.428,-0.218],[0,0],[-1.12,0],[-0.376,0.192]],"v":[[-8.782,-5.908],[-9,-3.8],[-9,7],[9,7],[9,-3.8],[8.782,-5.908],[7.908,-6.782],[5.8,-7],[-5.8,-7],[-7.908,-6.782]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":1,"y":0},"t":20,"s":[{"i":[[0.256,-0.027],[0,-0.08],[0,0],[0,0],[0,0],[0.291,0.031],[0.502,0.014],[1.493,0],[0,0],[0.57,-0.016]],"o":[[-0.291,0.031],[0,0],[0,0],[0,0],[0,-0.08],[-0.256,-0.027],[-0.57,-0.016],[0,0],[-1.493,0],[-0.502,0.014]],"v":[[-11.709,-0.422],[-12,-0.271],[-12,0.5],[12,0.5],[12,-0.271],[11.709,-0.422],[10.544,-0.484],[7.733,-0.5],[-7.733,-0.5],[-10.544,-0.484]],"c":true}]},{"t":40,"s":[{"i":[[0.192,-0.376],[0,-1.12],[0,0],[0,0],[0,0],[0.218,0.428],[0.376,0.192],[1.12,0],[0,0],[0.428,-0.218]],"o":[[-0.218,0.428],[0,0],[0,0],[0,0],[0,-1.12],[-0.192,-0.376],[-0.428,-0.218],[0,0],[-1.12,0],[-0.376,0.192]],"v":[[-8.782,-5.908],[-9,-3.8],[-9,7],[9,7],[9,-3.8],[8.782,-5.908],[7.908,-6.782],[5.8,-7],[-5.8,-7],[-7.908,-6.782]],"c":true}]}],"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Обводка 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Rectangle 21","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-120,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"apple 2","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.955,-1.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[80,80,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[1,1,0.333],"y":[0,0,0]},"t":20,"s":[40,0,100]},{"t":40,"s":[80,80,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.31,0.445],[-0.161,0.375],[-0.056,0.176],[0.222,0.21],[0.009,0.704],[-0.829,0.5],[0.927,0.074],[0.494,-0.176],[0.093,0],[0.431,0.162],[0.264,0],[0.409,-0.246],[0.242,-0.418],[0,-0.737],[-0.236,-0.686],[-0.359,-0.515],[-0.213,-0.185],[-0.334,0.014],[-0.357,0.144],[-0.344,0.006],[-0.307,-0.133],[-0.25,0],[-0.306,0.278]],"o":[[0.234,-0.334],[0.07,-0.162],[-0.28,-0.122],[-0.5,-0.463],[-0.009,-0.899],[-0.463,-0.658],[-0.34,-0.028],[-0.524,0.19],[-0.12,0],[-0.431,-0.162],[-0.477,0.005],[-0.414,0.248],[-0.32,0.533],[0,0.644],[0.201,0.594],[0.32,0.449],[0.334,0.31],[0.222,-0.009],[0.317,-0.135],[0.335,0.007],[0.352,0.148],[0.347,-0.009],[0.195,-0.171]],"v":[[3.848,4.137],[4.441,3.071],[4.631,2.566],[3.871,2.066],[3.107,0.315],[4.334,-1.789],[2.249,-2.887],[0.998,-2.665],[0.076,-2.377],[-0.753,-2.623],[-1.8,-2.868],[-3.153,-2.484],[-4.154,-1.469],[-4.631,0.435],[-4.274,2.427],[-3.431,4.1],[-2.634,5.054],[-1.633,5.499],[-0.767,5.272],[0.234,5.059],[1.207,5.272],[2.11,5.49],[3.088,5.059]],"c":true},"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-0.482,0.565],[0,0.551],[0.005,0.074],[0.277,-0.142],[0.217,-0.246],[0,-0.528],[-0.01,-0.069]],"o":[[0.403,-0.473],[0,-0.074],[-0.31,0.023],[-0.297,0.139],[-0.408,0.463],[0,0.07],[0.63,0.051]],"v":[[1.717,-3.739],[2.319,-5.278],[2.31,-5.5],[1.42,-5.25],[0.642,-4.666],[0.002,-3.109],[0.016,-2.901]],"c":true},"ix":2},"nm":"Контур 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Объединить контуры 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"apple","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-120,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/Telegram/Resources/icons/settings/devices/device_desktop_mac.png b/Telegram/Resources/icons/settings/devices/device_desktop_mac.png new file mode 100644 index 000000000..8490e5e07 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_desktop_mac.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_desktop_mac@2x.png b/Telegram/Resources/icons/settings/devices/device_desktop_mac@2x.png new file mode 100644 index 000000000..6992485c1 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_desktop_mac@2x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_desktop_mac@3x.png b/Telegram/Resources/icons/settings/devices/device_desktop_mac@3x.png new file mode 100644 index 000000000..75e0e14f8 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_desktop_mac@3x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_desktop_win.lottie b/Telegram/Resources/icons/settings/devices/device_desktop_win.lottie new file mode 100644 index 000000000..979b7d8ea --- /dev/null +++ b/Telegram/Resources/icons/settings/devices/device_desktop_win.lottie @@ -0,0 +1 @@ +{"v":"5.7.4","fr":60,"ip":0,"op":120,"w":30,"h":30,"nm":"windows_30","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Union","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[15]},{"i":{"x":[0.833],"y":[0.889]},"o":{"x":[0.333],"y":[0]},"t":25,"s":[12.5]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.222]},"t":35,"s":[15]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":55,"s":[17.5]},{"t":70,"s":[15]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[15]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":25,"s":[15]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":35,"s":[15]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":55,"s":[15]},{"t":70,"s":[15]}],"ix":4}},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[16.667,16.667,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-1,11],[-1,1],[-12.667,1],[-12.667,-1],[-1,-1],[-1,-11],[1,-11],[1,-1],[12.667,-1],[12.667,1],[1,1],[1,11]],"c":true},"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Union","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-60,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Rectangle 23","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[15,15,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[16.667,16.667,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[9,-9],[9,9],[-9,9],[-9,-9]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":25,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[9,-10.667],[9,10.667],[-10.667,5.667],[-10.667,-5.667]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":35,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[9,-9],[9,9],[-9,9],[-9,-9]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":55,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[10.667,-5.667],[10.667,5.667],[-9,10.667],[-9,-10.667]],"c":true}]},{"t":70,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[9,-9],[9,9],[-9,9],[-9,-9]],"c":true}]}],"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Rectangle 23","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-60,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/Telegram/Resources/icons/settings/devices/device_desktop_win.png b/Telegram/Resources/icons/settings/devices/device_desktop_win.png new file mode 100644 index 000000000..713026f68 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_desktop_win.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_desktop_win@2x.png b/Telegram/Resources/icons/settings/devices/device_desktop_win@2x.png new file mode 100644 index 000000000..1c9da1243 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_desktop_win@2x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_desktop_win@3x.png b/Telegram/Resources/icons/settings/devices/device_desktop_win@3x.png new file mode 100644 index 000000000..3d42cf390 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_desktop_win@3x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_linux.lottie b/Telegram/Resources/icons/settings/devices/device_linux.lottie new file mode 100644 index 000000000..bdbaff768 --- /dev/null +++ b/Telegram/Resources/icons/settings/devices/device_linux.lottie @@ -0,0 +1 @@ +{"v":"5.7.4","fr":60,"ip":0,"op":120,"w":30,"h":30,"nm":"linux_30","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Vector 25","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[15,21.686,0],"to":[0,0.069,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[15,22.103,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":20,"s":[15,21.686,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":30,"s":[15,22.103,0],"to":[0,0,0],"ti":[0,0.069,0]},{"t":40,"s":[15,21.686,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[16.667,16.667,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-0.452,0.127],[-1.798,0],[-1.471,-0.414],[0.428,-0.195],[1.333,-1.348],[0.372,0.376],[1.412,0.643]],"o":[[1.471,-0.414],[1.798,0],[0.452,0.127],[-1.412,0.643],[-0.372,0.376],[-1.333,-1.348],[-0.428,-0.195]],"v":[[-5.66,-1.69],[0,-2.686],[5.66,-1.69],[5.712,-0.738],[0.726,2.404],[-0.726,2.404],[-5.712,-0.738]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[-0.452,0.127],[-1.798,0],[-1.471,-0.414],[0.428,-0.195],[1.333,-1.348],[0.372,0.376],[1.412,0.643]],"o":[[1.471,-0.414],[1.798,0],[0.452,0.127],[-1.412,0.643],[-0.372,0.376],[-1.333,-1.348],[-0.428,-0.195]],"v":[[-5.66,-1.69],[0,-2.686],[5.66,-1.69],[5.712,-0.738],[0.726,2.821],[-0.726,2.821],[-5.712,-0.738]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":20,"s":[{"i":[[-0.452,0.127],[-1.798,0],[-1.471,-0.414],[0.428,-0.195],[1.333,-1.348],[0.372,0.376],[1.412,0.643]],"o":[[1.471,-0.414],[1.798,0],[0.452,0.127],[-1.412,0.643],[-0.372,0.376],[-1.333,-1.348],[-0.428,-0.195]],"v":[[-5.66,-1.69],[0,-2.686],[5.66,-1.69],[5.712,-0.738],[0.726,2.404],[-0.726,2.404],[-5.712,-0.738]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":30,"s":[{"i":[[-0.452,0.127],[-1.798,0],[-1.471,-0.414],[0.428,-0.195],[1.333,-1.348],[0.372,0.376],[1.412,0.643]],"o":[[1.471,-0.414],[1.798,0],[0.452,0.127],[-1.412,0.643],[-0.372,0.376],[-1.333,-1.348],[-0.428,-0.195]],"v":[[-5.66,-1.69],[0,-2.686],[5.66,-1.69],[5.712,-0.738],[0.726,2.821],[-0.726,2.821],[-5.712,-0.738]],"c":true}]},{"t":40,"s":[{"i":[[-0.452,0.127],[-1.798,0],[-1.471,-0.414],[0.428,-0.195],[1.333,-1.348],[0.372,0.376],[1.412,0.643]],"o":[[1.471,-0.414],[1.798,0],[0.452,0.127],[-1.412,0.643],[-0.372,0.376],[-1.333,-1.348],[-0.428,-0.195]],"v":[[-5.66,-1.69],[0,-2.686],[5.66,-1.69],[5.712,-0.738],[0.726,2.404],[-0.726,2.404],[-5.712,-0.738]],"c":true}]}],"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Vector 25","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-60,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Ellipse 28","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[19,16.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[16.667,16.667,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":10,"s":[20,5,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":20,"s":[16.667,16.667,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":30,"s":[20,5,100]},{"t":40,"s":[16.667,16.667,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[2,2],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Контур эллипса 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Ellipse 28","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-60,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Ellipse 27","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[11,16.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[16.667,16.667,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":10,"s":[20,5,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":20,"s":[16.667,16.667,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":30,"s":[20,5,100]},{"t":40,"s":[16.667,16.667,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[2,2],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Контур эллипса 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Ellipse 27","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-60,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Subtract","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[15,12,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[16.667,16.667,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,1.422],[-5.523,0],[0,-5.523],[0.535,-1.225],[0,0],[2.761,0],[0.81,-1.668],[1.979,0],[0,-2.761],[0,0]],"o":[[0,-5.523],[5.523,0],[0,1.422],[0,0],[0,-2.761],[-1.979,0],[-0.81,-1.668],[-2.761,0],[0,0],[-0.535,-1.225]],"v":[[-10,3],[0,-7],[10,3],[9.168,7],[9.5,5],[4.5,0],[0,2.818],[-4.5,0],[-9.5,5],[-9.168,7]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[0,1.422],[-5.523,0],[0,-5.523],[0.535,-1.225],[0,0],[2.761,0],[0.81,-1.668],[1.979,0],[0,-2.761],[0,0]],"o":[[0,-5.523],[5.523,0],[0,1.422],[0,0],[0,-2.761],[-1.979,0],[-0.81,-1.668],[-2.761,0],[0,0],[-0.535,-1.225]],"v":[[-10,3],[0,-7],[10,3],[9.168,7],[9.5,5],[4.5,1.25],[0,4.068],[-4.5,1.25],[-9.5,5],[-9.168,7]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":20,"s":[{"i":[[0,1.422],[-5.523,0],[0,-5.523],[0.535,-1.225],[0,0],[2.761,0],[0.81,-1.668],[1.979,0],[0,-2.761],[0,0]],"o":[[0,-5.523],[5.523,0],[0,1.422],[0,0],[0,-2.761],[-1.979,0],[-0.81,-1.668],[-2.761,0],[0,0],[-0.535,-1.225]],"v":[[-10,3],[0,-7],[10,3],[9.168,7],[9.5,5],[4.5,0],[0,2.818],[-4.5,0],[-9.5,5],[-9.168,7]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":30,"s":[{"i":[[0,1.422],[-5.523,0],[0,-5.523],[0.535,-1.225],[0,0],[2.761,0],[0.81,-1.668],[1.979,0],[0,-2.761],[0,0]],"o":[[0,-5.523],[5.523,0],[0,1.422],[0,0],[0,-2.761],[-1.979,0],[-0.81,-1.668],[-2.761,0],[0,0],[-0.535,-1.225]],"v":[[-10,3],[0,-7],[10,3],[9.168,7],[9.5,5],[4.5,1.25],[0,4.068],[-4.5,1.25],[-9.5,5],[-9.168,7]],"c":true}]},{"t":40,"s":[{"i":[[0,1.422],[-5.523,0],[0,-5.523],[0.535,-1.225],[0,0],[2.761,0],[0.81,-1.668],[1.979,0],[0,-2.761],[0,0]],"o":[[0,-5.523],[5.523,0],[0,1.422],[0,0],[0,-2.761],[-1.979,0],[-0.81,-1.668],[-2.761,0],[0,0],[-0.535,-1.225]],"v":[[-10,3],[0,-7],[10,3],[9.168,7],[9.5,5],[4.5,0],[0,2.818],[-4.5,0],[-9.5,5],[-9.168,7]],"c":true}]}],"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Subtract","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-60,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/Telegram/Resources/icons/settings/devices/device_linux.png b/Telegram/Resources/icons/settings/devices/device_linux.png new file mode 100644 index 000000000..9bd1ab9c3 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_linux.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_linux@2x.png b/Telegram/Resources/icons/settings/devices/device_linux@2x.png new file mode 100644 index 000000000..e6eecd577 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_linux@2x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_linux@3x.png b/Telegram/Resources/icons/settings/devices/device_linux@3x.png new file mode 100644 index 000000000..728a922cf Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_linux@3x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_linux_ubuntu.lottie b/Telegram/Resources/icons/settings/devices/device_linux_ubuntu.lottie new file mode 100644 index 000000000..b4f556719 --- /dev/null +++ b/Telegram/Resources/icons/settings/devices/device_linux_ubuntu.lottie @@ -0,0 +1 @@ +{"v":"5.7.4","fr":60,"ip":0,"op":120,"w":30,"h":30,"nm":"ubuntu_30","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Union","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[13.594,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-4.297,-5.178],[-6.797,-9.508],[-8.234,-8.678],[-5.734,-4.348]],"c":true},"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-8.234,8.678],[-5.734,4.348],[-4.297,5.178],[-6.797,9.508]],"c":true},"ix":2},"nm":"Контур 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[3.234,0.83],[8.234,0.83],[8.234,-0.83],[3.234,-0.83]],"c":true},"ix":2},"nm":"Контур 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Объединить контуры 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Union","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-60,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Ellipse 24","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[30,51.962,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[-100,-100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":15,"s":[-70,-70,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":20,"s":[-130,-130,100]},{"t":30,"s":[-100,-100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[4,4],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Контур эллипса 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.66,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Обводка 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":60,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Ellipse 24","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-60,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Ellipse 24","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-60,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[-100,-100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":15,"s":[-70,-70,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":20,"s":[-130,-130,100]},{"t":30,"s":[-100,-100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[4,4],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Контур эллипса 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.66,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Обводка 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Ellipse 24","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-60,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Ellipse 25","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[30,-51.961,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[-100,-100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":15,"s":[-70,-70,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":20,"s":[-130,-130,100]},{"t":30,"s":[-100,-100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[4,4],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Контур эллипса 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.66,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Обводка 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":-60,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Ellipse 24","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-60,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Ellipse 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":20,"s":[-245]},{"t":30,"s":[-240]}],"ix":10},"p":{"a":0,"k":[15,15,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[16.667,16.667,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[16,16],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Контур эллипса 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Обводка 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Ellipse 3","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-60,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/Telegram/Resources/icons/settings/devices/device_linux_ubuntu.png b/Telegram/Resources/icons/settings/devices/device_linux_ubuntu.png new file mode 100644 index 000000000..8cb524953 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_linux_ubuntu.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_linux_ubuntu@2x.png b/Telegram/Resources/icons/settings/devices/device_linux_ubuntu@2x.png new file mode 100644 index 000000000..37a44106e Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_linux_ubuntu@2x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_linux_ubuntu@3x.png b/Telegram/Resources/icons/settings/devices/device_linux_ubuntu@3x.png new file mode 100644 index 000000000..8544938c1 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_linux_ubuntu@3x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_other.png b/Telegram/Resources/icons/settings/devices/device_other.png new file mode 100644 index 000000000..ad8bce06a Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_other.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_other@2x.png b/Telegram/Resources/icons/settings/devices/device_other@2x.png new file mode 100644 index 000000000..db27d3482 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_other@2x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_other@3x.png b/Telegram/Resources/icons/settings/devices/device_other@3x.png new file mode 100644 index 000000000..9ec10ee96 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_other@3x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_other_large.png b/Telegram/Resources/icons/settings/devices/device_other_large.png new file mode 100644 index 000000000..0f47128db Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_other_large.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_other_large@2x.png b/Telegram/Resources/icons/settings/devices/device_other_large@2x.png new file mode 100644 index 000000000..ee45e7b9a Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_other_large@2x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_other_large@3x.png b/Telegram/Resources/icons/settings/devices/device_other_large@3x.png new file mode 100644 index 000000000..5b617f833 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_other_large@3x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_phone_android.lottie b/Telegram/Resources/icons/settings/devices/device_phone_android.lottie new file mode 100644 index 000000000..355d94431 --- /dev/null +++ b/Telegram/Resources/icons/settings/devices/device_phone_android.lottie @@ -0,0 +1 @@ +{"v":"5.7.4","fr":60,"ip":0,"op":120,"w":30,"h":30,"nm":"android_30","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Eye L","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[10.5,16,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[16.667,16.667,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":10,"s":[20,5,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":20,"s":[16.667,16.667,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":30,"s":[20,5,100]},{"t":40,"s":[16.667,16.667,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[3,3],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Контур эллипса 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Eye L","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-10,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Eye R","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[19.5,16,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[16.667,16.667,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":10,"s":[20,5,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":20,"s":[16.667,16.667,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":30,"s":[20,5,100]},{"t":40,"s":[16.667,16.667,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[3,3],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Контур эллипса 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Eye R","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-10,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Face","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[15,15.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[16.667,16.667,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.335,1.25],[-4.973,0],[-1.361,-5.079],[0.382,-0.702],[0.129,-0.123],[1.573,0],[0,0],[0.579,0.551],[0.085,0.157]],"o":[[1.361,-5.079],[4.973,0],[0.335,1.25],[-0.085,0.157],[-0.579,0.551],[0,0],[-1.573,0],[-0.129,-0.123],[-0.382,-0.702]],"v":[[-10.536,2.379],[0,-6],[10.536,2.379],[10.657,4.957],[10.279,5.449],[7.34,6],[-7.34,6],[-10.279,5.449],[-10.657,4.957]],"c":true},"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Face","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-10,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Ri","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[20.75,9.75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[16.667,16.667,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":1,"y":0},"t":0,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[1.25,-2.25],[-1.25,2.25]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[2.5,-1],[-1.25,2.25]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":1,"y":0},"t":20,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[1.25,-2.25],[-1.25,2.25]],"c":false}]},{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":30,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[2.5,-1],[-1.25,2.25]],"c":false}]},{"t":40,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[1.25,-2.25],[-1.25,2.25]],"c":false}]}],"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.66,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Обводка 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Ri","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-10,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Le","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[9.25,9.75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[16.667,16.667,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":1,"y":0},"t":0,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-1.25,-2.25],[1.25,2.25]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.5,-1],[1.25,2.25]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":1,"y":0},"t":20,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-1.25,-2.25],[1.25,2.25]],"c":false}]},{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":30,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-2.5,-1],[1.25,2.25]],"c":false}]},{"t":40,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-1.25,-2.25],[1.25,2.25]],"c":false}]}],"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.66,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Обводка 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Le","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-10,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/Telegram/Resources/icons/settings/devices/device_phone_android.png b/Telegram/Resources/icons/settings/devices/device_phone_android.png new file mode 100644 index 000000000..19b73ecd0 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_phone_android.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_phone_android@2x.png b/Telegram/Resources/icons/settings/devices/device_phone_android@2x.png new file mode 100644 index 000000000..331136c78 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_phone_android@2x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_phone_android@3x.png b/Telegram/Resources/icons/settings/devices/device_phone_android@3x.png new file mode 100644 index 000000000..710ba2fa9 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_phone_android@3x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_phone_ios.lottie b/Telegram/Resources/icons/settings/devices/device_phone_ios.lottie new file mode 100644 index 000000000..e5cefd536 --- /dev/null +++ b/Telegram/Resources/icons/settings/devices/device_phone_ios.lottie @@ -0,0 +1 @@ +{"v":"5.7.4","fr":60,"ip":0,"op":120,"w":30,"h":30,"nm":"iphone_30","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"apple","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[14.841,14.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":10,"s":[0,13.333,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[1,1,0.333],"y":[0,0,0]},"t":20,"s":[15,15,100]},{"t":30,"s":[0,13.333,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.31,0.445],[-0.161,0.375],[-0.056,0.176],[0.222,0.21],[0.009,0.704],[-0.829,0.5],[0.927,0.074],[0.494,-0.176],[0.093,0],[0.431,0.162],[0.264,0],[0.409,-0.246],[0.242,-0.418],[0,-0.737],[-0.236,-0.686],[-0.359,-0.515],[-0.213,-0.185],[-0.334,0.014],[-0.357,0.144],[-0.344,0.006],[-0.307,-0.133],[-0.25,0],[-0.306,0.278]],"o":[[0.234,-0.334],[0.07,-0.162],[-0.28,-0.122],[-0.5,-0.463],[-0.009,-0.899],[-0.463,-0.658],[-0.34,-0.028],[-0.524,0.19],[-0.12,0],[-0.431,-0.162],[-0.477,0.005],[-0.414,0.248],[-0.32,0.533],[0,0.644],[0.201,0.594],[0.32,0.449],[0.334,0.31],[0.222,-0.009],[0.317,-0.135],[0.335,0.007],[0.352,0.148],[0.347,-0.009],[0.195,-0.171]],"v":[[3.848,4.137],[4.441,3.071],[4.631,2.566],[3.871,2.066],[3.107,0.315],[4.334,-1.789],[2.249,-2.887],[0.998,-2.665],[0.076,-2.377],[-0.753,-2.623],[-1.8,-2.868],[-3.153,-2.484],[-4.154,-1.469],[-4.631,0.435],[-4.274,2.427],[-3.431,4.1],[-2.634,5.054],[-1.633,5.499],[-0.767,5.272],[0.234,5.059],[1.207,5.272],[2.11,5.49],[3.088,5.059]],"c":true},"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-0.482,0.565],[0,0.551],[0.005,0.074],[0.277,-0.142],[0.217,-0.246],[0,-0.528],[-0.01,-0.069]],"o":[[0.403,-0.473],[0,-0.074],[-0.31,0.023],[-0.297,0.139],[-0.408,0.463],[0,0.07],[0.63,0.051]],"v":[[1.717,-3.739],[2.319,-5.278],[2.31,-5.5],[1.42,-5.25],[0.642,-4.666],[0.002,-3.109],[0.016,-2.901]],"c":true},"ix":2},"nm":"Контур 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Объединить контуры 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"apple","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-120,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Vector 39","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[15,6,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[16.667,16.667,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[0,0],[0,-0.884],[0.221,0],[0,0],[0,0.221],[0.884,0],[0,0]],"o":[[0,0],[-0.884,0],[0,0.221],[0,0],[-0.221,0],[0,-0.884],[0,0],[0,0]],"v":[[4,-1],[3.6,-1],[2,0.6],[1.6,1],[-1.6,1],[-2,0.6],[-3.6,-1],[-4,-1]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":7,"s":[{"i":[[0,0],[0,0],[0,-0.884],[0.028,0],[0,0],[0,0.221],[0.11,0],[0,0]],"o":[[0,0],[-0.11,0],[0,0.221],[0,0],[-0.028,0],[0,-0.884],[0,0],[0,0]],"v":[[0.5,-1],[0.45,-1],[0.25,0.6],[0.2,1],[-0.2,1],[-0.25,0.6],[-0.45,-1],[-0.5,-1]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":33,"s":[{"i":[[0,0],[0,0],[0,-0.884],[0.028,0],[0,0],[0,0.221],[0.11,0],[0,0]],"o":[[0,0],[-0.11,0],[0,0.221],[0,0],[-0.028,0],[0,-0.884],[0,0],[0,0]],"v":[[0.5,-1],[0.45,-1],[0.25,0.6],[0.2,1],[-0.2,1],[-0.25,0.6],[-0.45,-1],[-0.5,-1]],"c":false}]},{"t":40,"s":[{"i":[[0,0],[0,0],[0,-0.884],[0.221,0],[0,0],[0,0.221],[0.884,0],[0,0]],"o":[[0,0],[-0.884,0],[0,0.221],[0,0],[-0.221,0],[0,-0.884],[0,0],[0,0]],"v":[[4,-1],[3.6,-1],[2,0.6],[1.6,1],[-1.6,1],[-2,0.6],[-3.6,-1],[-4,-1]],"c":false}]}],"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Обводка 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Vector 39","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-120,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Rectangle 21","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[15,15,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[16.667,16.667,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0.192,-0.376],[0,-1.12],[0,0],[-0.218,-0.428],[-0.376,-0.192],[-1.12,0],[0,0],[-0.428,0.218],[-0.192,0.376],[0,1.12],[0,0],[0.218,0.428],[0.376,0.192],[1.12,0],[0,0],[0.428,-0.218]],"o":[[-0.218,0.428],[0,0],[0,1.12],[0.192,0.376],[0.428,0.218],[0,0],[1.12,0],[0.376,-0.192],[0.218,-0.428],[0,0],[0,-1.12],[-0.192,-0.376],[-0.428,-0.218],[0,0],[-1.12,0],[-0.376,0.192]],"v":[[-5.782,-8.908],[-6,-6.8],[-6,6.8],[-5.782,8.908],[-4.908,9.782],[-2.8,10],[2.8,10],[4.908,9.782],[5.782,8.908],[6,6.8],[6,-6.8],[5.782,-8.908],[4.908,-9.782],[2.8,-10],[-2.8,-10],[-4.908,-9.782]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[0.016,-0.376],[0,-1.12],[0,0],[-0.018,-0.428],[-0.031,-0.192],[-0.093,0],[0,0],[-0.036,0.218],[-0.016,0.376],[0,1.12],[0,0],[0.018,0.428],[0.031,0.192],[0.093,0],[0,0],[0.036,-0.218]],"o":[[-0.018,0.428],[0,0],[0,1.12],[0.016,0.376],[0.036,0.218],[0,0],[0.093,0],[0.031,-0.192],[0.018,-0.428],[0,0],[0,-1.12],[-0.016,-0.376],[-0.036,-0.218],[0,0],[-0.093,0],[-0.031,0.192]],"v":[[-0.482,-8.908],[-0.5,-6.8],[-0.5,6.8],[-0.482,8.908],[-0.409,9.782],[-0.233,10],[0.233,10],[0.409,9.782],[0.482,8.908],[0.5,6.8],[0.5,-6.8],[0.482,-8.908],[0.409,-9.782],[0.233,-10],[-0.233,-10],[-0.409,-9.782]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":1,"y":0},"t":20,"s":[{"i":[[0.192,-0.376],[0,-1.12],[0,0],[-0.218,-0.428],[-0.376,-0.192],[-1.12,0],[0,0],[-0.428,0.218],[-0.192,0.376],[0,1.12],[0,0],[0.218,0.428],[0.376,0.192],[1.12,0],[0,0],[0.428,-0.218]],"o":[[-0.218,0.428],[0,0],[0,1.12],[0.192,0.376],[0.428,0.218],[0,0],[1.12,0],[0.376,-0.192],[0.218,-0.428],[0,0],[0,-1.12],[-0.192,-0.376],[-0.428,-0.218],[0,0],[-1.12,0],[-0.376,0.192]],"v":[[-5.782,-8.908],[-6,-6.8],[-6,6.8],[-5.782,8.908],[-4.908,9.782],[-2.8,10],[2.8,10],[4.908,9.782],[5.782,8.908],[6,6.8],[6,-6.8],[5.782,-8.908],[4.908,-9.782],[2.8,-10],[-2.8,-10],[-4.908,-9.782]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0.016,-0.376],[0,-1.12],[0,0],[-0.018,-0.428],[-0.031,-0.192],[-0.093,0],[0,0],[-0.036,0.218],[-0.016,0.376],[0,1.12],[0,0],[0.018,0.428],[0.031,0.192],[0.093,0],[0,0],[0.036,-0.218]],"o":[[-0.018,0.428],[0,0],[0,1.12],[0.016,0.376],[0.036,0.218],[0,0],[0.093,0],[0.031,-0.192],[0.018,-0.428],[0,0],[0,-1.12],[-0.016,-0.376],[-0.036,-0.218],[0,0],[-0.093,0],[-0.031,0.192]],"v":[[-0.482,-8.908],[-0.5,-6.8],[-0.5,6.8],[-0.482,8.908],[-0.409,9.782],[-0.233,10],[0.233,10],[0.409,9.782],[0.482,8.908],[0.5,6.8],[0.5,-6.8],[0.482,-8.908],[0.409,-9.782],[0.233,-10],[-0.233,-10],[-0.409,-9.782]],"c":true}]},{"t":40,"s":[{"i":[[0.192,-0.376],[0,-1.12],[0,0],[-0.218,-0.428],[-0.376,-0.192],[-1.12,0],[0,0],[-0.428,0.218],[-0.192,0.376],[0,1.12],[0,0],[0.218,0.428],[0.376,0.192],[1.12,0],[0,0],[0.428,-0.218]],"o":[[-0.218,0.428],[0,0],[0,1.12],[0.192,0.376],[0.428,0.218],[0,0],[1.12,0],[0.376,-0.192],[0.218,-0.428],[0,0],[0,-1.12],[-0.192,-0.376],[-0.428,-0.218],[0,0],[-1.12,0],[-0.376,0.192]],"v":[[-5.782,-8.908],[-6,-6.8],[-6,6.8],[-5.782,8.908],[-4.908,9.782],[-2.8,10],[2.8,10],[4.908,9.782],[5.782,8.908],[6,6.8],[6,-6.8],[5.782,-8.908],[4.908,-9.782],[2.8,-10],[-2.8,-10],[-4.908,-9.782]],"c":true}]}],"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"rd","nm":"Скругленные углы 1","r":{"a":0,"k":2,"ix":1},"ix":2,"mn":"ADBE Vector Filter - RC","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Обводка 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":11,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":29,"s":[100]},{"t":30,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Rectangle 21","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-120,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/Telegram/Resources/icons/settings/devices/device_phone_ios.png b/Telegram/Resources/icons/settings/devices/device_phone_ios.png new file mode 100644 index 000000000..9c67d38a1 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_phone_ios.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_phone_ios@2x.png b/Telegram/Resources/icons/settings/devices/device_phone_ios@2x.png new file mode 100644 index 000000000..c7b96ea8a Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_phone_ios@2x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_phone_ios@3x.png b/Telegram/Resources/icons/settings/devices/device_phone_ios@3x.png new file mode 100644 index 000000000..935622b5e Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_phone_ios@3x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_tablet_android.png b/Telegram/Resources/icons/settings/devices/device_tablet_android.png new file mode 100644 index 000000000..a88ae751c Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_tablet_android.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_tablet_android@2x.png b/Telegram/Resources/icons/settings/devices/device_tablet_android@2x.png new file mode 100644 index 000000000..44a466034 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_tablet_android@2x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_tablet_android@3x.png b/Telegram/Resources/icons/settings/devices/device_tablet_android@3x.png new file mode 100644 index 000000000..596ae0425 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_tablet_android@3x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_tablet_ios.lottie b/Telegram/Resources/icons/settings/devices/device_tablet_ios.lottie new file mode 100644 index 000000000..8817d76b4 --- /dev/null +++ b/Telegram/Resources/icons/settings/devices/device_tablet_ios.lottie @@ -0,0 +1 @@ +{"v":"5.7.4","fr":60,"ip":0,"op":120,"w":30,"h":30,"nm":"ipad_30","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"apple","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[14.841,14.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":10,"s":[0,13.333,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[1,1,0.333],"y":[0,0,0]},"t":20,"s":[15,15,100]},{"t":30,"s":[0,13.333,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.31,0.445],[-0.161,0.375],[-0.056,0.176],[0.222,0.21],[0.009,0.704],[-0.829,0.5],[0.927,0.074],[0.494,-0.176],[0.093,0],[0.431,0.162],[0.264,0],[0.409,-0.246],[0.242,-0.418],[0,-0.737],[-0.236,-0.686],[-0.359,-0.515],[-0.213,-0.185],[-0.334,0.014],[-0.357,0.144],[-0.344,0.006],[-0.307,-0.133],[-0.25,0],[-0.306,0.278]],"o":[[0.234,-0.334],[0.07,-0.162],[-0.28,-0.122],[-0.5,-0.463],[-0.009,-0.899],[-0.463,-0.658],[-0.34,-0.028],[-0.524,0.19],[-0.12,0],[-0.431,-0.162],[-0.477,0.005],[-0.414,0.248],[-0.32,0.533],[0,0.644],[0.201,0.594],[0.32,0.449],[0.334,0.31],[0.222,-0.009],[0.317,-0.135],[0.335,0.007],[0.352,0.148],[0.347,-0.009],[0.195,-0.171]],"v":[[3.848,4.137],[4.441,3.071],[4.631,2.566],[3.871,2.066],[3.107,0.315],[4.334,-1.789],[2.249,-2.887],[0.998,-2.665],[0.076,-2.377],[-0.753,-2.623],[-1.8,-2.868],[-3.153,-2.484],[-4.154,-1.469],[-4.631,0.435],[-4.274,2.427],[-3.431,4.1],[-2.634,5.054],[-1.633,5.499],[-0.767,5.272],[0.234,5.059],[1.207,5.272],[2.11,5.49],[3.088,5.059]],"c":true},"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-0.482,0.565],[0,0.551],[0.005,0.074],[0.277,-0.142],[0.217,-0.246],[0,-0.528],[-0.01,-0.069]],"o":[[0.403,-0.473],[0,-0.074],[-0.31,0.023],[-0.297,0.139],[-0.408,0.463],[0,0.07],[0.63,0.051]],"v":[[1.717,-3.739],[2.319,-5.278],[2.31,-5.5],[1.42,-5.25],[0.642,-4.666],[0.002,-3.109],[0.016,-2.901]],"c":true},"ix":2},"nm":"Контур 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Объединить контуры 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"apple","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-120,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Rectangle 21","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[15,15,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[16.667,16.667,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0.192,-0.376],[0,-1.12],[0,0],[-0.218,-0.428],[-0.376,-0.192],[-1.12,0],[0,0],[-0.428,0.218],[-0.192,0.376],[0,1.12],[0,0],[0.218,0.428],[0.376,0.192],[1.12,0],[0,0],[0.428,-0.218]],"o":[[-0.218,0.428],[0,0],[0,1.12],[0.192,0.376],[0.428,0.218],[0,0],[1.12,0],[0.376,-0.192],[0.218,-0.428],[0,0],[0,-1.12],[-0.192,-0.376],[-0.428,-0.218],[0,0],[-1.12,0],[-0.376,0.192]],"v":[[-7.782,-8.908],[-8,-6.8],[-8,6.8],[-7.782,8.908],[-6.908,9.782],[-4.8,10],[4.8,10],[6.908,9.782],[7.782,8.908],[8,6.8],[8,-6.8],[7.782,-8.908],[6.908,-9.782],[4.8,-10],[-4.8,-10],[-6.908,-9.782]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[0.012,-0.376],[0,-1.12],[0,0],[-0.014,-0.428],[-0.024,-0.192],[-0.07,0],[0,0],[-0.027,0.218],[-0.012,0.376],[0,1.12],[0,0],[0.014,0.428],[0.024,0.192],[0.07,0],[0,0],[0.027,-0.218]],"o":[[-0.014,0.428],[0,0],[0,1.12],[0.012,0.376],[0.027,0.218],[0,0],[0.07,0],[0.024,-0.192],[0.014,-0.428],[0,0],[0,-1.12],[-0.012,-0.376],[-0.027,-0.218],[0,0],[-0.07,0],[-0.024,0.192]],"v":[[-0.486,-8.908],[-0.5,-6.8],[-0.5,6.8],[-0.486,8.908],[-0.432,9.782],[-0.3,10],[0.3,10],[0.432,9.782],[0.486,8.908],[0.5,6.8],[0.5,-6.8],[0.486,-8.908],[0.432,-9.782],[0.3,-10],[-0.3,-10],[-0.432,-9.782]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":1,"y":0},"t":20,"s":[{"i":[[0.192,-0.376],[0,-1.12],[0,0],[-0.218,-0.428],[-0.376,-0.192],[-1.12,0],[0,0],[-0.428,0.218],[-0.192,0.376],[0,1.12],[0,0],[0.218,0.428],[0.376,0.192],[1.12,0],[0,0],[0.428,-0.218]],"o":[[-0.218,0.428],[0,0],[0,1.12],[0.192,0.376],[0.428,0.218],[0,0],[1.12,0],[0.376,-0.192],[0.218,-0.428],[0,0],[0,-1.12],[-0.192,-0.376],[-0.428,-0.218],[0,0],[-1.12,0],[-0.376,0.192]],"v":[[-7.782,-8.908],[-8,-6.8],[-8,6.8],[-7.782,8.908],[-6.908,9.782],[-4.8,10],[4.8,10],[6.908,9.782],[7.782,8.908],[8,6.8],[8,-6.8],[7.782,-8.908],[6.908,-9.782],[4.8,-10],[-4.8,-10],[-6.908,-9.782]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0.012,-0.376],[0,-1.12],[0,0],[-0.014,-0.428],[-0.024,-0.192],[-0.07,0],[0,0],[-0.027,0.218],[-0.012,0.376],[0,1.12],[0,0],[0.014,0.428],[0.024,0.192],[0.07,0],[0,0],[0.027,-0.218]],"o":[[-0.014,0.428],[0,0],[0,1.12],[0.012,0.376],[0.027,0.218],[0,0],[0.07,0],[0.024,-0.192],[0.014,-0.428],[0,0],[0,-1.12],[-0.012,-0.376],[-0.027,-0.218],[0,0],[-0.07,0],[-0.024,0.192]],"v":[[-0.486,-8.908],[-0.5,-6.8],[-0.5,6.8],[-0.486,8.908],[-0.432,9.782],[-0.3,10],[0.3,10],[0.432,9.782],[0.486,8.908],[0.5,6.8],[0.5,-6.8],[0.486,-8.908],[0.432,-9.782],[0.3,-10],[-0.3,-10],[-0.432,-9.782]],"c":true}]},{"t":40,"s":[{"i":[[0.192,-0.376],[0,-1.12],[0,0],[-0.218,-0.428],[-0.376,-0.192],[-1.12,0],[0,0],[-0.428,0.218],[-0.192,0.376],[0,1.12],[0,0],[0.218,0.428],[0.376,0.192],[1.12,0],[0,0],[0.428,-0.218]],"o":[[-0.218,0.428],[0,0],[0,1.12],[0.192,0.376],[0.428,0.218],[0,0],[1.12,0],[0.376,-0.192],[0.218,-0.428],[0,0],[0,-1.12],[-0.192,-0.376],[-0.428,-0.218],[0,0],[-1.12,0],[-0.376,0.192]],"v":[[-7.782,-8.908],[-8,-6.8],[-8,6.8],[-7.782,8.908],[-6.908,9.782],[-4.8,10],[4.8,10],[6.908,9.782],[7.782,8.908],[8,6.8],[8,-6.8],[7.782,-8.908],[6.908,-9.782],[4.8,-10],[-4.8,-10],[-6.908,-9.782]],"c":true}]}],"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Обводка 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":11,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":29,"s":[100]},{"t":30,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Rectangle 21","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-120,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/Telegram/Resources/icons/settings/devices/device_tablet_ios.png b/Telegram/Resources/icons/settings/devices/device_tablet_ios.png new file mode 100644 index 000000000..09fbacf4c Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_tablet_ios.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_tablet_ios@2x.png b/Telegram/Resources/icons/settings/devices/device_tablet_ios@2x.png new file mode 100644 index 000000000..2af837f4c Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_tablet_ios@2x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_tablet_ios@3x.png b/Telegram/Resources/icons/settings/devices/device_tablet_ios@3x.png new file mode 100644 index 000000000..2dcf36c1d Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_tablet_ios@3x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_web_chrome.lottie b/Telegram/Resources/icons/settings/devices/device_web_chrome.lottie new file mode 100644 index 000000000..b97d9dba0 --- /dev/null +++ b/Telegram/Resources/icons/settings/devices/device_web_chrome.lottie @@ -0,0 +1 @@ +{"v":"5.7.4","fr":60,"ip":0,"op":120,"w":30,"h":30,"nm":"chrome_30","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Vector 20","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-38.383,-12.48,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[-100,-100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[5,0],[-5,0]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[5,0],[-5.252,2.02]],"c":false}]},{"t":20,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[5,0],[-5,0]],"c":false}]}],"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.66,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Обводка 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":60,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Vector 20","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-10,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Vector 20","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[8.383,39.481,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[-100,-100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[5,0],[-5,0]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[5,0],[-5.879,2.144]],"c":false}]},{"t":20,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[5,0],[-5,0]],"c":false}]}],"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.66,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Обводка 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":-60,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Vector 20","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-10,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Vector 20","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[30,-27,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[5,0],[-5,0]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[5,0],[-5.333,2]],"c":false}]},{"t":20,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[5,0],[-5,0]],"c":false}]}],"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.66,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Обводка 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Vector 20","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-10,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Ellipse 18","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.333,0.333],"y":[0,0]},"t":0,"s":[9,9]},{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":10,"s":[5,5]},{"t":20,"s":[9,9]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Контур эллипса 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.66,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Обводка 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Ellipse 18","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-10,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Ellipse 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":20,"s":[-245]},{"t":30,"s":[-240]}],"ix":10},"p":{"a":0,"k":[15,15,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[16.667,16.667,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[20,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Контур эллипса 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Ellipse 3","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-10,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/Telegram/Resources/icons/settings/devices/device_web_chrome.png b/Telegram/Resources/icons/settings/devices/device_web_chrome.png new file mode 100644 index 000000000..bc2445354 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_web_chrome.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_web_chrome@2x.png b/Telegram/Resources/icons/settings/devices/device_web_chrome@2x.png new file mode 100644 index 000000000..928758262 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_web_chrome@2x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_web_chrome@3x.png b/Telegram/Resources/icons/settings/devices/device_web_chrome@3x.png new file mode 100644 index 000000000..8776b1c28 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_web_chrome@3x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_web_edge.lottie b/Telegram/Resources/icons/settings/devices/device_web_edge.lottie new file mode 100644 index 000000000..487209ad5 --- /dev/null +++ b/Telegram/Resources/icons/settings/devices/device_web_edge.lottie @@ -0,0 +1 @@ +{"v":"5.7.4","fr":60,"ip":0,"op":120,"w":30,"h":30,"nm":"edge_30","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Union","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.003,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-0.082,0.078],[-0.014,0.019],[0,0.42],[0.001,0.035],[0,0],[0,0],[0,0.009],[0.002,0.03],[0.293,0.391],[0.348,0.176],[0.39,0.003],[0.247,-0.117],[0,0],[0,0],[0.009,-0.004],[0.226,-0.375],[0.005,-0.438],[-3.697,0],[-0.897,0.336],[-0.267,0.139],[-0.069,-0.011],[-0.048,-0.051],[-0.007,-0.069],[0.037,-0.059],[2.152,-0.744],[0,0],[0.253,-0.054],[-0.228,0.072],[2.252,0.966],[1.04,2.219],[-0.014,1.51],[-0.03,0.216],[0.003,-0.209],[-1.868,1.842],[-2.628,0],[-1.655,-3.238],[-0.012,-0.024],[-0.003,-0.006],[0.019,-1.66],[0.417,-0.725],[0.722,-0.422],[0.832,-0.002],[0.001,0],[1.105,0.768],[0,0.204]],"o":[[0.024,-0.023],[0.395,-0.514],[0,-0.029],[0,0],[0,0],[0,-0.009],[-0.001,-0.027],[-0.029,-0.486],[-0.233,-0.313],[-0.348,-0.176],[-0.533,-0.01],[0,0],[0,0],[-0.009,0.004],[-0.384,0.209],[-0.226,0.375],[0,3.263],[0.958,0.002],[0.282,-0.106],[0.061,-0.034],[0.069,0.011],[0.048,0.051],[0.007,0.069],[-1.215,1.926],[0,0],[-0.193,0.061],[0.234,-0.044],[-2.325,0.775],[-2.252,-0.966],[-0.636,-1.37],[0,-0.213],[-0.033,0.203],[0.041,-2.622],[1.871,-1.845],[3.886,0],[0.011,0.022],[0.003,0.006],[0.283,0.552],[-0.002,0.836],[-0.417,0.725],[-0.716,0.425],[0,0],[-0.073,0.003],[-0.236,-0.165],[0,-0.191]],"v":[[1.84,1.691],[1.9,1.628],[2.509,0.073],[2.508,-0.023],[2.508,-0.022],[2.508,-0.021],[2.507,-0.048],[2.504,-0.134],[2.011,-1.479],[1.127,-2.223],[0.004,-2.495],[-1.181,-2.196],[-1.181,-2.196],[-1.183,-2.195],[-1.21,-2.182],[-2.142,-1.29],[-2.493,-0.049],[4.423,5.757],[7.228,5.252],[8.051,4.885],[8.251,4.849],[8.43,4.943],[8.514,5.128],[8.467,5.325],[3.264,9.45],[3.162,9.484],[2.478,9.66],[3.171,9.487],[-3.935,9.19],[-9.045,4.245],[-9.991,-0.13],[-9.945,-0.774],[-10,-0.156],[-7.022,-7.122],[-0.001,-10.001],[9.114,-4.9],[9.149,-4.832],[9.157,-4.815],[10,-1.47],[9.361,0.912],[7.622,2.661],[5.258,3.314],[5.256,3.314],[2.011,2.635],[1.642,2.071]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":40,"s":[{"i":[[-0.082,0.078],[-0.014,0.019],[0,0.42],[0.001,0.035],[0,0],[0,0],[0,0.009],[0.002,0.03],[0.293,0.391],[0.348,0.176],[0.39,0.003],[0.247,-0.117],[0,0],[0,0],[0.009,-0.004],[0.226,-0.375],[0.005,-0.438],[-3.279,0.493],[-0.897,0.336],[-0.267,0.139],[-0.069,-0.011],[-0.048,-0.051],[-0.007,-0.069],[0.037,-0.059],[2.152,-0.744],[0,0],[0.253,-0.054],[-0.228,0.072],[2.252,0.966],[1.04,2.219],[-0.014,1.51],[-0.03,0.216],[0.003,-0.209],[-1.868,1.842],[-2.628,0],[-1.655,-3.238],[-0.012,-0.024],[-0.003,-0.006],[0.019,-1.66],[0.417,-0.725],[0.722,-0.422],[0.832,-0.002],[0.001,0],[1.105,0.768],[0,0.204]],"o":[[0.024,-0.023],[0.395,-0.514],[0,-0.029],[0,0],[0,0],[0,-0.009],[-0.001,-0.027],[-0.029,-0.486],[-0.233,-0.313],[-0.348,-0.176],[-0.533,-0.01],[0,0],[0,0],[-0.009,0.004],[-0.384,0.209],[-0.226,0.375],[0,3.263],[1.152,-0.173],[0.282,-0.106],[0.061,-0.034],[0.069,0.011],[0.048,0.051],[0.007,0.069],[-1.215,1.926],[0,0],[-0.193,0.061],[0.234,-0.044],[-2.325,0.775],[-2.252,-0.966],[-0.636,-1.37],[0,-0.213],[-0.033,0.203],[0.041,-2.622],[1.871,-1.845],[3.886,0],[0.011,0.022],[0.003,0.006],[0.283,0.552],[-0.002,0.836],[-0.417,0.725],[-0.716,0.425],[0,0],[-0.073,0.003],[-0.236,-0.165],[0,-0.191]],"v":[[1.319,2.836],[1.379,2.774],[2.509,0.073],[2.508,-0.023],[2.508,-0.022],[2.508,-0.021],[2.507,-0.048],[2.504,-0.134],[2.011,-1.479],[1.127,-2.223],[0.004,-2.495],[-1.181,-2.196],[-1.181,-2.196],[-1.183,-2.195],[-1.21,-2.182],[-2.142,-1.29],[-2.493,-0.049],[4.423,5.757],[6.189,5.293],[7.114,4.78],[7.313,4.745],[7.493,4.839],[7.577,5.024],[7.529,5.221],[3.264,9.45],[3.162,9.484],[2.478,9.66],[3.171,9.487],[-3.935,9.19],[-9.045,4.245],[-9.991,-0.13],[-9.945,-0.774],[-10,-0.156],[-7.022,-7.122],[-0.001,-10.001],[9.114,-4.9],[9.149,-4.832],[9.157,-4.815],[10,-1.47],[8.84,2.058],[7.102,3.807],[4.737,4.459],[4.735,4.459],[1.49,3.781],[1.121,3.216]],"c":true}]},{"t":50,"s":[{"i":[[-0.082,0.078],[-0.014,0.019],[0,0.42],[0.001,0.035],[0,0],[0,0],[0,0.009],[0.002,0.03],[0.293,0.391],[0.348,0.176],[0.39,0.003],[0.247,-0.117],[0,0],[0,0],[0.009,-0.004],[0.226,-0.375],[0.005,-0.438],[-3.697,0],[-0.897,0.336],[-0.267,0.139],[-0.069,-0.011],[-0.048,-0.051],[-0.007,-0.069],[0.037,-0.059],[2.152,-0.744],[0,0],[0.253,-0.054],[-0.228,0.072],[2.252,0.966],[1.04,2.219],[-0.014,1.51],[-0.03,0.216],[0.003,-0.209],[-1.868,1.842],[-2.628,0],[-1.655,-3.238],[-0.012,-0.024],[-0.003,-0.006],[0.019,-1.66],[0.417,-0.725],[0.722,-0.422],[0.832,-0.002],[0.001,0],[1.105,0.768],[0,0.204]],"o":[[0.024,-0.023],[0.395,-0.514],[0,-0.029],[0,0],[0,0],[0,-0.009],[-0.001,-0.027],[-0.029,-0.486],[-0.233,-0.313],[-0.348,-0.176],[-0.533,-0.01],[0,0],[0,0],[-0.009,0.004],[-0.384,0.209],[-0.226,0.375],[0,3.263],[0.958,0.002],[0.282,-0.106],[0.061,-0.034],[0.069,0.011],[0.048,0.051],[0.007,0.069],[-1.215,1.926],[0,0],[-0.193,0.061],[0.234,-0.044],[-2.325,0.775],[-2.252,-0.966],[-0.636,-1.37],[0,-0.213],[-0.033,0.203],[0.041,-2.622],[1.871,-1.845],[3.886,0],[0.011,0.022],[0.003,0.006],[0.283,0.552],[-0.002,0.836],[-0.417,0.725],[-0.716,0.425],[0,0],[-0.073,0.003],[-0.236,-0.165],[0,-0.191]],"v":[[1.84,1.691],[1.9,1.628],[2.509,0.073],[2.508,-0.023],[2.508,-0.022],[2.508,-0.021],[2.507,-0.048],[2.504,-0.134],[2.011,-1.479],[1.127,-2.223],[0.004,-2.495],[-1.181,-2.196],[-1.181,-2.196],[-1.183,-2.195],[-1.21,-2.182],[-2.142,-1.29],[-2.493,-0.049],[4.423,5.757],[7.228,5.252],[8.051,4.885],[8.251,4.849],[8.43,4.943],[8.514,5.128],[8.467,5.325],[3.264,9.45],[3.162,9.484],[2.478,9.66],[3.171,9.487],[-3.935,9.19],[-9.045,4.245],[-9.991,-0.13],[-9.945,-0.774],[-10,-0.156],[-7.022,-7.122],[-0.001,-10.001],[9.114,-4.9],[9.149,-4.832],[9.157,-4.815],[10,-1.47],[9.361,0.912],[7.622,2.661],[5.258,3.314],[5.256,3.314],[2.011,2.635],[1.642,2.071]],"c":true}]}],"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0.005]],"o":[[0,-0.005],[0,0]],"v":[[-10,-0.142],[-10,-0.156]],"c":false},"ix":2},"nm":"Контур 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Объединить контуры 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Union","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-60,"bm":0},{"ddd":0,"ind":2,"ty":3,"nm":"Ellipse 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":40,"s":[1090]},{"t":50,"s":[1080]}],"ix":10},"p":{"a":0,"k":[15,15,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[16.667,16.667,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":20,"s":[11.667,11.667,100]},{"t":40,"s":[16.667,16.667,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":120,"st":-60,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/Telegram/Resources/icons/settings/devices/device_web_edge.png b/Telegram/Resources/icons/settings/devices/device_web_edge.png new file mode 100644 index 000000000..4f3e0d750 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_web_edge.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_web_edge@2x.png b/Telegram/Resources/icons/settings/devices/device_web_edge@2x.png new file mode 100644 index 000000000..451475472 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_web_edge@2x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_web_edge@3x.png b/Telegram/Resources/icons/settings/devices/device_web_edge@3x.png new file mode 100644 index 000000000..cd347cea8 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_web_edge@3x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_web_firefox.lottie b/Telegram/Resources/icons/settings/devices/device_web_firefox.lottie new file mode 100644 index 000000000..2db4fbd72 --- /dev/null +++ b/Telegram/Resources/icons/settings/devices/device_web_firefox.lottie @@ -0,0 +1 @@ +{"v":"5.7.4","fr":60,"ip":0,"op":120,"w":30,"h":30,"nm":"firefox_30","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Vector","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.974,-1.629,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[-0.03,-0.196],[0.001,-0.315],[0,-0.044],[0.784,-1.26],[0.037,-0.059],[0.129,-0.308],[0.711,-0.509],[0.55,-0.259],[0,0],[0,0],[0.07,-0.029],[0.18,-0.163],[3.652,0],[0.988,0.296],[-0.036,-0.014],[0.11,0.04],[0.723,0.489],[0.845,1.898],[-0.075,1.505],[0.08,-0.373],[-0.467,1.186],[0.175,-0.384],[-0.528,0.892],[-0.21,0.229],[0,0],[-0.018,0.121],[-0.293,0.65],[0,0],[0,-0.031],[-0.004,0.014],[-0.041,0.083],[-0.067,0.076],[-0.005,0.007],[0.006,-0.047],[0,0.004],[-0.041,0.093],[-0.224,0.154],[0,0],[-0.14,-0.364],[0,0],[-0.016,0],[0,-0.005],[-0.039,-0.068],[-0.052,-0.077],[0,0],[0,0],[-0.157,-0.134],[0,0],[-0.764,-0.289],[-0.118,0.088],[0.005,-0.039],[-0.257,0.109],[-0.001,-0.033],[-0.368,0.071],[0.068,-0.058],[-0.176,0.009],[0.062,-0.026],[0.117,-0.057],[0.035,-0.028],[0.261,-0.684],[0,0],[0,0],[-0.485,-0.077],[-0.041,-0.152],[0,0],[0.006,-0.04],[0.233,-0.198],[0.07,-0.045],[0.28,-0.123],[0.316,-0.196],[0.045,-0.044],[0,-0.005],[-0.011,-0.056],[-0.036,-0.068],[0.009,-0.115],[0.083,-0.192],[0.101,0.051],[0,0],[-0.046,-0.118],[0,0],[0.09,0.05],[0.113,0.049],[0.012,-0.004],[0,0],[0.129,0.064],[0.093,-0.166],[-0.179,-0.324],[-0.259,-0.177],[-0.015,-0.012],[-0.218,-0.109],[-0.068,-0.039],[-0.064,-0.034],[-0.763,0.111],[-0.451,-0.592],[0.366,0.116],[0,0],[0.762,-0.423],[0.818,0.173],[0.199,0.069],[0,0],[0,0],[-0.562,-0.34],[-0.416,-0.078],[-1.148,0.914],[-0.001,1.467],[0.272,0.642],[-0.488,-0.931],[1.067,0.88],[0.381,1.005],[0,0],[-1.363,-1.273],[-0.526,-0.531],[-0.141,0.387],[-0.334,-0.559],[-0.386,-0.63],[-0.079,-0.119],[-0.165,-0.347],[-0.16,-0.563],[-0.039,-0.466]],"o":[[0,0],[0.055,0.31],[0,0.04],[-0.062,1.483],[-0.04,0.067],[0.117,0.313],[-0.218,0.847],[-0.474,0.382],[0,0],[0,0],[-0.072,0.033],[-0.104,0.22],[-0.111,0.139],[-1.032,-0.001],[0.036,0.016],[-0.114,-0.038],[-0.834,-0.257],[-1.778,-1.076],[-0.63,-1.369],[-0.123,0.361],[0,-1.275],[-0.241,0.346],[0.247,-1.007],[0.158,-0.267],[0,0],[-0.004,-0.122],[0.065,-0.71],[0,0],[0.006,-0.012],[0,0.031],[0.026,-0.089],[0.045,-0.091],[0.005,-0.007],[0.005,-0.007],[0,0.017],[0.027,-0.051],[0.09,-0.257],[0,0],[-0.013,0.39],[0,0],[0.014,0.02],[0.003,0.001],[0.04,0.082],[0.055,0.097],[0,0],[0,0],[0.115,0.18],[0,0],[0.797,-0.161],[0.095,-0.112],[0,0.039],[0.171,-0.221],[-0.014,0.03],[0.323,-0.19],[0.048,0],[0.162,-0.07],[0.288,-0.03],[-0.122,0.044],[-0.035,0.029],[-0.613,0.401],[0,0],[0,0],[0.108,0.479],[1.373,0.129],[0,0],[-0.002,0.04],[-0.056,0.301],[-0.062,0.056],[-0.047,0.027],[-0.344,0.141],[-0.053,0.035],[-0.016,0.017],[0.025,0.051],[-0.063,-0.071],[0.04,0.108],[0.007,0.209],[-0.091,-0.068],[0,0],[0.106,0.069],[0.034,0.095],[-0.059,-0.084],[-0.132,-0.078],[-0.012,0.007],[0.032,0.057],[0,0],[-0.171,0.083],[-0.151,0.338],[0.174,0.261],[0.016,0.012],[0.2,0.14],[0.066,0.044],[0.061,0.04],[1,0.525],[0.733,-0.129],[0.261,0.371],[0,0],[-0.374,-0.124],[-0.755,0.359],[-0.207,-0.04],[0,0],[0,0],[0.383,0.533],[0.377,0.192],[1.431,0.327],[1.148,-0.914],[0.004,-0.698],[0.904,0.537],[-0.606,-1.77],[-0.858,-0.646],[-1.103,-2.96],[0,0],[0.276,0.257],[0.074,-0.405],[0.11,0.642],[0.512,0.728],[0.083,0.112],[0.221,0.314],[0.262,0.523],[0.128,0.45],[0.127,-0.167]],"v":[[10.333,-1.238],[10.393,-0.927],[10.475,0.011],[10.475,0.135],[9.184,4.32],[9.068,4.509],[9.048,5.476],[7.611,7.575],[6.069,8.54],[5.987,8.579],[5.942,8.599],[5.729,8.692],[5.298,9.271],[0.329,10.801],[-2.718,10.353],[-2.614,10.398],[-2.948,10.281],[-5.296,9.156],[-9.325,4.587],[-10.17,0.211],[-10.475,1.314],[-9.767,-2.41],[-10.394,-1.312],[-9.224,-4.176],[-8.671,-4.921],[-8.671,-4.932],[-8.65,-5.296],[-8.109,-7.351],[-8.094,-7.38],[-8.094,-7.305],[-8.094,-7.261],[-7.992,-7.52],[-7.822,-7.772],[-7.809,-7.791],[-7.831,-7.656],[-7.831,-7.632],[-7.736,-7.827],[-7.253,-8.459],[-7.242,-8.464],[-7.05,-7.322],[-7.05,-7.318],[-7.009,-7.374],[-7,-7.366],[-6.881,-7.137],[-6.721,-6.875],[-6.708,-6.86],[-6.697,-6.863],[-6.282,-6.389],[-6.275,-6.383],[-3.888,-6.184],[-3.568,-6.485],[-3.575,-6.367],[-2.922,-6.871],[-2.942,-6.776],[-1.898,-7.171],[-2.121,-7.035],[-1.609,-7.156],[-1.058,-7.036],[-1.416,-6.884],[-1.311,-6.863],[-2.655,-5.193],[-2.655,-5.182],[-2.655,-5.188],[-1.674,-4.268],[0.003,-3.872],[0.003,-3.801],[-0.009,-3.681],[-0.456,-2.909],[-0.654,-2.756],[-1.214,-2.513],[-2.206,-2.008],[-2.352,-1.889],[-2.404,-1.82],[-2.351,-1.658],[-2.434,-1.635],[-2.387,-1.296],[-2.502,-0.687],[-2.791,-0.866],[-2.802,-0.866],[-2.567,-0.579],[-2.579,-0.462],[-2.806,-0.665],[-3.301,-0.919],[-3.334,-0.912],[-3.244,-0.746],[-3.453,-0.858],[-3.859,-0.476],[-3.814,0.576],[-3.159,1.239],[-3.294,1.203],[-2.666,1.578],[-2.815,1.564],[-2.628,1.674],[-0.165,1.546],[1.763,2.299],[1.37,2.908],[1.363,2.908],[-0.235,3.521],[-2.661,3.807],[-3.27,3.644],[-3.355,3.613],[-3.349,3.624],[-1.917,4.948],[-0.722,5.354],[3.358,4.425],[5.176,0.656],[4.77,-1.375],[6.898,0.87],[4.076,-2.472],[2.181,-4.999],[3.623,-10.801],[4.828,-8.868],[6.088,-7.703],[6.41,-8.892],[7.081,-7.074],[8.422,-5.33],[8.665,-4.985],[9.245,-3.991],[9.879,-2.359],[10.13,-0.982]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":20,"s":[{"i":[[0,0],[-0.03,-0.196],[0.001,-0.315],[0,-0.044],[0.784,-1.26],[0.037,-0.059],[0.129,-0.308],[0.711,-0.509],[0.55,-0.259],[0,0],[0,0],[0.07,-0.029],[0.18,-0.163],[3.652,0],[0.988,0.296],[-0.036,-0.014],[0.11,0.04],[0.723,0.489],[0.845,1.898],[-0.075,1.505],[0.08,-0.373],[-0.467,1.186],[0.175,-0.384],[-0.528,0.892],[-0.21,0.229],[0,0],[-0.018,0.121],[-0.293,0.65],[0,0],[0,-0.031],[-0.004,0.014],[-0.041,0.083],[-0.067,0.076],[-0.005,0.007],[0.006,-0.047],[0,0.004],[-0.041,0.093],[-0.224,0.154],[0,0],[-0.14,-0.364],[0,0],[-0.016,0],[0,-0.005],[-0.039,-0.068],[-0.052,-0.077],[0,0],[0,0],[-0.157,-0.134],[0,0],[-0.764,-0.289],[-0.118,0.088],[0.005,-0.039],[-0.257,0.109],[-0.001,-0.033],[-0.368,0.071],[0.068,-0.058],[-0.176,0.009],[0.062,-0.026],[0.117,-0.057],[0.035,-0.028],[0.261,-0.684],[0,0],[0,0],[-0.485,-0.077],[-0.041,-0.152],[0,0],[0.006,-0.04],[0.233,-0.198],[0.07,-0.045],[0.28,-0.123],[0.316,-0.196],[0.045,-0.044],[0,-0.005],[-0.011,-0.056],[-0.036,-0.068],[0.009,-0.115],[0.083,-0.192],[0.101,0.051],[0,0],[-0.046,-0.118],[0,0],[0.09,0.05],[0.113,0.049],[0.012,-0.004],[0,0],[0.129,0.064],[0.093,-0.166],[-0.179,-0.324],[-0.259,-0.177],[-0.015,-0.012],[-0.218,-0.109],[-0.068,-0.039],[-0.017,-0.071],[-0.427,-0.474],[-0.451,-0.592],[0.366,0.116],[0,0],[0.762,-0.423],[0.818,0.173],[0.199,0.069],[0,0],[0,0],[-0.562,-0.34],[-0.416,-0.078],[-1.148,0.914],[-0.001,1.467],[0.272,0.642],[-0.488,-0.931],[1.067,0.88],[0.381,1.005],[0,0],[0.043,-1.277],[-0.03,-0.651],[-0.141,0.387],[-0.334,-0.559],[-0.386,-0.63],[-0.079,-0.119],[-0.165,-0.347],[-0.16,-0.563],[-0.039,-0.466]],"o":[[0,0],[0.055,0.31],[0,0.04],[-0.062,1.483],[-0.04,0.067],[0.117,0.313],[-0.218,0.847],[-0.474,0.382],[0,0],[0,0],[-0.072,0.033],[-0.104,0.22],[-0.111,0.139],[-1.032,-0.001],[0.036,0.016],[-0.114,-0.038],[-0.834,-0.257],[-1.778,-1.076],[-0.63,-1.369],[-0.123,0.361],[0,-1.275],[-0.241,0.346],[0.247,-1.007],[0.158,-0.267],[0,0],[-0.004,-0.122],[0.065,-0.71],[0,0],[0.006,-0.012],[0,0.031],[0.026,-0.089],[0.045,-0.091],[0.005,-0.007],[0.005,-0.007],[0,0.017],[0.027,-0.051],[0.09,-0.257],[0,0],[-0.013,0.39],[0,0],[0.014,0.02],[0.003,0.001],[0.04,0.082],[0.055,0.097],[0,0],[0,0],[0.115,0.18],[0,0],[0.797,-0.161],[0.095,-0.112],[0,0.039],[0.171,-0.221],[-0.014,0.03],[0.323,-0.19],[0.048,0],[0.162,-0.07],[0.288,-0.03],[-0.122,0.044],[-0.035,0.029],[-0.613,0.401],[0,0],[0,0],[0.108,0.479],[1.373,0.129],[0,0],[-0.002,0.04],[-0.056,0.301],[-0.062,0.056],[-0.047,0.027],[-0.344,0.141],[-0.053,0.035],[-0.016,0.017],[0.025,0.051],[-0.063,-0.071],[0.04,0.108],[0.007,0.209],[-0.091,-0.068],[0,0],[0.106,0.069],[0.034,0.095],[-0.059,-0.084],[-0.132,-0.078],[-0.012,0.007],[0.032,0.057],[0,0],[-0.171,0.083],[-0.151,0.338],[0.174,0.261],[0.016,0.012],[0.2,0.14],[0.066,0.044],[0.061,0.04],[0.332,1.348],[0.786,0.559],[0.261,0.371],[0,0],[-0.374,-0.124],[-0.603,-0.499],[-0.344,-0.557],[0,0],[0,0],[0.383,0.533],[0.377,0.192],[1.431,0.327],[1.148,-0.914],[0.004,-0.698],[0.904,0.537],[-0.606,-1.77],[-0.858,-0.646],[-1.103,-2.96],[0,0],[0.293,0.889],[0.074,-0.405],[0.11,0.642],[0.512,0.728],[0.083,0.112],[0.221,0.314],[0.262,0.523],[0.128,0.45],[0.127,-0.167]],"v":[[10.333,-1.238],[10.393,-0.927],[10.475,0.011],[10.475,0.135],[9.184,4.32],[9.068,4.509],[9.048,5.476],[7.611,7.575],[6.069,8.54],[5.987,8.579],[5.942,8.599],[5.729,8.692],[5.298,9.271],[0.329,10.801],[-2.718,10.353],[-2.614,10.398],[-2.948,10.281],[-5.296,9.156],[-9.325,4.587],[-10.17,0.211],[-10.162,1.314],[-9.767,-2.41],[-10.185,-1.312],[-9.224,-4.176],[-8.671,-4.921],[-8.671,-4.932],[-8.65,-5.296],[-8.109,-6.934],[-8.094,-6.963],[-8.094,-6.888],[-8.094,-6.845],[-7.992,-7.103],[-7.822,-7.355],[-7.809,-7.375],[-7.831,-7.239],[-7.831,-7.216],[-7.736,-7.41],[-7.253,-8.042],[-7.242,-8.048],[-7.05,-6.906],[-7.05,-6.901],[-7.009,-6.957],[-7,-6.949],[-6.881,-6.72],[-6.721,-6.458],[-6.708,-6.443],[-6.697,-6.446],[-6.282,-6.389],[-6.275,-6.383],[-3.888,-6.184],[-3.568,-6.485],[-3.575,-6.367],[-2.922,-6.662],[-2.942,-6.568],[-1.898,-6.676],[-2.121,-6.54],[-1.609,-6.661],[-1.058,-6.541],[-1.416,-6.389],[-1.311,-6.368],[-2.655,-5.193],[-2.655,-5.182],[-2.655,-5.188],[-1.674,-4.268],[-0.31,-3.872],[-0.31,-3.801],[-0.321,-3.681],[-0.768,-2.909],[-0.967,-2.756],[-1.526,-2.513],[-2.206,-2.008],[-2.352,-1.889],[-2.404,-1.82],[-2.351,-1.658],[-2.434,-1.635],[-2.387,-1.296],[-1.761,-0.752],[-2.049,-0.931],[-2.06,-0.931],[-1.826,-0.644],[-1.838,-0.527],[-2.064,-0.73],[-1.967,-1.036],[-2,-1.028],[-1.909,-0.863],[-2.119,-0.975],[-2.525,-0.593],[-2.48,0.459],[-2.346,1.122],[-2.481,1.087],[-2.135,1.462],[-2.284,1.447],[-2.097,1.557],[-1.721,2.102],[-0.49,2.763],[-0.883,3.372],[-0.889,3.372],[-1.895,2.891],[-2.101,2.604],[-2.066,2.045],[-2.151,2.014],[-2.144,2.025],[-1.357,2.808],[-0.162,3.214],[2.435,2.414],[3.385,0.876],[2.979,-1.155],[4.378,1.091],[2.001,-2.291],[2.181,-4.999],[4.248,-10.801],[5.453,-8.868],[5.359,-7.703],[5.264,-8.892],[7.081,-7.074],[8.422,-5.33],[8.665,-4.985],[9.245,-3.991],[9.879,-2.359],[10.13,-0.982]],"c":true}]},{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":37,"s":[{"i":[[0,0],[-0.03,-0.196],[0.001,-0.315],[0,-0.044],[0.784,-1.26],[0.037,-0.059],[0.129,-0.308],[0.711,-0.509],[0.55,-0.259],[0,0],[0,0],[0.07,-0.029],[0.18,-0.163],[3.652,0],[0.988,0.296],[-0.036,-0.014],[0.11,0.04],[0.723,0.489],[0.845,1.898],[-0.075,1.505],[0.08,-0.373],[-0.467,1.186],[0.175,-0.384],[-0.528,0.892],[-0.21,0.229],[0,0],[-0.018,0.121],[-0.293,0.65],[0,0],[0,-0.031],[-0.004,0.014],[-0.041,0.083],[-0.067,0.076],[-0.005,0.007],[0.006,-0.047],[0,0.004],[-0.041,0.093],[-0.224,0.154],[0,0],[-0.14,-0.364],[0,0],[-0.016,0],[0,-0.005],[-0.039,-0.068],[-0.052,-0.077],[0,0],[0,0],[-0.157,-0.134],[0,0],[-0.764,-0.289],[-0.118,0.088],[0.005,-0.039],[-0.257,0.109],[-0.001,-0.033],[-0.368,0.071],[0.068,-0.058],[-0.176,0.009],[0.062,-0.026],[0.117,-0.057],[0.035,-0.028],[0.261,-0.684],[0,0],[0,0],[-0.485,-0.077],[-0.041,-0.152],[0,0],[0.006,-0.04],[0.233,-0.198],[0.07,-0.045],[0.28,-0.123],[0.316,-0.196],[0.045,-0.044],[0,-0.005],[-0.011,-0.056],[-0.036,-0.068],[0.009,-0.115],[0.083,-0.192],[0.101,0.051],[0,0],[-0.046,-0.118],[0,0],[0.09,0.05],[0.113,0.049],[0.012,-0.004],[0,0],[0.129,0.064],[0.093,-0.166],[-0.179,-0.324],[-0.259,-0.177],[-0.015,-0.012],[-0.218,-0.109],[-0.068,-0.039],[-0.017,-0.071],[-0.427,-0.474],[-0.451,-0.592],[0.366,0.116],[0,0],[0.762,-0.423],[0.818,0.173],[0.199,0.069],[0,0],[0,0],[-0.562,-0.34],[-0.416,-0.078],[-1.148,0.914],[-0.001,1.467],[0.272,0.642],[-0.488,-0.931],[1.067,0.88],[0.381,1.005],[0,0],[0.043,-1.277],[-0.03,-0.651],[-0.141,0.387],[-0.334,-0.559],[-0.386,-0.63],[-0.079,-0.119],[-0.165,-0.347],[-0.16,-0.563],[-0.039,-0.466]],"o":[[0,0],[0.055,0.31],[0,0.04],[-0.062,1.483],[-0.04,0.067],[0.117,0.313],[-0.218,0.847],[-0.474,0.382],[0,0],[0,0],[-0.072,0.033],[-0.104,0.22],[-0.111,0.139],[-1.032,-0.001],[0.036,0.016],[-0.114,-0.038],[-0.834,-0.257],[-1.778,-1.076],[-0.63,-1.369],[-0.123,0.361],[0,-1.275],[-0.241,0.346],[0.247,-1.007],[0.158,-0.267],[0,0],[-0.004,-0.122],[0.065,-0.71],[0,0],[0.006,-0.012],[0,0.031],[0.026,-0.089],[0.045,-0.091],[0.005,-0.007],[0.005,-0.007],[0,0.017],[0.027,-0.051],[0.09,-0.257],[0,0],[-0.013,0.39],[0,0],[0.014,0.02],[0.003,0.001],[0.04,0.082],[0.055,0.097],[0,0],[0,0],[0.115,0.18],[0,0],[0.797,-0.161],[0.095,-0.112],[0,0.039],[0.171,-0.221],[-0.014,0.03],[0.323,-0.19],[0.048,0],[0.162,-0.07],[0.288,-0.03],[-0.122,0.044],[-0.035,0.029],[-0.613,0.401],[0,0],[0,0],[0.108,0.479],[1.373,0.129],[0,0],[-0.002,0.04],[-0.056,0.301],[-0.062,0.056],[-0.047,0.027],[-0.344,0.141],[-0.053,0.035],[-0.016,0.017],[0.025,0.051],[-0.063,-0.071],[0.04,0.108],[0.007,0.209],[-0.091,-0.068],[0,0],[0.106,0.069],[0.034,0.095],[-0.059,-0.084],[-0.132,-0.078],[-0.012,0.007],[0.032,0.057],[0,0],[-0.171,0.083],[-0.151,0.338],[0.174,0.261],[0.016,0.012],[0.2,0.14],[0.066,0.044],[0.061,0.04],[0.332,1.348],[0.786,0.559],[0.261,0.371],[0,0],[-0.374,-0.124],[-0.603,-0.499],[-0.344,-0.557],[0,0],[0,0],[0.383,0.533],[0.377,0.192],[1.431,0.327],[1.148,-0.914],[0.004,-0.698],[0.904,0.537],[-0.606,-1.77],[-0.858,-0.646],[-1.103,-2.96],[0,0],[0.293,0.889],[0.074,-0.405],[0.11,0.642],[0.512,0.728],[0.083,0.112],[0.221,0.314],[0.262,0.523],[0.128,0.45],[0.127,-0.167]],"v":[[10.333,-1.238],[10.393,-0.927],[10.475,0.011],[10.475,0.135],[9.184,4.32],[9.068,4.509],[9.048,5.476],[7.611,7.575],[6.069,8.54],[5.987,8.579],[5.942,8.599],[5.729,8.692],[5.298,9.271],[0.329,10.801],[-2.718,10.353],[-2.614,10.398],[-2.948,10.281],[-5.296,9.156],[-9.325,4.587],[-10.17,0.211],[-10.162,1.314],[-9.767,-2.41],[-10.185,-1.312],[-9.224,-4.176],[-8.671,-4.921],[-8.671,-4.932],[-8.65,-5.296],[-8.109,-6.934],[-8.094,-6.963],[-8.094,-6.888],[-8.094,-6.845],[-7.992,-7.103],[-7.822,-7.355],[-7.809,-7.375],[-7.831,-7.239],[-7.831,-7.216],[-7.736,-7.41],[-7.253,-8.042],[-7.242,-8.048],[-7.05,-6.906],[-7.05,-6.901],[-7.009,-6.957],[-7,-6.949],[-6.881,-6.72],[-6.721,-6.458],[-6.708,-6.443],[-6.697,-6.446],[-6.282,-6.389],[-6.275,-6.383],[-3.888,-6.184],[-3.568,-6.485],[-3.575,-6.367],[-2.922,-6.662],[-2.942,-6.568],[-1.898,-6.676],[-2.121,-6.54],[-1.609,-6.661],[-1.058,-6.541],[-1.416,-6.389],[-1.311,-6.368],[-2.655,-5.193],[-2.655,-5.182],[-2.655,-5.188],[-1.674,-4.268],[-0.31,-3.872],[-0.31,-3.801],[-0.321,-3.681],[-0.768,-2.909],[-0.967,-2.756],[-1.526,-2.513],[-2.206,-2.008],[-2.352,-1.889],[-2.404,-1.82],[-2.351,-1.658],[-2.434,-1.635],[-2.387,-1.296],[-1.761,-0.752],[-2.049,-0.931],[-2.06,-0.931],[-1.826,-0.644],[-1.838,-0.527],[-2.064,-0.73],[-1.967,-1.036],[-2,-1.028],[-1.909,-0.863],[-2.119,-0.975],[-2.525,-0.593],[-2.48,0.459],[-2.346,1.122],[-2.481,1.087],[-2.135,1.462],[-2.284,1.447],[-2.097,1.557],[-1.721,2.102],[-0.49,2.763],[-0.883,3.372],[-0.889,3.372],[-1.895,2.891],[-2.101,2.604],[-2.066,2.045],[-2.151,2.014],[-2.144,2.025],[-1.357,2.808],[-0.162,3.214],[2.435,2.414],[3.385,0.876],[2.979,-1.155],[4.378,1.091],[2.001,-2.291],[2.181,-4.999],[4.248,-10.801],[5.453,-8.868],[5.359,-7.703],[5.264,-8.892],[7.081,-7.074],[8.422,-5.33],[8.665,-4.985],[9.245,-3.991],[9.879,-2.359],[10.13,-0.982]],"c":true}]},{"t":60,"s":[{"i":[[0,0],[-0.03,-0.196],[0.001,-0.315],[0,-0.044],[0.784,-1.26],[0.037,-0.059],[0.129,-0.308],[0.711,-0.509],[0.55,-0.259],[0,0],[0,0],[0.07,-0.029],[0.18,-0.163],[3.652,0],[0.988,0.296],[-0.036,-0.014],[0.11,0.04],[0.723,0.489],[0.845,1.898],[-0.075,1.505],[0.08,-0.373],[-0.467,1.186],[0.175,-0.384],[-0.528,0.892],[-0.21,0.229],[0,0],[-0.018,0.121],[-0.293,0.65],[0,0],[0,-0.031],[-0.004,0.014],[-0.041,0.083],[-0.067,0.076],[-0.005,0.007],[0.006,-0.047],[0,0.004],[-0.041,0.093],[-0.224,0.154],[0,0],[-0.14,-0.364],[0,0],[-0.016,0],[0,-0.005],[-0.039,-0.068],[-0.052,-0.077],[0,0],[0,0],[-0.157,-0.134],[0,0],[-0.764,-0.289],[-0.118,0.088],[0.005,-0.039],[-0.257,0.109],[-0.001,-0.033],[-0.368,0.071],[0.068,-0.058],[-0.176,0.009],[0.062,-0.026],[0.117,-0.057],[0.035,-0.028],[0.261,-0.684],[0,0],[0,0],[-0.485,-0.077],[-0.041,-0.152],[0,0],[0.006,-0.04],[0.233,-0.198],[0.07,-0.045],[0.28,-0.123],[0.316,-0.196],[0.045,-0.044],[0,-0.005],[-0.011,-0.056],[-0.036,-0.068],[0.009,-0.115],[0.083,-0.192],[0.101,0.051],[0,0],[-0.046,-0.118],[0,0],[0.09,0.05],[0.113,0.049],[0.012,-0.004],[0,0],[0.129,0.064],[0.093,-0.166],[-0.179,-0.324],[-0.259,-0.177],[-0.015,-0.012],[-0.218,-0.109],[-0.068,-0.039],[-0.064,-0.034],[-0.763,0.111],[-0.451,-0.592],[0.366,0.116],[0,0],[0.762,-0.423],[0.818,0.173],[0.199,0.069],[0,0],[0,0],[-0.562,-0.34],[-0.416,-0.078],[-1.148,0.914],[-0.001,1.467],[0.272,0.642],[-0.488,-0.931],[1.067,0.88],[0.381,1.005],[0,0],[-1.363,-1.273],[-0.526,-0.531],[-0.141,0.387],[-0.334,-0.559],[-0.386,-0.63],[-0.079,-0.119],[-0.165,-0.347],[-0.16,-0.563],[-0.039,-0.466]],"o":[[0,0],[0.055,0.31],[0,0.04],[-0.062,1.483],[-0.04,0.067],[0.117,0.313],[-0.218,0.847],[-0.474,0.382],[0,0],[0,0],[-0.072,0.033],[-0.104,0.22],[-0.111,0.139],[-1.032,-0.001],[0.036,0.016],[-0.114,-0.038],[-0.834,-0.257],[-1.778,-1.076],[-0.63,-1.369],[-0.123,0.361],[0,-1.275],[-0.241,0.346],[0.247,-1.007],[0.158,-0.267],[0,0],[-0.004,-0.122],[0.065,-0.71],[0,0],[0.006,-0.012],[0,0.031],[0.026,-0.089],[0.045,-0.091],[0.005,-0.007],[0.005,-0.007],[0,0.017],[0.027,-0.051],[0.09,-0.257],[0,0],[-0.013,0.39],[0,0],[0.014,0.02],[0.003,0.001],[0.04,0.082],[0.055,0.097],[0,0],[0,0],[0.115,0.18],[0,0],[0.797,-0.161],[0.095,-0.112],[0,0.039],[0.171,-0.221],[-0.014,0.03],[0.323,-0.19],[0.048,0],[0.162,-0.07],[0.288,-0.03],[-0.122,0.044],[-0.035,0.029],[-0.613,0.401],[0,0],[0,0],[0.108,0.479],[1.373,0.129],[0,0],[-0.002,0.04],[-0.056,0.301],[-0.062,0.056],[-0.047,0.027],[-0.344,0.141],[-0.053,0.035],[-0.016,0.017],[0.025,0.051],[-0.063,-0.071],[0.04,0.108],[0.007,0.209],[-0.091,-0.068],[0,0],[0.106,0.069],[0.034,0.095],[-0.059,-0.084],[-0.132,-0.078],[-0.012,0.007],[0.032,0.057],[0,0],[-0.171,0.083],[-0.151,0.338],[0.174,0.261],[0.016,0.012],[0.2,0.14],[0.066,0.044],[0.061,0.04],[1,0.525],[0.733,-0.129],[0.261,0.371],[0,0],[-0.374,-0.124],[-0.755,0.359],[-0.207,-0.04],[0,0],[0,0],[0.383,0.533],[0.377,0.192],[1.431,0.327],[1.148,-0.914],[0.004,-0.698],[0.904,0.537],[-0.606,-1.77],[-0.858,-0.646],[-1.103,-2.96],[0,0],[0.276,0.257],[0.074,-0.405],[0.11,0.642],[0.512,0.728],[0.083,0.112],[0.221,0.314],[0.262,0.523],[0.128,0.45],[0.127,-0.167]],"v":[[10.333,-1.238],[10.393,-0.927],[10.475,0.011],[10.475,0.135],[9.184,4.32],[9.068,4.509],[9.048,5.476],[7.611,7.575],[6.069,8.54],[5.987,8.579],[5.942,8.599],[5.729,8.692],[5.298,9.271],[0.329,10.801],[-2.718,10.353],[-2.614,10.398],[-2.948,10.281],[-5.296,9.156],[-9.325,4.587],[-10.17,0.211],[-10.475,1.314],[-9.767,-2.41],[-10.394,-1.312],[-9.224,-4.176],[-8.671,-4.921],[-8.671,-4.932],[-8.65,-5.296],[-8.109,-7.351],[-8.094,-7.38],[-8.094,-7.305],[-8.094,-7.261],[-7.992,-7.52],[-7.822,-7.772],[-7.809,-7.791],[-7.831,-7.656],[-7.831,-7.632],[-7.736,-7.827],[-7.253,-8.459],[-7.242,-8.464],[-7.05,-7.322],[-7.05,-7.318],[-7.009,-7.374],[-7,-7.366],[-6.881,-7.137],[-6.721,-6.875],[-6.708,-6.86],[-6.697,-6.863],[-6.282,-6.389],[-6.275,-6.383],[-3.888,-6.184],[-3.568,-6.485],[-3.575,-6.367],[-2.922,-6.871],[-2.942,-6.776],[-1.898,-7.171],[-2.121,-7.035],[-1.609,-7.156],[-1.058,-7.036],[-1.416,-6.884],[-1.311,-6.863],[-2.655,-5.193],[-2.655,-5.182],[-2.655,-5.188],[-1.674,-4.268],[0.003,-3.872],[0.003,-3.801],[-0.009,-3.681],[-0.456,-2.909],[-0.654,-2.756],[-1.214,-2.513],[-2.206,-2.008],[-2.352,-1.889],[-2.404,-1.82],[-2.351,-1.658],[-2.434,-1.635],[-2.387,-1.296],[-2.502,-0.687],[-2.791,-0.866],[-2.802,-0.866],[-2.567,-0.579],[-2.579,-0.462],[-2.806,-0.665],[-3.301,-0.919],[-3.334,-0.912],[-3.244,-0.746],[-3.453,-0.858],[-3.859,-0.476],[-3.814,0.576],[-3.159,1.239],[-3.294,1.203],[-2.666,1.578],[-2.815,1.564],[-2.628,1.674],[-0.165,1.546],[1.763,2.299],[1.37,2.908],[1.363,2.908],[-0.235,3.521],[-2.661,3.807],[-3.27,3.644],[-3.355,3.613],[-3.349,3.624],[-1.917,4.948],[-0.722,5.354],[3.358,4.425],[5.176,0.656],[4.77,-1.375],[6.898,0.87],[4.076,-2.472],[2.181,-4.999],[3.623,-10.801],[4.828,-8.868],[6.088,-7.703],[6.41,-8.892],[7.081,-7.074],[8.422,-5.33],[8.665,-4.985],[9.245,-3.991],[9.879,-2.359],[10.13,-0.982]],"c":true}]}],"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[-0.02,0.05]],"o":[[0.018,-0.051],[0,0]],"v":[[-2.71,-5.03],[-2.655,-5.182]],"c":false},"ix":2},"nm":"Контур 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Объединить контуры 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Vector","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-60,"bm":0},{"ddd":0,"ind":2,"ty":3,"nm":"Ellipse 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":40,"s":[1090]},{"t":50,"s":[1080]}],"ix":10},"p":{"a":0,"k":[15,15,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[16.667,16.667,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":20,"s":[11.667,11.667,100]},{"t":40,"s":[16.667,16.667,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":120,"st":-60,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/Telegram/Resources/icons/settings/devices/device_web_firefox.png b/Telegram/Resources/icons/settings/devices/device_web_firefox.png new file mode 100644 index 000000000..aba0ee2de Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_web_firefox.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_web_firefox@2x.png b/Telegram/Resources/icons/settings/devices/device_web_firefox@2x.png new file mode 100644 index 000000000..d7a177b83 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_web_firefox@2x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_web_firefox@3x.png b/Telegram/Resources/icons/settings/devices/device_web_firefox@3x.png new file mode 100644 index 000000000..06ec192d5 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_web_firefox@3x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_web_other.png b/Telegram/Resources/icons/settings/devices/device_web_other.png new file mode 100644 index 000000000..9b777f53f Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_web_other.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_web_other@2x.png b/Telegram/Resources/icons/settings/devices/device_web_other@2x.png new file mode 100644 index 000000000..ce90741f8 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_web_other@2x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_web_other@3x.png b/Telegram/Resources/icons/settings/devices/device_web_other@3x.png new file mode 100644 index 000000000..b6740fc96 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_web_other@3x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_web_other_large.png b/Telegram/Resources/icons/settings/devices/device_web_other_large.png new file mode 100644 index 000000000..200206a6e Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_web_other_large.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_web_other_large@2x.png b/Telegram/Resources/icons/settings/devices/device_web_other_large@2x.png new file mode 100644 index 000000000..740152c94 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_web_other_large@2x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_web_other_large@3x.png b/Telegram/Resources/icons/settings/devices/device_web_other_large@3x.png new file mode 100644 index 000000000..6463c3a34 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_web_other_large@3x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_web_safari.lottie b/Telegram/Resources/icons/settings/devices/device_web_safari.lottie new file mode 100644 index 000000000..5ff6fcbba --- /dev/null +++ b/Telegram/Resources/icons/settings/devices/device_web_safari.lottie @@ -0,0 +1 @@ +{"v":"5.7.4","fr":60,"ip":0,"op":120,"w":30,"h":30,"nm":"safari_30","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Com 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":20,"s":[185]},{"t":30,"s":[180]}],"ix":10},"p":{"a":0,"k":[15,15,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[16.667,16.667,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.15,0.205],[-0.233,0.543],[0,0],[0.142,0.444],[0.385,0.123],[1.435,-0.615],[0,0],[0.231,-0.17],[0.15,-0.205],[0.233,-0.543],[0,0],[-0.142,-0.444],[-0.385,-0.123],[-1.435,0.615],[0,0],[-0.231,0.17]],"o":[[0.17,-0.231],[0,0],[0.615,-1.435],[-0.123,-0.385],[-0.444,-0.142],[0,0],[-0.543,0.233],[-0.205,0.15],[-0.17,0.231],[0,0],[-0.615,1.435],[0.123,0.385],[0.444,0.142],[0,0],[0.543,-0.233],[0.205,-0.15]],"v":[[2.611,2.075],[3.13,1.029],[4.28,-1.654],[5.061,-4.251],[4.251,-5.061],[1.654,-4.28],[-1.029,-3.13],[-2.075,-2.611],[-2.611,-2.075],[-3.13,-1.029],[-4.28,1.654],[-5.061,4.251],[-4.251,5.061],[-1.654,4.28],[1.029,3.13],[2.075,2.611]],"c":true},"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"d":1,"ty":"el","s":{"a":0,"k":[2.5,2.5],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Контур эллипса 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"mm","mm":3,"nm":"Объединить контуры 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Com 2","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-10,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Com 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[15,15,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[16.667,16.667,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[20,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Контур эллипса 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Com 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-10,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/Telegram/Resources/icons/settings/devices/device_web_safari.png b/Telegram/Resources/icons/settings/devices/device_web_safari.png new file mode 100644 index 000000000..6ca985c19 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_web_safari.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_web_safari@2x.png b/Telegram/Resources/icons/settings/devices/device_web_safari@2x.png new file mode 100644 index 000000000..5ac37986c Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_web_safari@2x.png differ diff --git a/Telegram/Resources/icons/settings/devices/device_web_safari@3x.png b/Telegram/Resources/icons/settings/devices/device_web_safari@3x.png new file mode 100644 index 000000000..b7d19c566 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/device_web_safari@3x.png differ diff --git a/Telegram/Resources/icons/settings/devices/terminate_all.png b/Telegram/Resources/icons/settings/devices/terminate_all.png new file mode 100644 index 000000000..ccf538575 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/terminate_all.png differ diff --git a/Telegram/Resources/icons/settings/devices/terminate_all@2x.png b/Telegram/Resources/icons/settings/devices/terminate_all@2x.png new file mode 100644 index 000000000..8720d7f23 Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/terminate_all@2x.png differ diff --git a/Telegram/Resources/icons/settings/devices/terminate_all@3x.png b/Telegram/Resources/icons/settings/devices/terminate_all@3x.png new file mode 100644 index 000000000..d4da46f9f Binary files /dev/null and b/Telegram/Resources/icons/settings/devices/terminate_all@3x.png differ diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index a927b025a..9f2550f2d 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -78,7 +78,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_month_day_year" = "{month} {day}, {year}"; "lng_month_year" = "{month} {year}"; -"lng_calendar_beginning" = "Beginning"; +"lng_calendar_select_days" = "Select days"; +"lng_calendar_start_tip" = "Press and hold to jump to the start."; +"lng_calendar_end_tip" = "Press and hold to jump to the end."; +"lng_calendar_days#one" = "{count} day"; +"lng_calendar_days#other" = "{count} days"; "lng_box_ok" = "OK"; "lng_box_done" = "Done"; @@ -148,6 +152,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_error_admin_limit" = "Sorry, you've reached the maximum number of admins for this group."; "lng_error_admin_limit_channel" = "Sorry, you've reached the maximum number of admins for this channel."; "lng_error_post_link_invalid" = "Unfortunately, you can't access this message. You are not a member of the chat where it was posted."; +"lng_error_noforwards_group" = "Sorry, forwarding from this group is disabled by admins."; +"lng_error_noforwards_channel" = "Sorry, forwarding from this channel is disabled by admins."; +"lng_error_nocopy_group" = "Sorry, copying from this group is disabled by admins."; +"lng_error_nocopy_channel" = "Sorry, copying from this channel is disabled by admins."; "lng_sure_add_admin_invite" = "This user is not a member of this group. Add them to the group and promote them to admin?"; "lng_sure_add_admin_invite_channel" = "This user is not a subscriber of this channel. Add them to the channel and promote them to admin?"; "lng_sure_add_admin_unremove" = "This user is currently restricted or removed. Are you sure you want to promote them?"; @@ -687,10 +695,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_destroy_if" = "If away for..."; "lng_settings_change_phone" = "Change phone number"; +"lng_settings_terminate_title" = "Terminate old sessions"; +"lng_settings_terminate_if" = "If inactive for..."; "lng_settings_reset" = "Terminate all other sessions"; "lng_settings_reset_sure" = "Are you sure you want to terminate\nall other sessions?"; "lng_settings_reset_one_sure" = "Do you want to terminate this session?"; "lng_settings_reset_button" = "Terminate"; +"lng_settings_rename_device" = "Rename"; +"lng_settings_device_name" = "Device name"; +"lng_settings_rename_device_title" = "Rename current device"; "lng_settings_manage_local_storage" = "Manage local storage"; "lng_settings_ask_question" = "Ask a Question"; "lng_settings_ask_sure" = "Please note that Telegram Support is run by volunteers. We try to respond as quickly as possible, but it may take a while.\n\nPlease take a look at the Telegram FAQ: it has important troubleshooting tips and answers to most questions."; @@ -710,6 +723,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_sessions_terminate_all_about" = "Logs out all devices except for this one."; "lng_sessions_incomplete" = "Incomplete login attempts"; "lng_sessions_incomplete_about" = "The devices above have no access to your messages. The code was entered correctly, but no correct password was given."; +"lng_sessions_info" = "Info"; +"lng_sessions_terminate" = "Terminate Session"; +"lng_sessions_application" = "Application"; +"lng_sessions_system" = "System version"; +"lng_sessions_ip" = "IP address"; +"lng_sessions_location" = "Location"; +"lng_sessions_location_about" = "This location is based only on the IP address and may not always be accurate."; +"lng_sessions_about_apps" = "The official Telegram app is available for Android, iPhone, iPad, Windows, macOS and Linux."; "lng_blocked_list_title" = "Blocked users"; "lng_blocked_list_unknown_phone" = "unknown phone number"; @@ -805,6 +826,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_self_destruct_title" = "Account self-destruction"; "lng_self_destruct_description" = "If you don't come online at least once within this period, your account will be deleted along with all groups, messages and contacts."; +"lng_self_destruct_sessions_title" = "Session termination"; +"lng_self_destruct_sessions_description" = "If you don't come online from a specific session at least once within this period, it will be terminated."; +"lng_self_destruct_weeks#one" = "{count} week"; +"lng_self_destruct_weeks#other" = "{count} weeks"; "lng_self_destruct_months#one" = "{count} month"; "lng_self_destruct_months#other" = "{count} months"; "lng_self_destruct_years#one" = "{count} year"; @@ -993,6 +1018,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_manage_public_group_title" = "Public"; "lng_manage_private_peer_title" = "Private"; "lng_manage_public_peer_title" = "Public"; +"lng_manage_peer_no_forwards_title" = "Saving content"; +"lng_manage_peer_no_forwards" = "Restrict saving content"; +"lng_manage_peer_no_forwards_about" = "Members won't be able to forward messages from this group or save media files."; +"lng_manage_peer_no_forwards_about_channel" = "Subscribers won't be able to forward messages from this channel or save media files."; "lng_manage_discussion_group" = "Discussion"; "lng_manage_discussion_group_add" = "Add a group"; @@ -1128,6 +1157,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_sure_delete_saved_messages" = "Are you sure, you want to delete all your saved messages?\n\nThis action cannot be undone."; "lng_no_clear_history_channel" = "In channels you can enable auto-delete for messages."; "lng_no_clear_history_group" = "In public groups you can enable auto-delete for messages."; +"lng_sure_delete_by_date_one" = "Are you sure you want to delete all messages for **{date}**?\n\nThis action cannot be undone."; +"lng_sure_delete_by_date_many" = "Are you sure you want to delete all messages for the **{days}**?\n\nThis action cannot be undone."; +"lng_sure_delete_selected_days#one" = "{count} selected day"; +"lng_sure_delete_selected_days#other" = "{count} selected days"; "lng_message_empty" = "Empty Message"; "lng_message_unsupported" = "This message is not supported by your version of Telegram Desktop. Please update to the latest version in Settings, or install it from {link}"; @@ -1520,6 +1553,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_new_contact_add_name" = "Add {user} to contacts"; "lng_new_contact_add_done" = "{user} is now in your contact list."; "lng_new_contact_unarchive" = "Unarchive"; +"lng_new_contact_from_request_channel" = "{user} is an admin of {name}, a channel you requested to join."; +"lng_new_contact_from_request_group" = "{user} is an admin of {name}, a group you requested to join."; +"lng_from_request_title_channel" = "Chat with channel's admin"; +"lng_from_request_title_group" = "Chat with group's admin"; +"lng_from_request_body" = "You received this message because you requested to join {name} on {date}."; +"lng_from_request_understand" = "I understand"; "lng_cant_send_to_not_contact" = "Sorry, you can only send messages to\nmutual contacts at the moment.\n{more_info}"; "lng_cant_invite_not_contact" = "Sorry, you can only add mutual contacts\nto groups at the moment.\n{more_info}"; "lng_cant_more_info" = "More info »"; @@ -1543,6 +1582,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_broadcast_ph" = "Broadcast a message..."; "lng_broadcast_silent_ph" = "Silent broadcast..."; "lng_send_anonymous_ph" = "Send anonymously..."; +"lng_send_as_title" = "Send message as..."; +"lng_send_as_anonymous_admin" = "Anonymous admin"; "lng_record_cancel" = "Release outside this field to cancel"; "lng_record_lock_cancel_sure" = "Are you sure you want to stop recording and discard your voice message?"; "lng_record_listen_cancel_sure" = "Are you sure you want to discard your recorded voice message?"; @@ -1977,7 +2018,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_payments_webview_no_card" = "Unfortunately, you can't add a new card with current system configuration."; "lng_payments_webview_no_use" = "Unfortunately, you can't use payments with current system configuration."; "lng_payments_webview_install_edge" = "Please install {link}."; -"lng_payments_webview_install_webkit" = "Please install WebKitGTK 4 (webkit2gtk-4.0) using your package manager."; +"lng_payments_webview_install_webkit" = "Please install WebKitGTK (webkit2gtk-5.0/webkit2gtk-4.1/webkit2gtk-4.0) using your package manager."; "lng_payments_webview_switch_mutter" = "Qt's window embedding doesn't work well with Mutter window manager. Please switch to another window manager or desktop environment."; "lng_payments_webview_switch_wayland" = "There is no way to embed WebView window on Wayland. Please switch to X11."; "lng_payments_sure_close" = "Are you sure you want to close this payment form? The changes you made will be lost."; @@ -2296,6 +2337,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_player_message_yesterday" = "Yesterday at {time}"; "lng_player_message_date" = "{date} at {time}"; +"lng_audio_player_reverse" = "Reverse order"; +"lng_audio_player_shuffle" = "Shuffle"; + "lng_rights_edit_admin" = "Manage permissions"; "lng_rights_edit_admin_header" = "What can this admin do?"; "lng_rights_edit_admin_rank_name" = "Custom title"; @@ -2475,16 +2519,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_admin_log_invites_disabled" = "{from} disabled group invites"; "lng_admin_log_signatures_enabled" = "{from} enabled signatures"; "lng_admin_log_signatures_disabled" = "{from} disabled signatures"; +"lng_admin_log_forwards_enabled" = "{from} allowed content copying"; +"lng_admin_log_forwards_disabled" = "{from} restricted content copying"; "lng_admin_log_history_made_hidden" = "{from} made group history hidden for new members"; "lng_admin_log_history_made_visible" = "{from} made group history visible for new members"; -"lng_admin_log_pinned_message" = "{from} pinned message:"; -"lng_admin_log_unpinned_message" = "{from} unpinned message"; +"lng_admin_log_pinned_message" = "{from} pinned this message:"; +"lng_admin_log_unpinned_message" = "{from} unpinned this message"; "lng_admin_log_edited_caption" = "{from} edited caption:"; "lng_admin_log_removed_caption" = "{from} removed caption"; "lng_admin_log_previous_caption" = "Original caption"; -"lng_admin_log_edited_message" = "{from} edited message:"; +"lng_admin_log_edited_message" = "{from} edited this message:"; "lng_admin_log_previous_message" = "Original message"; -"lng_admin_log_deleted_message" = "{from} deleted message:"; +"lng_admin_log_deleted_message" = "{from} deleted this message:"; +"lng_admin_log_sent_message" = "{from} sent this message:"; "lng_admin_log_participant_joined" = "{from} joined the group"; "lng_admin_log_participant_joined_channel" = "{from} joined the channel"; "lng_admin_log_participant_joined_by_link" = "{from} joined the group via {link}"; diff --git a/Telegram/Resources/qrc/telegram/telegram.qrc b/Telegram/Resources/qrc/telegram/telegram.qrc index b1914767c..cc93a15cf 100644 --- a/Telegram/Resources/qrc/telegram/telegram.qrc +++ b/Telegram/Resources/qrc/telegram/telegram.qrc @@ -78,6 +78,21 @@ ../../art/recording/recording_info_video_landscape.svg ../../art/recording/recording_info_video_portrait.svg + + ../../icons/calls/hands.lottie + ../../icons/calls/voice.lottie + ../../icons/settings/devices/device_desktop_mac.lottie + ../../icons/settings/devices/device_desktop_win.lottie + ../../icons/settings/devices/device_linux.lottie + ../../icons/settings/devices/device_linux_ubuntu.lottie + ../../icons/settings/devices/device_phone_android.lottie + ../../icons/settings/devices/device_phone_ios.lottie + ../../icons/settings/devices/device_tablet_ios.lottie + ../../icons/settings/devices/device_web_chrome.lottie + ../../icons/settings/devices/device_web_edge.lottie + ../../icons/settings/devices/device_web_firefox.lottie + ../../icons/settings/devices/device_web_safari.lottie + ../qmime/freedesktop.org.xml diff --git a/Telegram/Resources/tl/api.tl b/Telegram/Resources/tl/api.tl index 159b5b1c5..f113191df 100644 --- a/Telegram/Resources/tl/api.tl +++ b/Telegram/Resources/tl/api.tl @@ -123,13 +123,13 @@ userStatusLastWeek#7bf09fc = UserStatus; userStatusLastMonth#77ebc742 = UserStatus; chatEmpty#29562865 id:long = Chat; -chat#41cbf256 flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true id:long title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat; +chat#41cbf256 flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true noforwards:flags.25?true id:long title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat; chatForbidden#6592a1a7 id:long title:string = Chat; -channel#8261ac61 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat; +channel#8261ac61 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat; channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat; chatFull#46a6ffb4 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector = ChatFull; -channelFull#59cff963 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector = ChatFull; +channelFull#56662e2e flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer = ChatFull; chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant; chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant; @@ -142,7 +142,7 @@ chatPhotoEmpty#37c1011c = ChatPhoto; chatPhoto#1c6e1c11 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = ChatPhoto; messageEmpty#90a6ca84 flags:# id:int peer_id:flags.0?Peer = Message; -message#85d6cbe2 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector ttl_period:flags.25?int = Message; +message#85d6cbe2 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector ttl_period:flags.25?int = Message; messageService#2b085862 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction ttl_period:flags.25?int = Message; messageMediaEmpty#3ded6320 = MessageMedia; @@ -208,7 +208,7 @@ geoPoint#b2a2f663 flags:# long:double lat:double access_hash:long accuracy_radiu auth.sentCode#5e002502 flags:# type:auth.SentCodeType phone_code_hash:string next_type:flags.1?auth.CodeType timeout:flags.2?int = auth.SentCode; -auth.authorization#cd050916 flags:# tmp_sessions:flags.0?int user:User = auth.Authorization; +auth.authorization#33fb7bb8 flags:# setup_password_required:flags.1?true otherwise_relogin_days:flags.1?int tmp_sessions:flags.0?int user:User = auth.Authorization; auth.authorizationSignUpRequired#44747e9a flags:# terms_of_service:flags.0?help.TermsOfService = auth.Authorization; auth.exportedAuthorization#b434e2b8 id:long bytes:bytes = auth.ExportedAuthorization; @@ -222,7 +222,7 @@ inputPeerNotifySettings#9c3d198e flags:# show_previews:flags.0?Bool silent:flags peerNotifySettings#af509d20 flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?string = PeerNotifySettings; -peerSettings#733f2961 flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true invite_members:flags.8?true geo_distance:flags.6?int = PeerSettings; +peerSettings#a518110d flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true invite_members:flags.8?true request_chat_broadcast:flags.10?true geo_distance:flags.6?int request_chat_title:flags.9?string request_chat_date:flags.9?int = PeerSettings; wallPaper#a437c3ed id:long flags:# creator:flags.0?true default:flags.1?true pattern:flags.3?true dark:flags.4?true access_hash:long slug:string document:Document settings:flags.2?WallPaperSettings = WallPaper; wallPaperNoFile#e0804116 id:long flags:# default:flags.1?true dark:flags.4?true settings:flags.2?WallPaperSettings = WallPaper; @@ -236,7 +236,7 @@ inputReportReasonCopyright#9b89f93a = ReportReason; inputReportReasonGeoIrrelevant#dbd4feed = ReportReason; inputReportReasonFake#f5ddd6e7 = ReportReason; -userFull#d697ff05 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true user:User about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string = UserFull; +userFull#cf366521 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true id:long about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string = UserFull; contact#145ade0b user_id:long mutual:Bool = Contact; @@ -538,9 +538,9 @@ webPagePending#c586da1c id:long date:int = WebPage; webPage#e89c45b2 flags:# id:long url:string display_url:string hash:int type:flags.0?string site_name:flags.1?string title:flags.2?string description:flags.3?string photo:flags.4?Photo embed_url:flags.5?string embed_type:flags.5?string embed_width:flags.6?int embed_height:flags.6?int duration:flags.7?int author:flags.8?string document:flags.9?Document cached_page:flags.10?Page attributes:flags.12?Vector = WebPage; webPageNotModified#7311ca11 flags:# cached_page_views:flags.0?int = WebPage; -authorization#ad01d61d flags:# current:flags.0?true official_app:flags.1?true password_pending:flags.2?true hash:long device_model:string platform:string system_version:string api_id:int app_name:string app_version:string date_created:int date_active:int ip:string country:string region:string = Authorization; +authorization#ad01d61d flags:# current:flags.0?true official_app:flags.1?true password_pending:flags.2?true encrypted_requests_disabled:flags.3?true call_requests_disabled:flags.4?true hash:long device_model:string platform:string system_version:string api_id:int app_name:string app_version:string date_created:int date_active:int ip:string country:string region:string = Authorization; -account.authorizations#1250abde authorizations:Vector = account.Authorizations; +account.authorizations#4bff8ea0 authorization_ttl_days:int authorizations:Vector = account.Authorizations; account.password#185b184f flags:# has_recovery:flags.0?true has_secure_values:flags.1?true has_password:flags.2?true current_algo:flags.2?PasswordKdfAlgo srp_B:flags.2?bytes srp_id:flags.2?long hint:flags.3?string email_unconfirmed_pattern:flags.4?string new_algo:PasswordKdfAlgo new_secure_algo:SecurePasswordKdfAlgo secure_random:bytes pending_reset_date:flags.5?int = account.Password; @@ -568,6 +568,7 @@ inputStickerSetAnimatedEmojiAnimations#cde3739 = InputStickerSet; stickerSet#d7df217a flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector thumb_dc_id:flags.4?int thumb_version:flags.4?int count:int hash:int = StickerSet; messages.stickerSet#b60a24a6 set:StickerSet packs:Vector documents:Vector = messages.StickerSet; +messages.stickerSetNotModified#d3f924eb = messages.StickerSet; botCommand#c27ac8c7 command:string description:string = BotCommand; @@ -584,6 +585,8 @@ keyboardButtonBuy#afd93fbb text:string = KeyboardButton; keyboardButtonUrlAuth#10b78d29 flags:# text:string fwd_text:flags.0?string url:string button_id:int = KeyboardButton; inputKeyboardButtonUrlAuth#d02e7fd4 flags:# request_write_access:flags.0?true text:string fwd_text:flags.1?string url:string bot:InputUser = KeyboardButton; keyboardButtonRequestPoll#bbc7515d flags:# quiz:flags.0?Bool text:string = KeyboardButton; +inputKeyboardButtonUserProfile#e988037b text:string user_id:InputUser = KeyboardButton; +keyboardButtonUserProfile#308660c1 text:string user_id:long = KeyboardButton; keyboardButtonRow#77608b83 buttons:Vector = KeyboardButtonRow; @@ -628,7 +631,7 @@ channelMessagesFilterEmpty#94d42ee7 = ChannelMessagesFilter; channelMessagesFilter#cd77d957 flags:# exclude_new_messages:flags.1?true ranges:Vector = ChannelMessagesFilter; channelParticipant#c00c07c0 user_id:long date:int = ChannelParticipant; -channelParticipantSelf#35a8bfa7 flags:# via_invite:flags.0?true user_id:long inviter_id:long date:int = ChannelParticipant; +channelParticipantSelf#35a8bfa7 flags:# via_request:flags.0?true user_id:long inviter_id:long date:int = ChannelParticipant; channelParticipantCreator#2fe601d3 flags:# user_id:long admin_rights:ChatAdminRights rank:flags.0?string = ChannelParticipant; channelParticipantAdmin#34c3bb53 flags:# can_edit:flags.0?true self:flags.1?true user_id:long inviter_id:flags.1?long promoted_by:long date:int admin_rights:ChatAdminRights rank:flags.2?string = ChannelParticipant; channelParticipantBanned#6df8014e flags:# left:flags.0?true peer:Peer kicked_by:long date:int banned_rights:ChatBannedRights = ChannelParticipant; @@ -685,11 +688,13 @@ messageFwdHeader#5f777dce flags:# imported:flags.7?true from_id:flags.0?Peer fro auth.codeTypeSms#72a3158c = auth.CodeType; auth.codeTypeCall#741cd3e3 = auth.CodeType; auth.codeTypeFlashCall#226ccefb = auth.CodeType; +auth.codeTypeMissedCall#d61ad6ee = auth.CodeType; auth.sentCodeTypeApp#3dbb5986 length:int = auth.SentCodeType; auth.sentCodeTypeSms#c000bba2 length:int = auth.SentCodeType; auth.sentCodeTypeCall#5353e5a7 length:int = auth.SentCodeType; auth.sentCodeTypeFlashCall#ab03c6d9 pattern:string = auth.SentCodeType; +auth.sentCodeTypeMissedCall#82006484 prefix:string length:int = auth.SentCodeType; messages.botCallbackAnswer#36585ea4 flags:# alert:flags.1?true has_url:flags.3?true native_ui:flags.4?true message:flags.0?string url:flags.2?string cache_time:int = messages.BotCallbackAnswer; @@ -912,12 +917,14 @@ channelAdminLogEventActionExportedInviteEdit#e90ebb59 prev_invite:ExportedChatIn channelAdminLogEventActionParticipantVolume#3e7f6847 participant:GroupCallParticipant = ChannelAdminLogEventAction; channelAdminLogEventActionChangeHistoryTTL#6e941a38 prev_value:int new_value:int = ChannelAdminLogEventAction; channelAdminLogEventActionParticipantJoinByRequest#afb6144a invite:ExportedChatInvite approved_by:long = ChannelAdminLogEventAction; +channelAdminLogEventActionToggleNoForwards#cb2ac766 new_value:Bool = ChannelAdminLogEventAction; +channelAdminLogEventActionSendMessage#278f2868 message:Message = ChannelAdminLogEventAction; channelAdminLogEvent#1fad68cd id:long date:int user_id:long action:ChannelAdminLogEventAction = ChannelAdminLogEvent; channels.adminLogResults#ed8af74d events:Vector chats:Vector users:Vector = channels.AdminLogResults; -channelAdminLogEventsFilter#ea107ae4 flags:# join:flags.0?true leave:flags.1?true invite:flags.2?true ban:flags.3?true unban:flags.4?true kick:flags.5?true unkick:flags.6?true promote:flags.7?true demote:flags.8?true info:flags.9?true settings:flags.10?true pinned:flags.11?true edit:flags.12?true delete:flags.13?true group_call:flags.14?true invites:flags.15?true = ChannelAdminLogEventsFilter; +channelAdminLogEventsFilter#ea107ae4 flags:# join:flags.0?true leave:flags.1?true invite:flags.2?true ban:flags.3?true unban:flags.4?true kick:flags.5?true unkick:flags.6?true promote:flags.7?true demote:flags.8?true info:flags.9?true settings:flags.10?true pinned:flags.11?true edit:flags.12?true delete:flags.13?true group_call:flags.14?true invites:flags.15?true send:flags.16?true = ChannelAdminLogEventsFilter; popularContact#5ce14175 client_id:long importers:int = PopularContact; @@ -1086,7 +1093,7 @@ inputWallPaperNoFile#967a462e id:long = InputWallPaper; account.wallPapersNotModified#1c199183 = account.WallPapers; account.wallPapers#cdc3858c hash:long wallpapers:Vector = account.WallPapers; -codeSettings#debebe83 flags:# allow_flashcall:flags.0?true current_number:flags.1?true allow_app_hash:flags.4?true = CodeSettings; +codeSettings#8a6469c2 flags:# allow_flashcall:flags.0?true current_number:flags.1?true allow_app_hash:flags.4?true allow_missed_call:flags.5?true logout_tokens:flags.6?Vector = CodeSettings; wallPaperSettings#1dc1bca4 flags:# blur:flags.1?true motion:flags.2?true background_color:flags.0?int second_background_color:flags.4?int third_background_color:flags.5?int fourth_background_color:flags.6?int intensity:flags.3?int rotation:flags.4?int = WallPaperSettings; @@ -1287,6 +1294,14 @@ searchResultPosition#7f648b67 msg_id:int date:int offset:int = SearchResultsPosi messages.searchResultsPositions#53b22baf count:int positions:Vector = messages.SearchResultsPositions; +channels.sendAsPeers#8356cda9 peers:Vector chats:Vector users:Vector = channels.SendAsPeers; + +users.userFull#3b6d152e full_user:UserFull chats:Vector users:Vector = users.UserFull; + +messages.peerSettings#6880b94d settings:PeerSettings chats:Vector users:Vector = messages.PeerSettings; + +auth.loggedOut#c3a2835f flags:# future_auth_token:flags.0?bytes = auth.LoggedOut; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1300,7 +1315,7 @@ invokeWithTakeout#aca9fd2e {X:Type} takeout_id:long query:!X = X; auth.sendCode#a677244f phone_number:string api_id:int api_hash:string settings:CodeSettings = auth.SentCode; auth.signUp#80eee427 phone_number:string phone_code_hash:string first_name:string last_name:string = auth.Authorization; auth.signIn#bcd51581 phone_number:string phone_code_hash:string phone_code:string = auth.Authorization; -auth.logOut#5717da40 = Bool; +auth.logOut#3e72ba19 = auth.LoggedOut; auth.resetAuthorizations#9fab0d1a = Bool; auth.exportAuthorization#e5bfffcd dc_id:int = auth.ExportedAuthorization; auth.importAuthorization#a57a7dad id:long bytes:bytes = auth.Authorization; @@ -1388,9 +1403,11 @@ account.reportProfilePhoto#fa8cc6f5 peer:InputPeer photo_id:InputPhoto reason:Re account.resetPassword#9308ce1b = account.ResetPasswordResult; account.declinePasswordReset#4c9409f6 = Bool; account.getChatThemes#d638de89 hash:long = account.Themes; +account.setAuthorizationTTL#bf899aa0 authorization_ttl_days:int = Bool; +account.changeAuthorizationSettings#40f48462 flags:# hash:long encrypted_requests_disabled:flags.0?Bool call_requests_disabled:flags.1?Bool = Bool; users.getUsers#d91a548 id:Vector = Vector; -users.getFullUser#ca30a5b1 id:InputUser = UserFull; +users.getFullUser#b60f5918 id:InputUser = users.UserFull; users.setSecureValueErrors#90c894b5 id:InputUser errors:Vector = Bool; contacts.getContactIDs#7adc669d hash:long = Vector; @@ -1423,11 +1440,11 @@ messages.deleteHistory#b08f922a flags:# just_clear:flags.0?true revoke:flags.1?t messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector = messages.AffectedMessages; messages.receivedMessages#5a954c0 max_id:int = Vector; messages.setTyping#58943ee2 flags:# peer:InputPeer top_msg_id:flags.0?int action:SendMessageAction = Bool; -messages.sendMessage#520c3870 flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int = Updates; -messages.sendMedia#3491eba9 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int = Updates; -messages.forwardMessages#d9fee60e flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true drop_author:flags.11?true drop_media_captions:flags.12?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer schedule_date:flags.10?int = Updates; +messages.sendMessage#d9d75a4 flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; +messages.sendMedia#e25ff8e0 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; +messages.forwardMessages#cc30290b flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true drop_author:flags.11?true drop_media_captions:flags.12?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; messages.reportSpam#cf1592db peer:InputPeer = Bool; -messages.getPeerSettings#3672e09c peer:InputPeer = PeerSettings; +messages.getPeerSettings#efd9a6a2 peer:InputPeer = messages.PeerSettings; messages.report#8953ab4e peer:InputPeer id:Vector reason:ReportReason message:string = Bool; messages.getChats#49e9528f id:Vector = messages.Chats; messages.getFullChat#aeb00b34 chat_id:long = messages.ChatFull; @@ -1454,7 +1471,7 @@ messages.getWebPagePreview#8b68b0cc flags:# message:string entities:flags.3?Vect messages.exportChatInvite#a02ce5d5 flags:# legacy_revoke_permanent:flags.2?true request_needed:flags.3?true peer:InputPeer expire_date:flags.0?int usage_limit:flags.1?int title:flags.4?string = ExportedChatInvite; messages.checkChatInvite#3eadb1bb hash:string = ChatInvite; messages.importChatInvite#6c50051c hash:string = Updates; -messages.getStickerSet#2619a90e stickerset:InputStickerSet = messages.StickerSet; +messages.getStickerSet#c8a0ec74 stickerset:InputStickerSet hash:int = messages.StickerSet; messages.installStickerSet#c78fe460 stickerset:InputStickerSet archived:Bool = messages.StickerSetInstallResult; messages.uninstallStickerSet#f96e55de stickerset:InputStickerSet = Bool; messages.startBot#e6df7378 bot:InputUser peer:InputPeer random_id:long start_param:string = Updates; @@ -1468,7 +1485,7 @@ messages.getSavedGifs#5cf09635 hash:long = messages.SavedGifs; messages.saveGif#327a30cb id:InputDocument unsave:Bool = Bool; messages.getInlineBotResults#514e999d flags:# bot:InputUser peer:InputPeer geo_point:flags.0?InputGeoPoint query:string offset:string = messages.BotResults; messages.setInlineBotResults#eb5ea206 flags:# gallery:flags.0?true private:flags.1?true query_id:long results:Vector cache_time:int next_offset:flags.2?string switch_pm:flags.3?InlineBotSwitchPM = Bool; -messages.sendInlineBotResult#220815b0 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true hide_via:flags.11?true peer:InputPeer reply_to_msg_id:flags.0?int random_id:long query_id:long id:string schedule_date:flags.10?int = Updates; +messages.sendInlineBotResult#7aa11297 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true hide_via:flags.11?true peer:InputPeer reply_to_msg_id:flags.0?int random_id:long query_id:long id:string schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; messages.getMessageEditData#fda68d36 peer:InputPeer id:int = messages.MessageEditData; messages.editMessage#48f71778 flags:# no_webpage:flags.1?true peer:InputPeer id:int message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.15?int = Updates; messages.editInlineBotMessage#83557dba flags:# no_webpage:flags.1?true id:InputBotInlineMessageID message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector = Bool; @@ -1504,7 +1521,7 @@ messages.faveSticker#b9ffc55b id:InputDocument unfave:Bool = Bool; messages.getUnreadMentions#46578472 peer:InputPeer offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages; messages.readMentions#f0189d3 peer:InputPeer = messages.AffectedHistory; messages.getRecentLocations#702a40e0 peer:InputPeer limit:int hash:long = messages.Messages; -messages.sendMultiMedia#cc0110cb flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int multi_media:Vector schedule_date:flags.10?int = Updates; +messages.sendMultiMedia#f803138f flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int multi_media:Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; messages.uploadEncryptedFile#5057c497 peer:InputEncryptedChat file:InputEncryptedFile = EncryptedFile; messages.searchStickerSets#35705b8a flags:# exclude_featured:flags.0?true q:string hash:long = messages.FoundStickerSets; messages.getSplitRanges#1cff7e08 = Vector; @@ -1560,6 +1577,9 @@ messages.getMessageReadParticipants#2c6f97b7 peer:InputPeer msg_id:int = Vector< messages.getSearchResultsCalendar#49f0bde9 peer:InputPeer filter:MessagesFilter offset_id:int offset_date:int = messages.SearchResultsCalendar; messages.getSearchResultsPositions#6e9583a3 peer:InputPeer filter:MessagesFilter offset_id:int limit:int = messages.SearchResultsPositions; messages.hideChatJoinRequest#7fe7e815 flags:# approved:flags.0?true peer:InputPeer user_id:InputUser = Updates; +messages.hideAllChatJoinRequests#e085f4ea flags:# approved:flags.0?true peer:InputPeer link:flags.1?string = Updates; +messages.toggleNoForwards#b11eafa2 peer:InputPeer enabled:Bool = Updates; +messages.saveDefaultSendAs#ccfddf96 peer:InputPeer send_as:InputPeer = Bool; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; @@ -1604,8 +1624,7 @@ help.getCountriesList#735787a8 lang_code:string hash:int = help.CountriesList; channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool; channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector = messages.AffectedMessages; -channels.deleteUserHistory#d10dd71b channel:InputChannel user_id:InputUser = messages.AffectedHistory; -channels.reportSpam#fe087810 channel:InputChannel user_id:InputUser id:Vector = Bool; +channels.reportSpam#f44a8315 channel:InputChannel participant:InputPeer id:Vector = Bool; channels.getMessages#ad8c9a23 channel:InputChannel id:Vector = messages.Messages; channels.getParticipants#77ced9d0 channel:InputChannel filter:ChannelParticipantsFilter offset:int limit:int hash:long = channels.ChannelParticipants; channels.getParticipant#a0ab6cc6 channel:InputChannel participant:InputPeer = channels.ChannelParticipant; @@ -1640,6 +1659,8 @@ channels.getInactiveChannels#11e831ee = messages.InactiveChats; channels.convertToGigagroup#b290c69 channel:InputChannel = Updates; channels.viewSponsoredMessage#beaedb94 channel:InputChannel random_id:bytes = Bool; channels.getSponsoredMessages#ec210fbf channel:InputChannel = messages.SponsoredMessages; +channels.getSendAs#dc770ee peer:InputPeer = channels.SendAsPeers; +channels.deleteParticipantHistory#367544db channel:InputChannel participant:InputPeer = messages.AffectedHistory; bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON; bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool; @@ -1707,4 +1728,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; -// LAYER 134 +// LAYER 135 diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 6b9c74065..9e4755846 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="3.3.0.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index a30d9f00b..3e06cabaf 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 3,2,5,0 - PRODUCTVERSION 3,2,5,0 + FILEVERSION 3,3,0,0 + PRODUCTVERSION 3,3,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "3.2.5.0" + VALUE "FileVersion", "3.3.0.0" VALUE "LegalCopyright", "Copyright (C) 2014-2021" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "3.2.5.0" + VALUE "ProductVersion", "3.3.0.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index abb519a32..7e621db0b 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 3,2,5,0 - PRODUCTVERSION 3,2,5,0 + FILEVERSION 3,3,0,0 + PRODUCTVERSION 3,3,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop Updater" - VALUE "FileVersion", "3.2.5.0" + VALUE "FileVersion", "3.3.0.0" VALUE "LegalCopyright", "Copyright (C) 2014-2021" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "3.2.5.0" + VALUE "ProductVersion", "3.3.0.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/_other/updater_linux.cpp b/Telegram/SourceFiles/_other/updater_linux.cpp index 596183361..871a103ea 100644 --- a/Telegram/SourceFiles/_other/updater_linux.cpp +++ b/Telegram/SourceFiles/_other/updater_linux.cpp @@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include #include -#include #include #include #include @@ -98,6 +97,11 @@ bool copyFile(const char *from, const char *to, bool writeprotected) { fclose(ffrom); return false; } + static const int BufSize = 65536; + char buf[BufSize]; + while (size_t size = fread(buf, 1, BufSize, ffrom)) { + fwrite(buf, 1, size, fto); + } struct stat fst; // from http://stackoverflow.com/questions/5486774/keeping-fileowner-and-permissions-after-copying-file-in-c //let's say this wont fail since you already worked OK on that fp @@ -106,33 +110,6 @@ bool copyFile(const char *from, const char *to, bool writeprotected) { fclose(fto); return false; } - - ssize_t copied = sendfile( - fileno(fto), - fileno(ffrom), - nullptr, - fst.st_size); - - if (copied == -1) { - writeLog( - "Copy by sendfile '%s' to '%s' failed, error: %d, fallback now.", - from, - to, - int(errno)); - static const int BufSize = 65536; - char buf[BufSize]; - while (size_t size = fread(buf, 1, BufSize, ffrom)) { - fwrite(buf, 1, size, fto); - } - } else { - writeLog( - "Copy by sendfile '%s' to '%s' done, size: %d, result: %d.", - from, - to, - int(fst.st_size), - int(copied)); - } - //update to the same uid/gid if (!writeprotected && fchown(fileno(fto), fst.st_uid, fst.st_gid) != 0) { fclose(ffrom); diff --git a/Telegram/SourceFiles/api/api_attached_stickers.cpp b/Telegram/SourceFiles/api/api_attached_stickers.cpp index 9a7e9d495..1aa455963 100644 --- a/Telegram/SourceFiles/api/api_attached_stickers.cpp +++ b/Telegram/SourceFiles/api/api_attached_stickers.cpp @@ -59,7 +59,7 @@ void AttachedStickers::request( strongController->show( Box(strongController, setId), Ui::LayerOption::KeepOther); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _requestId = 0; if (const auto strongController = weak.get()) { strongController->show( diff --git a/Telegram/SourceFiles/api/api_authorizations.cpp b/Telegram/SourceFiles/api/api_authorizations.cpp index d8e997c36..143166b78 100644 --- a/Telegram/SourceFiles/api/api_authorizations.cpp +++ b/Telegram/SourceFiles/api/api_authorizations.cpp @@ -10,12 +10,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "base/unixtime.h" #include "core/changelogs.h" +#include "core/application.h" #include "lang/lang_keys.h" namespace Api { namespace { constexpr auto TestApiId = 17349; +constexpr auto SnapApiId = 611335; constexpr auto DesktopApiId = 2040; Authorizations::Entry ParseEntry(const MTPDauthorization &data) { @@ -24,9 +26,11 @@ Authorizations::Entry ParseEntry(const MTPDauthorization &data) { result.hash = data.is_current() ? 0 : data.vhash().v; result.incomplete = data.is_password_pending(); - const auto apiId = data.vapi_id().v; + const auto apiId = result.apiId = data.vapi_id().v; const auto isTest = (apiId == TestApiId); - const auto isDesktop = (apiId == DesktopApiId) || isTest; + const auto isDesktop = (apiId == DesktopApiId) + || (apiId == SnapApiId) + || isTest; const auto appName = isDesktop ? QString("Telegram Desktop%1").arg(isTest ? " (GitHub)" : QString()) @@ -46,29 +50,26 @@ Authorizations::Entry ParseEntry(const MTPDauthorization &data) { return version; }(); - result.name = QString("%1%2").arg( - appName, - appVer.isEmpty() ? QString() : (' ' + appVer)); + result.name = result.hash + ? qs(data.vdevice_model()) + : Core::App().settings().deviceModel(); const auto country = qs(data.vcountry()); - const auto platform = qs(data.vplatform()); + //const auto platform = qs(data.vplatform()); //const auto &countries = countriesByISO2(); //const auto j = countries.constFind(country); //if (j != countries.cend()) { // country = QString::fromUtf8(j.value()->name); //} - + result.system = qs(data.vsystem_version()); + result.platform = qs(data.vplatform()); result.activeTime = data.vdate_active().v ? data.vdate_active().v : data.vdate_created().v; - result.info = QString("%1, %2%3").arg( - qs(data.vdevice_model()), - platform.isEmpty() ? QString() : platform + ' ', - qs(data.vsystem_version())); - result.ip = qs(data.vip()) - + (country.isEmpty() - ? QString() - : QString::fromUtf8(" \xe2\x80\x93 ") + country); + result.info = QString("%1%2").arg( + appName, + appVer.isEmpty() ? QString() : (' ' + appVer)); + result.ip = qs(data.vip()); if (!result.hash) { result.active = tr::lng_status_online(tr::now); } else { @@ -85,6 +86,7 @@ Authorizations::Entry ParseEntry(const MTPDauthorization &data) { result.active = lastDate.toString(cDateFormat()); } } + result.location = country; return result; } @@ -93,6 +95,23 @@ Authorizations::Entry ParseEntry(const MTPDauthorization &data) { Authorizations::Authorizations(not_null api) : _api(&api->instance()) { + Core::App().settings().deviceModelChanges( + ) | rpl::start_with_next([=](const QString &model) { + auto changed = false; + for (auto &entry : _list) { + if (!entry.hash) { + entry.name = model; + changed = true; + } + } + if (changed) { + _listChanges.fire({}); + } + }, _lifetime); + + if (Core::App().settings().disableCallsLegacy()) { + toggleCallsDisabledHere(true); + } } void Authorizations::reload() { @@ -105,6 +124,7 @@ void Authorizations::reload() { _requestId = 0; _lastReceived = crl::now(); result.match([&](const MTPDaccount_authorizations &auths) { + _ttlDays = auths.vauthorization_ttl_days().v; _list = ( auths.vauthorizations().v ) | ranges::views::transform([](const MTPAuthorization &d) { @@ -112,7 +132,7 @@ void Authorizations::reload() { }) | ranges::to; _listChanges.fire({}); }); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _requestId = 0; }).send(); } @@ -170,6 +190,55 @@ rpl::producer Authorizations::totalChanges() const { _listChanges.events() | rpl::map([=] { return total(); })); } +void Authorizations::updateTTL(int days) { + _api.request(_ttlRequestId).cancel(); + _ttlRequestId = _api.request(MTPaccount_SetAuthorizationTTL( + MTP_int(days) + )).done([=] { + _ttlRequestId = 0; + }).fail([=] { + _ttlRequestId = 0; + }).send(); + _ttlDays = days; +} + +rpl::producer Authorizations::ttlDays() const { + return _ttlDays.value() | rpl::filter(rpl::mappers::_1 != 0); +} + +void Authorizations::toggleCallsDisabled(uint64 hash, bool disabled) { + if (const auto sent = _toggleCallsDisabledRequests.take(hash)) { + _api.request(*sent).cancel(); + } + using Flag = MTPaccount_ChangeAuthorizationSettings::Flag; + const auto id = _api.request(MTPaccount_ChangeAuthorizationSettings( + MTP_flags(Flag::f_call_requests_disabled), + MTP_long(hash), + MTPBool(), // encrypted_requests_disabled + MTP_bool(disabled) + )).done([=] { + _toggleCallsDisabledRequests.remove(hash); + }).fail([=] { + _toggleCallsDisabledRequests.remove(hash); + }).send(); + _toggleCallsDisabledRequests.emplace(hash, id); + if (!hash) { + _callsDisabledHere = disabled; + } +} + +bool Authorizations::callsDisabledHere() const { + return _callsDisabledHere.current(); +} + +rpl::producer Authorizations::callsDisabledHereValue() const { + return _callsDisabledHere.value(); +} + +rpl::producer Authorizations::callsDisabledHereChanges() const { + return _callsDisabledHere.changes(); +} + int Authorizations::total() const { return ranges::count_if( _list, diff --git a/Telegram/SourceFiles/api/api_authorizations.h b/Telegram/SourceFiles/api/api_authorizations.h index 96e036199..f789740fc 100644 --- a/Telegram/SourceFiles/api/api_authorizations.h +++ b/Telegram/SourceFiles/api/api_authorizations.h @@ -21,8 +21,9 @@ public: uint64 hash = 0; bool incomplete = false; + int apiId = 0; TimeId activeTime = 0; - QString name, active, info, ip; + QString name, active, info, ip, location, system, platform; }; using List = std::vector; @@ -40,6 +41,17 @@ public: [[nodiscard]] int total() const; [[nodiscard]] rpl::producer totalChanges() const; + void updateTTL(int days); + [[nodiscard]] rpl::producer ttlDays() const; + + void toggleCallsDisabledHere(bool disabled) { + toggleCallsDisabled(0, disabled); + } + void toggleCallsDisabled(uint64 hash, bool disabled); + [[nodiscard]] bool callsDisabledHere() const; + [[nodiscard]] rpl::producer callsDisabledHereValue() const; + [[nodiscard]] rpl::producer callsDisabledHereChanges() const; + private: MTP::Sender _api; mtpRequestId _requestId = 0; @@ -47,7 +59,14 @@ private: List _list; rpl::event_stream<> _listChanges; + mtpRequestId _ttlRequestId = 0; + rpl::variable _ttlDays = 0; + + base::flat_map _toggleCallsDisabledRequests; + rpl::variable _callsDisabledHere; + crl::time _lastReceived = 0; + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/api/api_blocked_peers.cpp b/Telegram/SourceFiles/api/api_blocked_peers.cpp index f6edad324..c28b9c37f 100644 --- a/Telegram/SourceFiles/api/api_blocked_peers.cpp +++ b/Telegram/SourceFiles/api/api_blocked_peers.cpp @@ -80,7 +80,7 @@ void BlockedPeers::block(not_null peer) { } else if (_blockRequests.find(peer) == end(_blockRequests)) { const auto requestId = _api.request(MTPcontacts_Block( peer->input - )).done([=](const MTPBool &result) { + )).done([=] { _blockRequests.erase(peer); peer->setIsBlocked(true); if (_slice) { @@ -90,7 +90,7 @@ void BlockedPeers::block(not_null peer) { ++_slice->total; _changes.fire_copy(*_slice); } - }).fail([=](const MTP::Error &error) { + }).fail([=] { _blockRequests.erase(peer); }).send(); @@ -109,7 +109,7 @@ void BlockedPeers::unblock(not_null peer, Fn onDone) { } const auto requestId = _api.request(MTPcontacts_Unblock( peer->input - )).done([=](const MTPBool &result) { + )).done([=] { _blockRequests.erase(peer); peer->setIsBlocked(false); if (_slice) { @@ -128,7 +128,7 @@ void BlockedPeers::unblock(not_null peer, Fn onDone) { if (onDone) { onDone(); } - }).fail([=](const MTP::Error &error) { + }).fail([=] { _blockRequests.erase(peer); }).send(); _blockRequests.emplace(peer, requestId); @@ -165,7 +165,7 @@ void BlockedPeers::request(int offset, Fn onDone) { )).done([=](const MTPcontacts_Blocked &result) { _requestId = 0; onDone(TLToSlice(result, _session->data())); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _requestId = 0; }).send(); } diff --git a/Telegram/SourceFiles/api/api_bot.cpp b/Telegram/SourceFiles/api/api_bot.cpp index 1ff37debe..f51941396 100644 --- a/Telegram/SourceFiles/api/api_bot.cpp +++ b/Telegram/SourceFiles/api/api_bot.cpp @@ -35,8 +35,8 @@ void SendBotCallbackData( not_null item, int row, int column, - std::optional password = std::nullopt, - Fn handleError = nullptr) { + std::optional password = std::nullopt, + Fn handleError = nullptr) { if (!item->isRegular()) { return; } @@ -79,7 +79,7 @@ void SendBotCallbackData( history->peer->input, MTP_int(item->id), MTP_bytes(sendData), - password.value_or(MTP_inputCheckPasswordEmpty()) + password ? password->result : MTP_inputCheckPasswordEmpty() )).done([=](const MTPmessages_BotCallbackAnswer &result) { const auto item = owner->message(fullId); if (!item) { @@ -89,34 +89,41 @@ void SendBotCallbackData( button->requestId = 0; owner->requestItemRepaint(item); } - result.match([&](const MTPDmessages_botCallbackAnswer &data) { - if (const auto message = data.vmessage()) { - if (data.is_alert()) { - Ui::show(Box(qs(*message))); - } else { - if (withPassword) { - Ui::hideLayer(); - } - Ui::Toast::Show(qs(*message)); - } - } else if (const auto url = data.vurl()) { - const auto link = qs(*url); - if (!isGame) { - UrlClickHandler::Open(link); - return; - } - const auto scoreLink = AppendShareGameScoreUrl( - session, - link, - item->fullId()); - BotGameUrlClickHandler(bot, scoreLink).onClick({}); - session->sendProgressManager().update( - history, - Api::SendProgressType::PlayGame); - } else if (withPassword) { - Ui::hideLayer(); - } + const auto &data = result.match([]( + const auto &data) -> const MTPDmessages_botCallbackAnswer& { + return data; }); + const auto message = data.vmessage() + ? qs(*data.vmessage()) + : QString(); + const auto link = data.vurl() ? qs(*data.vurl()) : QString(); + const auto showAlert = data.is_alert(); + + if (!message.isEmpty()) { + if (showAlert) { + Ui::show(Box(message)); + } else { + if (withPassword) { + Ui::hideLayer(); + } + Ui::Toast::Show(message); + } + } else if (!link.isEmpty()) { + if (!isGame) { + UrlClickHandler::Open(link); + return; + } + const auto scoreLink = AppendShareGameScoreUrl( + session, + link, + item->fullId()); + BotGameUrlClickHandler(bot, scoreLink).onClick({}); + session->sendProgressManager().update( + history, + Api::SendProgressType::PlayGame); + } else if (withPassword) { + Ui::hideLayer(); + } }).fail([=](const MTP::Error &error) { const auto item = owner->message(fullId); if (!item) { @@ -128,7 +135,7 @@ void SendBotCallbackData( owner->requestItemRepaint(item); } if (handleError) { - handleError(error); + handleError(error.type()); } }).send(); @@ -144,7 +151,7 @@ void SendBotCallbackData( not_null item, int row, int column) { - SendBotCallbackData(item, row, column, MTP_inputCheckPasswordEmpty()); + SendBotCallbackData(item, row, column, std::nullopt); } void SendBotCallbackDataWithPassword( @@ -171,7 +178,7 @@ void SendBotCallbackDataWithPassword( return; } api->cloudPassword().reload(); - SendBotCallbackData(item, row, column, MTP_inputCheckPasswordEmpty(), [=](const MTP::Error &error) { + SendBotCallbackData(item, row, column, std::nullopt, [=](const QString &error) { auto box = PrePasswordErrorBox( error, session, @@ -213,7 +220,7 @@ void SendBotCallbackDataWithPassword( return; } if (const auto item = owner->message(fullId)) { - SendBotCallbackData(item, row, column, result.result, [=](const MTP::Error &error) { + SendBotCallbackData(item, row, column, result, [=](const QString &error) { if (*box) { (*box)->handleCustomCheckError(error); } diff --git a/Telegram/SourceFiles/api/api_chat_participants.cpp b/Telegram/SourceFiles/api/api_chat_participants.cpp new file mode 100644 index 000000000..c1fbc1797 --- /dev/null +++ b/Telegram/SourceFiles/api/api_chat_participants.cpp @@ -0,0 +1,676 @@ +/* +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_chat_participants.h" + +#include "apiwrap.h" +#include "boxes/add_contact_box.h" // ShowAddParticipantsError +#include "data/data_changes.h" +#include "data/data_channel.h" +#include "data/data_channel_admins.h" +#include "data/data_chat.h" +#include "data/data_histories.h" +#include "data/data_session.h" +#include "data/data_user.h" +#include "history/history.h" +#include "main/main_session.h" +#include "mtproto/mtproto_config.h" + +namespace Api { +namespace { + +using Members = ChatParticipants::Members; + +constexpr auto kSmallDelayMs = crl::time(5); + +// 1 second wait before reload members in channel after adding. +constexpr auto kReloadChannelMembersTimeout = 1000; + +// Max users in one super group invite request. +constexpr auto kMaxUsersPerInvite = 100; + +// How many messages from chat history server should forward to user, +// that was added to this chat. +constexpr auto kForwardMessagesOnAdd = 100; + +std::vector ParseList( + const ChatParticipants::TLMembers &data, + not_null peer) { + return ranges::views::all( + data.vparticipants().v + ) | ranges::views::transform([&](const MTPChannelParticipant &p) { + return ChatParticipant(p, peer); + }) | ranges::to_vector; +} + +void ApplyMegagroupAdmins(not_null channel, Members list) { + Expects(channel->isMegagroup()); + + const auto i = ranges::find_if(list, &Api::ChatParticipant::isCreator); + if (i != list.end()) { + i->tryApplyCreatorTo(channel); + } else { + channel->mgInfo->creator = nullptr; + channel->mgInfo->creatorRank = QString(); + } + + auto adding = base::flat_map(); + for (const auto &p : list) { + if (p.isUser()) { + adding.emplace(p.userId(), p.rank()); + } + } + if (channel->mgInfo->creator) { + adding.emplace( + peerToUser(channel->mgInfo->creator->id), + channel->mgInfo->creatorRank); + } + auto removing = channel->mgInfo->admins; + if (removing.empty() && adding.empty()) { + // Add some admin-placeholder so we don't DDOS + // server with admins list requests. + LOG(("API Error: Got empty admins list from server.")); + adding.emplace(0, QString()); + } + + Data::ChannelAdminChanges changes(channel); + for (const auto &[addingId, rank] : adding) { + if (!removing.remove(addingId)) { + changes.add(addingId, rank); + } + } + for (const auto &[removingId, rank] : removing) { + changes.remove(removingId); + } +} + +void RefreshChannelAdmins( + not_null channel, + Members participants) { + Data::ChannelAdminChanges changes(channel); + for (const auto &p : participants) { + if (p.isUser()) { + if (p.isCreatorOrAdmin()) { + p.tryApplyCreatorTo(channel); + changes.add(p.userId(), p.rank()); + } else { + changes.remove(p.userId()); + } + } + } +} + +void ApplyLastList( + not_null channel, + int availableCount, + Members list) { + channel->mgInfo->lastAdmins.clear(); + channel->mgInfo->lastRestricted.clear(); + channel->mgInfo->lastParticipants.clear(); + channel->mgInfo->lastParticipantsStatus = + MegagroupInfo::LastParticipantsUpToDate + | MegagroupInfo::LastParticipantsOnceReceived; + + auto botStatus = channel->mgInfo->botStatus; + for (const auto &p : list) { + const auto participant = channel->owner().peer(p.id()); + const auto user = participant->asUser(); + const auto adminRights = p.rights(); + const auto restrictedRights = p.restrictions(); + if (p.isCreator()) { + Assert(user != nullptr); + p.tryApplyCreatorTo(channel); + if (!channel->mgInfo->admins.empty()) { + Data::ChannelAdminChanges(channel).add(p.userId(), p.rank()); + } + } + if (user + && !base::contains(channel->mgInfo->lastParticipants, user)) { + channel->mgInfo->lastParticipants.push_back(user); + if (adminRights.flags) { + channel->mgInfo->lastAdmins.emplace( + user, + MegagroupInfo::Admin{ adminRights, p.canBeEdited() }); + } else if (restrictedRights.flags) { + channel->mgInfo->lastRestricted.emplace( + user, + MegagroupInfo::Restricted{ restrictedRights }); + } + if (user->isBot()) { + channel->mgInfo->bots.insert(user); + if ((channel->mgInfo->botStatus != 0) + && (channel->mgInfo->botStatus < 2)) { + channel->mgInfo->botStatus = 2; + } + } + } + } + // + // getParticipants(Recent) sometimes can't return all members, + // only some last subset, size of this subset is availableCount. + // + // So both list size and availableCount have nothing to do with + // the full supergroup members count. + // + //if (list.isEmpty()) { + // channel->setMembersCount(channel->mgInfo->lastParticipants.size()); + //} else { + // channel->setMembersCount(availableCount); + //} + channel->session().changes().peerUpdated( + channel, + (Data::PeerUpdate::Flag::Members | Data::PeerUpdate::Flag::Admins)); + + channel->mgInfo->botStatus = botStatus; + channel->session().changes().peerUpdated( + channel, + Data::PeerUpdate::Flag::FullInfo); +} + +void ApplyBotsList( + not_null channel, + int availableCount, + Members list) { + const auto history = channel->owner().historyLoaded(channel); + channel->mgInfo->bots.clear(); + channel->mgInfo->botStatus = -1; + + auto needBotsInfos = false; + auto botStatus = channel->mgInfo->botStatus; + auto keyboardBotFound = !history || !history->lastKeyboardFrom; + for (const auto &p : list) { + const auto participant = channel->owner().peer(p.id()); + const auto user = participant->asUser(); + if (user && user->isBot()) { + channel->mgInfo->bots.insert(user); + botStatus = 2;// (botStatus > 0/* || !i.key()->botInfo->readsAllHistory*/) ? 2 : 1; + if (!user->botInfo->inited) { + needBotsInfos = true; + } + } + if (!keyboardBotFound + && participant->id == history->lastKeyboardFrom) { + keyboardBotFound = true; + } + } + if (needBotsInfos) { + channel->session().api().requestFullPeer(channel); + } + if (!keyboardBotFound) { + history->clearLastKeyboard(); + } + + channel->mgInfo->botStatus = botStatus; + channel->session().changes().peerUpdated( + channel, + Data::PeerUpdate::Flag::FullInfo); +} + +} // namespace + +ChatParticipant::ChatParticipant( + const MTPChannelParticipant &p, + not_null peer) { + _peer = p.match([](const MTPDchannelParticipantBanned &data) { + return peerFromMTP(data.vpeer()); + }, [](const MTPDchannelParticipantLeft &data) { + return peerFromMTP(data.vpeer()); + }, [](const auto &data) { + return peerFromUser(data.vuser_id()); + }); + + p.match([&](const MTPDchannelParticipantCreator &data) { + _canBeEdited = (peer->session().userPeerId() == _peer); + _type = Type::Creator; + _rights = ChatAdminRightsInfo(data.vadmin_rights()); + _rank = qs(data.vrank().value_or_empty()); + }, [&](const MTPDchannelParticipantAdmin &data) { + _canBeEdited = data.is_can_edit(); + _type = Type::Admin; + _rank = qs(data.vrank().value_or_empty()); + _rights = ChatAdminRightsInfo(data.vadmin_rights()); + _by = peerToUser(peerFromUser(data.vpromoted_by())); + }, [&](const MTPDchannelParticipantSelf &data) { + _type = Type::Member; + _by = peerToUser(peerFromUser(data.vinviter_id())); + }, [&](const MTPDchannelParticipant &data) { + _type = Type::Member; + }, [&](const MTPDchannelParticipantBanned &data) { + _restrictions = ChatRestrictionsInfo(data.vbanned_rights()); + _by = peerToUser(peerFromUser(data.vkicked_by())); + + _type = (_restrictions.flags & ChatRestriction::ViewMessages) + ? Type::Banned + : Type::Restricted; + }, [&](const MTPDchannelParticipantLeft &data) { + _type = Type::Left; + }); +} + +ChatParticipant::ChatParticipant( + Type type, + PeerId peerId, + UserId by, + ChatRestrictionsInfo restrictions, + ChatAdminRightsInfo rights, + bool canBeEdited, + QString rank) +: _type(type) +, _peer(peerId) +, _by(by) +, _canBeEdited(canBeEdited) +, _rank(rank) +, _restrictions(std::move(restrictions)) +, _rights(std::move(rights)) { +} + +void ChatParticipant::tryApplyCreatorTo( + not_null channel) const { + if (isCreator() && isUser()) { + if (const auto info = channel->mgInfo.get()) { + info->creator = channel->owner().userLoaded(userId()); + info->creatorRank = rank(); + } + } +} + +bool ChatParticipant::isUser() const { + return peerIsUser(_peer); +} + +bool ChatParticipant::isCreator() const { + return _type == Type::Creator; +} + +bool ChatParticipant::isCreatorOrAdmin() const { + return _type == Type::Creator || _type == Type::Admin; +} + +bool ChatParticipant::isKicked() const { + return _type == Type::Banned; +} + +bool ChatParticipant::canBeEdited() const { + return _canBeEdited; +} + +UserId ChatParticipant::by() const { + return _by; +} + +PeerId ChatParticipant::id() const { + return _peer; +} + +UserId ChatParticipant::userId() const { + return peerToUser(_peer); +} + +ChatRestrictionsInfo ChatParticipant::restrictions() const { + return _restrictions; +} + +ChatAdminRightsInfo ChatParticipant::rights() const { + return _rights; +} + +ChatParticipant::Type ChatParticipant::type() const { + return _type; +} + +QString ChatParticipant::rank() const { + return _rank; +} + +ChatParticipants::ChatParticipants(not_null api) +: _api(&api->instance()) { +} + +void ChatParticipants::requestForAdd( + not_null channel, + Fn callback) { + Expects(callback != nullptr); + _forAdd.callback = std::move(callback); + if (_forAdd.channel == channel) { + return; + } + _api.request(base::take(_forAdd.requestId)).cancel(); + + const auto offset = 0; + const auto participantsHash = uint64(0); + + _forAdd.channel = channel; + _forAdd.requestId = _api.request(MTPchannels_GetParticipants( + channel->inputChannel, + MTP_channelParticipantsRecent(), + MTP_int(offset), + MTP_int(channel->session().serverConfig().chatSizeMax), + MTP_long(participantsHash) + )).done([=](const MTPchannels_ChannelParticipants &result) { + result.match([&](const MTPDchannels_channelParticipants &data) { + base::take(_forAdd).callback(data); + }, [&](const MTPDchannels_channelParticipantsNotModified &) { + base::take(_forAdd); + LOG(("API Error: " + "channels.channelParticipantsNotModified received!")); + }); + }).fail([=] { + base::take(_forAdd); + }).send(); +} + +void ChatParticipants::requestLast(not_null channel) { + if (!channel->isMegagroup() + || _participantsRequests.contains(channel)) { + return; + } + + const auto offset = 0; + const auto participantsHash = uint64(0); + const auto requestId = _api.request(MTPchannels_GetParticipants( + channel->inputChannel, + MTP_channelParticipantsRecent(), + MTP_int(offset), + MTP_int(channel->session().serverConfig().chatSizeMax), + MTP_long(participantsHash) + )).done([=](const MTPchannels_ChannelParticipants &result) { + _participantsRequests.remove(channel); + + result.match([&](const MTPDchannels_channelParticipants &data) { + const auto &[availableCount, list] = Parse(channel, data); + ApplyLastList(channel, availableCount, list); + }, [](const MTPDchannels_channelParticipantsNotModified &) { + LOG(("API Error: " + "channels.channelParticipantsNotModified received!")); + }); + }).fail([this, channel] { + _participantsRequests.remove(channel); + }).send(); + + _participantsRequests[channel] = requestId; +} + +void ChatParticipants::requestBots(not_null channel) { + if (!channel->isMegagroup() || _botsRequests.contains(channel)) { + return; + } + + const auto offset = 0; + const auto participantsHash = uint64(0); + const auto requestId = _api.request(MTPchannels_GetParticipants( + channel->inputChannel, + MTP_channelParticipantsBots(), + MTP_int(offset), + MTP_int(channel->session().serverConfig().chatSizeMax), + MTP_long(participantsHash) + )).done([=](const MTPchannels_ChannelParticipants &result) { + _botsRequests.remove(channel); + result.match([&](const MTPDchannels_channelParticipants &data) { + const auto &[availableCount, list] = Parse(channel, data); + ApplyBotsList(channel, availableCount, list); + }, [](const MTPDchannels_channelParticipantsNotModified &) { + LOG(("API Error: " + "channels.channelParticipantsNotModified received!")); + }); + }).fail([=] { + _botsRequests.remove(channel); + }).send(); + + _botsRequests[channel] = requestId; +} + +void ChatParticipants::requestAdmins(not_null channel) { + if (!channel->isMegagroup() || _adminsRequests.contains(channel)) { + return; + } + + const auto offset = 0; + const auto participantsHash = uint64(0); + const auto requestId = _api.request(MTPchannels_GetParticipants( + channel->inputChannel, + MTP_channelParticipantsAdmins(), + MTP_int(offset), + MTP_int(channel->session().serverConfig().chatSizeMax), + MTP_long(participantsHash) + )).done([=](const MTPchannels_ChannelParticipants &result) { + _adminsRequests.remove(channel); + result.match([&](const MTPDchannels_channelParticipants &data) { + channel->owner().processUsers(data.vusers()); + ApplyMegagroupAdmins(channel, ParseList(data, channel)); + }, [](const MTPDchannels_channelParticipantsNotModified &) { + LOG(("API Error: " + "channels.channelParticipantsNotModified received!")); + }); + }).fail([=] { + _adminsRequests.remove(channel); + }).send(); + + _adminsRequests[channel] = requestId; +} + +void ChatParticipants::requestCountDelayed( + not_null channel) { + _participantsCountRequestTimer.call( + kReloadChannelMembersTimeout, + [=] { channel->updateFullForced(); }); +} + +void ChatParticipants::add( + not_null peer, + const std::vector> &users, + Fn done) { + if (const auto chat = peer->asChat()) { + for (const auto &user : users) { + _api.request(MTPmessages_AddChatUser( + chat->inputChat, + user->inputUser, + MTP_int(kForwardMessagesOnAdd) + )).done([=](const MTPUpdates &result) { + chat->session().api().applyUpdates(result); + if (done) done(true); + }).fail([=](const MTP::Error &error) { + ShowAddParticipantsError(error.type(), peer, { 1, user }); + if (done) done(false); + }).afterDelay(kSmallDelayMs).send(); + } + } else if (const auto channel = peer->asChannel()) { + const auto hasBot = ranges::any_of(users, &UserData::isBot); + if (!peer->isMegagroup() && hasBot) { + ShowAddParticipantsError("USER_BOT", peer, users); + return; + } + auto list = QVector(); + list.reserve(std::min(int(users.size()), int(kMaxUsersPerInvite))); + const auto send = [&] { + const auto callback = base::take(done); + _api.request(MTPchannels_InviteToChannel( + channel->inputChannel, + MTP_vector(list) + )).done([=](const MTPUpdates &result) { + channel->session().api().applyUpdates(result); + requestCountDelayed(channel); + if (callback) callback(true); + }).fail([=](const MTP::Error &error) { + ShowAddParticipantsError(error.type(), peer, users); + if (callback) callback(false); + }).afterDelay(kSmallDelayMs).send(); + }; + for (const auto &user : users) { + list.push_back(user->inputUser); + if (list.size() == kMaxUsersPerInvite) { + send(); + list.clear(); + } + } + if (!list.empty()) { + send(); + } + } else { + Unexpected("User in ChatParticipants::add."); + } +} + +ChatParticipants::Parsed ChatParticipants::Parse( + not_null channel, + const TLMembers &data) { + channel->owner().processUsers(data.vusers()); + channel->owner().processChats(data.vchats()); + auto list = ParseList(data, channel); + if (channel->mgInfo) { + RefreshChannelAdmins(channel, list); + } + return { data.vcount().v, std::move(list) }; +} + +ChatParticipants::Parsed ChatParticipants::ParseRecent( + not_null channel, + const TLMembers &data) { + const auto result = Parse(channel, data); + const auto applyLast = channel->isMegagroup() + && (channel->mgInfo->lastParticipants.size() <= result.list.size()); + if (applyLast) { + ApplyLastList(channel, result.availableCount, result.list); + } + return result; +} + +void ChatParticipants::requestSelf(not_null channel) { + if (_selfParticipantRequests.contains(channel)) { + return; + } + + const auto finalize = [=]( + UserId inviter = -1, + TimeId inviteDate = 0, + bool inviteViaRequest = false) { + channel->inviter = inviter; + channel->inviteDate = inviteDate; + channel->inviteViaRequest = inviteViaRequest; + if (const auto history = channel->owner().historyLoaded(channel)) { + if (history->lastMessageKnown()) { + history->checkLocalMessages(); + history->owner().sendHistoryChangeNotifications(); + } else { + history->owner().histories().requestDialogEntry(history); + } + } + }; + _selfParticipantRequests.emplace(channel); + _api.request(MTPchannels_GetParticipant( + channel->inputChannel, + MTP_inputPeerSelf() + )).done([=](const MTPchannels_ChannelParticipant &result) { + _selfParticipantRequests.erase(channel); + result.match([&](const MTPDchannels_channelParticipant &data) { + channel->owner().processUsers(data.vusers()); + + const auto &participant = data.vparticipant(); + participant.match([&](const MTPDchannelParticipantSelf &data) { + finalize( + data.vinviter_id().v, + data.vdate().v, + data.is_via_request()); + }, [&](const MTPDchannelParticipantCreator &) { + if (channel->mgInfo) { + channel->mgInfo->creator = channel->session().user(); + } + finalize(channel->session().userId(), channel->date); + }, [&](const MTPDchannelParticipantAdmin &data) { + const auto inviter = data.is_self() + ? data.vinviter_id().value_or(-1) + : -1; + finalize(inviter, data.vdate().v); + }, [&](const MTPDchannelParticipantBanned &data) { + LOG(("API Error: Got self banned participant.")); + finalize(); + }, [&](const MTPDchannelParticipant &data) { + LOG(("API Error: Got self regular participant.")); + finalize(); + }, [&](const MTPDchannelParticipantLeft &data) { + LOG(("API Error: Got self left participant.")); + finalize(); + }); + }); + }).fail([=](const MTP::Error &error) { + _selfParticipantRequests.erase(channel); + if (error.type() == qstr("CHANNEL_PRIVATE")) { + channel->privateErrorReceived(); + } + finalize(); + }).afterDelay(kSmallDelayMs).send(); +} + +void ChatParticipants::kick( + not_null chat, + not_null participant) { + Expects(participant->isUser()); + + _api.request(MTPmessages_DeleteChatUser( + MTP_flags(0), + chat->inputChat, + participant->asUser()->inputUser + )).done([=](const MTPUpdates &result) { + chat->session().api().applyUpdates(result); + }).send(); +} + +void ChatParticipants::kick( + not_null channel, + not_null participant, + ChatRestrictionsInfo currentRights) { + const auto kick = KickRequest(channel, participant); + if (_kickRequests.contains(kick)) return; + + const auto rights = ChannelData::KickedRestrictedRights(participant); + const auto requestId = _api.request(MTPchannels_EditBanned( + channel->inputChannel, + participant->input, + MTP_chatBannedRights( + MTP_flags( + MTPDchatBannedRights::Flags::from_raw(uint32(rights.flags))), + MTP_int(rights.until)) + )).done([=](const MTPUpdates &result) { + channel->session().api().applyUpdates(result); + + _kickRequests.remove(KickRequest(channel, participant)); + channel->applyEditBanned(participant, currentRights, rights); + }).fail([this, kick] { + _kickRequests.remove(kick); + }).send(); + + _kickRequests.emplace(kick, requestId); +} + +void ChatParticipants::unblock( + not_null channel, + not_null participant) { + const auto kick = KickRequest(channel, participant); + if (_kickRequests.contains(kick)) { + return; + } + + const auto requestId = _api.request(MTPchannels_EditBanned( + channel->inputChannel, + participant->input, + MTP_chatBannedRights(MTP_flags(0), MTP_int(0)) + )).done([=](const MTPUpdates &result) { + channel->session().api().applyUpdates(result); + + _kickRequests.remove(KickRequest(channel, participant)); + if (channel->kickedCount() > 0) { + channel->setKickedCount(channel->kickedCount() - 1); + } else { + channel->updateFullForced(); + } + }).fail([=] { + _kickRequests.remove(kick); + }).send(); + + _kickRequests.emplace(kick, requestId); +} + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_chat_participants.h b/Telegram/SourceFiles/api/api_chat_participants.h new file mode 100644 index 000000000..70e5eafa5 --- /dev/null +++ b/Telegram/SourceFiles/api/api_chat_participants.h @@ -0,0 +1,142 @@ +/* +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 "data/data_chat_participant_status.h" +#include "mtproto/sender.h" +#include "base/timer.h" + +class ApiWrap; +class ChannelData; + +namespace Api { + +class ChatParticipant final { +public: + enum class Type { + Creator, + Admin, + Member, + Restricted, + Left, + Banned, + }; + + explicit ChatParticipant( + const MTPChannelParticipant &p, + not_null peer); + ChatParticipant( + Type type, + PeerId peerId, + UserId by, + ChatRestrictionsInfo restrictions, + ChatAdminRightsInfo rights, + bool canBeEdited = false, + QString rank = QString()); + + bool isUser() const; + bool isCreator() const; + bool isCreatorOrAdmin() const; + bool isKicked() const; + bool canBeEdited() const; + + UserId by() const; + PeerId id() const; + UserId userId() const; + + ChatRestrictionsInfo restrictions() const; + ChatAdminRightsInfo rights() const; + + Type type() const; + QString rank() const; + + void tryApplyCreatorTo(not_null channel) const; +private: + Type _type = Type::Member; + + PeerId _peer; + UserId _by; // Banned/Restricted/Promoted. + + bool _canBeEdited = false; + + QString _rank; + + ChatRestrictionsInfo _restrictions; + ChatAdminRightsInfo _rights; +}; + +class ChatParticipants final { +public: + struct Parsed { + const int availableCount; + const std::vector list; + }; + + using TLMembers = MTPDchannels_channelParticipants; + using Members = const std::vector &; + explicit ChatParticipants(not_null api); + + void requestLast(not_null channel); + void requestBots(not_null channel); + void requestAdmins(not_null channel); + void requestCountDelayed(not_null channel); + + static Parsed Parse( + not_null channel, + const TLMembers &data); + static Parsed ParseRecent( + not_null channel, + const TLMembers &data); + void add( + not_null peer, + const std::vector> &users, + Fn done = nullptr); + + void requestSelf(not_null channel); + + void requestForAdd( + not_null channel, + Fn callback); + + void kick( + not_null chat, + not_null participant); + void kick( + not_null channel, + not_null participant, + ChatRestrictionsInfo currentRights); + void unblock( + not_null channel, + not_null participant); + +private: + MTP::Sender _api; + + using PeerRequests = base::flat_map; + + PeerRequests _participantsRequests; + PeerRequests _botsRequests; + PeerRequests _adminsRequests; + base::DelayedCallTimer _participantsCountRequestTimer; + + struct { + ChannelData *channel = nullptr; + mtpRequestId requestId = 0; + Fn callback; + } _forAdd; + + base::flat_set> _selfParticipantRequests; + + using KickRequest = std::pair< + not_null, + not_null>; + base::flat_map _kickRequests; + +}; + +} // namespace Api diff --git a/Telegram/SourceFiles/api/api_cloud_password.cpp b/Telegram/SourceFiles/api/api_cloud_password.cpp index b2f36d0b9..03ca032ad 100644 --- a/Telegram/SourceFiles/api/api_cloud_password.cpp +++ b/Telegram/SourceFiles/api/api_cloud_password.cpp @@ -36,17 +36,17 @@ void CloudPassword::reload() { } _stateChanges.fire_copy(*_state); }); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _requestId = 0; }).send(); } void CloudPassword::clearUnconfirmedPassword() { _requestId = _api.request(MTPaccount_CancelPasswordEmail( - )).done([=](const MTPBool &result) { + )).done([=] { _requestId = 0; reload(); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _requestId = 0; reload(); }).send(); diff --git a/Telegram/SourceFiles/api/api_common.h b/Telegram/SourceFiles/api/api_common.h index 2d69e4844..e8cab1fdb 100644 --- a/Telegram/SourceFiles/api/api_common.h +++ b/Telegram/SourceFiles/api/api_common.h @@ -12,6 +12,7 @@ class History; namespace Api { struct SendOptions { + PeerData *sendAs = nullptr; TimeId scheduled = 0; bool silent = false; bool handleSupportSwitch = false; @@ -26,7 +27,11 @@ enum class SendType { }; struct SendAction { - explicit SendAction(not_null history) : history(history) { + explicit SendAction( + not_null history, + SendOptions options = SendOptions()) + : history(history) + , options(options) { } not_null history; @@ -38,7 +43,7 @@ struct SendAction { }; struct MessageToSend { - explicit MessageToSend(not_null history) : action(history) { + explicit MessageToSend(SendAction action) : action(action) { } SendAction action; @@ -46,4 +51,10 @@ struct MessageToSend { WebPageId webPageId = 0; }; +struct RemoteFileInfo { + MTPInputFile file; + std::optional thumb; + std::vector attachedStickers; +}; + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_confirm_phone.cpp b/Telegram/SourceFiles/api/api_confirm_phone.cpp index cfe13eb07..0db192021 100644 --- a/Telegram/SourceFiles/api/api_confirm_phone.cpp +++ b/Telegram/SourceFiles/api/api_confirm_phone.cpp @@ -30,7 +30,7 @@ void ConfirmPhone::resolve( } _sendRequestId = _api.request(MTPaccount_SendConfirmPhoneCode( MTP_string(hash), - MTP_codeSettings(MTP_flags(0)) + MTP_codeSettings(MTP_flags(0), MTP_vector()) )).done([=](const MTPauth_SentCode &result) { _sendRequestId = 0; @@ -46,6 +46,9 @@ void ConfirmPhone::resolve( }, [&](const MTPDauth_sentCodeTypeFlashCall &data) { LOG(("Error: should not be flashcall!")); return 0; + }, [&](const MTPDauth_sentCodeTypeMissedCall &data) { + LOG(("Error: should not be missedcall!")); + return 0; }); const auto phoneHash = qs(data.vphone_code_hash()); const auto timeout = [&]() -> std::optional { @@ -80,7 +83,7 @@ void ConfirmPhone::resolve( _checkRequestId = _api.request(MTPaccount_ConfirmPhone( MTP_string(phoneHash), MTP_string(code) - )).done([=](const MTPBool &result) { + )).done([=] { _checkRequestId = 0; controller->show( Box( diff --git a/Telegram/SourceFiles/api/api_editing.cpp b/Telegram/SourceFiles/api/api_editing.cpp index 2f083f389..46a120c54 100644 --- a/Telegram/SourceFiles/api/api_editing.cpp +++ b/Telegram/SourceFiles/api/api_editing.cpp @@ -172,33 +172,28 @@ void RescheduleMessage( void EditMessageWithUploadedDocument( HistoryItem *item, - const MTPInputFile &file, - const std::optional &thumb, - SendOptions options, - std::vector attachedStickers) { + RemoteFileInfo info, + SendOptions options) { if (!item || !item->media() || !item->media()->document()) { return; } - const auto media = PrepareUploadedDocument( + EditMessageWithUploadedMedia( item, - file, - thumb, - std::move(attachedStickers)); - EditMessageWithUploadedMedia(item, options, media); + options, + PrepareUploadedDocument(item, std::move(info))); } void EditMessageWithUploadedPhoto( HistoryItem *item, - const MTPInputFile &file, - SendOptions options, - std::vector attachedStickers) { + RemoteFileInfo info, + SendOptions options) { if (!item || !item->media() || !item->media()->photo()) { return; } - const auto media = PrepareUploadedPhoto( - file, - std::move(attachedStickers)); - EditMessageWithUploadedMedia(item, options, media); + EditMessageWithUploadedMedia( + item, + options, + PrepareUploadedPhoto(std::move(info))); } mtpRequestId EditCaption( diff --git a/Telegram/SourceFiles/api/api_editing.h b/Telegram/SourceFiles/api/api_editing.h index 87200cde2..4a80e9dae 100644 --- a/Telegram/SourceFiles/api/api_editing.h +++ b/Telegram/SourceFiles/api/api_editing.h @@ -16,6 +16,7 @@ class Error; namespace Api { struct SendOptions; +struct RemoteFileInfo; const auto kDefaultEditMessagesErrors = { u"MESSAGE_ID_INVALID"_q, @@ -29,16 +30,13 @@ void RescheduleMessage( void EditMessageWithUploadedDocument( HistoryItem *item, - const MTPInputFile &file, - const std::optional &thumb, - SendOptions options, - std::vector attachedStickers); + RemoteFileInfo info, + SendOptions options); void EditMessageWithUploadedPhoto( HistoryItem *item, - const MTPInputFile &file, - SendOptions options, - std::vector attachedStickers); + RemoteFileInfo info, + SendOptions options); mtpRequestId EditCaption( not_null item, diff --git a/Telegram/SourceFiles/api/api_global_privacy.cpp b/Telegram/SourceFiles/api/api_global_privacy.cpp index 3d2c1d462..c940dbe86 100644 --- a/Telegram/SourceFiles/api/api_global_privacy.cpp +++ b/Telegram/SourceFiles/api/api_global_privacy.cpp @@ -33,7 +33,7 @@ void GlobalPrivacy::reload(Fn callback) { for (const auto &callback : base::take(_callbacks)) { callback(); } - }).fail([=](const MTP::Error &error) { + }).fail([=] { _requestId = 0; for (const auto &callback : base::take(_callbacks)) { callback(); @@ -86,7 +86,7 @@ void GlobalPrivacy::update(bool archiveAndMute) { )).done([=](const MTPGlobalPrivacySettings &result) { _requestId = 0; apply(result); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _requestId = 0; }).send(); _archiveAndMute = archiveAndMute; diff --git a/Telegram/SourceFiles/api/api_invite_links.cpp b/Telegram/SourceFiles/api/api_invite_links.cpp index d3d1d4c85..3aad8c14e 100644 --- a/Telegram/SourceFiles/api/api_invite_links.cpp +++ b/Telegram/SourceFiles/api/api_invite_links.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "api/api_invite_links.h" +#include "api/api_chat_participants.h" #include "data/data_peer.h" #include "data/data_user.h" #include "data/data_chat.h" @@ -127,7 +128,7 @@ void InviteLinks::performCreate( callback(link); } } - }).fail([=](const MTP::Error &error) { + }).fail([=] { _createCallbacks.erase(peer); }).send(); } @@ -312,7 +313,7 @@ void InviteLinks::performEdit( prepend(peer, admin, data.vnew_invite()); } }); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _editCallbacks.erase(key); }).send(); } @@ -362,7 +363,7 @@ void InviteLinks::destroy( _api->request(MTPmessages_DeleteExportedChatInvite( peer->input, MTP_string(link) - )).done([=](const MTPBool &result) { + )).done([=] { const auto callbacks = _deleteCallbacks.take(key); if (callbacks) { for (const auto &callback : *callbacks) { @@ -374,7 +375,7 @@ void InviteLinks::destroy( .admin = admin, .was = key.link, }); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _deleteCallbacks.erase(key); }).send(); } @@ -397,14 +398,13 @@ void InviteLinks::destroyAllRevoked( _api->request(MTPmessages_DeleteRevokedExportedChatInvites( peer->input, admin->inputUser - )).done([=](const MTPBool &result) { + )).done([=] { if (const auto callbacks = _deleteRevokedCallbacks.take(peer)) { for (const auto &callback : *callbacks) { callback(); } } _allRevokedDestroyed.fire({ peer, admin }); - }).fail([=](const MTP::Error &error) { }).send(); } @@ -445,7 +445,7 @@ void InviteLinks::requestMyLinks(not_null peer) { i->second.count = std::max(slice.count, int(existing.size())); } notify(peer); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _firstSliceRequests.remove(peer); }).send(); _firstSliceRequests.emplace(peer, requestId); @@ -478,7 +478,7 @@ void InviteLinks::processRequest( ++chat->count; } } else if (const auto channel = peer->asChannel()) { - _api->requestParticipantsCountDelayed(channel); + _api->chatParticipants().requestCountDelayed(channel); } _api->applyUpdates(result); if (link.isEmpty() && approved) { @@ -506,7 +506,7 @@ void InviteLinks::processRequest( done(); } } - }).fail([=](const MTP::Error &error) { + }).fail([=] { if (const auto callbacks = _processRequests.take({ peer, user })) { if (const auto &fail = callbacks->fail) { fail(); @@ -607,7 +607,7 @@ void InviteLinks::requestJoinedFirstSlice(LinkKey key) { _firstJoinedRequests.remove(key); _firstJoined[key] = ParseJoinedByLinkSlice(key.peer, result); _joinedFirstSliceLoaded.fire_copy(key); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _firstJoinedRequests.remove(key); }).send(); _firstJoinedRequests.emplace(key, requestId); @@ -770,7 +770,7 @@ void InviteLinks::requestMoreLinks( MTP_int(kPerPage) )).done([=](const MTPmessages_ExportedChatInvites &result) { done(parseSlice(peer, result)); - }).fail([=](const MTP::Error &error) { + }).fail([=] { done(Links()); }).send(); } diff --git a/Telegram/SourceFiles/api/api_media.cpp b/Telegram/SourceFiles/api/api_media.cpp index 517389d13..6038060f7 100644 --- a/Telegram/SourceFiles/api/api_media.cpp +++ b/Telegram/SourceFiles/api/api_media.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "api/api_media.h" +#include "api/api_common.h" #include "data/data_document.h" #include "data/stickers/data_stickers_set.h" #include "history/history_item.h" @@ -74,41 +75,39 @@ MTPVector ComposeSendingDocumentAttributes( } // namespace -MTPInputMedia PrepareUploadedPhoto( - const MTPInputFile &file, - std::vector attachedStickers) { - const auto flags = attachedStickers.empty() +MTPInputMedia PrepareUploadedPhoto(RemoteFileInfo info) { + const auto flags = info.attachedStickers.empty() ? MTPDinputMediaUploadedPhoto::Flags(0) : MTPDinputMediaUploadedPhoto::Flag::f_stickers; return MTP_inputMediaUploadedPhoto( MTP_flags(flags), - file, - MTP_vector(ranges::to(attachedStickers)), + info.file, + MTP_vector( + ranges::to(info.attachedStickers)), MTP_int(0)); } MTPInputMedia PrepareUploadedDocument( not_null item, - const MTPInputFile &file, - const std::optional &thumb, - std::vector attachedStickers) { + RemoteFileInfo info) { if (!item || !item->media() || !item->media()->document()) { return MTP_inputMediaEmpty(); } const auto emptyFlag = MTPDinputMediaUploadedDocument::Flags(0); using DocFlags = MTPDinputMediaUploadedDocument::Flag; const auto flags = emptyFlag - | (thumb ? DocFlags::f_thumb : emptyFlag) + | (info.thumb ? DocFlags::f_thumb : emptyFlag) | (item->groupId() ? DocFlags::f_nosound_video : emptyFlag) - | (attachedStickers.empty() ? DocFlags::f_stickers : emptyFlag); + | (info.attachedStickers.empty() ? DocFlags::f_stickers : emptyFlag); const auto document = item->media()->document(); return MTP_inputMediaUploadedDocument( MTP_flags(flags), - file, - thumb.value_or(MTPInputFile()), + info.file, + info.thumb.value_or(MTPInputFile()), MTP_string(document->mimeString()), ComposeSendingDocumentAttributes(document), - MTP_vector(ranges::to(attachedStickers)), + MTP_vector( + ranges::to(info.attachedStickers)), MTP_int(0)); } diff --git a/Telegram/SourceFiles/api/api_media.h b/Telegram/SourceFiles/api/api_media.h index d9dbaefb2..b9f744856 100644 --- a/Telegram/SourceFiles/api/api_media.h +++ b/Telegram/SourceFiles/api/api_media.h @@ -11,15 +11,13 @@ class HistoryItem; namespace Api { -MTPInputMedia PrepareUploadedPhoto( - const MTPInputFile &file, - std::vector attachedStickers); +struct RemoteFileInfo; + +MTPInputMedia PrepareUploadedPhoto(RemoteFileInfo info); MTPInputMedia PrepareUploadedDocument( not_null item, - const MTPInputFile &file, - const std::optional &thumb, - std::vector attachedStickers); + RemoteFileInfo info); bool HasAttachedStickers(MTPInputMedia media); diff --git a/Telegram/SourceFiles/api/api_peer_photo.cpp b/Telegram/SourceFiles/api/api_peer_photo.cpp index 1d75b38dc..28f47583f 100644 --- a/Telegram/SourceFiles/api/api_peer_photo.cpp +++ b/Telegram/SourceFiles/api/api_peer_photo.cpp @@ -104,8 +104,8 @@ PeerPhoto::PeerPhoto(not_null api) // You can't use _session->lifetime() in the constructor, // only queued, because it is not constructed yet. _session->uploader().photoReady( - ) | rpl::start_with_next([=](const Storage::UploadedPhoto &data) { - ready(data.fullId, data.file); + ) | rpl::start_with_next([=](const Storage::UploadedMedia &data) { + ready(data.fullId, data.info.file); }, _session->lifetime()); }); } diff --git a/Telegram/SourceFiles/api/api_polls.cpp b/Telegram/SourceFiles/api/api_polls.cpp index fcfe7f983..7f4924a41 100644 --- a/Telegram/SourceFiles/api/api_polls.cpp +++ b/Telegram/SourceFiles/api/api_polls.cpp @@ -60,6 +60,10 @@ void Polls::create( if (action.options.scheduled) { sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date; } + const auto sendAs = action.options.sendAs; + if (sendAs) { + sendFlags |= MTPmessages_SendMedia::Flag::f_send_as; + } auto &histories = history->owner().histories(); const auto requestType = Data::Histories::RequestType::Send; histories.sendRequest(history, requestType, [=](Fn finish) { @@ -73,7 +77,8 @@ void Polls::create( MTP_long(base::RandomValue()), MTPReplyMarkup(), MTPVector(), - MTP_int(action.options.scheduled) + MTP_int(action.options.scheduled), + (sendAs ? sendAs->input : MTP_inputPeerEmpty()) )).done([=]( const MTPUpdates &result, const MTP::Response &response) mutable { @@ -145,7 +150,7 @@ void Polls::sendVotes( _pollVotesRequestIds.erase(itemId); hideSending(); _session->updates().applyUpdates(result); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _pollVotesRequestIds.erase(itemId); hideSending(); }).send(); @@ -174,7 +179,7 @@ void Polls::close(not_null item) { )).done([=](const MTPUpdates &result) { _pollCloseRequestIds.erase(itemId); _session->updates().applyUpdates(result); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _pollCloseRequestIds.erase(itemId); }).send(); _pollCloseRequestIds.emplace(itemId, requestId); @@ -191,7 +196,7 @@ void Polls::reloadResults(not_null item) { )).done([=](const MTPUpdates &result) { _pollReloadRequestIds.erase(itemId); _session->updates().applyUpdates(result); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _pollReloadRequestIds.erase(itemId); }).send(); _pollReloadRequestIds.emplace(itemId, requestId); diff --git a/Telegram/SourceFiles/api/api_self_destruct.cpp b/Telegram/SourceFiles/api/api_self_destruct.cpp index ea7de681b..fb8497637 100644 --- a/Telegram/SourceFiles/api/api_self_destruct.cpp +++ b/Telegram/SourceFiles/api/api_self_destruct.cpp @@ -25,7 +25,7 @@ void SelfDestruct::reload() { result.match([&](const MTPDaccountDaysTTL &data) { _days = data.vdays().v; }); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _requestId = 0; }).send(); } @@ -40,9 +40,9 @@ void SelfDestruct::update(int days) { _api.request(_requestId).cancel(); _requestId = _api.request(MTPaccount_SetAccountTTL( MTP_accountDaysTTL(MTP_int(days)) - )).done([=](const MTPBool &result) { + )).done([=] { _requestId = 0; - }).fail([=](const MTP::Error &result) { + }).fail([=] { _requestId = 0; }).send(); _days = days; diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index fac6558a6..65710c3d6 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -38,11 +38,11 @@ namespace Api { namespace { void InnerFillMessagePostFlags( - const Api::SendOptions &options, + const SendOptions &options, not_null peer, MessageFlags &flags) { const auto anonymousPost = peer->amAnonymous(); - if (!anonymousPost) { + if (!anonymousPost || options.sendAs) { flags |= MessageFlag::HasFromId; return; } else if (peer->asMegagroup()) { @@ -61,7 +61,7 @@ void InnerFillMessagePostFlags( template void SendExistingMedia( - Api::MessageToSend &&message, + MessageToSend &&message, not_null media, Fn inputMedia, Data::FileOrigin origin, @@ -93,8 +93,18 @@ void SendExistingMedia( if (silentPost) { sendFlags |= MTPmessages_SendMedia::Flag::f_silent; } - auto messageFromId = anonymousPost ? 0 : session->userPeerId(); - auto messagePostAuthor = peer->isBroadcast() ? session->user()->name : QString(); + const auto sendAs = message.action.options.sendAs; + const auto messageFromId = sendAs + ? sendAs->id + : anonymousPost + ? 0 + : session->userPeerId(); + if (sendAs) { + sendFlags |= MTPmessages_SendMedia::Flag::f_send_as; + } + const auto messagePostAuthor = peer->isBroadcast() + ? session->user()->name + : QString(); auto caption = TextWithEntities{ message.textWithTags.text, @@ -144,7 +154,8 @@ void SendExistingMedia( MTP_long(randomId), MTPReplyMarkup(), sentEntities, - MTP_int(message.action.options.scheduled) + MTP_int(message.action.options.scheduled), + (sendAs ? sendAs->input : MTP_inputPeerEmpty()) )).done([=](const MTPUpdates &result, mtpRequestId requestId) { api->applyUpdates(result, randomId); if (doneCallback) { @@ -201,7 +212,7 @@ void SendWebDocument( } void SendExistingDocument( - Api::MessageToSend &&message, + MessageToSend &&message, not_null document, Fn doneCallback, bool forwarding) { @@ -226,7 +237,7 @@ void SendExistingDocument( } void SendExistingPhoto( - Api::MessageToSend &&message, + MessageToSend &&message, not_null photo, Fn doneCallback, bool forwarding) { @@ -246,7 +257,7 @@ void SendExistingPhoto( } bool SendDice( - Api::MessageToSend &message, + MessageToSend &message, Fn doneCallback, bool forwarding) { const auto full = QStringView(message.textWithTags.text).trimmed(); @@ -301,8 +312,18 @@ bool SendDice( if (silentPost) { sendFlags |= MTPmessages_SendMedia::Flag::f_silent; } - auto messageFromId = anonymousPost ? 0 : session->userPeerId(); - auto messagePostAuthor = peer->isBroadcast() ? session->user()->name : QString(); + const auto sendAs = message.action.options.sendAs; + const auto messageFromId = sendAs + ? sendAs->id + : anonymousPost + ? 0 + : session->userPeerId(); + if (sendAs) { + sendFlags |= MTPmessages_SendMedia::Flag::f_send_as; + } + const auto messagePostAuthor = peer->isBroadcast() + ? session->user()->name + : QString(); const auto replyTo = message.action.replyTo; if (message.action.options.scheduled) { @@ -336,7 +357,8 @@ bool SendDice( MTP_long(randomId), MTPReplyMarkup(), MTP_vector(), - MTP_int(message.action.options.scheduled) + MTP_int(message.action.options.scheduled), + (sendAs ? sendAs->input : MTP_inputPeerEmpty()) )).done([=](const MTPUpdates &result, mtpRequestId requestId) { api->applyUpdates(result, randomId); if (doneCallback) { @@ -357,7 +379,7 @@ bool SendDice( } void FillMessagePostFlags( - const Api::SendAction &action, + const SendAction &action, not_null peer, MessageFlags &flags) { InnerFillMessagePostFlags(action.options, peer, flags); @@ -394,8 +416,7 @@ void SendConfirmedFile( const auto history = session->data().history(file->to.peer); const auto peer = history->peer; - auto action = Api::SendAction(history); - action.options = file->to.options; + auto action = SendAction(history, file->to.options); action.clearDraft = false; action.replyTo = file->to.replyTo; action.generateLocal = true; @@ -434,7 +455,12 @@ void SendConfirmedFile( } } - const auto messageFromId = anonymousPost ? 0 : session->userPeerId(); + const auto messageFromId = + file->to.options.sendAs + ? file->to.options.sendAs->id + : anonymousPost + ? PeerId() + : session->userPeerId(); const auto messagePostAuthor = peer->isBroadcast() ? session->user()->name : QString(); @@ -523,6 +549,12 @@ void SendLocationPoint( history->clearLocalDraft(); history->clearCloudDraft(); } + const auto anonymousPost = peer->amAnonymous(); + const auto sendAs = action.options.sendAs; + + if (sendAs) { + sendFlags |= MTPmessages_SendMedia::Flag::f_send_as; + } const auto silentPost = action.options.silent || (peer->isBroadcast() && session->data().notifySilentPosts(peer)); if (silentPost) { @@ -549,7 +581,8 @@ void SendLocationPoint( MTP_long(base::RandomValue()), MTPReplyMarkup(), MTPVector(), - MTP_int(action.options.scheduled) + MTP_int(action.options.scheduled), + (sendAs ? sendAs->input : MTP_inputPeerEmpty()) )).done([=](const MTPUpdates &result) mutable { api->applyUpdates(result); done(); diff --git a/Telegram/SourceFiles/api/api_sending.h b/Telegram/SourceFiles/api/api_sending.h index b8da63791..6adb5ba57 100644 --- a/Telegram/SourceFiles/api/api_sending.h +++ b/Telegram/SourceFiles/api/api_sending.h @@ -30,25 +30,25 @@ struct MessageToSend; struct SendAction; void SendWebDocument( - Api::MessageToSend &&message, + MessageToSend &&message, not_null document, Fn doneCallback = nullptr, bool forwarding = false); void SendExistingDocument( - Api::MessageToSend &&message, + MessageToSend &&message, not_null document, Fn doneCallback = nullptr, bool forwarding = false); void SendExistingPhoto( - Api::MessageToSend &&message, + MessageToSend &&message, not_null photo, Fn doneCallback = nullptr, bool forwarding = false); bool SendDice( - Api::MessageToSend &message, + MessageToSend &message, Fn doneCallback = nullptr, bool forwarding = false); diff --git a/Telegram/SourceFiles/api/api_sensitive_content.cpp b/Telegram/SourceFiles/api/api_sensitive_content.cpp index a18967368..e2d3e49ed 100644 --- a/Telegram/SourceFiles/api/api_sensitive_content.cpp +++ b/Telegram/SourceFiles/api/api_sensitive_content.cpp @@ -36,7 +36,7 @@ void SensitiveContent::reload() { _enabled = data.is_sensitive_enabled(); _canChange = data.is_sensitive_can_change(); }); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _requestId = 0; }).send(); } @@ -61,9 +61,9 @@ void SensitiveContent::update(bool enabled) { _api.request(_requestId).cancel(); _requestId = _api.request(MTPaccount_SetContentSettings( MTP_flags(enabled ? Flag::f_sensitive_enabled : Flag(0)) - )).done([=](const MTPBool &result) { + )).done([=] { _requestId = 0; - }).fail([=](const MTP::Error &error) { + }).fail([=] { _requestId = 0; }).send(); _enabled = enabled; diff --git a/Telegram/SourceFiles/api/api_single_message_search.cpp b/Telegram/SourceFiles/api/api_single_message_search.cpp index 781a1740a..4b374f3bb 100644 --- a/Telegram/SourceFiles/api/api_single_message_search.cpp +++ b/Telegram/SourceFiles/api/api_single_message_search.cpp @@ -117,7 +117,7 @@ std::optional SingleMessageSearch::performLookupByChannel( } else { fail(); } - }).fail([=](const MTP::Error &error) { + }).fail([=] { fail(); }).send(); @@ -154,7 +154,7 @@ std::optional SingleMessageSearch::performLookupById( fail(); } }); - }).fail([=](const MTP::Error &error) { + }).fail([=] { fail(); }).send(); @@ -198,7 +198,7 @@ std::optional SingleMessageSearch::performLookupByUsername( fail(); } }); - }).fail([=](const MTP::Error &error) { + }).fail([=] { fail(); }).send(); diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 5dd2d8744..fb97541c2 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_updates.h" #include "api/api_authorizations.h" +#include "api/api_chat_participants.h" #include "api/api_text_entities.h" #include "api/api_user_privacy.h" #include "main/main_session.h" @@ -768,7 +769,7 @@ void Updates::channelRangeDifferenceSend( )).done([=](const MTPupdates_ChannelDifference &result) { _rangeDifferenceRequests.remove(channel); channelRangeDifferenceDone(channel, range, result); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _rangeDifferenceRequests.remove(channel); }).send(); _rangeDifferenceRequests.emplace(channel, requestId); @@ -918,9 +919,9 @@ void Updates::updateOnline(crl::time lastNonIdleTime, bool gotOtherOffline) { } else { _onlineRequest = api().request(MTPaccount_UpdateStatus( MTP_bool(!isOnline) - )).done([=](const MTPBool &result) { + )).done([=] { Core::App().quitPreventFinished(); - }).fail([=](const MTP::Error &error) { + }).fail([=] { Core::App().quitPreventFinished(); }).send(); } @@ -1498,7 +1499,7 @@ void Updates::feedUpdate(const MTPUpdate &update) { if (channel->mgInfo->lastParticipants.size() < _session->serverConfig().chatSizeMax && (channel->mgInfo->lastParticipants.empty() || channel->mgInfo->lastParticipants.size() < channel->membersCount())) { - session().api().requestLastParticipants(channel); + session().api().chatParticipants().requestLast(channel); } } @@ -2127,7 +2128,7 @@ void Updates::feedUpdate(const MTPUpdate &update) { history->owner().histories().requestDialogEntry(history); } if (!channel->amCreator()) { - session().api().requestSelfParticipant(channel); + session().api().chatParticipants().requestSelf(channel); } } } @@ -2214,7 +2215,11 @@ void Updates::feedUpdate(const MTPUpdate &update) { ////// Cloud sticker sets case mtpc_updateNewStickerSet: { const auto &d = update.c_updateNewStickerSet(); - session().data().stickers().newSetReceived(d.vstickerset()); + d.vstickerset().match([&](const MTPDmessages_stickerSet &data) { + session().data().stickers().newSetReceived(data); + }, [](const MTPDmessages_stickerSetNotModified &) { + LOG(("API Error: Unexpected messages.stickerSetNotModified.")); + }); } break; case mtpc_updateStickerSetsOrder: { diff --git a/Telegram/SourceFiles/api/api_user_privacy.cpp b/Telegram/SourceFiles/api/api_user_privacy.cpp index 6baf18129..7a5f01de7 100644 --- a/Telegram/SourceFiles/api/api_user_privacy.cpp +++ b/Telegram/SourceFiles/api/api_user_privacy.cpp @@ -241,7 +241,7 @@ void UserPrivacy::save( _privacySaveRequests.remove(keyTypeId); apply(keyTypeId, data.vrules(), true); }); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _privacySaveRequests.remove(keyTypeId); }).send(); @@ -277,7 +277,7 @@ void UserPrivacy::reload(Key key) { _session->data().processChats(data.vchats()); pushPrivacy(key, data.vrules()); }); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _privacyRequestIds.erase(key); }).send(); _privacyRequestIds.emplace(key, requestId); diff --git a/Telegram/SourceFiles/api/api_who_read.cpp b/Telegram/SourceFiles/api/api_who_read.cpp index d7fcbb8ff..40f659c95 100644 --- a/Telegram/SourceFiles/api/api_who_read.cpp +++ b/Telegram/SourceFiles/api/api_who_read.cpp @@ -164,7 +164,7 @@ struct State { peers.push_back(UserId(id)); } entry.list = std::move(peers); - }).fail([=](const MTP::Error &error) { + }).fail([=] { auto &entry = context->cache(item); entry.requestId = 0; if (ListUnknown(entry.list.current(), item)) { diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index defa5ea2c..53af85e42 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -10,6 +10,7 @@ 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_chat_participants.h" #include "api/api_cloud_password.h" #include "api/api_hash.h" #include "api/api_invite_links.h" @@ -90,19 +91,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace { -// 1 second wait before reload members in channel after adding. -constexpr auto kReloadChannelMembersTimeout = 1000; - // Save draft to the cloud with 1 sec extra delay. constexpr auto kSaveCloudDraftTimeout = 1000; -// Max users in one super group invite request. -constexpr auto kMaxUsersPerInvite = 100; - -// How many messages from chat history server should forward to user, -// that was added to this chat. -constexpr auto kForwardMessagesOnAdd = 100; - constexpr auto kTopPromotionInterval = TimeId(60 * 60); constexpr auto kTopPromotionMinDelay = TimeId(10); constexpr auto kSmallDelayMs = 5; @@ -151,7 +142,8 @@ ApiWrap::ApiWrap(not_null session) , _views(std::make_unique(this)) , _confirmPhone(std::make_unique(this)) , _peerPhoto(std::make_unique(this)) -, _polls(std::make_unique(this)) { +, _polls(std::make_unique(this)) +, _chatParticipants(std::make_unique(this)) { crl::on_main(session, [=] { // You can't use _session->lifetime() in the constructor, // only queued, because it is not constructed yet. @@ -235,7 +227,7 @@ void ApiWrap::refreshTopPromotion() { )).done([=](const MTPhelp_PromoData &result) { _topPromotionRequestId = 0; topPromotionDone(result); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _topPromotionRequestId = 0; const auto now = base::unixtime::now(); const auto next = _topPromotionNextRequestTime = now @@ -285,7 +277,7 @@ void ApiWrap::requestDeepLinkInfo( if (result.type() == mtpc_help_deepLinkInfo) { callback(result.c_help_deepLinkInfo()); } - }).fail([=](const MTP::Error &error) { + }).fail([=] { _deepLinkInfoRequestId = 0; }).send(); } @@ -332,7 +324,7 @@ void ApiWrap::requestTermsUpdate() { } break; default: Unexpected("Type in requestTermsUpdate()."); } - }).fail([=](const MTP::Error &error) { + }).fail([=] { _termsUpdateRequestId = 0; _termsUpdateSendAt = crl::now() + kTermsUpdateTimeoutMin; requestTermsUpdate(); @@ -342,7 +334,7 @@ void ApiWrap::requestTermsUpdate() { void ApiWrap::acceptTerms(bytes::const_span id) { request(MTPhelp_AcceptTermsOfService( MTP_dataJSON(MTP_bytes(id)) - )).done([=](const MTPBool &result) { + )).done([=] { requestTermsUpdate(); }).send(); } @@ -464,7 +456,7 @@ void ApiWrap::toggleHistoryArchived( if (isPinned) { _session->data().notifyPinnedDialogsOrderUpdated(); } - }).fail([=](const MTP::Error &error) { + }).fail([=] { _historyArchivedRequests.remove(history); }).send(); _historyArchivedRequests.emplace(history, requestId, callback); @@ -505,6 +497,11 @@ void ApiWrap::sendMessageFail( scheduled.removeSending(item); Ui::show(Box(tr::lng_cant_do_this(tr::now))); } + } else if (error.type() == qstr("CHAT_FORWARDS_RESTRICTED")) { + Ui::ShowMultilineToast({ .text = { peer->isBroadcast() + ? tr::lng_error_noforwards_channel(tr::now) + : tr::lng_error_noforwards_group(tr::now) + }, .duration = kJoinErrorDuration }); } if (const auto item = _session->data().message(itemId)) { Assert(randomId != 0); @@ -740,7 +737,7 @@ void ApiWrap::requestContacts() { } } _session->data().contactsLoaded() = true; - }).fail([=](const MTP::Error &error) { + }).fail([=] { _contactsRequestId = 0; }).send(); } @@ -814,7 +811,7 @@ void ApiWrap::requestMoreDialogs(Data::Folder *folder) { } requestMoreDialogsIfNeeded(); _session->data().chatsListChanged(folder); - }).fail([=](const MTP::Error &error) { + }).fail([=] { dialogsLoadState(folder)->requestId = 0; }).send(); @@ -959,7 +956,7 @@ void ApiWrap::requestPinnedDialogs(Data::Folder *folder) { _session->data().chatsListChanged(folder); _session->data().notifyPinnedDialogsOrderUpdated(); }); - }).fail([=](const MTP::Error &error) { + }).fail([=] { finalize(); }).send(); } @@ -1028,7 +1025,7 @@ void ApiWrap::requestFullPeer(not_null peer) { const auto requestId = [&] { const auto failHandler = [=](const MTP::Error &error) { _fullPeerRequests.remove(peer); - migrateFail(peer, error); + migrateFail(peer, error.type()); }; if (const auto user = peer->asUser()) { if (_session->supportMode()) { @@ -1036,7 +1033,11 @@ void ApiWrap::requestFullPeer(not_null peer) { } return request(MTPusers_GetFullUser( user->inputUser - )).done([=](const MTPUserFull &result, mtpRequestId requestId) { + )).done([=](const MTPusers_UserFull &result, mtpRequestId requestId) { + result.match([&](const MTPDusers_userFull &data) { + _session->data().processUsers(data.vusers()); + _session->data().processChats(data.vchats()); + }); gotUserFull(user, result, requestId); }).fail(failHandler).send(); } else if (const auto chat = peer->asChat()) { @@ -1068,12 +1069,6 @@ void ApiWrap::processFullPeer( gotChatFull(peer, result, mtpRequestId(0)); } -void ApiWrap::processFullPeer( - not_null user, - const MTPUserFull &result) { - gotUserFull(user, result, mtpRequestId(0)); -} - void ApiWrap::gotChatFull( not_null peer, const MTPmessages_ChatFull &result, @@ -1113,18 +1108,20 @@ void ApiWrap::gotChatFull( void ApiWrap::gotUserFull( not_null user, - const MTPUserFull &result, + const MTPusers_UserFull &result, mtpRequestId req) { - const auto &d = result.c_userFull(); - if (user == _session->user() && !_session->validateSelf(d.vuser())) { - constexpr auto kRequestUserAgainTimeout = crl::time(10000); - base::call_delayed(kRequestUserAgainTimeout, _session, [=] { - requestFullPeer(user); + result.match([&](const MTPDusers_userFull &data) { + data.vfull_user().match([&](const MTPDuserFull &fields) { + if (user == _session->user() && !_session->validateSelf(fields.vid().v)) { + constexpr auto kRequestUserAgainTimeout = crl::time(10000); + base::call_delayed(kRequestUserAgainTimeout, _session, [=] { + requestFullPeer(user); + }); + return; + } + Data::ApplyUserUpdate(user, fields); }); - return; - } - Data::ApplyUserUpdate(user, d); - + }); if (req) { const auto i = _fullPeerRequests.find(user); if (i != _fullPeerRequests.cend() && i.value() == req) { @@ -1142,7 +1139,7 @@ void ApiWrap::requestPeer(not_null peer) { } const auto requestId = [&] { - const auto failHandler = [=](const MTP::Error &error) { + const auto failHandler = [=] { _peerRequests.remove(peer); }; const auto chatHandler = [=](const MTPmessages_Chats &result) { @@ -1180,10 +1177,14 @@ void ApiWrap::requestPeerSettings(not_null peer) { } request(MTPmessages_GetPeerSettings( peer->input - )).done([=](const MTPPeerSettings &result) { - peer->setSettings(result); - _requestedPeerSettings.erase(peer); - }).fail([=](const MTP::Error &error) { + )).done([=](const MTPmessages_PeerSettings &result) { + result.match([&](const MTPDmessages_peerSettings &data) { + _session->data().processUsers(data.vusers()); + _session->data().processChats(data.vchats()); + peer->setSettings(data.vsettings()); + _requestedPeerSettings.erase(peer); + }); + }).fail([=] { _requestedPeerSettings.erase(peer); }).send(); } @@ -1191,7 +1192,7 @@ void ApiWrap::requestPeerSettings(not_null peer) { void ApiWrap::migrateChat( not_null chat, FnMut)> done, - Fn fail) { + Fn fail) { const auto callback = [&] { return MigrateCallbacks{ std::move(done), std::move(fail) }; }; @@ -1214,7 +1215,7 @@ void ApiWrap::migrateChat( chat, MTP::Error::Local( "BAD_MIGRATION", - "Chat is already deactivated")); + "Chat is already deactivated").type()); }); return; } else if (!chat->amCreator()) { @@ -1223,7 +1224,7 @@ void ApiWrap::migrateChat( chat, MTP::Error::Local( "BAD_MIGRATION", - "Current user is not the creator of that chat")); + "Current user is not the creator of that chat").type()); }); return; } @@ -1242,10 +1243,10 @@ void ApiWrap::migrateChat( } else { migrateFail( chat, - MTP::Error::Local("MIGRATION_FAIL", "No channel")); + MTP::Error::Local("MIGRATION_FAIL", "No channel").type()); } }).fail([=](const MTP::Error &error) { - migrateFail(chat, error); + migrateFail(chat, error.type()); }).send(); } @@ -1262,9 +1263,8 @@ void ApiWrap::migrateDone( } } -void ApiWrap::migrateFail(not_null peer, const MTP::Error &error) { - const auto &type = error.type(); - if (type == qstr("CHANNELS_TOO_MUCH")) { +void ApiWrap::migrateFail(not_null peer, const QString &error) { + if (error == u"CHANNELS_TOO_MUCH"_q) { Ui::show(Box(tr::lng_migrate_error(tr::now))); } if (auto handlers = _migrateCallbacks.take(peer)) { @@ -1382,380 +1382,13 @@ void ApiWrap::requestPeers(const QList &peers) { } } -void ApiWrap::requestLastParticipants(not_null channel) { - if (!channel->isMegagroup() - || _participantsRequests.contains(channel)) { - return; - } - - const auto offset = 0; - const auto participantsHash = uint64(0); - const auto requestId = request(MTPchannels_GetParticipants( - channel->inputChannel, - MTP_channelParticipantsRecent(), - MTP_int(offset), - MTP_int(_session->serverConfig().chatSizeMax), - MTP_long(participantsHash) - )).done([=](const MTPchannels_ChannelParticipants &result) { - _participantsRequests.remove(channel); - parseChannelParticipants(channel, result, [&]( - int availableCount, - const QVector &list) { - applyLastParticipantsList( - channel, - availableCount, - list); - }); - }).fail([this, channel](const MTP::Error &error) { - _participantsRequests.remove(channel); - }).send(); - - _participantsRequests.insert(channel, requestId); -} - -void ApiWrap::requestBots(not_null channel) { - if (!channel->isMegagroup() || _botsRequests.contains(channel)) { - return; - } - - const auto offset = 0; - const auto participantsHash = uint64(0); - const auto requestId = request(MTPchannels_GetParticipants( - channel->inputChannel, - MTP_channelParticipantsBots(), - MTP_int(offset), - MTP_int(_session->serverConfig().chatSizeMax), - MTP_long(participantsHash) - )).done([=](const MTPchannels_ChannelParticipants &result) { - _botsRequests.remove(channel); - parseChannelParticipants(channel, result, [&]( - int availableCount, - const QVector &list) { - applyBotsList( - channel, - availableCount, - list); - }); - }).fail([=](const MTP::Error &error) { - _botsRequests.remove(channel); - }).send(); - - _botsRequests.insert(channel, requestId); -} - -void ApiWrap::requestAdmins(not_null channel) { - if (!channel->isMegagroup() || _adminsRequests.contains(channel)) { - return; - } - - const auto offset = 0; - const auto participantsHash = uint64(0); - const auto requestId = request(MTPchannels_GetParticipants( - channel->inputChannel, - MTP_channelParticipantsAdmins(), - MTP_int(offset), - MTP_int(_session->serverConfig().chatSizeMax), - MTP_long(participantsHash) - )).done([=](const MTPchannels_ChannelParticipants &result) { - _adminsRequests.remove(channel); - result.match([&](const MTPDchannels_channelParticipants &data) { - Data::ApplyMegagroupAdmins(channel, data); - }, [&](const MTPDchannels_channelParticipantsNotModified &) { - LOG(("API Error: channels.channelParticipantsNotModified received!")); - }); - }).fail([=](const MTP::Error &error) { - _adminsRequests.remove(channel); - }).send(); - - _adminsRequests.insert(channel, requestId); -} - -void ApiWrap::applyLastParticipantsList( +void ApiWrap::deleteAllFromParticipant( not_null channel, - int availableCount, - const QVector &list) { - channel->mgInfo->lastAdmins.clear(); - channel->mgInfo->lastRestricted.clear(); - channel->mgInfo->lastParticipants.clear(); - channel->mgInfo->lastParticipantsStatus = MegagroupInfo::LastParticipantsUpToDate - | MegagroupInfo::LastParticipantsOnceReceived; - - auto botStatus = channel->mgInfo->botStatus; - for (const auto &p : list) { - const auto participantId = p.match([]( - const MTPDchannelParticipantBanned &data) { - return peerFromMTP(data.vpeer()); - }, [](const MTPDchannelParticipantLeft &data) { - return peerFromMTP(data.vpeer()); - }, [](const auto &data) { - return peerFromUser(data.vuser_id()); - }); - if (!participantId) { - continue; - } - const auto participant = _session->data().peer(participantId); - const auto user = participant->asUser(); - const auto adminCanEdit = (p.type() == mtpc_channelParticipantAdmin) - ? p.c_channelParticipantAdmin().is_can_edit() - : (p.type() == mtpc_channelParticipantCreator) - ? channel->amCreator() - : false; - const auto adminRights = (p.type() == mtpc_channelParticipantAdmin) - ? ChatAdminRightsInfo(p.c_channelParticipantAdmin().vadmin_rights()) - : (p.type() == mtpc_channelParticipantCreator) - ? ChatAdminRightsInfo(p.c_channelParticipantCreator().vadmin_rights()) - : ChatAdminRightsInfo(); - const auto restrictedRights = (p.type() == mtpc_channelParticipantBanned) - ? ChatRestrictionsInfo( - p.c_channelParticipantBanned().vbanned_rights()) - : ChatRestrictionsInfo(); - if (p.type() == mtpc_channelParticipantCreator) { - Assert(user != nullptr); - const auto &creator = p.c_channelParticipantCreator(); - const auto rank = qs(creator.vrank().value_or_empty()); - channel->mgInfo->creator = user; - channel->mgInfo->creatorRank = rank; - if (!channel->mgInfo->admins.empty()) { - Data::ChannelAdminChanges(channel).add( - peerToUser(participantId), - rank); - } - } - if (user - && !base::contains(channel->mgInfo->lastParticipants, user)) { - channel->mgInfo->lastParticipants.push_back(user); - if (adminRights.flags) { - channel->mgInfo->lastAdmins.emplace( - user, - MegagroupInfo::Admin{ adminRights, adminCanEdit }); - } else if (restrictedRights.flags) { - channel->mgInfo->lastRestricted.emplace( - user, - MegagroupInfo::Restricted{ restrictedRights }); - } - if (user->isBot()) { - channel->mgInfo->bots.insert(user); - if (channel->mgInfo->botStatus != 0 && channel->mgInfo->botStatus < 2) { - channel->mgInfo->botStatus = 2; - } - } - } - } - // - // getParticipants(Recent) sometimes can't return all members, - // only some last subset, size of this subset is availableCount. - // - // So both list size and availableCount have nothing to do with - // the full supergroup members count. - // - //if (list.isEmpty()) { - // channel->setMembersCount(channel->mgInfo->lastParticipants.size()); - //} else { - // channel->setMembersCount(availableCount); - //} - session().changes().peerUpdated( - channel, - (Data::PeerUpdate::Flag::Members | Data::PeerUpdate::Flag::Admins)); - - channel->mgInfo->botStatus = botStatus; - _session->changes().peerUpdated( - channel, - Data::PeerUpdate::Flag::FullInfo); -} - -void ApiWrap::applyBotsList( - not_null channel, - int availableCount, - const QVector &list) { - const auto history = _session->data().historyLoaded(channel); - channel->mgInfo->bots.clear(); - channel->mgInfo->botStatus = -1; - - auto needBotsInfos = false; - auto botStatus = channel->mgInfo->botStatus; - auto keyboardBotFound = !history || !history->lastKeyboardFrom; - for (const auto &p : list) { - const auto participantId = p.match([]( - const MTPDchannelParticipantBanned &data) { - return peerFromMTP(data.vpeer()); - }, [](const MTPDchannelParticipantLeft &data) { - return peerFromMTP(data.vpeer()); - }, [](const auto &data) { - return peerFromUser(data.vuser_id()); - }); - if (!participantId) { - continue; - } - - const auto participant = _session->data().peer(participantId); - const auto user = participant->asUser(); - if (user && user->isBot()) { - channel->mgInfo->bots.insert(user); - botStatus = 2;// (botStatus > 0/* || !i.key()->botInfo->readsAllHistory*/) ? 2 : 1; - if (!user->botInfo->inited) { - needBotsInfos = true; - } - } - if (!keyboardBotFound - && participant->id == history->lastKeyboardFrom) { - keyboardBotFound = true; - } - } - if (needBotsInfos) { - requestFullPeer(channel); - } - if (!keyboardBotFound) { - history->clearLastKeyboard(); - } - - channel->mgInfo->botStatus = botStatus; - _session->changes().peerUpdated( - channel, - Data::PeerUpdate::Flag::FullInfo); -} - -void ApiWrap::requestSelfParticipant(not_null channel) { - if (_selfParticipantRequests.contains(channel)) { - return; - } - - const auto finalize = [=]( - UserId inviter = -1, - TimeId inviteDate = 0, - bool inviteViaRequest = false) { - channel->inviter = inviter; - channel->inviteDate = inviteDate; - channel->inviteViaRequest = inviteViaRequest; - if (const auto history = _session->data().historyLoaded(channel)) { - if (history->lastMessageKnown()) { - history->checkLocalMessages(); - history->owner().sendHistoryChangeNotifications(); - } else { - history->owner().histories().requestDialogEntry(history); - } - } - }; - _selfParticipantRequests.emplace(channel); - request(MTPchannels_GetParticipant( - channel->inputChannel, - MTP_inputPeerSelf() - )).done([=](const MTPchannels_ChannelParticipant &result) { - _selfParticipantRequests.erase(channel); - result.match([&](const MTPDchannels_channelParticipant &data) { - _session->data().processUsers(data.vusers()); - - const auto &participant = data.vparticipant(); - participant.match([&](const MTPDchannelParticipantSelf &data) { - finalize( - data.vinviter_id().v, - data.vdate().v, - data.is_via_invite()); - }, [&](const MTPDchannelParticipantCreator &) { - if (channel->mgInfo) { - channel->mgInfo->creator = _session->user(); - } - finalize(_session->userId(), channel->date); - }, [&](const MTPDchannelParticipantAdmin &data) { - const auto inviter = data.is_self() - ? data.vinviter_id().value_or(-1) - : -1; - finalize(inviter, data.vdate().v); - }, [&](const MTPDchannelParticipantBanned &data) { - LOG(("API Error: Got self banned participant.")); - finalize(); - }, [&](const MTPDchannelParticipant &data) { - LOG(("API Error: Got self regular participant.")); - finalize(); - }, [&](const MTPDchannelParticipantLeft &data) { - LOG(("API Error: Got self left participant.")); - finalize(); - }); - }); - }).fail([=](const MTP::Error &error) { - _selfParticipantRequests.erase(channel); - if (error.type() == qstr("CHANNEL_PRIVATE")) { - channel->privateErrorReceived(); - } - finalize(); - }).afterDelay(kSmallDelayMs).send(); -} - -void ApiWrap::kickParticipant( - not_null chat, - not_null participant) { - Expects(participant->isUser()); - - request(MTPmessages_DeleteChatUser( - MTP_flags(0), - chat->inputChat, - participant->asUser()->inputUser - )).done([=](const MTPUpdates &result) { - applyUpdates(result); - }).send(); -} - -void ApiWrap::kickParticipant( - not_null channel, - not_null participant, - ChatRestrictionsInfo currentRights) { - const auto kick = KickRequest(channel, participant); - if (_kickRequests.contains(kick)) return; - - const auto rights = ChannelData::KickedRestrictedRights(participant); - const auto requestId = request(MTPchannels_EditBanned( - channel->inputChannel, - participant->input, - MTP_chatBannedRights( - MTP_flags( - MTPDchatBannedRights::Flags::from_raw(uint32(rights.flags))), - MTP_int(rights.until)) - )).done([=](const MTPUpdates &result) { - applyUpdates(result); - - _kickRequests.remove(KickRequest(channel, participant)); - channel->applyEditBanned(participant, currentRights, rights); - }).fail([this, kick](const MTP::Error &error) { - _kickRequests.remove(kick); - }).send(); - - _kickRequests.emplace(kick, requestId); -} - -void ApiWrap::unblockParticipant( - not_null channel, - not_null participant) { - const auto kick = KickRequest(channel, participant); - if (_kickRequests.contains(kick)) { - return; - } - - const auto requestId = request(MTPchannels_EditBanned( - channel->inputChannel, - participant->input, - MTP_chatBannedRights(MTP_flags(0), MTP_int(0)) - )).done([=](const MTPUpdates &result) { - applyUpdates(result); - - _kickRequests.remove(KickRequest(channel, participant)); - if (channel->kickedCount() > 0) { - channel->setKickedCount(channel->kickedCount() - 1); - } else { - channel->updateFullForced(); - } - }).fail([=](const MTP::Error &error) { - _kickRequests.remove(kick); - }).send(); - - _kickRequests.emplace(kick, requestId); -} - -void ApiWrap::deleteAllFromUser( - not_null channel, - not_null from) { + not_null from) { const auto history = _session->data().historyLoaded(channel); const auto ids = history - ? history->collectMessagesFromUserToDelete(from) - : QVector(); + ? history->collectMessagesFromParticipantToDelete(from) + : std::vector(); const auto channelId = peerToChannel(channel->id); for (const auto &msgId : ids) { if (const auto item = _session->data().message(channelId, msgId)) { @@ -1765,55 +1398,25 @@ void ApiWrap::deleteAllFromUser( _session->data().sendHistoryChangeNotifications(); - deleteAllFromUserSend(channel, from); + deleteAllFromParticipantSend(channel, from); } -void ApiWrap::deleteAllFromUserSend( +void ApiWrap::deleteAllFromParticipantSend( not_null channel, - not_null from) { - request(MTPchannels_DeleteUserHistory( + not_null from) { + request(MTPchannels_DeleteParticipantHistory( channel->inputChannel, - from->inputUser + from->input )).done([=](const MTPmessages_AffectedHistory &result) { const auto offset = applyAffectedHistory(channel, result); if (offset > 0) { - deleteAllFromUserSend(channel, from); + deleteAllFromParticipantSend(channel, from); } else if (const auto history = _session->data().historyLoaded(channel)) { history->requestChatListMessage(); } }).send(); } -void ApiWrap::requestChannelMembersForAdd( - not_null channel, - Fn callback) { - _channelMembersForAddCallback = std::move(callback); - if (_channelMembersForAdd == channel) { - return; - } - request(base::take(_channelMembersForAddRequestId)).cancel(); - - const auto offset = 0; - const auto participantsHash = uint64(0); - - _channelMembersForAdd = channel; - _channelMembersForAddRequestId = request(MTPchannels_GetParticipants( - channel->inputChannel, - MTP_channelParticipantsRecent(), - MTP_int(offset), - MTP_int(_session->serverConfig().chatSizeMax), - MTP_long(participantsHash) - )).done([=](const MTPchannels_ChannelParticipants &result) { - base::take(_channelMembersForAddRequestId); - base::take(_channelMembersForAdd); - base::take(_channelMembersForAddCallback)(result); - }).fail([=](const MTP::Error &error) { - base::take(_channelMembersForAddRequestId); - base::take(_channelMembersForAdd); - base::take(_channelMembersForAddCallback); - }).send(); -} - void ApiWrap::scheduleStickerSetRequest(uint64 setId, uint64 access) { if (!_stickerSetRequests.contains(setId)) { _stickerSetRequests.insert(setId, qMakePair(access, 0)); @@ -1830,10 +1433,11 @@ void ApiWrap::requestStickerSets() { MTP_long(i.key()), MTP_long(i.value().first)); i.value().second = request(MTPmessages_GetStickerSet( - id + id, + MTP_int(0) // hash )).done([=, setId = i.key()](const MTPmessages_StickerSet &result) { gotStickerSet(setId, result); - }).fail([=, setId = i.key()](const MTP::Error &error) { + }).fail([=, setId = i.key()] { _stickerSetRequests.remove(setId); }).afterDelay(waitMs).send(); } @@ -1874,9 +1478,9 @@ void ApiWrap::saveStickerSets( reorderRequestId() = request(MTPmessages_ReorderStickerSets( MTP_flags(flags), MTP_vector(mtpOrder) - )).done([=](const MTPBool &result) { + )).done([=] { reorderRequestId() = 0; - }).fail([=](const MTP::Error &error) { + }).fail([=] { reorderRequestId() = 0; if (setsMasks) { _session->data().stickers().setLastMasksUpdate(0); @@ -1947,11 +1551,7 @@ void ApiWrap::saveStickerSets( }; requestId = request(MTPmessages_ClearRecentStickers( MTP_flags(flags) - )).done([=](const MTPBool &result) { - finish(); - }).fail([=](const MTP::Error &error) { - finish(); - }).send(); + )).done(finish).fail(finish).send(); continue; } @@ -2151,7 +1751,7 @@ void ApiWrap::leaveChannel(not_null channel) { )).done([=](const MTPUpdates &result) { _channelAmInRequests.remove(channel); applyUpdates(result); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _channelAmInRequests.remove(channel); }).send(); @@ -2193,7 +1793,7 @@ void ApiWrap::requestNotifySettings(const MTPInputNotifyPeer &peer) { )).done([=](const MTPPeerNotifySettings &result) { applyNotifySettings(peer, result); _notifySettingRequests.erase(key); - }).fail([=](const MTP::Error &error) { + }).fail([=] { applyNotifySettings( peer, MTP_peerNotifySettings( @@ -2275,7 +1875,7 @@ void ApiWrap::updatePrivacyLastSeens() { } } } - }).fail([this](const MTP::Error &error) { + }).fail([this] { _contactsStatusesRequestId = 0; }).send(); } @@ -2309,7 +1909,7 @@ void ApiWrap::deleteConversation(not_null peer, bool revoke) { )).done([=](const MTPUpdates &result) { applyUpdates(result); deleteHistory(peer, false, revoke); - }).fail([=](const MTP::Error &error) { + }).fail([=] { deleteHistory(peer, false, revoke); }).send(); } else { @@ -2585,7 +2185,11 @@ void ApiWrap::applyNotifySettings( void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result) { _stickerSetRequests.remove(setId); - _session->data().stickers().feedSetFull(result); + result.match([&](const MTPDmessages_stickerSet &data) { + _session->data().stickers().feedSetFull(data); + }, [](const MTPDmessages_stickerSetNotModified &) { + LOG(("API Error: Unexpected messages.stickerSetNotModified.")); + }); } void ApiWrap::requestWebPageDelayed(WebPageData *page) { @@ -2687,13 +2291,6 @@ void ApiWrap::resolveWebPages() { } } -void ApiWrap::requestParticipantsCountDelayed( - not_null channel) { - _participantsCountRequestTimer.call( - kReloadChannelMembersTimeout, - [=] { channel->updateFullForced(); }); -} - template void ApiWrap::requestFileReference( Data::FileOrigin origin, @@ -2736,7 +2333,7 @@ void ApiWrap::requestFileReference( for (auto &handler : handlers) { handler(parsed); } - }).fail([=](const MTP::Error &error) { + }).fail([=] { const auto i = _fileReferenceHandlers.find(origin); Assert(i != end(_fileReferenceHandlers)); auto handlers = std::move(i->second); @@ -2842,7 +2439,8 @@ void ApiWrap::refreshFileReference( request(MTPmessages_GetStickerSet( MTP_inputStickerSetID( MTP_long(data.setId), - MTP_long(data.accessHash))), + MTP_long(data.accessHash)), + MTP_int(0)), // hash [=] { crl::on_main(_session, [=] { local().writeInstalledStickers(); local().writeRecentStickers(); @@ -2988,7 +2586,7 @@ void ApiWrap::requestStickers(TimeId now) { }; _stickersUpdateRequest = request(MTPmessages_GetAllStickers( MTP_long(Api::CountStickersHash(_session, true)) - )).done(done).fail([=](const MTP::Error &error) { + )).done(done).fail([=] { LOG(("App Fail: Failed to get stickers!")); done(MTP_messages_allStickersNotModified()); }).send(); @@ -3012,7 +2610,7 @@ void ApiWrap::requestMasks(TimeId now) { }; _masksUpdateRequest = request(MTPmessages_GetMaskStickers( MTP_long(Api::CountMasksHash(_session, true)) - )).done(done).fail([=](const MTP::Error &error) { + )).done(done).fail([=] { LOG(("App Fail: Failed to get masks!")); done(MTP_messages_allStickersNotModified()); }).send(); @@ -3072,7 +2670,7 @@ void ApiWrap::requestRecentStickersWithHash(uint64 hash, bool attached) { } return; default: Unexpected("Type in ApiWrap::recentStickersDone()"); } - }).fail([=](const MTP::Error &error) { + }).fail([=] { finish(); LOG(("App Fail: Failed to get recent stickers!")); @@ -3103,7 +2701,7 @@ void ApiWrap::requestFavedStickers(TimeId now) { } return; default: Unexpected("Type in ApiWrap::favedStickersDone()"); } - }).fail([=](const MTP::Error &error) { + }).fail([=] { _session->data().stickers().setLastFavedUpdate(crl::now()); _favedStickersUpdateRequest = 0; @@ -3133,7 +2731,7 @@ void ApiWrap::requestFeaturedStickers(TimeId now) { } return; default: Unexpected("Type in ApiWrap::featuredStickersDone()"); } - }).fail([=](const MTP::Error &error) { + }).fail([=] { _session->data().stickers().setLastFeaturedUpdate(crl::now()); _featuredStickersUpdateRequest = 0; @@ -3162,7 +2760,7 @@ void ApiWrap::requestSavedGifs(TimeId now) { } return; default: Unexpected("Type in ApiWrap::savedGifsDone()"); } - }).fail([=](const MTP::Error &error) { + }).fail([=] { _session->data().stickers().setLastSavedGifsUpdate(crl::now()); _savedGifsUpdateRequest = 0; @@ -3197,7 +2795,7 @@ void ApiWrap::readFeaturedSets() { if (!wrappedIds.empty()) { auto requestData = MTPmessages_ReadFeaturedStickers( MTP_vector(wrappedIds)); - request(std::move(requestData)).done([=](const MTPBool &result) { + request(std::move(requestData)).done([=] { local().writeFeaturedStickers(); _session->data().stickers().notifyUpdated(); }).send(); @@ -3206,88 +2804,6 @@ void ApiWrap::readFeaturedSets() { } } -void ApiWrap::parseChannelParticipants( - not_null channel, - const MTPchannels_ChannelParticipants &result, - Fn &list)> callbackList, - Fn callbackNotModified) { - result.match([&](const MTPDchannels_channelParticipants &data) { - _session->data().processUsers(data.vusers()); - if (channel->mgInfo) { - refreshChannelAdmins(channel, data.vparticipants().v); - } - if (callbackList) { - callbackList(data.vcount().v, data.vparticipants().v); - } - }, [&](const MTPDchannels_channelParticipantsNotModified &) { - if (callbackNotModified) { - callbackNotModified(); - } else { - LOG(("API Error: " - "channels.channelParticipantsNotModified received!")); - } - }); -} - -void ApiWrap::refreshChannelAdmins( - not_null channel, - const QVector &participants) { - Data::ChannelAdminChanges changes(channel); - for (const auto &p : participants) { - const auto participantId = p.match([]( - const MTPDchannelParticipantBanned &data) { - return peerFromMTP(data.vpeer()); - }, [](const MTPDchannelParticipantLeft &data) { - return peerFromMTP(data.vpeer()); - }, [](const auto &data) { - return peerFromUser(data.vuser_id()); - }); - const auto userId = peerToUser(participantId); - p.match([&](const MTPDchannelParticipantAdmin &data) { - Assert(peerIsUser(participantId)); - changes.add(userId, qs(data.vrank().value_or_empty())); - }, [&](const MTPDchannelParticipantCreator &data) { - Assert(peerIsUser(participantId)); - const auto rank = qs(data.vrank().value_or_empty()); - if (const auto info = channel->mgInfo.get()) { - info->creator = channel->owner().userLoaded(userId); - info->creatorRank = rank; - } - changes.add(userId, rank); - }, [&](const auto &data) { - if (userId) { - changes.remove(userId); - } - }); - } -} - -void ApiWrap::parseRecentChannelParticipants( - not_null channel, - const MTPchannels_ChannelParticipants &result, - Fn &list)> callbackList, - Fn callbackNotModified) { - parseChannelParticipants(channel, result, [&]( - int availableCount, - const QVector &list) { - auto applyLast = channel->isMegagroup() - && (channel->mgInfo->lastParticipants.size() <= list.size()); - if (applyLast) { - applyLastParticipantsList( - channel, - availableCount, - list); - } - if (callbackList) { - callbackList(availableCount, list); - } - }, std::move(callbackNotModified)); -} - void ApiWrap::jumpToDate(Dialogs::Key chat, const QDate &date) { if (const auto peer = chat.peer()) { jumpToHistoryDate(peer, date); @@ -3403,7 +2919,7 @@ void ApiWrap::preloadEnoughUnreadMentions(not_null history) { auto requestId = request(MTPmessages_GetUnreadMentions(history->peer->input, MTP_int(offsetId), MTP_int(addOffset), MTP_int(limit), MTP_int(maxId), MTP_int(minId))).done([this, history](const MTPmessages_Messages &result) { _unreadMentionsRequests.remove(history); history->addUnreadMentionsSlice(result); - }).fail([this, history](const MTP::Error &error) { + }).fail([this, history] { _unreadMentionsRequests.remove(history); }).send(); _unreadMentionsRequests.emplace(history, requestId); @@ -3425,61 +2941,6 @@ void ApiWrap::checkForUnreadMentions( } } -void ApiWrap::addChatParticipants( - not_null peer, - const std::vector> &users, - Fn done) { - if (const auto chat = peer->asChat()) { - for (const auto &user : users) { - request(MTPmessages_AddChatUser( - chat->inputChat, - user->inputUser, - MTP_int(kForwardMessagesOnAdd) - )).done([=](const MTPUpdates &result) { - applyUpdates(result); - if (done) done(true); - }).fail([=](const MTP::Error &error) { - ShowAddParticipantsError(error.type(), peer, { 1, user }); - if (done) done(false); - }).afterDelay(crl::time(5)).send(); - } - } else if (const auto channel = peer->asChannel()) { - const auto hasBot = ranges::any_of(users, &UserData::isBot); - if (!peer->isMegagroup() && hasBot) { - ShowAddParticipantsError("USER_BOT", peer, users); - return; - } - auto list = QVector(); - list.reserve(qMin(int(users.size()), int(kMaxUsersPerInvite))); - const auto send = [&] { - const auto callback = base::take(done); - request(MTPchannels_InviteToChannel( - channel->inputChannel, - MTP_vector(list) - )).done([=](const MTPUpdates &result) { - applyUpdates(result); - requestParticipantsCountDelayed(channel); - if (callback) callback(true); - }).fail([=](const MTP::Error &error) { - ShowAddParticipantsError(error.type(), peer, users); - if (callback) callback(false); - }).afterDelay(crl::time(5)).send(); - }; - for (const auto &user : users) { - list.push_back(user->inputUser); - if (list.size() == kMaxUsersPerInvite) { - send(); - list.clear(); - } - } - if (!list.empty()) { - send(); - } - } else { - Unexpected("User in ApiWrap::addChatParticipants."); - } -} - void ApiWrap::requestSharedMediaCount( not_null peer, Storage::SharedMediaType type) { @@ -3517,7 +2978,7 @@ void ApiWrap::requestSharedMedia( _sharedMediaRequests.remove(key); sharedMediaDone(peer, type, messageId, slice, result); finish(); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _sharedMediaRequests.remove(key); finish(); }).send(); @@ -3566,7 +3027,7 @@ void ApiWrap::requestUserPhotos( )).done([this, user, afterId](const MTPphotos_Photos &result) { _userPhotosRequests.remove(user); userPhotosDone(user, afterId, result); - }).fail([this, user](const MTP::Error &error) { + }).fail([this, user] { _userPhotosRequests.remove(user); }).send(); _userPhotosRequests.emplace(user, requestId); @@ -3678,6 +3139,7 @@ void ApiWrap::forwardMessages( } const auto anonymousPost = peer->amAnonymous(); const auto silentPost = ShouldSendSilent(peer, action.options); + const auto sendAs = action.options.sendAs; auto flags = MessageFlags(); auto sendFlags = MTPmessages_ForwardMessages::Flags(0); @@ -3695,6 +3157,9 @@ void ApiWrap::forwardMessages( if (draft.options == Data::ForwardOptions::NoNamesAndCaptions) { sendFlags |= MTPmessages_ForwardMessages::Flag::f_drop_media_captions; } + if (sendAs) { + sendFlags |= MTPmessages_ForwardMessages::Flag::f_send_as; + } auto forwardFrom = draft.items.front()->history()->peer; auto forwardGroupId = draft.items.front()->groupId(); @@ -3715,7 +3180,8 @@ void ApiWrap::forwardMessages( MTP_vector(ids), MTP_vector(randomIds), peer->input, - MTP_int(action.options.scheduled) + MTP_int(action.options.scheduled), + (sendAs ? sendAs->input : MTP_inputPeerEmpty()) )).done([=](const MTPUpdates &result) { applyUpdates(result); if (shared && !--shared->requestsLeft) { @@ -3751,7 +3217,9 @@ void ApiWrap::forwardMessages( peerToChannel(peer->id), _session->data().nextLocalMessageId()); const auto self = _session->user(); - const auto messageFromId = anonymousPost + const auto messageFromId = sendAs + ? sendAs->id + : anonymousPost ? PeerId(0) : self->id; const auto messagePostAuthor = peer->isBroadcast() @@ -3821,6 +3289,13 @@ void ApiWrap::forwardMessagesUnquoted( const auto anonymousPost = peer->amAnonymous(); const auto silentPost = ShouldSendSilent(peer, action.options); + const auto sendAs = action.options.sendAs; + const auto self = _session->user(); + const auto messageFromId = sendAs + ? sendAs->id + : anonymousPost + ? PeerId(0) + : self->id; auto flags = MessageFlags(); auto sendFlags = MTPmessages_ForwardMessages::Flags(0); @@ -3832,6 +3307,9 @@ void ApiWrap::forwardMessagesUnquoted( flags |= MessageFlag::IsOrWasScheduled; sendFlags |= MTPmessages_ForwardMessages::Flag::f_schedule_date; } + if (sendAs) { + sendFlags |= MTPmessages_ForwardMessages::Flag::f_send_as; + } auto forwardFrom = draft.items.front()->history()->peer; auto currentGroupId = draft.items.front()->groupId(); @@ -3841,7 +3319,6 @@ void ApiWrap::forwardMessagesUnquoted( auto fromIter = draft.items.begin(); auto toIter = draft.items.begin(); auto messageGroupCount = 0; - auto messageFromId = anonymousPost ? 0 : _session->userPeerId(); auto messagePostAuthor = peer->isBroadcast() ? _session->user()->name : QString(); const auto needNextGroup = [&] (not_null item) { @@ -3907,7 +3384,8 @@ void ApiWrap::forwardMessagesUnquoted( MTP_vector(currentIds), MTP_vector(currentRandomIds), peer->input, - MTP_int(action.options.scheduled) + MTP_int(action.options.scheduled), + (sendAs ? sendAs->input : MTP_inputPeerEmpty()) )).done([=](const MTPUpdates &result) { applyUpdates(result); if (shared && !--shared->requestsLeft) { @@ -4033,7 +3511,8 @@ void ApiWrap::forwardMessagesUnquoted( peer->input, MTPint(), MTP_vector(*mediaInputs), - MTP_int(action.options.scheduled) + MTP_int(action.options.scheduled), + (sendAs ? sendAs->input : MTP_inputPeerEmpty()) )).done([=](const MTPUpdates &result) { applyUpdates(result); if (shared && !--shared->requestsLeft) { @@ -4094,7 +3573,7 @@ void ApiWrap::forwardMessagesUnquoted( } const auto media = item->media(); - auto message = ApiWrap::MessageToSend(history); + auto message = MessageToSend(action); const auto caption = (draft.options != Data::ForwardOptions::NoNamesAndCaptions && !media->geoPoint() && !media->sharedContact()) @@ -4105,7 +3584,6 @@ void ApiWrap::forwardMessagesUnquoted( caption.text, TextUtilities::ConvertEntitiesToTextTags(caption.entities) }; - message.action.options = action.options; message.action.clearDraft = false; auto doneCallback = [=] () { @@ -4160,9 +3638,8 @@ void ApiWrap::forwardMessagesUnquoted( Unexpected("Non-dice in ApiWrap::forwardMessages."); } - auto message = ApiWrap::MessageToSend(history); + auto message = MessageToSend(action); message.textWithTags.text = dice->emoji(); - message.action.options = action.options; message.action.clearDraft = false; Api::SendDice(message, [=] (const MTPUpdates &result, mtpRequestId requestId) { @@ -4182,12 +3659,11 @@ void ApiWrap::forwardMessagesUnquoted( ? CancelledWebPageId : media->webpage()->id; - auto message = ApiWrap::MessageToSend(history); + auto message = MessageToSend(action); message.textWithTags = TextWithTags{ item->originalText().text, TextUtilities::ConvertEntitiesToTextTags(item->originalText().entities) }; - message.action.options = action.options; message.action.clearDraft = false; message.webPageId = webPageId; @@ -4313,7 +3789,11 @@ void ApiWrap::sendSharedContact( if (action.options.scheduled) { flags |= MessageFlag::IsOrWasScheduled; } - const auto messageFromId = anonymousPost ? 0 : _session->userPeerId(); + const auto messageFromId = action.options.sendAs + ? action.options.sendAs->id + : anonymousPost + ? PeerId() + : _session->userPeerId(); const auto messagePostAuthor = peer->isBroadcast() ? _session->user()->name : QString(); @@ -4393,9 +3873,8 @@ void ApiWrap::sendFiles( const SendAction &action) { const auto haveCaption = !caption.text.isEmpty(); if (haveCaption && !list.canAddCaption(album != nullptr)) { - auto message = MessageToSend(action.history); + auto message = MessageToSend(action); message.textWithTags = base::take(caption); - message.action = action; message.action.clearDraft = false; sendMessage(std::move(message)); } @@ -4452,13 +3931,10 @@ void ApiWrap::sendFile( void ApiWrap::sendUploadedPhoto( FullMsgId localId, - const MTPInputFile &file, - Api::SendOptions options, - std::vector attachedStickers) { + Api::RemoteFileInfo info, + Api::SendOptions options) { if (const auto item = _session->data().message(localId)) { - const auto media = Api::PrepareUploadedPhoto( - file, - std::move(attachedStickers)); + const auto media = Api::PrepareUploadedPhoto(std::move(info)); if (const auto groupId = item->groupId()) { uploadAlbumMedia(item, groupId, media); } else { @@ -4469,19 +3945,15 @@ void ApiWrap::sendUploadedPhoto( void ApiWrap::sendUploadedDocument( FullMsgId localId, - const MTPInputFile &file, - const std::optional &thumb, - Api::SendOptions options, - std::vector attachedStickers) { + Api::RemoteFileInfo info, + Api::SendOptions options) { if (const auto item = _session->data().message(localId)) { if (!item->media() || !item->media()->document()) { return; } const auto media = Api::PrepareUploadedDocument( item, - file, - thumb, - std::move(attachedStickers)); + std::move(info)); const auto groupId = item->groupId(); if (groupId) { uploadAlbumMedia(item, groupId, media); @@ -4584,8 +4056,16 @@ void ApiWrap::sendMessage( history->clearCloudDraft(); history->startSavingCloudDraft(); } - auto messageFromId = anonymousPost ? 0 : _session->userPeerId(); - auto messagePostAuthor = peer->isBroadcast() + const auto sendAs = action.options.sendAs; + const auto messageFromId = sendAs + ? sendAs->id + : anonymousPost + ? PeerId() + : _session->userPeerId(); + if (sendAs) { + sendFlags |= MTPmessages_SendMessage::Flag::f_send_as; + } + const auto messagePostAuthor = peer->isBroadcast() ? _session->user()->name : QString(); if (action.options.scheduled) { @@ -4613,7 +4093,8 @@ void ApiWrap::sendMessage( MTP_long(randomId), MTPReplyMarkup(), sentEntities, - MTP_int(action.options.scheduled) + MTP_int(action.options.scheduled), + (sendAs ? sendAs->input : MTP_inputPeerEmpty()) )).done([=]( const MTPUpdates &result, const MTP::Response &response) { @@ -4662,7 +4143,8 @@ void ApiWrap::sendBotStart(not_null bot, PeerData *chat) { auto &info = bot->botInfo; auto &token = chat ? info->startGroupToken : info->startToken; if (token.isEmpty()) { - auto message = ApiWrap::MessageToSend(_session->data().history(bot)); + auto message = MessageToSend( + Api::SendAction(_session->data().history(bot))); message.textWithTags = { qsl("/start"), TextWithTags::Tags() }; sendMessage(std::move(message)); return; @@ -4719,7 +4201,14 @@ void ApiWrap::sendInlineResult( sendFlags |= MTPmessages_SendInlineBotResult::Flag::f_schedule_date; } - const auto messageFromId = anonymousPost ? 0 : _session->userPeerId(); + const auto sendAs = action.options.sendAs; + const auto messageFromId = sendAs + ? sendAs->id + : anonymousPost ? PeerId() + : _session->userPeerId(); + if (sendAs) { + sendFlags |= MTPmessages_SendInlineBotResult::Flag::f_send_as; + } const auto messagePostAuthor = peer->isBroadcast() ? _session->user()->name : QString(); @@ -4749,7 +4238,8 @@ void ApiWrap::sendInlineResult( MTP_long(randomId), MTP_long(data->getQueryId()), MTP_string(data->getId()), - MTP_int(action.options.scheduled) + MTP_int(action.options.scheduled), + (sendAs ? sendAs->input : MTP_inputPeerEmpty()) )).done([=]( const MTPUpdates &result, const MTP::Response &response) { @@ -4842,7 +4332,7 @@ void ApiWrap::uploadAlbumMedia( sendAlbumWithUploaded(item, groupId, media); } break; } - }).fail([=](const MTP::Error &error) { + }).fail([=] { failed(); }).send(); } @@ -4886,6 +4376,9 @@ void ApiWrap::sendMediaWithRandomId( : MTPmessages_SendMedia::Flag(0)) | (options.scheduled ? MTPmessages_SendMedia::Flag::f_schedule_date + : MTPmessages_SendMedia::Flag(0)) + | (options.sendAs + ? MTPmessages_SendMedia::Flag::f_send_as : MTPmessages_SendMedia::Flag(0)); auto &histories = history->owner().histories(); @@ -4902,7 +4395,8 @@ void ApiWrap::sendMediaWithRandomId( MTP_long(randomId), MTPReplyMarkup(), sentEntities, - MTP_int(options.scheduled) + MTP_int(options.scheduled), + (options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()) )).done([=](const MTPUpdates &result) { applyUpdates(result); finish(); @@ -4985,6 +4479,7 @@ void ApiWrap::sendAlbumIfReady(not_null album) { } const auto history = sample->history(); const auto replyTo = sample->replyToId(); + const auto sendAs = album->options.sendAs; const auto flags = MTPmessages_SendMultiMedia::Flags(0) | (replyTo ? MTPmessages_SendMultiMedia::Flag::f_reply_to_msg_id @@ -4994,6 +4489,9 @@ void ApiWrap::sendAlbumIfReady(not_null album) { : MTPmessages_SendMultiMedia::Flag(0)) | (album->options.scheduled ? MTPmessages_SendMultiMedia::Flag::f_schedule_date + : MTPmessages_SendMultiMedia::Flag(0)) + | (sendAs + ? MTPmessages_SendMultiMedia::Flag::f_send_as : MTPmessages_SendMultiMedia::Flag(0)); auto &histories = history->owner().histories(); const auto requestType = Data::Histories::RequestType::Send; @@ -5004,7 +4502,8 @@ void ApiWrap::sendAlbumIfReady(not_null album) { peer->input, MTP_int(replyTo), MTP_vector(medias), - MTP_int(album->options.scheduled) + MTP_int(album->options.scheduled), + (sendAs ? sendAs->input : MTP_inputPeerEmpty()) )).done([=](const MTPUpdates &result) { _sendingAlbums.remove(groupId); applyUpdates(result); @@ -5044,7 +4543,7 @@ void ApiWrap::reloadContactSignupSilent() { const auto silent = mtpIsTrue(result); _contactSignupSilent = silent; _contactSignupSilentChanges.fire_copy(silent); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _contactSignupSilentRequestId = 0; }).send(); _contactSignupSilentRequestId = requestId; @@ -5066,11 +4565,11 @@ void ApiWrap::saveContactSignupSilent(bool silent) { const auto requestId = request(MTPaccount_SetContactSignUpNotification( MTP_bool(silent) - )).done([=](const MTPBool &) { + )).done([=] { _contactSignupSilentRequestId = 0; _contactSignupSilent = silent; _contactSignupSilentChanges.fire_copy(silent); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _contactSignupSilentRequestId = 0; }).send(); _contactSignupSilentRequestId = requestId; @@ -5095,7 +4594,7 @@ void ApiWrap::saveSelfBio(const QString &text) { _session->data().processUser(result); _session->user()->setAbout(_bio.requestedText); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _bio.requestId = 0; }).send(); } @@ -5151,3 +4650,7 @@ Api::PeerPhoto &ApiWrap::peerPhoto() { Api::Polls &ApiWrap::polls() { return *_polls; } + +Api::ChatParticipants &ApiWrap::chatParticipants() { + return *_chatParticipants; +} diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 26d872764..e3f3c5c03 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -66,6 +66,7 @@ class ViewsManager; class ConfirmPhone; class PeerPhoto; class Polls; +class ChatParticipants; namespace details { @@ -172,10 +173,6 @@ public: void requestPeer(not_null peer); void requestPeers(const QList &peers); void requestPeerSettings(not_null peer); - void requestLastParticipants(not_null channel); - void requestBots(not_null channel); - void requestAdmins(not_null channel); - void requestParticipantsCountDelayed(not_null channel); using UpdatedFileReferences = Data::UpdatedFileReferences; using FileReferencesHandler = FnMut; @@ -204,38 +201,21 @@ public: Fn fail); void importChatInvite(const QString &hash, bool isGroup); - void requestChannelMembersForAdd( - not_null channel, - Fn callback); void processFullPeer( not_null peer, const MTPmessages_ChatFull &result); - void processFullPeer( - not_null user, - const MTPUserFull &result); void migrateChat( not_null chat, FnMut)> done, - Fn fail = nullptr); + Fn fail = nullptr); void markMediaRead(const base::flat_set> &items); void markMediaRead(not_null item); - void requestSelfParticipant(not_null channel); - void kickParticipant( - not_null chat, - not_null participant); - void kickParticipant( + void deleteAllFromParticipant( not_null channel, - not_null participant, - ChatRestrictionsInfo currentRights); - void unblockParticipant( - not_null channel, - not_null participant); - void deleteAllFromUser( - not_null channel, - not_null from); + not_null from); void requestWebPageDelayed(WebPageData *page); void clearWebPageRequest(WebPageData *page); @@ -295,25 +275,6 @@ public: void readFeaturedSetDelayed(uint64 setId); - void parseChannelParticipants( - not_null channel, - const MTPchannels_ChannelParticipants &result, - Fn &list)> callbackList, - Fn callbackNotModified = nullptr); - void parseRecentChannelParticipants( - not_null channel, - const MTPchannels_ChannelParticipants &result, - Fn &list)> callbackList = nullptr, - Fn callbackNotModified = nullptr); - void addChatParticipants( - not_null peer, - const std::vector> &users, - Fn done = nullptr); - rpl::producer sendActions() const { return _sendActions.events(); } @@ -361,15 +322,12 @@ public: void sendUploadedPhoto( FullMsgId localId, - const MTPInputFile &file, - Api::SendOptions options, - std::vector attachedStickers); + Api::RemoteFileInfo info, + Api::SendOptions options); void sendUploadedDocument( FullMsgId localId, - const MTPInputFile &file, - const std::optional &thumb, - Api::SendOptions options, - std::vector attachedStickers); + Api::RemoteFileInfo file, + Api::SendOptions options); void cancelLocalItem(not_null item); @@ -408,6 +366,7 @@ public: [[nodiscard]] Api::ConfirmPhone &confirmPhone(); [[nodiscard]] Api::PeerPhoto &peerPhoto(); [[nodiscard]] Api::Polls &polls(); + [[nodiscard]] Api::ChatParticipants &chatParticipants(); void updatePrivacyLastSeens(); @@ -469,16 +428,8 @@ private: mtpRequestId req); void gotUserFull( not_null user, - const MTPUserFull &result, + const MTPusers_UserFull &result, mtpRequestId req); - void applyLastParticipantsList( - not_null channel, - int availableCount, - const QVector &list); - void applyBotsList( - not_null channel, - int availableCount, - const QVector &list); void resolveWebPages(); void gotWebPages( ChannelData *channel, @@ -495,10 +446,6 @@ private: void requestSavedGifs(TimeId now); void readFeaturedSets(); - void refreshChannelAdmins( - not_null channel, - const QVector &participants); - void jumpToHistoryDate(not_null peer, const QDate &date); template void requestMessageAfterDate( @@ -531,9 +478,9 @@ private: bool revoke); void applyAffectedMessages(const MTPmessages_AffectedMessages &result); - void deleteAllFromUserSend( + void deleteAllFromParticipantSend( not_null channel, - not_null from); + not_null from); void uploadAlbumMedia( not_null item, @@ -572,7 +519,7 @@ private: void migrateDone( not_null peer, not_null channel); - void migrateFail(not_null peer, const MTP::Error &error); + void migrateFail(not_null peer, const QString &error); not_null _session; @@ -589,26 +536,10 @@ private: PeerRequests _peerRequests; base::flat_set> _requestedPeerSettings; - PeerRequests _participantsRequests; - PeerRequests _botsRequests; - PeerRequests _adminsRequests; - base::DelayedCallTimer _participantsCountRequestTimer; - - ChannelData *_channelMembersForAdd = nullptr; - mtpRequestId _channelMembersForAddRequestId = 0; - Fn _channelMembersForAddCallback; base::flat_map< not_null, std::pair>> _historyArchivedRequests; - using KickRequest = std::pair< - not_null, - not_null>; - base::flat_map _kickRequests; - - base::flat_set> _selfParticipantRequests; - QMap _webPagesPending; base::Timer _webPagesTimer; @@ -689,7 +620,7 @@ private: struct MigrateCallbacks { FnMut)> done; - Fn fail; + Fn fail; }; base::flat_map< not_null, @@ -715,6 +646,7 @@ private: const std::unique_ptr _confirmPhone; const std::unique_ptr _peerPhoto; const std::unique_ptr _polls; + const std::unique_ptr _chatParticipants; mtpRequestId _wallPaperRequestId = 0; QString _wallPaperSlug; diff --git a/Telegram/SourceFiles/boxes/add_contact_box.cpp b/Telegram/SourceFiles/boxes/add_contact_box.cpp index 09d4df8ab..43fa0abb4 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.cpp +++ b/Telegram/SourceFiles/boxes/add_contact_box.cpp @@ -128,7 +128,7 @@ void ShowAddParticipantsError( const QString &error, not_null chat, const std::vector> &users) { - if (error == qstr("USER_BOT")) { + if (error == u"USER_BOT"_q) { const auto channel = chat->asChannel(); if ((users.size() == 1) && users.front()->isBot() @@ -168,28 +168,28 @@ void ShowAddParticipantsError( } const auto hasBot = ranges::any_of(users, &UserData::isBot); const auto text = [&] { - if (error == qstr("USER_BOT")) { + if (error == u"USER_BOT"_q) { return tr::lng_cant_invite_bot_to_channel(tr::now); - } else if (error == qstr("USER_LEFT_CHAT")) { + } else if (error == u"USER_LEFT_CHAT"_q) { // Trying to return a user who has left. - } else if (error == qstr("USER_KICKED")) { + } else if (error == u"USER_KICKED"_q) { // Trying to return a user who was kicked by admin. return tr::lng_cant_invite_banned(tr::now); - } else if (error == qstr("USER_PRIVACY_RESTRICTED")) { + } else if (error == u"USER_PRIVACY_RESTRICTED"_q) { return tr::lng_cant_invite_privacy(tr::now); - } else if (error == qstr("USER_NOT_MUTUAL_CONTACT")) { + } else if (error == u"USER_NOT_MUTUAL_CONTACT"_q) { // Trying to return user who does not have me in contacts. return tr::lng_failed_add_not_mutual(tr::now); - } else if (error == qstr("USER_ALREADY_PARTICIPANT") && hasBot) { + } else if (error == u"USER_ALREADY_PARTICIPANT"_q && hasBot) { return tr::lng_bot_already_in_group(tr::now); - } else if (error == qstr("BOT_GROUPS_BLOCKED")) { + } else if (error == u"BOT_GROUPS_BLOCKED"_q) { return tr::lng_error_cant_add_bot(tr::now); - } else if (error == qstr("PEER_FLOOD")) { + } else if (error == u"PEER_FLOOD"_q) { const auto type = (chat->isChat() || chat->isMegagroup()) ? PeerFloodType::InviteGroup : PeerFloodType::InviteChannel; return PeerFloodErrorText(&chat->session(), type); - } else if (error == qstr("ADMINS_TOO_MUCH")) { + } else if (error == u"ADMINS_TOO_MUCH"_q) { return ((chat->isChat() || chat->isMegagroup()) ? tr::lng_error_admin_limit : tr::lng_error_admin_limit_channel)(tr::now); @@ -261,7 +261,8 @@ AddContactBox::AddContactBox( st::defaultInputField, tr::lng_contact_phone(), Countries::ExtractPhoneCode(session->user()->phone()), - phone) + phone, + [](const QString &s) { return Countries::Groups(s); }) , _invertOrder(langFirstNameGoesSecond()) { if (!phone.isEmpty()) { _phone->setDisabled(true); @@ -609,9 +610,11 @@ void GroupInfoBox::createGroup( not_null selectUsersBox, const QString &title, const std::vector> &users) { - if (_creationRequestId) return; - - auto inputs = QVector(); + if (_creationRequestId) { + return; + } + using TLUsers = MTPInputUser; + auto inputs = QVector(); inputs.reserve(users.size()); for (auto peer : users) { auto user = peer->asUser(); @@ -624,7 +627,7 @@ void GroupInfoBox::createGroup( return; } _creationRequestId = _api.request(MTPmessages_CreateChat( - MTP_vector(inputs), + MTP_vector(inputs), MTP_string(title) )).done([=](const MTPUpdates &result) { auto image = _photo->takeResultImage(); @@ -633,26 +636,28 @@ void GroupInfoBox::createGroup( Ui::hideLayer(); // Destroys 'this'. ChatCreateDone(navigation, std::move(image), result); }).fail([=](const MTP::Error &error) { + const auto &type = error.type(); _creationRequestId = 0; - if (error.type() == qstr("NO_CHAT_TITLE")) { - auto weak = Ui::MakeWeak(this); + const auto controller = _navigation->parentController(); + if (type == u"NO_CHAT_TITLE"_q) { + const auto weak = Ui::MakeWeak(this); selectUsersBox->closeBox(); if (weak) { _title->showError(); } - } else if (error.type() == qstr("USERS_TOO_FEW")) { - Ui::show( + } else if (type == u"USERS_TOO_FEW"_q) { + controller->show( Box(tr::lng_cant_invite_privacy(tr::now)), Ui::LayerOption::KeepOther); - } else if (error.type() == qstr("PEER_FLOOD")) { - Ui::show( + } else if (type == u"PEER_FLOOD"_q) { + controller->show( Box( PeerFloodErrorText( &_navigation->session(), PeerFloodType::InviteGroup)), Ui::LayerOption::KeepOther); - } else if (error.type() == qstr("USER_RESTRICTED")) { - Ui::show( + } else if (type == u"USER_RESTRICTED"_q) { + controller->show( Box(tr::lng_cant_do_this(tr::now)), Ui::LayerOption::KeepOther); } @@ -756,14 +761,20 @@ void GroupInfoBox::createChannel( closeBox(); } }).fail([this](const MTP::Error &error) { + const auto &type = error.type(); _creationRequestId = 0; - if (error.type() == "NO_CHAT_TITLE") { + const auto controller = _navigation->parentController(); + if (type == u"NO_CHAT_TITLE"_q) { _title->setFocus(); _title->showError(); - } else if (error.type() == qstr("USER_RESTRICTED")) { - Ui::show(Box(tr::lng_cant_do_this(tr::now))); - } else if (error.type() == qstr("CHANNELS_TOO_MUCH")) { - Ui::show(Box(tr::lng_cant_do_this(tr::now))); // TODO + } else if (type == u"USER_RESTRICTED"_q) { + controller->show( + Box(tr::lng_cant_do_this(tr::now)), + Ui::LayerOption::CloseOther); + } else if (type == u"CHANNELS_TOO_MUCH"_q) { + controller->show( + Box(tr::lng_cant_do_this(tr::now)), + Ui::LayerOption::CloseOther); // TODO } }).send(); } @@ -795,9 +806,9 @@ void GroupInfoBox::channelReady() { closeBox(); callback(argument); } else { - Ui::show(Box( - _navigation, - _createdChannel)); + _navigation->parentController()->show( + Box(_navigation, _createdChannel), + Ui::LayerOption::CloseOther); } } @@ -890,7 +901,7 @@ void SetupChannelBox::prepare() { MTP_string("preston") )).fail([=](const MTP::Error &error) { _checkRequestId = 0; - firstCheckFail(error.type()); + firstCheckFail(parseError(error.type())); }).send(); addButton(tr::lng_settings_save(), [=] { save(); }); @@ -1112,14 +1123,14 @@ void SetupChannelBox::save() { _saveRequestId = _api.request(MTPchannels_UpdateUsername( _channel->inputChannel, MTP_string(_sentUsername) - )).done([=](const MTPBool &result) { + )).done([=] { _channel->setName( TextUtilities::SingleLine(_channel->name), _sentUsername); closeBox(); }).fail([=](const MTP::Error &error) { _saveRequestId = 0; - updateFail(error.type()); + updateFail(parseError(error.type())); }).send(); }; if (_saveRequestId) { @@ -1207,7 +1218,7 @@ void SetupChannelBox::check() { update(); }).fail([=](const MTP::Error &error) { _checkRequestId = 0; - checkFail(error.type()); + checkFail(parseError(error.type())); }).send(); } } @@ -1241,20 +1252,38 @@ void SetupChannelBox::privacyChanged(Privacy value) { update(); } -void SetupChannelBox::updateFail(const QString &error) { - if ((error == "USERNAME_NOT_MODIFIED") +SetupChannelBox::UsernameResult SetupChannelBox::parseError( + const QString &error) { + if (error == u"USERNAME_NOT_MODIFIED"_q) { + return UsernameResult::Ok; + } else if (error == u"USERNAME_INVALID"_q) { + return UsernameResult::Invalid; + } else if (error == u"USERNAME_OCCUPIED"_q) { + return UsernameResult::Occupied; + } else if (error == u"USERNAMES_UNAVAILABLE"_q) { + return UsernameResult::Occupied; + } else if (error == u"CHANNEL_PUBLIC_GROUP_NA"_q) { + return UsernameResult::NA; + } else if (error == u"CHANNELS_ADMIN_PUBLIC_TOO_MUCH"_q) { + return UsernameResult::ChatsTooMuch; + } else { + return UsernameResult::Unknown; + } +} + +void SetupChannelBox::updateFail(UsernameResult result) { + if ((result == UsernameResult::Ok) || (_sentUsername == _channel->username)) { _channel->setName( TextUtilities::SingleLine(_channel->name), TextUtilities::SingleLine(_sentUsername)); closeBox(); - } else if (error == "USERNAME_INVALID") { + } else if (result == UsernameResult::Invalid) { _link->setFocus(); _link->showError(); _errorText = tr::lng_create_channel_link_invalid(tr::now); update(); - } else if ((error == "USERNAME_OCCUPIED") - || (error == "USERNAMES_UNAVAILABLE")) { + } else if (result == UsernameResult::Occupied) { _link->setFocus(); _link->showError(); _errorText = tr::lng_create_channel_link_occupied(tr::now); @@ -1264,20 +1293,20 @@ void SetupChannelBox::updateFail(const QString &error) { } } -void SetupChannelBox::checkFail(const QString &error) { - if (error == qstr("CHANNEL_PUBLIC_GROUP_NA")) { +void SetupChannelBox::checkFail(UsernameResult result) { + if (result == UsernameResult::NA) { Ui::hideLayer(); - } else if (error == qstr("CHANNELS_ADMIN_PUBLIC_TOO_MUCH")) { + } else if (result == UsernameResult::ChatsTooMuch) { if (_existing) { showRevokePublicLinkBoxForEdit(); } else { _tooMuchUsernames = true; _privacyGroup->setValue(Privacy::Private); } - } else if (error == qstr("USERNAME_INVALID")) { + } else if (result == UsernameResult::Invalid) { _errorText = tr::lng_create_channel_link_invalid(tr::now); update(); - } else if (error == qstr("USERNAME_OCCUPIED") + } else if ((result == UsernameResult::Occupied) && _checkUsername != _channel->username) { _errorText = tr::lng_create_channel_link_occupied(tr::now); update(); @@ -1304,10 +1333,10 @@ void SetupChannelBox::showRevokePublicLinkBoxForEdit() { Ui::LayerOption::KeepOther); } -void SetupChannelBox::firstCheckFail(const QString &error) { - if (error == qstr("CHANNEL_PUBLIC_GROUP_NA")) { +void SetupChannelBox::firstCheckFail(UsernameResult result) { + if (result == UsernameResult::NA) { Ui::hideLayer(); - } else if (error == qstr("CHANNELS_ADMIN_PUBLIC_TOO_MUCH")) { + } else if (result == UsernameResult::ChatsTooMuch) { if (_existing) { showRevokePublicLinkBoxForEdit(); } else { @@ -1604,7 +1633,7 @@ void RevokePublicLinkBox::Inner::mouseReleaseEvent(QMouseEvent *e) { _revokeRequestId = _api.request(MTPchannels_UpdateUsername( pressed->asChannel()->inputChannel, MTP_string() - )).done([=, close = std::move(close)](const MTPBool &result) { + )).done([=, close = std::move(close)] { close(); if (const auto callback = _revokeCallback) { callback(); diff --git a/Telegram/SourceFiles/boxes/add_contact_box.h b/Telegram/SourceFiles/boxes/add_contact_box.h index 0f2d884cc..885ff9d12 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.h +++ b/Telegram/SourceFiles/boxes/add_contact_box.h @@ -171,16 +171,26 @@ private: Public, Private, }; + enum class UsernameResult { + Ok, + Invalid, + Occupied, + ChatsTooMuch, + NA, + Unknown, + }; + [[nodiscard]] UsernameResult parseError(const QString &error); + void privacyChanged(Privacy value); void updateSelected(const QPoint &cursorGlobalPosition); void handleChange(); void check(); void save(); - void updateFail(const QString &error); + void updateFail(UsernameResult result); - void checkFail(const QString &error); - void firstCheckFail(const QString &error); + void checkFail(UsernameResult result); + void firstCheckFail(UsernameResult result); void updateMaxHeight(); diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 2db57a787..460d20397 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -279,49 +279,6 @@ membersAbout: FlatLabel(defaultFlatLabel) { style: boxLabelStyle; } -sessionsScroll: boxScroll; -sessionsHeight: 350px; -sessionHeight: 70px; -sessionCurrentPadding: margins(0px, 7px, 0px, 4px); -sessionCurrentHeight: 118px; -sessionPadding: margins(22px, 10px, 22px, 0px); -sessionNameFont: msgNameFont; -sessionNameFg: boxTextFg; -sessionWhenFont: msgDateFont; -sessionWhenFg: windowSubTextFg; -sessionInfoFont: msgFont; -sessionInfoFg: windowSubTextFg; -sessionTerminateTop: 28px; -sessionTerminateSkip: 22px; -sessionNamePadding: margins(0px, 0px, 5px, 0px); -sessionTerminate: IconButton { - width: 20px; - height: 20px; - - icon: smallCloseIcon; - iconOver: smallCloseIconOver; - iconPosition: point(5px, 5px); - - rippleAreaPosition: point(0px, 0px); - rippleAreaSize: 20px; - ripple: RippleAnimation(defaultRippleAnimation) { - color: windowBgOver; - } -} -sessionTerminateAllButton: LinkButton(boxLinkButton) { - color: attentionButtonFg; - overColor: attentionButtonFg; -} -sessionNameStyle: TextStyle(defaultTextStyle) { - font: sessionNameFont; -} -sessionWhenStyle: TextStyle(defaultTextStyle) { - font: sessionWhenFont; -} -sessionInfoStyle: TextStyle(defaultTextStyle) { - font: sessionInfoFont; -} - passcodeHeaderFont: font(19px); passcodeHeaderHeight: 80px; passcodeInput: InputField(introPhone) { @@ -492,8 +449,8 @@ calendarPrevious: IconButton { width: calendarTitleHeight; height: calendarTitleHeight; - icon: icon {{ "title_back", boxTitleFg }}; - iconPosition: point(20px, 20px); + icon: icon {{ "calendar_down-flip_vertical", boxTitleFg }}; + iconPosition: point(-1px, -1px); rippleAreaPosition: point(6px, 6px); rippleAreaSize: 44px; @@ -501,9 +458,9 @@ calendarPrevious: IconButton { color: windowBgOver; } } -calendarPreviousDisabled: icon {{ "title_back", menuIconFg }}; +calendarPreviousDisabled: icon {{ "calendar_down-flip_vertical", menuIconFg }}; calendarNext: IconButton(calendarPrevious) { - icon: icon {{ "title_back-flip_horizontal", boxTitleFg }}; + icon: icon {{ "calendar_down", boxTitleFg }}; } CalendarSizes { width: pixels; @@ -512,18 +469,25 @@ CalendarSizes { cellInner: pixels; padding: margins; } -calendarNextDisabled: icon {{ "title_back-flip_horizontal", menuIconFg }}; +calendarNextDisabled: icon {{ "calendar_down", menuIconFg }}; calendarTitleFont: boxTitleFont; defaultCalendarSizes: CalendarSizes { width: boxWideWidth; - daysHeight: 32px; + daysHeight: 40px; cellSize: size(48px, 40px); cellInner: 34px; - padding: margins(14px, 15px, 14px, 10px); + padding: margins(14px, 0px, 14px, 0px); } calendarDaysFont: normalFont; calendarDaysFg: boxTitleAdditionalFg; -calendarScroll: backgroundScroll; +calendarScroll: ScrollArea(defaultSolidScroll) { + deltat: 3px; + deltab: 3px; + round: 1px; + width: 8px; + deltax: 3px; + hiding: 1000; +} passcodeTextStyle: TextStyle(defaultTextStyle) { lineHeight: 20px; diff --git a/Telegram/SourceFiles/boxes/change_phone_box.cpp b/Telegram/SourceFiles/boxes/change_phone_box.cpp index e35331ecb..2a589ddde 100644 --- a/Telegram/SourceFiles/boxes/change_phone_box.cpp +++ b/Telegram/SourceFiles/boxes/change_phone_box.cpp @@ -156,7 +156,8 @@ void ChangePhoneBox::EnterPhone::prepare() { st::defaultInputField, tr::lng_change_phone_new_title(), Countries::ExtractPhoneCode(_controller->session().user()->phone()), - phoneValue); + phoneValue, + [](const QString &s) { return Countries::Groups(s); }); _phone->resize( st::boxWidth - 2 * st::boxPadding.left(), @@ -191,7 +192,7 @@ void ChangePhoneBox::EnterPhone::submit() { const auto phoneNumber = _phone->getLastText().trimmed(); _requestId = _api.request(MTPaccount_SendChangePhoneCode( MTP_string(phoneNumber), - MTP_codeSettings(MTP_flags(0)) + MTP_codeSettings(MTP_flags(0), MTP_vector()) )).done([=](const MTPauth_SentCode &result) { _requestId = 0; sendPhoneDone(result, phoneNumber); @@ -225,6 +226,10 @@ void ChangePhoneBox::EnterPhone::sendPhoneDone( LOG(("Error: should not be flashcall!")); showError(Lang::Hard::ServerError()); return false; + }, [&](const MTPDauth_sentCodeTypeMissedCall &data) { + LOG(("Error: should not be missedcall!")); + showError(Lang::Hard::ServerError()); + return false; }); if (!hasLength) { return; diff --git a/Telegram/SourceFiles/boxes/delete_messages_box.cpp b/Telegram/SourceFiles/boxes/delete_messages_box.cpp index fed0f4db3..f23a23a30 100644 --- a/Telegram/SourceFiles/boxes/delete_messages_box.cpp +++ b/Telegram/SourceFiles/boxes/delete_messages_box.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/delete_messages_box.h" #include "apiwrap.h" +#include "api/api_chat_participants.h" #include "base/unixtime.h" #include "data/data_channel.h" #include "data/data_chat.h" @@ -39,7 +40,7 @@ DeleteMessagesBox::DeleteMessagesBox( _moderateBan = item->suggestBanReport(); _moderateDeleteAll = item->suggestDeleteAllReport(); if (_moderateBan || _moderateDeleteAll) { - _moderateFrom = item->from()->asUser(); + _moderateFrom = item->from(); _moderateInChannel = item->history()->peer->asChannel(); } } @@ -54,6 +55,18 @@ DeleteMessagesBox::DeleteMessagesBox( Expects(!_ids.empty()); } +DeleteMessagesBox::DeleteMessagesBox( + QWidget*, + not_null peer, + QDate firstDayToDelete, + QDate lastDayToDelete) +: _session(&peer->session()) +, _wipeHistoryPeer(peer) +, _wipeHistoryJustClear(true) +, _wipeHistoryFirstToDelete(firstDayToDelete) +, _wipeHistoryLastToDelete(lastDayToDelete) { +} + DeleteMessagesBox::DeleteMessagesBox( QWidget*, not_null peer, @@ -73,7 +86,27 @@ void DeleteMessagesBox::prepare() { auto deleteStyle = &st::defaultBoxButton; auto canDelete = true; if (const auto peer = _wipeHistoryPeer) { - if (_wipeHistoryJustClear) { + if (!_wipeHistoryFirstToDelete.isNull()) { + details = (_wipeHistoryFirstToDelete + == _wipeHistoryLastToDelete) + ? tr::lng_sure_delete_by_date_one( + tr::now, + lt_date, + TextWithEntities{ + langDayOfMonthFull(_wipeHistoryFirstToDelete) }, + Ui::Text::RichLangValue) + : tr::lng_sure_delete_by_date_many( + tr::now, + lt_days, + tr::lng_sure_delete_selected_days( + tr::now, + lt_count, + _wipeHistoryFirstToDelete.daysTo( + _wipeHistoryLastToDelete) + 1, + Ui::Text::WithEntities), + Ui::Text::RichLangValue); + deleteStyle = &st::attentionBoxButton; + } else if (_wipeHistoryJustClear) { const auto isChannel = peer->isBroadcast(); const auto isPublicGroup = peer->isMegagroup() && peer->asChannel()->isPublic(); @@ -397,16 +430,41 @@ void DeleteMessagesBox::keyPressEvent(QKeyEvent *e) { void DeleteMessagesBox::deleteAndClear() { const auto revoke = _revoke ? _revoke->checked() : false; - if (const auto peer = _wipeHistoryPeer) { + const auto session = _session; + const auto invokeCallbackAndClose = [&] { + // deleteMessages can initiate closing of the current section, + // which will cause this box to be destroyed. + const auto weak = Ui::MakeWeak(this); + if (const auto callback = _deleteConfirmedCallback) { + callback(); + } + if (const auto strong = weak.data()) { + strong->closeBox(); + } + }; + if (!_wipeHistoryFirstToDelete.isNull()) { + const auto peer = _wipeHistoryPeer; + const auto firstDayToDelete = _wipeHistoryFirstToDelete; + const auto lastDayToDelete = _wipeHistoryLastToDelete; + + invokeCallbackAndClose(); + session->data().histories().deleteMessagesByDates( + session->data().history(peer), + firstDayToDelete, + lastDayToDelete, + revoke); + session->data().sendHistoryChangeNotifications(); + return; + } else if (const auto peer = _wipeHistoryPeer) { const auto justClear = _wipeHistoryJustClear; - closeBox(); + invokeCallbackAndClose(); if (justClear) { - peer->session().api().clearHistory(peer, revoke); + session->api().clearHistory(peer, revoke); } else { - for (const auto &controller : peer->session().windows()) { + for (const auto &controller : session->windows()) { if (controller->activeChatCurrent().peer() == peer) { - Ui::showChatsList(&peer->session()); + Ui::showChatsList(session); } } // Don't delete old history by default, @@ -415,13 +473,13 @@ void DeleteMessagesBox::deleteAndClear() { //if (const auto from = peer->migrateFrom()) { // peer->session().api().deleteConversation(from, false); //} - peer->session().api().deleteConversation(peer, revoke); + session->api().deleteConversation(peer, revoke); } return; } if (_moderateFrom) { if (_banUser && _banUser->checked()) { - _moderateInChannel->session().api().kickParticipant( + _moderateInChannel->session().api().chatParticipants().kick( _moderateInChannel, _moderateFrom, ChatRestrictionsInfo()); @@ -430,30 +488,19 @@ void DeleteMessagesBox::deleteAndClear() { _moderateInChannel->session().api().request( MTPchannels_ReportSpam( _moderateInChannel->inputChannel, - _moderateFrom->inputUser, + _moderateFrom->input, MTP_vector(1, MTP_int(_ids[0].msg))) ).send(); } if (_deleteAll && _deleteAll->checked()) { - _moderateInChannel->session().api().deleteAllFromUser( + _moderateInChannel->session().api().deleteAllFromParticipant( _moderateInChannel, _moderateFrom); } } - if (_deleteConfirmedCallback) { - _deleteConfirmedCallback(); - } - - // deleteMessages can initiate closing of the current section, - // which will cause this box to be destroyed. - const auto session = _session; - const auto weak = Ui::MakeWeak(this); - - session->data().histories().deleteMessages(_ids, revoke); - - if (const auto strong = weak.data()) { - strong->closeBox(); - } + const auto ids = _ids; + invokeCallbackAndClose(); + session->data().histories().deleteMessages(ids, revoke); session->data().sendHistoryChangeNotifications(); } diff --git a/Telegram/SourceFiles/boxes/delete_messages_box.h b/Telegram/SourceFiles/boxes/delete_messages_box.h index 627f433df..61b8773e7 100644 --- a/Telegram/SourceFiles/boxes/delete_messages_box.h +++ b/Telegram/SourceFiles/boxes/delete_messages_box.h @@ -29,6 +29,11 @@ public: QWidget*, not_null session, MessageIdsList &&selected); + DeleteMessagesBox( + QWidget*, + not_null peer, + QDate firstDayToDelete, + QDate lastDayToDelete); DeleteMessagesBox(QWidget*, not_null peer, bool justClear); void setDeleteConfirmedCallback(Fn callback) { @@ -56,8 +61,10 @@ private: PeerData * const _wipeHistoryPeer = nullptr; const bool _wipeHistoryJustClear = false; + const QDate _wipeHistoryFirstToDelete; + const QDate _wipeHistoryLastToDelete; const MessageIdsList _ids; - UserData *_moderateFrom = nullptr; + PeerData *_moderateFrom = nullptr; ChannelData *_moderateInChannel = nullptr; bool _moderateBan = false; bool _moderateDeleteAll = false; diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp index 09c9ccdd0..1e61643e9 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp @@ -533,7 +533,7 @@ void EditCaptionBox::emojiFilterForGeometry(not_null event) { void EditCaptionBox::updateEmojiPanelGeometry() { const auto parent = _emojiPanel->parentWidget(); - const auto global = _emojiToggle->mapToGlobal({ 0, 0 }); + const auto global = _emojiToggle->mapToGlobal(QPoint()); const auto local = parent->mapFromGlobal(global); _emojiPanel->moveBottomRight( local.y(), @@ -670,8 +670,7 @@ void EditCaptionBox::save() { options.scheduled = item->isScheduled() ? item->date() : 0; if (!_preparedList.files.empty()) { - auto action = Api::SendAction(item->history()); - action.options = options; + auto action = Api::SendAction(item->history(), options); action.replaceMediaOf = item->fullId().msg; Storage::ApplyModifications(_preparedList); diff --git a/Telegram/SourceFiles/boxes/language_box.cpp b/Telegram/SourceFiles/boxes/language_box.cpp index 2bca858d5..060b1ff68 100644 --- a/Telegram/SourceFiles/boxes/language_box.cpp +++ b/Telegram/SourceFiles/boxes/language_box.cpp @@ -963,8 +963,8 @@ void Content::setupContent( const auto selectedCoords = [=] { const auto coords = [=](Rows *rows, int index) { const auto result = rows->rowScrollRequest(index); - const auto shift = rows->mapToGlobal({ 0, 0 }).y() - - mapToGlobal({ 0, 0 }).y(); + const auto shift = rows->mapToGlobal(QPoint()).y() + - mapToGlobal(QPoint()).y(); return Ui::ScrollToRequest( result.ymin + shift, result.ymax + shift); diff --git a/Telegram/SourceFiles/boxes/mute_settings_box.cpp b/Telegram/SourceFiles/boxes/mute_settings_box.cpp index 4dade2b9c..df8e56b5e 100644 --- a/Telegram/SourceFiles/boxes/mute_settings_box.cpp +++ b/Telegram/SourceFiles/boxes/mute_settings_box.cpp @@ -147,7 +147,7 @@ void MuteSettingsBox::prepare() { case QEvent::ContextMenu: case QEvent::MouseButtonDblClick: - return base::EventFilterResult::Cancel; + return base::EventFilterResult::Cancel; case QEvent::MouseButtonPress: if (group->value() != kCustomFor) { @@ -175,8 +175,8 @@ void MuteSettingsBox::prepare() { }); } - const auto parentTopLeft = window()->mapToGlobal({ 0, 0 }); - const auto inputTopLeft = _forPeriodInput->mapToGlobal({ 0, 0 }); + const auto parentTopLeft = window()->mapToGlobal(QPoint()); + const auto inputTopLeft = _forPeriodInput->mapToGlobal(QPoint()); const auto parentRect = QRect(parentTopLeft, window()->size()); const auto inputRect = QRect(inputTopLeft, _forPeriodInput->size()); _menu->move( diff --git a/Telegram/SourceFiles/boxes/passcode_box.cpp b/Telegram/SourceFiles/boxes/passcode_box.cpp index b94915bc1..c90defd25 100644 --- a/Telegram/SourceFiles/boxes/passcode_box.cpp +++ b/Telegram/SourceFiles/boxes/passcode_box.cpp @@ -479,10 +479,6 @@ void PasscodeBox::closeReplacedBy() { } } -void PasscodeBox::setPasswordFail(const MTP::Error &error) { - setPasswordFail(error.type()); -} - void PasscodeBox::setPasswordFail(const QString &type) { if (MTP::IsFloodError(type)) { closeReplacedBy(); @@ -534,7 +530,7 @@ void PasscodeBox::setPasswordFail( validateEmail(email, codeLength, newPasswordBytes); } else { - setPasswordFail(error); + setPasswordFail(error.type()); } } @@ -551,7 +547,7 @@ void PasscodeBox::validateEmail( } _setRequest = _api.request(MTPaccount_ConfirmPasswordEmail( MTP_string(code) - )).done([=](const MTPBool &result) { + )).done([=] { *set = true; setPasswordDone(newPasswordBytes); }).fail([=](const MTP::Error &error) { @@ -580,10 +576,10 @@ void PasscodeBox::validateEmail( return; } _setRequest = _api.request(MTPaccount_ResendPasswordEmail( - )).done([=](const MTPBool &result) { + )).done([=] { _setRequest = 0; resent->fire(tr::lng_cloud_password_resent(tr::now)); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _setRequest = 0; errors->fire(Lang::Hard::ServerError()); }).send(); @@ -835,7 +831,7 @@ void PasscodeBox::sendClearCloudPassword( MTP_string(hint), MTP_string(email), MTPSecureSecretSettings()) - )).done([=](const MTPBool &result) { + )).done([=] { setPasswordDone({}); }).fail([=](const MTP::Error &error) mutable { setPasswordFail({}, QString(), error); @@ -870,7 +866,7 @@ void PasscodeBox::setNewCloudPassword(const QString &newPassword) { _setRequest = _api.request(MTPaccount_UpdatePasswordSettings( MTP_inputCheckPasswordEmpty(), settings - )).done([=](const MTPBool &result) { + )).done([=] { setPasswordDone(newPasswordBytes); }).fail([=](const MTP::Error &error) { setPasswordFail(newPasswordBytes, email, error); @@ -943,7 +939,7 @@ void PasscodeBox::changeCloudPassword( }); } }).fail([=](const MTP::Error &error) { - setPasswordFail(error); + setPasswordFail(error.type()); }).handleFloodErrors().send(); } @@ -977,7 +973,7 @@ void PasscodeBox::resetSecret( MTP_securePasswordKdfAlgoUnknown(), // secure_algo MTP_bytes(), // secure_secret MTP_long(0))) // secure_secret_id - )).done([=](const MTPBool &result) { + )).done([=] { _setRequest = 0; callback(); checkPasswordHash([=](const Core::CloudPasswordResult &check) { @@ -1031,7 +1027,7 @@ void PasscodeBox::sendChangeCloudPassword( Core::PrepareSecureSecretAlgo(_cloudFields.newSecureSecretAlgo), MTP_bytes(newSecureSecret), MTP_long(newSecureSecretId))) - )).done([=](const MTPBool &result) { + )).done([=] { setPasswordDone(newPasswordBytes); }).fail([=](const MTP::Error &error) { setPasswordFail(newPasswordBytes, QString(), error); @@ -1262,7 +1258,7 @@ void RecoverBox::submit() { // From "Change cloud password". _submitRequest = _api.request(MTPauth_CheckRecoveryPassword( MTP_string(code) - )).done([=](const MTPBool &result) { + )).done([=] { proceedToChange(code); }).fail([=](const MTP::Error &error) { checkSubmitFail(error); @@ -1383,7 +1379,7 @@ RecoveryEmailValidation ConfirmRecoveryEmail( } *requestId = session->api().request(MTPaccount_ConfirmPasswordEmail( MTP_string(code) - )).done([=](const MTPBool &result) { + )).done([=] { *requestId = 0; reloads->fire({}); if (*weak) { @@ -1417,10 +1413,10 @@ RecoveryEmailValidation ConfirmRecoveryEmail( return; } *requestId = session->api().request(MTPaccount_ResendPasswordEmail( - )).done([=](const MTPBool &result) { + )).done([=] { *requestId = 0; resent->fire(tr::lng_cloud_password_resent(tr::now)); - }).fail([=](const MTP::Error &error) { + }).fail([=] { *requestId = 0; errors->fire(Lang::Hard::ServerError()); }).send(); @@ -1439,15 +1435,14 @@ RecoveryEmailValidation ConfirmRecoveryEmail( } [[nodiscard]] object_ptr PrePasswordErrorBox( - const MTP::Error &error, + const QString &error, not_null session, TextWithEntities &&about) { const auto type = [&] { - const auto &type = error.type(); - if (type == qstr("PASSWORD_MISSING")) { + if (error == u"PASSWORD_MISSING"_q) { return PasswordErrorType::NoPassword; - } else if (type.startsWith(qstr("PASSWORD_TOO_FRESH_")) - || type.startsWith(qstr("SESSION_TOO_FRESH_"))) { + } else if (error.startsWith(u"PASSWORD_TOO_FRESH_"_q) + || error.startsWith(u"SESSION_TOO_FRESH_"_q)) { return PasswordErrorType::Later; } return PasswordErrorType::None; diff --git a/Telegram/SourceFiles/boxes/passcode_box.h b/Telegram/SourceFiles/boxes/passcode_box.h index 40683a17d..4d731d749 100644 --- a/Telegram/SourceFiles/boxes/passcode_box.h +++ b/Telegram/SourceFiles/boxes/passcode_box.h @@ -98,7 +98,6 @@ private: void recoverPasswordDone( const QByteArray &newPasswordBytes, const MTPauth_Authorization &result); - void setPasswordFail(const MTP::Error &error); void setPasswordFail(const QString &type); void setPasswordFail( const QByteArray &newPasswordBytes, @@ -244,6 +243,6 @@ struct RecoveryEmailValidation { const QString &pattern); [[nodiscard]] object_ptr PrePasswordErrorBox( - const MTP::Error &error, + const QString &error, not_null session, TextWithEntities &&about); diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index d821c3556..185714985 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -437,21 +437,11 @@ PeerListRow::PeerListRow(not_null peer) PeerListRow::PeerListRow(not_null peer, PeerListRowId id) : _id(id) -, _peer(peer) -, _hidden(false) -, _initialized(false) -, _isSearchResult(false) -, _isSavedMessagesChat(false) -, _isRepliesMessagesChat(false) { +, _peer(peer) { } PeerListRow::PeerListRow(PeerListRowId id) -: _id(id) -, _hidden(false) -, _initialized(false) -, _isSearchResult(false) -, _isSavedMessagesChat(false) -, _isRepliesMessagesChat(false) { +: _id(id) { } PeerListRow::~PeerListRow() = default; diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index 6fd2779d8..4289f7956 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -275,11 +275,11 @@ private: base::flat_set _nameFirstLetters; int _absoluteIndex = -1; State _disabledState = State::Active; - bool _hidden : 1; - bool _initialized : 1; - bool _isSearchResult : 1; - bool _isSavedMessagesChat : 1; - bool _isRepliesMessagesChat : 1; + bool _hidden : 1 = false; + bool _initialized : 1 = false; + bool _isSearchResult : 1 = false; + bool _isSavedMessagesChat : 1 = false; + bool _isRepliesMessagesChat : 1 = false; }; diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp index ef73edb6c..bb8fc79eb 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/peer_list_controllers.h" +#include "api/api_chat_participants.h" #include "base/random.h" #include "ui/boxes/confirm_box.h" #include "ui/widgets/checkbox.h" @@ -50,7 +51,8 @@ void ShareBotGame(not_null bot, not_null chat) { MTP_long(randomId), MTPReplyMarkup(), MTPVector(), - MTP_int(0) // schedule_date + MTP_int(0), // schedule_date + MTPInputPeer() // send_as )).done([=](const MTPUpdates &result) { api->applyUpdates(result, randomId); finish(); @@ -70,7 +72,7 @@ void AddBotToGroup(not_null bot, not_null chat) { if (bot->isBot() && !bot->botInfo->startGroupToken.isEmpty()) { chat->session().api().sendBotStart(bot, chat); } else { - chat->session().api().addChatParticipants(chat, { 1, bot }); + chat->session().api().chatParticipants().add(chat, { 1, bot }); } Ui::hideLayer(); Ui::showPeerHistory(chat, ShowAtUnreadMsgId); diff --git a/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp index 9c7aeb168..1c70ee885 100644 --- a/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/peers/add_participants_box.h" +#include "api/api_chat_participants.h" #include "boxes/peers/edit_participant_box.h" #include "boxes/peers/edit_peer_type_box.h" #include "ui/boxes/confirm_box.h" @@ -231,7 +232,7 @@ bool AddParticipantsBoxController::inviteSelectedUsers( if (users.empty()) { return false; } - _peer->session().api().addChatParticipants(_peer, users); + _peer->session().api().chatParticipants().add(_peer, users); return true; } @@ -464,10 +465,10 @@ void AddSpecialBoxController::loadMoreRows() { MTP_long(participantsHash) )).done([=](const MTPchannels_ChannelParticipants &result) { _loadRequestId = 0; - auto &session = channel->session(); - session.api().parseChannelParticipants(channel, result, [&]( - int availableCount, - const QVector &list) { + result.match([&](const MTPDchannels_channelParticipants &data) { + const auto &[availableCount, list] = Api::ChatParticipants::Parse( + channel, + data); for (const auto &data : list) { if (const auto participant = _additional.applyParticipant( data)) { @@ -480,15 +481,16 @@ void AddSpecialBoxController::loadMoreRows() { // To be sure - wait for a whole empty result list. _allLoaded = true; } + }, [&](const MTPDchannels_channelParticipantsNotModified &) { + LOG(("API Error: channels.channelParticipantsNotModified received!")); }); - if (delegate()->peerListFullRowsCount() > 0) { setDescriptionText(QString()); } else if (_allLoaded) { setDescriptionText(tr::lng_blocked_list_not_found(tr::now)); } delegate()->peerListRefreshRows(); - }).fail([this](const MTP::Error &error) { + }).fail([this] { _loadRequestId = 0; }).send(); } @@ -524,10 +526,11 @@ bool AddSpecialBoxController::checkInfoLoaded( )).done([=](const MTPchannels_ChannelParticipant &result) { result.match([&](const MTPDchannels_channelParticipant &data) { channel->owner().processUsers(data.vusers()); - _additional.applyParticipant(data.vparticipant()); + _additional.applyParticipant( + Api::ChatParticipant(data.vparticipant(), channel)); }); callback(); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _additional.setExternal(participant); callback(); }).send(); @@ -658,37 +661,7 @@ void AddSpecialBoxController::editAdminDone( _editParticipantBox->closeBox(); } - const auto date = base::unixtime::now(); // Incorrect, but ignored. - if (_additional.isCreator(user) && user->isSelf()) { - using Flag = MTPDchannelParticipantCreator::Flag; - _additional.applyParticipant(MTP_channelParticipantCreator( - MTP_flags(rank.isEmpty() ? Flag(0) : Flag::f_rank), - peerToBareMTPInt(user->id), - MTP_chatAdminRights( - MTP_flags(MTPDchatAdminRights::Flags::from_raw( - uint32(rights.flags)))), - MTP_string(rank))); - } else if (!rights.flags) { - _additional.applyParticipant(MTP_channelParticipant( - peerToBareMTPInt(user->id), - MTP_int(date))); - } else { - using Flag = MTPDchannelParticipantAdmin::Flag; - const auto alreadyPromotedBy = _additional.adminPromotedBy(user); - _additional.applyParticipant(MTP_channelParticipantAdmin( - MTP_flags(Flag::f_can_edit - | (rank.isEmpty() ? Flag(0) : Flag::f_rank)), - peerToBareMTPInt(user->id), - MTPlong(), // inviter_id - peerToBareMTPInt(alreadyPromotedBy - ? alreadyPromotedBy->id - : user->session().userPeerId()), - MTP_int(date), - MTP_chatAdminRights( - MTP_flags(MTPDchatAdminRights::Flags::from_raw( - uint32(rights.flags)))), - MTP_string(rank))); - } + _additional.applyAdminLocally(user, rights, rank); if (const auto callback = _adminDoneCallback) { callback(user, rights, rank); } @@ -765,33 +738,7 @@ void AddSpecialBoxController::editRestrictedDone( _editParticipantBox->closeBox(); } - const auto date = base::unixtime::now(); // Incorrect, but ignored. - if (!rights.flags) { - if (const auto user = participant->asUser()) { - _additional.applyParticipant(MTP_channelParticipant( - peerToBareMTPInt(user->id), - MTP_int(date))); - } else { - _additional.setExternal(participant); - } - } else { - const auto kicked = rights.flags & ChatRestriction::ViewMessages; - const auto alreadyRestrictedBy = _additional.restrictedBy( - participant); - _additional.applyParticipant(MTP_channelParticipantBanned( - MTP_flags(kicked - ? MTPDchannelParticipantBanned::Flag::f_left - : MTPDchannelParticipantBanned::Flag(0)), - peerToMTP(participant->id), - peerToBareMTPInt(alreadyRestrictedBy - ? alreadyRestrictedBy->id - : participant->session().userPeerId()), - MTP_int(date), - MTP_chatBannedRights( - MTP_flags(MTPDchatBannedRights::Flags::from_raw( - uint32(rights.flags))), - MTP_int(rights.until)))); - } + _additional.applyBannedLocally(participant, rights); if (const auto callback = _bannedDoneCallback) { callback(participant, rights); } @@ -1021,7 +968,7 @@ void AddSpecialBoxSearchController::searchParticipantsDone( const auto channel = _peer->asChannel(); auto query = _query; if (requestId) { - const auto addToCache = [&](auto&&...) { + const auto addToCache = [&] { auto it = _participantsQueries.find(requestId); if (it != _participantsQueries.cend()) { query = it->second.text; @@ -1033,10 +980,13 @@ void AddSpecialBoxSearchController::searchParticipantsDone( _participantsQueries.erase(it); } }; - channel->session().api().parseChannelParticipants( - channel, - result, - addToCache); + result.match([&](const MTPDchannels_channelParticipants &data) { + Api::ChatParticipants::Parse(channel, data); + addToCache(); + }, [&](const MTPDchannels_channelParticipantsNotModified &) { + LOG(("API Error: " + "channels.channelParticipantsNotModified received!")); + }); } if (_requestId != requestId) { @@ -1056,7 +1006,8 @@ void AddSpecialBoxSearchController::searchParticipantsDone( } } for (const auto &data : list) { - if (const auto user = _additional->applyParticipant(data)) { + if (const auto user = _additional->applyParticipant( + Api::ChatParticipant(data, channel))) { delegate()->peerListSearchAddRow(user); } } diff --git a/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp index 9de98a6ed..7c5767463 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_contact_box.cpp @@ -73,7 +73,6 @@ void SendRequest( lt_user, first)); } - }).fail([=](const MTP::Error &error) { }).send(); } diff --git a/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp index bef92b703..3bd4ea453 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp @@ -469,15 +469,12 @@ void EditAdminBox::transferOwnership() { MTP_inputCheckPasswordEmpty() )).fail([=](const MTP::Error &error) { _checkTransferRequestId = 0; - if (!handleTransferPasswordError(error)) { - const auto box = std::make_shared>(); - const auto callback = crl::guard(this, [=] { + if (!handleTransferPasswordError(error.type())) { + const auto callback = crl::guard(this, [=](Fn &&close) { transferOwnershipChecked(); - if (*box) { - (*box)->closeBox(); - } + close(); }); - *box = getDelegate()->show(Box( + getDelegate()->show(Box( tr::lng_rights_transfer_about( tr::now, lt_group, @@ -491,7 +488,7 @@ void EditAdminBox::transferOwnership() { }).send(); } -bool EditAdminBox::handleTransferPasswordError(const MTP::Error &error) { +bool EditAdminBox::handleTransferPasswordError(const QString &error) { const auto session = &user()->session(); auto about = tr::lng_rights_transfer_check_about( tr::now, @@ -722,20 +719,22 @@ void EditRestrictedBox::showRestrictUntil() { : base::unixtime::parse(getRealUntilValue()).date(); auto month = highlighted; _restrictUntilBox = Ui::show( - Box( - month, - highlighted, - [this](const QDate &date) { + Box(Ui::CalendarBoxArgs{ + .month = month, + .highlighted = highlighted, + .callback = [=](const QDate &date) { setRestrictUntil( static_cast(date.startOfDay().toSecsSinceEpoch())); - }), + }, + .finalize = [=](not_null box) { + box->addLeftButton( + tr::lng_rights_chat_banned_forever(), + [=] { setRestrictUntil(0); }); + }, + .minDate = tomorrow, + .maxDate = QDate::currentDate().addDays(kMaxRestrictDelayDays), + }), Ui::LayerOption::KeepOther); - _restrictUntilBox->setMaxDate( - QDate::currentDate().addDays(kMaxRestrictDelayDays)); - _restrictUntilBox->setMinDate(tomorrow); - _restrictUntilBox->addLeftButton( - tr::lng_rights_chat_banned_forever(), - [=] { setRestrictUntil(0); }); } void EditRestrictedBox::setRestrictUntil(TimeId until) { diff --git a/Telegram/SourceFiles/boxes/peers/edit_participant_box.h b/Telegram/SourceFiles/boxes/peers/edit_participant_box.h index a2c8352a3..75c88365f 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participant_box.h +++ b/Telegram/SourceFiles/boxes/peers/edit_participant_box.h @@ -9,11 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/abstract_box.h" #include "base/unique_qptr.h" -#include "data/data_peer.h" - -namespace MTP { -class Error; -} // namespace MTP +#include "data/data_chat_participant_status.h" namespace Ui { class FlatLabel; @@ -96,7 +92,7 @@ private: not_null addRankInput(); void transferOwnership(); void transferOwnershipChecked(); - bool handleTransferPasswordError(const MTP::Error &error); + bool handleTransferPasswordError(const QString &error); void requestTransferPassword(not_null channel); void sendTransferRequestFrom( QPointer box, diff --git a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp index a1d927e70..d78871812 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peers/edit_participants_box.h" #include "kotato/kotato_lang.h" +#include "api/api_chat_participants.h" #include "boxes/peer_list_controllers.h" #include "boxes/peers/edit_participant_box.h" #include "boxes/peers/add_participants_box.h" @@ -63,7 +64,7 @@ void RemoveAdmin( if (onDone) { onDone(); } - }).fail([=](const MTP::Error &error) { + }).fail([=] { if (onFail) { onFail(); } @@ -103,7 +104,7 @@ void SaveChatAdmin( chat->inputChat, user->inputUser, MTP_bool(isAdmin) - )).done([=](const MTPBool &result) { + )).done([=] { chat->applyEditAdmin(user, isAdmin); if (onDone) { onDone(); @@ -170,7 +171,7 @@ void SaveChannelRestriction( if (onDone) { onDone(); } - }).fail([=](const MTP::Error &error) { + }).fail([=] { if (onFail) { onFail(); } @@ -191,7 +192,7 @@ void SaveChatParticipantKick( if (onDone) { onDone(); } - }).fail([=](const MTP::Error &error) { + }).fail([=] { if (onFail) { onFail(); } @@ -533,50 +534,114 @@ void ParticipantsAdditionalData::fillFromChannel( } } +void ParticipantsAdditionalData::applyAdminLocally( + UserData *user, + ChatAdminRightsInfo rights, + const QString &rank) { + if (isCreator(user) && user->isSelf()) { + applyParticipant(Api::ChatParticipant( + Api::ChatParticipant::Type::Creator, + user->id, + UserId(), + ChatRestrictionsInfo(), + std::move(rights), + true, // As the creator is self. + rank)); + } else if (!rights.flags) { + applyParticipant(Api::ChatParticipant( + Api::ChatParticipant::Type::Member, + user->id, + UserId(), + ChatRestrictionsInfo(), + ChatAdminRightsInfo())); + } else { + const auto alreadyPromotedBy = adminPromotedBy(user); + applyParticipant(Api::ChatParticipant( + Api::ChatParticipant::Type::Admin, + user->id, + alreadyPromotedBy + ? peerToUser(alreadyPromotedBy->id) + : user->session().userId(), + ChatRestrictionsInfo(), + std::move(rights), + true, + rank)); + } +} + +void ParticipantsAdditionalData::applyBannedLocally( + not_null participant, + ChatRestrictionsInfo rights) { + const auto user = participant->asUser(); + if (!rights.flags) { + if (user) { + applyParticipant(Api::ChatParticipant( + Api::ChatParticipant::Type::Member, + user->id, + UserId(), + ChatRestrictionsInfo(), + ChatAdminRightsInfo())); + } else { + setExternal(participant); + } + } else { + const auto kicked = rights.flags & ChatRestriction::ViewMessages; + const auto alreadyRestrictedBy = restrictedBy(participant); + applyParticipant(Api::ChatParticipant( + kicked + ? Api::ChatParticipant::Type::Banned + : Api::ChatParticipant::Type::Restricted, + participant->id, + alreadyRestrictedBy + ? peerToUser(alreadyRestrictedBy->id) + : participant->session().userId(), + std::move(rights), + ChatAdminRightsInfo())); + } +} + PeerData *ParticipantsAdditionalData::applyParticipant( - const MTPChannelParticipant &data) { + const Api::ChatParticipant &data) { return applyParticipant(data, _role); } PeerData *ParticipantsAdditionalData::applyParticipant( - const MTPChannelParticipant &data, + const Api::ChatParticipant &data, Role overrideRole) { const auto logBad = [&]() -> PeerData* { LOG(("API Error: Bad participant type %1 got " "while requesting for participants, role: %2" - ).arg(data.type() + ).arg(static_cast(data.type()) ).arg(static_cast(overrideRole))); return nullptr; }; - return data.match([&]( - const MTPDchannelParticipantCreator &data) -> PeerData* { + switch (data.type()) { + case Api::ChatParticipant::Type::Creator: { if (overrideRole != Role::Profile && overrideRole != Role::Members && overrideRole != Role::Admins) { return logBad(); } return applyCreator(data); - }, [&](const MTPDchannelParticipantAdmin &data) -> PeerData* { + } + case Api::ChatParticipant::Type::Admin: { if (overrideRole != Role::Profile && overrideRole != Role::Members && overrideRole != Role::Admins) { return logBad(); } return applyAdmin(data); - }, [&](const MTPDchannelParticipantSelf &data) -> PeerData* { + } + case Api::ChatParticipant::Type::Member: { if (overrideRole != Role::Profile && overrideRole != Role::Members) { return logBad(); } - return applyRegular(data.vuser_id()); - }, [&](const MTPDchannelParticipant &data) -> PeerData* { - if (overrideRole != Role::Profile - && overrideRole != Role::Members) { - return logBad(); - } - return applyRegular(data.vuser_id()); - }, [&](const MTPDchannelParticipantBanned &data) { + return applyRegular(data.userId()); + } + case Api::ChatParticipant::Type::Restricted: + case Api::ChatParticipant::Type::Banned: if (overrideRole != Role::Profile && overrideRole != Role::Members && overrideRole != Role::Restricted @@ -584,23 +649,24 @@ PeerData *ParticipantsAdditionalData::applyParticipant( return logBad(); } return applyBanned(data); - }, [&](const MTPDchannelParticipantLeft &data) { + case Api::ChatParticipant::Type::Left: return logBad(); - }); + }; + Unexpected("Api::ChatParticipant::type in applyParticipant."); } UserData *ParticipantsAdditionalData::applyCreator( - const MTPDchannelParticipantCreator &data) { - if (const auto user = applyRegular(data.vuser_id())) { + const Api::ChatParticipant &data) { + if (const auto user = applyRegular(data.userId())) { _creator = user; - _adminRights[user] = ChatAdminRightsInfo(data.vadmin_rights()); + _adminRights[user] = data.rights(); if (user->isSelf()) { _adminCanEdit.emplace(user); } else { _adminCanEdit.erase(user); } - if (const auto rank = data.vrank()) { - _adminRanks[user] = qs(*rank); + if (!data.rank().isEmpty()) { + _adminRanks[user] = data.rank(); } else { _adminRanks.remove(user); } @@ -610,8 +676,8 @@ UserData *ParticipantsAdditionalData::applyCreator( } UserData *ParticipantsAdditionalData::applyAdmin( - const MTPDchannelParticipantAdmin &data) { - const auto user = _peer->owner().userLoaded(UserId(data.vuser_id().v)); + const Api::ChatParticipant &data) { + const auto user = _peer->owner().userLoaded(data.userId()); if (!user) { return nullptr; } else if (const auto chat = _peer->asChat()) { @@ -624,18 +690,18 @@ UserData *ParticipantsAdditionalData::applyAdmin( _restrictedRights.erase(user); _kicked.erase(user); _restrictedBy.erase(user); - _adminRights[user] = ChatAdminRightsInfo(data.vadmin_rights()); - if (data.is_can_edit()) { + _adminRights[user] = data.rights(); + if (data.canBeEdited()) { _adminCanEdit.emplace(user); } else { _adminCanEdit.erase(user); } - if (const auto rank = data.vrank()) { - _adminRanks[user] = qs(*rank); + if (!data.rank().isEmpty()) { + _adminRanks[user] = data.rank(); } else { _adminRanks.remove(user); } - if (const auto by = _peer->owner().userLoaded(data.vpromoted_by())) { + if (const auto by = _peer->owner().userLoaded(data.by())) { const auto i = _adminPromotedBy.find(user); if (i == _adminPromotedBy.end()) { _adminPromotedBy.emplace(user, by); @@ -644,12 +710,12 @@ UserData *ParticipantsAdditionalData::applyAdmin( } } else { LOG(("API Error: No user %1 for admin promoted by." - ).arg(data.vpromoted_by().v)); + ).arg(data.by().bare)); } return user; } -UserData *ParticipantsAdditionalData::applyRegular(MTPlong userId) { +UserData *ParticipantsAdditionalData::applyRegular(UserId userId) { const auto user = _peer->owner().userLoaded(userId); if (!user) { return nullptr; @@ -670,9 +736,8 @@ UserData *ParticipantsAdditionalData::applyRegular(MTPlong userId) { } PeerData *ParticipantsAdditionalData::applyBanned( - const MTPDchannelParticipantBanned &data) { - const auto participant = _peer->owner().peerLoaded( - peerFromMTP(data.vpeer())); + const Api::ChatParticipant &data) { + const auto participant = _peer->owner().peerLoaded(data.id()); if (!participant) { return nullptr; } @@ -683,14 +748,13 @@ PeerData *ParticipantsAdditionalData::applyBanned( _adminCanEdit.erase(user); _adminPromotedBy.erase(user); } - if (data.is_left()) { + if (data.isKicked()) { _kicked.emplace(participant); } else { _kicked.erase(participant); } - _restrictedRights[participant] = ChatRestrictionsInfo( - data.vbanned_rights()); - if (const auto by = _peer->owner().userLoaded(data.vkicked_by())) { + _restrictedRights[participant] = data.restrictions(); + if (const auto by = _peer->owner().userLoaded(data.by())) { const auto i = _restrictedBy.find(participant); if (i == _restrictedBy.end()) { _restrictedBy.emplace(participant, by); @@ -1327,22 +1391,11 @@ void ParticipantsBoxController::loadMoreRows() { auto wasRecentRequest = firstLoad && (_role == Role::Members || _role == Role::Profile); - auto parseParticipants = [&](auto &&result, auto &&callback) { - if (wasRecentRequest) { - channel->session().api().parseRecentChannelParticipants( - channel, - result, - callback); - } else { - channel->session().api().parseChannelParticipants( - channel, - result, - callback); - } - }; - parseParticipants(result, [&]( - int availableCount, - const QVector &list) { + + result.match([&](const MTPDchannels_channelParticipants &data) { + const auto &[availableCount, list] = wasRecentRequest + ? Api::ChatParticipants::ParseRecent(channel, data) + : Api::ChatParticipants::Parse(channel, data); for (const auto &data : list) { if (const auto participant = _additional.applyParticipant( data)) { @@ -1355,6 +1408,9 @@ void ParticipantsBoxController::loadMoreRows() { // To be sure - wait for a whole empty result list. _allLoaded = true; } + }, [](const MTPDchannels_channelParticipantsNotModified &) { + LOG(("API Error: " + "channels.channelParticipantsNotModified received!")); }); if (_allLoaded @@ -1365,7 +1421,7 @@ void ParticipantsBoxController::loadMoreRows() { _onlineSorter->sort(); } delegate()->peerListRefreshRows(); - }).fail([this](const MTP::Error &error) { + }).fail([this] { _loadRequestId = 0; }).send(); } @@ -1596,43 +1652,18 @@ void ParticipantsBoxController::editAdminDone( _editParticipantBox->closeBox(); } - const auto date = base::unixtime::now(); // Incorrect, but ignored. - if (_additional.isCreator(user) && user->isSelf()) { - using Flag = MTPDchannelParticipantCreator::Flag; - _additional.applyParticipant(MTP_channelParticipantCreator( - MTP_flags(rank.isEmpty() ? Flag(0) : Flag::f_rank), - peerToBareMTPInt(user->id), - MTP_chatAdminRights( - MTP_flags(MTPDchatAdminRights::Flags::from_raw( - uint32(rights.flags)))), - MTP_string(rank))); - } else if (!rights.flags) { - _additional.applyParticipant(MTP_channelParticipant( - peerToBareMTPInt(user->id), - MTP_int(date))); - if (_role == Role::Admins) { - removeRow(user); - } - } else { - using Flag = MTPDchannelParticipantAdmin::Flag; - const auto alreadyPromotedBy = _additional.adminPromotedBy(user); - _additional.applyParticipant(MTP_channelParticipantAdmin( - MTP_flags(Flag::f_can_edit - | (rank.isEmpty() ? Flag(0) : Flag::f_rank)), - peerToBareMTPInt(user->id), - MTPlong(), // inviter_id - peerToBareMTPInt(alreadyPromotedBy - ? alreadyPromotedBy->id - : user->session().userPeerId()), - MTP_int(date), - MTP_chatAdminRights( - MTP_flags(MTPDchatAdminRights::Flags::from_raw( - uint32(rights.flags)))), - MTP_string(rank))); - if (_role == Role::Admins) { - prependRow(user); - } else if (_role == Role::Kicked || _role == Role::Restricted) { - removeRow(user); + _additional.applyAdminLocally(user, rights, rank); + if (!_additional.isCreator(user) || !user->isSelf()) { + if (!rights.flags) { + if (_role == Role::Admins) { + removeRow(user); + } + } else { + if (_role == Role::Admins) { + prependRow(user); + } else if (_role == Role::Kicked || _role == Role::Restricted) { + removeRow(user); + } } } recomputeTypeFor(user); @@ -1674,36 +1705,13 @@ void ParticipantsBoxController::editRestrictedDone( _editParticipantBox->closeBox(); } - const auto user = participant->asUser(); - const auto date = base::unixtime::now(); // Incorrect, but ignored. + _additional.applyBannedLocally(participant, rights); if (!rights.flags) { - if (user) { - _additional.applyParticipant(MTP_channelParticipant( - peerToBareMTPInt(user->id), - MTP_int(date))); - } else { - _additional.setExternal(participant); - } if (_role == Role::Kicked || _role == Role::Restricted) { removeRow(participant); } } else { const auto kicked = rights.flags & ChatRestriction::ViewMessages; - const auto alreadyRestrictedBy = _additional.restrictedBy( - participant); - _additional.applyParticipant(MTP_channelParticipantBanned( - MTP_flags(kicked - ? MTPDchannelParticipantBanned::Flag::f_left - : MTPDchannelParticipantBanned::Flag(0)), - peerToMTP(participant->id), - peerToBareMTPInt(alreadyRestrictedBy - ? alreadyRestrictedBy->id - : participant->session().userPeerId()), - MTP_int(date), - MTP_chatBannedRights( - MTP_flags(MTPDchatBannedRights::Flags::from_raw( - uint32(rights.flags))), - MTP_int(rights.until)))); if (kicked) { if (_role == Role::Kicked) { prependRow(participant); @@ -1748,7 +1756,7 @@ void ParticipantsBoxController::unkickParticipant(not_null user) { delegate()->peerListRemoveRow(row); delegate()->peerListRefreshRows(); } - _peer->session().api().addChatParticipants(_peer, { 1, user }); + _peer->session().api().chatParticipants().add(_peer, { 1, user }); } void ParticipantsBoxController::kickParticipantSure( @@ -1766,9 +1774,12 @@ void ParticipantsBoxController::kickParticipantSure( } auto &session = _peer->session(); if (const auto chat = _peer->asChat()) { - session.api().kickParticipant(chat, participant); + session.api().chatParticipants().kick(chat, participant); } else if (const auto channel = _peer->asChannel()) { - session.api().kickParticipant(channel, participant, currentRights); + session.api().chatParticipants().kick( + channel, + participant, + currentRights); } } @@ -1819,7 +1830,9 @@ void ParticipantsBoxController::removeKickedWithRow( void ParticipantsBoxController::removeKicked( not_null participant) { if (const auto channel = _peer->asChannel()) { - channel->session().api().unblockParticipant(channel, participant); + channel->session().api().chatParticipants().unblock( + channel, + participant); } } @@ -2040,7 +2053,12 @@ void ParticipantsBoxController::subscribeToCreatorChange( channel->mgInfo->lastAdmins.clear(); channel->mgInfo->lastRestricted.clear(); channel->mgInfo->lastParticipants.clear(); - api->parseRecentChannelParticipants(channel, result); + + result.match([&](const MTPDchannels_channelParticipants &data) { + Api::ChatParticipants::ParseRecent(channel, data); + }, [](const MTPDchannels_channelParticipantsNotModified &) { + }); + if (weak) { fullListRefresh(); } @@ -2205,10 +2223,13 @@ void ParticipantsBoxSearchController::searchDone( _queries.erase(it); } }; - _channel->session().api().parseChannelParticipants( - _channel, - result, - addToCache); + result.match([&](const MTPDchannels_channelParticipants &data) { + Api::ChatParticipants::Parse(_channel, data); + addToCache(); + }, [&](const MTPDchannels_channelParticipantsNotModified &) { + LOG(("API Error: " + "channels.channelParticipantsNotModified received!")); + }); } if (_requestId != requestId) { return; @@ -2228,7 +2249,7 @@ void ParticipantsBoxSearchController::searchDone( : _role; for (const auto &data : list) { const auto user = _additional->applyParticipant( - data, + Api::ChatParticipant(data, _channel), overrideRole); if (user) { delegate()->peerListSearchAddRow(user); diff --git a/Telegram/SourceFiles/boxes/peers/edit_participants_box.h b/Telegram/SourceFiles/boxes/peers/edit_participants_box.h index c713a2ea9..cfbb5fbf3 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participants_box.h +++ b/Telegram/SourceFiles/boxes/peers/edit_participants_box.h @@ -21,6 +21,10 @@ namespace Window { class SessionNavigation; } // namespace Window +namespace Api { +class ChatParticipant; +} // namespace Api + Fn peer, Role role); - PeerData *applyParticipant(const MTPChannelParticipant &data); + PeerData *applyParticipant(const Api::ChatParticipant &data); PeerData *applyParticipant( - const MTPChannelParticipant &data, + const Api::ChatParticipant &data, Role overrideRole); void setExternal(not_null participant); void checkForLoaded(not_null participant); @@ -108,11 +112,19 @@ public: void migrate(not_null chat, not_null channel); + void applyAdminLocally( + UserData *user, + ChatAdminRightsInfo rights, + const QString &rank); + void applyBannedLocally( + not_null participant, + ChatRestrictionsInfo rights); + private: - UserData *applyCreator(const MTPDchannelParticipantCreator &data); - UserData *applyAdmin(const MTPDchannelParticipantAdmin &data); - UserData *applyRegular(MTPlong userId); - PeerData *applyBanned(const MTPDchannelParticipantBanned &data); + UserData *applyCreator(const Api::ChatParticipant &data); + UserData *applyAdmin(const Api::ChatParticipant &data); + UserData *applyRegular(UserId userId); + PeerData *applyBanned(const Api::ChatParticipant &data); void fillFromChat(not_null chat); void fillFromChannel(not_null channel); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index 499263320..4ae4700c0 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -244,7 +244,7 @@ void ShowEditPermissions( const auto api = &peer->session().api(); api->migrateChat(chat, [=](not_null channel) { save(channel, result); - }, [=](const MTP::Error &error) { + }, [=](const QString &) { *saving = false; }); }, box->lifetime()); @@ -277,6 +277,7 @@ private: std::optional description; std::optional hiddenPreHistory; std::optional signatures; + std::optional noForwards; std::optional linkedChat; }; @@ -312,6 +313,7 @@ private: bool validateDescription(Saving &to) const; bool validateHistoryVisibility(Saving &to) const; bool validateSignatures(Saving &to) const; + bool validateForwards(Saving &to) const; void save(); void saveUsername(); @@ -320,6 +322,7 @@ private: void saveDescription(); void saveHistoryVisibility(); void saveSignatures(); + void saveForwards(); void savePhoto(); void pushSaveStage(FnMut &&lambda); void continueSave(); @@ -341,6 +344,7 @@ private: std::optional _historyVisibilitySavedValue; std::optional _usernameSavedValue; std::optional _signaturesSavedValue; + std::optional _noForwardsSavedValue; const not_null _navigation; const not_null _box; @@ -606,10 +610,11 @@ void Controller::refreshHistoryVisibility() { void Controller::showEditPeerTypeBox( std::optional> error) { const auto boxCallback = crl::guard(this, [=]( - Privacy checked, QString publicLink) { + Privacy checked, QString publicLink, bool noForwards) { _privacyTypeUpdates.fire(std::move(checked)); _privacySavedValue = checked; _usernameSavedValue = publicLink; + _noForwardsSavedValue = noForwards; refreshHistoryVisibility(); }); _navigation->parentController()->show( @@ -619,6 +624,7 @@ void Controller::showEditPeerTypeBox( boxCallback, _privacySavedValue, _usernameSavedValue, + _noForwardsSavedValue, error), Ui::LayerOption::KeepOther); } @@ -679,7 +685,7 @@ void Controller::showEditLinkedChatBox() { std::move(chats), callback), Ui::LayerOption::KeepOther); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _linkedChatsRequestId = 0; }).send(); } @@ -694,6 +700,7 @@ void Controller::fillPrivacyTypeButton() { && _peer->asChannel()->hasUsername()) ? Privacy::HasUsername : Privacy::NoUsername; + _noForwardsSavedValue = !_peer->allowsForwarding(); const auto isGroup = (_peer->isChat() || _peer->isMegagroup()); AddButtonWithText( @@ -854,10 +861,10 @@ void Controller::fillManageSection() { const auto isChannel = (!chat); if (!chat && !channel) return; - const auto canEditUsername = [&] { + const auto canEditType = [&] { return isChannel - ? channel->canEditUsername() - : chat->canEditUsername(); + ? channel->amCreator() + : chat->amCreator(); }(); const auto canEditSignatures = [&] { return isChannel @@ -923,7 +930,7 @@ void Controller::fillManageSection() { AddSkip(_controls.buttonsLayout, 0); - if (canEditUsername) { + if (canEditType) { fillPrivacyTypeButton(); //} else if (canEditInviteLinks) { // fillInviteLinkButton(); @@ -941,7 +948,7 @@ void Controller::fillManageSection() { || canEditSignatures //|| canEditInviteLinks || canViewOrEditLinkedChat - || canEditUsername) { + || canEditType) { AddSkip( _controls.buttonsLayout, st::editPeerTopButtonsLayoutSkip, @@ -962,7 +969,7 @@ void Controller::fillManageSection() { st::infoIconPermissions); } if (canEditInviteLinks - && (canEditUsername + && (canEditType || !_peer->isChannel() || !_peer->asChannel()->hasUsername())) { auto count = Info::Profile::MigratedOrMeValue( @@ -1154,7 +1161,8 @@ std::optional Controller::validate() const { && validateTitle(result) && validateDescription(result) && validateHistoryVisibility(result) - && validateSignatures(result)) { + && validateSignatures(result) + && validateForwards(result)) { return result; } return {}; @@ -1229,6 +1237,14 @@ bool Controller::validateSignatures(Saving &to) const { return true; } +bool Controller::validateForwards(Saving &to) const { + if (!_noForwardsSavedValue.has_value()) { + return true; + } + to.noForwards = _noForwardsSavedValue; + return true; +} + void Controller::save() { Expects(_wrap != nullptr); @@ -1243,6 +1259,7 @@ void Controller::save() { pushSaveStage([=] { saveDescription(); }); pushSaveStage([=] { saveHistoryVisibility(); }); pushSaveStage([=] { saveSignatures(); }); + pushSaveStage([=] { saveForwards(); }); pushSaveStage([=] { savePhoto(); }); continueSave(); } @@ -1286,7 +1303,7 @@ void Controller::saveUsername() { _api.request(MTPchannels_UpdateUsername( channel->inputChannel, MTP_string(*_savingData.username) - )).done([=](const MTPBool &result) { + )).done([=] { channel->setName( TextUtilities::SingleLine(channel->name), *_savingData.username); @@ -1341,10 +1358,10 @@ void Controller::saveLinkedChat() { _api.request(MTPchannels_SetDiscussionGroup( (channel->isBroadcast() ? channel->inputChannel : input), (channel->isBroadcast() ? input : channel->inputChannel) - )).done([=](const MTPBool &result) { + )).done([=] { channel->setLinkedChat(*_savingData.linkedChat); continueSave(); - }).fail([=](const MTP::Error &error) { + }).fail([=] { cancelSave(); }).send(); } @@ -1408,7 +1425,7 @@ void Controller::saveDescription() { _api.request(MTPmessages_EditChatAbout( _peer->input, MTP_string(*_savingData.description) - )).done([=](const MTPBool &result) { + )).done([=] { successCallback(); }).fail([=](const MTP::Error &error) { const auto &type = error.type(); @@ -1499,6 +1516,26 @@ void Controller::saveSignatures() { }).send(); } +void Controller::saveForwards() { + if (!_savingData.noForwards + || *_savingData.noForwards != _peer->allowsForwarding()) { + return continueSave(); + } + _api.request(MTPmessages_ToggleNoForwards( + _peer->input, + MTP_bool(*_savingData.noForwards) + )).done([=](const MTPUpdates &result) { + _peer->session().api().applyUpdates(result); + continueSave(); + }).fail([=](const MTP::Error &error) { + if (error.type() == qstr("CHAT_NOT_MODIFIED")) { + continueSave(); + } else { + cancelSave(); + } + }).send(); +} + void Controller::savePhoto() { auto image = _controls.photo ? _controls.photo->takeResultImage() diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp index c025e4ccd..0520858b7 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp @@ -689,7 +689,7 @@ void Controller::loadMoreRows() { auto slice = Api::ParseJoinedByLinkSlice(_peer, result); _allLoaded = slice.users.empty(); appendSlice(slice); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _requestId = 0; _allLoaded = true; }).send(); @@ -1145,9 +1145,8 @@ void ShareInviteLinkBox(not_null peer, const QString &link) { auto &api = peer->session().api(); for (const auto peer : result) { const auto history = owner->history(peer); - auto message = ApiWrap::MessageToSend(history); + auto message = Api::MessageToSend(Api::SendAction(history, options)); message.textWithTags = comment; - message.action.options = options; message.action.clearDraft = false; api.sendMessage(std::move(message)); } diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp index 9097160e2..5c89e1395 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp @@ -347,7 +347,7 @@ Fn AboutGigagroupCallback(not_null channel) { channel->session().api().applyUpdates(result); Ui::hideSettingsAndLayer(); Ui::Toast::Show(tr::lng_gigagroup_done(tr::now)); - }).fail([=](const MTP::Error &error) { + }).fail([=] { *converting = false; }).send(); }; diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.h b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.h index d11e18626..f4e23de39 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.h +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.h @@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "boxes/abstract_box.h" -#include "data/data_peer.h" +#include "data/data_chat_participant_status.h" namespace Ui { class RoundButton; diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.cpp index 4f60cd638..02370e74a 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.cpp @@ -340,7 +340,7 @@ void RequestsBoxController::loadMoreRows() { refreshDescription(); } delegate()->peerListRefreshRows(); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _loadRequestId = 0; _allLoaded = true; }).send(); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp index ac777e5eb..41140c2e4 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.cpp @@ -63,13 +63,14 @@ public: not_null peer, bool useLocationPhrases, std::optional privacySavedValue, - std::optional usernameSavedValue); + std::optional usernameSavedValue, + std::optional noForwardsSavedValue); void createContent(); - QString getUsernameInput(); + [[nodiscard]] QString getUsernameInput() const; void setFocusUsername(); - rpl::producer getTitle() { + [[nodiscard]] rpl::producer getTitle() const { return !_privacySavedValue ? tr::lng_create_invite_link_title() : _isGroup @@ -77,14 +78,18 @@ public: : tr::lng_manage_peer_channel_type(); } - bool isAllowSave() { - return _isAllowSave; + [[nodiscard]] bool goodUsername() const { + return _goodUsername; } - Privacy getPrivacy() { + [[nodiscard]] Privacy getPrivacy() const { return _controls.privacy->value(); } + [[nodiscard]] bool noForwards() const { + return _controls.noForwards->toggled(); + } + void showError(rpl::producer text) { _controls.usernameInput->showError(); showUsernameError(std::move(text)); @@ -100,6 +105,8 @@ private: Ui::SlideWrap *inviteLinkWrap = nullptr; Ui::FlatLabel *inviteLink = nullptr; + + Ui::SettingsButton *noForwards = nullptr; }; Controls _controls; @@ -133,10 +140,11 @@ private: MTP::Sender _api; std::optional _privacySavedValue; std::optional _usernameSavedValue; + std::optional _noForwardsSavedValue; bool _useLocationPhrases = false; bool _isGroup = false; - bool _isAllowSave = false; + bool _goodUsername = false; base::unique_qptr _wrap; base::Timer _checkUsernameTimer; @@ -153,15 +161,18 @@ Controller::Controller( not_null peer, bool useLocationPhrases, std::optional privacySavedValue, - std::optional usernameSavedValue) + std::optional usernameSavedValue, + std::optional noForwardsSavedValue) : _peer(peer) , _linkOnly(!privacySavedValue.has_value()) , _api(&_peer->session().mtp()) , _privacySavedValue(privacySavedValue) , _usernameSavedValue(usernameSavedValue) +, _noForwardsSavedValue(noForwardsSavedValue) , _useLocationPhrases(useLocationPhrases) , _isGroup(_peer->isChat() || _peer->isMegagroup()) -, _isAllowSave(!_usernameSavedValue.value_or(QString()).isEmpty()) +, _goodUsername(!_usernameSavedValue.value_or( + _peer->isChannel() ? _peer->asChannel()->username : QString()).isEmpty()) , _wrap(container) , _checkUsernameTimer([=] { checkUsernameAvailability(); }) { _peer->updateFull(); @@ -196,13 +207,38 @@ void Controller::createContent() { AddSkip(_wrap.get()); AddDividerText(_wrap.get(), tr::lng_group_invite_manage_about()); + if (!_linkOnly) { + AddSkip(_wrap.get()); + AddSubsectionTitle(_wrap.get(), tr::lng_manage_peer_no_forwards_title()); + _controls.noForwards = _wrap->add(EditPeerInfoBox::CreateButton( + _wrap.get(), + tr::lng_manage_peer_no_forwards(), + rpl::single(QString()), + [=] {}, + st::manageGroupTopButtonWithText, + nullptr + )); + _controls.noForwards->toggleOn( + rpl::single(_noForwardsSavedValue.value_or(false)) + )->toggledValue( + ) | rpl::start_with_next([=](bool toggled) { + _noForwardsSavedValue = toggled; + }, _wrap->lifetime()); + AddSkip(_wrap.get()); + AddDividerText( + _wrap.get(), + (_isGroup + ? tr::lng_manage_peer_no_forwards_about + : tr::lng_manage_peer_no_forwards_about_channel)()); + } if (_linkOnly) { _controls.inviteLinkWrap->show(anim::type::instant); } else { if (_controls.privacy->value() == Privacy::NoUsername) { checkUsernameAvailability(); } - const auto forShowing = _privacySavedValue.value_or(Privacy::NoUsername); + const auto forShowing = _privacySavedValue.value_or( + Privacy::NoUsername); _controls.inviteLinkWrap->toggle( (forShowing != Privacy::HasUsername), anim::type::instant); @@ -238,15 +274,7 @@ void Controller::addRoundButton( void Controller::fillPrivaciesButtons( not_null parent, std::optional savedValue) { - const auto canEditUsername = [&] { - if (const auto chat = _peer->asChat()) { - return chat->canEditUsername(); - } else if (const auto channel = _peer->asChannel()) { - return channel->canEditUsername(); - } - Unexpected("Peer type in Controller::createPrivaciesEdit."); - }(); - if (!canEditUsername || _linkOnly) { + if (_linkOnly) { return; } @@ -299,7 +327,7 @@ void Controller::setFocusUsername() { } } -QString Controller::getUsernameInput() { +QString Controller::getUsernameInput() const { return _controls.usernameInput->getLastText().trimmed(); } @@ -307,8 +335,8 @@ object_ptr Controller::createUsernameEdit() { Expects(_wrap != nullptr); const auto channel = _peer->asChannel(); - const auto username = - _usernameSavedValue.value_or(channel ? channel->username : QString()); + const auto username = _usernameSavedValue.value_or( + channel ? channel->username : QString()); auto result = object_ptr>( _wrap, @@ -479,7 +507,7 @@ void Controller::askUsernameRevoke() { } void Controller::usernameChanged() { - _isAllowSave = false; + _goodUsername = false; const auto username = getUsernameInput(); if (username.isEmpty()) { _controls.usernameResult = nullptr; @@ -503,12 +531,12 @@ void Controller::usernameChanged() { } void Controller::showUsernameError(rpl::producer &&error) { - _isAllowSave = false; + _goodUsername = false; showUsernameResult(std::move(error), &st::editPeerUsernameError); } void Controller::showUsernameGood() { - _isAllowSave = true; + _goodUsername = true; showUsernameResult( tr::lng_create_channel_link_available(), &st::editPeerUsernameGood); @@ -575,22 +603,24 @@ EditPeerTypeBox::EditPeerTypeBox( QWidget*, not_null peer, bool useLocationPhrases, - std::optional> savedCallback, + std::optional> savedCallback, std::optional privacySaved, std::optional usernameSaved, + std::optional noForwardsValue, std::optional> usernameError) : _peer(peer) , _useLocationPhrases(useLocationPhrases) , _savedCallback(std::move(savedCallback)) , _privacySavedValue(privacySaved) , _usernameSavedValue(usernameSaved) +, _noForwardsValue(noForwardsValue) , _usernameError(usernameError) { } EditPeerTypeBox::EditPeerTypeBox( QWidget*, not_null peer) -: EditPeerTypeBox(nullptr, peer, {}, {}, {}, {}, {}) { +: EditPeerTypeBox(nullptr, peer, {}, {}, {}, {}, {}, {}) { } void EditPeerTypeBox::setInnerFocus() { @@ -608,7 +638,8 @@ void EditPeerTypeBox::prepare() { _peer, _useLocationPhrases, _privacySavedValue, - _usernameSavedValue); + _usernameSavedValue, + _noForwardsValue); _focusRequests.events( ) | rpl::start_with_next( [=] { @@ -626,16 +657,18 @@ void EditPeerTypeBox::prepare() { if (_savedCallback.has_value()) { addButton(tr::lng_settings_save(), [=] { const auto v = controller->getPrivacy(); - if (!controller->isAllowSave() && (v == Privacy::HasUsername)) { + if ((v == Privacy::HasUsername) && !controller->goodUsername()) { controller->setFocusUsername(); return; } auto local = std::move(*_savedCallback); - local(v, - (v == Privacy::HasUsername) + local( + v, + (v == Privacy::HasUsername ? controller->getUsernameInput() - : QString()); // We don't need username with private type. + : QString()), + controller->noForwards()); // We don't need username with private type. closeBox(); }); addButton(tr::lng_cancel(), [=] { closeBox(); }); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.h b/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.h index 0d4a828b1..fa7744125 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.h +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_type_box.h @@ -36,9 +36,10 @@ public: QWidget*, not_null peer, bool useLocationPhrases, - std::optional> savedCallback, + std::optional> savedCallback, std::optional privacySaved, std::optional usernameSaved, + std::optional noForwardsSaved, std::optional> usernameError = {}); // For invite link only. @@ -53,10 +54,11 @@ protected: private: not_null _peer; bool _useLocationPhrases = false; - std::optional> _savedCallback; + std::optional> _savedCallback; std::optional _privacySavedValue; std::optional _usernameSavedValue; + std::optional _noForwardsValue; std::optional> _usernameError; rpl::event_stream<> _focusRequests; diff --git a/Telegram/SourceFiles/boxes/pin_messages_box.cpp b/Telegram/SourceFiles/boxes/pin_messages_box.cpp index 45a1a3c60..af4c42a1d 100644 --- a/Telegram/SourceFiles/boxes/pin_messages_box.cpp +++ b/Telegram/SourceFiles/boxes/pin_messages_box.cpp @@ -123,7 +123,7 @@ void PinMessageBox::pinMessage() { )).done([=](const MTPUpdates &result) { _peer->session().api().applyUpdates(result); Ui::hideLayer(); - }).fail([=](const MTP::Error &error) { + }).fail([=] { Ui::hideLayer(); }).send(); } diff --git a/Telegram/SourceFiles/boxes/self_destruction_box.cpp b/Telegram/SourceFiles/boxes/self_destruction_box.cpp index 9a167f6c4..2d4b94748 100644 --- a/Telegram/SourceFiles/boxes/self_destruction_box.cpp +++ b/Telegram/SourceFiles/boxes/self_destruction_box.cpp @@ -12,16 +12,33 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/labels.h" #include "apiwrap.h" #include "api/api_self_destruct.h" +#include "api/api_authorizations.h" #include "main/main_session.h" #include "styles/style_layers.h" #include "styles/style_boxes.h" +namespace { + +using Type = SelfDestructionBox::Type; + +[[nodiscard]] std::vector Values(Type type) { + switch (type) { + case Type::Account: return { 30, 90, 180, 365 }; + case Type::Sessions: return { 7, 30, 90, 180 }; + } + Unexpected("SelfDestructionBox::Type in Values."); +} + +} // namespace + SelfDestructionBox::SelfDestructionBox( QWidget*, not_null session, + Type type, rpl::producer preloaded) -: _session(session) -, _ttlValues{ 30, 90, 180, 365 } +: _type(type) +, _session(session) +, _ttlValues(Values(type)) , _loading( this, tr::lng_contacts_loading(tr::now), @@ -57,7 +74,9 @@ void SelfDestructionBox::showContent() { auto y = st::boxOptionListPadding.top(); _description.create( this, - tr::lng_self_destruct_description(tr::now), + (_type == Type::Account + ? tr::lng_self_destruct_description(tr::now) + : tr::lng_self_destruct_sessions_description(tr::now)), st::boxLabel); _description->moveToLeft(st::boxPadding.left(), y); y += _description->height() + st::boxMediumSkip; @@ -76,24 +95,46 @@ void SelfDestructionBox::showContent() { clearButtons(); addButton(tr::lng_settings_save(), [=] { - _session->api().selfDestruct().update(_ttlGroup->value()); + switch (_type) { + case Type::Account: + _session->api().selfDestruct().update(_ttlGroup->value()); + break; + case Type::Sessions: + _session->api().authorizations().updateTTL(_ttlGroup->value()); + break; + } + closeBox(); }); addButton(tr::lng_cancel(), [=] { closeBox(); }); } QString SelfDestructionBox::DaysLabel(int days) { - return (days > 364) + return !days + ? QString() + : (days > 364) ? tr::lng_self_destruct_years(tr::now, lt_count, days / 365) - : tr::lng_self_destruct_months(tr::now, lt_count, qMax(days / 30, 1)); + : (days > 25) + ? tr::lng_self_destruct_months( + tr::now, + lt_count, + qMax(days / 30, 1)) + : tr::lng_self_destruct_weeks( + tr::now, + lt_count, + qMax(days / 7, 1)); } void SelfDestructionBox::prepare() { - setTitle(tr::lng_self_destruct_title()); + setTitle((_type == Type::Account + ? tr::lng_self_destruct_title() + : tr::lng_self_destruct_sessions_title())); auto fake = object_ptr( this, - tr::lng_self_destruct_description(tr::now), + (_type == Type::Account + ? tr::lng_self_destruct_description(tr::now) + : tr::lng_self_destruct_sessions_description(tr::now)), st::boxLabel); const auto boxHeight = st::boxOptionListPadding.top() + fake->height() + st::boxMediumSkip diff --git a/Telegram/SourceFiles/boxes/self_destruction_box.h b/Telegram/SourceFiles/boxes/self_destruction_box.h index 95f7928c8..228953aa6 100644 --- a/Telegram/SourceFiles/boxes/self_destruction_box.h +++ b/Telegram/SourceFiles/boxes/self_destruction_box.h @@ -22,9 +22,14 @@ class Session; class SelfDestructionBox : public Ui::BoxContent { public: + enum class Type { + Account, + Sessions, + }; SelfDestructionBox( QWidget*, not_null session, + Type type, rpl::producer preloaded); static QString DaysLabel(int days); @@ -36,6 +41,7 @@ private: void gotCurrent(int days); void showContent(); + const Type _type; const not_null _session; bool _prepared = false; std::vector _ttlValues; diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index d59f9559d..3dad0b11f 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -769,7 +769,7 @@ void SendFilesBox::emojiFilterForGeometry(not_null event) { void SendFilesBox::updateEmojiPanelGeometry() { const auto parent = _emojiPanel->parentWidget(); - const auto global = _emojiToggle->mapToGlobal({ 0, 0 }); + const auto global = _emojiToggle->mapToGlobal(QPoint()); const auto local = parent->mapFromGlobal(global); _emojiPanel->moveBottomRight( local.y(), @@ -1026,9 +1026,7 @@ void SendFilesBox::send( } void SendFilesBox::sendSilent() { - auto options = Api::SendOptions(); - options.silent = true; - send(options); + send({ .silent = true }); } void SendFilesBox::sendScheduled() { diff --git a/Telegram/SourceFiles/boxes/sessions_box.cpp b/Telegram/SourceFiles/boxes/sessions_box.cpp index 59d955b3d..c7c79a257 100644 --- a/Telegram/SourceFiles/boxes/sessions_box.cpp +++ b/Telegram/SourceFiles/boxes/sessions_box.cpp @@ -11,29 +11,609 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_authorizations.h" #include "base/timer.h" #include "base/unixtime.h" +#include "base/algorithm.h" +#include "base/platform/base_platform_info.h" +#include "boxes/self_destruction_box.h" +#include "boxes/peer_lists_box.h" #include "ui/boxes/confirm_box.h" #include "lang/lang_keys.h" #include "main/main_session.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" +#include "ui/widgets/labels.h" +#include "ui/widgets/scroll_area.h" +#include "ui/wrap/slide_wrap.h" +#include "ui/wrap/padding_wrap.h" +#include "ui/wrap/vertical_layout.h" +#include "ui/layers/generic_box.h" +#include "lottie/lottie_icon.h" +#include "core/application.h" +#include "core/core_settings.h" +#include "window/window_session_controller.h" #include "styles/style_boxes.h" #include "styles/style_info.h" #include "styles/style_layers.h" #include "styles/style_settings.h" -#include "ui/widgets/buttons.h" -#include "ui/widgets/labels.h" -#include "ui/widgets/scroll_area.h" -#include "ui/wrap/slide_wrap.h" -#include "ui/wrap/vertical_layout.h" -#include "window/window_session_controller.h" namespace { constexpr auto kSessionsShortPollTimeout = 60 * crl::time(1000); +constexpr auto kMaxDeviceModelLength = 32; + +using EntryData = Api::Authorizations::Entry; + +enum class Type { + Windows, + Mac, + Ubuntu, + Linux, + iPhone, + iPad, + Android, + Web, + Chrome, + Edge, + Firefox, + Safari, + Other, +}; + +class Row; + +class RowDelegate { +public: + virtual void rowUpdateRow(not_null row) = 0; +}; + +class Row final : public PeerListRow { +public: + Row(not_null delegate, const EntryData &data); + + void update(const EntryData &data); + void updateName(const QString &name); + + [[nodiscard]] EntryData data() const; + + QString generateName() override; + QString generateShortName() override; + PaintRoundImageCallback generatePaintUserpicCallback() override; + + int elementsCount() const override; + QRect elementGeometry(int element, int outerWidth) const override; + bool elementDisabled(int element) const override; + bool elementOnlySelect(int element) const override; + void elementAddRipple( + int element, + QPoint point, + Fn updateCallback) override; + void elementsStopLastRipple() override; + void elementsPaint( + Painter &p, + int outerWidth, + bool selected, + int selectedElement) override; + +private: + const not_null _delegate; + Ui::Text::String _location; + Type _type = Type::Other; + EntryData _data; + QImage _userpic; + +}; + +void RenameBox(not_null box) { + box->setTitle(tr::lng_settings_rename_device_title()); + + const auto skip = st::settingsSubsectionTitlePadding.top(); + box->addRow( + object_ptr( + box, + tr::lng_settings_device_name(), + st::settingsSubsectionTitle), + st::boxRowPadding + style::margins(0, skip, 0, 0)); + const auto name = box->addRow( + object_ptr( + box, + st::settingsDeviceName, + rpl::single(Platform::DeviceModelPretty()), + Core::App().settings().customDeviceModel()), + st::boxRowPadding - style::margins( + st::settingsDeviceName.textMargins.left(), + 0, + st::settingsDeviceName.textMargins.right(), + 0)); + name->setMaxLength(kMaxDeviceModelLength); + box->setFocusCallback([=] { + name->setFocusFast(); + }); + const auto submit = [=] { + const auto result = base::CleanAndSimplify( + name->getLastText()); + box->closeBox(); + Core::App().settings().setCustomDeviceModel(result); + Core::App().saveSettingsDelayed(); + }; + QObject::connect(name, &Ui::InputField::submitted, submit); + box->addButton(tr::lng_settings_save(), submit); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); +} + +[[nodiscard]] QString LocationAndDate(const EntryData &entry) { + return (entry.location.isEmpty() ? entry.ip : entry.location) + + (entry.hash + ? (QString::fromUtf8(" \xE2\x80\xA2 ") + entry.active) + : QString()); +} + +[[nodiscard]] Type TypeFromEntry(const EntryData &entry) { + const auto platform = entry.platform.toLower(); + const auto device = entry.name.toLower(); + const auto system = entry.system.toLower(); + const auto apiId = entry.apiId; + const auto kDesktop = std::array{ 2040, 17349, 611335 }; + const auto kMac = std::array{ 2834 }; + const auto kAndroid + = std::array{ 5, 6, 24, 1026, 1083, 2458, 2521, 21724 }; + const auto kiOS = std::array{ 1, 7, 10840, 16352 }; + const auto kWeb = std::array{ 2496, 739222, 1025907 }; + + const auto detectBrowser = [&]() -> std::optional { + if (device.contains("edg/") + || device.contains("edgios/") + || device.contains("edga/")) { + return Type::Edge; + } else if (device.contains("chrome")) { + return Type::Chrome; + } else if (device.contains("safari")) { + return Type::Safari; + } else if (device.contains("firefox")) { + return Type::Firefox; + } + return {}; + }; + const auto detectDesktop = [&]() -> std::optional { + if (platform.contains("windows") || system.contains("windows")) { + return Type::Windows; + } else if (platform.contains("macos") || system.contains("macos")) { + return Type::Mac; + } else if (platform.contains("ubuntu") + || system.contains("ubuntu") + || platform.contains("unity") + || system.contains("unity")) { + return Type::Ubuntu; + } else if (platform.contains("linux") || system.contains("linux")) { + return Type::Linux; + } + return {}; + }; + + if (ranges::contains(kAndroid, apiId)) { + return Type::Android; + } else if (ranges::contains(kDesktop, apiId)) { + return detectDesktop().value_or(Type::Linux); + } else if (ranges::contains(kMac, apiId)) { + return Type::Mac; + } else if (ranges::contains(kWeb, apiId)) { + return detectBrowser().value_or(Type::Web); + } else if (device.contains("chromebook")) { + return Type::Other; + } else if (const auto browser = detectBrowser()) { + return *browser; + } else if (device.contains("iphone")) { + return Type::iPhone; + } else if (device.contains("ipad")) { + return Type::iPad; + } else if (ranges::contains(kiOS, apiId)) { + return Type::iPhone; + } else if (const auto desktop = detectDesktop()) { + return *desktop; + } else if (platform.contains("android") || system.contains("android")) { + return Type::Android; + } else if (platform.contains("ios") || system.contains("ios")) { + return Type::iPhone; + } + return Type::Other; +} + +[[nodiscard]] style::color ColorForType(Type type) { + switch (type) { + case Type::Windows: + case Type::Mac: + case Type::Other: + return st::historyPeer4UserpicBg; // blue + case Type::Ubuntu: + return st::historyPeer8UserpicBg; // orange + case Type::Linux: + return st::historyPeer5UserpicBg; // purple + case Type::iPhone: + case Type::iPad: + return st::historyPeer7UserpicBg; // sea + case Type::Android: + return st::historyPeer2UserpicBg; // green + case Type::Web: + case Type::Chrome: + case Type::Edge: + case Type::Firefox: + case Type::Safari: + return st::historyPeer6UserpicBg; // pink + } + Unexpected("Type in ColorForType."); +} + +[[nodiscard]] const style::icon &IconForType(Type type) { + switch (type) { + case Type::Windows: return st::sessionIconWindows; + case Type::Mac: return st::sessionIconMac; + case Type::Ubuntu: return st::sessionIconUbuntu; + case Type::Linux: return st::sessionIconLinux; + case Type::iPhone: return st::sessionIconiPhone; + case Type::iPad: return st::sessionIconiPad; + case Type::Android: return st::sessionIconAndroid; + case Type::Web: return st::sessionIconWeb; + case Type::Chrome: return st::sessionIconChrome; + case Type::Edge: return st::sessionIconEdge; + case Type::Firefox: return st::sessionIconFirefox; + case Type::Safari: return st::sessionIconSafari; + case Type::Other: return st::sessionIconOther; + } + Unexpected("Type in IconForType."); +} + +[[nodiscard]] const style::icon *IconBigForType(Type type) { + switch (type) { + case Type::Web: return &st::sessionBigIconWeb; + case Type::Other: return &st::sessionBigIconOther; + } + return nullptr; +} + +[[nodiscard]] std::unique_ptr LottieForType(Type type) { + if (IconBigForType(type)) { + return nullptr; + } + const auto path = [&] { + switch (type) { + case Type::Windows: return "device_desktop_win"; + case Type::Mac: return "device_desktop_mac"; + case Type::Ubuntu: return "device_linux_ubuntu"; + case Type::Linux: return "device_linux"; + case Type::iPhone: return "device_phone_ios"; + case Type::iPad: return "device_tablet_ios"; + case Type::Android: return "device_phone_android"; + case Type::Chrome: return "device_web_chrome"; + case Type::Edge: return "device_web_edge"; + case Type::Firefox: return "device_web_firefox"; + case Type::Safari: return "device_web_safari"; + } + Unexpected("Type in LottieForType."); + }(); + const auto size = st::sessionBigLottieSize; + static const auto kWhite = style::owned_color(Qt::white); + return std::make_unique(Lottie::IconDescriptor{ + .path = u":/icons/settings/devices/"_q + path + u".lottie"_q, + .color = kWhite.color(), + .sizeOverride = QSize(size, size), + .frame = 1, + }); +} + +[[nodiscard]] QImage GenerateUserpic(Type type) { + const auto size = st::sessionListItem.photoSize; + const auto full = size * style::DevicePixelRatio(); + const auto rect = QRect(0, 0, size, size); + + auto result = QImage(full, full, QImage::Format_ARGB32_Premultiplied); + result.fill(Qt::transparent); + result.setDevicePixelRatio(style::DevicePixelRatio()); + + auto p = QPainter(&result); + auto hq = PainterHighQualityEnabler(p); + p.setBrush(ColorForType(type)); + p.setPen(Qt::NoPen); + p.drawEllipse(rect); + IconForType(type).paintInCenter(p, rect); + p.end(); + + return result; +} + +[[nodiscard]] not_null GenerateUserpicBig( + not_null parent, + rpl::producer<> shown, + Type type) { + const auto size = st::sessionBigUserpicSize; + const auto full = size * style::DevicePixelRatio(); + const auto rect = QRect(0, 0, size, size); + + const auto result = Ui::CreateChild(parent.get()); + result->resize(rect.size()); + struct State { + QImage background; + std::unique_ptr lottie; + QImage lottieFrame; + QImage colorizedFrame; + }; + const auto state = result->lifetime().make_state(); + state->background = QImage( + full, + full, + QImage::Format_ARGB32_Premultiplied); + state->background.fill(Qt::transparent); + state->background.setDevicePixelRatio(style::DevicePixelRatio()); + state->colorizedFrame = state->lottieFrame = state->background; + + auto p = QPainter(&state->background); + auto hq = PainterHighQualityEnabler(p); + p.setBrush(ColorForType(type)); + p.setPen(Qt::NoPen); + p.drawEllipse(rect); + if (const auto icon = IconBigForType(type)) { + icon->paintInCenter(p, rect); + } + p.end(); + + if ((state->lottie = LottieForType(type))) { + std::move( + shown + ) | rpl::start_with_next([=] { + state->lottie->animate( + [=] { result->update(); }, + 0, + state->lottie->framesCount()); + }, result->lifetime()); + } + + result->paintRequest( + ) | rpl::start_with_next([=] { + auto p = QPainter(result); + p.drawImage(QPoint(0, 0), state->background); + if (state->lottie) { + state->lottieFrame.fill(Qt::black); + auto q = QPainter(&state->lottieFrame); + state->lottie->paintInCenter(q, result->rect()); + q.end(); + style::colorizeImage( + state->lottieFrame, + st::historyPeerUserpicFg->c, + &state->colorizedFrame); + p.drawImage(QPoint(0, 0), state->colorizedFrame); + + } + }, result->lifetime()); + + return result; +} + +void SessionInfoBox( + not_null box, + const EntryData &data, + Fn terminate) { + box->setWidth(st::boxWideWidth); + + const auto shown = box->lifetime().make_state>(); + box->setShowFinishedCallback([=] { + shown->fire({}); + }); + + const auto userpicWrap = box->addRow( + object_ptr(box, st::sessionBigUserpicSize), + st::sessionBigCoverPadding); + const auto big = GenerateUserpicBig( + userpicWrap, + shown->events(), + TypeFromEntry(data)); + userpicWrap->sizeValue( + ) | rpl::start_with_next([=](QSize size) { + big->move((size.width() - big->width()) / 2, 0); + }, userpicWrap->lifetime()); + + const auto nameWrap = box->addRow( + object_ptr( + box, + st::sessionBigName.maxHeight)); + const auto name = Ui::CreateChild( + nameWrap, + rpl::single(data.name), + st::sessionBigName); + nameWrap->widthValue( + ) | rpl::start_with_next([=](int width) { + name->resizeToWidth(width); + name->move((width - name->width()) / 2, 0); + }, name->lifetime()); + + const auto dateWrap = box->addRow( + object_ptr( + box, + st::sessionDateLabel.style.font->height), + style::margins(0, 0, 0, st::sessionDateSkip)); + const auto date = Ui::CreateChild( + dateWrap, + rpl::single( + langDateTimeFull(base::unixtime::parse(data.activeTime))), + st::sessionDateLabel); + rpl::combine( + dateWrap->widthValue(), + date->widthValue() + ) | rpl::start_with_next([=](int outer, int inner) { + date->move((outer - inner) / 2, 0); + }, date->lifetime()); + + using namespace Settings; + const auto container = box->verticalLayout(); + AddDivider(container); + AddSkip(container, st::sessionSubtitleSkip); + AddSubsectionTitle(container, tr::lng_sessions_info()); + + const auto add = [&](rpl::producer label, QString value) { + if (value.isEmpty()) { + return; + } + container->add( + object_ptr( + container, + rpl::single(value), + st::boxLabel), + st::boxRowPadding + st::sessionValuePadding); + container->add( + object_ptr( + container, + std::move(label), + st::sessionValueLabel), + (st::boxRowPadding + + style::margins{ 0, 0, 0, st::sessionValueSkip })); + }; + add(tr::lng_sessions_application(), data.info); + add(tr::lng_sessions_system(), data.system); + add(tr::lng_sessions_ip(), data.ip); + add(tr::lng_sessions_location(), data.location); + AddSkip(container, st::sessionValueSkip); + if (!data.location.isEmpty()) { + AddDividerText(container, tr::lng_sessions_location_about()); + } + + box->addButton(tr::lng_about_done(), [=] { box->closeBox(); }); + if (const auto hash = data.hash) { + box->addLeftButton(tr::lng_sessions_terminate(), [=] { + const auto weak = Ui::MakeWeak(box.get()); + terminate(hash); + if (weak) { + box->closeBox(); + } + }, st::attentionBoxButton); + } +} + +Row::Row(not_null delegate, const EntryData &data) +: PeerListRow(data.hash) +, _delegate(delegate) +, _location(st::defaultTextStyle, LocationAndDate(data)) +, _type(TypeFromEntry(data)) +, _data(data) +, _userpic(GenerateUserpic(_type)) { + setCustomStatus(_data.info); +} + +void Row::update(const EntryData &data) { + _data = data; + setCustomStatus(_data.info); + refreshName(st::sessionListItem); + _location.setText(st::defaultTextStyle, LocationAndDate(_data)); + _type = TypeFromEntry(_data); + _userpic = GenerateUserpic(_type); + _delegate->rowUpdateRow(this); +} + +void Row::updateName(const QString &name) { + _data.name = name; + refreshName(st::sessionListItem); + _delegate->rowUpdateRow(this); +} + +EntryData Row::data() const { + return _data; +} + +QString Row::generateName() { + return _data.name; +} + +QString Row::generateShortName() { + return generateName(); +} + +PaintRoundImageCallback Row::generatePaintUserpicCallback() { + return [=]( + Painter &p, + int x, + int y, + int outerWidth, + int size) { + p.drawImage(x, y, _userpic); + }; +} + +int Row::elementsCount() const { + return 2; +} + +QRect Row::elementGeometry(int element, int outerWidth) const { + switch (element) { + case 1: { + return QRect( + st::sessionListItem.namePosition.x(), + st::sessionLocationTop, + outerWidth, + st::normalFont->height); + } break; + case 2: { + const auto size = QSize( + st::sessionTerminate.width, + st::sessionTerminate.height); + const auto margins = QMargins( + 0, + (st::sessionListItem.height - size.height()) / 2, + st::sessionListThreeDotsSkip, + 0); + const auto right = st::sessionTerminateSkip; + const auto top = st::sessionTerminateTop; + const auto left = outerWidth - right - size.width(); + return QRect(QPoint(left, top), size); + } break; + } + return QRect(); +} + +bool Row::elementDisabled(int element) const { + return !id() || (element == 1); +} + +bool Row::elementOnlySelect(int element) const { + return false; +} + +void Row::elementAddRipple( + int element, + QPoint point, + Fn updateCallback) { +} + +void Row::elementsStopLastRipple() { +} + +void Row::elementsPaint( + Painter &p, + int outerWidth, + bool selected, + int selectedElement) { + if (id()) { + const auto geometry = elementGeometry(2, outerWidth); + const auto position = geometry.topLeft() + + st::sessionTerminate.iconPosition; + const auto &icon = (selectedElement == 2) + ? st::sessionTerminate.iconOver + : st::sessionTerminate.icon; + icon.paint(p, position.x(), position.y(), outerWidth); + } + p.setFont(st::msgFont); + p.setPen(st::sessionInfoFg); + const auto locationLeft = st::sessionListItem.namePosition.x(); + const auto available = outerWidth - locationLeft; + _location.drawLeftElided( + p, + locationLeft, + st::sessionLocationTop, + available, + outerWidth); +} } // namespace class SessionsContent : public Ui::RpWidget { public: - SessionsContent(QWidget*, not_null session); + SessionsContent( + QWidget*, + not_null controller); void setupContent(); @@ -42,31 +622,13 @@ protected: void paintEvent(QPaintEvent *e) override; private: - struct Entry { - Entry() = default; - Entry(const Api::Authorizations::Entry &entry) - : hash(entry.hash) - , incomplete(entry.incomplete) - , activeTime(entry.activeTime) - , name(st::sessionNameStyle, entry.name) - , active(st::sessionWhenStyle, entry.active) - , info(st::sessionInfoStyle, entry.info) - , ip(st::sessionInfoStyle, entry.ip) { - }; - - uint64 hash = 0; - - bool incomplete = false; - TimeId activeTime = 0; - Ui::Text::String name, active, info, ip; - }; struct Full { - Entry current; - std::vector incomplete; - std::vector list; + EntryData current; + std::vector incomplete; + std::vector list; }; class Inner; - class List; + class ListController; void shortPollSessions(); void parse(const Api::Authorizations::List &list); @@ -75,6 +637,7 @@ private: void terminateOne(uint64 hash); void terminateAll(); + const not_null _controller; const not_null _authorizations; rpl::variable _loading = false; @@ -87,61 +650,73 @@ private: }; -class SessionsContent::List : public Ui::RpWidget { +class SessionsContent::ListController final + : public PeerListController + , public RowDelegate + , public base::has_weak_ptr { public: - List(QWidget *parent); + explicit ListController(not_null session); - void showData(gsl::span items); + Main::Session &session() const override; + void prepare() override; + void rowClicked(not_null row) override; + void rowElementClicked(not_null row, int element) override; + + void rowUpdateRow(not_null row) override; + + void showData(gsl::span items); rpl::producer itemsCount() const; - rpl::producer terminate() const; + rpl::producer terminateRequests() const; + [[nodiscard]] rpl::producer showRequests() const; - void terminating(uint64 hash, bool terminating); - -protected: - void resizeEvent(QResizeEvent *e) override; - void paintEvent(QPaintEvent *e) override; - - int resizeGetHeight(int newWidth) override; + [[nodiscard]] static std::unique_ptr Add( + not_null container, + not_null session, + style::margins margins = {}); private: - struct RowWidth { - int available = 0; - int info = 0; - }; - RowWidth _rowWidth; + void subscribeToCustomDeviceModel(); - void computeRowWidth(); + const not_null _session; - std::vector _items; - std::map> _terminateButtons; - rpl::event_stream _terminate; + rpl::event_stream _terminateRequests; rpl::event_stream _itemsCount; + rpl::event_stream _showRequests; }; class SessionsContent::Inner : public Ui::RpWidget { public: - Inner(QWidget *parent); + Inner( + QWidget *parent, + not_null controller, + rpl::producer ttlDays); void showData(const Full &data); - rpl::producer terminateOne() const; - rpl::producer<> terminateAll() const; - - void terminatingOne(uint64 hash, bool terminating); + [[nodiscard]] rpl::producer showRequests() const; + [[nodiscard]] rpl::producer terminateOne() const; + [[nodiscard]] rpl::producer<> terminateAll() const; private: void setupContent(); - QPointer _current; + const not_null _controller; + std::unique_ptr _current; QPointer _terminateAll; - QPointer _incomplete; - QPointer _list; + std::unique_ptr _incomplete; + std::unique_ptr _list; + rpl::variable _ttlDays; }; -SessionsContent::SessionsContent(QWidget*, not_null session) -: _authorizations(&session->api().authorizations()) -, _inner(this) +//, location(st::sessionInfoStyle, LocationAndDate(entry)) + +SessionsContent::SessionsContent( + QWidget*, + not_null controller) +: _controller(controller) +, _authorizations(&controller->session().api().authorizations()) +, _inner(this, controller, _authorizations->ttlDays()) , _shortPollTimer([=] { shortPollSessions(); }) { } @@ -154,6 +729,14 @@ void SessionsContent::setupContent() { resize(width(), height); }, _inner->lifetime()); + _inner->showRequests( + ) | rpl::start_with_next([=](const EntryData &data) { + _controller->show(Box( + SessionInfoBox, + data, + [=](uint64 hash) { terminateOne(hash); })); + }, lifetime()); + _inner->terminateOne( ) | rpl::start_with_next([=](uint64 hash) { terminateOne(hash); @@ -184,20 +767,19 @@ void SessionsContent::parse(const Api::Authorizations::List &list) { } _data = Full(); for (const auto &auth : list) { - auto entry = Entry(auth); - if (!entry.hash) { - _data.current = std::move(entry); - } else if (entry.incomplete) { - _data.incomplete.push_back(std::move(entry)); + if (!auth.hash) { + _data.current = auth; + } else if (auth.incomplete) { + _data.incomplete.push_back(auth); } else { - _data.list.push_back(std::move(entry)); + _data.list.push_back(auth); } } _loading = false; - ranges::sort(_data.list, std::greater<>(), &Entry::activeTime); - ranges::sort(_data.incomplete, std::greater<>(), &Entry::activeTime); + ranges::sort(_data.list, std::greater<>(), &EntryData::activeTime); + ranges::sort(_data.incomplete, std::greater<>(), &EntryData::activeTime); _inner->showData(_data); @@ -265,10 +847,12 @@ void SessionsContent::terminateOne(uint64 hash) { if (mtpIsFalse(result)) { return; } - _inner->terminatingOne(hash, false); - const auto removeByHash = [&](std::vector &list) { + const auto removeByHash = [&](std::vector &list) { list.erase( - ranges::remove(list, hash, &Entry::hash), + ranges::remove( + list, + hash, + [](const EntryData &entry) { return entry.hash; }), end(list)); }; removeByHash(_data.incomplete); @@ -276,13 +860,11 @@ void SessionsContent::terminateOne(uint64 hash) { _inner->showData(_data); }); auto fail = crl::guard(weak, [=](const MTP::Error &error) { - _inner->terminatingOne(hash, false); }); _authorizations->requestTerminate( std::move(done), std::move(fail), hash); - _inner->terminatingOne(hash, true); }; terminate(std::move(callback), tr::lng_settings_reset_one_sure(tr::now)); } @@ -302,8 +884,13 @@ void SessionsContent::terminateAll() { terminate(std::move(callback), tr::lng_settings_reset_sure(tr::now)); } -SessionsContent::Inner::Inner(QWidget *parent) -: RpWidget(parent) { +SessionsContent::Inner::Inner( + QWidget *parent, + not_null controller, + rpl::producer ttlDays) +: RpWidget(parent) +, _controller(controller) +, _ttlDays(std::move(ttlDays)) { setupContent(); } @@ -313,18 +900,46 @@ void SessionsContent::Inner::setupContent() { const auto content = Ui::CreateChild(this); - AddSubsectionTitle(content, tr::lng_sessions_header()); - _current = content->add(object_ptr(content)); + const auto header = AddSubsectionTitle( + content, + tr::lng_sessions_header()); + const auto rename = Ui::CreateChild( + content, + tr::lng_settings_rename_device(tr::now), + st::defaultLinkButton); + rpl::combine( + content->sizeValue(), + header->positionValue() + ) | rpl::start_with_next([=](QSize outer, QPoint position) { + const auto x = st::sessionTerminateSkip + + st::sessionTerminate.iconPosition.x(); + const auto y = st::settingsSubsectionTitlePadding.top() + + st::settingsSubsectionTitle.style.font->ascent + - st::defaultLinkButton.font->ascent; + rename->moveToRight(x, y, outer.width()); + }, rename->lifetime()); + rename->setClickedCallback([=] { + Ui::show(Box(RenameBox), Ui::LayerOption::KeepOther); + }); + + const auto session = &_controller->session(); + _current = ListController::Add( + content, + session, + style::margins{ 0, 0, 0, st::sessionCurrentSkip }); const auto terminateWrap = content->add( object_ptr>( content, object_ptr(content)))->setDuration(0); const auto terminateInner = terminateWrap->entity(); _terminateAll = terminateInner->add( - object_ptr( + CreateButton( terminateInner, tr::lng_sessions_terminate_all(), - st::terminateSessionsButton)); + st::sessionsTerminateAll, + &st::sessionsTerminateAllIcon, + st::sessionsTerminateAllIconLeft, + &st::attentionButtonFg)); AddSkip(terminateInner); AddDividerText(terminateInner, tr::lng_sessions_terminate_all_about()); @@ -333,9 +948,9 @@ void SessionsContent::Inner::setupContent() { content, object_ptr(content)))->setDuration(0); const auto incompleteInner = incompleteWrap->entity(); - AddSkip(incompleteInner); + AddSkip(incompleteInner, st::sessionSubtitleSkip); AddSubsectionTitle(incompleteInner, tr::lng_sessions_incomplete()); - _incomplete = incompleteInner->add(object_ptr(incompleteInner)); + _incomplete = ListController::Add(incompleteInner, session); AddSkip(incompleteInner); AddDividerText(incompleteInner, tr::lng_sessions_incomplete_about()); @@ -344,10 +959,34 @@ void SessionsContent::Inner::setupContent() { content, object_ptr(content)))->setDuration(0); const auto listInner = listWrap->entity(); - AddSkip(listInner); + AddSkip(listInner, st::sessionSubtitleSkip); AddSubsectionTitle(listInner, tr::lng_sessions_other_header()); - _list = listInner->add(object_ptr(listInner)); + _list = ListController::Add(listInner, session); AddSkip(listInner); + AddDividerText(listInner, tr::lng_sessions_about_apps()); + + const auto ttlWrap = content->add( + object_ptr>( + content, + object_ptr(content)))->setDuration(0); + const auto ttlInner = ttlWrap->entity(); + AddSkip(ttlInner, st::sessionSubtitleSkip); + AddSubsectionTitle(ttlInner, tr::lng_settings_terminate_title()); + + AddButtonWithLabel( + ttlInner, + tr::lng_settings_terminate_if(), + _ttlDays.value( + ) | rpl::map(SelfDestructionBox::DaysLabel), + st::settingsButton + )->addClickHandler([=] { + _controller->show(Box( + &_controller->session(), + SelfDestructionBox::Type::Sessions, + _ttlDays.value())); + }); + + AddSkip(ttlInner); const auto placeholder = content->add( object_ptr>( @@ -365,6 +1004,7 @@ void SessionsContent::Inner::setupContent() { (_1 + _2) > 0)); incompleteWrap->toggleOn(_incomplete->itemsCount() | rpl::map(_1 > 0)); listWrap->toggleOn(_list->itemsCount() | rpl::map(_1 > 0)); + ttlWrap->toggleOn(_list->itemsCount() | rpl::map(_1 > 0)); placeholder->toggleOn(_list->itemsCount() | rpl::map(_1 == 0)); Ui::ResizeFitChild(this, content); @@ -382,146 +1022,130 @@ rpl::producer<> SessionsContent::Inner::terminateAll() const { rpl::producer SessionsContent::Inner::terminateOne() const { return rpl::merge( - _incomplete->terminate(), - _list->terminate()); + _incomplete->terminateRequests(), + _list->terminateRequests()); } -void SessionsContent::Inner::terminatingOne(uint64 hash, bool terminating) { - _incomplete->terminating(hash, terminating); - _list->terminating(hash, terminating); +rpl::producer SessionsContent::Inner::showRequests() const { + return rpl::merge( + _current->showRequests(), + _incomplete->showRequests(), + _list->showRequests()); } -SessionsContent::List::List(QWidget *parent) : RpWidget(parent) { - setAttribute(Qt::WA_OpaquePaintEvent); +SessionsContent::ListController::ListController( + not_null session) +: _session(session) { } -void SessionsContent::List::resizeEvent(QResizeEvent *e) { - RpWidget::resizeEvent(e); - - computeRowWidth(); +Main::Session &SessionsContent::ListController::session() const { + return *_session; } -void SessionsContent::List::showData(gsl::span items) { - computeRowWidth(); +void SessionsContent::ListController::subscribeToCustomDeviceModel() { + Core::App().settings().deviceModelChanges( + ) | rpl::start_with_next([=](const QString &model) { + for (auto i = 0; i != delegate()->peerListFullRowsCount(); ++i) { + const auto row = delegate()->peerListRowAt(i); + if (!row->id()) { + static_cast(row.get())->updateName(model); + } + } + }, lifetime()); +} - auto buttons = base::take(_terminateButtons); - _items.clear(); - _items.insert(begin(_items), items.begin(), items.end()); - for (const auto &entry : _items) { - const auto hash = entry.hash; - if (!hash) { +void SessionsContent::ListController::prepare() { +} + +void SessionsContent::ListController::rowClicked( + not_null row) { + _showRequests.fire_copy(static_cast(row.get())->data()); +} + +void SessionsContent::ListController::rowElementClicked( + not_null row, + int element) { + if (element == 2) { + if (const auto hash = static_cast(row.get())->data().hash) { + _terminateRequests.fire_copy(hash); + } + } +} + +void SessionsContent::ListController::rowUpdateRow(not_null row) { + delegate()->peerListUpdateRow(row); +} + +void SessionsContent::ListController::showData( + gsl::span items) { + auto index = 0; + auto positions = base::flat_map(); + positions.reserve(items.size()); + for (const auto &entry : items) { + const auto id = entry.hash; + positions.emplace(id, index++); + if (const auto row = delegate()->peerListFindRow(id)) { + static_cast(row)->update(entry); + } else { + delegate()->peerListAppendRow( + std::make_unique(this, entry)); + } + } + for (auto i = 0; i != delegate()->peerListFullRowsCount();) { + const auto row = delegate()->peerListRowAt(i); + if (positions.contains(row->id())) { + ++i; continue; } - const auto button = [&] { - const auto i = buttons.find(hash); - return _terminateButtons.emplace( - hash, - (i != end(buttons) - ? std::move(i->second) - : std::make_unique( - this, - st::sessionTerminate))).first->second.get(); - }(); - button->setClickedCallback([=] { - _terminate.fire_copy(hash); - }); - button->show(); - const auto number = _terminateButtons.size() - 1; - widthValue( - ) | rpl::start_with_next([=] { - button->moveToRight( - st::sessionTerminateSkip, - (number * st::sessionHeight + st::sessionTerminateTop)); - }, lifetime()); + delegate()->peerListRemoveRow(row); } - resizeToWidth(width()); - _itemsCount.fire(_items.size()); + delegate()->peerListSortRows([&]( + const PeerListRow &a, + const PeerListRow &b) { + return positions[a.id()] < positions[b.id()]; + }); + delegate()->peerListRefreshRows(); + _itemsCount.fire(delegate()->peerListFullRowsCount()); } -rpl::producer SessionsContent::List::itemsCount() const { - return _itemsCount.events_starting_with(_items.size()); +rpl::producer SessionsContent::ListController::itemsCount() const { + return _itemsCount.events_starting_with( + delegate()->peerListFullRowsCount()); } -rpl::producer SessionsContent::List::terminate() const { - return _terminate.events(); +rpl::producer SessionsContent::ListController::terminateRequests() const { + return _terminateRequests.events(); } -void SessionsContent::List::terminating(uint64 hash, bool terminating) { - const auto i = _terminateButtons.find(hash); - if (i != _terminateButtons.cend()) { - if (terminating) { - i->second->clearState(); - i->second->hide(); - } else { - i->second->show(); - } - } +rpl::producer SessionsContent::ListController::showRequests() const { + return _showRequests.events(); } -int SessionsContent::List::resizeGetHeight(int newWidth) { - return _items.size() * st::sessionHeight; +auto SessionsContent::ListController::Add( + not_null container, + not_null session, + style::margins margins) +-> std::unique_ptr { + auto &lifetime = container->lifetime(); + const auto delegate = lifetime.make_state< + PeerListContentDelegateSimple + >(); + auto controller = std::make_unique(session); + controller->setStyleOverrides(&st::sessionList); + const auto content = container->add( + object_ptr( + container, + controller.get()), + margins); + delegate->setContent(content); + controller->setDelegate(delegate); + return controller; } -void SessionsContent::List::computeRowWidth() { - const auto available = width() - - st::sessionPadding.left() - - st::sessionTerminateSkip; - _rowWidth = { - .available = available, - .info = available - st::sessionTerminate.width, - }; -} - -void SessionsContent::List::paintEvent(QPaintEvent *e) { - QRect r(e->rect()); - Painter p(this); - - p.fillRect(r, st::boxBg); - p.setFont(st::linkFont); - const auto count = int(_items.size()); - const auto from = floorclamp(r.y(), st::sessionHeight, 0, count); - const auto till = ceilclamp( - r.y() + r.height(), - st::sessionHeight, - 0, - count); - - const auto available = _rowWidth.available; - const auto x = st::sessionPadding.left(); - const auto y = st::sessionPadding.top(); - const auto w = width(); - const auto xact = st::sessionTerminateSkip - + st::sessionTerminate.iconPosition.x(); - p.translate(0, from * st::sessionHeight); - for (auto i = from; i != till; ++i) { - const auto &entry = _items[i]; - - const auto activeW = entry.active.maxWidth(); - const auto nameW = available - - activeW - - st::sessionNamePadding.right(); - const auto nameH = entry.name.style()->font->height; - const auto infoW = entry.hash ? _rowWidth.info : available; - const auto infoH = entry.info.style()->font->height; - - p.setPen(entry.hash ? st::sessionWhenFg : st::contactsStatusFgOnline); - entry.active.drawRight(p, xact, y, activeW, w); - - p.setPen(st::sessionNameFg); - entry.name.drawLeftElided(p, x, y, nameW, w); - - p.setPen(st::boxTextFg); - entry.info.drawLeftElided(p, x, y + nameH, infoW, w); - - p.setPen(st::sessionInfoFg); - entry.ip.drawLeftElided(p, x, y + nameH + infoH, available, w); - - p.translate(0, st::sessionHeight); - } -} - -SessionsBox::SessionsBox(QWidget*, not_null session) -: _session(session) { +SessionsBox::SessionsBox( + QWidget*, + not_null controller) +: _controller(controller) { } void SessionsBox::prepare() { @@ -532,7 +1156,7 @@ void SessionsBox::prepare() { const auto w = st::boxWideWidth; const auto content = setInnerWidget( - object_ptr(this, _session), + object_ptr(this, _controller), st::sessionsScroll); content->resize(w, st::noContactsHeight); content->setupContent(); @@ -551,8 +1175,9 @@ Sessions::Sessions( void Sessions::setupContent(not_null controller) { const auto container = Ui::CreateChild(this); + AddSkip(container, st::settingsPrivacySkip); const auto content = container->add( - object_ptr(container, &controller->session())); + object_ptr(container, controller)); content->setupContent(); Ui::ResizeFitChild(this, container); diff --git a/Telegram/SourceFiles/boxes/sessions_box.h b/Telegram/SourceFiles/boxes/sessions_box.h index bbde05411..ac6654015 100644 --- a/Telegram/SourceFiles/boxes/sessions_box.h +++ b/Telegram/SourceFiles/boxes/sessions_box.h @@ -31,12 +31,12 @@ private: class SessionsBox : public Ui::BoxContent { public: - SessionsBox(QWidget*, not_null session); + SessionsBox(QWidget*, not_null controller); protected: void prepare() override; private: - const not_null _session; + const not_null _controller; }; diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index 9ceb3cb70..4f5d18bc5 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -566,8 +566,8 @@ bool ShareBox::showMenu(not_null button) { addGroupingOption(Data::GroupingOptions::Separate, "ktg_forward_menu_separate_messages", 2); } - const auto parentTopLeft = window()->mapToGlobal({ 0, 0 }); - const auto buttonTopLeft = button->mapToGlobal({ 0, 0 }); + const auto parentTopLeft = window()->mapToGlobal(QPoint()); + const auto buttonTopLeft = button->mapToGlobal(QPoint()); const auto parentRect = QRect(parentTopLeft, window()->size()); const auto buttonRect = QRect(buttonTopLeft, button->size()); _menu->move( @@ -652,9 +652,7 @@ void ShareBox::submit(Api::SendOptions options) { } void ShareBox::submitSilent() { - auto options = Api::SendOptions(); - options.silent = true; - submit(options); + submit({ .silent = true }); } void ShareBox::submitScheduled() { diff --git a/Telegram/SourceFiles/boxes/sticker_set_box.cpp b/Telegram/SourceFiles/boxes/sticker_set_box.cpp index 778ad3440..0bd7ee38a 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp +++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp @@ -403,8 +403,8 @@ bool StickerSetBox::showMenu(not_null button) { _menu->addAction(tr::lng_stickers_archive_pack(tr::now), archive); } - const auto parentTopLeft = window()->mapToGlobal({ 0, 0 }); - const auto buttonTopLeft = button->mapToGlobal({ 0, 0 }); + const auto parentTopLeft = window()->mapToGlobal(QPoint()); + const auto buttonTopLeft = button->mapToGlobal(QPoint()); const auto parentRect = QRect(parentTopLeft, window()->size()); const auto buttonRect = QRect(buttonTopLeft, button->size()); _menu->move( @@ -437,10 +437,11 @@ StickerSetBox::Inner::Inner( , _input(set) , _previewTimer([=] { showPreview(); }) { _api.request(MTPmessages_GetStickerSet( - Data::InputStickerSet(_input) + Data::InputStickerSet(_input), + MTP_int(0) // hash )).done([=](const MTPmessages_StickerSet &result) { gotSet(result); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _loaded = true; _errors.fire(Error::NotFound); }).send(); @@ -533,6 +534,8 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) { set->setThumbnail(_setThumbnail); } }); + }, [&](const MTPDmessages_stickerSetNotModified &data) { + LOG(("API Error: Unexpected messages.stickerSetNotModified.")); }); if (_pack.isEmpty()) { @@ -695,7 +698,7 @@ void StickerSetBox::Inner::mouseReleaseEvent(QMouseEvent *e) { if (index < 0 || index >= _pack.size() || isMasksSet()) { return; } - send(_pack[index], Api::SendOptions()); + send(_pack[index], {}); } void StickerSetBox::Inner::send( @@ -1001,7 +1004,7 @@ void StickerSetBox::Inner::install() { MTP_bool(false) )).done([=](const MTPmessages_StickerSetInstallResult &result) { installDone(result); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _errors.fire(Error::NotFound); }).send(); } @@ -1014,7 +1017,7 @@ void StickerSetBox::Inner::archiveStickers() { if (result.type() == mtpc_messages_stickerSetInstallResultSuccess) { _setArchived.fire_copy(_setId); } - }).fail([](const MTP::Error &error) { + }).fail([] { Ui::Toast::Show(Lang::Hard::ServerError()); }).send(); } diff --git a/Telegram/SourceFiles/boxes/stickers_box.cpp b/Telegram/SourceFiles/boxes/stickers_box.cpp index fcdab0848..763ef4101 100644 --- a/Telegram/SourceFiles/boxes/stickers_box.cpp +++ b/Telegram/SourceFiles/boxes/stickers_box.cpp @@ -492,7 +492,7 @@ void StickersBox::getArchivedDone( const auto index = archived.indexOf(set->id); if (archived.isEmpty() || index != archived.size() - 1) { changedSets = true; - if (index < archived.size() - 1) { + if (index >= 0 && index < archived.size() - 1) { archived.removeAt(index); } archived.push_back(set->id); @@ -1887,12 +1887,17 @@ void StickersBox::Inner::handleMegagroupSetAddressChange() { } } else if (!_megagroupSetRequestId) { _megagroupSetRequestId = _api.request(MTPmessages_GetStickerSet( - MTP_inputStickerSetShortName(MTP_string(text)) + MTP_inputStickerSetShortName(MTP_string(text)), + MTP_int(0) // hash )).done([=](const MTPmessages_StickerSet &result) { _megagroupSetRequestId = 0; - auto set = session().data().stickers().feedSetFull(result); - setMegagroupSelectedSet(set->identifier()); - }).fail([=](const MTP::Error &error) { + result.match([&](const MTPDmessages_stickerSet &data) { + const auto set = session().data().stickers().feedSetFull(data); + setMegagroupSelectedSet(set->identifier()); + }, [](const MTPDmessages_stickerSetNotModified &) { + LOG(("API Error: Unexpected messages.stickerSetNotModified.")); + }); + }).fail([=] { _megagroupSetRequestId = 0; setMegagroupSelectedSet({}); }).send(); diff --git a/Telegram/SourceFiles/boxes/url_auth_box.cpp b/Telegram/SourceFiles/boxes/url_auth_box.cpp index 909d04747..685d3ca3c 100644 --- a/Telegram/SourceFiles/boxes/url_auth_box.cpp +++ b/Telegram/SourceFiles/boxes/url_auth_box.cpp @@ -67,7 +67,7 @@ void UrlAuthBox::Activate( Request(data, item, row, column); } }); - }).fail([=](const MTP::Error &error) { + }).fail([=] { const auto button = HistoryMessageMarkupButton::Get( &session->data(), itemId, @@ -105,7 +105,7 @@ void UrlAuthBox::Activate( }, [&](const MTPDurlAuthResultRequest &data) { Request(data, session, url, context); }); - }).fail([=](const MTP::Error &error) { + }).fail([=] { HiddenUrlClickHandler::Open(url, context); }).send(); } @@ -165,7 +165,7 @@ void UrlAuthBox::Request( return url; }); finishWithUrl(to); - }).fail([=](const MTP::Error &error) { + }).fail([=] { finishWithUrl(url); }).send(); } @@ -216,7 +216,7 @@ void UrlAuthBox::Request( return url; }); finishWithUrl(to); - }).fail([=](const MTP::Error &error) { + }).fail([=] { finishWithUrl(url); }).send(); } diff --git a/Telegram/SourceFiles/calls/calls_box_controller.cpp b/Telegram/SourceFiles/calls/calls_box_controller.cpp index edb664c77..616a32687 100644 --- a/Telegram/SourceFiles/calls/calls_box_controller.cpp +++ b/Telegram/SourceFiles/calls/calls_box_controller.cpp @@ -346,7 +346,7 @@ void BoxController::loadMoreRows() { } break; default: Unexpected("Type of messages.Messages (Calls::BoxController::preloadRows)"); } - }).fail([this](const MTP::Error &error) { + }).fail([this] { _loadRequestId = 0; }).send(); } diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp index d15c24b37..fde79072e 100644 --- a/Telegram/SourceFiles/calls/calls_call.cpp +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -36,8 +36,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace tgcalls { class InstanceImpl; +class InstanceV2Impl; class InstanceImplLegacy; -class InstanceImplReference; void SetLegacyGlobalServerConfig(const std::string &serverConfig); } // namespace tgcalls @@ -50,8 +50,9 @@ constexpr auto kSha256Size = 32; constexpr auto kAuthKeySize = 256; const auto kDefaultVersion = "2.4.4"_q; -const auto RegisterTag = tgcalls::Register(); -const auto RegisterTagLegacy = tgcalls::Register(); +const auto Register = tgcalls::Register(); +const auto RegisterV2 = tgcalls::Register(); +const auto RegisterLegacy = tgcalls::Register(); void AppendEndpoint( std::vector &list, @@ -285,7 +286,7 @@ void Call::startIncoming() { _api.request(MTPphone_ReceivedCall( MTP_inputPhoneCall(MTP_long(_id), MTP_long(_accessHash)) - )).done([=](const MTPBool &result) { + )).done([=] { if (_state.current() == State::Starting) { setState(State::WaitingIncoming); } @@ -386,12 +387,15 @@ void Call::setupOutgoingVideo() { _videoCaptureIsScreencast); _videoCapture->setOutput(_videoOutgoing->sink()); } + _videoCapture->setState(tgcalls::VideoState::Active); if (_instance) { _instance->setVideoCapture(_videoCapture); } - _videoCapture->setState(tgcalls::VideoState::Active); } else if (_videoCapture) { _videoCapture->setState(tgcalls::VideoState::Inactive); + if (_instance) { + _instance->setVideoCapture(nullptr); + } } }, _lifetime); } @@ -1205,7 +1209,7 @@ void Call::finish(FinishType type, const MTPPhoneCallDiscardReason &reason) { // updates being handled, but in a guarded way. crl::on_main(weak, [=] { setState(finalState); }); session->api().applyUpdates(result); - }).fail(crl::guard(weak, [this, finalState](const MTP::Error &error) { + }).fail(crl::guard(weak, [this, finalState] { setState(finalState); })).send(); } diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index 417370501..b38d5c07c 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -333,7 +333,7 @@ void Instance::refreshDhConfig() { } else { _delegate->callFailed(call); } - }).fail([=](const MTP::Error &error) { + }).fail([=] { const auto call = weak.get(); if (!call) { return; @@ -392,7 +392,7 @@ void Instance::refreshServerConfig(not_null session) { const auto &json = result.c_dataJSON().vdata().v; UpdateConfig(std::string(json.data(), json.size())); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _serverConfigRequestSession = nullptr; }).send(); } @@ -504,8 +504,6 @@ void Instance::handleCallUpdate( } else if (phoneCall.vdate().v + (config.callRingTimeoutMs / 1000) < base::unixtime::now()) { LOG(("Ignoring too old call.")); - } else if (Core::App().settings().disableCalls()) { - LOG(("Ignoring call because of 'accept calls' settings.")); } else { createCall(user, Call::Type::Incoming, phoneCall.is_video()); _currentCall->handleUpdate(call); diff --git a/Telegram/SourceFiles/calls/calls_userpic.cpp b/Telegram/SourceFiles/calls/calls_userpic.cpp index 8d148f6ce..dd1d71a3a 100644 --- a/Telegram/SourceFiles/calls/calls_userpic.cpp +++ b/Telegram/SourceFiles/calls/calls_userpic.cpp @@ -153,7 +153,7 @@ void Userpic::processPhoto() { _photo->wanted(Data::PhotoSize::Thumbnail, _peer->userpicPhotoOrigin()); } else { _photo = nullptr; - if (_peer->userpicPhotoUnknown() || (photo && !photo->date)) { + if (_peer->userpicPhotoUnknown() || (photo && photo->isNull())) { _peer->session().api().requestFullPeer(_peer); } } diff --git a/Telegram/SourceFiles/calls/group/calls_choose_join_as.cpp b/Telegram/SourceFiles/calls/group/calls_choose_join_as.cpp index 655f1165e..4293feeea 100644 --- a/Telegram/SourceFiles/calls/group/calls_choose_join_as.cpp +++ b/Telegram/SourceFiles/calls/group/calls_choose_join_as.cpp @@ -467,7 +467,7 @@ void ChooseJoinAsProcess::start( _request->box = box.data(); _request->showBox(std::move(box)); - }).fail([=](const MTP::Error &error) { + }).fail([=] { finish({ .peer = _request->peer, .joinAs = _request->peer->session().user(), diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index bcee0a6cc..b63d57804 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -1604,7 +1604,7 @@ void GroupCall::discard() { // updates being handled, but in a guarded way. crl::on_main(this, [=] { hangup(); }); _peer->session().api().applyUpdates(result); - }).fail([=](const MTP::Error &error) { + }).fail([=] { hangup(); }).send(); } @@ -1672,7 +1672,7 @@ void GroupCall::leave() { // updates being handled, but in a guarded way. crl::on_main(weak, [=] { setState(finalState); }); session->api().applyUpdates(result); - }).fail(crl::guard(weak, [=](const MTP::Error &error) { + }).fail(crl::guard(weak, [=] { setState(finalState); })).send(); } @@ -2225,7 +2225,6 @@ void GroupCall::changeTitle(const QString &title) { )).done([=](const MTPUpdates &result) { _peer->session().api().applyUpdates(result); _titleChanged.fire({}); - }).fail([=](const MTP::Error &error) { }).send(); } @@ -2258,7 +2257,7 @@ void GroupCall::toggleRecording( )).done([=](const MTPUpdates &result) { _peer->session().api().applyUpdates(result); _recordingStoppedByMe = false; - }).fail([=](const MTP::Error &error) { + }).fail([=] { _recordingStoppedByMe = false; }).send(); } diff --git a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp index 74aecd992..d8ed2c237 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_invite_controller.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "calls/group/calls_group_invite_controller.h" +#include "api/api_chat_participants.h" #include "calls/group/calls_group_call.h" #include "calls/group/calls_group_menu.h" #include "boxes/peer_lists_box.h" @@ -221,7 +222,7 @@ object_ptr PrepareInviteBox( const std::vector> &users, const std::vector> &nonMembers, Fn finish) { - peer->session().api().addChatParticipants( + peer->session().api().chatParticipants().add( peer, nonMembers, [=](bool) { invite(users); finish(); }); diff --git a/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp b/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp index ec673d57f..aaa7a9cea 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp @@ -126,12 +126,7 @@ MembersRow::MembersRow( not_null delegate, not_null participantPeer) : PeerListRow(participantPeer) -, _delegate(delegate) -, _sounding(false) -, _speaking(false) -, _raisedHandStatus(false) -, _skipLevelUpdate(false) -, _mutedByMe(false) { +, _delegate(delegate) { refreshStatus(); _aboutText = participantPeer->about(); } diff --git a/Telegram/SourceFiles/calls/group/calls_group_members_row.h b/Telegram/SourceFiles/calls/group/calls_group_members_row.h index bbabbbe8c..85f57664d 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members_row.h +++ b/Telegram/SourceFiles/calls/group/calls_group_members_row.h @@ -210,11 +210,11 @@ private: crl::time _speakingLastTime = 0; uint64 _raisedHandRating = 0; int _volume = Group::kDefaultVolume; - bool _sounding : 1; - bool _speaking : 1; - bool _raisedHandStatus : 1; - bool _skipLevelUpdate : 1; - bool _mutedByMe : 1; + bool _sounding : 1 = false; + bool _speaking : 1 = false; + bool _raisedHandStatus : 1 = false; + bool _skipLevelUpdate : 1 = false; + bool _mutedByMe : 1 = false; }; diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 870d0c6df..f0248ed7d 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -49,7 +49,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unixtime.h" #include "base/qt_signal_producer.h" #include "base/timer_rpl.h" -#include "apiwrap.h" // api().kickParticipant. +#include "apiwrap.h" // api().kick. +#include "api/api_chat_participants.h" // api().kick. #include "webrtc/webrtc_video_track.h" #include "webrtc/webrtc_media_devices.h" // UniqueDesktopCaptureSource. #include "webrtc/webrtc_audio_input_tester.h" @@ -1344,7 +1345,7 @@ void Panel::showBox( void Panel::kickParticipantSure(not_null participantPeer) { if (const auto chat = _peer->asChat()) { - chat->session().api().kickParticipant(chat, participantPeer); + chat->session().api().chatParticipants().kick(chat, participantPeer); } else if (const auto channel = _peer->asChannel()) { const auto currentRestrictedRights = [&] { const auto user = participantPeer->asUser(); @@ -1356,7 +1357,7 @@ void Panel::kickParticipantSure(not_null participantPeer) { ? i->second.rights : ChatRestrictionsInfo(); }(); - channel->session().api().kickParticipant( + channel->session().api().chatParticipants().kick( channel, participantPeer, currentRestrictedRights); diff --git a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp index 36e6b98ee..e32438a72 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp @@ -177,9 +177,9 @@ object_ptr ShareInviteLinkBox( auto &api = peer->session().api(); for (const auto peer : result) { const auto history = owner->history(peer); - auto message = ApiWrap::MessageToSend(history); + auto message = Api::MessageToSend( + Api::SendAction(history, options)); message.textWithTags = comment; - message.action.options = options; message.action.clearDraft = false; api.sendMessage(std::move(message)); } diff --git a/Telegram/SourceFiles/chat_helpers/emoji_keywords.cpp b/Telegram/SourceFiles/chat_helpers/emoji_keywords.cpp index 72eb9b1d9..a532de970 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_keywords.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_keywords.cpp @@ -400,7 +400,7 @@ void EmojiKeywords::LangPack::refresh() { _requestId = 0; _lastRefreshTime = crl::now(); applyDifference(result); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _requestId = 0; _lastRefreshTime = crl::now(); }).send(); @@ -670,7 +670,7 @@ void EmojiKeywords::refreshRemoteList() { }); }) | ranges::to_vector); _langsRequestId = 0; - }).fail([=](const MTP::Error &error) { + }).fail([=] { _langsRequestId = 0; }).send(); } diff --git a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp index 1d2282339..c0aef0d3c 100644 --- a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp +++ b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp @@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/message_field.h" // PrepareMentionTag. #include "mainwindow.h" #include "apiwrap.h" +#include "api/api_chat_participants.h" #include "main/main_session.h" #include "storage/storage_account.h" #include "core/application.h" @@ -66,7 +67,7 @@ public: bool chooseAtIndex( FieldAutocomplete::ChooseMethod method, int index, - Api::SendOptions options = Api::SendOptions()) const; + Api::SendOptions options = {}) const; void setRecentInlineBotsInRows(int32 bots); void setSendMenuType(Fn &&callback); @@ -431,7 +432,8 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) { } } else if (_channel && _channel->isMegagroup()) { if (_channel->lastParticipantsRequestNeeded()) { - _channel->session().api().requestLastParticipants(_channel); + _channel->session().api().chatParticipants().requestLast( + _channel); } else { mrows.reserve(mrows.size() + _channel->mgInfo->lastParticipants.size()); for (const auto user : _channel->mgInfo->lastParticipants) { @@ -489,7 +491,8 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) { } else if (_channel && _channel->isMegagroup()) { if (_channel->mgInfo->bots.empty()) { if (!_channel->mgInfo->botStatus) { - _channel->session().api().requestBots(_channel); + _channel->session().api().chatParticipants().requestBots( + _channel); } } else { const auto &commands = _channel->mgInfo->botCommands(); diff --git a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp index 252e701df..ac43fa5de 100644 --- a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp @@ -855,7 +855,7 @@ void GifsListWidget::sendInlineRequest() { MTP_string(nextOffset) )).done([this](const MTPmessages_BotResults &result) { inlineResultsDone(result); - }).fail([this](const MTP::Error &error) { + }).fail([this] { // show error? _footer->setLoading(false); _inlineRequestId = 0; diff --git a/Telegram/SourceFiles/chat_helpers/send_context_menu.cpp b/Telegram/SourceFiles/chat_helpers/send_context_menu.cpp index 596ed7b92..0bd3cf82c 100644 --- a/Telegram/SourceFiles/chat_helpers/send_context_menu.cpp +++ b/Telegram/SourceFiles/chat_helpers/send_context_menu.cpp @@ -195,7 +195,7 @@ void SetupUnreadMentionsMenu( )).done([=](const MTPmessages_AffectedHistory &result) { state->sentForPeers.remove(peer); peer->session().api().applyAffectedHistory(peer, result); - }).fail([=](const MTP::Error &error) { + }).fail([=] { state->sentForPeers.remove(peer); }).send(); }); diff --git a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp index 4c2335b55..d197246e8 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_dice_pack.cpp @@ -47,12 +47,15 @@ void DicePack::load() { return; } _requestId = _session->api().request(MTPmessages_GetStickerSet( - MTP_inputStickerSetDice(MTP_string(_emoji)) + MTP_inputStickerSetDice(MTP_string(_emoji)), + MTP_int(0) // hash )).done([=](const MTPmessages_StickerSet &result) { result.match([&](const MTPDmessages_stickerSet &data) { applySet(data); + }, [](const MTPDmessages_stickerSetNotModified &) { + LOG(("API Error: Unexpected messages.stickerSetNotModified.")); }); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _requestId = 0; }).send(); } diff --git a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp index 5192e6dce..233190f00 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp @@ -55,49 +55,24 @@ constexpr auto kRefreshTimeout = 7200 * crl::time(1000); Expects(index >= 1 && index <= 5); static const auto color1 = Lottie::ColorReplacements{ - { - { 0xf77e41U, 0xcb7b55U }, - { 0xffb139U, 0xf6b689U }, - { 0xffd140U, 0xffcda7U }, - { 0xffdf79U, 0xffdfc5U }, - }, - 1, + .modifier = Lottie::SkinModifier::Color1, + .tag = 1, }; static const auto color2 = Lottie::ColorReplacements{ - { - { 0xf77e41U, 0xa45a38U }, - { 0xffb139U, 0xdf986bU }, - { 0xffd140U, 0xedb183U }, - { 0xffdf79U, 0xf4c3a0U }, - }, - 2, + .modifier = Lottie::SkinModifier::Color2, + .tag = 2, }; static const auto color3 = Lottie::ColorReplacements{ - { - { 0xf77e41U, 0x703a17U }, - { 0xffb139U, 0xab673dU }, - { 0xffd140U, 0xc37f4eU }, - { 0xffdf79U, 0xd89667U }, - }, - 3, + .modifier = Lottie::SkinModifier::Color3, + .tag = 3, }; static const auto color4 = Lottie::ColorReplacements{ - { - { 0xf77e41U, 0x4a2409U }, - { 0xffb139U, 0x7d3e0eU }, - { 0xffd140U, 0x965529U }, - { 0xffdf79U, 0xa96337U }, - }, - 4, + .modifier = Lottie::SkinModifier::Color4, + .tag = 4, }; static const auto color5 = Lottie::ColorReplacements{ - { - { 0xf77e41U, 0x200f0aU }, - { 0xffb139U, 0x412924U }, - { 0xffd140U, 0x593d37U }, - { 0xffdf79U, 0x63453fU }, - }, - 5, + .modifier = Lottie::SkinModifier::Color5, + .tag = 5, }; static const auto list = std::array{ &color1, @@ -237,12 +212,15 @@ void EmojiPack::refresh() { return; } _requestId = _session->api().request(MTPmessages_GetStickerSet( - MTP_inputStickerSetAnimatedEmoji() + MTP_inputStickerSetAnimatedEmoji(), + MTP_int(0) // hash )).done([=](const MTPmessages_StickerSet &result) { _requestId = 0; refreshAnimations(); result.match([&](const MTPDmessages_stickerSet &data) { applySet(data); + }, [](const MTPDmessages_stickerSetNotModified &) { + LOG(("API Error: Unexpected messages.stickerSetNotModified.")); }); }).fail([=](const MTP::Error &error) { _requestId = 0; @@ -255,14 +233,17 @@ void EmojiPack::refreshAnimations() { return; } _animationsRequestId = _session->api().request(MTPmessages_GetStickerSet( - MTP_inputStickerSetAnimatedEmojiAnimations() + MTP_inputStickerSetAnimatedEmojiAnimations(), + MTP_int(0) // hash )).done([=](const MTPmessages_StickerSet &result) { _animationsRequestId = 0; refreshDelayed(); result.match([&](const MTPDmessages_stickerSet &data) { applyAnimationsSet(data); + }, [](const MTPDmessages_stickerSetNotModified &) { + LOG(("API Error: Unexpected messages.stickerSetNotModified.")); }); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _animationsRequestId = 0; refreshDelayed(); }).send(); diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp index b752dc538..690b4b142 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp @@ -1110,7 +1110,6 @@ void StickersListWidget::preloadMoreOfficial() { }); resizeToWidth(width()); update(); - }).fail([=](const MTP::Error &error) { }).send(); } @@ -1280,7 +1279,7 @@ void StickersListWidget::sendSearchRequest() { MTP_long(hash) )).done([=](const MTPmessages_FoundStickerSets &result) { searchResultsDone(result); - }).fail([=](const MTP::Error &error) { + }).fail([=] { // show error? _footer->setLoading(false); _searchRequestId = 0; @@ -2786,16 +2785,21 @@ void StickersListWidget::refreshMegagroupStickers(GroupStickersPlace place) { } _megagroupSetIdRequested = set.id; _api.request(MTPmessages_GetStickerSet( - Data::InputStickerSet(set) + Data::InputStickerSet(set), + MTP_int(0) // hash )).done([=](const MTPmessages_StickerSet &result) { - if (const auto set = session().data().stickers().feedSetFull(result)) { - refreshStickers(); - if (set->id == _megagroupSetIdRequested) { - _megagroupSetIdRequested = 0; - } else { - LOG(("API Error: Got different set.")); + result.match([&](const MTPDmessages_stickerSet &data) { + if (const auto set = session().data().stickers().feedSetFull(data)) { + refreshStickers(); + if (set->id == _megagroupSetIdRequested) { + _megagroupSetIdRequested = 0; + } else { + LOG(("API Error: Got different set.")); + } } - } + }, [](const MTPDmessages_stickerSetNotModified &) { + LOG(("API Error: Unexpected messages.stickerSetNotModified.")); + }); }).send(); } @@ -3122,9 +3126,14 @@ void StickersListWidget::installSet(uint64 setId) { const auto input = set->mtpInput(); if ((set->flags & SetFlag::NotLoaded) || set->stickers.empty()) { _api.request(MTPmessages_GetStickerSet( - input + input, + MTP_int(0) // hash )).done([=](const MTPmessages_StickerSet &result) { - session().data().stickers().feedSetFull(result); + result.match([&](const MTPDmessages_stickerSet &data) { + session().data().stickers().feedSetFull(data); + }, [](const MTPDmessages_stickerSetNotModified &) { + LOG(("API Error: Unexpected messages.stickerSetNotModified.")); + }); sendInstallRequest(setId, input); }).send(); } else { @@ -3144,7 +3153,7 @@ void StickersListWidget::sendInstallRequest( session().data().stickers().applyArchivedResult( result.c_messages_stickerSetInstallResultArchive()); } - }).fail([=](const MTP::Error &error) { + }).fail([=] { notInstalledLocally(setId); session().data().stickers().undoInstallLocally(setId); }).send(); diff --git a/Telegram/SourceFiles/core/changelogs.cpp b/Telegram/SourceFiles/core/changelogs.cpp index b31d15b39..5b68c42fe 100644 --- a/Telegram/SourceFiles/core/changelogs.cpp +++ b/Telegram/SourceFiles/core/changelogs.cpp @@ -129,6 +129,23 @@ std::map BetaLogs() { "- Several crash fixes.\n" }, + { + 3002006, + "- Try out the new audio player with playlist shuffle and repeat.\n" + + "- Give a custom name to your desktop session " + "to distinguish it in the sessions list.\n" + }, + { + 3002007, + "- Active sessions list redesign.\n" + + "- Fix disappearing emoji selector button.\n" + + "- Fix a crash in archived stickers loading.\n" + + "- Fix a crash in calls to old Telegram versions.\n" + } }; }; diff --git a/Telegram/SourceFiles/core/click_handler_types.cpp b/Telegram/SourceFiles/core/click_handler_types.cpp index 764871190..d0ee8295d 100644 --- a/Telegram/SourceFiles/core/click_handler_types.cpp +++ b/Telegram/SourceFiles/core/click_handler_types.cpp @@ -66,6 +66,26 @@ bool UrlRequiresConfirmation(const QUrl &url) { RegExOption::CaseInsensitive); } +QString HiddenUrlClickHandler::copyToClipboardText() const { + return url().startsWith(qstr("internal:url:")) + ? url().mid(qstr("internal:url:").size()) + : url(); +} + +QString HiddenUrlClickHandler::copyToClipboardContextItemText() const { + return url().isEmpty() + ? QString() + : !url().startsWith(qstr("internal:")) + ? UrlClickHandler::copyToClipboardContextItemText() + : url().startsWith(qstr("internal:url:")) + ? UrlClickHandler::copyToClipboardContextItemText() + : QString(); +} + +QString HiddenUrlClickHandler::dragText() const { + return HiddenUrlClickHandler::copyToClipboardText(); +} + void HiddenUrlClickHandler::Open(QString url, QVariant context) { url = Core::TryConvertUrlToLocal(url); if (Core::InternalPassportLink(url)) { diff --git a/Telegram/SourceFiles/core/click_handler_types.h b/Telegram/SourceFiles/core/click_handler_types.h index a4ff0f8b1..6bdb282f9 100644 --- a/Telegram/SourceFiles/core/click_handler_types.h +++ b/Telegram/SourceFiles/core/click_handler_types.h @@ -9,6 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/basic_click_handlers.h" +constexpr auto kPeerLinkPeerIdProperty = 0x01; + namespace Main { class Session; } // namespace Main @@ -39,11 +41,9 @@ class HiddenUrlClickHandler : public UrlClickHandler { public: HiddenUrlClickHandler(QString url) : UrlClickHandler(url, false) { } - QString copyToClipboardContextItemText() const override { - return (url().isEmpty() || url().startsWith(qstr("internal:"))) - ? QString() - : UrlClickHandler::copyToClipboardContextItemText(); - } + QString copyToClipboardText() const override; + QString copyToClipboardContextItemText() const override; + QString dragText() const override; static void Open(QString url, QVariant context = {}); void onClick(ClickContext context) const override { diff --git a/Telegram/SourceFiles/core/core_settings.cpp b/Telegram/SourceFiles/core/core_settings.cpp index a0124b6a6..ca15ba2cf 100644 --- a/Telegram/SourceFiles/core/core_settings.cpp +++ b/Telegram/SourceFiles/core/core_settings.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/section_widget.h" #include "base/platform/base_platform_info.h" #include "webrtc/webrtc_create_adm.h" +#include "media/player/media_player_instance.h" #include "ui/gl/gl_detection.h" #include "calls/group/calls_group_common.h" #include "facades.h" @@ -124,7 +125,9 @@ QByteArray Settings::serialize() const { + Serialize::bytearraySize(proxy) + sizeof(qint32) * 2 + Serialize::bytearraySize(_photoEditorBrush) - + sizeof(qint32); + + sizeof(qint32) * 3 + + Serialize::stringSize(_customDeviceModel.current()) + + sizeof(qint32) * 2; auto result = QByteArray(); result.reserve(size); @@ -200,7 +203,7 @@ QByteArray Settings::serialize() const { << _groupCallPushToTalkShortcut << qint64(_groupCallPushToTalkDelay) << qint32(0) // Call audio backend - << qint32(_disableCalls ? 1 : 0) + << qint32(0) // Legacy disable calls, now in session settings << windowPosition << qint32(recentEmojiPreloadData.size()); for (const auto &[id, rating] : recentEmojiPreloadData) { @@ -221,7 +224,10 @@ QByteArray Settings::serialize() const { << _photoEditorBrush << qint32(_groupCallNoiseSuppression ? 1 : 0) << qint32(_voicePlaybackSpeed * 100) - << qint32(_closeToTaskbar.current() ? 1 : 0); + << qint32(_closeToTaskbar.current() ? 1 : 0) + << _customDeviceModel.current() + << qint32(_playerRepeatMode.current()) + << qint32(_playerOrderMode.current()); } return result; } @@ -294,7 +300,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { QByteArray groupCallPushToTalkShortcut = _groupCallPushToTalkShortcut; qint64 groupCallPushToTalkDelay = _groupCallPushToTalkDelay; qint32 callAudioBackend = 0; - qint32 disableCalls = _disableCalls ? 1 : 0; + qint32 disableCallsLegacy = 0; QByteArray windowPosition; std::vector recentEmojiPreload; base::flat_map emojiVariants; @@ -305,6 +311,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) { qint32 hiddenGroupCallTooltips = qint32(_hiddenGroupCallTooltips.value()); QByteArray photoEditorBrush = _photoEditorBrush; qint32 closeToTaskbar = _closeToTaskbar.current() ? 1 : 0; + QString customDeviceModel = _customDeviceModel.current(); + qint32 playerRepeatMode = static_cast(_playerRepeatMode.current()); + qint32 playerOrderMode = static_cast(_playerOrderMode.current()); stream >> themesAccentColors; if (!stream.atEnd()) { @@ -404,7 +413,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { stream >> callAudioBackend; } if (!stream.atEnd()) { - stream >> disableCalls; + stream >> disableCallsLegacy; } if (!stream.atEnd()) { stream >> windowPosition; @@ -465,6 +474,14 @@ void Settings::addFromSerialized(const QByteArray &serialized) { if (!stream.atEnd()) { stream >> closeToTaskbar; } + if (!stream.atEnd()) { + stream >> customDeviceModel; + } + if (!stream.atEnd()) { + stream + >> playerRepeatMode + >> playerOrderMode; + } if (stream.status() != QDataStream::Ok) { LOG(("App Error: " "Bad data for Core::Settings::constructFromSerialized()")); @@ -576,7 +593,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { _groupCallPushToTalk = (groupCallPushToTalk == 1); _groupCallPushToTalkShortcut = groupCallPushToTalkShortcut; _groupCallPushToTalkDelay = groupCallPushToTalkDelay; - _disableCalls = (disableCalls == 1); + _disableCallsLegacy = (disableCallsLegacy == 1); if (!windowPosition.isEmpty()) { _windowPosition = Deserialize(windowPosition); } @@ -606,6 +623,19 @@ void Settings::addFromSerialized(const QByteArray &serialized) { }(); _photoEditorBrush = photoEditorBrush; _closeToTaskbar = (closeToTaskbar == 1); + _customDeviceModel = customDeviceModel; + const auto uncheckedPlayerRepeatMode = static_cast(playerRepeatMode); + switch (uncheckedPlayerRepeatMode) { + case Media::Player::RepeatMode::None: + case Media::Player::RepeatMode::One: + case Media::Player::RepeatMode::All: _playerRepeatMode = uncheckedPlayerRepeatMode; break; + } + const auto uncheckedPlayerOrderMode = static_cast(playerOrderMode); + switch (uncheckedPlayerOrderMode) { + case Media::Player::OrderMode::Default: + case Media::Player::OrderMode::Reverse: + case Media::Player::OrderMode::Shuffle: _playerOrderMode = uncheckedPlayerOrderMode; break; + } } QString Settings::getSoundPath(const QString &key) const { @@ -672,6 +702,23 @@ void Settings::setThirdColumnWidth(int width) { _thirdColumnWidth = width; } +QString Settings::deviceModel() const { + const auto custom = customDeviceModel(); + return custom.isEmpty() ? Platform::DeviceModelPretty() : custom; +} + +rpl::producer Settings::deviceModelChanges() const { + return customDeviceModelChanges() | rpl::map([=] { + return deviceModel(); + }); +} + +rpl::producer Settings::deviceModelValue() const { + return customDeviceModelValue() | rpl::map([=] { + return deviceModel(); + }); +} + int Settings::thirdColumnWidth() const { return _thirdColumnWidth.current(); } @@ -823,7 +870,7 @@ void Settings::resetOnLastLogout() { //_callInputVolume = 100; //_callAudioDuckingEnabled = true; - _disableCalls = false; + _disableCallsLegacy = false; _groupCallPushToTalk = false; _groupCallPushToTalkShortcut = QByteArray(); diff --git a/Telegram/SourceFiles/core/core_settings.h b/Telegram/SourceFiles/core/core_settings.h index 94e72a3bf..675cf2e02 100644 --- a/Telegram/SourceFiles/core/core_settings.h +++ b/Telegram/SourceFiles/core/core_settings.h @@ -33,6 +33,11 @@ namespace Calls::Group { enum class StickedTooltip; } // namespace Calls::Group +namespace Media::Player { +enum class RepeatMode; +enum class OrderMode; +} // namespace Media::Player + namespace Core { struct WindowPosition { @@ -261,11 +266,8 @@ public: _callAudioDuckingEnabled = value; } [[nodiscard]] Webrtc::Backend callAudioBackend() const; - void setDisableCalls(bool value) { - _disableCalls = value; - } - [[nodiscard]] bool disableCalls() const { - return _disableCalls; + [[nodiscard]] bool disableCallsLegacy() const { + return _disableCallsLegacy; } [[nodiscard]] bool groupCallPushToTalk() const { return _groupCallPushToTalk; @@ -615,6 +617,47 @@ public: return _closeToTaskbar.changes(); } + void setCustomDeviceModel(const QString &model) { + _customDeviceModel = model; + } + [[nodiscard]] QString customDeviceModel() const { + return _customDeviceModel.current(); + } + [[nodiscard]] rpl::producer customDeviceModelChanges() const { + return _customDeviceModel.changes(); + } + [[nodiscard]] rpl::producer customDeviceModelValue() const { + return _customDeviceModel.value(); + } + [[nodiscard]] QString deviceModel() const; + [[nodiscard]] rpl::producer deviceModelChanges() const; + [[nodiscard]] rpl::producer deviceModelValue() const; + + void setPlayerRepeatMode(Media::Player::RepeatMode mode) { + _playerRepeatMode = mode; + } + [[nodiscard]] Media::Player::RepeatMode playerRepeatMode() const { + return _playerRepeatMode.current(); + } + [[nodiscard]] rpl::producer playerRepeatModeValue() const { + return _playerRepeatMode.value(); + } + [[nodiscard]] rpl::producer playerRepeatModeChanges() const { + return _playerRepeatMode.changes(); + } + void setPlayerOrderMode(Media::Player::OrderMode mode) { + _playerOrderMode = mode; + } + [[nodiscard]] Media::Player::OrderMode playerOrderMode() const { + return _playerOrderMode.current(); + } + [[nodiscard]] rpl::producer playerOrderModeValue() const { + return _playerOrderMode.value(); + } + [[nodiscard]] rpl::producer playerOrderModeChanges() const { + return _playerOrderMode.changes(); + } + [[nodiscard]] static bool ThirdColumnByDefault(); [[nodiscard]] static float64 DefaultDialogsWidthRatio(); [[nodiscard]] static qint32 SerializePlaybackSpeed(float64 speed) { @@ -669,7 +712,7 @@ private: int _callOutputVolume = 100; int _callInputVolume = 100; bool _callAudioDuckingEnabled = true; - bool _disableCalls = false; + bool _disableCallsLegacy = false; bool _groupCallPushToTalk = false; bool _groupCallNoiseSuppression = false; QByteArray _groupCallPushToTalkShortcut; @@ -715,6 +758,9 @@ private: rpl::variable _workMode = WorkMode::WindowAndTray; base::flags _hiddenGroupCallTooltips; rpl::variable _closeToTaskbar = false; + rpl::variable _customDeviceModel; + rpl::variable _playerRepeatMode; + rpl::variable _playerOrderMode; bool _tabbedReplacedWithInfo = false; // per-window rpl::event_stream _tabbedReplacedWithInfoValue; // per-window diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index 0b3f0f802..6d01a93b2 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -383,17 +383,16 @@ bool ResolveSettings( if (section.isEmpty()) { controller->window().showSettings(); return true; - } - if (section == qstr("devices")) { - controller->session().api().authorizations().reload(); - controller->show(Box(&controller->session())); - return true; } else if (section == qstr("language")) { ShowLanguagesBox(); return true; + } else if (section == qstr("devices")) { + controller->session().api().authorizations().reload(); } const auto type = (section == qstr("folders")) ? ::Settings::Type::Folders + : (section == qstr("devices")) + ? ::Settings::Type::Sessions : (section == qstr("kotato")) ? ::Settings::Type::Kotato : ::Settings::Type::Main; @@ -483,6 +482,15 @@ bool ShowInviteLink( return true; } +bool OpenExternalLink( + Window::SessionController *controller, + const Match &match, + const QVariant &context) { + return Ui::Integration::Instance().handleUrlClick( + match->captured(1), + context); +} + void ExportTestChatTheme( not_null session, not_null theme) { @@ -707,6 +715,10 @@ const std::vector &InternalUrlHandlers() { qsl("^show_invite_link/?\\?link=([a-zA-Z0-9_\\+\\/\\=\\-]+)(&|$)"), ShowInviteLink }, + { + qsl("^url:(.+)$"), + OpenExternalLink + }, }; return Result; } diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 4564c1a00..d2f234c69 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 = 3002005; -constexpr auto AppVersionStr = "3.2.5"; +constexpr auto AppVersion = 3003000; +constexpr auto AppVersionStr = "3.3"; constexpr auto AppBetaVersion = false; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/SourceFiles/countries/countries_instance.cpp b/Telegram/SourceFiles/countries/countries_instance.cpp index 8e6aaf785..88c42c213 100644 --- a/Telegram/SourceFiles/countries/countries_instance.cpp +++ b/Telegram/SourceFiles/countries/countries_instance.cpp @@ -470,4 +470,12 @@ QString ExtractPhoneCode(const QString &phone) { return Instance().format({ .phone = phone, .onlyCode = true }).code; } +QVector Groups(const QString &phone) { + return Instance().format({ + .phone = phone, + .onlyGroups = true, + .incomplete = true, + }).groups; +} + } // namespace Countries diff --git a/Telegram/SourceFiles/countries/countries_instance.h b/Telegram/SourceFiles/countries/countries_instance.h index 6052377b2..d8d7562e8 100644 --- a/Telegram/SourceFiles/countries/countries_instance.h +++ b/Telegram/SourceFiles/countries/countries_instance.h @@ -70,5 +70,6 @@ private: CountriesInstance &Instance(); [[nodiscard]] QString ExtractPhoneCode(const QString &phone); +[[nodiscard]] QVector Groups(const QString &phone); } // namespace Countries diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index f0d20639e..e6a8b4c9e 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -18,6 +18,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_histories.h" #include "data/data_group_call.h" #include "lang/lang_keys.h" +#include "main/main_session.h" +#include "main/session/send_as_peers.h" #include "base/unixtime.h" #include "history/history.h" #include "main/main_session.h" @@ -491,6 +493,10 @@ bool ChannelData::canWrite() const { && !amRestricted(Restriction::SendMessages))); } +bool ChannelData::allowsForwarding() const { + return !(flags() & Flag::NoForwards); +} + bool ChannelData::canViewMembers() const { return flags() & Flag::CanViewParticipants; } @@ -789,8 +795,8 @@ void ApplyMigration( void ApplyChannelUpdate( not_null channel, const MTPDupdateChatDefaultBannedRights &update) { - channel->setDefaultRestrictions(Data::ChatBannedRightsFlags( - update.vdefault_banned_rights())); + channel->setDefaultRestrictions(ChatRestrictionsInfo( + update.vdefault_banned_rights()).flags); } void ApplyChannelUpdate( @@ -932,81 +938,14 @@ void ApplyChannelUpdate( MTP_inputNotifyPeer(channel->input), update.vnotify_settings()); + if (const auto sendAs = update.vdefault_send_as()) { + session->sendAsPeers().setChosen(channel, peerFromMTP(*sendAs)); + } else { + session->sendAsPeers().setChosen(channel, PeerId()); + } + // For clearUpTill() call. channel->owner().sendHistoryChangeNotifications(); } -void ApplyMegagroupAdmins( - not_null channel, - const MTPDchannels_channelParticipants &data) { - Expects(channel->isMegagroup()); - - channel->owner().processUsers(data.vusers()); - - const auto &list = data.vparticipants().v; - const auto i = ranges::find( - list, - mtpc_channelParticipantCreator, - &MTPChannelParticipant::type); - if (i != list.end()) { - const auto &data = i->c_channelParticipantCreator(); - const auto userId = data.vuser_id().v; - channel->mgInfo->creator = channel->owner().userLoaded(userId); - channel->mgInfo->creatorRank = qs(data.vrank().value_or_empty()); - } else { - channel->mgInfo->creator = nullptr; - channel->mgInfo->creatorRank = QString(); - } - - auto adding = base::flat_map(); - auto admins = ranges::make_subrange( - list.begin(), list.end() - ) | ranges::views::transform([](const MTPChannelParticipant &p) { - const auto participantId = p.match([]( - const MTPDchannelParticipantBanned &data) { - return peerFromMTP(data.vpeer()); - }, [](const MTPDchannelParticipantLeft &data) { - return peerFromMTP(data.vpeer()); - }, [](const auto &data) { - return peerFromUser(data.vuser_id()); - }); - const auto rank = p.match([](const MTPDchannelParticipantAdmin &data) { - return qs(data.vrank().value_or_empty()); - }, [](const MTPDchannelParticipantCreator &data) { - return qs(data.vrank().value_or_empty()); - }, [](const auto &data) { - return QString(); - }); - return std::make_pair(participantId, rank); - }) | ranges::views::filter([](const auto &pair) { - return peerIsUser(pair.first); - }); - for (const auto &[participantId, rank] : admins) { - Assert(peerIsUser(participantId)); - adding.emplace(peerToUser(participantId), rank); - } - if (channel->mgInfo->creator) { - adding.emplace( - peerToUser(channel->mgInfo->creator->id), - channel->mgInfo->creatorRank); - } - auto removing = channel->mgInfo->admins; - if (removing.empty() && adding.empty()) { - // Add some admin-placeholder so we don't DDOS - // server with admins list requests. - LOG(("API Error: Got empty admins list from server.")); - adding.emplace(0, QString()); - } - - Data::ChannelAdminChanges changes(channel); - for (const auto &[addingId, rank] : adding) { - if (!removing.remove(addingId)) { - changes.add(addingId, rank); - } - } - for (const auto &[removingId, rank] : removing) { - changes.remove(removingId); - } -} - } // namespace Data diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index 5d7fec972..256020d70 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_peer.h" #include "data/data_pts_waiter.h" #include "data/data_location.h" +#include "data/data_chat_participant_status.h" struct ChannelLocation { QString address; @@ -50,6 +51,7 @@ enum class ChannelDataFlag { CanViewParticipants = (1 << 17), HasLink = (1 << 18), SlowmodeEnabled = (1 << 19), + NoForwards = (1 << 20), }; inline constexpr bool is_flag_type(ChannelDataFlag) { return true; }; using ChannelDataFlags = base::flags; @@ -294,6 +296,7 @@ public: // Like in ChatData. [[nodiscard]] bool canWrite() const; + [[nodiscard]] bool allowsForwarding() const; [[nodiscard]] bool canEditInformation() const; [[nodiscard]] bool canEditPermissions() const; [[nodiscard]] bool canEditUsername() const; @@ -479,8 +482,4 @@ void ApplyChannelUpdate( not_null channel, const MTPDchannelFull &update); -void ApplyMegagroupAdmins( - not_null channel, - const MTPDchannels_channelParticipants &data); - } // namespace Data diff --git a/Telegram/SourceFiles/data/data_chat.cpp b/Telegram/SourceFiles/data/data_chat.cpp index 217cac924..731b595bf 100644 --- a/Telegram/SourceFiles/data/data_chat.cpp +++ b/Telegram/SourceFiles/data/data_chat.cpp @@ -28,7 +28,7 @@ ChatData::ChatData(not_null owner, PeerId id) , inputChat(MTP_long(peerToChat(id).bare)) { _flags.changes( ) | rpl::start_with_next([=](const Flags::Change &change) { - if (change.diff & ChatDataFlag::CallNotEmpty) { + if (change.diff & Flag::CallNotEmpty) { if (const auto history = this->owner().historyLoaded(this)) { history->updateChatListEntry(); } @@ -47,7 +47,7 @@ void ChatData::setPhoto(const MTPChatPhoto &photo) { ChatAdminRightsInfo ChatData::defaultAdminRights(not_null user) { const auto isCreator = (creator == peerToUser(user->id)) || (user->isSelf() && amCreator()); - using Flag = AdminRight; + using Flag = ChatAdminRight; return ChatAdminRightsInfo(Flag::Other | Flag::ChangeInfo | Flag::DeleteMessages @@ -60,21 +60,25 @@ ChatAdminRightsInfo ChatData::defaultAdminRights(not_null user) { bool ChatData::canWrite() const { // Duplicated in Data::CanWriteValue(). - return amIn() && !amRestricted(Restriction::SendMessages); + return amIn() && !amRestricted(ChatRestriction::SendMessages); +} + +bool ChatData::allowsForwarding() const { + return !(flags() & Flag::NoForwards); } bool ChatData::canEditInformation() const { - return amIn() && !amRestricted(Restriction::ChangeInfo); + return amIn() && !amRestricted(ChatRestriction::ChangeInfo); } bool ChatData::canEditPermissions() const { return amIn() - && (amCreator() || (adminRights() & AdminRight::BanUsers)); + && (amCreator() || (adminRights() & ChatAdminRight::BanUsers)); } bool ChatData::canEditUsername() const { return amCreator() - && (flags() & ChatDataFlag::CanSetUsername); + && (flags() & Flag::CanSetUsername); } bool ChatData::canEditPreHistoryHidden() const { @@ -83,15 +87,15 @@ bool ChatData::canEditPreHistoryHidden() const { bool ChatData::canDeleteMessages() const { return amCreator() - || (adminRights() & AdminRight::DeleteMessages); + || (adminRights() & ChatAdminRight::DeleteMessages); } bool ChatData::canAddMembers() const { - return amIn() && !amRestricted(Restriction::InviteUsers); + return amIn() && !amRestricted(ChatRestriction::InviteUsers); } bool ChatData::canSendPolls() const { - return amIn() && !amRestricted(Restriction::SendPolls); + return amIn() && !amRestricted(ChatRestriction::SendPolls); } bool ChatData::canAddAdmins() const { @@ -100,11 +104,11 @@ bool ChatData::canAddAdmins() const { bool ChatData::canBanMembers() const { return amCreator() - || (adminRights() & AdminRight::BanUsers); + || (adminRights() & ChatAdminRight::BanUsers); } bool ChatData::anyoneCanAddMembers() const { - return !(defaultRestrictions() & Restriction::InviteUsers); + return !(defaultRestrictions() & ChatRestriction::InviteUsers); } void ChatData::setName(const QString &newName) { @@ -138,7 +142,7 @@ void ChatData::setInviteLink(const QString &newInviteLink) { bool ChatData::canHaveInviteLink() const { return amCreator() - || (adminRights() & AdminRight::InviteUsers); + || (adminRights() & ChatAdminRight::InviteUsers); } void ChatData::setAdminRights(ChatAdminRights rights) { @@ -222,7 +226,7 @@ void ChatData::setGroupCall( scheduleDate); owner().registerGroupCall(_call.get()); session().changes().peerUpdated(this, UpdateFlag::GroupCall); - addFlags(ChatDataFlag::CallActive); + addFlags(Flag::CallActive); }); } @@ -236,7 +240,7 @@ void ChatData::clearGroupCall() { _call = nullptr; } session().changes().peerUpdated(this, UpdateFlag::GroupCall); - removeFlags(ChatDataFlag::CallActive | ChatDataFlag::CallNotEmpty); + removeFlags(Flag::CallActive | Flag::CallNotEmpty); } void ChatData::setGroupCallDefaultJoinAs(PeerId peerId) { @@ -410,8 +414,8 @@ void ApplyChatUpdate( != ChatData::UpdateStatus::Good) { return; } - chat->setDefaultRestrictions(Data::ChatBannedRightsFlags( - update.vdefault_banned_rights())); + chat->setDefaultRestrictions(ChatRestrictionsInfo( + update.vdefault_banned_rights()).flags); } void ApplyChatUpdate(not_null chat, const MTPDchatFull &update) { diff --git a/Telegram/SourceFiles/data/data_chat.h b/Telegram/SourceFiles/data/data_chat.h index 97f022c82..69cd51627 100644 --- a/Telegram/SourceFiles/data/data_chat.h +++ b/Telegram/SourceFiles/data/data_chat.h @@ -8,6 +8,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "data/data_peer.h" +#include "data/data_chat_participant_status.h" + +enum class ChatAdminRight; enum class ChatDataFlag { Left = (1 << 0), @@ -18,6 +21,7 @@ enum class ChatDataFlag { CallActive = (1 << 5), CallNotEmpty = (1 << 6), CanSetUsername = (1 << 7), + NoForwards = (1 << 8), }; inline constexpr bool is_flag_type(ChatDataFlag) { return true; }; using ChatDataFlags = base::flags; @@ -27,13 +31,6 @@ public: using Flag = ChatDataFlag; using Flags = Data::Flags; - using AdminRight = ChatAdminRight; - using Restriction = ChatRestriction; - using AdminRights = ChatAdminRights; - using Restrictions = ChatRestrictions; - using AdminRightFlags = Data::Flags; - using RestrictionFlags = Data::Flags; - ChatData(not_null owner, PeerId id); void setName(const QString &newName); @@ -109,6 +106,7 @@ public: // Like in ChannelData. [[nodiscard]] bool canWrite() const; + [[nodiscard]] bool allowsForwarding() const; [[nodiscard]] bool canEditInformation() const; [[nodiscard]] bool canEditPermissions() const; [[nodiscard]] bool canEditUsername() const; @@ -195,8 +193,8 @@ private: Flags _flags; QString _inviteLink; - RestrictionFlags _defaultRestrictions; - AdminRightFlags _adminRights; + Data::Flags _defaultRestrictions; + Data::Flags _adminRights; int _version = 0; int _pendingRequestsCount = 0; std::vector _recentRequesters; diff --git a/Telegram/SourceFiles/data/data_chat_filters.cpp b/Telegram/SourceFiles/data/data_chat_filters.cpp index 4fcb01a7c..bbf517159 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.cpp +++ b/Telegram/SourceFiles/data/data_chat_filters.cpp @@ -417,7 +417,7 @@ void ChatFilters::load(bool force) { )).done([=](const MTPVector &result) { received(result.v); _loadRequestId = 0; - }).fail([=](const MTP::Error &error) { + }).fail([=] { _loadRequestId = 0; }).send(); } @@ -780,7 +780,7 @@ bool ChatFilters::loadNextExceptions(bool chatsListLoaded) { _exceptionsLoadRequestId = 0; _owner->session().data().histories().applyPeerDialogs(result); _owner->session().api().requestMoreDialogsIfNeeded(); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _exceptionsLoadRequestId = 0; _owner->session().api().requestMoreDialogsIfNeeded(); }).send(); @@ -819,7 +819,7 @@ void ChatFilters::requestSuggested() { }) | ranges::to_vector; _suggestedUpdated.fire({}); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _suggestedRequestId = 0; _suggestedLastReceived = crl::now() + kRefreshSuggestedTimeout / 2; diff --git a/Telegram/SourceFiles/data/data_chat_participant_status.cpp b/Telegram/SourceFiles/data/data_chat_participant_status.cpp new file mode 100644 index 000000000..554ed00cf --- /dev/null +++ b/Telegram/SourceFiles/data/data_chat_participant_status.cpp @@ -0,0 +1,64 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "data/data_chat_participant_status.h" + +namespace { + +[[nodiscard]] ChatAdminRights ChatAdminRightsFlags( + const MTPChatAdminRights &rights) { + return rights.match([](const MTPDchatAdminRights &data) { + return ChatAdminRights::from_raw(int32(data.vflags().v)); + }); +} + +[[nodiscard]] ChatRestrictions ChatBannedRightsFlags( + const MTPChatBannedRights &rights) { + return rights.match([](const MTPDchatBannedRights &data) { + return ChatRestrictions::from_raw(int32(data.vflags().v)); + }); +} + +[[nodiscard]] TimeId ChatBannedRightsUntilDate( + const MTPChatBannedRights &rights) { + return rights.match([](const MTPDchatBannedRights &data) { + return data.vuntil_date().v; + }); +} + +} // namespace + +ChatAdminRightsInfo::ChatAdminRightsInfo(const MTPChatAdminRights &rights) +: flags(ChatAdminRightsFlags(rights)) { +} + +ChatRestrictionsInfo::ChatRestrictionsInfo(const MTPChatBannedRights &rights) +: flags(ChatBannedRightsFlags(rights)) +, until(ChatBannedRightsUntilDate(rights)) { +} + +namespace Data { + +std::vector ListOfRestrictions() { + using Flag = ChatRestriction; + + return { + Flag::SendMessages, + Flag::SendMedia, + Flag::SendStickers, + Flag::SendGifs, + Flag::SendGames, + Flag::SendInline, + Flag::EmbedLinks, + Flag::SendPolls, + Flag::InviteUsers, + Flag::PinMessages, + Flag::ChangeInfo, + }; +} + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_chat_participant_status.h b/Telegram/SourceFiles/data/data_chat_participant_status.h new file mode 100644 index 000000000..7dff3e685 --- /dev/null +++ b/Telegram/SourceFiles/data/data_chat_participant_status.h @@ -0,0 +1,68 @@ +/* +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 + +enum class ChatAdminRight { + ChangeInfo = (1 << 0), + PostMessages = (1 << 1), + EditMessages = (1 << 2), + DeleteMessages = (1 << 3), + BanUsers = (1 << 4), + InviteUsers = (1 << 5), + PinMessages = (1 << 7), + AddAdmins = (1 << 9), + Anonymous = (1 << 10), + ManageCall = (1 << 11), + Other = (1 << 12), +}; +inline constexpr bool is_flag_type(ChatAdminRight) { return true; } +using ChatAdminRights = base::flags; + +enum class ChatRestriction { + ViewMessages = (1 << 0), + SendMessages = (1 << 1), + SendMedia = (1 << 2), + SendStickers = (1 << 3), + SendGifs = (1 << 4), + SendGames = (1 << 5), + SendInline = (1 << 6), + EmbedLinks = (1 << 7), + SendPolls = (1 << 8), + ChangeInfo = (1 << 10), + InviteUsers = (1 << 15), + PinMessages = (1 << 17), +}; +inline constexpr bool is_flag_type(ChatRestriction) { return true; } +using ChatRestrictions = base::flags; + +struct ChatAdminRightsInfo { + ChatAdminRightsInfo() = default; + explicit ChatAdminRightsInfo(ChatAdminRights flags) : flags(flags) { + } + explicit ChatAdminRightsInfo(const MTPChatAdminRights &rights); + + ChatAdminRights flags; +}; + +struct ChatRestrictionsInfo { + ChatRestrictionsInfo() = default; + ChatRestrictionsInfo(ChatRestrictions flags, TimeId until) + : flags(flags) + , until(until) { + } + explicit ChatRestrictionsInfo(const MTPChatBannedRights &rights); + + ChatRestrictions flags; + TimeId until = 0; +}; + +namespace Data { + +std::vector ListOfRestrictions(); + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_cloud_file.cpp b/Telegram/SourceFiles/data/data_cloud_file.cpp index aa5bfa6c3..f0801e13e 100644 --- a/Telegram/SourceFiles/data/data_cloud_file.cpp +++ b/Telegram/SourceFiles/data/data_cloud_file.cpp @@ -99,13 +99,19 @@ bool CloudImage::failed() const { return (_file.flags & CloudFile::Flag::Failed); } +bool CloudImage::loadedOnce() const { + return (_file.flags & CloudFile::Flag::Loaded); +} + void CloudImage::load(not_null session, FileOrigin origin) { const auto autoLoading = false; const auto finalCheck = [=] { if (const auto active = activeView()) { return !active->image(); + } else if (_file.flags & CloudFile::Flag::Loaded) { + return false; } - return true; + return !(_file.flags & CloudFile::Flag::Loaded); }; const auto done = [=](QImage result) { if (const auto active = activeView()) { @@ -247,6 +253,7 @@ void LoadCloudFile( if (!file.loader || file.loader->cancelled()) { file.flags |= CloudFile::Flag::Cancelled; } else { + file.flags |= CloudFile::Flag::Loaded; done(file); } // NB! file.loader may be in ~FileLoader() already. diff --git a/Telegram/SourceFiles/data/data_cloud_file.h b/Telegram/SourceFiles/data/data_cloud_file.h index a1ccc7afe..56a5e250b 100644 --- a/Telegram/SourceFiles/data/data_cloud_file.h +++ b/Telegram/SourceFiles/data/data_cloud_file.h @@ -31,6 +31,7 @@ struct CloudFile final { enum class Flag : uchar { Cancelled = 0x01, Failed = 0x02, + Loaded = 0x04, }; friend inline constexpr bool is_flag_type(Flag) { return true; }; @@ -73,6 +74,7 @@ public: [[nodiscard]] bool empty() const; [[nodiscard]] bool loading() const; [[nodiscard]] bool failed() const; + [[nodiscard]] bool loadedOnce() const; void load(not_null session, FileOrigin origin); [[nodiscard]] const ImageLocation &location() const; [[nodiscard]] int byteSize() const; diff --git a/Telegram/SourceFiles/data/data_cloud_themes.cpp b/Telegram/SourceFiles/data/data_cloud_themes.cpp index e1505c0e1..033ac8f0f 100644 --- a/Telegram/SourceFiles/data/data_cloud_themes.cpp +++ b/Telegram/SourceFiles/data/data_cloud_themes.cpp @@ -193,7 +193,7 @@ void CloudThemes::reloadCurrent() { MTP_long(fields.documentId) )).done([=](const MTPTheme &result) { applyUpdate(result); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _reloadCurrentTimer.callOnce(kReloadTimeout); }).send(); } @@ -346,7 +346,7 @@ void CloudThemes::refresh() { _updates.fire({}); }, [](const MTPDaccount_themesNotModified &) { }); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _refreshRequestId = 0; }).send(); } @@ -374,7 +374,7 @@ void CloudThemes::refreshChatThemes() { _chatThemesUpdates.fire({}); }, [](const MTPDaccount_themesNotModified &) { }); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _chatThemesRequestId = 0; }).send(); } diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index a30c1a18e..e555f96ff 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -1084,13 +1084,19 @@ bool DocumentData::isStickerSetInstalled() const { } } -Image *DocumentData::getReplyPreview(Data::FileOrigin origin) { +Image *DocumentData::getReplyPreview( + Data::FileOrigin origin, + not_null context) { if (!hasThumbnail()) { return nullptr; } else if (!_replyPreview) { _replyPreview = std::make_unique(this); } - return _replyPreview->image(origin); + return _replyPreview->image(origin, context); +} + +Image *DocumentData::getReplyPreview(not_null item) { + return getReplyPreview(item->fullId(), item->history()->peer); } bool DocumentData::replyPreviewLoaded() const { @@ -1158,12 +1164,14 @@ bool DocumentData::useStreamingLoader() const { || isVoiceMessage(); } -bool DocumentData::canBeStreamed() const { +bool DocumentData::canBeStreamed(HistoryItem *item) const { // Streaming couldn't be used with external player // Maybe someone brave will implement this once upon a time... return hasRemoteLocation() && supportsStreaming() - && (!cUseExternalVideoPlayer() || !isVideoFile()); + && (!isVideoFile() + || !cUseExternalVideoPlayer() + || (item && !item->allowsForward())); } void DocumentData::setInappPlaybackFailed() { diff --git a/Telegram/SourceFiles/data/data_document.h b/Telegram/SourceFiles/data/data_document.h index 49c35ecdf..5d99cb265 100644 --- a/Telegram/SourceFiles/data/data_document.h +++ b/Telegram/SourceFiles/data/data_document.h @@ -115,7 +115,8 @@ public: void setWaitingForAlbum(); [[nodiscard]] bool waitingForAlbum() const; - [[nodiscard]] const Core::FileLocation &location(bool check = false) const; + [[nodiscard]] const Core::FileLocation &location( + bool check = false) const; void setLocation(const Core::FileLocation &loc); bool saveFromData(); @@ -124,7 +125,10 @@ public: [[nodiscard]] bool saveToCache() const; - [[nodiscard]] Image *getReplyPreview(Data::FileOrigin origin); + [[nodiscard]] Image *getReplyPreview( + Data::FileOrigin origin, + not_null context); + [[nodiscard]] Image *getReplyPreview(not_null item); [[nodiscard]] bool replyPreviewLoaded() const; [[nodiscard]] StickerData *sticker() const; @@ -234,7 +238,7 @@ public: [[nodiscard]] Storage::Cache::Key cacheKey() const; [[nodiscard]] uint8 cacheTag() const; - [[nodiscard]] bool canBeStreamed() const; + [[nodiscard]] bool canBeStreamed(HistoryItem *item) const; [[nodiscard]] auto createStreamingLoader( Data::FileOrigin origin, bool forceRemoteLoader) const diff --git a/Telegram/SourceFiles/data/data_document_media.cpp b/Telegram/SourceFiles/data/data_document_media.cpp index c8548610c..113ad943f 100644 --- a/Telegram/SourceFiles/data/data_document_media.cpp +++ b/Telegram/SourceFiles/data/data_document_media.cpp @@ -349,10 +349,10 @@ float64 DocumentMedia::progress() const { : (loaded() ? 1. : 0.); } -bool DocumentMedia::canBePlayed() const { +bool DocumentMedia::canBePlayed(HistoryItem *item) const { return !_owner->inappPlaybackFailed() && _owner->useStreamingLoader() - && (loaded() || _owner->canBeStreamed()); + && (loaded() || _owner->canBeStreamed(item)); } bool DocumentMedia::thumbnailEnoughForSticker() const { diff --git a/Telegram/SourceFiles/data/data_document_media.h b/Telegram/SourceFiles/data/data_document_media.h index 9329023c9..5a2fea7c7 100644 --- a/Telegram/SourceFiles/data/data_document_media.h +++ b/Telegram/SourceFiles/data/data_document_media.h @@ -74,7 +74,7 @@ public: [[nodiscard]] QByteArray bytes() const; [[nodiscard]] bool loaded(bool check = false) const; [[nodiscard]] float64 progress() const; - [[nodiscard]] bool canBePlayed() const; + [[nodiscard]] bool canBePlayed(HistoryItem *item) const; void automaticLoad(Data::FileOrigin origin, const HistoryItem *item); diff --git a/Telegram/SourceFiles/data/data_document_resolver.cpp b/Telegram/SourceFiles/data/data_document_resolver.cpp index 40e9f725e..1a0a38d26 100644 --- a/Telegram/SourceFiles/data/data_document_resolver.cpp +++ b/Telegram/SourceFiles/data/data_document_resolver.cpp @@ -206,7 +206,7 @@ void ResolveDocument( Window::SessionController *controller, not_null document, HistoryItem *item) { - if (!document->date) { + if (document->isNull()) { return; } const auto msgId = item ? item->fullId() : FullMsgId(); @@ -252,7 +252,7 @@ void ResolveDocument( if (document->isTheme() && media->loaded(true)) { showDocument(); location.accessDisable(); - } else if (media->canBePlayed()) { + } else if (media->canBePlayed(item)) { if (document->isAudioFile() || document->isVoiceMessage() || document->isVideoMessage()) { diff --git a/Telegram/SourceFiles/data/data_file_click_handler.cpp b/Telegram/SourceFiles/data/data_file_click_handler.cpp index e4a9ad934..ae82e775f 100644 --- a/Telegram/SourceFiles/data/data_file_click_handler.cpp +++ b/Telegram/SourceFiles/data/data_file_click_handler.cpp @@ -63,7 +63,7 @@ void DocumentSaveClickHandler::Save( Data::FileOrigin origin, not_null data, Mode mode) { - if (!data->date) { + if (data->isNull()) { return; } @@ -107,7 +107,7 @@ DocumentCancelClickHandler::DocumentCancelClickHandler( void DocumentCancelClickHandler::onClickImpl() const { const auto data = document(); - if (!data->date) { + if (data->isNull()) { return; } else if (data->uploading() && _handler) { _handler(context()); @@ -119,7 +119,7 @@ void DocumentCancelClickHandler::onClickImpl() const { void DocumentOpenWithClickHandler::Open( Data::FileOrigin origin, not_null data) { - if (!data->date) { + if (data->isNull()) { return; } @@ -171,7 +171,7 @@ void PhotoOpenClickHandler::onClickImpl() const { void PhotoSaveClickHandler::onClickImpl() const { const auto data = photo(); - if (!data->date) { + if (data->isNull()) { return; } else { data->clearFailed(Data::PhotoSize::Large); @@ -189,7 +189,7 @@ PhotoCancelClickHandler::PhotoCancelClickHandler( void PhotoCancelClickHandler::onClickImpl() const { const auto data = photo(); - if (!data->date) { + if (data->isNull()) { return; } else if (data->uploading() && _handler) { _handler(context()); diff --git a/Telegram/SourceFiles/data/data_file_origin.cpp b/Telegram/SourceFiles/data/data_file_origin.cpp index 4eeca4535..f55cd470f 100644 --- a/Telegram/SourceFiles/data/data_file_origin.cpp +++ b/Telegram/SourceFiles/data/data_file_origin.cpp @@ -140,6 +140,7 @@ struct FileReferenceAccumulator { void push(const MTPmessages_StickerSet &data) { data.match([&](const MTPDmessages_stickerSet &data) { push(data.vdocuments()); + }, [](const MTPDmessages_stickerSetNotModified &data) { }); } void push(const MTPmessages_SavedGifs &data) { diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index f3ef08f80..3139e752d 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -135,7 +135,7 @@ void GroupCall::requestParticipants() { _participantsReloaded.fire({}); } }); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _participantsRequestId = 0; const auto reloaded = processSavedFullCall(); setServerParticipantsCount(_participants.size()); @@ -511,7 +511,7 @@ void GroupCall::reload() { } _reloadRequestId = 0; processFullCall(result); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _reloadRequestId = 0; }).send(); } @@ -898,7 +898,7 @@ void GroupCall::requestUnknownParticipants() { _participantsResolved.fire(&ssrcs); } requestUnknownParticipants(); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _unknownParticipantPeersRequestId = 0; for (const auto &[ssrc, when] : ssrcs) { _unknownSpokenSsrcs.remove(ssrc); diff --git a/Telegram/SourceFiles/data/data_group_call.h b/Telegram/SourceFiles/data/data_group_call.h index 9979d7b1d..b1394f7a0 100644 --- a/Telegram/SourceFiles/data/data_group_call.h +++ b/Telegram/SourceFiles/data/data_group_call.h @@ -32,14 +32,14 @@ struct GroupCallParticipant { uint64 raisedHandRating = 0; uint32 ssrc = 0; int volume = 0; - bool sounding : 1; - bool speaking : 1; - bool additionalSounding : 1; - bool additionalSpeaking : 1; - bool muted : 1; - bool mutedByMe : 1; - bool canSelfUnmute : 1; - bool onlyMinLoaded : 1; + bool sounding : 1 = false; + bool speaking : 1 = false; + bool additionalSounding : 1 = false; + bool additionalSpeaking : 1 = false; + bool muted : 1 = false; + bool mutedByMe : 1 = false; + bool canSelfUnmute : 1 = false; + bool onlyMinLoaded : 1 = false; bool videoJoined = false; bool applyVolumeFromMin = true; diff --git a/Telegram/SourceFiles/data/data_histories.cpp b/Telegram/SourceFiles/data/data_histories.cpp index 2af0634b7..b6c1f2a26 100644 --- a/Telegram/SourceFiles/data/data_histories.cpp +++ b/Telegram/SourceFiles/data/data_histories.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_chat.h" #include "data/data_folder.h" #include "data/data_scheduled_messages.h" +#include "base/unixtime.h" #include "main/main_session.h" #include "window/notifications_manager.h" #include "history/history.h" @@ -267,7 +268,7 @@ void Histories::requestDialogEntry(not_null folder) { )).done([=](const MTPmessages_PeerDialogs &result) { applyPeerDialogs(result); _dialogFolderRequests.remove(folder); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _dialogFolderRequests.remove(folder); }).send(); } @@ -347,7 +348,7 @@ void Histories::sendDialogRequests() { )).done([=](const MTPmessages_PeerDialogs &result) { applyPeerDialogs(result); finalize(); - }).fail([=](const MTP::Error &error) { + }).fail([=] { finalize(); }).send(); } @@ -427,7 +428,7 @@ void Histories::requestFakeChatListMessage( _fakeChatListRequests.erase(history); history->setFakeChatListMessageFrom(result); finish(); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _fakeChatListRequests.erase(history); history->setFakeChatListMessageFrom(MTP_messages_messages( MTP_vector(0), @@ -471,7 +472,7 @@ void Histories::requestGroupAround(not_null item) { history->channelId(), result); finish(); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _chatListGroupRequests.remove(history); finish(); }).send(); @@ -552,11 +553,7 @@ void Histories::sendReadRequest(not_null history, State &state) { return session().api().request(MTPchannels_ReadHistory( channel->inputChannel, MTP_int(tillId) - )).done([=](const MTPBool &result) { - finished(); - }).fail([=](const MTP::Error &error) { - finished(); - }).send(); + )).done(finished).fail(finished).send(); } else { return session().api().request(MTPmessages_ReadHistory( history->peer->input, @@ -564,7 +561,7 @@ void Histories::sendReadRequest(not_null history, State &state) { )).done([=](const MTPmessages_AffectedMessages &result) { session().api().applyAffectedMessages(history->peer, result); finished(); - }).fail([=](const MTP::Error &error) { + }).fail([=] { finished(); }).send(); } @@ -609,20 +606,17 @@ void Histories::deleteMessages( finish(); history->requestChatListMessage(); }; - const auto fail = [=](const MTP::Error &error) { - finish(); - }; if (const auto channel = history->peer->asChannel()) { return session().api().request(MTPchannels_DeleteMessages( channel->inputChannel, MTP_vector(ids) - )).done(done).fail(fail).send(); + )).done(done).fail(finish).send(); } else { using Flag = MTPmessages_DeleteMessages::Flag; return session().api().request(MTPmessages_DeleteMessages( MTP_flags(revoke ? Flag::f_revoke : Flag(0)), MTP_vector(ids) - )).done(done).fail(fail).send(); + )).done(done).fail(finish).send(); } }); } @@ -634,9 +628,6 @@ void Histories::deleteAllMessages( bool revoke) { sendRequest(history, RequestType::Delete, [=](Fn finish) { const auto peer = history->peer; - const auto fail = [=](const MTP::Error &error) { - finish(); - }; const auto chat = peer->asChat(); const auto channel = peer->asChannel(); if (!justClear && revoke && channel && channel->canDelete()) { @@ -653,15 +644,11 @@ void Histories::deleteAllMessages( return session().api().request(MTPchannels_DeleteHistory( channel->inputChannel, MTP_int(deleteTillId) - )).done([=](const MTPBool &result) { - finish(); - }).fail(fail).send(); + )).done(finish).fail(finish).send(); } else if (revoke && chat && chat->amCreator()) { return session().api().request(MTPmessages_DeleteChat( chat->inputChat - )).done([=](const MTPBool &result) { - finish(); - }).fail([=](const MTP::Error &error) { + )).done(finish).fail([=](const MTP::Error &error) { if (error.type() == "PEER_ID_INVALID") { // Try to join and delete, // while delete fails for non-joined. @@ -703,11 +690,60 @@ void Histories::deleteAllMessages( revoke); } finish(); - }).fail(fail).send(); + }).fail(finish).send(); } }); } +void Histories::deleteMessagesByDates( + not_null history, + QDate firstDayToDelete, + QDate lastDayToDelete, + bool revoke) { + const auto firstSecondToDelete = base::unixtime::serialize( + { firstDayToDelete, QTime(0, 0) } + ); + const auto lastSecondToDelete = base::unixtime::serialize( + { lastDayToDelete, QTime(23, 59, 59) } + ); + deleteMessagesByDates( + history, + firstSecondToDelete - 1, + lastSecondToDelete + 1, + revoke); +} + +void Histories::deleteMessagesByDates( + not_null history, + TimeId minDate, + TimeId maxDate, + bool revoke) { + sendRequest(history, RequestType::Delete, [=](Fn finish) { + const auto peer = history->peer; + using Flag = MTPmessages_DeleteHistory::Flag; + const auto flags = Flag::f_just_clear + | Flag::f_min_date + | Flag::f_max_date + | (revoke ? Flag::f_revoke : Flag(0)); + return session().api().request(MTPmessages_DeleteHistory( + MTP_flags(flags), + peer->input, + MTP_int(0), + MTP_int(minDate), + MTP_int(maxDate) + )).done([=](const MTPmessages_AffectedHistory &result) { + const auto offset = session().api().applyAffectedHistory( + peer, + result); + if (offset > 0) { + deleteMessagesByDates(history, minDate, maxDate, revoke); + } + finish(); + }).fail(finish).send(); + }); + history->destroyMessagesByDates(minDate, maxDate); +} + void Histories::deleteMessages(const MessageIdsList &ids, bool revoke) { auto remove = std::vector>(); remove.reserve(ids.size()); diff --git a/Telegram/SourceFiles/data/data_histories.h b/Telegram/SourceFiles/data/data_histories.h index 81e9fd1fa..6d36b2f48 100644 --- a/Telegram/SourceFiles/data/data_histories.h +++ b/Telegram/SourceFiles/data/data_histories.h @@ -71,6 +71,17 @@ public: bool justClear, bool revoke); + void deleteMessagesByDates( + not_null history, + QDate firstDayToDelete, + QDate lastDayToDelete, + bool revoke); + void deleteMessagesByDates( + not_null history, + TimeId minDate, + TimeId maxDate, + bool revoke); + void deleteMessages(const MessageIdsList &ids, bool revoke); int sendRequest( diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index f4c7ba3cb..ad43bb106 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -164,18 +164,8 @@ using ItemPreviewImage = HistoryView::ItemPreviewImage; } else if (const auto large = media->image(PhotoSize::Large)) { return { PreparePreviewImage(large, radius), readyCacheKey }; } - const auto allowedToDownload = [&] { - const auto photo = media->owner(); - if (media->loaded() || photo->cancelled()) { - return false; - } - return photo->hasExact(PhotoSize::Small) - || photo->hasExact(PhotoSize::Thumbnail) - || AutoDownload::Should( - photo->session().settings().autoDownload(), - item->history()->peer, - photo); - }(); + const auto allowedToDownload = media->autoLoadThumbnailAllowed( + item->history()->peer); const auto cacheKey = allowedToDownload ? 0 : readyCacheKey; if (allowedToDownload) { media->owner()->load(PhotoSize::Small, item->fullId()); @@ -537,7 +527,7 @@ bool MediaPhoto::hasReplyPreview() const { } Image *MediaPhoto::replyPreview() const { - return _photo->getReplyPreview(parent()->fullId()); + return _photo->getReplyPreview(parent()); } bool MediaPhoto::replyPreviewLoaded() const { @@ -742,7 +732,7 @@ bool MediaFile::hasReplyPreview() const { } Image *MediaFile::replyPreview() const { - return _document->getReplyPreview(parent()->fullId()); + return _document->getReplyPreview(parent()); } bool MediaFile::replyPreviewLoaded() const { @@ -1303,9 +1293,9 @@ bool MediaWebPage::hasReplyPreview() const { Image *MediaWebPage::replyPreview() const { if (const auto document = MediaWebPage::document()) { - return document->getReplyPreview(parent()->fullId()); + return document->getReplyPreview(parent()); } else if (const auto photo = MediaWebPage::photo()) { - return photo->getReplyPreview(parent()->fullId()); + return photo->getReplyPreview(parent()); } return nullptr; } @@ -1376,9 +1366,9 @@ bool MediaGame::hasReplyPreview() const { Image *MediaGame::replyPreview() const { if (const auto document = _game->document) { - return document->getReplyPreview(parent()->fullId()); + return document->getReplyPreview(parent()); } else if (const auto photo = _game->photo) { - return photo->getReplyPreview(parent()->fullId()); + return photo->getReplyPreview(parent()); } return nullptr; } @@ -1486,7 +1476,7 @@ bool MediaInvoice::hasReplyPreview() const { Image *MediaInvoice::replyPreview() const { if (const auto photo = _invoice.photo) { - return photo->getReplyPreview(parent()->fullId()); + return photo->getReplyPreview(parent()); } return nullptr; } @@ -1691,7 +1681,8 @@ ClickHandlerPtr MediaDice::MakeHandler( const ClickHandlerPtr &handler, Qt::MouseButton button) { if (button == Qt::LeftButton && !ShownToast.empty()) { - auto message = Api::MessageToSend(history); + auto message = Api::MessageToSend( + Api::SendAction(history)); message.action.clearDraft = false; message.textWithTags.text = emoji; diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 8c3da4676..4ede5f1e2 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/crc32hash.h" #include "lang/lang_keys.h" #include "apiwrap.h" +#include "api/api_chat_participants.h" #include "ui/boxes/confirm_box.h" #include "main/main_session.h" #include "main/main_session_settings.h" @@ -162,6 +163,7 @@ bool UpdateBotCommands( PeerClickHandler::PeerClickHandler(not_null peer) : _peer(peer) { + setProperty(kPeerLinkPeerIdProperty, peer->id.value); } void PeerClickHandler::onClick(ClickContext context) const { @@ -599,10 +601,9 @@ bool PeerData::canEditMessagesIndefinitely() const { } bool PeerData::canExportChatHistory() const { - if (isRepliesChat()) { + if (isRepliesChat() || !allowsForwarding()) { return false; - } - if (const auto channel = asChannel()) { + } else if (const auto channel = asChannel()) { if (!channel->amIn() && channel->invitePeekExpires()) { return false; } @@ -642,6 +643,9 @@ void PeerData::checkFolder(FolderId folderId) { void PeerData::setSettings(const MTPPeerSettings &data) { data.match([&](const MTPDpeerSettings &data) { + _requestChatTitle = data.vrequest_chat_title().value_or_empty(); + _requestChatDate = data.vrequest_chat_date().value_or_empty(); + using Flag = PeerSetting; setSettings((data.is_add_contact() ? Flag::AddContact : Flag()) | (data.is_autoarchived() ? Flag::AutoArchived : Flag()) @@ -652,7 +656,11 @@ void PeerData::setSettings(const MTPPeerSettings &data) { : Flag()) //| (data.is_report_geo() ? Flag::ReportGeo : Flag()) | (data.is_report_spam() ? Flag::ReportSpam : Flag()) - | (data.is_share_contact() ? Flag::ShareContact : Flag())); + | (data.is_share_contact() ? Flag::ShareContact : Flag()) + | (data.vrequest_chat_title() ? Flag::RequestChat : Flag()) + | (data.is_request_chat_broadcast() + ? Flag::RequestChatIsBroadcast + : Flag())); }); } @@ -718,7 +726,7 @@ void PeerData::updateFullForced() { session().api().requestFullPeer(this); if (const auto channel = asChannel()) { if (!channel->amCreator() && !channel->inviter) { - session().api().requestSelfParticipant(channel); + session().api().chatParticipants().requestSelf(channel); } } } @@ -868,6 +876,13 @@ QString PeerData::userName() const { return QString(); } +bool PeerData::isSelf() const { + if (const auto user = asUser()) { + return (user->flags() & UserDataFlag::Self); + } + return false; +} + bool PeerData::isVerified() const { if (const auto user = asUser()) { return user->isVerified(); @@ -929,6 +944,17 @@ bool PeerData::canWrite() const { return false; } +bool PeerData::allowsForwarding() const { + if (const auto user = asUser()) { + return true; + } else if (const auto channel = asChannel()) { + return channel->allowsForwarding(); + } else if (const auto chat = asChat()) { + return chat->allowsForwarding(); + } + return false; +} + Data::RestrictionCheckResult PeerData::amRestricted( ChatRestriction right) const { using Result = Data::RestrictionCheckResult; @@ -1132,24 +1158,6 @@ void PeerData::setMessagesTTL(TimeId period) { namespace Data { -std::vector ListOfRestrictions() { - using Flag = ChatRestriction; - - return { - Flag::SendMessages, - Flag::SendMedia, - Flag::SendStickers, - Flag::SendGifs, - Flag::SendGames, - Flag::SendInline, - Flag::EmbedLinks, - Flag::SendPolls, - Flag::InviteUsers, - Flag::PinMessages, - Flag::ChangeInfo, - }; -} - std::optional RestrictionError( not_null peer, ChatRestriction restriction) { @@ -1338,22 +1346,4 @@ std::optional ResolvePinnedCount( : std::nullopt; } -ChatAdminRights ChatAdminRightsFlags(const MTPChatAdminRights &rights) { - return rights.match([](const MTPDchatAdminRights &data) { - return ChatAdminRights::from_raw(int32(data.vflags().v)); - }); -} - -ChatRestrictions ChatBannedRightsFlags(const MTPChatBannedRights &rights) { - return rights.match([](const MTPDchatBannedRights &data) { - return ChatRestrictions::from_raw(int32(data.vflags().v)); - }); -} - -TimeId ChatBannedRightsUntilDate(const MTPChatBannedRights &rights) { - return rights.match([](const MTPDchatBannedRights &data) { - return data.vuntil_date().v; - }); -} - } // namespace Data diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index 256f45b6f..65e26af05 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -17,75 +17,7 @@ class UserData; class ChatData; class ChannelData; -enum class ChatAdminRight { - ChangeInfo = (1 << 0), - PostMessages = (1 << 1), - EditMessages = (1 << 2), - DeleteMessages = (1 << 3), - BanUsers = (1 << 4), - InviteUsers = (1 << 5), - PinMessages = (1 << 7), - AddAdmins = (1 << 9), - Anonymous = (1 << 10), - ManageCall = (1 << 11), - Other = (1 << 12), -}; -inline constexpr bool is_flag_type(ChatAdminRight) { return true; } -using ChatAdminRights = base::flags; - -enum class ChatRestriction { - ViewMessages = (1 << 0), - SendMessages = (1 << 1), - SendMedia = (1 << 2), - SendStickers = (1 << 3), - SendGifs = (1 << 4), - SendGames = (1 << 5), - SendInline = (1 << 6), - EmbedLinks = (1 << 7), - SendPolls = (1 << 8), - ChangeInfo = (1 << 10), - InviteUsers = (1 << 15), - PinMessages = (1 << 17), -}; -inline constexpr bool is_flag_type(ChatRestriction) { return true; } -using ChatRestrictions = base::flags; - -namespace Data { - -[[nodiscard]] ChatAdminRights ChatAdminRightsFlags( - const MTPChatAdminRights &rights); -[[nodiscard]] ChatRestrictions ChatBannedRightsFlags( - const MTPChatBannedRights &rights); -[[nodiscard]] TimeId ChatBannedRightsUntilDate( - const MTPChatBannedRights &rights); - -} // namespace Data - -struct ChatAdminRightsInfo { - ChatAdminRightsInfo() = default; - explicit ChatAdminRightsInfo(ChatAdminRights flags) : flags(flags) { - } - explicit ChatAdminRightsInfo(const MTPChatAdminRights &rights) - : flags(Data::ChatAdminRightsFlags(rights)) { - } - - ChatAdminRights flags; -}; - -struct ChatRestrictionsInfo { - ChatRestrictionsInfo() = default; - ChatRestrictionsInfo(ChatRestrictions flags, TimeId until) - : flags(flags) - , until(until) { - } - explicit ChatRestrictionsInfo(const MTPChatBannedRights &rights) - : flags(Data::ChatBannedRightsFlags(rights)) - , until(Data::ChatBannedRightsUntilDate(rights)) { - } - - ChatRestrictions flags; - TimeId until = 0; -}; +enum class ChatRestriction; struct BotCommand { QString command; @@ -199,7 +131,9 @@ enum class PeerSetting { ShareContact = (1 << 3), NeedContactsException = (1 << 4), AutoArchived = (1 << 5), - Unknown = (1 << 6), + RequestChat = (1 << 6), + RequestChatIsBroadcast = (1 << 7), + Unknown = (1 << 8), }; inline constexpr bool is_flag_type(PeerSetting) { return true; }; using PeerSettings = base::flags; @@ -230,9 +164,7 @@ public: [[nodiscard]] bool isChannel() const { return peerIsChannel(id); } - [[nodiscard]] bool isSelf() const { - return (input.type() == mtpc_inputPeerSelf); - } + [[nodiscard]] bool isSelf() const; [[nodiscard]] bool isVerified() const; [[nodiscard]] bool isScam() const; [[nodiscard]] bool isFake() const; @@ -274,6 +206,7 @@ public: } [[nodiscard]] bool canWrite() const; + [[nodiscard]] bool allowsForwarding() const; [[nodiscard]] Data::RestrictionCheckResult amRestricted( ChatRestriction right) const; [[nodiscard]] bool amAnonymous() const; @@ -426,7 +359,7 @@ public: // Returns true if about text was changed. bool setAbout(const QString &newAbout); - const QString &about() const { + [[nodiscard]] const QString &about() const { return _about; } @@ -435,16 +368,22 @@ public: void setSettings(PeerSettings which) { _settings.set(which); } - auto settings() const { + [[nodiscard]] auto settings() const { return (_settings.current() & PeerSetting::Unknown) ? std::nullopt : std::make_optional(_settings.current()); } - auto settingsValue() const { + [[nodiscard]] auto settingsValue() const { return (_settings.current() & PeerSetting::Unknown) ? _settings.changes() : (_settings.value() | rpl::type_erased()); } + [[nodiscard]] QString requestChatTitle() const { + return _requestChatTitle; + } + [[nodiscard]] TimeId requestChatDate() const { + return _requestChatDate; + } void setSettings(const MTPPeerSettings &data); @@ -533,6 +472,9 @@ private: BlockStatus _blockStatus = BlockStatus::Unknown; LoadedStatus _loadedStatus = LoadedStatus::Not; + QString _requestChatTitle; + TimeId _requestChatDate = 0; + QString _about; QString _themeEmoticon; @@ -540,8 +482,6 @@ private: namespace Data { -std::vector ListOfRestrictions(); - std::optional RestrictionError( not_null peer, ChatRestriction restriction); diff --git a/Telegram/SourceFiles/data/data_peer_values.cpp b/Telegram/SourceFiles/data/data_peer_values.cpp index 8526437b4..2671c756f 100644 --- a/Telegram/SourceFiles/data/data_peer_values.cpp +++ b/Telegram/SourceFiles/data/data_peer_values.cpp @@ -12,6 +12,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_user.h" +#include "data/data_changes.h" +#include "main/main_session.h" +#include "ui/image/image_prepare.h" #include "base/unixtime.h" namespace Data { @@ -448,4 +451,58 @@ bool ChannelHasActiveCall(not_null channel) { return (channel->flags() & ChannelDataFlag::CallNotEmpty); } +rpl::producer PeerUserpicImageValue( + not_null peer, + int size) { + return PeerUserpicImageValue(peer, size, + cUserpicCornersType() == 0 + ? ImageRoundRadius::None + : cUserpicCornersType() == 1 + ? ImageRoundRadius::Small + : cUserpicCornersType() == 2 + ? ImageRoundRadius::Large + : ImageRoundRadius::Ellipse); +} + +rpl::producer PeerUserpicImageValue( + not_null peer, + int size, + ImageRoundRadius radius) { + return [=](auto consumer) { + auto result = rpl::lifetime(); + struct State { + std::shared_ptr view; + rpl::lifetime waiting; + InMemoryKey key = {}; + bool empty = true; + Fn push; + }; + const auto state = result.make_state(); + state->push = [=] { + const auto key = peer->userpicUniqueKey(state->view); + const auto loading = state->view && !state->view->image(); + + if (loading && !state->waiting) { + peer->session().downloaderTaskFinished( + ) | rpl::start_with_next(state->push, state->waiting); + } else if (!loading && state->waiting) { + state->waiting.destroy(); + } + + if (!state->empty && (loading || key == state->key)) { + return; + } + state->key = key; + state->empty = false; + consumer.put_next( + peer->generateUserpicImage(state->view, size, radius)); + }; + peer->session().changes().peerFlagsValue( + peer, + PeerUpdate::Flag::Photo + ) | rpl::start_with_next(state->push, result); + return result; + }; +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_peer_values.h b/Telegram/SourceFiles/data/data_peer_values.h index b371d242c..31cbcb6b3 100644 --- a/Telegram/SourceFiles/data/data_peer_values.h +++ b/Telegram/SourceFiles/data/data_peer_values.h @@ -12,6 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include "data/data_peer.h" +enum class ImageRoundRadius; + namespace Data { template @@ -110,4 +112,12 @@ inline auto PeerFullFlagValue( [[nodiscard]] bool IsUserOnline(not_null user); [[nodiscard]] bool ChannelHasActiveCall(not_null channel); +[[nodiscard]] rpl::producer PeerUserpicImageValue( + not_null peer, + int size); +[[nodiscard]] rpl::producer PeerUserpicImageValue( + not_null peer, + int size, + ImageRoundRadius radius); + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_photo.cpp b/Telegram/SourceFiles/data/data_photo.cpp index 08676cad0..153456d52 100644 --- a/Telegram/SourceFiles/data/data_photo.cpp +++ b/Telegram/SourceFiles/data/data_photo.cpp @@ -13,6 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_photo_media.h" #include "ui/image/image.h" #include "main/main_session.h" +#include "history/history.h" +#include "history/history_item.h" #include "media/streaming/media_streaming_loader_local.h" #include "media/streaming/media_streaming_loader_mtproto.h" #include "mainwidget.h" @@ -206,11 +208,17 @@ bool PhotoData::uploading() const { return (uploadingData != nullptr); } -Image *PhotoData::getReplyPreview(Data::FileOrigin origin) { +Image *PhotoData::getReplyPreview( + Data::FileOrigin origin, + not_null context) { if (!_replyPreview) { _replyPreview = std::make_unique(this); } - return _replyPreview->image(origin); + return _replyPreview->image(origin, context); +} + +Image *PhotoData::getReplyPreview(not_null item) { + return getReplyPreview(item->fullId(), item->history()->peer); } bool PhotoData::replyPreviewLoaded() const { diff --git a/Telegram/SourceFiles/data/data_photo.h b/Telegram/SourceFiles/data/data_photo.h index 4922b28d3..4e6127965 100644 --- a/Telegram/SourceFiles/data/data_photo.h +++ b/Telegram/SourceFiles/data/data_photo.h @@ -64,7 +64,10 @@ public: void setWaitingForAlbum(); [[nodiscard]] bool waitingForAlbum() const; - [[nodiscard]] Image *getReplyPreview(Data::FileOrigin origin); + [[nodiscard]] Image *getReplyPreview( + Data::FileOrigin origin, + not_null context); + [[nodiscard]] Image *getReplyPreview(not_null item); [[nodiscard]] bool replyPreviewLoaded() const; void setRemoteLocation( diff --git a/Telegram/SourceFiles/data/data_photo_media.cpp b/Telegram/SourceFiles/data/data_photo_media.cpp index 19035b346..620d7429e 100644 --- a/Telegram/SourceFiles/data/data_photo_media.cpp +++ b/Telegram/SourceFiles/data/data_photo_media.cpp @@ -128,6 +128,18 @@ float64 PhotoMedia::progress() const { : (loaded() ? 1. : 0.); } +bool PhotoMedia::autoLoadThumbnailAllowed(not_null peer) const { + if (loaded() || _owner->cancelled()) { + return false; + } + return _owner->hasExact(PhotoSize::Small) + || _owner->hasExact(PhotoSize::Thumbnail) + || AutoDownload::Should( + _owner->session().settings().autoDownload(), + peer, + _owner); +} + void PhotoMedia::automaticLoad( Data::FileOrigin origin, const HistoryItem *item) { diff --git a/Telegram/SourceFiles/data/data_photo_media.h b/Telegram/SourceFiles/data/data_photo_media.h index 42f52b805..7a8b095d9 100644 --- a/Telegram/SourceFiles/data/data_photo_media.h +++ b/Telegram/SourceFiles/data/data_photo_media.h @@ -36,6 +36,8 @@ public: [[nodiscard]] bool loaded() const; [[nodiscard]] float64 progress() const; + [[nodiscard]] bool autoLoadThumbnailAllowed( + not_null peer) const; void automaticLoad(Data::FileOrigin origin, const HistoryItem *item); void collectLocalData(not_null local); diff --git a/Telegram/SourceFiles/data/data_replies_list.cpp b/Telegram/SourceFiles/data/data_replies_list.cpp index 080bbfa6d..6c41cdcc8 100644 --- a/Telegram/SourceFiles/data/data_replies_list.cpp +++ b/Telegram/SourceFiles/data/data_replies_list.cpp @@ -465,7 +465,7 @@ void RepliesList::loadAround(MsgId id) { _skippedBefore = 0; } } - }).fail([=](const MTP::Error &error) { + }).fail([=] { _beforeId = 0; _loadingAround = std::nullopt; finish(); @@ -513,7 +513,7 @@ void RepliesList::loadBefore() { _fullCount = _list.size(); } } - }).fail([=](const MTP::Error &error) { + }).fail([=] { _beforeId = 0; finish(); }).send(); @@ -557,7 +557,7 @@ void RepliesList::loadAfter() { _fullCount = _list.size(); } } - }).fail([=](const MTP::Error &error) { + }).fail([=] { _afterId = 0; finish(); }).send(); diff --git a/Telegram/SourceFiles/data/data_reply_preview.cpp b/Telegram/SourceFiles/data/data_reply_preview.cpp index 1067e2346..39da244df 100644 --- a/Telegram/SourceFiles/data/data_reply_preview.cpp +++ b/Telegram/SourceFiles/data/data_reply_preview.cpp @@ -55,7 +55,9 @@ void ReplyPreview::prepare(not_null image, Images::Options options) { _good = ((options & Images::Option::Blurred) == 0); } -Image *ReplyPreview::image(Data::FileOrigin origin) { +Image *ReplyPreview::image( + Data::FileOrigin origin, + not_null context) { if (_checked) { return _image.get(); } @@ -84,8 +86,14 @@ Image *ReplyPreview::image(Data::FileOrigin origin) { } else { Assert(_photo != nullptr); if (!_image || !_good) { + const auto createMedia = !_photoMedia; + const auto inlineThumbnailBytes = _photo->inlineThumbnailBytes(); if (!_photoMedia) { _photoMedia = _photo->createMediaView(); + } + const auto loadThumbnail = inlineThumbnailBytes.isEmpty() + || _photoMedia->autoLoadThumbnailAllowed(context); + if (loadThumbnail) { _photoMedia->wanted(PhotoSize::Small, origin); } if (const auto small = _photoMedia->image(PhotoSize::Small)) { diff --git a/Telegram/SourceFiles/data/data_reply_preview.h b/Telegram/SourceFiles/data/data_reply_preview.h index 01314e7ef..04abeb433 100644 --- a/Telegram/SourceFiles/data/data_reply_preview.h +++ b/Telegram/SourceFiles/data/data_reply_preview.h @@ -23,7 +23,9 @@ public: explicit ReplyPreview(not_null photo); ~ReplyPreview(); - [[nodiscard]] Image *image(Data::FileOrigin origin); + [[nodiscard]] Image *image( + Data::FileOrigin origin, + not_null context); [[nodiscard]] bool loaded() const; private: diff --git a/Telegram/SourceFiles/data/data_scheduled_messages.cpp b/Telegram/SourceFiles/data/data_scheduled_messages.cpp index 374b4cc30..292a332d6 100644 --- a/Telegram/SourceFiles/data/data_scheduled_messages.cpp +++ b/Telegram/SourceFiles/data/data_scheduled_messages.cpp @@ -195,7 +195,7 @@ void ScheduledMessages::sendNowSimpleMessage( MTP_message( MTP_flags(flags), update.vid(), - peerToMTP(_session->userPeerId()), + peerToMTP(local->from()->id), peerToMTP(history->peer->id), MTPMessageFwdHeader(), MTPlong(), // via_bot_id @@ -389,7 +389,7 @@ void ScheduledMessages::request(not_null history) { MTP_long(hash)) ).done([=](const MTPmessages_Messages &result) { parse(history, result); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _requests.remove(history); }).send(); } diff --git a/Telegram/SourceFiles/data/data_search_controller.cpp b/Telegram/SourceFiles/data/data_search_controller.cpp index 1bd42b218..76c2182f0 100644 --- a/Telegram/SourceFiles/data/data_search_controller.cpp +++ b/Telegram/SourceFiles/data/data_search_controller.cpp @@ -85,6 +85,10 @@ std::optional PrepareSearchRequest( }(); const auto hash = uint64(0); + const auto mtpOffsetId = int(std::clamp( + offsetId.bare, + int64(0), + int64(0x3FFFFFFF))); return MTPmessages_Search( MTP_flags(0), peer->input, @@ -94,7 +98,7 @@ std::optional PrepareSearchRequest( filter, MTP_int(0), // min_date MTP_int(0), // max_date - MTP_int(offsetId), + MTP_int(mtpOffsetId), MTP_int(addOffset), MTP_int(limit), MTP_int(maxId), @@ -391,7 +395,7 @@ void SearchController::requestMore( parsed.noSkipRange, parsed.fullCount); finish(); - }).fail([=](const MTP::Error &error) { + }).fail([=] { finish(); }).send(); }); diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 6c5872b5f..aec22ee83 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -641,7 +641,8 @@ not_null Session::processChat(const MTPChat &data) { | Flag::Deactivated | Flag::Forbidden | Flag::CallActive - | Flag::CallNotEmpty; + | Flag::CallNotEmpty + | Flag::NoForwards; const auto flagsSet = (data.is_left() ? Flag::Left : Flag()) | (data.is_kicked() ? Flag::Kicked : Flag()) | (data.is_creator() ? Flag::Creator : Flag()) @@ -651,7 +652,8 @@ not_null Session::processChat(const MTPChat &data) { || (chat->groupCall() && chat->groupCall()->fullCount() > 0)) ? Flag::CallNotEmpty - : Flag()); + : Flag()) + | (data.is_noforwards() ? Flag::NoForwards : Flag()); chat->setFlags((chat->flags() & ~flagsMask) | flagsSet); chat->count = data.vparticipants_count().v; @@ -739,10 +741,8 @@ not_null Session::processChat(const MTPChat &data) { | Flag::CallActive | Flag::CallNotEmpty | Flag::Forbidden - | (!minimal - ? Flag::Left - | Flag::Creator - : Flag()); + | (!minimal ? (Flag::Left | Flag::Creator) : Flag()) + | Flag::NoForwards; const auto flagsSet = (data.is_broadcast() ? Flag::Broadcast : Flag()) | (data.is_verified() ? Flag::Verified : Flag()) | (data.is_scam() ? Flag::Scam : Flag()) @@ -762,7 +762,8 @@ not_null Session::processChat(const MTPChat &data) { | (!minimal ? (data.is_left() ? Flag::Left : Flag()) | (data.is_creator() ? Flag::Creator : Flag()) - : Flag()); + : Flag()) + | (data.is_noforwards() ? Flag::NoForwards : Flag()); channel->setFlags((channel->flags() & ~flagsMask) | flagsSet); channel->setName( diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index f35c1dbcf..27bd7cb9f 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -782,10 +782,6 @@ private: PhotoData *photo, DocumentData *document); - void folderApplyFields( - not_null folder, - const MTPDfolder &data); - void setPinnedFromDialog(const Dialogs::Key &key, bool pinned); NotifySettings &defaultNotifySettings(not_null peer); diff --git a/Telegram/SourceFiles/data/data_shared_media.cpp b/Telegram/SourceFiles/data/data_shared_media.cpp index e6afc073f..6f584ecbb 100644 --- a/Telegram/SourceFiles/data/data_shared_media.cpp +++ b/Telegram/SourceFiles/data/data_shared_media.cpp @@ -74,7 +74,7 @@ bool IsItemGoodForType(const not_null item, Type type) { || ((videoType || photoVideoType) && videoDoc) || (fileType && (document->isTheme() || document->isImage() - || !document->canBeStreamed())); + || !document->canBeStreamed(item))); } } // namespace diff --git a/Telegram/SourceFiles/data/data_sponsored_messages.cpp b/Telegram/SourceFiles/data/data_sponsored_messages.cpp index 1dc5b49d1..155dc5f90 100644 --- a/Telegram/SourceFiles/data/data_sponsored_messages.cpp +++ b/Telegram/SourceFiles/data/data_sponsored_messages.cpp @@ -124,7 +124,7 @@ void SponsoredMessages::request(not_null history) { channel->inputChannel) ).done([=](const MTPmessages_sponsoredMessages &result) { parse(history, result); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _requests.remove(history); }).send(); } @@ -228,7 +228,7 @@ void SponsoredMessages::view(const FullMsgId &fullId) { auto &request = _viewRequests[randomId]; request.lastReceived = crl::now(); request.requestId = 0; - }).fail([=](const MTP::Error &error) { + }).fail([=] { _viewRequests.remove(randomId); }).send(); } diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h index d1bdb5b8e..1bb5f0db6 100644 --- a/Telegram/SourceFiles/data/data_types.h +++ b/Telegram/SourceFiles/data/data_types.h @@ -236,52 +236,53 @@ enum class MessageFlag : uint32 { MediaIsUnread = (1U << 13), MentionsMe = (1U << 14), IsOrWasScheduled = (1U << 15), + NoForwards = (1U << 16), // Needs to return back to inline mode. - HasSwitchInlineButton = (1U << 16), + HasSwitchInlineButton = (1U << 17), // For "shared links" indexing. - HasTextLinks = (1U << 17), + HasTextLinks = (1U << 18), // Group / channel create or migrate service message. - IsGroupEssential = (1U << 18), + IsGroupEssential = (1U << 19), // Edited media is generated on the client // and should not update media from server. - IsLocalUpdateMedia = (1U << 19), + IsLocalUpdateMedia = (1U << 20), // Sent from inline bot, need to re-set media when sent. - FromInlineBot = (1U << 20), + FromInlineBot = (1U << 21), // Generated on the client side and should be unread. - ClientSideUnread = (1U << 21), + ClientSideUnread = (1U << 22), // In a supergroup. - HasAdminBadge = (1U << 22), + HasAdminBadge = (1U << 23), // Outgoing message that is being sent. - BeingSent = (1U << 23), + BeingSent = (1U << 24), // Outgoing message and failed to be sent. - SendingFailed = (1U << 24), + SendingFailed = (1U << 25), // No media and only a several emoji text. - IsolatedEmoji = (1U << 25), + IsolatedEmoji = (1U << 26), // Message existing in the message history. - HistoryEntry = (1U << 26), + HistoryEntry = (1U << 27), // Local message, not existing on the server. - Local = (1U << 27), + Local = (1U << 28), // Fake message for some UI element. - FakeHistoryItem = (1U << 28), + FakeHistoryItem = (1U << 29), // Contact sign-up message, notification should be skipped for Silent. - IsContactSignUp = (1U << 29), + IsContactSignUp = (1U << 30), // In channels. - IsSponsored = (1U << 30), + IsSponsored = (1U << 31), }; inline constexpr bool is_flag_type(MessageFlag) { return true; } using MessageFlags = base::flags; diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp index 43aa88743..68b79056c 100644 --- a/Telegram/SourceFiles/data/data_user.cpp +++ b/Telegram/SourceFiles/data/data_user.cpp @@ -25,7 +25,8 @@ using UpdateFlag = Data::PeerUpdate::Flag; } // namespace UserData::UserData(not_null owner, PeerId id) -: PeerData(owner, id) { +: PeerData(owner, id) +, _flags((id == owner->session().userPeerId()) ? Flag::Self : Flag(0)) { } bool UserData::canShareThisContact() const { @@ -173,6 +174,19 @@ void UserData::setAccessHash(uint64 accessHash) { } } +void UserData::setFlags(UserDataFlags which) { + _flags.set((flags() & UserDataFlag::Self) + | (which & ~UserDataFlag::Self)); +} + +void UserData::addFlags(UserDataFlags which) { + _flags.add(which & ~UserDataFlag::Self); +} + +void UserData::removeFlags(UserDataFlags which) { + _flags.remove(which & ~UserDataFlag::Self); +} + void UserData::setCallsStatus(CallsStatus callsStatus) { if (callsStatus != _callsStatus) { _callsStatus = callsStatus; @@ -188,7 +202,6 @@ bool UserData::hasCalls() const { namespace Data { void ApplyUserUpdate(not_null user, const MTPDuserFull &update) { - user->owner().processUser(update.vuser()); if (const auto photo = update.vprofile_photo()) { user->owner().processPhoto(*photo); } diff --git a/Telegram/SourceFiles/data/data_user.h b/Telegram/SourceFiles/data/data_user.h index be0bbf746..b7e3c6bd9 100644 --- a/Telegram/SourceFiles/data/data_user.h +++ b/Telegram/SourceFiles/data/data_user.h @@ -37,6 +37,7 @@ enum class UserDataFlag { Support = (1 << 10), CanPinMessages = (1 << 11), DiscardMinPhoto = (1 << 12), + Self = (1 << 13), }; inline constexpr bool is_flag_type(UserDataFlag) { return true; }; using UserDataFlags = base::flags; @@ -68,21 +69,15 @@ public: } void setAccessHash(uint64 accessHash); - void setFlags(UserDataFlags which) { - _flags.set(which); - } - void addFlags(UserDataFlags which) { - _flags.add(which); - } - void removeFlags(UserDataFlags which) { - _flags.remove(which); - } auto flags() const { return _flags.current(); } auto flagsValue() const { return _flags.value(); } + void setFlags(UserDataFlags which); + void addFlags(UserDataFlags which); + void removeFlags(UserDataFlags which); [[nodiscard]] bool isVerified() const { return flags() & UserDataFlag::Verified; diff --git a/Telegram/SourceFiles/data/stickers/data_stickers.cpp b/Telegram/SourceFiles/data/stickers/data_stickers.cpp index 09293c429..6a8988855 100644 --- a/Telegram/SourceFiles/data/stickers/data_stickers.cpp +++ b/Telegram/SourceFiles/data/stickers/data_stickers.cpp @@ -518,26 +518,28 @@ void Stickers::requestSetToPushFaved(not_null document) { setIsFaved(document, std::move(list)); }; session().api().request(MTPmessages_GetStickerSet( - Data::InputStickerSet(document->sticker()->set) + Data::InputStickerSet(document->sticker()->set), + MTP_int(0) // hash )).done([=](const MTPmessages_StickerSet &result) { - Expects(result.type() == mtpc_messages_stickerSet); - - auto list = std::vector>(); - auto &d = result.c_messages_stickerSet(); - list.reserve(d.vpacks().v.size()); - for (const auto &mtpPack : d.vpacks().v) { - auto &pack = mtpPack.c_stickerPack(); - for (const auto &documentId : pack.vdocuments().v) { - if (documentId.v == document->id) { - if (const auto emoji = Ui::Emoji::Find(qs(mtpPack.c_stickerPack().vemoticon()))) { - list.emplace_back(emoji); + result.match([&](const MTPDmessages_stickerSet &data) { + auto list = std::vector>(); + list.reserve(data.vpacks().v.size()); + for (const auto &mtpPack : data.vpacks().v) { + auto &pack = mtpPack.c_stickerPack(); + for (const auto &documentId : pack.vdocuments().v) { + if (documentId.v == document->id) { + if (const auto emoji = Ui::Emoji::Find(qs(mtpPack.c_stickerPack().vemoticon()))) { + list.emplace_back(emoji); + } + break; } - break; } } - } - addAnyway(std::move(list)); - }).fail([=](const MTP::Error &error) { + addAnyway(std::move(list)); + }, [](const MTPDmessages_stickerSetNotModified &) { + LOG(("API Error: Unexpected messages.stickerSetNotModified.")); + }); + }).fail([=] { // Perhaps this is a deleted sticker pack. Add anyway. addAnyway({}); }).send(); @@ -1230,11 +1232,9 @@ StickersSet *Stickers::feedSet(const MTPDstickerSet &data) { return it->second.get(); } -StickersSet *Stickers::feedSetFull(const MTPmessages_StickerSet &data) { - Expects(data.type() == mtpc_messages_stickerSet); - Expects(data.c_messages_stickerSet().vset().type() == mtpc_stickerSet); +StickersSet *Stickers::feedSetFull(const MTPDmessages_stickerSet &d) { + Expects(d.vset().type() == mtpc_stickerSet); - const auto &d = data.c_messages_stickerSet(); const auto &s = d.vset().c_stickerSet(); auto &sets = setsRef(); @@ -1353,8 +1353,7 @@ StickersSet *Stickers::feedSetFull(const MTPmessages_StickerSet &data) { return set; } -void Stickers::newSetReceived(const MTPmessages_StickerSet &data) { - const auto &set = data.c_messages_stickerSet(); +void Stickers::newSetReceived(const MTPDmessages_stickerSet &set) { const auto &s = set.vset().c_stickerSet(); if (!s.vinstalled_date()) { LOG(("API Error: " @@ -1374,7 +1373,7 @@ void Stickers::newSetReceived(const MTPmessages_StickerSet &data) { order.insert(insertAtIndex, s.vid().v); } - feedSetFull(data); + feedSetFull(set); } QString Stickers::getSetTitle(const MTPDstickerSet &s) { diff --git a/Telegram/SourceFiles/data/stickers/data_stickers.h b/Telegram/SourceFiles/data/stickers/data_stickers.h index 3550e4a44..b381e0b49 100644 --- a/Telegram/SourceFiles/data/stickers/data_stickers.h +++ b/Telegram/SourceFiles/data/stickers/data_stickers.h @@ -198,8 +198,8 @@ public: not_null document); StickersSet *feedSet(const MTPDstickerSet &data); - StickersSet *feedSetFull(const MTPmessages_StickerSet &data); - void newSetReceived(const MTPmessages_StickerSet &data); + StickersSet *feedSetFull(const MTPDmessages_stickerSet &d); + void newSetReceived(const MTPDmessages_stickerSet &set); QString getSetTitle(const MTPDstickerSet &s); diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index f37ea8e3c..ce1fe06b5 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -101,8 +101,8 @@ dialogsMenuToggle: IconButton { width: 40px; height: 40px; - icon: icon {{ "dialogs_menu", dialogsMenuIconFg }}; - iconOver: icon {{ "dialogs_menu", dialogsMenuIconFgOver }}; + icon: icon {{ "dialogs/dialogs_menu", dialogsMenuIconFg }}; + iconOver: icon {{ "dialogs/dialogs_menu", dialogsMenuIconFgOver }}; iconPosition: point(-1px, -1px); rippleAreaPosition: point(0px, 0px); @@ -112,32 +112,32 @@ dialogsMenuToggle: IconButton { } } dialogsMenuToggleUnread: icon { - { "dialogs_menu_unread", dialogsMenuIconFg }, - { "dialogs_menu_unread_dot", dialogsUnreadBg }, + { "dialogs/dialogs_menu_unread", dialogsMenuIconFg }, + { "dialogs/dialogs_menu_unread_dot", dialogsUnreadBg }, }; dialogsMenuToggleUnreadMuted: icon { - { "dialogs_menu_unread", dialogsMenuIconFg }, - { "dialogs_menu_unread_dot", dialogsMenuIconFg }, + { "dialogs/dialogs_menu_unread", dialogsMenuIconFg }, + { "dialogs/dialogs_menu_unread_dot", dialogsMenuIconFg }, }; dialogsLock: IconButton(dialogsMenuToggle) { - icon: icon {{ "dialogs_lock", dialogsMenuIconFg }}; - iconOver: icon {{ "dialogs_lock", dialogsMenuIconFgOver }}; + icon: icon {{ "dialogs/dialogs_lock", dialogsMenuIconFg }}; + iconOver: icon {{ "dialogs/dialogs_lock", dialogsMenuIconFgOver }}; } -dialogsUnlockIcon: icon {{ "dialogs_unlock", dialogsMenuIconFg }}; -dialogsUnlockIconOver: icon {{ "dialogs_unlock", dialogsMenuIconFgOver }}; +dialogsUnlockIcon: icon {{ "dialogs/dialogs_unlock", dialogsMenuIconFg }}; +dialogsUnlockIconOver: icon {{ "dialogs/dialogs_unlock", dialogsMenuIconFgOver }}; dialogsCalendar: IconButton { width: 29px; height: 32px; - icon: icon {{ "dialogs_calendar", dialogsMenuIconFg }}; - iconOver: icon {{ "dialogs_calendar", dialogsMenuIconFgOver }}; + icon: icon {{ "dialogs/dialogs_calendar", dialogsMenuIconFg }}; + iconOver: icon {{ "dialogs/dialogs_calendar", dialogsMenuIconFgOver }}; iconPosition: point(0px, 5px); } dialogsSearchFrom: IconButton(dialogsCalendar) { width: 26px; - icon: icon {{ "dialogs_search_from", dialogsMenuIconFg }}; - iconOver: icon {{ "dialogs_search_from", dialogsMenuIconFgOver }}; + icon: icon {{ "dialogs/dialogs_search_from", dialogsMenuIconFg }}; + iconOver: icon {{ "dialogs/dialogs_search_from", dialogsMenuIconFgOver }}; } dialogsSearchForNarrowFilters: IconButton(dialogsMenuToggle) { icon: icon {{ "top_bar_search", menuIconFg }}; @@ -154,8 +154,8 @@ dialogsFilter: FlatInput(defaultFlatInput) { textMrg: margins(12px, 3px, 30px, 3px); } dialogsCancelSearchInPeer: IconButton(dialogsMenuToggle) { - icon: icon {{ "dialogs_cancel_search", dialogsMenuIconFg }}; - iconOver: icon {{ "dialogs_cancel_search", dialogsMenuIconFgOver }}; + icon: icon {{ "dialogs/dialogs_cancel_search", dialogsMenuIconFg }}; + iconOver: icon {{ "dialogs/dialogs_cancel_search", dialogsMenuIconFgOver }}; iconPosition: point(11px, 11px); rippleAreaPosition: point(3px, 3px); rippleAreaSize: 34px; @@ -180,52 +180,52 @@ dialogsCancelSearch: CrossButton { } dialogsChatTypeSkip: 22px; -dialogsChatIcon: icon {{ "dialogs_chat", dialogsChatIconFg, point(1px, 4px) }}; -dialogsChatIconOver: icon {{ "dialogs_chat", dialogsChatIconFgOver, point(1px, 4px) }}; -dialogsChatIconActive: icon {{ "dialogs_chat", dialogsChatIconFgActive, point(1px, 4px) }}; -dialogsChannelIcon: icon {{ "dialogs_channel", dialogsChatIconFg, point(3px, 4px) }}; -dialogsChannelIconOver: icon {{ "dialogs_channel", dialogsChatIconFgOver, point(3px, 4px) }}; -dialogsChannelIconActive: icon {{ "dialogs_channel", dialogsChatIconFgActive, point(3px, 4px) }}; -dialogsBotIcon: icon {{ "dialogs_bot", dialogsChatIconFg, point(1px, 3px) }}; -dialogsBotIconOver: icon {{ "dialogs_bot", dialogsChatIconFgOver, point(1px, 3px) }}; -dialogsBotIconActive: icon {{ "dialogs_bot", dialogsChatIconFgActive, point(1px, 3px) }}; -dialogsDeletedIcon: icon {{ "dialogs_deleted", dialogsChatIconFg, point(1px, 4px) }}; -dialogsDeletedIconOver: icon {{ "dialogs_deleted", dialogsChatIconFgOver, point(1px, 4px) }}; -dialogsDeletedIconActive: icon {{ "dialogs_deleted", dialogsChatIconFgActive, point(1px, 4px) }}; +dialogsChatIcon: icon {{ "dialogs/dialogs_chat", dialogsChatIconFg, point(1px, 4px) }}; +dialogsChatIconOver: icon {{ "dialogs/dialogs_chat", dialogsChatIconFgOver, point(1px, 4px) }}; +dialogsChatIconActive: icon {{ "dialogs/dialogs_chat", dialogsChatIconFgActive, point(1px, 4px) }}; +dialogsChannelIcon: icon {{ "dialogs/dialogs_channel", dialogsChatIconFg, point(3px, 4px) }}; +dialogsChannelIconOver: icon {{ "dialogs/dialogs_channel", dialogsChatIconFgOver, point(3px, 4px) }}; +dialogsChannelIconActive: icon {{ "dialogs/dialogs_channel", dialogsChatIconFgActive, point(3px, 4px) }}; +dialogsBotIcon: icon {{ "dialogs/dialogs_bot", dialogsChatIconFg, point(1px, 3px) }}; +dialogsBotIconOver: icon {{ "dialogs/dialogs_bot", dialogsChatIconFgOver, point(1px, 3px) }}; +dialogsBotIconActive: icon {{ "dialogs/dialogs_bot", dialogsChatIconFgActive, point(1px, 3px) }}; +dialogsDeletedIcon: icon {{ "dialogs/dialogs_deleted", dialogsChatIconFg, point(1px, 4px) }}; +dialogsDeletedIconOver: icon {{ "dialogs/dialogs_deleted", dialogsChatIconFgOver, point(1px, 4px) }}; +dialogsDeletedIconActive: icon {{ "dialogs/dialogs_deleted", dialogsChatIconFgActive, point(1px, 4px) }}; dialogsArchiveUserpic: icon {{ "archive_userpic", historyPeerUserpicFg }}; dialogsRepliesUserpic: icon {{ "replies_userpic", historyPeerUserpicFg }}; dialogsSendStateSkip: 20px; -dialogsSendingIcon: icon {{ "dialogs_sending", dialogsSendingIconFg, point(8px, 4px) }}; -dialogsSendingIconOver: icon {{ "dialogs_sending", dialogsSendingIconFgOver, point(8px, 4px) }}; -dialogsSendingIconActive: icon {{ "dialogs_sending", dialogsSendingIconFgActive, point(8px, 4px) }}; -dialogsSentIcon: icon {{ "dialogs_sent", dialogsSentIconFg, point(10px, 4px) }}; -dialogsSentIconOver: icon {{ "dialogs_sent", dialogsSentIconFgOver, point(10px, 4px) }}; -dialogsSentIconActive: icon {{ "dialogs_sent", dialogsSentIconFgActive, point(10px, 4px) }}; -dialogsReceivedIcon: icon {{ "dialogs_received", dialogsSentIconFg, point(5px, 4px) }}; -dialogsReceivedIconOver: icon {{ "dialogs_received", dialogsSentIconFgOver, point(5px, 4px) }}; -dialogsReceivedIconActive: icon {{ "dialogs_received", dialogsSentIconFgActive, point(5px, 4px) }}; -dialogsPinnedIcon: icon {{ "dialogs_pinned", dialogsUnreadBgMuted }}; -dialogsPinnedIconOver: icon {{ "dialogs_pinned", dialogsUnreadBgMutedOver }}; -dialogsPinnedIconActive: icon {{ "dialogs_pinned", dialogsUnreadBgMutedActive }}; +dialogsSendingIcon: icon {{ "dialogs/dialogs_sending", dialogsSendingIconFg, point(8px, 4px) }}; +dialogsSendingIconOver: icon {{ "dialogs/dialogs_sending", dialogsSendingIconFgOver, point(8px, 4px) }}; +dialogsSendingIconActive: icon {{ "dialogs/dialogs_sending", dialogsSendingIconFgActive, point(8px, 4px) }}; +dialogsSentIcon: icon {{ "dialogs/dialogs_sent", dialogsSentIconFg, point(10px, 4px) }}; +dialogsSentIconOver: icon {{ "dialogs/dialogs_sent", dialogsSentIconFgOver, point(10px, 4px) }}; +dialogsSentIconActive: icon {{ "dialogs/dialogs_sent", dialogsSentIconFgActive, point(10px, 4px) }}; +dialogsReceivedIcon: icon {{ "dialogs/dialogs_received", dialogsSentIconFg, point(5px, 4px) }}; +dialogsReceivedIconOver: icon {{ "dialogs/dialogs_received", dialogsSentIconFgOver, point(5px, 4px) }}; +dialogsReceivedIconActive: icon {{ "dialogs/dialogs_received", dialogsSentIconFgActive, point(5px, 4px) }}; +dialogsPinnedIcon: icon {{ "dialogs/dialogs_pinned", dialogsUnreadBgMuted }}; +dialogsPinnedIconOver: icon {{ "dialogs/dialogs_pinned", dialogsUnreadBgMutedOver }}; +dialogsPinnedIconActive: icon {{ "dialogs/dialogs_pinned", dialogsUnreadBgMutedActive }}; dialogsVerifiedIcon: icon { - { "dialogs_verified_star", dialogsVerifiedIconBg, point(4px, 2px) }, - { "dialogs_verified_check", dialogsVerifiedIconFg, point(7px, 7px) }, + { "dialogs/dialogs_verified_star", dialogsVerifiedIconBg }, + { "dialogs/dialogs_verified_check", dialogsVerifiedIconFg }, }; dialogsVerifiedIconOver: icon { - { "dialogs_verified_star", dialogsVerifiedIconBgOver, point(4px, 2px) }, - { "dialogs_verified_check", dialogsVerifiedIconFgOver, point(7px, 7px) }, + { "dialogs/dialogs_verified_star", dialogsVerifiedIconBgOver }, + { "dialogs/dialogs_verified_check", dialogsVerifiedIconFgOver }, }; dialogsVerifiedIconActive: icon { - { "dialogs_verified_star", dialogsVerifiedIconBgActive, point(4px, 2px) }, - { "dialogs_verified_check", dialogsVerifiedIconFgActive, point(7px, 7px) }, + { "dialogs/dialogs_verified_star", dialogsVerifiedIconBgActive }, + { "dialogs/dialogs_verified_check", dialogsVerifiedIconFgActive }, }; -historySendingIcon: icon {{ "dialogs_sending", historySendingOutIconFg, point(5px, 5px) }}; -historySendingInvertedIcon: icon {{ "dialogs_sending", historySendingInvertedIconFg, point(5px, 5px) }}; -historyViewsSendingIcon: icon {{ "dialogs_sending", historySendingInIconFg, point(3px, 0px) }}; -historyViewsSendingInvertedIcon: icon {{ "dialogs_sending", historySendingInvertedIconFg, point(3px, 0px) }}; +historySendingIcon: icon {{ "dialogs/dialogs_sending", historySendingOutIconFg, point(5px, 5px) }}; +historySendingInvertedIcon: icon {{ "dialogs/dialogs_sending", historySendingInvertedIconFg, point(5px, 5px) }}; +historyViewsSendingIcon: icon {{ "dialogs/dialogs_sending", historySendingInIconFg, point(3px, 0px) }}; +historyViewsSendingInvertedIcon: icon {{ "dialogs/dialogs_sending", historySendingInvertedIconFg, point(3px, 0px) }}; dialogsUpdateButton: FlatButton { color: activeButtonFg; diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp index 1cf2615f3..a9a707f58 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp @@ -63,8 +63,8 @@ Data::Folder *Entry::asFolder() { return _isFolder ? static_cast(this) : nullptr; } -void Entry::pinnedIndexChanged(int was, int now) { - if (session().supportMode()) { +void Entry::pinnedIndexChanged(FilterId filterId, int was, int now) { + if (!filterId && session().supportMode()) { // Force reorder in support mode. _sortKeyInChatList = 0; } @@ -83,15 +83,12 @@ void Entry::cachePinnedIndex(FilterId filterId, int index) { } if (!index) { _pinnedIndex.erase(i); - pinnedIndexChanged(was, index); + } else if (!was) { + _pinnedIndex.emplace(filterId, index); } else { - if (!was) { - _pinnedIndex.emplace(filterId, index); - } else { - i->second = index; - } - pinnedIndexChanged(was, index); + i->second = index; } + pinnedIndexChanged(filterId, was, index); } void Entry::cacheTopPromoted(bool promoted) { diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.h b/Telegram/SourceFiles/dialogs/dialogs_entry.h index bb4bba3db..bff8ad2a5 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_entry.h +++ b/Telegram/SourceFiles/dialogs/dialogs_entry.h @@ -205,7 +205,7 @@ protected: private: virtual void changedChatListPinHook(); - void pinnedIndexChanged(int was, int now); + void pinnedIndexChanged(FilterId filterId, int was, int now); [[nodiscard]] uint64 computeSortPosition(FilterId filterId) const; void setChatListExistence(bool exists); diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 36e1fc790..93e55f812 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -42,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "main/main_session_settings.h" #include "window/notifications_manager.h" +#include "window/window_controller.h" #include "window/window_session_controller.h" #include "window/window_peer_menu.h" #include "ui/widgets/multi_select.h" @@ -3042,7 +3043,9 @@ void InnerWidget::updateRowCornerStatusShown( void InnerWidget::setupShortcuts() { Shortcuts::Requests( ) | rpl::filter([=] { - return isActiveWindow() && !Ui::isLayerShown(); + return isActiveWindow() + && !Ui::isLayerShown() + && !_controller->window().locked(); }) | rpl::start_with_next([=](not_null request) { using Command = Shortcuts::Command; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 793795cb6..85eef3638 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -286,7 +286,7 @@ Widget::Widget( }, lifetime()); _cancelSearch->setClickedCallback([this] { onCancelSearch(); }); - _jumpToDate->entity()->setClickedCallback([this] { showJumpToDate(); }); + _jumpToDate->entity()->setClickedCallback([this] { showCalendar(); }); _chooseFromUser->entity()->setClickedCallback([this] { showSearchFrom(); }); rpl::single( rpl::empty_value() @@ -1463,9 +1463,9 @@ void Widget::clearSearchCache() { cancelSearchRequest(); } -void Widget::showJumpToDate() { +void Widget::showCalendar() { if (_searchInChat) { - controller()->showJumpToDate(_searchInChat, QDate()); + controller()->showCalendar(_searchInChat, QDate()); } } diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index 678a0d893..f6604083b 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -150,7 +150,7 @@ private: void setupMainMenuToggle(); bool searchForPeersRequired(const QString &query) const; void setSearchInChat(Key chat, PeerData *from = nullptr); - void showJumpToDate(); + void showCalendar(); void showSearchFrom(); void showMainMenu(); void clearSearchCache(); diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index 3410d7403..2048feb41 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -1283,15 +1283,14 @@ std::map ParseMessagesList( return result; } -PersonalInfo ParsePersonalInfo(const MTPUserFull &data) { - Expects(data.type() == mtpc_userFull); - - const auto &fields = data.c_userFull(); +PersonalInfo ParsePersonalInfo(const MTPDusers_userFull &data) { auto result = PersonalInfo(); - result.user = ParseUser(fields.vuser()); - if (const auto about = fields.vabout()) { - result.bio = ParseString(*about); - } + result.user = ParseUser(data.vusers().v[0]); + data.vfull_user().match([&](const MTPDuserFull &data) { + if (const auto about = data.vabout()) { + result.bio = ParseString(*about); + } + }); return result; } diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h index 677bfb3a8..495446814 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -255,7 +255,7 @@ struct PersonalInfo { Utf8String bio; }; -PersonalInfo ParsePersonalInfo(const MTPUserFull &data); +PersonalInfo ParsePersonalInfo(const MTPDusers_userFull &data); struct TopPeer { Peer peer; diff --git a/Telegram/SourceFiles/export/export_api_wrap.cpp b/Telegram/SourceFiles/export/export_api_wrap.cpp index bd62779cc..464f7ff90 100644 --- a/Telegram/SourceFiles/export/export_api_wrap.cpp +++ b/Telegram/SourceFiles/export/export_api_wrap.cpp @@ -674,15 +674,14 @@ void ApiWrap::startMainSession(FnMut done) { void ApiWrap::requestPersonalInfo(FnMut done) { mainRequest(MTPusers_GetFullUser( _user - )).done([=, done = std::move(done)](const MTPUserFull &result) mutable { - Expects(result.type() == mtpc_userFull); - - const auto &full = result.c_userFull(); - if (full.vuser().type() == mtpc_user) { - done(Data::ParsePersonalInfo(result)); - } else { - error("Bad user type."); - } + )).done([=, done = std::move(done)](const MTPusers_UserFull &result) mutable { + result.match([&](const MTPDusers_userFull &data) { + if (!data.vusers().v.empty()) { + done(Data::ParsePersonalInfo(data)); + } else { + error("Bad user type."); + } + }); }).send(); } diff --git a/Telegram/SourceFiles/export/view/export.style b/Telegram/SourceFiles/export/view/export.style index 4f74e8b54..5091de896 100644 --- a/Telegram/SourceFiles/export/view/export.style +++ b/Telegram/SourceFiles/export/view/export.style @@ -97,8 +97,8 @@ exportTopBarLabel: FlatLabel(defaultFlatLabel) { } exportCalendarSizes: CalendarSizes(defaultCalendarSizes) { width: 320px; - daysHeight: 32px; + daysHeight: 40px; cellSize: size(42px, 38px); cellInner: 32px; - padding: margins(14px, 15px, 14px, 10px); + padding: margins(14px, 0px, 14px, 0px); } diff --git a/Telegram/SourceFiles/export/view/export_view_settings.cpp b/Telegram/SourceFiles/export/view/export_view_settings.cpp index bf8ecbef3..9745e386f 100644 --- a/Telegram/SourceFiles/export/view/export_view_settings.cpp +++ b/Telegram/SourceFiles/export/view/export_view_settings.cpp @@ -464,12 +464,6 @@ void SettingsWidget::editDateLimit( const auto month = highlighted; const auto shared = std::make_shared>(); const auto finalize = [=](not_null box) { - box->setMaxDate(max - ? base::unixtime::parse(max).date() - : QDate::currentDate()); - box->setMinDate(min - ? base::unixtime::parse(min).date() - : QDate(2013, 8, 1)); // Telegram was launched in August 2013 :) box->addLeftButton(std::move(resetLabel), crl::guard(this, [=] { done(0); if (const auto weak = shared->data()) { @@ -483,12 +477,19 @@ void SettingsWidget::editDateLimit( weak->closeBox(); } }); - auto box = Box( - month, - highlighted, - callback, - finalize, - st::exportCalendarSizes); + auto box = Box(Ui::CalendarBoxArgs{ + .month = month, + .highlighted = highlighted, + .callback = callback, + .finalize = finalize, + .st = st::exportCalendarSizes, + .minDate = (min + ? base::unixtime::parse(min).date() + : QDate(2013, 8, 1)), // Telegram was launched in August 2013 :) + .maxDate = (max + ? base::unixtime::parse(max).date() + : QDate::currentDate()), + }); *shared = Ui::MakeWeak(box.data()); _showBoxCallback(std::move(box)); } diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index ff0a08659..6cd9e0bb1 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -211,6 +211,17 @@ void activateBotCommand( case ButtonType::Auth: UrlAuthBox::Activate(msg, row, column); break; + + case ButtonType::UserProfile: { + const auto session = &msg->history()->session(); + const auto userId = UserId(button->data.toULongLong()); + if (const auto user = session->data().userLoaded(userId)) { + const auto &windows = session->windows(); + if (!windows.empty()) { + windows.front()->showPeerInfo(user); + } + } + } break; } } diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_filter.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_filter.cpp index 69c81cd14..6030ca61a 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_filter.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_filter.cpp @@ -183,7 +183,7 @@ private: QPointer _allFlags; base::flat_map< - MTPDchannelAdminLogEventsFilter::Flags, + FilterValue::Flags, QPointer> _filterFlags; QPointer _allUsers; @@ -238,8 +238,8 @@ void FilterBox::Inner::createAllActionsCheckbox(const FilterValue &filter) { } void FilterBox::Inner::createActionsCheckboxes(const FilterValue &filter) { - using Flag = MTPDchannelAdminLogEventsFilter::Flag; - using Flags = MTPDchannelAdminLogEventsFilter::Flags; + using Flag = FilterValue::Flag; + using Flags = FilterValue::Flags; auto addFlag = [this, &filter](Flags flag, QString &&text) { auto checked = (filter.flags == 0) || (filter.flags & flag); auto checkbox = addRow(object_ptr(this, std::move(text), checked, st::defaultBoxCheckbox), st::adminLogFilterLittleSkip); @@ -264,21 +264,40 @@ void FilterBox::Inner::createActionsCheckboxes(const FilterValue &filter) { }; auto isGroup = _channel->isMegagroup(); if (isGroup) { - addFlag(Flag::f_ban | Flag::f_unban | Flag::f_kick | Flag::f_unkick, tr::lng_admin_log_filter_restrictions(tr::now)); + addFlag( + Flag::Ban + | Flag::Unban + | Flag::Kick + | Flag::Unkick, + tr::lng_admin_log_filter_restrictions(tr::now)); } - addFlag(Flag::f_promote | Flag::f_demote, tr::lng_admin_log_filter_admins_new(tr::now)); - addFlag(Flag::f_join | Flag::f_invite, tr::lng_admin_log_filter_members_new(tr::now)); - addFlag(Flag::f_info | Flag::f_settings, _channel->isMegagroup() ? tr::lng_admin_log_filter_info_group(tr::now) : tr::lng_admin_log_filter_info_channel(tr::now)); - addFlag(Flag::f_delete, tr::lng_admin_log_filter_messages_deleted(tr::now)); - addFlag(Flag::f_edit, tr::lng_admin_log_filter_messages_edited(tr::now)); + addFlag( + Flag::Promote | Flag::Demote, + tr::lng_admin_log_filter_admins_new(tr::now)); + addFlag( + Flag::Join | Flag::Invite, + tr::lng_admin_log_filter_members_new(tr::now)); + addFlag( + Flag::Info | Flag::Settings, + _channel->isMegagroup() + ? tr::lng_admin_log_filter_info_group(tr::now) + : tr::lng_admin_log_filter_info_channel(tr::now)); + addFlag(Flag::Delete, tr::lng_admin_log_filter_messages_deleted(tr::now)); + addFlag(Flag::Edit, tr::lng_admin_log_filter_messages_edited(tr::now)); if (isGroup) { - addFlag(Flag::f_pinned, tr::lng_admin_log_filter_messages_pinned(tr::now)); - addFlag(Flag::f_group_call, tr::lng_admin_log_filter_voice_chats(tr::now)); + addFlag( + Flag::Pinned, + tr::lng_admin_log_filter_messages_pinned(tr::now)); + addFlag( + Flag::GroupCall, + tr::lng_admin_log_filter_voice_chats(tr::now)); } else { - addFlag(Flag::f_group_call, tr::lng_admin_log_filter_voice_chats_channel(tr::now)); + addFlag( + Flag::GroupCall, + tr::lng_admin_log_filter_voice_chats_channel(tr::now)); } - addFlag(Flag::f_invites, tr::lng_admin_log_filter_invite_links(tr::now)); - addFlag(Flag::f_leave, tr::lng_admin_log_filter_members_removed(tr::now)); + addFlag(Flag::Invites, tr::lng_admin_log_filter_invite_links(tr::now)); + addFlag(Flag::Leave, tr::lng_admin_log_filter_members_removed(tr::now)); } void FilterBox::Inner::createAllUsersCheckbox(const FilterValue &filter) { 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 7c64f520d..1b5bba577 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -20,12 +20,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_cursor_state.h" #include "chat_helpers/message_field.h" #include "boxes/sticker_set_box.h" +#include "ui/boxes/confirm_box.h" #include "base/platform/base_platform_info.h" #include "base/unixtime.h" #include "mainwindow.h" #include "mainwidget.h" #include "core/application.h" #include "apiwrap.h" +#include "api/api_chat_participants.h" #include "api/api_attached_stickers.h" #include "window/window_session_controller.h" #include "main/main_session.h" @@ -445,44 +447,27 @@ void InnerWidget::requestAdmins() { MTP_int(kMaxChannelAdmins), MTP_long(participantsHash) )).done([=](const MTPchannels_ChannelParticipants &result) { - session().api().parseChannelParticipants(_channel, result, [&]( - int availableCount, - const QVector &list) { - auto filtered = ( - list - ) | ranges::views::transform([&](const MTPChannelParticipant &p) { - const auto participantId = p.match([]( - const MTPDchannelParticipantBanned &data) { - return peerFromMTP(data.vpeer()); - }, [](const MTPDchannelParticipantLeft &data) { - return peerFromMTP(data.vpeer()); - }, [](const auto &data) { - return peerFromUser(data.vuser_id()); - }); - const auto canEdit = p.match([]( - const MTPDchannelParticipantAdmin &data) { - return data.is_can_edit(); - }, [](const auto &) { - return false; - }); - return std::make_pair(participantId, canEdit); - }) | ranges::views::transform([&](auto &&pair) { - return std::make_pair( - (peerIsUser(pair.first) - ? session().data().userLoaded( - peerToUser(pair.first)) - : nullptr), - pair.second); - }) | ranges::views::filter([&](auto &&pair) { - return (pair.first != nullptr); - }); - - for (auto [user, canEdit] : filtered) { - _admins.emplace_back(user); - if (canEdit) { - _adminsCanEdit.emplace_back(user); + result.match([&](const MTPDchannels_channelParticipants &data) { + const auto &[availableCount, list] = Api::ChatParticipants::Parse( + _channel, + data); + _admins.clear(); + _adminsCanEdit.clear(); + for (const auto &parsed : list) { + if (parsed.isUser()) { + const auto user = _channel->owner().userLoaded( + parsed.userId()); + if (user) { + _admins.emplace_back(user); + if (parsed.canBeEdited() && !parsed.isCreator()) { + _adminsCanEdit.emplace_back(user); + } + } } } + }, [&](const MTPDchannels_channelParticipantsNotModified &) { + LOG(("API Error: c" + "hannels.channelParticipantsNotModified received!")); }); if (_admins.empty()) { _admins.push_back(session().user()); @@ -724,7 +709,29 @@ void InnerWidget::preloadMore(Direction direction) { } auto flags = MTPchannels_GetAdminLog::Flags(0); - auto filter = MTP_channelAdminLogEventsFilter(MTP_flags(_filter.flags)); + const auto filter = [&] { + using Flag = MTPDchannelAdminLogEventsFilter::Flag; + using LocalFlag = FilterValue::Flag; + const auto empty = MTPDchannelAdminLogEventsFilter::Flags(0); + const auto f = _filter.flags; + return empty + | ((f & LocalFlag::Join) ? Flag::f_join : empty) + | ((f & LocalFlag::Leave) ? Flag::f_leave : empty) + | ((f & LocalFlag::Invite) ? Flag::f_invite : empty) + | ((f & LocalFlag::Ban) ? Flag::f_ban : empty) + | ((f & LocalFlag::Unban) ? Flag::f_unban : empty) + | ((f & LocalFlag::Kick) ? Flag::f_kick : empty) + | ((f & LocalFlag::Unkick) ? Flag::f_unkick : empty) + | ((f & LocalFlag::Promote) ? Flag::f_promote : empty) + | ((f & LocalFlag::Demote) ? Flag::f_demote : empty) + | ((f & LocalFlag::Info) ? Flag::f_info : empty) + | ((f & LocalFlag::Settings) ? Flag::f_settings : empty) + | ((f & LocalFlag::Pinned) ? Flag::f_pinned : empty) + | ((f & LocalFlag::Edit) ? Flag::f_edit : empty) + | ((f & LocalFlag::Delete) ? Flag::f_delete : empty) + | ((f & LocalFlag::GroupCall) ? Flag::f_group_call : empty) + | ((f & LocalFlag::Invites) ? Flag::f_invites : empty); + }(); if (_filter.flags != 0) { flags |= MTPchannels_GetAdminLog::Flag::f_events_filter; } @@ -745,7 +752,7 @@ void InnerWidget::preloadMore(Direction direction) { MTP_flags(flags), _channel->inputChannel, MTP_string(_searchQuery), - filter, + MTP_channelAdminLogEventsFilter(MTP_flags(filter)), MTP_vector(admins), MTP_long(maxId), MTP_long(minId), @@ -761,7 +768,7 @@ void InnerWidget::preloadMore(Direction direction) { if (!loadedFlag) { addEvents(direction, results.vevents().v); } - }).fail([this, &requestId, &loadedFlag](const MTP::Error &error) { + }).fail([this, &requestId, &loadedFlag] { requestId = 0; loadedFlag = true; update(); @@ -789,37 +796,39 @@ void InnerWidget::addEvents(Direction direction, const QVectordata(), sentDate); - } - _eventIds.emplace(id); - _itemsByData.emplace(item->data(), item.get()); - addToItems.push_back(std::move(item)); - ++count; - }; - GenerateItems( - this, - _history, - data, - addOne); - if (count > 1) { - // Reverse the inner order of the added messages, because we load events - // from bottom to top but inside one event they go from top to bottom. - auto full = addToItems.size(); - auto from = full - count; - for (auto i = 0, toReverse = count / 2; i != toReverse; ++i) { - std::swap(addToItems[from + i], addToItems[full - i - 1]); - } - } + const auto &data = event.match([](const MTPDchannelAdminLogEvent &d) + -> const MTPDchannelAdminLogEvent & { + return d; }); + const auto id = data.vid().v; + if (_eventIds.find(id) != _eventIds.end()) { + return; + } + + auto count = 0; + const auto addOne = [&](OwnedItem item, TimeId sentDate) { + if (sentDate) { + _itemDates.emplace(item->data(), sentDate); + } + _eventIds.emplace(id); + _itemsByData.emplace(item->data(), item.get()); + addToItems.push_back(std::move(item)); + ++count; + }; + GenerateItems( + this, + _history, + data, + addOne); + if (count > 1) { + // Reverse the inner order of the added messages, because we load events + // from bottom to top but inside one event they go from top to bottom. + auto full = addToItems.size(); + auto from = full - count; + for (auto i = 0, toReverse = count / 2; i != toReverse; ++i) { + std::swap(addToItems[from + i], addToItems[full - i - 1]); + } + } } auto newItemsCount = _items.size() + ((direction == Direction::Up) ? 0 : newItemsForDownDirection.size()); if (newItemsCount != oldItemsCount) { @@ -1149,10 +1158,12 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { : App::hoveredLinkItem(); auto lnkPhoto = dynamic_cast(link.get()); auto lnkDocument = dynamic_cast(link.get()); - auto lnkPeer = dynamic_cast(link.get()); auto lnkIsVideo = lnkDocument ? lnkDocument->document()->isVideoFile() : false; auto lnkIsVoice = lnkDocument ? lnkDocument->document()->isVoiceMessage() : false; auto lnkIsAudio = lnkDocument ? lnkDocument->document()->isAudioFile() : false; + const auto fromId = PeerId(link + ? link->property(kPeerLinkPeerIdProperty).toULongLong() + : 0); if (lnkPhoto || lnkDocument) { if (isUponSelected > 0) { _menu->addAction(tr::lng_context_copy_selected(tr::now), [=] { @@ -1224,9 +1235,9 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { } } } - } else if (lnkPeer) { // suggest to block - if (auto user = lnkPeer->peer()->asUser()) { - suggestRestrictUser(user); + } else if (fromId) { // suggest to block + if (const auto participant = session().data().peer(fromId)) { + suggestRestrictParticipant(participant); } } else { // maybe cursor on some text history item? const auto item = view ? view->data().get() : nullptr; @@ -1350,38 +1361,59 @@ void InnerWidget::copyContextText(FullMsgId itemId) { } } -void InnerWidget::suggestRestrictUser(not_null user) { +void InnerWidget::suggestRestrictParticipant( + not_null participant) { Expects(_menu != nullptr); - if (!_channel->isMegagroup() || !_channel->canBanMembers() || _admins.empty()) { + if (!_channel->isMegagroup() + || !_channel->canBanMembers() + || _admins.empty()) { return; } - if (base::contains(_admins, user)) { - if (!base::contains(_adminsCanEdit, user)) { + if (ranges::contains(_admins, participant)) { + if (!ranges::contains(_adminsCanEdit, participant)) { return; } } - _menu->addAction(tr::lng_context_restrict_user(tr::now), [=] { + + const auto user = participant->asUser(); + + _menu->addAction(user ? tr::lng_context_restrict_user(tr::now) : tr::lng_context_remove_from_group(tr::now), [=] { auto editRestrictions = [=](bool hasAdminRights, ChatRestrictionsInfo currentRights) { auto weak = QPointer(this); - auto weakBox = std::make_shared>(); + auto weakBox = std::make_shared>(); auto box = Box(_channel, user, hasAdminRights, currentRights); box->setSaveCallback([=]( ChatRestrictionsInfo oldRights, ChatRestrictionsInfo newRights) { if (weak) { - weak->restrictUser(user, oldRights, newRights); + weak->restrictParticipant(participant, oldRights, newRights); } if (*weakBox) { (*weakBox)->closeBox(); } }); - *weakBox = QPointer(box.data()); - _controller->show( - std::move(box), - Ui::LayerOption::KeepOther); + *weakBox = _controller->show(std::move(box)); }; - if (base::contains(_admins, user)) { + if (!user) { + const auto text = (_channel->isBroadcast() + ? tr::lng_profile_sure_kick_channel + : tr::lng_profile_sure_kick)( + tr::now, + lt_user, + participant->name); + auto weakBox = std::make_shared>(); + const auto sure = crl::guard(this, [=] { + restrictParticipant( + participant, + ChatRestrictionsInfo(), + ChannelData::KickedRestrictedRights(participant)); + if (*weakBox) { + (*weakBox)->closeBox(); + } + }); + *weakBox = _controller->show(Box(text, sure)); + } else if (base::contains(_admins, user)) { editRestrictions(true, ChatRestrictionsInfo()); } else { _api.request(MTPchannels_GetParticipant( @@ -1403,87 +1435,93 @@ void InnerWidget::suggestRestrictUser(not_null user) { || (type == mtpc_channelParticipantCreator); editRestrictions(hasAdminRights, ChatRestrictionsInfo()); } - }).fail([=](const MTP::Error &error) { + }).fail([=] { editRestrictions(false, ChatRestrictionsInfo()); }).send(); } }); - _menu->addAction(tr::lng_context_remove_from_group(tr::now), [=] { - const auto text = tr::lng_profile_sure_kick(tr::now, lt_user, user->firstName); - auto editRestrictions = [=](bool hasAdminRights, ChatRestrictionsInfo currentRights) { - auto weak = QPointer(this); - auto weakBox = std::make_shared>(); - auto box = Box( - text, - tr::lng_box_remove(tr::now), - crl::guard(this, [=] { - if (weak) { - weak->restrictUser(user, - currentRights, - ChannelData::KickedRestrictedRights(user)); - } - if (*weakBox) { - (*weakBox)->closeBox(); - } - })); - *weakBox = Ui::show( - std::move(box), - Ui::LayerOption::KeepOther); - }; - if (base::contains(_admins, user)) { - editRestrictions(true, ChatRestrictionsInfo()); - } else { - _api.request(MTPchannels_GetParticipant( - _channel->inputChannel, - user->input - )).done([=](const MTPchannels_ChannelParticipant &result) { - Expects(result.type() == mtpc_channels_channelParticipant); + if (user) { + _menu->addAction(tr::lng_context_remove_from_group(tr::now), [=] { + auto editRestrictions = [=](bool hasAdminRights, ChatRestrictionsInfo currentRights) { + const auto text = tr::lng_profile_sure_kick(tr::now, lt_user, user->firstName); + auto weak = QPointer(this); + auto weakBox = std::make_shared>(); + auto box = Box( + text, + tr::lng_box_remove(tr::now), + crl::guard(this, [=] { + if (weak) { + weak->restrictParticipant( + participant, + ChatRestrictionsInfo(), + ChannelData::KickedRestrictedRights(participant)); + } + if (*weakBox) { + (*weakBox)->closeBox(); + } + })); + *weakBox = Ui::show( + std::move(box), + Ui::LayerOption::KeepOther); + }; + if (base::contains(_admins, user)) { + editRestrictions(true, ChatRestrictionsInfo()); + } else { + _api.request(MTPchannels_GetParticipant( + _channel->inputChannel, + user->input + )).done([=](const MTPchannels_ChannelParticipant &result) { + Expects(result.type() == mtpc_channels_channelParticipant); - auto &participant = result.c_channels_channelParticipant(); - _channel->owner().processUsers(participant.vusers()); - auto type = participant.vparticipant().type(); - if (type == mtpc_channelParticipantBanned) { - auto &banned = participant.vparticipant().c_channelParticipantBanned(); - editRestrictions(false, ChatRestrictionsInfo(banned.vbanned_rights())); - } else { - auto hasAdminRights = (type == mtpc_channelParticipantAdmin) - || (type == mtpc_channelParticipantCreator); + auto &participant = result.c_channels_channelParticipant(); + _channel->owner().processUsers(participant.vusers()); + auto type = participant.vparticipant().type(); + if (type == mtpc_channelParticipantBanned) { + auto &banned = participant.vparticipant().c_channelParticipantBanned(); + editRestrictions(false, ChatRestrictionsInfo(banned.vbanned_rights())); + } else { + auto hasAdminRights = (type == mtpc_channelParticipantAdmin) + || (type == mtpc_channelParticipantCreator); + auto bannedRights = ChatRestrictionsInfo(); + editRestrictions(hasAdminRights, bannedRights); + } + }).fail([=](const MTP::Error &error) { auto bannedRights = ChatRestrictionsInfo(); - editRestrictions(hasAdminRights, bannedRights); - } - }).fail([=](const MTP::Error &error) { - auto bannedRights = ChatRestrictionsInfo(); - editRestrictions(false, bannedRights); - }).send(); - } - }); + editRestrictions(false, bannedRights); + }).send(); + } + }); + } } -void InnerWidget::restrictUser( - not_null user, +void InnerWidget::restrictParticipant( + not_null participant, ChatRestrictionsInfo oldRights, ChatRestrictionsInfo newRights) { const auto done = [=](ChatRestrictionsInfo newRights) { - restrictUserDone(user, newRights); + restrictParticipantDone(participant, newRights); }; const auto callback = SaveRestrictedCallback( _channel, - user, + participant, crl::guard(this, done), nullptr); callback(oldRights, newRights); } -void InnerWidget::restrictUserDone( - not_null user, +void InnerWidget::restrictParticipantDone( + not_null participant, ChatRestrictionsInfo rights) { if (rights.flags) { _admins.erase( - std::remove(_admins.begin(), _admins.end(), user), + std::remove(_admins.begin(), _admins.end(), participant), _admins.end()); _adminsCanEdit.erase( - std::remove(_adminsCanEdit.begin(), _adminsCanEdit.end(), user), + std::remove( + _adminsCanEdit.begin(), + _adminsCanEdit.end(), + participant), _adminsCanEdit.end()); } _downLoaded = false; 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 0f3d1d959..65792b3d4 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h @@ -204,9 +204,14 @@ private: void copyContextText(FullMsgId itemId); void copySelectedText(); TextForMimeData getSelectedText() const; - void suggestRestrictUser(not_null user); - void restrictUser(not_null user, ChatRestrictionsInfo oldRights, ChatRestrictionsInfo newRights); - void restrictUserDone(not_null user, ChatRestrictionsInfo rights); + void suggestRestrictParticipant(not_null participant); + void restrictParticipant( + not_null participant, + ChatRestrictionsInfo oldRights, + ChatRestrictionsInfo newRights); + void restrictParticipantDone( + not_null participant, + ChatRestrictionsInfo rights); void requestAdmins(); void checkPreloadMore(); 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 25e906749..408279891 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_service.h" #include "history/history_message.h" #include "history/history.h" +#include "api/api_chat_participants.h" #include "api/api_text_entities.h" #include "data/data_channel.h" #include "data/data_user.h" @@ -24,14 +25,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/sticker_set_box.h" #include "base/unixtime.h" #include "core/application.h" -#include "mainwindow.h" // App::wnd()->sessionController +#include "core/click_handler_types.h" #include "main/main_session.h" +#include "window/window_session_controller.h" #include "facades.h" namespace AdminLog { namespace { -TextWithEntities PrepareText(const QString &value, const QString &emptyValue) { +TextWithEntities PrepareText( + const QString &value, + const QString &emptyValue) { auto result = TextWithEntities { TextUtilities::Clean(value) }; if (result.text.isEmpty()) { result.text = emptyValue; @@ -42,7 +46,12 @@ TextWithEntities PrepareText(const QString &value, const QString &emptyValue) { int(emptyValue.size()) }); } } else { - TextUtilities::ParseEntities(result, TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands); + TextUtilities::ParseEntities( + result, + TextParseLinks + | TextParseMentions + | TextParseHashtags + | TextParseBotCommands); } return result; } @@ -123,7 +132,8 @@ bool MediaCanHaveCaption(const MTPMessage &message) { const auto &data = message.c_message(); const auto media = data.vmedia(); const auto mediaType = media ? media->type() : mtpc_messageMediaEmpty; - return (mediaType == mtpc_messageMediaDocument || mediaType == mtpc_messageMediaPhoto); + return (mediaType == mtpc_messageMediaDocument) + || (mediaType == mtpc_messageMediaPhoto); } TextWithEntities ExtractEditedText( @@ -139,7 +149,10 @@ TextWithEntities ExtractEditedText( }; } -const auto CollectChanges = [](auto &phraseMap, auto plusFlags, auto minusFlags) { +const auto CollectChanges = []( + auto &phraseMap, + auto plusFlags, + auto minusFlags) { auto withPrefix = [&phraseMap](auto flags, QChar prefix) { auto result = QString(); for (auto &phrase : phraseMap) { @@ -150,7 +163,8 @@ const auto CollectChanges = [](auto &phraseMap, auto plusFlags, auto minusFlags) return result; }; const auto kMinus = QChar(0x2212); - return withPrefix(plusFlags & ~minusFlags, '+') + withPrefix(minusFlags & ~plusFlags, kMinus); + return withPrefix(plusFlags & ~minusFlags, '+') + + withPrefix(minusFlags & ~plusFlags, kMinus); }; const auto CollectChangesKtg = [](auto &phraseMap, auto &ktgPhraseMap, auto plusFlags, auto minusFlags) { @@ -181,7 +195,11 @@ TextWithEntities GenerateAdminChangeText( using Flag = ChatAdminRight; using Flags = ChatAdminRights; - auto result = tr::lng_admin_log_promoted(tr::now, lt_user, user, Ui::Text::WithEntities); + auto result = tr::lng_admin_log_promoted( + tr::now, + lt_user, + user, + Ui::Text::WithEntities); const auto useInviteLinkPhrase = channel->isMegagroup() && channel->anyoneCanAddMembers(); @@ -211,7 +229,10 @@ TextWithEntities GenerateAdminChangeText( prevRights.flags &= ~Flag::BanUsers; } - auto changes = CollectChanges(phraseMap, newRights.flags, prevRights.flags); + const auto changes = CollectChanges( + phraseMap, + newRights.flags, + prevRights.flags); if (!changes.isEmpty()) { result.text.append('\n' + changes); } @@ -251,16 +272,26 @@ TextWithEntities GenerateBannedChangeText( ChatRestrictionsInfo prevRights) { using Flag = ChatRestriction; - auto newFlags = newRights.flags; - auto newUntil = newRights.until; - auto prevFlags = prevRights.flags; - auto indefinitely = ChannelData::IsRestrictedForever(newUntil); + const auto newFlags = newRights.flags; + const auto newUntil = newRights.until; + const auto prevFlags = prevRights.flags; + const auto indefinitely = ChannelData::IsRestrictedForever(newUntil); if (newFlags & Flag::ViewMessages) { - return tr::lng_admin_log_banned(tr::now, lt_user, user, Ui::Text::WithEntities); - } else if (newFlags == 0 && (prevFlags & Flag::ViewMessages) && !peerIsUser(participantId)) { - return tr::lng_admin_log_unbanned(tr::now, lt_user, user, Ui::Text::WithEntities); + return tr::lng_admin_log_banned( + tr::now, + lt_user, + user, + Ui::Text::WithEntities); + } else if (newFlags == 0 + && (prevFlags & Flag::ViewMessages) + && !peerIsUser(participantId)) { + return tr::lng_admin_log_unbanned( + tr::now, + lt_user, + user, + Ui::Text::WithEntities); } - auto untilText = indefinitely + const auto untilText = indefinitely ? tr::lng_admin_log_restricted_forever(tr::now) : tr::lng_admin_log_restricted_until( tr::now, @@ -329,7 +360,11 @@ TextWithEntities GenerateInviteLinkChangeText( int(link.text.size()), InternalInviteLinkUrl(newLink) }); } - auto result = tr::lng_admin_log_edited_invite_link(tr::now, lt_link, link, Ui::Text::WithEntities); + auto result = tr::lng_admin_log_edited_invite_link( + tr::now, + lt_link, + link, + Ui::Text::WithEntities); result.text.append('\n'); const auto label = [](const MTPExportedChatInvite &link) { @@ -371,19 +406,41 @@ TextWithEntities GenerateInviteLinkChangeText( const auto wasRequestApproval = requestApproval(prevLink); const auto nowRequestApproval = requestApproval(newLink); if (wasLabel != nowLabel) { - result.text.append('\n').append(tr::lng_admin_log_invite_link_label(tr::now, lt_previous, wasLabel, lt_limit, nowLabel)); + result.text.append('\n').append( + tr::lng_admin_log_invite_link_label( + tr::now, + lt_previous, + wasLabel, + lt_limit, + nowLabel)); } if (wasExpireDate != nowExpireDate) { - result.text.append('\n').append(tr::lng_admin_log_invite_link_expire_date(tr::now, lt_previous, wrapDate(wasExpireDate), lt_limit, wrapDate(nowExpireDate))); + result.text.append('\n').append( + tr::lng_admin_log_invite_link_expire_date( + tr::now, + lt_previous, + wrapDate(wasExpireDate), + lt_limit, + wrapDate(nowExpireDate))); } if (wasUsageLimit != nowUsageLimit) { - result.text.append('\n').append(tr::lng_admin_log_invite_link_usage_limit(tr::now, lt_previous, wrapUsage(wasUsageLimit), lt_limit, wrapUsage(nowUsageLimit))); + result.text.append('\n').append( + tr::lng_admin_log_invite_link_usage_limit( + tr::now, + lt_previous, + wrapUsage(wasUsageLimit), + lt_limit, + wrapUsage(nowUsageLimit))); } if (wasRequestApproval != nowRequestApproval) { - result.text.append('\n').append(nowRequestApproval ? tr::lng_admin_log_invite_link_request_needed(tr::now) : tr::lng_admin_log_invite_link_request_not_needed(tr::now)); + result.text.append('\n').append( + nowRequestApproval + ? tr::lng_admin_log_invite_link_request_needed(tr::now) + : tr::lng_admin_log_invite_link_request_not_needed(tr::now)); } - result.entities.push_front(EntityInText(EntityType::Italic, 0, result.text.size())); + result.entities.push_front( + EntityInText(EntityType::Italic, 0, result.text.size())); return result; }; @@ -391,7 +448,7 @@ auto GenerateParticipantString( not_null session, PeerId participantId) { // User name in "User name (@username)" format with entities. - auto peer = session->data().peer(participantId); + const auto peer = session->data().peer(participantId); auto name = TextWithEntities { peer->name }; if (const auto user = peer->asUser()) { auto entityData = QString::number(user->id.value) @@ -403,7 +460,7 @@ auto GenerateParticipantString( int(name.text.size()), entityData }); } - auto username = peer->userName(); + const auto username = peer->userName(); if (username.isEmpty()) { return name; } @@ -421,86 +478,113 @@ auto GenerateParticipantString( Ui::Text::WithEntities); } -auto GenerateParticipantChangeTextInner( +auto GenerateParticipantChangeText( not_null channel, - const MTPChannelParticipant &participant, - const MTPChannelParticipant *oldParticipant) { - const auto oldType = oldParticipant ? oldParticipant->type() : 0; + const Api::ChatParticipant &participant, + std::optional oldParticipant = std::nullopt) { + using Type = Api::ChatParticipant::Type; + const auto oldRights = oldParticipant + ? oldParticipant->rights() + : ChatAdminRightsInfo(); + const auto oldRestrictions = oldParticipant + ? oldParticipant->restrictions() + : ChatRestrictionsInfo(); + const auto generateOther = [&](PeerId participantId) { auto user = GenerateParticipantString( &channel->session(), participantId); - if (oldType == mtpc_channelParticipantAdmin) { + if (oldParticipant && oldParticipant->type() == Type::Admin) { return GenerateAdminChangeText( channel, user, ChatAdminRightsInfo(), - ChatAdminRightsInfo( - oldParticipant->c_channelParticipantAdmin().vadmin_rights())); - } else if (oldType == mtpc_channelParticipantBanned) { + oldRights); + } else if (oldParticipant && oldParticipant->type() == Type::Banned) { return GenerateBannedChangeText( participantId, user, ChatRestrictionsInfo(), - ChatRestrictionsInfo( - oldParticipant->c_channelParticipantBanned().vbanned_rights())); + oldRestrictions); } - return tr::lng_admin_log_invited(tr::now, lt_user, user, Ui::Text::WithEntities); - }; - return participant.match([&](const MTPDchannelParticipantCreator &data) { - // No valid string here :( - return tr::lng_admin_log_transferred( + return tr::lng_admin_log_invited( tr::now, lt_user, - GenerateParticipantString( - &channel->session(), - peerFromUser(data.vuser_id())), + user, Ui::Text::WithEntities); - }, [&](const MTPDchannelParticipantAdmin &data) { - const auto user = GenerateParticipantString( - &channel->session(), - peerFromUser(data.vuser_id())); - return GenerateAdminChangeText( - channel, - user, - ChatAdminRightsInfo(data.vadmin_rights()), - (oldType == mtpc_channelParticipantAdmin - ? ChatAdminRightsInfo( - oldParticipant->c_channelParticipantAdmin().vadmin_rights()) - : ChatAdminRightsInfo())); - }, [&](const MTPDchannelParticipantBanned &data) { - const auto participantId = peerFromMTP(data.vpeer()); - const auto user = GenerateParticipantString( - &channel->session(), - participantId); - return GenerateBannedChangeText( - participantId, - user, - ChatRestrictionsInfo(data.vbanned_rights()), - (oldType == mtpc_channelParticipantBanned - ? ChatRestrictionsInfo( - oldParticipant->c_channelParticipantBanned().vbanned_rights()) - : ChatRestrictionsInfo())); - }, [&](const MTPDchannelParticipantLeft &data) { - return generateOther(peerFromMTP(data.vpeer())); - }, [&](const auto &data) { - return generateOther(peerFromUser(data.vuser_id())); - }); -} + }; -TextWithEntities GenerateParticipantChangeText(not_null channel, const MTPChannelParticipant &participant, const MTPChannelParticipant *oldParticipant = nullptr) { - auto result = GenerateParticipantChangeTextInner(channel, participant, oldParticipant); - result.entities.push_front(EntityInText(EntityType::Italic, 0, result.text.size())); + auto result = [&] { + const auto &peerId = participant.id(); + switch (participant.type()) { + case Api::ChatParticipant::Type::Creator: { + // No valid string here :( + return tr::lng_admin_log_transferred( + tr::now, + lt_user, + GenerateParticipantString(&channel->session(), peerId), + Ui::Text::WithEntities); + } + case Api::ChatParticipant::Type::Admin: { + const auto user = GenerateParticipantString( + &channel->session(), + peerId); + return GenerateAdminChangeText( + channel, + user, + participant.rights(), + oldRights); + } + case Api::ChatParticipant::Type::Restricted: + case Api::ChatParticipant::Type::Banned: { + const auto user = GenerateParticipantString( + &channel->session(), + peerId); + return GenerateBannedChangeText( + peerId, + user, + participant.restrictions(), + oldRestrictions); + } + case Api::ChatParticipant::Type::Left: + case Api::ChatParticipant::Type::Member: + return generateOther(peerId); + }; + Unexpected("Participant type in GenerateParticipantChangeText."); + }(); + + result.entities.push_front( + EntityInText(EntityType::Italic, 0, result.text.size())); return result; } -TextWithEntities GenerateDefaultBannedRightsChangeText(not_null channel, ChatRestrictionsInfo rights, ChatRestrictionsInfo oldRights) { - auto result = TextWithEntities{ tr::lng_admin_log_changed_default_permissions(tr::now) }; +TextWithEntities GenerateParticipantChangeText( + not_null channel, + const MTPChannelParticipant &participant, + std::optionaloldParticipant = std::nullopt) { + return GenerateParticipantChangeText( + channel, + Api::ChatParticipant(participant, channel), + oldParticipant + ? std::make_optional(Api::ChatParticipant( + *oldParticipant, + channel)) + : std::nullopt); +} + +TextWithEntities GenerateDefaultBannedRightsChangeText( + not_null channel, + ChatRestrictionsInfo rights, + ChatRestrictionsInfo oldRights) { + auto result = TextWithEntities{ + tr::lng_admin_log_changed_default_permissions(tr::now) + }; const auto changes = GenerateBannedChangeText(rights, oldRights); if (!changes.isEmpty()) { result.text.append('\n' + changes); } - result.entities.push_front(EntityInText(EntityType::Italic, 0, result.text.size())); + result.entities.push_front( + EntityInText(EntityType::Italic, 0, result.text.size())); return result; } @@ -550,6 +634,48 @@ void GenerateItems( Fn callback) { Expects(history->peer->isChannel()); + using LogTitle = MTPDchannelAdminLogEventActionChangeTitle; + using LogAbout = MTPDchannelAdminLogEventActionChangeAbout; + using LogUsername = MTPDchannelAdminLogEventActionChangeUsername; + using LogPhoto = MTPDchannelAdminLogEventActionChangePhoto; + using LogInvites = MTPDchannelAdminLogEventActionToggleInvites; + using LogSign = MTPDchannelAdminLogEventActionToggleSignatures; + using LogPin = MTPDchannelAdminLogEventActionUpdatePinned; + using LogEdit = MTPDchannelAdminLogEventActionEditMessage; + using LogDelete = MTPDchannelAdminLogEventActionDeleteMessage; + using LogJoin = MTPDchannelAdminLogEventActionParticipantJoin; + using LogLeave = MTPDchannelAdminLogEventActionParticipantLeave; + using LogInvite = MTPDchannelAdminLogEventActionParticipantInvite; + using LogBan = MTPDchannelAdminLogEventActionParticipantToggleBan; + using LogPromote = MTPDchannelAdminLogEventActionParticipantToggleAdmin; + using LogSticker = MTPDchannelAdminLogEventActionChangeStickerSet; + using LogPreHistory = + MTPDchannelAdminLogEventActionTogglePreHistoryHidden; + using LogPermissions = MTPDchannelAdminLogEventActionDefaultBannedRights; + using LogPoll = MTPDchannelAdminLogEventActionStopPoll; + using LogDiscussion = MTPDchannelAdminLogEventActionChangeLinkedChat; + using LogLocation = MTPDchannelAdminLogEventActionChangeLocation; + using LogSlowMode = MTPDchannelAdminLogEventActionToggleSlowMode; + using LogStartCall = MTPDchannelAdminLogEventActionStartGroupCall; + using LogDiscardCall = MTPDchannelAdminLogEventActionDiscardGroupCall; + using LogMute = MTPDchannelAdminLogEventActionParticipantMute; + using LogUnmute = MTPDchannelAdminLogEventActionParticipantUnmute; + using LogCallSetting = + MTPDchannelAdminLogEventActionToggleGroupCallSetting; + using LogJoinByInvite = + MTPDchannelAdminLogEventActionParticipantJoinByInvite; + using LogInviteDelete = + MTPDchannelAdminLogEventActionExportedInviteDelete; + using LogInviteRevoke = + MTPDchannelAdminLogEventActionExportedInviteRevoke; + using LogInviteEdit = MTPDchannelAdminLogEventActionExportedInviteEdit; + using LogVolume = MTPDchannelAdminLogEventActionParticipantVolume; + using LogTTL = MTPDchannelAdminLogEventActionChangeHistoryTTL; + using LogJoinByRequest = + MTPDchannelAdminLogEventActionParticipantJoinByRequest; + using LogNoForwards = MTPDchannelAdminLogEventActionToggleNoForwards; + using LogActionSendMessage = MTPDchannelAdminLogEventActionSendMessage; + const auto session = &history->session(); const auto id = event.vid().v; const auto from = history->owner().user(event.vuser_id().v); @@ -567,7 +693,10 @@ void GenerateItems( const auto fromLink = from->createOpenLink(); const auto fromLinkText = textcmdLink(1, fromName); - auto addSimpleServiceMessage = [&](const QString &text, PhotoData *photo = nullptr, bool showTime = true) { + auto addSimpleServiceMessage = [&]( + const QString &text, + PhotoData *photo = nullptr, + bool showTime = true) { auto message = HistoryService::PreparedText { text }; message.links.push_back(fromLink); addPart(history->makeServiceMessage( @@ -580,7 +709,7 @@ void GenerateItems( showTime)); }; - auto createChangeTitle = [&](const MTPDchannelAdminLogEventActionChangeTitle &action) { + const auto createChangeTitle = [&](const LogTitle &action) { auto text = (channel->isMegagroup() ? tr::lng_action_changed_title : tr::lng_admin_log_changed_title_channel)( @@ -592,11 +721,12 @@ void GenerateItems( addSimpleServiceMessage(text); }; - auto makeSimpleTextMessage = [&](TextWithEntities &&text) { - auto bodyFlags = MessageFlag::HasFromId | MessageFlag::AdminLogEntry; - auto bodyReplyTo = MsgId(); - auto bodyViaBotId = UserId(); - auto bodyGroupedId = uint64(); + const auto makeSimpleTextMessage = [&](TextWithEntities &&text) { + const auto bodyFlags = MessageFlag::HasFromId + | MessageFlag::AdminLogEntry; + const auto bodyReplyTo = MsgId(); + const auto bodyViaBotId = UserId(); + const auto bodyGroupedId = uint64(); return history->makeMessage( history->nextNonHistoryEntryId(), bodyFlags, @@ -611,14 +741,14 @@ void GenerateItems( bodyGroupedId); }; - auto addSimpleTextMessage = [&](TextWithEntities &&text) { + const 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()); - auto text = (channel->isMegagroup() + const auto createChangeAbout = [&](const LogAbout &action) { + const auto newValue = qs(action.vnew_value()); + const auto oldValue = qs(action.vprev_value()); + const auto text = (channel->isMegagroup() ? (newValue.isEmpty() ? tr::lng_admin_log_removed_description_group : tr::lng_admin_log_changed_description_group) @@ -628,19 +758,22 @@ void GenerateItems( )(tr::now, lt_from, fromLinkText); addSimpleServiceMessage(text, nullptr, false); - auto newDescription = PrepareText(newValue, QString()); - auto body = makeSimpleTextMessage(PrepareText(newValue, QString())); + const 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); + const auto oldDescription = PrepareText(oldValue, QString()); + body->addLogEntryOriginal( + id, + tr::lng_admin_log_previous_description(tr::now), + oldDescription); } addPart(body); }; - auto createChangeUsername = [&](const MTPDchannelAdminLogEventActionChangeUsername &action) { - auto newValue = qs(action.vnew_value()); - auto oldValue = qs(action.vprev_value()); - auto text = (channel->isMegagroup() + const auto createChangeUsername = [&](const LogUsername &action) { + const auto newValue = qs(action.vnew_value()); + const auto oldValue = qs(action.vprev_value()); + const auto text = (channel->isMegagroup() ? (newValue.isEmpty() ? tr::lng_admin_log_removed_link_group : tr::lng_admin_log_changed_link_group) @@ -650,29 +783,27 @@ void GenerateItems( )(tr::now, lt_from, fromLinkText); addSimpleServiceMessage(text, nullptr, false); - auto newLink = newValue.isEmpty() - ? TextWithEntities() - : PrepareText( - history->session().createInternalLinkFull(newValue), - QString()); - auto body = makeSimpleTextMessage(newValue.isEmpty() + const auto body = makeSimpleTextMessage(newValue.isEmpty() ? TextWithEntities() : PrepareText( history->session().createInternalLinkFull(newValue), QString())); if (!oldValue.isEmpty()) { - auto oldLink = PrepareText( + const auto oldLink = PrepareText( history->session().createInternalLinkFull(oldValue), QString()); - body->addLogEntryOriginal(id, tr::lng_admin_log_previous_link(tr::now), oldLink); + body->addLogEntryOriginal( + id, + tr::lng_admin_log_previous_link(tr::now), + oldLink); } addPart(body); }; - auto createChangePhoto = [&](const MTPDchannelAdminLogEventActionChangePhoto &action) { + const auto createChangePhoto = [&](const LogPhoto &action) { action.vnew_photo().match([&](const MTPDphoto &data) { - auto photo = history->owner().processPhoto(data); - auto text = (channel->isMegagroup() + const auto photo = history->owner().processPhoto(data); + const auto text = (channel->isMegagroup() ? tr::lng_admin_log_changed_photo_group : tr::lng_admin_log_changed_photo_channel)( tr::now, @@ -680,7 +811,7 @@ void GenerateItems( fromLinkText); addSimpleServiceMessage(text, photo); }, [&](const MTPDphotoEmpty &data) { - auto text = (channel->isMegagroup() + const auto text = (channel->isMegagroup() ? tr::lng_admin_log_removed_photo_group : tr::lng_admin_log_removed_photo_channel)( tr::now, @@ -690,31 +821,34 @@ void GenerateItems( }); }; - auto createToggleInvites = [&](const MTPDchannelAdminLogEventActionToggleInvites &action) { - auto enabled = (action.vnew_value().type() == mtpc_boolTrue); - auto text = (enabled + const auto createToggleInvites = [&](const LogInvites &action) { + const auto enabled = (action.vnew_value().type() == mtpc_boolTrue); + const auto text = (enabled ? tr::lng_admin_log_invites_enabled : tr::lng_admin_log_invites_disabled); addSimpleServiceMessage(text(tr::now, lt_from, fromLinkText)); }; - auto createToggleSignatures = [&](const MTPDchannelAdminLogEventActionToggleSignatures &action) { - auto enabled = (action.vnew_value().type() == mtpc_boolTrue); - auto text = (enabled + const auto createToggleSignatures = [&](const LogSign &action) { + const auto enabled = (action.vnew_value().type() == mtpc_boolTrue); + const auto text = (enabled ? tr::lng_admin_log_signatures_enabled : tr::lng_admin_log_signatures_disabled); addSimpleServiceMessage(text(tr::now, lt_from, fromLinkText)); }; - auto createUpdatePinned = [&](const MTPDchannelAdminLogEventActionUpdatePinned &action) { + const auto createUpdatePinned = [&](const LogPin &action) { action.vmessage().match([&](const MTPDmessage &data) { const auto pinned = data.is_pinned(); - auto text = (pinned + const auto text = (pinned ? tr::lng_admin_log_pinned_message - : tr::lng_admin_log_unpinned_message)(tr::now, lt_from, fromLinkText); + : tr::lng_admin_log_unpinned_message)( + tr::now, + lt_from, + fromLinkText); addSimpleServiceMessage(text, nullptr, false); - auto detachExistingItem = false; + const auto detachExistingItem = false; addPart( history->createItem( history->nextNonHistoryEntryId(), @@ -723,15 +857,21 @@ void GenerateItems( detachExistingItem), ExtractSentDate(action.vmessage())); }, [&](const auto &) { - auto text = tr::lng_admin_log_unpinned_message(tr::now, lt_from, fromLinkText); + const auto text = tr::lng_admin_log_unpinned_message( + tr::now, + lt_from, + fromLinkText); addSimpleServiceMessage(text); }); }; - auto createEditMessage = [&](const MTPDchannelAdminLogEventActionEditMessage &action) { - auto newValue = ExtractEditedText(session, action.vnew_message()); - auto canHaveCaption = MediaCanHaveCaption(action.vnew_message()); - auto text = (!canHaveCaption + const auto createEditMessage = [&](const LogEdit &action) { + const auto newValue = ExtractEditedText( + session, + action.vnew_message()); + const auto canHaveCaption = MediaCanHaveCaption( + action.vnew_message()); + const auto text = (!canHaveCaption ? tr::lng_admin_log_edited_message : newValue.text.isEmpty() ? tr::lng_admin_log_removed_caption @@ -741,15 +881,19 @@ void GenerateItems( fromLinkText); addSimpleServiceMessage(text, nullptr, false); - auto oldValue = ExtractEditedText(session, action.vprev_message()); - auto detachExistingItem = false; - auto body = history->createItem( + auto oldValue = ExtractEditedText( + session, + action.vprev_message()); + const auto detachExistingItem = false; + const auto body = history->createItem( history->nextNonHistoryEntryId(), PrepareLogMessage(action.vnew_message(), date), MessageFlag::AdminLogEntry, detachExistingItem); if (oldValue.text.isEmpty()) { - oldValue = PrepareText(QString(), tr::lng_admin_log_empty_text(tr::now)); + oldValue = PrepareText( + QString(), + tr::lng_admin_log_empty_text(tr::now)); } body->addLogEntryOriginal( @@ -761,11 +905,14 @@ void GenerateItems( addPart(body); }; - auto createDeleteMessage = [&](const MTPDchannelAdminLogEventActionDeleteMessage &action) { - auto text = tr::lng_admin_log_deleted_message(tr::now, lt_from, fromLinkText); + const auto createDeleteMessage = [&](const LogDelete &action) { + const auto text = tr::lng_admin_log_deleted_message( + tr::now, + lt_from, + fromLinkText); addSimpleServiceMessage(text, nullptr, false); - auto detachExistingItem = false; + const auto detachExistingItem = false; addPart( history->createItem( history->nextNonHistoryEntryId(), @@ -775,58 +922,76 @@ void GenerateItems( ExtractSentDate(action.vmessage())); }; - auto createParticipantJoin = [&]() { - auto text = (channel->isMegagroup() + const auto createParticipantJoin = [&]() { + const auto text = (channel->isMegagroup() ? tr::lng_admin_log_participant_joined : tr::lng_admin_log_participant_joined_channel); addSimpleServiceMessage(text(tr::now, lt_from, fromLinkText)); }; - auto createParticipantLeave = [&]() { - auto text = (channel->isMegagroup() + const auto createParticipantLeave = [&]() { + const auto text = (channel->isMegagroup() ? tr::lng_admin_log_participant_left : tr::lng_admin_log_participant_left_channel); addSimpleServiceMessage(text(tr::now, lt_from, fromLinkText)); }; - auto createParticipantInvite = [&](const MTPDchannelAdminLogEventActionParticipantInvite &action) { + const auto createParticipantInvite = [&](const LogInvite &action) { addSimpleTextMessage( GenerateParticipantChangeText(channel, action.vparticipant())); }; - auto createParticipantToggleBan = [&](const MTPDchannelAdminLogEventActionParticipantToggleBan &action) { + const auto createParticipantToggleBan = [&](const LogBan &action) { addSimpleTextMessage( - GenerateParticipantChangeText(channel, action.vnew_participant(), &action.vprev_participant())); + GenerateParticipantChangeText( + channel, + action.vnew_participant(), + action.vprev_participant())); }; - auto createParticipantToggleAdmin = [&](const MTPDchannelAdminLogEventActionParticipantToggleAdmin &action) { - if (action.vnew_participant().type() == mtpc_channelParticipantAdmin - && action.vprev_participant().type() == mtpc_channelParticipantCreator) { + const auto createParticipantToggleAdmin = [&](const LogPromote &action) { + if ((action.vnew_participant().type() == mtpc_channelParticipantAdmin) + && (action.vprev_participant().type() + == mtpc_channelParticipantCreator)) { // In case of ownership transfer we show that message in // the "User > Creator" part and skip the "Creator > Admin" part. return; } addSimpleTextMessage( - GenerateParticipantChangeText(channel, action.vnew_participant(), &action.vprev_participant())); + GenerateParticipantChangeText( + channel, + action.vnew_participant(), + action.vprev_participant())); }; - auto createChangeStickerSet = [&](const MTPDchannelAdminLogEventActionChangeStickerSet &action) { - auto set = action.vnew_stickerset(); - auto removed = (set.type() == mtpc_inputStickerSetEmpty); + const auto createChangeStickerSet = [&](const LogSticker &action) { + const auto set = action.vnew_stickerset(); + const auto removed = (set.type() == mtpc_inputStickerSetEmpty); if (removed) { - auto text = tr::lng_admin_log_removed_stickers_group(tr::now, lt_from, fromLinkText); + const auto text = tr::lng_admin_log_removed_stickers_group( + tr::now, + lt_from, + fromLinkText); addSimpleServiceMessage(text); } else { - auto text = tr::lng_admin_log_changed_stickers_group( + const auto text = tr::lng_admin_log_changed_stickers_group( tr::now, lt_from, fromLinkText, lt_sticker_set, - textcmdLink(2, tr::lng_admin_log_changed_stickers_set(tr::now))); - auto setLink = std::make_shared([set] { - Ui::show(Box( - App::wnd()->sessionController(), - Data::FromInputSet(set))); + textcmdLink( + 2, + tr::lng_admin_log_changed_stickers_set(tr::now))); + const auto setLink = std::make_shared([=]( + ClickContext context) { + const auto my = context.other.value(); + if (const auto controller = my.sessionWindow.get()) { + controller->show( + Box( + controller, + Data::FromInputSet(set)), + Ui::LayerOption::CloseOther); + } }); auto message = HistoryService::PreparedText { text }; message.links.push_back(fromLink); @@ -840,15 +1005,17 @@ void GenerateItems( } }; - auto createTogglePreHistoryHidden = [&](const MTPDchannelAdminLogEventActionTogglePreHistoryHidden &action) { - auto hidden = (action.vnew_value().type() == mtpc_boolTrue); - auto text = (hidden + const auto createTogglePreHistoryHidden = [&]( + const LogPreHistory &action) { + const auto hidden = (action.vnew_value().type() == mtpc_boolTrue); + const auto text = (hidden ? tr::lng_admin_log_history_made_hidden : tr::lng_admin_log_history_made_visible); addSimpleServiceMessage(text(tr::now, lt_from, fromLinkText)); }; - auto createDefaultBannedRights = [&](const MTPDchannelAdminLogEventActionDefaultBannedRights &action) { + const auto createDefaultBannedRights = [&]( + const LogPermissions &action) { addSimpleTextMessage( GenerateDefaultBannedRightsChangeText( channel, @@ -856,11 +1023,14 @@ void GenerateItems( ChatRestrictionsInfo(action.vprev_banned_rights()))); }; - auto createStopPoll = [&](const MTPDchannelAdminLogEventActionStopPoll &action) { - auto text = tr::lng_admin_log_stopped_poll(tr::now, lt_from, fromLinkText); + const auto createStopPoll = [&](const LogPoll &action) { + const auto text = tr::lng_admin_log_stopped_poll( + tr::now, + lt_from, + fromLinkText); addSimpleServiceMessage(text, nullptr, false); - auto detachExistingItem = false; + const auto detachExistingItem = false; addPart( history->createItem( history->nextNonHistoryEntryId(), @@ -870,10 +1040,11 @@ void GenerateItems( ExtractSentDate(action.vmessage())); }; - auto createChangeLinkedChat = [&](const MTPDchannelAdminLogEventActionChangeLinkedChat &action) { - const auto now = history->owner().channelLoaded(action.vnew_value().v); + const auto createChangeLinkedChat = [&](const LogDiscussion &action) { + const auto now = history->owner().channelLoaded( + action.vnew_value().v); if (!now) { - auto text = (broadcast + const auto text = (broadcast ? tr::lng_admin_log_removed_linked_chat : tr::lng_admin_log_removed_linked_channel)( tr::now, @@ -881,7 +1052,7 @@ void GenerateItems( fromLinkText); addSimpleServiceMessage(text); } else { - auto text = (broadcast + const auto text = (broadcast ? tr::lng_admin_log_changed_linked_chat : tr::lng_admin_log_changed_linked_channel)( tr::now, @@ -889,7 +1060,7 @@ void GenerateItems( fromLinkText, lt_chat, textcmdLink(2, now->name)); - auto chatLink = std::make_shared([=] { + const auto chatLink = std::make_shared([=] { Ui::showPeerHistory(now, ShowAtUnreadMsgId); }); auto message = HistoryService::PreparedText{ text }; @@ -904,10 +1075,11 @@ void GenerateItems( } }; - auto createChangeLocation = [&](const MTPDchannelAdminLogEventActionChangeLocation &action) { + const auto createChangeLocation = [&](const LogLocation &action) { action.vnew_value().match([&](const MTPDchannelLocation &data) { const auto address = qs(data.vaddress()); - const auto link = data.vgeo_point().match([&](const MTPDgeoPoint &data) { + const auto link = data.vgeo_point().match([&]( + const MTPDgeoPoint &data) { return textcmdLink( LocationClickHandler::Url(Data::LocationPoint(data)), address); @@ -922,16 +1094,25 @@ void GenerateItems( link); addSimpleServiceMessage(text); }, [&](const MTPDchannelLocationEmpty &) { - const auto text = tr::lng_admin_log_removed_location_chat(tr::now, lt_from, fromLinkText); + const auto text = tr::lng_admin_log_removed_location_chat( + tr::now, + lt_from, + fromLinkText); addSimpleServiceMessage(text); }); }; - auto createToggleSlowMode = [&](const MTPDchannelAdminLogEventActionToggleSlowMode &action) { + const auto createToggleSlowMode = [&](const LogSlowMode &action) { if (const auto seconds = action.vnew_value().v) { const auto duration = (seconds >= 60) - ? tr::lng_admin_log_slow_mode_minutes(tr::now, lt_count, seconds / 60) - : tr::lng_admin_log_slow_mode_seconds(tr::now, lt_count, seconds); + ? tr::lng_admin_log_slow_mode_minutes( + tr::now, + lt_count, + seconds / 60) + : tr::lng_admin_log_slow_mode_seconds( + tr::now, + lt_count, + seconds); const auto text = tr::lng_admin_log_changed_slow_mode( tr::now, lt_from, @@ -940,32 +1121,44 @@ void GenerateItems( duration); addSimpleServiceMessage(text); } else { - const auto text = tr::lng_admin_log_removed_slow_mode(tr::now, lt_from, fromLinkText); + const auto text = tr::lng_admin_log_removed_slow_mode( + tr::now, + lt_from, + fromLinkText); addSimpleServiceMessage(text); } }; - auto createStartGroupCall = [&](const MTPDchannelAdminLogEventActionStartGroupCall &data) { + const auto createStartGroupCall = [&](const LogStartCall &data) { const auto text = (broadcast ? tr::lng_admin_log_started_group_call_channel - : tr::lng_admin_log_started_group_call)(tr::now, lt_from, fromLinkText); + : tr::lng_admin_log_started_group_call)( + tr::now, + lt_from, + fromLinkText); addSimpleServiceMessage(text); }; - auto createDiscardGroupCall = [&](const MTPDchannelAdminLogEventActionDiscardGroupCall &data) { + const auto createDiscardGroupCall = [&](const LogDiscardCall &data) { const auto text = (broadcast ? tr::lng_admin_log_discarded_group_call_channel - : tr::lng_admin_log_discarded_group_call)(tr::now, lt_from, fromLinkText); + : tr::lng_admin_log_discarded_group_call)( + tr::now, + lt_from, + fromLinkText); addSimpleServiceMessage(text); }; - auto groupCallParticipantPeer = [&](const MTPGroupCallParticipant &data) { + const auto groupCallParticipantPeer = [&]( + const MTPGroupCallParticipant &data) { return data.match([&](const MTPDgroupCallParticipant &data) { return history->owner().peer(peerFromMTP(data.vpeer())); }); }; - auto addServiceMessageWithLink = [&](const QString &text, const ClickHandlerPtr &link) { + const auto addServiceMessageWithLink = [&]( + const QString &text, + const ClickHandlerPtr &link) { auto message = HistoryService::PreparedText{ text }; message.links.push_back(fromLink); message.links.push_back(link); @@ -977,11 +1170,14 @@ void GenerateItems( peerToUser(from->id))); }; - auto createParticipantMute = [&](const MTPDchannelAdminLogEventActionParticipantMute &data) { - const auto participantPeer = groupCallParticipantPeer(data.vparticipant()); + const auto createParticipantMute = [&](const LogMute &data) { + const auto participantPeer = groupCallParticipantPeer( + data.vparticipant()); const auto participantPeerLink = participantPeer->createOpenLink(); - const auto participantPeerLinkText = textcmdLink(2, participantPeer->name); - auto text = (broadcast + const auto participantPeerLinkText = textcmdLink( + 2, + participantPeer->name); + const auto text = (broadcast ? tr::lng_admin_log_muted_participant_channel : tr::lng_admin_log_muted_participant)( tr::now, @@ -992,11 +1188,14 @@ void GenerateItems( addServiceMessageWithLink(text, participantPeerLink); }; - auto createParticipantUnmute = [&](const MTPDchannelAdminLogEventActionParticipantUnmute &data) { - const auto participantPeer = groupCallParticipantPeer(data.vparticipant()); + const auto createParticipantUnmute = [&](const LogUnmute &data) { + const auto participantPeer = groupCallParticipantPeer( + data.vparticipant()); const auto participantPeerLink = participantPeer->createOpenLink(); - const auto participantPeerLinkText = textcmdLink(2, participantPeer->name); - auto text = (broadcast + const auto participantPeerLinkText = textcmdLink( + 2, + participantPeer->name); + const auto text = (broadcast ? tr::lng_admin_log_unmuted_participant_channel : tr::lng_admin_log_unmuted_participant)( tr::now, @@ -1007,22 +1206,30 @@ void GenerateItems( addServiceMessageWithLink(text, participantPeerLink); }; - auto createToggleGroupCallSetting = [&](const MTPDchannelAdminLogEventActionToggleGroupCallSetting &data) { + const auto createToggleGroupCallSetting = [&]( + const LogCallSetting &data) { const auto text = (mtpIsTrue(data.vjoin_muted()) ? (broadcast ? tr::lng_admin_log_disallowed_unmute_self_channel : tr::lng_admin_log_disallowed_unmute_self) : (broadcast ? tr::lng_admin_log_allowed_unmute_self_channel - : tr::lng_admin_log_allowed_unmute_self))(tr::now, lt_from, fromLinkText); + : tr::lng_admin_log_allowed_unmute_self))( + tr::now, + lt_from, + fromLinkText); addSimpleServiceMessage(text); }; - auto addInviteLinkServiceMessage = [&](const QString &text, const MTPExportedChatInvite &data, ClickHandlerPtr additional = nullptr) { + const auto addInviteLinkServiceMessage = [&]( + const QString &text, + const MTPExportedChatInvite &data, + ClickHandlerPtr additional = nullptr) { auto message = HistoryService::PreparedText{ text }; message.links.push_back(fromLink); if (!ExtractInviteLink(data).endsWith("...")) { - message.links.push_back(std::make_shared(InternalInviteLinkUrl(data))); + message.links.push_back(std::make_shared( + InternalInviteLinkUrl(data))); } if (additional) { message.links.push_back(std::move(additional)); @@ -1036,8 +1243,9 @@ void GenerateItems( nullptr)); }; - auto createParticipantJoinByInvite = [&](const MTPDchannelAdminLogEventActionParticipantJoinByInvite &data) { - auto text = (channel->isMegagroup() + const auto createParticipantJoinByInvite = [&]( + const LogJoinByInvite &data) { + const auto text = (channel->isMegagroup() ? tr::lng_admin_log_participant_joined_by_link : tr::lng_admin_log_participant_joined_by_link_channel); addInviteLinkServiceMessage( @@ -1050,7 +1258,7 @@ void GenerateItems( data.vinvite()); }; - auto createExportedInviteDelete = [&](const MTPDchannelAdminLogEventActionExportedInviteDelete &data) { + const auto createExportedInviteDelete = [&](const LogInviteDelete &data) { addInviteLinkServiceMessage( tr::lng_admin_log_delete_invite_link( tr::now, @@ -1061,7 +1269,7 @@ void GenerateItems( data.vinvite()); }; - auto createExportedInviteRevoke = [&](const MTPDchannelAdminLogEventActionExportedInviteRevoke &data) { + const auto createExportedInviteRevoke = [&](const LogInviteRevoke &data) { addInviteLinkServiceMessage( tr::lng_admin_log_revoke_invite_link( tr::now, @@ -1072,15 +1280,20 @@ void GenerateItems( data.vinvite()); }; - auto createExportedInviteEdit = [&](const MTPDchannelAdminLogEventActionExportedInviteEdit &data) { + const auto createExportedInviteEdit = [&](const LogInviteEdit &data) { addSimpleTextMessage( - GenerateInviteLinkChangeText(data.vnew_invite(), data.vprev_invite())); + GenerateInviteLinkChangeText( + data.vnew_invite(), + data.vprev_invite())); }; - auto createParticipantVolume = [&](const MTPDchannelAdminLogEventActionParticipantVolume &data) { - const auto participantPeer = groupCallParticipantPeer(data.vparticipant()); + const auto createParticipantVolume = [&](const LogVolume &data) { + const auto participantPeer = groupCallParticipantPeer( + data.vparticipant()); const auto participantPeerLink = participantPeer->createOpenLink(); - const auto participantPeerLinkText = textcmdLink(2, participantPeer->name); + const auto participantPeerLinkText = textcmdLink( + 2, + participantPeer->name); const auto volume = data.vparticipant().match([&]( const MTPDgroupCallParticipant &data) { return data.vvolume().value_or(10000); @@ -1099,7 +1312,7 @@ void GenerateItems( addServiceMessageWithLink(text, participantPeerLink); }; - auto createChangeHistoryTTL = [&](const MTPDchannelAdminLogEventActionChangeHistoryTTL &data) { + const auto createChangeHistoryTTL = [&](const LogTTL &data) { const auto was = data.vprev_value().v; const auto now = data.vnew_value().v; const auto wrap = [](int duration) { @@ -1111,17 +1324,35 @@ void GenerateItems( ? tr::lng_manage_messages_ttl_after2(tr::now) : tr::lng_manage_messages_ttl_after3(tr::now); }; - auto text = !was - ? tr::lng_admin_log_messages_ttl_set(tr::now, lt_from, fromLinkText, lt_duration, wrap(now)) + const auto text = !was + ? tr::lng_admin_log_messages_ttl_set( + tr::now, + lt_from, + fromLinkText, + lt_duration, + wrap(now)) : !now - ? tr::lng_admin_log_messages_ttl_removed(tr::now, lt_from, fromLinkText, lt_duration, wrap(was)) - : tr::lng_admin_log_messages_ttl_changed(tr::now, lt_from, fromLinkText, lt_previous, wrap(was), lt_duration, wrap(now)); + ? tr::lng_admin_log_messages_ttl_removed( + tr::now, + lt_from, + fromLinkText, + lt_duration, + wrap(was)) + : tr::lng_admin_log_messages_ttl_changed( + tr::now, + lt_from, + fromLinkText, + lt_previous, + wrap(was), + lt_duration, + wrap(now)); addSimpleServiceMessage(text); }; - auto createParticipantJoinByRequest = [&](const MTPDchannelAdminLogEventActionParticipantJoinByRequest &data) { + const auto createParticipantJoinByRequest = [&]( + const LogJoinByRequest &data) { const auto user = channel->owner().user(UserId(data.vapproved_by())); - auto text = (channel->isMegagroup() + const auto text = (channel->isMegagroup() ? tr::lng_admin_log_participant_approved_by_link : tr::lng_admin_log_participant_approved_by_link_channel); const auto linkText = GenerateInviteLinkLink(data.vinvite()); @@ -1137,75 +1368,103 @@ void GenerateItems( textcmdLink(adminIndex, user->name)), data.vinvite(), user->createOpenLink()); - }; - action.match([&](const MTPDchannelAdminLogEventActionChangeTitle &data) { + const auto createToggleNoForwards = [&](const LogNoForwards &data) { + const auto disabled = (data.vnew_value().type() == mtpc_boolTrue); + const auto text = (disabled + ? tr::lng_admin_log_forwards_disabled + : tr::lng_admin_log_forwards_enabled); + addSimpleServiceMessage(text(tr::now, lt_from, fromLinkText)); + }; + + const auto createSendMessage = [&](const LogActionSendMessage &data) { + const auto text = tr::lng_admin_log_sent_message( + tr::now, + lt_from, + fromLinkText); + addSimpleServiceMessage(text); + + const auto detachExistingItem = false; + addPart( + history->createItem( + history->nextNonHistoryEntryId(), + PrepareLogMessage(data.vmessage(), date), + MessageFlag::AdminLogEntry, + detachExistingItem), + ExtractSentDate(data.vmessage())); + }; + + action.match([&](const LogTitle &data) { createChangeTitle(data); - }, [&](const MTPDchannelAdminLogEventActionChangeAbout &data) { + }, [&](const LogAbout &data) { createChangeAbout(data); - }, [&](const MTPDchannelAdminLogEventActionChangeUsername &data) { + }, [&](const LogUsername &data) { createChangeUsername(data); - }, [&](const MTPDchannelAdminLogEventActionChangePhoto &data) { + }, [&](const LogPhoto &data) { createChangePhoto(data); - }, [&](const MTPDchannelAdminLogEventActionToggleInvites &data) { + }, [&](const LogInvites &data) { createToggleInvites(data); - }, [&](const MTPDchannelAdminLogEventActionToggleSignatures &data) { + }, [&](const LogSign &data) { createToggleSignatures(data); - }, [&](const MTPDchannelAdminLogEventActionUpdatePinned &data) { + }, [&](const LogPin &data) { createUpdatePinned(data); - }, [&](const MTPDchannelAdminLogEventActionEditMessage &data) { + }, [&](const LogEdit &data) { createEditMessage(data); - }, [&](const MTPDchannelAdminLogEventActionDeleteMessage &data) { + }, [&](const LogDelete &data) { createDeleteMessage(data); - }, [&](const MTPDchannelAdminLogEventActionParticipantJoin &) { + }, [&](const LogJoin &) { createParticipantJoin(); - }, [&](const MTPDchannelAdminLogEventActionParticipantLeave &) { + }, [&](const LogLeave &) { createParticipantLeave(); - }, [&](const MTPDchannelAdminLogEventActionParticipantInvite &data) { + }, [&](const LogInvite &data) { createParticipantInvite(data); - }, [&](const MTPDchannelAdminLogEventActionParticipantToggleBan &data) { + }, [&](const LogBan &data) { createParticipantToggleBan(data); - }, [&](const MTPDchannelAdminLogEventActionParticipantToggleAdmin &data) { + }, [&](const LogPromote &data) { createParticipantToggleAdmin(data); - }, [&](const MTPDchannelAdminLogEventActionChangeStickerSet &data) { + }, [&](const LogSticker &data) { createChangeStickerSet(data); - }, [&](const MTPDchannelAdminLogEventActionTogglePreHistoryHidden &data) { + }, [&](const LogPreHistory &data) { createTogglePreHistoryHidden(data); - }, [&](const MTPDchannelAdminLogEventActionDefaultBannedRights &data) { + }, [&](const LogPermissions &data) { createDefaultBannedRights(data); - }, [&](const MTPDchannelAdminLogEventActionStopPoll &data) { + }, [&](const LogPoll &data) { createStopPoll(data); - }, [&](const MTPDchannelAdminLogEventActionChangeLinkedChat &data) { + }, [&](const LogDiscussion &data) { createChangeLinkedChat(data); - }, [&](const MTPDchannelAdminLogEventActionChangeLocation &data) { + }, [&](const LogLocation &data) { createChangeLocation(data); - }, [&](const MTPDchannelAdminLogEventActionToggleSlowMode &data) { + }, [&](const LogSlowMode &data) { createToggleSlowMode(data); - }, [&](const MTPDchannelAdminLogEventActionStartGroupCall &data) { + }, [&](const LogStartCall &data) { createStartGroupCall(data); - }, [&](const MTPDchannelAdminLogEventActionDiscardGroupCall &data) { + }, [&](const LogDiscardCall &data) { createDiscardGroupCall(data); - }, [&](const MTPDchannelAdminLogEventActionParticipantMute &data) { + }, [&](const LogMute &data) { createParticipantMute(data); - }, [&](const MTPDchannelAdminLogEventActionParticipantUnmute &data) { + }, [&](const LogUnmute &data) { createParticipantUnmute(data); - }, [&](const MTPDchannelAdminLogEventActionToggleGroupCallSetting &data) { + }, [&](const LogCallSetting &data) { createToggleGroupCallSetting(data); - }, [&](const MTPDchannelAdminLogEventActionParticipantJoinByInvite &data) { + }, [&](const LogJoinByInvite &data) { createParticipantJoinByInvite(data); - }, [&](const MTPDchannelAdminLogEventActionExportedInviteDelete &data) { + }, [&](const LogInviteDelete &data) { createExportedInviteDelete(data); - }, [&](const MTPDchannelAdminLogEventActionExportedInviteRevoke &data) { + }, [&](const LogInviteRevoke &data) { createExportedInviteRevoke(data); - }, [&](const MTPDchannelAdminLogEventActionExportedInviteEdit &data) { + }, [&](const LogInviteEdit &data) { createExportedInviteEdit(data); - }, [&](const MTPDchannelAdminLogEventActionParticipantVolume &data) { + }, [&](const LogVolume &data) { createParticipantVolume(data); - }, [&](const MTPDchannelAdminLogEventActionChangeHistoryTTL &data) { + }, [&](const LogTTL &data) { createChangeHistoryTTL(data); - }, [&](const MTPDchannelAdminLogEventActionParticipantJoinByRequest &data) { + }, [&](const LogJoinByRequest &data) { createParticipantJoinByRequest(data); + }, [&](const LogNoForwards &data) { + createToggleNoForwards(data); + }, [&](const LogActionSendMessage &data) { + createSendMessage(data); }); } diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.h b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.h index 791ffd150..fea67d9a2 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.h +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.h @@ -29,8 +29,31 @@ class InnerWidget; class SectionMemento; struct FilterValue { + enum class Flag : uint32 { + Join = (1U << 0), + Leave = (1U << 1), + Invite = (1U << 2), + Ban = (1U << 3), + Unban = (1U << 4), + Kick = (1U << 5), + Unkick = (1U << 6), + Promote = (1U << 7), + Demote = (1U << 8), + Info = (1U << 9), + Settings = (1U << 10), + Pinned = (1U << 11), + Edit = (1U << 12), + Delete = (1U << 13), + GroupCall = (1U << 14), + Invites = (1U << 15), + + MAX_FIELD = (1U << 15), + }; + using Flags = base::flags; + friend inline constexpr bool is_flag_type(Flag) { return true; }; + // Empty "flags" means all events. - MTPDchannelAdminLogEventsFilter::Flags flags = 0; + Flags flags = 0; std::vector> admins; bool allUsers = true; }; diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 46a0a6499..3349855e7 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_histories.h" #include "lang/lang_keys.h" #include "apiwrap.h" +#include "api/api_chat_participants.h" #include "mainwidget.h" #include "mainwindow.h" #include "main/main_session.h" @@ -464,6 +465,20 @@ void History::destroyMessage(not_null item) { } } +void History::destroyMessagesByDates(TimeId minDate, TimeId maxDate) { + auto toDestroy = std::vector>(); + for (const auto &message : _messages) { + if (message->isRegular() + && message->date() > minDate + && message->date() < maxDate) { + toDestroy.push_back(message.get()); + } + } + for (const auto item : toDestroy) { + item->destroy(); + } +} + void History::unpinAllMessages() { session().storage().remove( Storage::SharedMediaRemoveAll( @@ -2579,7 +2594,7 @@ void History::applyDialog( data.vtop_message().v); if (const auto item = owner().message(topMessageId)) { if (item->date() <= channel->date) { - session().api().requestSelfParticipant(channel); + session().api().chatParticipants().requestSelf(channel); } } } @@ -3086,13 +3101,13 @@ bool History::removeOrphanMediaGroupPart() { return false; } -QVector History::collectMessagesFromUserToDelete( - not_null user) const { - auto result = QVector(); +std::vector History::collectMessagesFromParticipantToDelete( + not_null participant) const { + auto result = std::vector(); for (const auto &block : blocks) { for (const auto &message : block->messages) { const auto item = message->data(); - if (item->from() == user && item->canDelete()) { + if (item->from() == participant && item->canDelete()) { result.push_back(item->id); } } diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index 2625a79e7..4ed26c193 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -109,8 +109,8 @@ public: Element *findLastDisplayed() const; bool hasOrphanMediaGroupPart() const; bool removeOrphanMediaGroupPart(); - QVector collectMessagesFromUserToDelete( - not_null user) const; + [[nodiscard]] std::vector collectMessagesFromParticipantToDelete( + not_null participant) const; enum class ClearType { Unload, @@ -140,6 +140,7 @@ public: std::forward(args)...)).get()); } void destroyMessage(not_null item); + void destroyMessagesByDates(TimeId minDate, TimeId maxDate); void unpinAllMessages(); diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index f56c9d545..122450776 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -26,7 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/chat_style.h" #include "ui/widgets/popup_menu.h" #include "ui/image/image.h" -#include "ui/toast/toast.h" +#include "ui/toasts/common_toasts.h" #include "ui/effects/path_shift_gradient.h" #include "ui/text/text_options.h" #include "ui/boxes/report_box.h" @@ -67,6 +67,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_poll.h" #include "data/data_photo.h" #include "data/data_photo_media.h" +#include "data/data_peer_values.h" +#include "data/data_chat.h" #include "data/data_user.h" #include "data/data_file_click_handler.h" #include "data/data_file_origin.h" @@ -259,12 +261,64 @@ HistoryInner::HistoryInner( ) | rpl::start_with_next([=](int d) { _scroll->scrollToY(_scroll->scrollTop() + d); }, _scroll->lifetime()); + + setupSharingDisallowed(); } Main::Session &HistoryInner::session() const { return _controller->session(); } +void HistoryInner::setupSharingDisallowed() { + Expects(_peer != nullptr); + + if (_peer->isUser()) { + _sharingDisallowed = false; + return; + } + const auto chat = _peer->asChat(); + const auto channel = _peer->asChannel(); + _sharingDisallowed = chat + ? Data::PeerFlagValue(chat, ChatDataFlag::NoForwards) + : Data::PeerFlagValue( + channel, + ChannelDataFlag::NoForwards + ) | rpl::type_erased(); + + auto rights = chat + ? chat->adminRightsValue() + : channel->adminRightsValue(); + auto canDelete = std::move( + rights + ) | rpl::map([=] { + return chat + ? chat->canDeleteMessages() + : channel->canDeleteMessages(); + }); + rpl::combine( + _sharingDisallowed.value(), + std::move(canDelete) + ) | rpl::filter([=](bool disallowed, bool canDelete) { + return hasSelectRestriction() && !getSelectedItems().empty(); + }) | rpl::start_with_next([=] { + _widget->clearSelected(); + if (_mouseAction == MouseAction::PrepareSelect) { + mouseActionCancel(); + } + }, lifetime()); +} + +bool HistoryInner::hasSelectRestriction() const { + if (!_sharingDisallowed.current()) { + return false; + } else if (const auto chat = _peer->asChat()) { + return !chat->canDeleteMessages(); + } else if (const auto channel = _peer->asChannel()) { + return !channel->canDeleteMessages(); + } + return true; +} + void HistoryInner::messagesReceived( PeerData *peer, const QVector &messages) { @@ -1217,12 +1271,12 @@ void HistoryInner::mouseActionStart(const QPoint &screenPos, Qt::MouseButton but _selected.emplace(_mouseActionItem, selStatus); _mouseAction = MouseAction::Selecting; repaintItem(_mouseActionItem); - } else { + } else if (!hasSelectRestriction()) { _mouseAction = MouseAction::PrepareSelect; } } } - } else if (!_pressWasInactive) { + } else if (!_pressWasInactive && !hasSelectRestriction()) { _mouseAction = MouseAction::PrepareSelect; // start items select } } @@ -1251,7 +1305,8 @@ std::unique_ptr HistoryInner::prepareDrag() { } const auto pressedHandler = ClickHandler::getPressed(); - if (dynamic_cast(pressedHandler.get())) { + if (dynamic_cast(pressedHandler.get()) + || hasCopyRestriction()) { return nullptr; } @@ -1282,7 +1337,6 @@ std::unique_ptr HistoryInner::prepareDrag() { } } } - auto urls = QList(); const auto selectedText = [&] { if (uponSelected) { @@ -1495,7 +1549,8 @@ void HistoryInner::mouseActionFinish( if (QGuiApplication::clipboard()->supportsSelection() && !_selected.empty() - && _selected.cbegin()->second != FullSelection) { + && _selected.cbegin()->second != FullSelection + && !hasCopyRestriction(_selected.cbegin()->first)) { const auto [item, selection] = *_selected.cbegin(); if (const auto view = item->mainView()) { TextUtilities::SetClipboardText( @@ -1683,14 +1738,15 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { }); } }; - const auto addPhotoActions = [&](not_null photo) { + const auto addPhotoActions = [&](not_null photo, HistoryItem *item) { const auto media = photo->activeMediaView(); - if (!photo->isNull() && media && media->loaded()) { + const auto itemId = item ? item->fullId() : FullMsgId(); + if (!photo->isNull() && media && media->loaded() && !hasCopyRestriction(item)) { _menu->addAction(tr::lng_context_save_image(tr::now), App::LambdaDelayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [=] { savePhotoToFile(photo); })); _menu->addAction(tr::lng_context_copy_image(tr::now), [=] { - copyContextImage(photo); + copyContextImage(photo, itemId); }); } if (photo->hasAttachedStickers()) { @@ -1701,14 +1757,13 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { }); } }; - const auto addDocumentActions = [&](not_null document) { + const auto addDocumentActions = [&](not_null document, HistoryItem *item) { if (document->loading()) { _menu->addAction(tr::lng_context_cancel_download(tr::now), [=] { cancelContextDownload(document); }); return; } - const auto item = _dragStateItem; const auto itemId = item ? item->fullId() : FullMsgId(); const auto lnkIsVideo = document->isVideoFile(); const auto lnkIsVoice = document->isVoiceMessage(); @@ -1727,18 +1782,22 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { openContextGif(itemId); }); } - _menu->addAction(tr::lng_context_save_gif(tr::now), [=] { - saveContextGif(itemId); - }); + if (!hasCopyRestriction(item)) { + _menu->addAction(tr::lng_context_save_gif(tr::now), [=] { + saveContextGif(itemId); + }); + } } if (!document->filepath(true).isEmpty()) { _menu->addAction(Platform::IsMac() ? tr::lng_context_show_in_finder(tr::now) : tr::lng_context_show_in_folder(tr::now), [=] { showContextInFolder(document); }); } - _menu->addAction(lnkIsVideo ? tr::lng_context_save_video(tr::now) : (lnkIsVoice ? tr::lng_context_save_audio(tr::now) : (lnkIsAudio ? tr::lng_context_save_audio_file(tr::now) : tr::lng_context_save_file(tr::now))), App::LambdaDelayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [=] { - saveDocumentToFile(itemId, document); - })); + if (!hasCopyRestriction(item)) { + _menu->addAction(lnkIsVideo ? tr::lng_context_save_video(tr::now) : (lnkIsVoice ? tr::lng_context_save_audio(tr::now) : (lnkIsAudio ? tr::lng_context_save_audio_file(tr::now) : tr::lng_context_save_file(tr::now))), App::LambdaDelayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [=] { + saveDocumentToFile(itemId, document); + })); + } if (document->hasAttachedStickers()) { _menu->addAction(tr::lng_context_attached_stickers(tr::now), [=] { session->api().attachedStickers().requestAttachedStickerSets( @@ -1748,6 +1807,29 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { } }; + const auto addSelectMessageAction = [&]( + not_null item, + bool asGroup = true) { + if (item->isRegular() + && !item->isService() + && !hasSelectRestriction()) { + const auto itemId = item->fullId(); + _menu->addAction(tr::lng_context_select_msg(tr::now), [=] { + if (const auto item = session->data().message(itemId)) { + if (const auto view = item->mainView()) { + if (asGroup) { + changeSelectionAsGroup(&_selected, item, SelectAction::Select); + } else { + changeSelection(&_selected, item, SelectAction::Select); + } + repaintItem(item); + _widget->updateTopBarSelection(); + } + } + }); + } + }; + if (hasWhoReadItem) { const auto participantChosen = [=](uint64 id) { controller->showPeerInfo(PeerId(id)); @@ -1766,7 +1848,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { if (lnkPhoto || lnkDocument) { const auto item = _dragStateItem; const auto itemId = item ? item->fullId() : FullMsgId(); - if (isUponSelected > 0) { + if (isUponSelected > 0 && !hasCopyRestrictionForSelected()) { _menu->addAction( (isUponSelected > 1 ? tr::lng_context_copy_selected_items(tr::now) @@ -1775,9 +1857,9 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { } addItemActions(item, item); if (lnkPhoto) { - addPhotoActions(lnkPhoto->photo()); + addPhotoActions(lnkPhoto->photo(), item); } else { - addDocumentActions(lnkDocument->document()); + addDocumentActions(lnkDocument->document(), item); } if (item && item->hasDirectLink() && isUponSelected != 2 && isUponSelected != -2) { _menu->addAction(item->history()->peer->isMegagroup() ? tr::lng_context_copy_message_link(tr::now) : tr::lng_context_copy_post_link(tr::now), [=] { @@ -1820,17 +1902,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { }); } } - if (item->isRegular() && !item->isService()) { - _menu->addAction(tr::lng_context_select_msg(tr::now), [=] { - if (const auto item = session->data().message(itemId)) { - if (const auto view = item->mainView()) { - changeSelection(&_selected, item, SelectAction::Select); - repaintItem(item); - _widget->updateTopBarSelection(); - } - } - }); - } + addSelectMessageAction(item, false); if (isUponSelected != -2 && blockSender) { _menu->addAction(tr::lng_profile_block_user(tr::now), [=] { blockSenderItem(itemId); @@ -1857,11 +1929,13 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { const auto view = item ? item->mainView() : nullptr; if (isUponSelected > 0) { - _menu->addAction( - ((isUponSelected > 1) - ? tr::lng_context_copy_selected_items(tr::now) - : tr::lng_context_copy_selected(tr::now)), - [=] { copySelectedText(); }); + if (!hasCopyRestrictionForSelected()) { + _menu->addAction( + ((isUponSelected > 1) + ? tr::lng_context_copy_selected_items(tr::now) + : tr::lng_context_copy_selected(tr::now)), + [=] { copySelectedText(); }); + } addItemActions(item, item); } else { addItemActions(item, albumPartItem); @@ -1878,9 +1952,11 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { Api::ToggleFavedSticker(document, itemId); }); } - _menu->addAction(tr::lng_context_save_image(tr::now), App::LambdaDelayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [=] { - saveDocumentToFile(itemId, document); - })); + if (!hasCopyRestriction(item)) { + _menu->addAction(tr::lng_context_save_image(tr::now), App::LambdaDelayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [=] { + saveDocumentToFile(itemId, document); + })); + } } } if (const auto media = item->media()) { @@ -1905,6 +1981,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { if (!item->isService() && view && !link + && !hasCopyRestriction(item) && (view->hasVisibleText() || mediaHasTextForCopy)) { _menu->addAction(tr::lng_context_copy_text(tr::now), [=] { copyContextText(itemId); @@ -1968,37 +2045,14 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { }); } } - if (item->isRegular() && !item->isService()) { - _menu->addAction(tr::lng_context_select_msg(tr::now), [=] { - if (const auto item = session->data().message(itemId)) { - if (const auto view = item->mainView()) { - changeSelectionAsGroup(&_selected, item, SelectAction::Select); - repaintItem(view); - _widget->updateTopBarSelection(); - } - } - }); - } + addSelectMessageAction(item); if (isUponSelected != -2 && canBlockSender) { _menu->addAction(tr::lng_profile_block_user(tr::now), [=] { blockSenderAsGroup(itemId); }); } - } else { - if (App::mousedItem() - && App::mousedItem()->data()->isRegular() - && !App::mousedItem()->data()->isService()) { - const auto itemId = App::mousedItem()->data()->fullId(); - _menu->addAction(tr::lng_context_select_msg(tr::now), [=] { - if (const auto item = session->data().message(itemId)) { - if (const auto view = item->mainView()) { - changeSelectionAsGroup(&_selected, item, SelectAction::Select); - repaintItem(item); - _widget->updateTopBarSelection(); - } - } - }); - } + } else if (App::mousedItem()) { + addSelectMessageAction(App::mousedItem()->data()); } } @@ -2010,8 +2064,47 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { } } +bool HistoryInner::hasCopyRestriction(HistoryItem *item) const { + return !_peer->allowsForwarding() || (item && item->forbidsForward()); +} + +bool HistoryInner::showCopyRestriction(HistoryItem *item) { + if (!hasCopyRestriction(item)) { + return false; + } + Ui::ShowMultilineToast({ + .text = { _peer->isBroadcast() + ? tr::lng_error_nocopy_channel(tr::now) + : tr::lng_error_nocopy_group(tr::now) }, + }); + return true; +} + +bool HistoryInner::hasCopyRestrictionForSelected() const { + if (hasCopyRestriction()) { + return true; + } + for (const auto &[item, selection] : _selected) { + if (item && item->forbidsForward()) { + return true; + } + } + return false; +} + +bool HistoryInner::showCopyRestrictionForSelected() { + for (const auto &[item, selection] : _selected) { + if (showCopyRestriction(item)) { + return true; + } + } + return false; +} + void HistoryInner::copySelectedText() { - TextUtilities::SetClipboardText(getSelectedText()); + if (!showCopyRestrictionForSelected()) { + TextUtilities::SetClipboardText(getSelectedText()); + } } void HistoryInner::savePhotoToFile(not_null photo) { @@ -2036,14 +2129,17 @@ void HistoryInner::savePhotoToFile(not_null photo) { })); } -void HistoryInner::copyContextImage(not_null photo) { +void HistoryInner::copyContextImage( + not_null photo, + FullMsgId itemId) { + const auto item = session().data().message(itemId); const auto media = photo->activeMediaView(); if (photo->isNull() || !media || !media->loaded()) { return; + } else if (!showCopyRestriction(item)) { + const auto image = media->image(Data::PhotoSize::Large)->original(); + QGuiApplication::clipboard()->setImage(image); } - - const auto image = media->image(Data::PhotoSize::Large)->original(); - QGuiApplication::clipboard()->setImage(image); } void HistoryInner::showStickerPackInfo(not_null document) { @@ -2082,9 +2178,11 @@ void HistoryInner::openContextGif(FullMsgId itemId) { void HistoryInner::saveContextGif(FullMsgId itemId) { if (const auto item = session().data().message(itemId)) { - if (const auto media = item->media()) { - if (const auto document = media->document()) { - Api::ToggleSavedGif(document, item->fullId(), true); + if (!hasCopyRestriction(item)) { + if (const auto media = item->media()) { + if (const auto document = media->document()) { + Api::ToggleSavedGif(document, item->fullId(), true); + } } } } @@ -2092,10 +2190,12 @@ void HistoryInner::saveContextGif(FullMsgId itemId) { void HistoryInner::copyContextText(FullMsgId itemId) { if (const auto item = session().data().message(itemId)) { - if (const auto group = session().data().groups().find(item)) { - TextUtilities::SetClipboardText(HistoryGroupText(group)); - } else { - TextUtilities::SetClipboardText(HistoryItemText(item)); + if (!showCopyRestriction(item)) { + if (const auto group = session().data().groups().find(item)) { + TextUtilities::SetClipboardText(HistoryGroupText(group)); + } else { + TextUtilities::SetClipboardText(HistoryItemText(item)); + } } } } @@ -2187,7 +2287,8 @@ void HistoryInner::keyPressEvent(QKeyEvent *e) { copySelectedText(); #ifdef Q_OS_MAC } else if (e->key() == Qt::Key_E - && e->modifiers().testFlag(Qt::ControlModifier)) { + && e->modifiers().testFlag(Qt::ControlModifier) + && !showCopyRestrictionForSelected()) { TextUtilities::SetClipboardText(getSelectedText(), QClipboard::FindBuffer); #endif // Q_OS_MAC } else if (e == QKeySequence::Delete) { @@ -2809,18 +2910,6 @@ MessageIdsList HistoryInner::getSelectedItems() const { return result; } -void HistoryInner::selectItem(not_null item) { - if (!_selected.empty() && _selected.cbegin()->second != FullSelection) { - _selected.clear(); - } else if (_selected.size() == MaxSelectedItems - && _selected.find(item) == _selected.cend()) { - return; - } - _selected.emplace(item, FullSelection); - _widget->updateTopBarSelection(); - _widget->update(); -} - void HistoryInner::onTouchSelect() { _touchSelect = true; mouseActionStart(_touchPos, Qt::LeftButton); @@ -3107,6 +3196,9 @@ void HistoryInner::mouseActionUpdate() { void HistoryInner::updateDragSelection(Element *dragSelFrom, Element *dragSelTo, bool dragSelecting) { if (_dragSelFrom == dragSelFrom && _dragSelTo == dragSelTo && _dragSelecting == dragSelecting) { return; + } else if (dragSelFrom && hasSelectRestriction()) { + updateDragSelection(nullptr, nullptr, false); + return; } _dragSelFrom = dragSelFrom; _dragSelTo = dragSelTo; @@ -3240,7 +3332,9 @@ void HistoryInner::notifyMigrateUpdated() { } void HistoryInner::applyDragSelection() { - applyDragSelection(&_selected); + if (!hasSelectRestriction()) { + applyDragSelection(&_selected); + } } bool HistoryInner::isSelected( diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index d55f5308e..8e0302223 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -85,7 +85,6 @@ public: HistoryView::TopBarWidget::SelectedState getSelectionState() const; void clearSelected(bool onlyTextSelection = false); MessageIdsList getSelectedItems() const; - void selectItem(not_null item); bool inSelectionMode() const; bool elementIntersectsRange( not_null view, @@ -266,7 +265,7 @@ private: void saveDocumentToFile( FullMsgId contextId, not_null document); - void copyContextImage(not_null photo); + void copyContextImage(not_null photo, FullMsgId itemId); void showStickerPackInfo(not_null document); void itemRemoved(not_null item); @@ -343,6 +342,13 @@ private: void blockSenderAsGroup(FullMsgId itemId); void copySelectedText(); + void setupSharingDisallowed(); + [[nodiscard]] bool hasCopyRestriction(HistoryItem *item = nullptr) const; + bool showCopyRestriction(HistoryItem *item = nullptr); + [[nodiscard]] bool hasCopyRestrictionForSelected() const; + bool showCopyRestrictionForSelected(); + [[nodiscard]] bool hasSelectRestriction() const; + // Does any of the shown histories has this flag set. bool hasPendingResizedItems() const; @@ -415,6 +421,8 @@ private: Ui::SelectScrollManager _selectScroll; + rpl::variable _sharingDisallowed = false; + Ui::TouchScrollState _touchScrollState = Ui::TouchScrollState::Manual; bool _touchPrevPosValid = false; bool _touchWaitingAcceleration = false; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 000eab4bd..5449431e6 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -646,6 +646,10 @@ bool HistoryItem::canStopPoll() const { return canBeEdited() && isRegular(); } +bool HistoryItem::forbidsForward() const { + return (_flags & MessageFlag::NoForwards); +} + bool HistoryItem::canDelete() const { if (isSponsored()) { return false; @@ -724,10 +728,7 @@ bool HistoryItem::suggestReport() const { bool HistoryItem::suggestBanReport() const { const auto channel = history()->peer->asChannel(); - const auto fromUser = from()->asUser(); - if (!channel - || !fromUser - || !channel->canRestrictParticipant(fromUser)) { + if (!channel || !channel->canRestrictParticipant(from())) { return false; } return !isPost() && !out(); @@ -738,7 +739,7 @@ bool HistoryItem::suggestDeleteAllReport() const { if (!channel || !channel->canDeleteMessages()) { return false; } - return !isPost() && !out() && from()->isUser(); + return !isPost() && !out(); } bool HistoryItem::hasDirectLink() const { @@ -975,22 +976,29 @@ ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const { } return {}; }(); + const auto fromSender = [](not_null sender) { + return sender->isSelf() + ? tr::lng_from_you(tr::now) + : sender->shortName(); + }; + const auto fromForwarded = [&]() -> std::optional { + if (const auto forwarded = Get()) { + return forwarded->originalSender + ? fromSender(forwarded->originalSender) + : forwarded->hiddenSenderInfo->name; + } + return {}; + }; const auto sender = [&]() -> std::optional { - const auto fromSender = [](not_null sender) { - return sender->isSelf() - ? tr::lng_from_you(tr::now) - : sender->shortName(); - }; if (options.hideSender || isPost() || isEmpty()) { return {}; - } else if (!_history->peer->isUser() || _history->peer->isSelf()) { - if (const auto forwarded = Get()) { - return forwarded->originalSender - ? fromSender(forwarded->originalSender) - : forwarded->hiddenSenderInfo->name; - } else if (!_history->peer->isUser()) { - return fromSender(displayFrom()); + } else if (!_history->peer->isUser()) { + if (const auto from = displayFrom()) { + return fromSender(from); } + return fromForwarded(); + } else if (_history->peer->isSelf()) { + return fromForwarded(); } return {}; }(); @@ -1082,7 +1090,8 @@ MessageFlags FlagsFromMTP( | ((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()); + | ((flags & MTP::f_views) ? Flag::HasViews : Flag()) + | ((flags & MTP::f_noforwards) ? Flag::NoForwards : Flag()); } MessageFlags FlagsFromMTP( diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 585be3048..82c1c996c 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -343,6 +343,10 @@ public: [[nodiscard]] virtual TextWithEntities originalText() const { return TextWithEntities(); } + [[nodiscard]] virtual auto originalTextWithLocalEntities() const + -> TextWithEntities { + return TextWithEntities(); + } [[nodiscard]] virtual TextForMimeData clipboardText() const { return TextForMimeData(); } @@ -375,6 +379,7 @@ public: [[nodiscard]] bool canPin() const; [[nodiscard]] bool canBeEdited() const; [[nodiscard]] bool canStopPoll() const; + [[nodiscard]] bool forbidsForward() const; [[nodiscard]] virtual bool allowsSendNow() const; [[nodiscard]] virtual bool allowsForward() const; [[nodiscard]] virtual bool allowsEdit(TimeId now) const; diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 76cb10439..8a3502ba2 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "data/data_file_origin.h" #include "data/data_document.h" +#include "data/data_web_page.h" #include "data/data_file_click_handler.h" #include "main/main_session.h" #include "window/window_session_controller.h" @@ -135,6 +136,8 @@ HiddenSenderInfo::HiddenSenderInfo(const QString &name, bool external) (external ? Ui::EmptyUserpic::ExternalName() : name)) { + Expects(!name.isEmpty()); + nameText.setText(st::msgNameStyle, name, Ui::NameTextOptions()); const auto parts = name.trimmed().split(' ', Qt::SkipEmptyParts); firstName = parts[0]; @@ -225,7 +228,7 @@ void HistoryMessageForwarded::create(const HistoryMessageVia *via) const { bool HistoryMessageReply::updateData( not_null holder, bool force) { - const auto guard = gsl::finally([&] { refreshReplyToDocument(); }); + const auto guard = gsl::finally([&] { refreshReplyToMedia(); }); if (!force) { if (replyToMsg || !replyToMsgId) { return true; @@ -292,7 +295,7 @@ void HistoryMessageReply::clearData(not_null holder) { replyToMsg = nullptr; } replyToMsgId = 0; - refreshReplyToDocument(); + refreshReplyToMedia(); } bool HistoryMessageReply::isNameUpdated() const { @@ -417,11 +420,14 @@ void HistoryMessageReply::paint( } } -void HistoryMessageReply::refreshReplyToDocument() { +void HistoryMessageReply::refreshReplyToMedia() { replyToDocumentId = 0; + replyToWebPageId = 0; if (const auto media = replyToMsg ? replyToMsg->media() : nullptr) { if (const auto document = media->document()) { replyToDocumentId = document->id; + } else if (const auto webpage = media->webpage()) { + replyToWebPageId = webpage->id; } } } diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index b41504da2..880b10639 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -130,6 +130,7 @@ struct HistoryMessageReply : public RuntimeComponent holder); - void refreshReplyToDocument(); + void refreshReplyToMedia(); PeerId replyToPeerId = 0; MsgId replyToMsgId = 0; MsgId replyToMsgTop = 0; HistoryItem *replyToMsg = nullptr; DocumentId replyToDocumentId = 0; + WebPageId replyToWebPageId = 0; ClickHandlerPtr replyToLnk; mutable Ui::Text::String replyToName, replyToText; mutable int replyToVersion = 0; diff --git a/Telegram/SourceFiles/history/history_item_reply_markup.cpp b/Telegram/SourceFiles/history/history_item_reply_markup.cpp index 9950b2696..230538d92 100644 --- a/Telegram/SourceFiles/history/history_item_reply_markup.cpp +++ b/Telegram/SourceFiles/history/history_item_reply_markup.cpp @@ -95,9 +95,6 @@ void HistoryMessageMarkupData::fillRows( qba(data.vurl()), qs(data.vfwd_text().value_or_empty()), data.vbutton_id().v); - }, [&](const MTPDinputKeyboardButtonUrlAuth &data) { - LOG(("API Error: inputKeyboardButtonUrlAuth received.")); - // Should not get those for the users. }, [&](const MTPDkeyboardButtonRequestPoll &data) { const auto quiz = [&] { if (!data.vquiz()) { @@ -113,6 +110,17 @@ void HistoryMessageMarkupData::fillRows( Type::RequestPoll, qs(data.vtext()), quiz); + }, [&](const MTPDkeyboardButtonUserProfile &data) { + row.emplace_back( + Type::UserProfile, + qs(data.vtext()), + QByteArray::number(data.vuser_id().v)); + }, [&](const MTPDinputKeyboardButtonUrlAuth &data) { + LOG(("API Error: inputKeyboardButtonUrlAuth.")); + // Should not get those for the users. + }, [&](const MTPDinputKeyboardButtonUserProfile &data) { + LOG(("API Error: inputKeyboardButtonUserProfile.")); + // Should not get those for the users. }); } if (!row.empty()) { diff --git a/Telegram/SourceFiles/history/history_item_reply_markup.h b/Telegram/SourceFiles/history/history_item_reply_markup.h index 8418bc50f..c857b5953 100644 --- a/Telegram/SourceFiles/history/history_item_reply_markup.h +++ b/Telegram/SourceFiles/history/history_item_reply_markup.h @@ -40,6 +40,7 @@ struct HistoryMessageMarkupButton { Game, Buy, Auth, + UserProfile, }; HistoryMessageMarkupButton( diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index c960a9d98..5f90e6aa9 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -46,6 +46,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "data/data_user.h" #include "data/data_histories.h" +#include "data/data_web_page.h" #include "styles/style_dialogs.h" #include "styles/style_widgets.h" #include "styles/style_chat.h" @@ -297,9 +298,9 @@ void FastShareMessage(not_null item) { for (const auto peer : result) { const auto history = owner->history(peer); if (!comment.text.isEmpty()) { - auto message = ApiWrap::MessageToSend(history); + auto message = Api::MessageToSend( + Api::SendAction(history, options)); message.textWithTags = comment; - message.action.options = options; message.action.clearDraft = false; api.sendMessage(std::move(message)); } @@ -315,7 +316,8 @@ void FastShareMessage(not_null item) { MTP_vector(msgIds), MTP_vector(generateRandom()), peer->input, - MTP_int(options.scheduled) + MTP_int(options.scheduled), + MTP_inputPeerEmpty() // send_as )).done([=](const MTPUpdates &updates, mtpRequestId requestId) { history->session().api().applyUpdates(updates); data->requests.remove(requestId); @@ -324,7 +326,7 @@ void FastShareMessage(not_null item) { Ui::hideLayer(); } finish(); - }).fail([=](const MTP::Error &error) { + }).fail([=] { finish(); }).afterRequest(history->sendRequestId).send(); return history->sendRequestId; @@ -1018,9 +1020,11 @@ void HistoryMessage::setCommentsItemId(FullMsgId id) { bool HistoryMessage::updateDependencyItem() { if (const auto reply = Get()) { const auto documentId = reply->replyToDocumentId; + const auto webpageId = reply->replyToWebPageId; const auto result = reply->updateData(this, true); - if (documentId != reply->replyToDocumentId - && generateLocalEntitiesByReply()) { + const auto mediaIdChanged = (documentId != reply->replyToDocumentId) + || (webpageId != reply->replyToWebPageId); + if (mediaIdChanged && generateLocalEntitiesByReply()) { reapplyText(); } return result; @@ -1052,7 +1056,10 @@ void HistoryMessage::applySentMessage( } bool HistoryMessage::allowsForward() const { - return isRegular() && (!_media || _media->allowsForward()); + return isRegular() + && !forbidsForward() + && history()->peer->allowsForwarding() + && (!_media || _media->allowsForward()); } bool HistoryMessage::allowsSendNow() const { @@ -1187,8 +1194,13 @@ void HistoryMessage::setupForwardedComponent(const CreateConfig &config) { return; } forwarded->originalDate = config.originalDate; - forwarded->originalSender = config.senderOriginal - ? history()->owner().peer(config.senderOriginal).get() + const auto originalSender = config.senderOriginal + ? config.senderOriginal + : !config.senderNameOriginal.isEmpty() + ? PeerId() + : from()->id; + forwarded->originalSender = originalSender + ? history()->owner().peer(originalSender).get() : nullptr; if (!forwarded->originalSender) { forwarded->hiddenSenderInfo = std::make_unique( @@ -1542,34 +1554,62 @@ Storage::SharedMediaTypesMask HistoryMessage::sharedMediaTypes() const { } bool HistoryMessage::generateLocalEntitiesByReply() const { - return !_media || _media->webpage(); + using namespace HistoryView; + if (!_media) { + return true; + } else if (const auto document = _media->document()) { + return !DurationForTimestampLinks(document); + } else if (const auto webpage = _media->webpage()) { + return (webpage->type != WebPageType::Video) + && !DurationForTimestampLinks(webpage); + } + return true; } TextWithEntities HistoryMessage::withLocalEntities( const TextWithEntities &textWithEntities) const { + using namespace HistoryView; if (!generateLocalEntitiesByReply()) { + if (!_media) { + } else if (const auto document = _media->document()) { + if (const auto duration = DurationForTimestampLinks(document)) { + return AddTimestampLinks( + textWithEntities, + duration, + TimestampLinkBase(document, fullId())); + } + } else if (const auto webpage = _media->webpage()) { + if (const auto duration = DurationForTimestampLinks(webpage)) { + return AddTimestampLinks( + textWithEntities, + duration, + TimestampLinkBase(webpage, fullId())); + } + } return textWithEntities; } if (const auto reply = Get()) { const auto document = reply->replyToDocumentId ? history()->owner().document(reply->replyToDocumentId).get() : nullptr; - if (document - && (document->isVideoFile() - || document->isSong() - || document->isVoiceMessage())) { - using namespace HistoryView; - const auto duration = document->getDuration(); - const auto base = (duration > 0) - ? DocumentTimestampLinkBase( - document, - reply->replyToMsg->fullId()) - : QString(); - if (!base.isEmpty()) { + const auto webpage = reply->replyToWebPageId + ? history()->owner().webpage(reply->replyToWebPageId).get() + : nullptr; + if (document) { + if (const auto duration = DurationForTimestampLinks(document)) { + const auto context = reply->replyToMsg->fullId(); return AddTimestampLinks( textWithEntities, duration, - base); + TimestampLinkBase(document, context)); + } + } else if (webpage) { + if (const auto duration = DurationForTimestampLinks(webpage)) { + const auto context = reply->replyToMsg->fullId(); + return AddTimestampLinks( + textWithEntities, + duration, + TimestampLinkBase(webpage, context)); } } } @@ -1702,6 +1742,10 @@ TextWithEntities HistoryMessage::originalText() const { return _text.toTextWithEntities(); } +TextWithEntities HistoryMessage::originalTextWithLocalEntities() const { + return withLocalEntities(originalText()); +} + TextForMimeData HistoryMessage::clipboardText() const { if (emptyText()) { return TextForMimeData(); diff --git a/Telegram/SourceFiles/history/history_message.h b/Telegram/SourceFiles/history/history_message.h index 3cf34ead0..ff12ca5b5 100644 --- a/Telegram/SourceFiles/history/history_message.h +++ b/Telegram/SourceFiles/history/history_message.h @@ -175,6 +175,8 @@ public: void setText(const TextWithEntities &textWithEntities) override; [[nodiscard]] Ui::Text::IsolatedEmoji isolatedEmoji() const override; [[nodiscard]] TextWithEntities originalText() const override; + [[nodiscard]] auto originalTextWithLocalEntities() const + -> TextWithEntities override; [[nodiscard]] TextForMimeData clipboardText() const override; [[nodiscard]] bool textHasLinks() const override; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 820d657f3..5b3313c73 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "kotato/json_settings.h" #include "api/api_editing.h" #include "api/api_bot.h" +#include "api/api_chat_participants.h" #include "api/api_sending.h" #include "api/api_text_entities.h" #include "api/api_send_progress.h" @@ -40,10 +41,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL //#include "ui/chat/forward_options_box.h" #include "ui/chat/message_bar.h" #include "ui/chat/attach/attach_send_files_way.h" +#include "ui/chat/choose_send_as.h" #include "ui/image/image.h" #include "ui/special_buttons.h" #include "ui/controls/emoji_button.h" #include "ui/controls/send_button.h" +#include "ui/controls/send_as_button.h" #include "inline_bots/inline_bot_result.h" #include "base/event_filter.h" #include "base/qt_signal_producer.h" @@ -124,6 +127,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "main/main_session_settings.h" #include "main/main_account.h" +#include "main/session/send_as_peers.h" #include "window/notifications_manager.h" #include "window/window_adaptive.h" #include "window/window_controller.h" @@ -789,11 +793,13 @@ HistoryWidget::HistoryWidget( } }, lifetime()); - subscribe(Media::Player::instance()->switchToNextNotifier(), [this](const Media::Player::Instance::Switch &pair) { - if (pair.from.type() == AudioMsgId::Type::Voice) { - scrollToCurrentVoiceMessage(pair.from.contextId(), pair.to); - } - }); + using MediaSwitch = Media::Player::Instance::Switch; + Media::Player::instance()->switchToNextEvents( + ) | rpl::filter([=](const MediaSwitch &pair) { + return (pair.from.type() == AudioMsgId::Type::Voice); + }) | rpl::start_with_next([=](const MediaSwitch &pair) { + scrollToCurrentVoiceMessage(pair.from.contextId(), pair.to); + }, lifetime()); using PeerUpdateFlag = Data::PeerUpdate::Flag; session().changes().peerUpdates( @@ -949,6 +955,7 @@ HistoryWidget::HistoryWidget( }, lifetime()); setupScheduledToggle(); + setupSendAsToggle(); orderWidgets(); setupShortcuts(); } @@ -1044,9 +1051,7 @@ void HistoryWidget::initVoiceRecordBar() { return; } - auto action = Api::SendAction(_history); - action.replyTo = replyToId(); - action.options = data.options; + auto action = prepareSendAction(data.options); session().api().sendVoiceMessage( data.bytes, data.waveform, @@ -1200,7 +1205,9 @@ void HistoryWidget::supportShareContact(Support::Contact contact) { if (!history) { return; } - auto options = Api::SendOptions(); + auto options = Api::SendOptions{ + .sendAs = prepareSendAction({}).options.sendAs, + }; auto action = Api::SendAction(history); send(options); options.handleSupportSwitch = Support::HandleSwitch(modifiers); @@ -1476,7 +1483,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) { sendBotCommand({ _peer, str, FullMsgId(), replyToId() }); - session().api().finishForwarding(Api::SendAction(_history)); + session().api().finishForwarding(prepareSendAction({})); setFieldText(_field->getTextWithTagsPart(_field->textCursor().position())); } else { _field->insertTag(str); @@ -1932,7 +1939,7 @@ void HistoryWidget::setupShortcuts() { return true; }); request->check(Command::JumpToDate, 1) && request->handle([=] { - controller()->showJumpToDate(Dialogs::Key(_history), QDate()); + controller()->showCalendar(Dialogs::Key(_history), QDate()); return true; }); } @@ -2318,6 +2325,7 @@ void HistoryWidget::showHistory( updateNotifyControls(); } refreshScheduledToggle(); + refreshSendAsToggle(); if (_showAtMsgId == ShowAtUnreadMsgId) { if (_history->scrollTopItem) { @@ -2407,8 +2415,6 @@ void HistoryWidget::showHistory( refreshTopBarActiveChat(); updateTopBarSelection(); checkMessagesTTL(); - // Restore default theme. - controller()->setChatStyleTheme(controller()->defaultChatTheme()); clearFieldText(); doneShow(); } @@ -2473,6 +2479,9 @@ void HistoryWidget::registerDraftSource() { void HistoryWidget::setEditMsgId(MsgId msgId) { unregisterDraftSources(); _editMsgId = msgId; + if (_history) { + refreshSendAsToggle(); + } registerDraftSource(); } @@ -2581,6 +2590,30 @@ void HistoryWidget::refreshScheduledToggle() { } } +void HistoryWidget::setupSendAsToggle() { + session().sendAsPeers().updated( + ) | rpl::filter([=](not_null peer) { + return (peer == _peer); + }) | rpl::start_with_next([=] { + refreshSendAsToggle(); + updateControlsVisibility(); + updateControlsGeometry(); + }, lifetime()); +} + +void HistoryWidget::refreshSendAsToggle() { + Expects(_peer != nullptr); + + if (_editMsgId || !session().sendAsPeers().shouldChoose(_peer)) { + _sendAs.destroy(); + return; + } else if (_sendAs) { + return; + } + _sendAs.create(this, st::sendAsButton, cUserpicCornersType()); + Ui::SetupSendAsButton(_sendAs.data(), controller()); +} + bool HistoryWidget::contentOverlapped(const QRect &globalRect) { return (_attachDragAreas.document->overlaps(globalRect) || _attachDragAreas.photo->overlaps(globalRect) @@ -2700,6 +2733,9 @@ void HistoryWidget::updateControlsVisibility() { if (_ttlInfo) { _ttlInfo->hide(); } + if (_sendAs) { + _sendAs->hide(); + } _kbScroll->hide(); _fieldBarCancel->hide(); _attachToggle->hide(); @@ -2767,6 +2803,9 @@ void HistoryWidget::updateControlsVisibility() { if (_ttlInfo) { _ttlInfo->show(); } + if (_sendAs) { + _sendAs->show(); + } updateFieldPlaceholder(); if (_editMsgId || _replyToId || readyToForward() || (_previewData && _previewData->pendingTill >= 0) || _kbReplyTo) { @@ -2800,9 +2839,11 @@ void HistoryWidget::updateControlsVisibility() { if (_ttlInfo) { _ttlInfo->hide(); } + if (_sendAs) { + _sendAs->hide(); + } _kbScroll->hide(); _fieldBarCancel->hide(); - _attachToggle->hide(); _tabbedSelectorToggle->hide(); _botKeyboardShow->hide(); _botKeyboardHide->hide(); @@ -3598,10 +3639,12 @@ void HistoryWidget::saveEditMsg() { })(); }; + auto options = Api::SendOptions(); + options.removeWebPageId = (webPageId == CancelledWebPageId); _saveEditMsgRequestId = Api::EditTextMessage( item, sending, - { .removeWebPageId = (webPageId == CancelledWebPageId) }, + options, done, fail); } @@ -3641,6 +3684,17 @@ void HistoryWidget::hideSelectorControlsAnimated() { } } +Api::SendAction HistoryWidget::prepareSendAction( + Api::SendOptions options) const { + auto result = Api::SendAction(_history, options); + result.replyTo = replyToId(); + result.options.sendAs = _sendAs + ? _history->session().sendAsPeers().resolveChosen( + _history->peer).get() + : nullptr; + return result; +} + void HistoryWidget::send(Api::SendOptions options) { if (!_history) { return; @@ -3662,10 +3716,8 @@ void HistoryWidget::send(Api::SendOptions options) { ? _previewData->id : WebPageId(0)); - auto message = ApiWrap::MessageToSend(_history); + auto message = ApiWrap::MessageToSend(prepareSendAction(options)); message.textWithTags = _field->getTextWithAppliedMarkdown(); - message.action.options = options; - message.action.replyTo = replyToId(); message.webPageId = webPageId; if (_canSendMessages) { @@ -3705,15 +3757,11 @@ void HistoryWidget::send(Api::SendOptions options) { } void HistoryWidget::sendWithModifiers(Qt::KeyboardModifiers modifiers) { - auto options = Api::SendOptions(); - options.handleSupportSwitch = Support::HandleSwitch(modifiers); - send(options); + send({ .handleSupportSwitch = Support::HandleSwitch(modifiers) }); } void HistoryWidget::sendSilent() { - auto options = Api::SendOptions(); - options.silent = true; - send(options); + send({ .silent = true }); } void HistoryWidget::sendScheduled() { @@ -4103,7 +4151,7 @@ void HistoryWidget::sendBotCommand(const Bot::SendCommandRequest &request) { ? request.command : Bot::WrapCommandInChat(_peer, request.command, request.context); - auto message = ApiWrap::MessageToSend(_history); + auto message = Api::MessageToSend(prepareSendAction({})); message.textWithTags = { toSend, TextWithTags::Tags() }; message.action.replyTo = request.replyTo ? ((!_peer->isUser()/* && (botStatus == 0 || botStatus == 2)*/) @@ -4595,13 +4643,16 @@ void HistoryWidget::moveFieldControls() { _kbScroll->setGeometryToLeft(0, bottom, width(), keyboardHeight); } -// _attachToggle --------- _inlineResults -------------------------------------- _tabbedPanel --------- _fieldBarCancel +// _attachToggle (_sendAs) ------- _inlineResults ---------------------------------- _tabbedPanel -------- _fieldBarCancel // (_attachDocument|_attachPhoto) _field (_ttlInfo) (_scheduled) (_silent|_cmdStart|_kbShow) (_kbHide|_tabbedSelectorToggle) _send // (_botStart|_unblock|_joinChannel|{_muteUnmute&_discuss}|_reportMessages) auto buttonsBottom = bottom - _attachToggle->height(); auto left = st::historySendRight; _attachToggle->moveToLeft(left, buttonsBottom); left += _attachToggle->width(); + if (_sendAs) { + _sendAs->moveToLeft(left, buttonsBottom); left += _sendAs->width(); + } _field->moveToLeft(left, bottom - _field->height() - st::historySendPadding); auto right = st::historySendRight; _send->moveToRight(right, buttonsBottom); right += _send->width(); @@ -4671,9 +4722,12 @@ void HistoryWidget::moveFieldControls() { void HistoryWidget::updateFieldSize() { auto kbShowShown = _history && !_kbShown && _keyboard->hasMarkup(); - auto fieldWidth = width() - _attachToggle->width() - st::historySendRight; - fieldWidth -= _send->width(); - fieldWidth -= _tabbedSelectorToggle->width(); + auto fieldWidth = width() + - _attachToggle->width() + - st::historySendRight + - _send->width() + - _tabbedSelectorToggle->width(); + if (_sendAs) fieldWidth -= _sendAs->width(); if (kbShowShown) fieldWidth -= _botKeyboardShow->width(); if (_cmdStartShown) fieldWidth -= _botCommandStart->width(); if (_silent) fieldWidth -= _silent->width(); @@ -4906,15 +4960,12 @@ void HistoryWidget::sendingFilesConfirmed( const auto type = way.sendImagesAsPhotos() ? SendMediaType::Photo : SendMediaType::File; - auto action = Api::SendAction(_history); - action.replyTo = replyToId(); - action.options = options; + auto action = prepareSendAction(options); action.clearDraft = false; if ((groups.size() != 1 || !groups.front().sentWithCaption()) && !caption.text.isEmpty()) { - auto message = Api::MessageToSend(_history); + auto message = Api::MessageToSend(action); message.textWithTags = base::take(caption); - message.action = action; session().api().sendMessage(std::move(message)); } for (auto &group : groups) { @@ -5004,9 +5055,7 @@ void HistoryWidget::uploadFile( SendMediaType type) { if (!canWriteMessage()) return; - auto action = Api::SendAction(_history); - action.replyTo = replyToId(); - session().api().sendFile(fileContent, type, action); + session().api().sendFile(fileContent, type, prepareSendAction({})); } void HistoryWidget::handleHistoryChange(not_null history) { @@ -5958,7 +6007,8 @@ void HistoryWidget::handlePeerMigration() { showHistory( channel->id, (_showAtMsgId > 0) ? (-_showAtMsgId) : _showAtMsgId); - channel->session().api().requestParticipantsCountDelayed(channel); + channel->session().api().chatParticipants().requestCountDelayed( + channel); } else { _migrated = _history->migrateFrom(); _list->notifyMigrateUpdated(); @@ -6073,9 +6123,7 @@ void HistoryWidget::sendInlineResult(InlineBots::ResultSelected result) { return; } - auto action = Api::SendAction(_history); - action.replyTo = replyToId(); - action.options = std::move(result.options); + auto action = prepareSendAction(result.options); action.generateLocal = true; session().api().sendInlineResult(result.bot, result.result, action); @@ -6504,14 +6552,14 @@ bool HistoryWidget::sendExistingDocument( return false; } - auto message = Api::MessageToSend(_history); - message.action.options = std::move(options); - message.action.replyTo = replyToId(); - if (document->hasRemoteLocation()) { - Api::SendExistingDocument(std::move(message), document); + Api::SendExistingDocument( + Api::MessageToSend(prepareSendAction(options)), + document); } else { - Api::SendWebDocument(std::move(message), document); + Api::SendWebDocument( + Api::MessageToSend(prepareSendAction(options)), + document); } if (_fieldAutocomplete->stickersShown()) { @@ -6545,10 +6593,9 @@ bool HistoryWidget::sendExistingPhoto( return false; } - auto message = Api::MessageToSend(_history); - message.action.replyTo = replyToId(); - message.action.options = std::move(options); - Api::SendExistingPhoto(std::move(message), photo); + Api::SendExistingPhoto( + Api::MessageToSend(prepareSendAction(options)), + photo); hideSelectorControlsAnimated(); @@ -7095,10 +7142,10 @@ void HistoryWidget::handlePeerUpdate() { session().api().requestFullPeer(_peer); } else if (auto channel = _peer->asMegagroup()) { if (!channel->mgInfo->botStatus) { - session().api().requestBots(channel); + session().api().chatParticipants().requestBots(channel); } if (channel->mgInfo->admins.empty()) { - session().api().requestAdmins(channel); + session().api().chatParticipants().requestAdmins(channel); } } if (!_a_show.animating()) { @@ -7145,13 +7192,10 @@ void HistoryWidget::confirmDeleteSelected() { if (items.empty()) { return; } - const auto weak = Ui::MakeWeak(this); auto box = Box(&session(), std::move(items)); - box->setDeleteConfirmedCallback([=] { - if (const auto strong = weak.data()) { - strong->clearSelected(); - } - }); + box->setDeleteConfirmedCallback(crl::guard(this, [=] { + clearSelected(); + })); controller()->show(std::move(box)); } @@ -7556,7 +7600,7 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) { textTop, st::msgReplyBarSize.height(), st::msgReplyBarSize.height()); - if (HistoryView::DrawWebPageDataPreview(p, _previewData, to)) { + if (HistoryView::DrawWebPageDataPreview(p, _previewData, _peer, to)) { previewLeft += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() @@ -7728,10 +7772,6 @@ void HistoryWidget::paintEvent(QPaintEvent *e) { } } -QRect HistoryWidget::historyRect() const { - return _scroll->geometry(); -} - QPoint HistoryWidget::clampMousePosition(QPoint point) { if (point.x() < 0) { point.setX(0); diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 571ec4be9..ade902805 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -42,6 +42,7 @@ enum class Type; namespace Api { struct SendOptions; +struct SendAction; } // namespace Api namespace InlineBots { @@ -76,6 +77,7 @@ class GroupCallBar; class RequestsBar; struct PreparedList; class SendFilesWay; +class SendAsButton; enum class ReportReason; namespace Toast { class Instance; @@ -155,8 +157,6 @@ public: void firstLoadMessages(); void delayedShowAt(MsgId showAtMsgId); - QRect historyRect() const; - void updateFieldPlaceholder(); bool updateStickersByEmoji(); @@ -379,6 +379,8 @@ private: void addRecentBot(not_null bot); + [[nodiscard]] Api::SendAction prepareSendAction( + Api::SendOptions options) const; void send(Api::SendOptions options); void sendWithModifiers(Qt::KeyboardModifiers modifiers); void sendSilent(); @@ -620,6 +622,8 @@ private: void setupScheduledToggle(); void refreshScheduledToggle(); + void setupSendAsToggle(); + void refreshSendAsToggle(); bool kbWasHidden() const; @@ -730,6 +734,7 @@ private: object_ptr _discuss; object_ptr _reportMessages; object_ptr _attachToggle; + object_ptr _sendAs = { nullptr }; object_ptr _tabbedSelectorToggle; object_ptr _botKeyboardShow; object_ptr _botKeyboardHide; 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 5f88dbb53..374268c3f 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_web_page.h" #include "storage/storage_account.h" #include "apiwrap.h" +#include "api/api_chat_participants.h" #include "ui/boxes/confirm_box.h" #include "history/history.h" #include "history/history_item.h" @@ -41,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "inline_bots/inline_bot_result.h" #include "lang/lang_keys.h" #include "main/main_session.h" +#include "main/session/send_as_peers.h" #include "media/audio/media_audio_capture.h" #include "media/audio/media_audio.h" #include "styles/style_chat.h" @@ -50,6 +52,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/format_values.h" #include "ui/controls/emoji_button.h" #include "ui/controls/send_button.h" +#include "ui/controls/send_as_button.h" +#include "ui/chat/choose_send_as.h" #include "ui/special_buttons.h" #include "window/window_adaptive.h" #include "window/window_session_controller.h" @@ -101,6 +105,7 @@ class FieldHeader final : public Ui::RpWidget { public: FieldHeader(QWidget *parent, not_null data); + void setHistory(const SetHistoryArgs &args); void init(); void editMessage(FullMsgId id); @@ -138,7 +143,7 @@ private: void resolveMessageData(); void updateShownMessageText(); - void paintWebPage(Painter &p); + void paintWebPage(Painter &p, not_null peer); void paintEditOrReplyToMessage(Painter &p); struct Preview { @@ -148,6 +153,7 @@ private: bool cancelled = false; }; + History *_history = nullptr; rpl::variable _title; rpl::variable _description; @@ -184,6 +190,10 @@ FieldHeader::FieldHeader(QWidget *parent, not_null data) init(); } +void FieldHeader::setHistory(const SetHistoryArgs &args) { + _history = *args.history; +} + void FieldHeader::init() { sizeValue( ) | rpl::start_with_next([=](QSize size) { @@ -205,7 +215,9 @@ void FieldHeader::init() { (!ShowWebPagePreview(_preview.data) || *leftIconPressed) ? paintEditOrReplyToMessage(p) - : paintWebPage(p); + : paintWebPage( + p, + _history ? _history->peer : _data->session().user()); }, lifetime()); _editMsgId.value( @@ -411,7 +423,7 @@ void FieldHeader::previewRequested( } -void FieldHeader::paintWebPage(Painter &p) { +void FieldHeader::paintWebPage(Painter &p, not_null context) { Expects(ShowWebPagePreview(_preview.data)); const auto textTop = st::msgReplyPadding.top(); @@ -428,7 +440,7 @@ void FieldHeader::paintWebPage(Painter &p) { textTop, st::msgReplyBarSize.height(), st::msgReplyBarSize.height()); - if (HistoryView::DrawWebPageDataPreview(p, _preview.data, to)) { + if (HistoryView::DrawWebPageDataPreview(p, _preview.data, context, to)) { previewLeft += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() @@ -570,12 +582,10 @@ MessageToEdit FieldHeader::queryToEdit() { return {}; } return { - item->fullId(), - { - item->isScheduled() ? item->date() : 0, - false, - false, - !hasPreview(), + .fullId = item->fullId(), + .options = { + .scheduled = item->isScheduled() ? item->date() : 0, + .removeWebPageId = !hasPreview(), }, }; } @@ -653,6 +663,7 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) { //} unregisterDraftSources(); _history = history; + _header->setHistory(args); registerDraftSource(); _window->tabbedSelector()->setCurrentPeer( history ? history->peer.get() : nullptr); @@ -662,6 +673,7 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) { updateControlsGeometry(_wrap->size()); updateControlsVisibility(); updateFieldPlaceholder(); + updateSendAsButton(); //if (!_history) { // return; //} @@ -670,7 +682,7 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) { session().api().requestFullPeer(peer); } else if (const auto channel = peer->asMegagroup()) { if (!channel->mgInfo->botStatus) { - session().api().requestBots(channel); + session().api().chatParticipants().requestBots(channel); } } else if (hasSilentBroadcastToggle()) { _silent = std::make_unique( @@ -688,6 +700,12 @@ void ComposeControls::setCurrentDialogsEntryState(Dialogs::EntryState state) { } } +PeerData *ComposeControls::sendAsPeer() const { + return (_sendAs && _history) + ? session().sendAsPeers().resolveChosen(_history->peer).get() + : nullptr; +} + void ComposeControls::move(int x, int y) { _wrap->move(x, y); _writeRestricted->move(x, y); @@ -977,6 +995,7 @@ void ComposeControls::init() { initField(); initTabbedSelector(); initSendButton(); + initSendAsButton(); initWriteRestriction(); initVoiceRecordBar(); initKeyHandler(); @@ -1002,6 +1021,10 @@ void ComposeControls::init() { ) | rpl::start_with_next([=](const auto &id) { unregisterDraftSources(); updateSendButtonType(); + if (_history && updateSendAsButton()) { + updateControlsVisibility(); + updateControlsGeometry(_wrap->size()); + } registerDraftSource(); }, _wrap->lifetime()); @@ -1631,6 +1654,18 @@ void ComposeControls::initSendButton() { SendMenu::DefaultScheduleCallback(_wrap.get(), sendMenuType(), send)); } +void ComposeControls::initSendAsButton() { + session().sendAsPeers().updated( + ) | rpl::filter([=](not_null peer) { + return _history && (peer == _history->peer); + }) | rpl::start_with_next([=] { + if (updateSendAsButton()) { + updateControlsVisibility(); + updateControlsGeometry(_wrap->size()); + } + }, _wrap->lifetime()); +} + void ComposeControls::inlineBotResolveDone( const MTPcontacts_ResolvedPeer &result) { Expects(result.type() == mtpc_contacts_resolvedPeer); @@ -1824,11 +1859,12 @@ void ComposeControls::finishAnimating() { } void ComposeControls::updateControlsGeometry(QSize size) { - // _attachToggle -- _inlineResults ------ _tabbedPanel -- _fieldBarCancel + // _attachToggle (_sendAs) -- _inlineResults ------ _tabbedPanel -- _fieldBarCancel // (_attachDocument|_attachPhoto) _field (_ttlInfo) (_silent|_botCommandStart) _tabbedSelectorToggle _send const auto fieldWidth = size.width() - _attachToggle->width() + - (_sendAs ? _sendAs->width() : 0) - st::historySendRight - _send->width() - _tabbedSelectorToggle->width() @@ -1850,6 +1886,10 @@ void ComposeControls::updateControlsGeometry(QSize size) { auto left = st::historySendRight; _attachToggle->moveToLeft(left, buttonsTop); left += _attachToggle->width(); + if (_sendAs) { + _sendAs->moveToLeft(left, buttonsTop); + left += _sendAs->width(); + } _field->moveToLeft( left, size.height() - _field->height() - st::historySendPadding); @@ -1887,6 +1927,9 @@ void ComposeControls::updateControlsVisibility() { if (_ttlInfo) { _ttlInfo->show(); } + if (_sendAs) { + _sendAs->show(); + } } bool ComposeControls::updateBotCommandShown() { @@ -1933,6 +1976,30 @@ void ComposeControls::updateMessagesTTLShown() { } } +bool ComposeControls::updateSendAsButton() { + Expects(_history != nullptr); + + const auto peer = _history->peer; + if (isEditingMessage() || !session().sendAsPeers().shouldChoose(peer)) { + if (!_sendAs) { + return false; + } + _sendAs = nullptr; + return true; + } else if (_sendAs) { + return false; + } + _sendAs = std::make_unique( + _wrap.get(), + st::sendAsButton, + cUserpicCornersType()); + Ui::SetupSendAsButton( + _sendAs.get(), + rpl::single(peer.get()), + _window); + return true; +} + void ComposeControls::paintBackground(QRect clip) { Painter p(_wrap.get()); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index 8cd5282e0..1a19acca9 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -49,6 +49,7 @@ namespace Ui { class SendButton; class IconButton; class EmojiButton; +class SendAsButton; class SilentToggle; } // namespace Ui @@ -102,6 +103,7 @@ public: [[nodiscard]] Main::Session &session() const; void setHistory(SetHistoryArgs &&args); void setCurrentDialogsEntryState(Dialogs::EntryState state); + [[nodiscard]] PeerData *sendAsPeer() const; void finishAnimating(); @@ -195,6 +197,7 @@ private: void initField(); void initTabbedSelector(); void initSendButton(); + void initSendAsButton(); void initWebpageProcess(); void initWriteRestriction(); void initVoiceRecordBar(); @@ -203,6 +206,7 @@ private: void updateSubmitSettings(); void updateSendButtonType(); void updateMessagesTTLShown(); + bool updateSendAsButton(); void updateHeight(); void updateWrappingVisibility(); void updateControlsVisibility(); @@ -290,6 +294,7 @@ private: const not_null _tabbedSelectorToggle; const not_null _field; const not_null _botCommandStart; + std::unique_ptr _sendAs; std::unique_ptr _silent; std::unique_ptr _ttlInfo; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_ttl_button.cpp b/Telegram/SourceFiles/history/view/controls/history_view_ttl_button.cpp index 826d0d0df..26f8c1ce3 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_ttl_button.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_ttl_button.cpp @@ -119,7 +119,7 @@ void AutoDeleteSettingsBox( if (const auto strong = state->weak.data()) { strong->closeBox(); } - }).fail([=](const MTP::Error &error) { + }).fail([=] { state->savingRequestId = 0; }).send(); }; diff --git a/Telegram/SourceFiles/history/view/history_view_contact_status.cpp b/Telegram/SourceFiles/history/view/history_view_contact_status.cpp index 8fa55ad9a..bb385ac3a 100644 --- a/Telegram/SourceFiles/history/view/history_view_contact_status.cpp +++ b/Telegram/SourceFiles/history/view/history_view_contact_status.cpp @@ -15,6 +15,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/toast/toast.h" #include "ui/text/format_values.h" // Ui::FormatPhone #include "ui/text/text_utilities.h" +#include "ui/boxes/confirm_box.h" +#include "ui/layers/generic_box.h" #include "data/data_peer.h" #include "data/data_user.h" #include "data/data_chat.h" @@ -26,10 +28,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "api/api_blocked_peers.h" #include "main/main_session.h" -#include "ui/boxes/confirm_box.h" +#include "base/unixtime.h" #include "boxes/peers/edit_contact_box.h" #include "styles/style_chat.h" #include "styles/style_layers.h" +#include "styles/style_info.h" namespace HistoryView { namespace { @@ -56,7 +59,73 @@ bool BarCurrentlyHidden(not_null peer) { } // namespace -ContactStatus::Bar::Bar(QWidget *parent, const QString &name) +class ContactStatus::BgButton final : public Ui::RippleButton { +public: + BgButton(QWidget *parent, const style::FlatButton &st); + +protected: + void paintEvent(QPaintEvent *e) override; + + void onStateChanged(State was, StateChangeSource source) override; + +private: + const style::FlatButton &_st; + +}; + +class ContactStatus::Bar final : public Ui::RpWidget { +public: + Bar(QWidget *parent, const QString &name); + + void showState(State state); + + [[nodiscard]] rpl::producer<> unarchiveClicks() const; + [[nodiscard]] rpl::producer<> addClicks() const; + [[nodiscard]] rpl::producer<> blockClicks() const; + [[nodiscard]] rpl::producer<> shareClicks() const; + [[nodiscard]] rpl::producer<> reportClicks() const; + [[nodiscard]] rpl::producer<> closeClicks() const; + [[nodiscard]] rpl::producer<> requestInfoClicks() const; + +private: + int resizeGetHeight(int newWidth) override; + + QString _name; + object_ptr _add; + object_ptr _unarchive; + object_ptr _block; + object_ptr _share; + object_ptr _report; + object_ptr _close; + object_ptr _requestChatBg; + object_ptr _requestChatInfo; + +}; + +ContactStatus::BgButton::BgButton( + QWidget *parent, + const style::FlatButton &st) +: RippleButton(parent, st.ripple) +, _st(st) { +} + +void ContactStatus::BgButton::onStateChanged( + State was, + StateChangeSource source) { + RippleButton::onStateChanged(was, source); + update(); +} + +void ContactStatus::BgButton::paintEvent(QPaintEvent *e) { + QPainter p(this); + + p.fillRect(e->rect(), isOver() ? _st.overBgColor : _st.bgColor); + paintRipple(p, 0, 0); +} + +ContactStatus::Bar::Bar( + QWidget *parent, + const QString &name) : RpWidget(parent) , _name(name) , _add( @@ -79,26 +148,46 @@ ContactStatus::Bar::Bar(QWidget *parent, const QString &name) this, QString(), st::historyContactStatusBlock) -, _close(this, st::historyReplyCancel) { - resize(_close->size()); +, _close(this, st::historyReplyCancel) +, _requestChatBg(this, st::historyContactStatusButton) +, _requestChatInfo( + this, + QString(), + st::historyContactStatusLabel) { + _requestChatInfo->setAttribute(Qt::WA_TransparentForMouseEvents); } void ContactStatus::Bar::showState(State state) { - _add->setVisible(state == State::AddOrBlock || state == State::Add); - _unarchive->setVisible(state == State::UnarchiveOrBlock - || state == State::UnarchiveOrReport); - _block->setVisible(state == State::AddOrBlock - || state == State::UnarchiveOrBlock); - _share->setVisible(state == State::SharePhoneNumber); - _report->setVisible(state == State::ReportSpam - || state == State::UnarchiveOrReport); - _add->setText((state == State::Add) + using Type = State::Type; + const auto type = state.type; + _add->setVisible(type == Type::AddOrBlock || type == Type::Add); + _unarchive->setVisible(type == Type::UnarchiveOrBlock + || type == Type::UnarchiveOrReport); + _block->setVisible(type == Type::AddOrBlock + || type == Type::UnarchiveOrBlock); + _share->setVisible(type == Type::SharePhoneNumber); + _close->setVisible(type != Type::RequestChatInfo); + _report->setVisible(type == Type::ReportSpam + || type == Type::UnarchiveOrReport); + _requestChatInfo->setVisible(type == Type::RequestChatInfo); + _requestChatBg->setVisible(type == Type::RequestChatInfo); + _add->setText((type == Type::Add) ? tr::lng_new_contact_add_name(tr::now, lt_user, _name).toUpper() : tr::lng_new_contact_add(tr::now).toUpper()); - _report->setText((state == State::ReportSpam) + _report->setText((type == Type::ReportSpam) ? tr::lng_report_spam_and_leave(tr::now).toUpper() : tr::lng_report_spam(tr::now).toUpper()); - updateButtonsGeometry(); + _requestChatInfo->setMarkedText( + (state.requestChatIsBroadcast + ? tr::lng_new_contact_from_request_channel + : tr::lng_new_contact_from_request_group)( + tr::now, + lt_user, + Ui::Text::Bold(_name), + lt_name, + Ui::Text::Bold(state.requestChatName), + Ui::Text::WithEntities)); + resizeToWidth(width()); } rpl::producer<> ContactStatus::Bar::unarchiveClicks() const { @@ -125,16 +214,19 @@ rpl::producer<> ContactStatus::Bar::closeClicks() const { return _close->clicks() | rpl::to_empty; } -void ContactStatus::Bar::resizeEvent(QResizeEvent *e) { - _close->moveToRight(0, 0); - updateButtonsGeometry(); +rpl::producer<> ContactStatus::Bar::requestInfoClicks() const { + return _requestChatBg->clicks() | rpl::to_empty; } -void ContactStatus::Bar::updateButtonsGeometry() { - const auto full = width(); +int ContactStatus::Bar::resizeGetHeight(int newWidth) { + _close->moveToRight(0, 0); + const auto closeWidth = _close->width(); - const auto available = full - closeWidth; + const auto available = newWidth - closeWidth; const auto skip = st::historyContactStatusMinSkip; + if (available <= 2 * skip) { + return _close->height(); + } const auto buttonWidth = [&](const object_ptr &button) { return button->textWidth() + 2 * skip; }; @@ -157,18 +249,18 @@ void ContactStatus::Bar::updateButtonsGeometry() { thatWidth + closeWidth - available, 0, closeWidth); - placeButton(button, full, margin); + placeButton(button, newWidth, margin); }; const auto &leftButton = _unarchive->isHidden() ? _add : _unarchive; const auto &rightButton = _block->isHidden() ? _report : _block; if (!leftButton->isHidden() && !rightButton->isHidden()) { const auto leftWidth = buttonWidth(leftButton); const auto rightWidth = buttonWidth(rightButton); - const auto half = full / 2; + const auto half = newWidth / 2; if (leftWidth <= half - && rightWidth + 2 * closeWidth <= full - half) { + && rightWidth + 2 * closeWidth <= newWidth - half) { placeButton(leftButton, half); - placeButton(rightButton, full - half); + placeButton(rightButton, newWidth - half); } else if (leftWidth + rightWidth <= available) { const auto margin = std::clamp( leftWidth + rightWidth + closeWidth - available, @@ -177,22 +269,31 @@ void ContactStatus::Bar::updateButtonsGeometry() { const auto realBlockWidth = rightWidth + 2 * closeWidth - margin; if (leftWidth > realBlockWidth) { placeButton(leftButton, leftWidth); - placeButton(rightButton, full - leftWidth, margin); + placeButton(rightButton, newWidth - leftWidth, margin); } else { - placeButton(leftButton, full - realBlockWidth); + placeButton(leftButton, newWidth - realBlockWidth); placeButton(rightButton, realBlockWidth, margin); } } else { const auto forLeft = (available * leftWidth) / (leftWidth + rightWidth); placeButton(leftButton, forLeft); - placeButton(rightButton, full - forLeft, closeWidth); + placeButton(rightButton, newWidth - forLeft, closeWidth); } } else { placeOne(_add); placeOne(_share); placeOne(_report); } + if (_requestChatInfo->isHidden()) { + return _close->height(); + } + const auto vskip = st::topBarArrowPadding.top(); + _requestChatInfo->resizeToWidth(available - 2 * skip); + _requestChatInfo->move(skip, vskip); + const auto newHeight = _requestChatInfo->height() + 2 * vskip; + _requestChatBg->setGeometry(0, 0, newWidth, newHeight); + return newHeight; } ContactStatus::ContactStatus( @@ -231,6 +332,7 @@ void ContactStatus::setupWidgets(not_null parent) { auto ContactStatus::PeerState(not_null peer) -> rpl::producer { using SettingsChange = PeerData::Settings::Change; + using Type = State::Type; if (const auto user = peer->asUser()) { using FlagsChange = UserData::Flags::Change; using Flag = UserDataFlag; @@ -245,34 +347,43 @@ auto ContactStatus::PeerState(not_null peer) user->settingsValue() ) | rpl::map([=]( FlagsChange flags, - SettingsChange settings) { + SettingsChange settings) -> State { if (flags.value & Flag::Blocked) { - return State::None; + return { Type::None }; } else if (user->isContact()) { if (settings.value & PeerSetting::ShareContact) { - return State::SharePhoneNumber; + return { Type::SharePhoneNumber }; } else { - return State::None; + return { Type::None }; } + } else if (settings.value & PeerSetting::RequestChat) { + return { + .type = Type::RequestChatInfo, + .requestChatName = peer->requestChatTitle(), + .requestChatIsBroadcast = !!(settings.value + & PeerSetting::RequestChatIsBroadcast), + .requestDate = peer->requestChatDate(), + }; } else if (settings.value & PeerSetting::AutoArchived) { - return State::UnarchiveOrBlock; + return { Type::UnarchiveOrBlock }; } else if (settings.value & PeerSetting::BlockContact) { - return State::AddOrBlock; + return { Type::AddOrBlock }; } else if (settings.value & PeerSetting::AddContact) { - return State::Add; + return { Type::Add }; } else { - return State::None; + return { Type::None }; } }); } return peer->settingsValue( ) | rpl::map([=](SettingsChange settings) { + using Type = State::Type; return (settings.value & PeerSetting::AutoArchived) - ? State::UnarchiveOrReport + ? State{ Type::UnarchiveOrReport } : (settings.value & PeerSetting::ReportSpam) - ? State::ReportSpam - : State::None; + ? State{ Type::ReportSpam } + : State{ Type::None }; }); } @@ -281,11 +392,12 @@ void ContactStatus::setupState(not_null peer) { peer->session().api().requestPeerSettings(peer); } + _bar.entity()->showState(State()); PeerState( peer ) | rpl::start_with_next([=](State state) { _state = state; - if (state == State::None) { + if (state.type == State::Type::None) { _bar.hide(anim::type::normal); } else { _bar.entity()->showState(state); @@ -303,6 +415,7 @@ void ContactStatus::setupHandlers(not_null peer) { setupUnarchiveHandler(peer); setupReportHandler(peer); setupCloseHandler(peer); + setupRequestInfoHandler(peer); } void ContactStatus::setupAddHandler(not_null user) { @@ -420,8 +533,45 @@ void ContactStatus::setupCloseHandler(not_null peer) { }, _bar.lifetime()); } +void ContactStatus::setupRequestInfoHandler(not_null peer) { + const auto request = _bar.lifetime().make_state(0); + _bar.entity()->requestInfoClicks( + ) | rpl::filter([=] { + return !(*request); + }) | rpl::start_with_next([=] { + _controller->show(Box([=](not_null box) { + box->setTitle((_state.requestChatIsBroadcast + ? tr::lng_from_request_title_channel + : tr::lng_from_request_title_group)()); + + box->addRow(object_ptr( + box, + tr::lng_from_request_body( + lt_name, + rpl::single(Ui::Text::Bold(_state.requestChatName)), + lt_date, + rpl::single(langDateTimeFull( + base::unixtime::parse(_state.requestDate) + )) | Ui::Text::ToWithEntities(), + Ui::Text::WithEntities), + st::boxLabel)); + + box->addButton(tr::lng_from_request_understand(), [=] { + if (*request) { + return; + } + peer->setSettings(0); + *request = peer->session().api().request( + MTPmessages_HidePeerSettingsBar(peer->input) + ).send(); + box->closeBox(); + }); + })); + }, _bar.lifetime()); +} + void ContactStatus::show() { - const auto visible = (_state != State::None); + const auto visible = (_state.type != State::Type::None); if (!_shown) { _shown = true; if (visible) { diff --git a/Telegram/SourceFiles/history/view/history_view_contact_status.h b/Telegram/SourceFiles/history/view/history_view_contact_status.h index 3e258555f..56b696fcf 100644 --- a/Telegram/SourceFiles/history/view/history_view_contact_status.h +++ b/Telegram/SourceFiles/history/view/history_view_contact_status.h @@ -18,6 +18,7 @@ class SessionController; namespace Ui { class FlatButton; class IconButton; +class FlatLabel; } // namespace Ui namespace HistoryView { @@ -33,51 +34,32 @@ public: void raise(); void move(int x, int y); - int height() const; - rpl::producer heightValue() const; + [[nodiscard]] int height() const; + [[nodiscard]] rpl::producer heightValue() const; - rpl::lifetime &lifetime() { + [[nodiscard]] rpl::lifetime &lifetime() { return _lifetime; } private: - enum class State { - None, - ReportSpam, - Add, - AddOrBlock, - UnarchiveOrBlock, - UnarchiveOrReport, - SharePhoneNumber, - }; - - class Bar : public Ui::RpWidget { - public: - Bar(QWidget *parent, const QString &name); - - void showState(State state); - - rpl::producer<> unarchiveClicks() const; - rpl::producer<> addClicks() const; - rpl::producer<> blockClicks() const; - rpl::producer<> shareClicks() const; - rpl::producer<> reportClicks() const; - rpl::producer<> closeClicks() const; - - protected: - void resizeEvent(QResizeEvent *e) override; - - private: - void updateButtonsGeometry(); - - QString _name; - object_ptr _add; - object_ptr _unarchive; - object_ptr _block; - object_ptr _share; - object_ptr _report; - object_ptr _close; + class Bar; + class BgButton; + struct State { + enum class Type { + None, + ReportSpam, + Add, + AddOrBlock, + UnarchiveOrBlock, + UnarchiveOrReport, + SharePhoneNumber, + RequestChatInfo, + }; + Type type = Type::None; + QString requestChatName; + bool requestChatIsBroadcast = false; + TimeId requestDate = 0; }; void setupWidgets(not_null parent); @@ -89,11 +71,12 @@ private: void setupUnarchiveHandler(not_null peer); void setupReportHandler(not_null peer); void setupCloseHandler(not_null peer); + void setupRequestInfoHandler(not_null peer); static rpl::producer PeerState(not_null peer); const not_null _controller; - State _state = State::None; + State _state; Ui::SlideWrap _bar; Ui::PlainShadow _shadow; bool _shown = false; diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index daadcbe0b..94656658d 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -128,16 +128,23 @@ void ToggleFavedSticker( void AddPhotoActions( not_null menu, not_null photo, + HistoryItem *item, not_null list) { - menu->addAction( - tr::lng_context_save_image(tr::now), - App::LambdaDelayed( - st::defaultDropdownMenu.menu.ripple.hideDuration, - &photo->session(), - [=] { SavePhotoToFile(photo); })); - menu->addAction(tr::lng_context_copy_image(tr::now), [=] { - CopyImage(photo); - }); + const auto contextId = item ? item->fullId() : FullMsgId(); + if (!list->hasCopyRestriction(item)) { + menu->addAction( + tr::lng_context_save_image(tr::now), + App::LambdaDelayed( + st::defaultDropdownMenu.menu.ripple.hideDuration, + &photo->session(), + [=] { SavePhotoToFile(photo); })); + menu->addAction(tr::lng_context_copy_image(tr::now), [=] { + const auto item = photo->owner().message(contextId); + if (!list->showCopyRestriction(item)) { + CopyImage(photo); + } + }); + } if (photo->hasAttachedStickers()) { const auto controller = list->controller(); auto callback = [=] { @@ -183,8 +190,14 @@ void ShowInFolder(not_null document) { void AddSaveDocumentAction( not_null menu, - Data::FileOrigin origin, - not_null document) { + HistoryItem *item, + not_null document, + not_null list) { + if (list->hasCopyRestriction(item)) { + return; + } + const auto origin = Data::FileOrigin( + item ? item->fullId() : FullMsgId()); const auto save = [=] { DocumentSaveClickHandler::Save( origin, @@ -211,7 +224,7 @@ void AddSaveDocumentAction( void AddDocumentActions( not_null menu, not_null document, - FullMsgId contextId, + HistoryItem *item, not_null list) { if (document->loading()) { menu->addAction(tr::lng_context_cancel_download(tr::now), [=] { @@ -219,8 +232,9 @@ void AddDocumentActions( }); return; } + const auto contextId = item ? item->fullId() : FullMsgId(); const auto session = &document->session(); - if (const auto item = session->data().message(contextId)) { + if (item) { const auto notAutoplayedGif = [&] { return document->isGifv() && !Data::AutoDownload::ShouldAutoPlay( @@ -233,7 +247,7 @@ void AddDocumentActions( OpenGif(list->controller(), contextId); }); } - if (document->isGifv()) { + if (document->isGifv() && !list->hasCopyRestriction(item)) { menu->addAction(tr::lng_context_save_gif(tr::now), [=] { SaveGif(list->controller(), contextId); }); @@ -268,7 +282,7 @@ void AddDocumentActions( tr::lng_context_attached_stickers(tr::now), std::move(callback)); } - AddSaveDocumentAction(menu, contextId, document); + AddSaveDocumentAction(menu, item, document, list); } void AddPostLinkAction( @@ -681,16 +695,13 @@ bool AddDeleteSelectedAction( } menu->addAction(tr::lng_context_delete_selected(tr::now), [=] { - const auto weak = Ui::MakeWeak(list); auto items = ExtractIdsList(request.selectedItems); auto box = Box( &request.navigation->session(), std::move(items)); - box->setDeleteConfirmedCallback([=] { - if (const auto strong = weak.data()) { - strong->cancelSelection(); - } - }); + box->setDeleteConfirmedCallback(crl::guard(list, [=] { + list->cancelSelection(); + })); request.navigation->parentController()->show(std::move(box)); }); return true; @@ -807,7 +818,10 @@ bool AddSelectMessageAction( const auto item = request.item; if (request.overSelection && !request.selectedItems.empty()) { return false; - } else if (!item || item->isLocal() || item->isService()) { + } else if (!item + || item->isLocal() + || item->isService() + || list->hasSelectRestriction()) { return false; } const auto owner = &item->history()->owner(); @@ -903,20 +917,22 @@ base::unique_qptr FillContextMenu( const auto hasSelection = !request.selectedItems.empty() || !request.selectedText.empty(); - if (request.overSelection) { + if (request.overSelection && !list->hasCopyRestrictionForSelected()) { const auto text = request.selectedItems.empty() ? tr::lng_context_copy_selected(tr::now) : tr::lng_context_copy_selected_items(tr::now); result->addAction(text, [=] { - TextUtilities::SetClipboardText(list->getSelectedText()); + if (!list->showCopyRestrictionForSelected()) { + TextUtilities::SetClipboardText(list->getSelectedText()); + } }); } AddTopMessageActions(result, request, list); if (linkPhoto) { - AddPhotoActions(result, photo, list); + AddPhotoActions(result, photo, item, list); } else if (linkDocument) { - AddDocumentActions(result, document, itemId, list); + AddDocumentActions(result, document, item, list); } else if (poll) { AddPollActions(result, poll, item, list->elementContext()); } else if (!request.overSelection && view && !hasSelection) { @@ -924,23 +940,23 @@ base::unique_qptr FillContextMenu( const auto media = view->media(); const auto mediaHasTextForCopy = media && media->hasTextForCopy(); if (const auto document = media ? media->getDocument() : nullptr) { - AddDocumentActions( - result, - document, - view->data()->fullId(), - list); + AddDocumentActions(result, document, view->data(), list); } - if (!link && (view->hasVisibleText() || mediaHasTextForCopy)) { + if (!link + && (view->hasVisibleText() || mediaHasTextForCopy) + && !list->hasCopyRestriction(view->data())) { const auto asGroup = (request.pointState != PointState::GroupPart); result->addAction(tr::lng_context_copy_text(tr::now), [=] { if (const auto item = owner->message(itemId)) { - if (asGroup) { - if (const auto group = owner->groups().find(item)) { - TextUtilities::SetClipboardText(HistoryGroupText(group)); - return; + if (!list->showCopyRestriction(item)) { + if (asGroup) { + if (const auto group = owner->groups().find(item)) { + TextUtilities::SetClipboardText(HistoryGroupText(group)); + return; + } } + TextUtilities::SetClipboardText(HistoryItemText(item)); } - TextUtilities::SetClipboardText(HistoryItemText(item)); } }); } @@ -1106,9 +1122,8 @@ void SendReport( peer->input, apiReason, MTP_string(comment) - )).done([=](const MTPBool &result) { + )).done([=] { Ui::Toast::Show(tr::lng_report_thanks(tr::now)); - }).fail([=](const MTP::Error &error) { }).send(); } else { auto apiIds = QVector(); @@ -1121,9 +1136,8 @@ void SendReport( MTP_vector(apiIds), apiReason, MTP_string(comment) - )).done([=](const MTPBool &result) { + )).done([=] { Ui::Toast::Show(tr::lng_report_thanks(tr::now)); - }).fail([=](const MTP::Error &error) { }).send(); } } diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index a9e316e51..a005215ca 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -20,7 +20,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unixtime.h" #include "core/application.h" #include "core/core_settings.h" +#include "core/click_handler_types.h" #include "main/main_session.h" +#include "main/main_domain.h" #include "chat_helpers/stickers_emoji_pack.h" #include "window/window_session_controller.h" #include "ui/effects/path_shift_gradient.h" @@ -543,11 +545,13 @@ void Element::refreshDataId() { } bool Element::computeIsAttachToPrevious(not_null previous) { - const auto mayBeAttached = [](not_null item) { + const auto mayBeAttached = [](not_null view) { + const auto item = view->data(); return !item->isService() && !item->isEmpty() && !item->isPost() - && (item->from() != item->history()->peer + && (!item->history()->peer->isMegagroup() + || !view->hasOutLayout() || !item->from()->isChannel()); }; const auto item = data(); @@ -555,8 +559,8 @@ bool Element::computeIsAttachToPrevious(not_null previous) { const auto prev = previous->data(); const auto possible = (std::abs(prev->date() - item->date()) < kAttachMessageToPreviousSecondsDelta) - && mayBeAttached(item) - && mayBeAttached(prev); + && mayBeAttached(this) + && mayBeAttached(previous); if (possible) { const auto forwarded = item->Get(); const auto prevForwarded = prev->Get(); @@ -578,10 +582,37 @@ bool Element::computeIsAttachToPrevious(not_null previous) { } ClickHandlerPtr Element::fromLink() const { + if (_fromLink) { + return _fromLink; + } const auto item = data(); - const auto from = item->displayFrom(); - if (from) { - return from->openLink(); + if (const auto from = item->displayFrom()) { + _fromLink = std::make_shared([=]( + ClickContext context) { + if (context.button != Qt::LeftButton) { + return; + } + const auto my = context.other.value(); + const auto window = [&]() -> Window::SessionController* { + if (const auto controller = my.sessionWindow.get()) { + return controller; + } + const auto session = &from->session(); + const auto &windows = session->windows(); + if (windows.empty()) { + session->domain().activate(&session->account()); + if (windows.empty()) { + return nullptr; + } + } + return windows.front(); + }(); + if (window) { + window->showPeerInfo(from); + } + }); + _fromLink->setProperty(kPeerLinkPeerIdProperty, from->id.value); + return _fromLink; } if (const auto forwarded = item->Get()) { if (forwarded->imported) { @@ -596,7 +627,8 @@ ClickHandlerPtr Element::fromLink() const { static const auto hidden = std::make_shared([] { Ui::Toast::Show(tr::lng_forwarded_hidden(tr::now)); }); - return hidden; + _fromLink = hidden; + return _fromLink; } void Element::createUnreadBar(rpl::producer text) { diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 2e784a154..8ee9cd581 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -422,6 +422,7 @@ private: const not_null _delegate; const not_null _data; std::unique_ptr _media; + mutable ClickHandlerPtr _fromLink; bool _isScheduledUntilOnline = false; const QDateTime _dateTime; diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index ba87a4999..ab943556d 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "ui/widgets/popup_menu.h" #include "ui/toast/toast.h" +#include "ui/toasts/common_toasts.h" #include "ui/inactive_press.h" #include "ui/effects/path_shift_gradient.h" #include "ui/chat/chat_theme.h" @@ -1075,7 +1076,9 @@ void ListWidget::cancelSelection() { } void ListWidget::selectItem(not_null item) { - if (const auto view = viewForItem(item)) { + if (hasSelectRestriction()) { + return; + } else if (const auto view = viewForItem(item)) { clearTextSelection(); changeSelection( _selected, @@ -1086,7 +1089,9 @@ void ListWidget::selectItem(not_null item) { } void ListWidget::selectItemAsGroup(not_null item) { - if (const auto view = viewForItem(item)) { + if (hasSelectRestriction()) { + return; + } else if (const auto view = viewForItem(item)) { clearTextSelection(); changeSelectionAsGroup( _selected, @@ -1164,6 +1169,52 @@ bool ListWidget::isEmpty() const { && (_itemsHeight + _itemsRevealHeight == 0); } +bool ListWidget::hasCopyRestriction(HistoryItem *item) const { + return _delegate->listCopyRestrictionType(item) + != CopyRestrictionType::None; +} + +bool ListWidget::showCopyRestriction(HistoryItem *item) { + const auto type = _delegate->listCopyRestrictionType(item); + if (type == CopyRestrictionType::None) { + return false; + } + Ui::ShowMultilineToast({ + .text = { (type == CopyRestrictionType::Channel) + ? tr::lng_error_nocopy_channel(tr::now) + : tr::lng_error_nocopy_group(tr::now) }, + }); + return true; +} + +bool ListWidget::hasCopyRestrictionForSelected() const { + if (hasCopyRestriction()) { + return true; + } + for (const auto &[itemId, selection] : _selected) { + if (const auto item = session().data().message(itemId)) { + if (item->forbidsForward()) { + return true; + } + } + } + return false; +} + +bool ListWidget::showCopyRestrictionForSelected() { + for (const auto &[itemId, selection] : _selected) { + if (showCopyRestriction(session().data().message(itemId))) { + return true; + } + } + return false; +} + +bool ListWidget::hasSelectRestriction() const { + return _delegate->listSelectRestrictionType() + != CopyRestrictionType::None; +} + int ListWidget::itemMinimalHeight() const { return st::msgMarginTopAttached + st::msgPhotoSize @@ -1731,7 +1782,9 @@ void ListWidget::paintEvent(QPaintEvent *e) { } void ListWidget::applyDragSelection() { - applyDragSelection(_selected); + if (!hasSelectRestriction()) { + applyDragSelection(_selected); + } clearDragSelection(); pushSelectedItems(); } @@ -1892,11 +1945,15 @@ void ListWidget::keyPressEvent(QKeyEvent *e) { _delegate->listCancelRequest(); } } else if (e == QKeySequence::Copy - && (hasSelectedText() || hasSelectedItems())) { + && (hasSelectedText() || hasSelectedItems()) + && !showCopyRestriction() + && !hasCopyRestrictionForSelected()) { TextUtilities::SetClipboardText(getSelectedText()); #ifdef Q_OS_MAC } else if (e->key() == Qt::Key_E - && e->modifiers().testFlag(Qt::ControlModifier)) { + && e->modifiers().testFlag(Qt::ControlModifier) + && !showCopyRestriction() + && !hasCopyRestrictionForSelected()) { TextUtilities::SetClipboardText(getSelectedText(), QClipboard::FindBuffer); #endif // Q_OS_MAC } else if (e == QKeySequence::Delete) { @@ -2058,7 +2115,9 @@ void ListWidget::leaveEventHook(QEvent *e) { } void ListWidget::updateDragSelection() { - if (!_overState.itemId || !_pressState.itemId) { + if (!_overState.itemId + || !_pressState.itemId + || hasSelectRestriction()) { clearDragSelection(); return; } else if (_items.empty() || !_overElement || !_selectEnabled) { @@ -2259,7 +2318,7 @@ void ListWidget::mouseActionStart( } else if (hasSelectedItems()) { if (overSelectedItems()) { _mouseAction = MouseAction::PrepareDrag; - } else if (!_pressWasInactive) { + } else if (!_pressWasInactive && !hasSelectRestriction()) { _mouseAction = MouseAction::PrepareSelect; } } @@ -2304,7 +2363,7 @@ void ListWidget::mouseActionStart( _mouseTextSymbol, _mouseTextSymbol)); _mouseAction = MouseAction::Selecting; - } else { + } else if (!hasSelectRestriction()) { _mouseAction = MouseAction::PrepareSelect; } } @@ -2417,7 +2476,8 @@ void ListWidget::mouseActionFinish( if (QGuiApplication::clipboard()->supportsSelection() && _selectedTextItem - && _selectedTextRange.from != _selectedTextRange.to) { + && _selectedTextRange.from != _selectedTextRange.to + && !hasCopyRestriction(_selectedTextItem)) { if (const auto view = viewForItem(_selectedTextItem)) { TextUtilities::SetClipboardText( view->selectedText(_selectedTextRange), @@ -2630,7 +2690,8 @@ std::unique_ptr ListWidget::prepareDrag() { return nullptr; } auto pressedHandler = ClickHandler::getPressed(); - if (dynamic_cast(pressedHandler.get())) { + if (dynamic_cast(pressedHandler.get()) + || hasCopyRestriction()) { return nullptr; } @@ -2992,15 +3053,12 @@ void ConfirmDeleteSelectedItems(not_null widget) { return; } } - const auto weak = Ui::MakeWeak(widget); auto box = Box( &widget->controller()->session(), widget->getSelectedIds()); - box->setDeleteConfirmedCallback([=] { - if (const auto strong = weak.data()) { - strong->cancelSelection(); - } - }); + box->setDeleteConfirmedCallback(crl::guard(widget, [=] { + widget->cancelSelection(); + })); widget->controller()->show(std::move(box)); } @@ -3058,4 +3116,28 @@ void ConfirmSendNowSelectedItems(not_null widget) { clearSelection); } +CopyRestrictionType CopyRestrictionTypeFor( + not_null peer, + HistoryItem *item) { + return (peer->allowsForwarding() && (!item || !item->forbidsForward())) + ? CopyRestrictionType::None + : peer->isBroadcast() + ? CopyRestrictionType::Channel + : CopyRestrictionType::Group; +} + +CopyRestrictionType SelectRestrictionTypeFor( + not_null peer) { + if (const auto chat = peer->asChat()) { + return chat->canDeleteMessages() + ? CopyRestrictionType::None + : CopyRestrictionTypeFor(peer); + } else if (const auto channel = peer->asChannel()) { + return channel->canDeleteMessages() + ? CopyRestrictionType::None + : CopyRestrictionTypeFor(peer); + } + return CopyRestrictionType::None; +} + } // 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 7d52f1f06..f12a1436c 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -41,6 +41,12 @@ enum class CursorState : char; enum class PointState : char; enum class Context : char; +enum class CopyRestrictionType : char { + None, + Group, + Channel, +}; + struct SelectedItem { explicit SelectedItem(FullMsgId msgId) : msgId(msgId) { } @@ -94,6 +100,12 @@ public: const FullMsgId &context) = 0; virtual void listHandleViaClick(not_null bot) = 0; virtual not_null listChatTheme() = 0; + virtual CopyRestrictionType listCopyRestrictionType( + HistoryItem *item) = 0; + CopyRestrictionType listCopyRestrictionType() { + return listCopyRestrictionType(nullptr); + } + virtual CopyRestrictionType listSelectRestrictionType() = 0; }; @@ -101,7 +113,6 @@ struct SelectionData { bool canDelete = false; bool canForward = false; bool canSendNow = false; - }; using SelectedMap = base::flat_map< @@ -199,11 +210,17 @@ public: void selectItem(not_null item); void selectItemAsGroup(not_null item); - bool loadedAtTopKnown() const; - bool loadedAtTop() const; - bool loadedAtBottomKnown() const; - bool loadedAtBottom() const; - bool isEmpty() const; + [[nodiscard]] bool loadedAtTopKnown() const; + [[nodiscard]] bool loadedAtTop() const; + [[nodiscard]] bool loadedAtBottomKnown() const; + [[nodiscard]] bool loadedAtBottom() const; + [[nodiscard]] bool isEmpty() const; + + [[nodiscard]] bool hasCopyRestriction(HistoryItem *item = nullptr) const; + [[nodiscard]] bool showCopyRestriction(HistoryItem *item = nullptr); + [[nodiscard]] bool hasCopyRestrictionForSelected() const; + [[nodiscard]] bool showCopyRestrictionForSelected(); + [[nodiscard]] bool hasSelectRestriction() const; // AbstractTooltipShower interface QString tooltipText() const override; @@ -605,4 +622,10 @@ void ConfirmDeleteSelectedItems(not_null widget); void ConfirmForwardSelectedItems(not_null widget); void ConfirmSendNowSelectedItems(not_null widget); +[[nodiscard]] CopyRestrictionType CopyRestrictionTypeFor( + not_null peer, + HistoryItem *item = nullptr); +[[nodiscard]] CopyRestrictionType SelectRestrictionTypeFor( + not_null peer); + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 9181699a2..6df36dab9 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -2062,7 +2062,7 @@ bool Message::hasFromName() const { case Context::Replies: { const auto item = message(); const auto peer = item->history()->peer; - if (hasOutLayout() && !item->from()->isMegagroup()) { + if (hasOutLayout() && !item->from()->isChannel()) { return false; } else if (!peer->isUser()) { return true; @@ -2217,7 +2217,7 @@ std::optional Message::rightActionSize() const { bool Message::displayFastShare() const { const auto item = message(); const auto peer = item->history()->peer; - if (!item->isRegular()) { + if (!item->allowsForward()) { return false; } else if (peer->isChannel()) { return !peer->isMegagroup(); diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp index db237777b..3115e0e1f 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp @@ -676,6 +676,15 @@ not_null PinnedWidget::listChatTheme() { return _theme.get(); } +CopyRestrictionType PinnedWidget::listCopyRestrictionType( + HistoryItem *item) { + return CopyRestrictionTypeFor(_history->peer, item); +} + +CopyRestrictionType PinnedWidget::listSelectRestrictionType() { + return SelectRestrictionTypeFor(_history->peer); +} + void PinnedWidget::confirmDeleteSelected() { ConfirmDeleteSelectedItems(_inner); } diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.h b/Telegram/SourceFiles/history/view/history_view_pinned_section.h index 27c81678b..0021fdc24 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_section.h +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.h @@ -103,6 +103,8 @@ public: const FullMsgId &context) override; void listHandleViaClick(not_null bot) override; not_null listChatTheme() override; + CopyRestrictionType listCopyRestrictionType(HistoryItem *item) override; + CopyRestrictionType listSelectRestrictionType() override; protected: void resizeEvent(QResizeEvent *e) override; diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index d8b2da20e..6241e5990 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -354,7 +354,7 @@ void RepliesWidget::sendReadTillRequest() { _root->history()->peer->input, MTP_int(_root->id), MTP_int(_root->computeRepliesInboxReadTillFull()) - )).done(crl::guard(this, [=](const MTPBool &) { + )).done(crl::guard(this, [=] { _readRequestId = 0; reloadUnreadCountIfNeeded(); })).send(); @@ -749,19 +749,15 @@ void RepliesWidget::sendingFilesConfirmed( std::move(list), way, _history->peer->slowmodeApplied()); - const auto replyTo = replyToId(); const auto type = way.sendImagesAsPhotos() ? SendMediaType::Photo : SendMediaType::File; - auto action = Api::SendAction(_history); - action.replyTo = replyTo ? replyTo : _rootId; - action.options = options; + auto action = prepareSendAction(options); action.clearDraft = false; if ((groups.size() != 1 || !groups.front().sentWithCaption()) && !caption.text.isEmpty()) { - auto message = Api::MessageToSend(_history); + auto message = Api::MessageToSend(action); message.textWithTags = base::take(caption); - message.action = action; session().api().sendMessage(std::move(message)); } for (auto &group : groups) { @@ -775,7 +771,7 @@ void RepliesWidget::sendingFilesConfirmed( album, action); } - if (_composeControls->replyingToMessage().msg == replyTo) { + if (_composeControls->replyingToMessage().msg == action.replyTo) { _composeControls->cancelReplyMessage(); refreshTopBarActiveChat(); } @@ -883,9 +879,7 @@ void RepliesWidget::uploadFile( const QByteArray &fileContent, SendMediaType type) { // #TODO replies schedule - auto action = Api::SendAction(_history); - action.replyTo = replyToId(); - session().api().sendFile(fileContent, type, action); + session().api().sendFile(fileContent, type, prepareSendAction({})); } bool RepliesWidget::showSendingFilesError( @@ -946,11 +940,19 @@ void RepliesWidget::addRecentBot(not_null bot) { } } +Api::SendAction RepliesWidget::prepareSendAction( + Api::SendOptions options) const { + auto result = Api::SendAction(_history, options); + result.replyTo = replyToId(); + result.options.sendAs = _composeControls->sendAsPeer(); + return result; +} + void RepliesWidget::send() { if (_composeControls->getTextWithAppliedMarkdown().text.isEmpty()) { return; } - send(Api::SendOptions()); + send({}); // #TODO replies schedule //const auto callback = [=](Api::SendOptions options) { send(options); }; //Ui::show( @@ -959,9 +961,7 @@ void RepliesWidget::send() { } void RepliesWidget::sendVoice(ComposeControls::VoiceToSend &&data) { - auto action = Api::SendAction(_history); - action.replyTo = replyToId(); - action.options = data.options; + auto action = prepareSendAction(data.options); session().api().sendVoiceMessage( data.bytes, data.waveform, @@ -980,10 +980,8 @@ void RepliesWidget::send(Api::SendOptions options) { const auto webPageId = _composeControls->webPageId(); - auto message = ApiWrap::MessageToSend(_history); + auto message = ApiWrap::MessageToSend(prepareSendAction(options)); message.textWithTags = _composeControls->getTextWithAppliedMarkdown(); - message.action.options = options; - message.action.replyTo = replyToId(); message.webPageId = webPageId; //const auto error = GetErrorTextForSending( @@ -1091,7 +1089,7 @@ void RepliesWidget::edit( void RepliesWidget::sendExistingDocument( not_null document) { - sendExistingDocument(document, Api::SendOptions()); + sendExistingDocument(document, {}); // #TODO replies schedule //const auto callback = [=](Api::SendOptions options) { // sendExistingDocument(document, options); @@ -1116,14 +1114,14 @@ bool RepliesWidget::sendExistingDocument( return false; } - auto message = Api::MessageToSend(_history); - message.action.replyTo = replyToId(); - message.action.options = options; - if (document->hasRemoteLocation()) { - Api::SendExistingDocument(std::move(message), document); + Api::SendExistingDocument( + Api::MessageToSend(prepareSendAction(options)), + document); } else { - Api::SendWebDocument(std::move(message), document); + Api::SendWebDocument( + Api::MessageToSend(prepareSendAction(options)), + document); } _composeControls->cancelReplyMessage(); @@ -1132,7 +1130,7 @@ bool RepliesWidget::sendExistingDocument( } void RepliesWidget::sendExistingPhoto(not_null photo) { - sendExistingPhoto(photo, Api::SendOptions()); + sendExistingPhoto(photo, {}); // #TODO replies schedule //const auto callback = [=](Api::SendOptions options) { // sendExistingPhoto(photo, options); @@ -1157,10 +1155,9 @@ bool RepliesWidget::sendExistingPhoto( return false; } - auto message = Api::MessageToSend(_history); - message.action.replyTo = replyToId(); - message.action.options = options; - Api::SendExistingPhoto(std::move(message), photo); + Api::SendExistingPhoto( + Api::MessageToSend(prepareSendAction(options)), + photo); _composeControls->cancelReplyMessage(); finishSending(); @@ -1175,7 +1172,7 @@ void RepliesWidget::sendInlineResult( controller()->show(Box(errorText)); return; } - sendInlineResult(result, bot, Api::SendOptions()); + sendInlineResult(result, bot, {}); //const auto callback = [=](Api::SendOptions options) { // sendInlineResult(result, bot, options); //}; @@ -1188,9 +1185,7 @@ void RepliesWidget::sendInlineResult( not_null result, not_null bot, Api::SendOptions options) { - auto action = Api::SendAction(_history); - action.replyTo = replyToId(); - action.options = options; + auto action = prepareSendAction(options); action.generateLocal = true; session().api().sendInlineResult(bot, result, action); @@ -1935,9 +1930,9 @@ void RepliesWidget::listSendBotCommand( _history->peer, command, context); - auto message = ApiWrap::MessageToSend(_history); + auto message = ApiWrap::MessageToSend( + prepareSendAction({})); message.textWithTags = { text }; - message.action.replyTo = replyToId(); session().api().sendMessage(std::move(message)); finishSending(); } @@ -1950,6 +1945,15 @@ not_null RepliesWidget::listChatTheme() { return _theme.get(); } +CopyRestrictionType RepliesWidget::listCopyRestrictionType( + HistoryItem *item) { + return CopyRestrictionTypeFor(_history->peer, item); +} + +CopyRestrictionType RepliesWidget::listSelectRestrictionType() { + return SelectRestrictionTypeFor(_history->peer); +} + void RepliesWidget::confirmDeleteSelected() { ConfirmDeleteSelectedItems(_inner); } diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index fccac4573..d4b422292 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -23,6 +23,7 @@ enum class Type; namespace Api { struct SendOptions; +struct SendAction; } // namespace Api namespace Storage { @@ -138,6 +139,8 @@ public: const FullMsgId &context) override; void listHandleViaClick(not_null bot) override; not_null listChatTheme() override; + CopyRestrictionType listCopyRestrictionType(HistoryItem *item) override; + CopyRestrictionType listSelectRestrictionType() override; protected: void resizeEvent(QResizeEvent *e) override; @@ -190,6 +193,8 @@ private: void addRecentBot(not_null bot); + [[nodiscard]] Api::SendAction prepareSendAction( + Api::SendOptions options) const; void send(); void send(Api::SendOptions options); void sendVoice(Controls::VoiceToSend &&data); diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 73dbd2b82..4b7b7fc5f 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -43,6 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/call_delayed.h" #include "core/file_utilities.h" #include "main/main_session.h" +#include "data/data_chat_participant_status.h" #include "data/data_session.h" #include "data/data_scheduled_messages.h" #include "data/data_user.h" @@ -446,14 +447,12 @@ void ScheduledWidget::sendingFilesConfirmed( const auto type = way.sendImagesAsPhotos() ? SendMediaType::Photo : SendMediaType::File; - auto action = Api::SendAction(_history); - action.options = options; + auto action = prepareSendAction(options); action.clearDraft = false; if ((groups.size() != 1 || !groups.front().sentWithCaption()) && !caption.text.isEmpty()) { - auto message = Api::MessageToSend(_history); + auto message = Api::MessageToSend(action); message.textWithTags = base::take(caption); - message.action = action; session().api().sendMessage(std::move(message)); } for (auto &group : groups) { @@ -490,10 +489,10 @@ void ScheduledWidget::uploadFile( const QByteArray &fileContent, SendMediaType type) { const auto callback = [=](Api::SendOptions options) { - auto action = Api::SendAction(_history); - //action.replyTo = replyToId(); - action.options = options; - session().api().sendFile(fileContent, type, action); + session().api().sendFile( + fileContent, + type, + prepareSendAction(options)); }; controller()->show( PrepareScheduleBox(this, sendMenuType(), callback), @@ -549,6 +548,13 @@ void ScheduledWidget::addRecentBot(not_null bot) { } } +Api::SendAction ScheduledWidget::prepareSendAction( + Api::SendOptions options) const { + auto result = Api::SendAction(_history, options); + result.options.sendAs = _composeControls->sendAsPeer(); + return result; +} + void ScheduledWidget::send() { if (_composeControls->getTextWithAppliedMarkdown().text.isEmpty()) { return; @@ -562,10 +568,8 @@ void ScheduledWidget::send() { void ScheduledWidget::send(Api::SendOptions options) { const auto webPageId = _composeControls->webPageId(); - auto message = ApiWrap::MessageToSend(_history); + auto message = ApiWrap::MessageToSend(prepareSendAction(options)); message.textWithTags = _composeControls->getTextWithAppliedMarkdown(); - message.action.options = options; - //message.action.replyTo = replyToId(); message.webPageId = webPageId; //const auto error = GetErrorTextForSending( @@ -609,9 +613,11 @@ void ScheduledWidget::sendVoice( VoiceWaveform waveform, int duration, Api::SendOptions options) { - auto action = Api::SendAction(_history); - action.options = options; - session().api().sendVoiceMessage(bytes, waveform, duration, action); + session().api().sendVoiceMessage( + bytes, + waveform, + duration, + prepareSendAction(options)); _composeControls->clearListenState(); } @@ -714,10 +720,9 @@ bool ScheduledWidget::sendExistingDocument( return false; } - auto message = Api::MessageToSend(_history); - //message.action.replyTo = replyToId(); - message.action.options = options; - Api::SendExistingDocument(std::move(message), document); + Api::SendExistingDocument( + Api::MessageToSend(prepareSendAction(options)), + document); _composeControls->hidePanelsAnimated(); _composeControls->focus(); @@ -746,10 +751,9 @@ bool ScheduledWidget::sendExistingPhoto( return false; } - auto message = Api::MessageToSend(_history); - //message.action.replyTo = replyToId(); - message.action.options = options; - Api::SendExistingPhoto(std::move(message), photo); + Api::SendExistingPhoto( + Api::MessageToSend(prepareSendAction(options)), + photo); _composeControls->hidePanelsAnimated(); _composeControls->focus(); @@ -776,9 +780,7 @@ void ScheduledWidget::sendInlineResult( not_null result, not_null bot, Api::SendOptions options) { - auto action = Api::SendAction(_history); - //action.replyTo = replyToId(); - action.options = options; + auto action = prepareSendAction(options); action.generateLocal = true; session().api().sendInlineResult(bot, result, action); @@ -1234,9 +1236,8 @@ void ScheduledWidget::listSendBotCommand( _history->peer, command, context); - auto message = ApiWrap::MessageToSend(_history); + auto message = ApiWrap::MessageToSend(prepareSendAction(options)); message.textWithTags = { text }; - message.action.options = options; session().api().sendMessage(std::move(message)); }; controller()->show( @@ -1252,6 +1253,15 @@ not_null ScheduledWidget::listChatTheme() { return _theme.get(); } +CopyRestrictionType ScheduledWidget::listCopyRestrictionType( + HistoryItem *item) { + return CopyRestrictionType::None; +} + +CopyRestrictionType ScheduledWidget::listSelectRestrictionType() { + return CopyRestrictionType::None; +} + void ScheduledWidget::confirmSendNowSelected() { ConfirmSendNowSelectedItems(_inner); } diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h index 2389592bd..d918e7c6c 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h @@ -22,6 +22,7 @@ enum class Type; namespace Api { struct SendOptions; +struct SendAction; } // namespace Api namespace Ui { @@ -119,6 +120,8 @@ public: const FullMsgId &context) override; void listHandleViaClick(not_null bot) override; not_null listChatTheme() override; + CopyRestrictionType listCopyRestrictionType(HistoryItem *item) override; + CopyRestrictionType listSelectRestrictionType() override; protected: void resizeEvent(QResizeEvent *e) override; @@ -155,6 +158,8 @@ private: void addRecentBot(not_null bot); + [[nodiscard]] Api::SendAction prepareSendAction( + Api::SendOptions options) const; void send(); void send(Api::SendOptions options); void sendVoice(QByteArray bytes, VoiceWaveform waveform, int duration); diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp index 7a218c743..93d642299 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -53,6 +53,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unixtime.h" #include "support/support_helper.h" #include "apiwrap.h" +#include "api/api_chat_participants.h" #include "styles/style_window.h" #include "styles/style_dialogs.h" #include "styles/style_chat.h" @@ -275,11 +276,11 @@ void TopBarWidget::setChooseForReportReason( updateControlsVisibility(); updateControlsGeometry(); update(); - if (wasNoReason != nowNoReason && _selectedCount > 0) { + if (wasNoReason != nowNoReason && showSelectedState()) { toggleSelectedControls(false); finishAnimating(); } - setCursor((nowNoReason && !_selectedCount) + setCursor((nowNoReason && !showSelectedState()) ? style::cur_pointer : style::cur_default); } @@ -594,9 +595,9 @@ QRect TopBarWidget::getMembersShowAreaGeometry() const { } void TopBarWidget::mousePressEvent(QMouseEvent *e) { - auto handleClick = (e->button() == Qt::LeftButton) + const auto handleClick = (e->button() == Qt::LeftButton) && (e->pos().y() < st::topBarHeight) - && !_selectedCount + && !showSelectedState() && !_chooseForReportReason; if (handleClick) { if (_animatingMode && _back->rect().contains(e->pos())) { @@ -941,7 +942,7 @@ void TopBarWidget::updateControlsVisibility() { void TopBarWidget::updateMembersShowArea() { const auto membersShowAreaNeeded = [&] { const auto peer = _activeChat.key.peer(); - if ((_selectedCount > 0) || !peer) { + if (showSelectedState() || !peer) { return false; } else if (const auto chat = peer->asChat()) { return chat->amIn(); @@ -966,44 +967,58 @@ void TopBarWidget::updateMembersShowArea() { _membersShowArea->setGeometry(getMembersShowAreaGeometry()); } +bool TopBarWidget::showSelectedState() const { + return (_selectedCount > 0) + && (_canDelete || _canForward || _canSendNow); +} + void TopBarWidget::showSelected(SelectedState state) { auto canDelete = (state.count > 0 && state.count == state.canDeleteCount); auto canForward = (state.count > 0 && state.count == state.canForwardCount); auto canSendNow = (state.count > 0 && state.count == state.canSendNowCount); - if (_selectedCount == state.count && _canDelete == canDelete && _canForward == canForward && _canSendNow == canSendNow) { + auto count = (!canDelete && !canForward && !canSendNow) ? 0 : state.count; + if (_selectedCount == count + && _canDelete == canDelete + && _canForward == canForward + && _canSendNow == canSendNow) { return; } - if (state.count == 0) { + if (count == 0) { // Don't change the visible buttons if the selection is cancelled. canDelete = _canDelete; canForward = _canForward; canSendNow = _canSendNow; } - auto wasSelected = (_selectedCount > 0); - _selectedCount = state.count; - if (_selectedCount > 0) { + const auto wasSelectedState = showSelectedState(); + const auto visibilityChanged = (_canDelete != canDelete) + || (_canForward != canForward) + || (_canSendNow != canSendNow); + _selectedCount = count; + _canDelete = canDelete; + _canForward = canForward; + _canSendNow = canSendNow; + const auto nowSelectedState = showSelectedState(); + if (nowSelectedState) { _forward->setNumbersText(_selectedCount); _sendNow->setNumbersText(_selectedCount); _delete->setNumbersText(_selectedCount); - if (!wasSelected) { + if (!wasSelectedState) { _forward->finishNumbersAnimation(); _sendNow->finishNumbersAnimation(); _delete->finishNumbersAnimation(); } } - auto hasSelected = (_selectedCount > 0); - if (_canDelete != canDelete || _canForward != canForward || _canSendNow != canSendNow) { - _canDelete = canDelete; - _canForward = canForward; - _canSendNow = canSendNow; + if (visibilityChanged) { updateControlsVisibility(); } - if (wasSelected != hasSelected && !_chooseForReportReason) { - setCursor(hasSelected ? style::cur_default : style::cur_pointer); + if (wasSelectedState != nowSelectedState && !_chooseForReportReason) { + setCursor(nowSelectedState + ? style::cur_default + : style::cur_pointer); updateMembersShowArea(); - toggleSelectedControls(hasSelected); + toggleSelectedControls(nowSelectedState); } else { updateControlsGeometry(); } @@ -1019,7 +1034,7 @@ void TopBarWidget::toggleSelectedControls(bool shown) { } bool TopBarWidget::showSelectedActions() const { - return (_selectedCount > 0) && !_chooseForReportReason; + return showSelectedState() && !_chooseForReportReason; } void TopBarWidget::selectedShowCallback() { @@ -1146,7 +1161,7 @@ void TopBarWidget::updateOnlineDisplay() { && (channel->membersCount() <= channel->session().serverConfig().chatSizeMax)) { if (channel->lastParticipantsRequestNeeded()) { - session().api().requestLastParticipants(channel); + session().api().chatParticipants().requestLast(channel); } const auto self = session().user(); auto online = 0; diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h index ae4a2b382..fae3f8522 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h @@ -57,6 +57,7 @@ public: void updateControlsVisibility(); void finishAnimating(); void showSelected(SelectedState state); + [[nodiscard]] bool showSelectedState() const; rpl::producer membersShowAreaActive() const { return _membersShowAreaActive.events(); } diff --git a/Telegram/SourceFiles/history/view/history_view_webpage_preview.cpp b/Telegram/SourceFiles/history/view/history_view_webpage_preview.cpp index d7d5398c9..01b83838e 100644 --- a/Telegram/SourceFiles/history/view/history_view_webpage_preview.cpp +++ b/Telegram/SourceFiles/history/view/history_view_webpage_preview.cpp @@ -56,9 +56,13 @@ WebPageText TitleAndDescriptionFromWebPage(not_null d) { return { resultTitle, resultDescription }; } -bool DrawWebPageDataPreview(Painter &p, not_null d, QRect to) { - const auto document = d->document; - const auto photo = d->photo; +bool DrawWebPageDataPreview( + Painter &p, + not_null webpage, + not_null context, + QRect to) { + const auto document = webpage->document; + const auto photo = webpage->photo; if ((!photo || photo->isNull()) && (!document || !document->hasThumbnail() @@ -67,8 +71,8 @@ bool DrawWebPageDataPreview(Painter &p, not_null d, QRect to) { } const auto preview = photo - ? photo->getReplyPreview(Data::FileOrigin()) - : document->getReplyPreview(Data::FileOrigin()); + ? photo->getReplyPreview(Data::FileOrigin(), context) + : document->getReplyPreview(Data::FileOrigin(), context); if (preview) { const auto w = preview->width(); const auto h = preview->height(); diff --git a/Telegram/SourceFiles/history/view/history_view_webpage_preview.h b/Telegram/SourceFiles/history/view/history_view_webpage_preview.h index 54594c28a..e20ad9232 100644 --- a/Telegram/SourceFiles/history/view/history_view_webpage_preview.h +++ b/Telegram/SourceFiles/history/view/history_view_webpage_preview.h @@ -15,6 +15,10 @@ struct WebPageText { }; WebPageText TitleAndDescriptionFromWebPage(not_null d); -bool DrawWebPageDataPreview(Painter &p, not_null d, QRect to); +bool DrawWebPageDataPreview( + Painter &p, + not_null webpage, + not_null context, + QRect to); } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp index d8c8cd350..adfa33383 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp @@ -333,7 +333,7 @@ void Document::draw( const auto cornerDownload = downloadInCorner(); - if (!_dataMedia->canBePlayed()) { + if (!_dataMedia->canBePlayed(_realParent)) { _dataMedia->automaticLoad(_realParent->fullId(), _realParent); } bool loaded = dataLoaded(), displayLoading = _data->displayLoading(); @@ -456,8 +456,8 @@ void Document::draw( return _data->isSongWithCover() ? sti->historyFileThumbPause : stm->historyFilePause; - } else if (loaded || _dataMedia->canBePlayed()) { - return _dataMedia->canBePlayed() + } else if (loaded || _dataMedia->canBePlayed(_realParent)) { + return _dataMedia->canBePlayed(_realParent) ? (_data->isSongWithCover() ? sti->historyFileThumbPlay : stm->historyFilePlay) @@ -597,9 +597,9 @@ void Document::ensureDataMediaCreated() const { bool Document::downloadInCorner() const { return _data->isAudioFile() - && _data->canBeStreamed() - && !_data->inappPlaybackFailed() - && _realParent->isRegular(); + && _realParent->allowsForward() + && _data->canBeStreamed(_realParent) + && !_data->inappPlaybackFailed(); } void Document::drawCornerDownload( @@ -786,7 +786,7 @@ TextState Document::textState( && (!_data->loading() || downloadInCorner()) && !_data->uploading() && !_data->isNull()) { - if (loaded || _dataMedia->canBePlayed()) { + if (loaded || _dataMedia->canBePlayed(_realParent)) { result.link = _openl; } else { result.link = _savel; @@ -1088,17 +1088,7 @@ TextWithEntities Document::getCaption() const { } Ui::Text::String Document::createCaption() { - const auto timestampLinksDuration = (_data->isSong() - || _data->isVoiceMessage()) - ? _data->getDuration() - : 0; - const auto timestampLinkBase = timestampLinksDuration - ? DocumentTimestampLinkBase(_data, _realParent->fullId()) - : QString(); - return File::createCaption( - _realParent, - timestampLinksDuration, - timestampLinkBase); + return File::createCaption(_realParent); } bool DrawThumbnailAsSongCover( diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 2efb07476..d3a2a8483 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -283,9 +283,9 @@ QSize Gif::videoSize() const { bool Gif::downloadInCorner() const { return _data->isVideoFile() && (_data->loading() || !autoplayEnabled()) - && _data->canBeStreamed() - && !_data->inappPlaybackFailed() - && !_parent->data()->isSending(); + && _realParent->allowsForward() + && _data->canBeStreamed(_realParent) + && !_data->inappPlaybackFailed(); } bool Gif::autoplayEnabled() const { @@ -307,7 +307,7 @@ void Gif::draw(Painter &p, const PaintContext &context) const { const auto stm = context.messageStyle(); const auto autoPaused = _parent->delegate()->elementIsGifPaused(); const auto cornerDownload = downloadInCorner(); - const auto canBePlayed = _dataMedia->canBePlayed(); + const auto canBePlayed = _dataMedia->canBePlayed(_realParent); const auto autoplay = autoplayEnabled() && canBePlayed && CanPlayInline(_data); @@ -855,7 +855,7 @@ TextState Gif::textState(QPoint point, StateRequest request) const { ? _cancell : _realParent->isSending() ? nullptr - : (dataLoaded() || _dataMedia->canBePlayed()) + : (dataLoaded() || _dataMedia->canBePlayed(_realParent)) ? _openl : _data->loading() ? _cancell @@ -936,7 +936,7 @@ void Gif::drawGrouped( const auto autoPaused = _parent->delegate()->elementIsGifPaused(); const auto fullFeatured = fullFeaturedGrouped(sides); const auto cornerDownload = fullFeatured && downloadInCorner(); - const auto canBePlayed = _dataMedia->canBePlayed(); + const auto canBePlayed = _dataMedia->canBePlayed(_realParent); const auto autoplay = fullFeatured && autoplayEnabled() && canBePlayed @@ -1128,7 +1128,7 @@ TextState Gif::getStateGrouped( ? _cancell : _realParent->isSending() ? nullptr - : (dataLoaded() || _dataMedia->canBePlayed()) + : (dataLoaded() || _dataMedia->canBePlayed(_realParent)) ? _openl : _data->loading() ? _cancell @@ -1277,7 +1277,7 @@ void Gif::updateStatusText() const { statusSize = _data->uploadingData->offset; } else if (!downloadInCorner() && _data->loading()) { statusSize = _data->loadOffset(); - } else if (dataLoaded() || _dataMedia->canBePlayed()) { + } else if (dataLoaded() || _dataMedia->canBePlayed(_realParent)) { statusSize = Ui::FileStatusSizeLoaded; } else { statusSize = Ui::FileStatusSizeReady; @@ -1341,16 +1341,7 @@ void Gif::refreshParentId(not_null realParent) { } void Gif::refreshCaption() { - const auto timestampLinksDuration = _data->isVideoFile() - ? _data->getDuration() - : 0; - const auto timestampLinkBase = timestampLinksDuration - ? DocumentTimestampLinkBase(_data, _realParent->fullId()) - : QString(); - _caption = createCaption( - _parent->data(), - timestampLinksDuration, - timestampLinkBase); + _caption = createCaption(_parent->data()); } int Gif::additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded) const { @@ -1407,7 +1398,7 @@ void Gif::playAnimation(bool autoplay) { } if (_streamed) { stopAnimation(); - } else if (_dataMedia->canBePlayed()) { + } else if (_dataMedia->canBePlayed(_realParent)) { if (!autoplayEnabled()) { history()->owner().checkPlayingAnimations(); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.cpp b/Telegram/SourceFiles/history/view/media/history_view_media.cpp index 09d24d454..e97caa050 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lottie/lottie_single_player.h" #include "storage/storage_shared_media.h" #include "data/data_document.h" +#include "data/data_web_page.h" #include "ui/item_text_options.h" #include "ui/chat/chat_style.h" #include "ui/chat/message_bubble.h" @@ -44,18 +45,77 @@ namespace { } // namespace -QString DocumentTimestampLinkBase( +TimeId DurationForTimestampLinks(not_null document) { + if (!document->isVideoFile() + && !document->isSong() + && !document->isVoiceMessage()) { + return TimeId(0); + } + return std::max(document->getDuration(), TimeId(0)); +} + +QString TimestampLinkBase( not_null document, FullMsgId context) { return QString( - "doc%1_%2_%3" + "media_timestamp?base=doc%1_%2_%3&t=" ).arg(document->id).arg(context.channel.bare).arg(context.msg.bare); } +TimeId DurationForTimestampLinks(not_null webpage) { + if (!webpage->collage.items.empty()) { + return 0; + } else if (const auto document = webpage->document) { + return DurationForTimestampLinks(document); + } else if (webpage->type != WebPageType::Video + || webpage->siteName != qstr("YouTube")) { + return TimeId(0); + } else if (webpage->duration > 0) { + return webpage->duration; + } + constexpr auto kMaxYouTubeTimestampDuration = 10 * 60 * TimeId(60); + return kMaxYouTubeTimestampDuration; +} + +QString TimestampLinkBase( + not_null webpage, + FullMsgId context) { + const auto url = webpage->url; + if (url.isEmpty()) { + return QString(); + } + auto parts = url.split(QChar('#')); + const auto base = parts[0]; + parts.pop_front(); + const auto use = [&] { + const auto query = base.indexOf(QChar('?')); + if (query < 0) { + return base + QChar('?'); + } + auto params = base.mid(query + 1).split(QChar('&')); + for (auto i = params.begin(); i != params.end();) { + if (i->startsWith("t=")) { + i = params.erase(i); + } else { + ++i; + } + } + return base.mid(0, query) + + (params.empty() ? "?" : ("?" + params.join(QChar('&')) + "&")); + }(); + return "url:" + + use + + "t=" + + (parts.empty() ? QString() : ("#" + parts.join(QChar('#')))); +} + TextWithEntities AddTimestampLinks( TextWithEntities text, TimeId duration, const QString &base) { + if (base.isEmpty()) { + return text; + } static const auto expression = QRegularExpression( "(? item, - TimeId timestampLinksDuration, - const QString ×tampLinkBase) const { - Expects(timestampLinksDuration >= 0); - +Ui::Text::String Media::createCaption(not_null item) const { if (item->emptyText()) { return {}; } @@ -146,12 +198,7 @@ Ui::Text::String Media::createCaption( }; result.setMarkedText( st::messageTextStyle, - (timestampLinksDuration - ? AddTimestampLinks( - item->originalText(), - timestampLinksDuration, - timestampLinkBase) - : item->originalText()), + item->originalTextWithLocalEntities(), Ui::ItemTextOptions(item), context); if (const auto width = _parent->skipBlockWidth()) { diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.h b/Telegram/SourceFiles/history/view/media/history_view_media.h index 9bcec685b..139e26f0a 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media.h @@ -52,9 +52,18 @@ enum class MediaInBubbleState { Bottom, }; -[[nodiscard]] QString DocumentTimestampLinkBase( +[[nodiscard]] TimeId DurationForTimestampLinks( + not_null document); +[[nodiscard]] QString TimestampLinkBase( not_null document, FullMsgId context); + +[[nodiscard]] TimeId DurationForTimestampLinks( + not_null webpage); +[[nodiscard]] QString TimestampLinkBase( + not_null webpage, + FullMsgId context); + [[nodiscard]] TextWithEntities AddTimestampLinks( TextWithEntities text, TimeId duration, @@ -288,9 +297,7 @@ public: protected: [[nodiscard]] QSize countCurrentSize(int newWidth) override; [[nodiscard]] Ui::Text::String createCaption( - not_null item, - TimeId timestampLinksDuration = 0, - const QString ×tampLinkBase = QString()) const; + not_null item) const; virtual void playAnimation(bool autoplay) { } 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 7ff04f3f3..690a2e59c 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -683,30 +683,7 @@ void GroupedMedia::updateNeedBubbleState() { }(); if (captionPart) { const auto &part = (*captionPart); - struct Timestamp { - int duration = 0; - QString base; - }; - const auto timestamp = [&]() -> Timestamp { - const auto &document = part->content->getDocument(); - if (!document || document->isAnimation()) { - return {}; - } - const auto duration = document->getDuration(); - return { - .duration = duration, - .base = duration - ? DocumentTimestampLinkBase( - document, - part->item->fullId()) - : QString(), - }; - }(); - _caption = createCaption( - part->item, - timestamp.duration, - timestamp.base); - + _caption = createCaption(part->item); _captionItem = part->item; } else { _captionItem = nullptr; diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style index 9a325efd1..12a1e5537 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -638,10 +638,6 @@ manageDeleteGroupButton: SettingsCountButton(manageGroupTopButtonWithText) { editPeerSkip: 7px; editPeerHistoryVisibilityMargins: margins(15px, 0px, 20px, 16px); -terminateSessionsButton: SettingsButton(infoBlockButton) { - padding: margins(22px, 12px, 22px, 10px); -} - infoEmptyFg: windowSubTextFg; infoEmptyPhoto: icon {{ "info_media_photo_empty", infoEmptyFg }}; infoEmptyVideo: icon {{ "info_media_video_empty", infoEmptyFg }}; @@ -668,8 +664,6 @@ editPeerTopButtonsLayoutSkipCustomBottom: 11px; editPeerHistoryVisibilityTopSkip: 8px; -editPeerDeleteButtonMargins: margins(25px, 11px, 22px, 16px); -editPeerDeleteButton: sessionTerminateAllButton; editPeerPhotoMargins: margins(22px, 16px, 22px, 8px); editPeerTitle: defaultInputField; editPeerTitleMargins: margins(27px, 21px, 22px, 8px); diff --git a/Telegram/SourceFiles/info/info_top_bar.cpp b/Telegram/SourceFiles/info/info_top_bar.cpp index b4a30e8d5..077cade1d 100644 --- a/Telegram/SourceFiles/info/info_top_bar.cpp +++ b/Telegram/SourceFiles/info/info_top_bar.cpp @@ -342,8 +342,10 @@ void TopBar::updateSelectionControlsGeometry(int newWidth) { _delete->moveToRight(right, 0, newWidth); right += _delete->width(); } - _forward->moveToRight(right, 0, newWidth); - right += _forward->width(); + if (_canForward) { + _forward->moveToRight(right, 0, newWidth); + right += _forward->width(); + } auto left = 0; _cancelSelection->moveToLeft(left, 0); @@ -412,6 +414,7 @@ void TopBar::setSelectedItems(SelectedItems &&items) { SelectedItems TopBar::takeSelectedItems() { _canDelete = false; + _canForward = false; return std::move(_selectedItems); } @@ -420,11 +423,13 @@ rpl::producer<> TopBar::cancelSelectionRequests() const { } void TopBar::updateSelectionState() { - Expects(_selectionText && _delete); + Expects(_selectionText && _delete && _forward); _canDelete = computeCanDelete(); + _canForward = computeCanForward(); _selectionText->entity()->setValue(generateSelectedText()); _delete->toggle(_canDelete, anim::type::instant); + _forward->toggle(_canForward, anim::type::instant); updateSelectionControlsGeometry(width()); } @@ -438,6 +443,7 @@ void TopBar::createSelectionControls() { return created; }; _canDelete = computeCanDelete(); + _canForward = computeCanForward(); _cancelSelection = wrap(Ui::CreateChild>( this, object_ptr(this, _st.mediaCancel), @@ -462,8 +468,12 @@ void TopBar::createSelectionControls() { this, object_ptr(this, _st.mediaForward), st::infoTopBarScale)); + registerToggleControlCallback( + _forward.data(), + [this] { return selectionMode() && _canForward; }); _forward->setDuration(st::infoTopBarDuration); _forward->entity()->addClickHandler([this] { performForward(); }); + _forward->entity()->setVisible(_canForward); _delete = wrap(Ui::CreateChild>( this, object_ptr(this, _st.mediaDelete), @@ -482,6 +492,10 @@ bool TopBar::computeCanDelete() const { return ranges::all_of(_selectedItems.list, &SelectedItem::canDelete); } +bool TopBar::computeCanForward() const { + return ranges::all_of(_selectedItems.list, &SelectedItem::canForward); +} + Ui::StringWithNumbers TopBar::generateSelectedText() const { using Type = Storage::SharedMediaType; const auto phrase = [&] { @@ -545,11 +559,9 @@ void TopBar::performDelete() { auto box = Box( &_navigation->session(), std::move(items)); - box->setDeleteConfirmedCallback([weak = Ui::MakeWeak(this)] { - if (weak) { - weak->_cancelSelectionClicks.fire({}); - } - }); + box->setDeleteConfirmedCallback(crl::guard(this, [=] { + _cancelSelectionClicks.fire({}); + })); _navigation->parentController()->show(std::move(box)); } } @@ -620,6 +632,8 @@ rpl::producer TitleValue( return tr::lng_settings_section_notify(); case Section::SettingsType::PrivacySecurity: return tr::lng_settings_section_privacy(); + case Section::SettingsType::Sessions: + return tr::lng_settings_sessions_title(); case Section::SettingsType::Advanced: return tr::lng_settings_advanced(); case Section::SettingsType::Chat: diff --git a/Telegram/SourceFiles/info/info_top_bar.h b/Telegram/SourceFiles/info/info_top_bar.h index d44a16f63..4d1c7e823 100644 --- a/Telegram/SourceFiles/info/info_top_bar.h +++ b/Telegram/SourceFiles/info/info_top_bar.h @@ -111,6 +111,7 @@ private: bool searchMode() const; Ui::StringWithNumbers generateSelectedText() const; [[nodiscard]] bool computeCanDelete() const; + [[nodiscard]] bool computeCanForward() const; void updateSelectionState(); void createSelectionControls(); void clearSelectionControls(); @@ -153,6 +154,7 @@ private: SelectedItems _selectedItems; bool _canDelete = false; + bool _canForward = false; QPointer> _cancelSelection; QPointer> _selectionText; QPointer> _forward; diff --git a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp index 0a72d810d..d47c4ea00 100644 --- a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp +++ b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp @@ -13,6 +13,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "layout/layout_selection.h" #include "data/data_media_types.h" #include "data/data_photo.h" +#include "data/data_chat.h" +#include "data/data_channel.h" +#include "data/data_peer_values.h" #include "data/data_document.h" #include "data/data_session.h" #include "data/data_file_click_handler.h" @@ -731,9 +734,47 @@ void ListWidget::start() { }, lifetime()); _controller->mediaSourceQueryValue( - ) | rpl::start_with_next([this]{ + ) | rpl::start_with_next([this] { restart(); }, lifetime()); + + setupSelectRestriction(); +} + +void ListWidget::setupSelectRestriction() { + if (_peer->isUser()) { + return; + } + const auto chat = _peer->asChat(); + const auto channel = _peer->asChannel(); + auto noForwards = chat + ? Data::PeerFlagValue(chat, ChatDataFlag::NoForwards) + : Data::PeerFlagValue( + channel, + ChannelDataFlag::NoForwards + ) | rpl::type_erased(); + + auto rights = chat + ? chat->adminRightsValue() + : channel->adminRightsValue(); + auto canDelete = std::move( + rights + ) | rpl::map([=] { + return chat + ? chat->canDeleteMessages() + : channel->canDeleteMessages(); + }); + rpl::combine( + std::move(noForwards), + std::move(canDelete) + ) | rpl::filter([=] { + return hasSelectRestriction() && hasSelectedItems(); + }) | rpl::start_with_next([=] { + clearSelected(); + if (_mouseAction == MouseAction::PrepareSelect) { + mouseActionCancel(); + } + }, lifetime()); } rpl::producer ListWidget::scrollToRequests() const { @@ -1595,15 +1636,17 @@ void ListWidget::showContextMenu( document, DocumentSaveClickHandler::Mode::ToNewFile); }); - _contextMenu->addAction( - (isVideo - ? tr::lng_context_save_video(tr::now) - : isVoice - ? tr::lng_context_save_audio(tr::now) - : isAudio - ? tr::lng_context_save_audio_file(tr::now) - : tr::lng_context_save_file(tr::now)), - std::move(handler)); + if (_peer->allowsForwarding()) { + _contextMenu->addAction( + (isVideo + ? tr::lng_context_save_video(tr::now) + : isVoice + ? tr::lng_context_save_audio(tr::now) + : isAudio + ? tr::lng_context_save_audio_file(tr::now) + : tr::lng_context_save_file(tr::now)), + std::move(handler)); + } } } } @@ -1654,18 +1697,20 @@ void ListWidget::showContextMenu( [=] { _contextMenu = nullptr; })); } } - _contextMenu->addAction( - tr::lng_context_select_msg(tr::now), - crl::guard(this, [this, universalId] { - if (hasSelectedText()) { - clearSelected(); - } else if (_selected.size() == MaxSelectedItems) { - return; - } else if (_selected.empty()) { - update(); - } - applyItemSelection(universalId, FullSelection); - })); + if (!hasSelectRestriction()) { + _contextMenu->addAction( + tr::lng_context_select_msg(tr::now), + crl::guard(this, [this, universalId] { + if (hasSelectedText()) { + clearSelected(); + } else if (_selected.size() == MaxSelectedItems) { + return; + } else if (_selected.empty()) { + update(); + } + applyItemSelection(universalId, FullSelection); + })); + } } _contextMenu->setDestroyedCallback(crl::guard( @@ -1713,12 +1758,9 @@ void ListWidget::forwardItems(MessageIdsList &&items) { void ListWidget::deleteSelected() { if (const auto box = deleteItems(collectSelectedIds())) { - const auto weak = Ui::MakeWeak(this); - box->setDeleteConfirmedCallback([=]{ - if (const auto strong = weak.data()) { - strong->clearSelected(); - } - }); + box->setDeleteConfirmedCallback(crl::guard(this, [=]{ + clearSelected(); + })); } } @@ -1740,6 +1782,17 @@ DeleteMessagesBox *ListWidget::deleteItems(MessageIdsList &&items) { return nullptr; } +bool ListWidget::hasSelectRestriction() const { + if (_peer->allowsForwarding()) { + return false; + } else if (const auto chat = _peer->asChat()) { + return !chat->canDeleteMessages(); + } else if (const auto channel = _peer->asChannel()) { + return !channel->canDeleteMessages(); + } + return true; +} + void ListWidget::setActionBoxWeak(QPointer box) { if ((_actionBoxWeak = box)) { _actionBoxWeakLifetime = _actionBoxWeak->alive( @@ -2048,7 +2101,7 @@ void ListWidget::updateDragSelection() { if (swapStates) { std::swap(fromState, tillState); } - if (!fromState.itemId || !tillState.itemId) { + if (!fromState.itemId || !tillState.itemId || hasSelectRestriction()) { clearDragSelection(); return; } @@ -2184,12 +2237,12 @@ void ListWidget::mouseActionStart( applyItemSelection(_pressState.itemId, selStatus); _mouseAction = MouseAction::Selecting; repaintItem(pressLayout); - } else { + } else if (!hasSelectRestriction()) { _mouseAction = MouseAction::PrepareSelect; } } } - } else if (!_pressWasInactive) { + } else if (!_pressWasInactive && !hasSelectRestriction()) { _mouseAction = MouseAction::PrepareSelect; // start items select } } @@ -2366,7 +2419,9 @@ void ListWidget::mouseActionFinish( } void ListWidget::applyDragSelection() { - applyDragSelection(_selected); + if (!hasSelectRestriction()) { + applyDragSelection(_selected); + } clearDragSelection(); pushSelectedItems(); } diff --git a/Telegram/SourceFiles/info/media/info_media_list_widget.h b/Telegram/SourceFiles/info/media/info_media_list_widget.h index 4c4e39cec..19d39d6ff 100644 --- a/Telegram/SourceFiles/info/media/info_media_list_widget.h +++ b/Telegram/SourceFiles/info/media/info_media_list_widget.h @@ -176,6 +176,9 @@ private: int recountHeight(); void refreshHeight(); + void setupSelectRestriction(); + [[nodiscard]] bool hasSelectRestriction() const; + QMargins padding() const; bool isMyItem(not_null item) const; bool isItemLayout( diff --git a/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.cpp b/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.cpp index eef36b2b3..76a5e5ebb 100644 --- a/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.cpp +++ b/Telegram/SourceFiles/info/polls/info_polls_results_inner_widget.cpp @@ -331,7 +331,7 @@ void ListController::loadMoreRows() { delegate()->peerListRefreshRows(); } _loadRequestId = 0; - }).fail([=](const MTP::Error &error) { + }).fail([=] { _loadRequestId = 0; }).send(); } diff --git a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp index 5ac7077be..5cc0be32c 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp @@ -121,7 +121,7 @@ object_ptr InnerWidget::setupContent( ? request.ymin : mapFromGlobal(_members->mapToGlobal({ 0, request.ymin })).y(); auto max = (request.ymin < 0) - ? mapFromGlobal(_members->mapToGlobal({ 0, 0 })).y() + ? mapFromGlobal(_members->mapToGlobal(QPoint())).y() : (request.ymax < 0) ? request.ymax : mapFromGlobal(_members->mapToGlobal({ 0, request.ymax })).y(); diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp index 583a9e046..54b4b8ad9 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp @@ -322,7 +322,8 @@ void Gif::validateThumbnail( frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), (Images::Option::Smooth - | (good ? Images::Option::None : Images::Option::Blurred)), + | (good ? Images::Option::None : Images::Option::Blurred) + | Images::Option::TransparentBackground), size.width(), size.height()); } @@ -663,7 +664,9 @@ void Photo::validateThumbnail( _thumb = image->pixNoCache( frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), - Images::Option::Smooth | (good ? Images::Option(0) : Images::Option::Blurred), + (Images::Option::Smooth + | (good ? Images::Option(0) : Images::Option::Blurred) + | Images::Option::TransparentBackground), size.width(), size.height()); _thumbGood = good; @@ -819,7 +822,12 @@ void Video::prepareThumbnail(QSize size) const { w = width; } } - _thumb = thumb->pixNoCache(w * cIntRetinaFactor(), h * cIntRetinaFactor(), Images::Option::Smooth, width, height); + _thumb = thumb->pixNoCache( + w * cIntRetinaFactor(), + h * cIntRetinaFactor(), + Images::Option::Smooth | Images::Option::TransparentBackground, + width, + height); } } @@ -1153,7 +1161,12 @@ void Contact::prepareThumbnail(int width, int height) const { w = width; } } - _thumb = thumb->pixNoCache(w * cIntRetinaFactor(), h * cIntRetinaFactor(), Images::Option::Smooth, width, height); + _thumb = thumb->pixNoCache( + w * cIntRetinaFactor(), + h * cIntRetinaFactor(), + Images::Option::Smooth | Images::Option::TransparentBackground, + width, + height); } Article::Article( @@ -1306,7 +1319,12 @@ void Article::prepareThumbnail(int width, int height) const { w = width; } } - _thumb = thumb->pixNoCache(w * cIntRetinaFactor(), h * cIntRetinaFactor(), Images::Option::Smooth, width, height); + _thumb = thumb->pixNoCache( + w * cIntRetinaFactor(), + h * cIntRetinaFactor(), + Images::Option::Smooth | Images::Option::TransparentBackground, + width, + height); } Game::Game(not_null context, not_null result) @@ -1524,7 +1542,8 @@ void Game::validateThumbnail(Image *image, QSize size, bool good) const { w * cIntRetinaFactor(), h * cIntRetinaFactor(), (Images::Option::Smooth - | (good ? Images::Option::None : Images::Option::Blurred)), + | (good ? Images::Option::None : Images::Option::Blurred) + | Images::Option::TransparentBackground), size.width(), size.height()); } diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp index 811d93d39..4b764bc25 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp @@ -190,7 +190,7 @@ ClickHandlerPtr ItemBase::getResultPreviewHandler() const { _result->_content_url, false); } else if (const auto document = _result->_document - ; document && document->createMediaView()->canBePlayed()) { + ; document && document->createMediaView()->canBePlayed(nullptr)) { return std::make_shared(); } else if (_result->_photo) { return std::make_shared(); diff --git a/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp b/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp index 7cbe35c99..02ebc1933 100644 --- a/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_file_origin.h" #include "data/data_user.h" #include "data/data_changes.h" +#include "data/data_chat_participant_status.h" #include "inline_bots/inline_bot_result.h" #include "inline_bots/inline_bot_layout_item.h" #include "lang/lang_keys.h" diff --git a/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp b/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp index d9d4123ca..0f317844a 100644 --- a/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp @@ -447,7 +447,7 @@ void Widget::onInlineRequest() { MTP_string(nextOffset) )).done([=](const MTPmessages_BotResults &result) { inlineResultsDone(result); - }).fail([=](const MTP::Error &error) { + }).fail([=] { // show error? _requesting.fire(false); _inlineRequestId = 0; diff --git a/Telegram/SourceFiles/intro/intro_phone.cpp b/Telegram/SourceFiles/intro/intro_phone.cpp index 28b374aca..9b5451b2b 100644 --- a/Telegram/SourceFiles/intro/intro_phone.cpp +++ b/Telegram/SourceFiles/intro/intro_phone.cpp @@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/boxes/confirm_box.h" #include "boxes/phone_banned_box.h" #include "core/application.h" +#include "countries/countries_instance.h" // Countries::Groups namespace Intro { namespace details { @@ -44,7 +45,10 @@ PhoneWidget::PhoneWidget( : Step(parent, account, data) , _country(this, st::introCountry) , _code(this, st::introCountryCode) -, _phone(this, st::introPhone) +, _phone( + this, + st::introPhone, + [](const QString &s) { return Countries::Groups(s); }) , _checkRequestTimer([=] { checkRequest(); }) { _phone->frontBackspaceEvent( ) | rpl::start_with_next([=](not_null e) { @@ -175,7 +179,7 @@ void PhoneWidget::submit() { MTP_string(_sentPhone), MTP_int(cApiId()), MTP_string(cApiHash()), - MTP_codeSettings(MTP_flags(0)) + MTP_codeSettings(MTP_flags(0), MTP_vector()) )).done([=](const MTPauth_SentCode &result) { phoneSubmitDone(result); }).fail([=](const MTP::Error &error) { diff --git a/Telegram/SourceFiles/intro/intro_qr.h b/Telegram/SourceFiles/intro/intro_qr.h index 99d19a316..7cc166a4b 100644 --- a/Telegram/SourceFiles/intro/intro_qr.h +++ b/Telegram/SourceFiles/intro/intro_qr.h @@ -11,13 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "intro/intro_step.h" #include "base/timer.h" -namespace Ui { -class PhonePartInput; -class CountryCodeInput; -class RoundButton; -class FlatLabel; -} // namespace Ui - namespace Intro { namespace details { diff --git a/Telegram/SourceFiles/intro/intro_step.cpp b/Telegram/SourceFiles/intro/intro_step.cpp index cb43cf8b0..00593c72b 100644 --- a/Telegram/SourceFiles/intro/intro_step.cpp +++ b/Telegram/SourceFiles/intro/intro_step.cpp @@ -166,7 +166,7 @@ void Step::finish(const MTPUser &user, QImage &&photo) { api().request(MTPmessages_GetDialogFilters( )).done([=](const MTPVector &result) { createSession(user, photo, result.v); - }).fail([=](const MTP::Error &error) { + }).fail([=] { createSession(user, photo, QVector()); }).send(); } @@ -309,22 +309,20 @@ bool Step::paintAnimated(Painter &p, QRect clip) { } void Step::fillSentCodeData(const MTPDauth_sentCode &data) { - const auto &type = data.vtype(); - switch (type.type()) { - case mtpc_auth_sentCodeTypeApp: { + data.vtype().match([&](const MTPDauth_sentCodeTypeApp &data) { getData()->codeByTelegram = true; - getData()->codeLength = type.c_auth_sentCodeTypeApp().vlength().v; - } break; - case mtpc_auth_sentCodeTypeSms: { + getData()->codeLength = data.vlength().v; + }, [&](const MTPDauth_sentCodeTypeSms &data) { getData()->codeByTelegram = false; - getData()->codeLength = type.c_auth_sentCodeTypeSms().vlength().v; - } break; - case mtpc_auth_sentCodeTypeCall: { + getData()->codeLength = data.vlength().v; + }, [&](const MTPDauth_sentCodeTypeCall &data) { getData()->codeByTelegram = false; - getData()->codeLength = type.c_auth_sentCodeTypeCall().vlength().v; - } break; - case mtpc_auth_sentCodeTypeFlashCall: LOG(("Error: should not be flashcall!")); break; - } + getData()->codeLength = data.vlength().v; + }, [&](const MTPDauth_sentCodeTypeFlashCall &) { + LOG(("Error: should not be flashcall!")); + }, [&](const MTPDauth_sentCodeTypeMissedCall &data) { + LOG(("Error: should not be missedcall!")); + }); } void Step::showDescription() { diff --git a/Telegram/SourceFiles/intro/intro_widget.cpp b/Telegram/SourceFiles/intro/intro_widget.cpp index aa3ad25a9..82d33af46 100644 --- a/Telegram/SourceFiles/intro/intro_widget.cpp +++ b/Telegram/SourceFiles/intro/intro_widget.cpp @@ -493,7 +493,7 @@ void Widget::resetAccount() { } _resetRequest = _api->request(MTPaccount_DeleteAccount( MTP_string("Forgot password") - )).done([=](const MTPBool &result) { + )).done([=] { _resetRequest = 0; Ui::hideLayer(); diff --git a/Telegram/SourceFiles/lang/lang_cloud_manager.cpp b/Telegram/SourceFiles/lang/lang_cloud_manager.cpp index 2113bcb6d..7e9299a19 100644 --- a/Telegram/SourceFiles/lang/lang_cloud_manager.cpp +++ b/Telegram/SourceFiles/lang/lang_cloud_manager.cpp @@ -232,7 +232,7 @@ void CloudManager::requestLangPackDifference(Pack pack) { )).done([=](const MTPLangPackDifference &result) { packRequestId(pack) = 0; applyLangPackDifference(result); - }).fail([=](const MTP::Error &error) { + }).fail([=] { packRequestId(pack) = 0; }).send(); } else { @@ -242,7 +242,7 @@ void CloudManager::requestLangPackDifference(Pack pack) { )).done([=](const MTPLangPackDifference &result) { packRequestId(pack) = 0; applyLangPackDifference(result); - }).fail([=](const MTP::Error &error) { + }).fail([=] { packRequestId(pack) = 0; }).send(); } @@ -320,7 +320,7 @@ void CloudManager::requestLanguageList() { _languageListChanged.fire({}); } _languagesRequestId = 0; - }).fail([=](const MTP::Error &error) { + }).fail([=] { _languagesRequestId = 0; }).send(); } @@ -509,7 +509,7 @@ void CloudManager::switchToLanguage(const Language &data) { tr::lng_cancel(tr::now), [=] { performSwitchAndRestart(data); }), Ui::LayerOption::KeepOther); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _getKeysForSwitchRequestId = 0; }).send(); } diff --git a/Telegram/SourceFiles/main/main_app_config.cpp b/Telegram/SourceFiles/main/main_app_config.cpp index 5c43c0bb9..fef8d9702 100644 --- a/Telegram/SourceFiles/main/main_app_config.cpp +++ b/Telegram/SourceFiles/main/main_app_config.cpp @@ -51,7 +51,7 @@ void AppConfig::refresh() { DEBUG_LOG(("getAppConfig result handled.")); } _refreshed.fire({}); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _requestId = 0; refreshDelayed(); }).send(); diff --git a/Telegram/SourceFiles/main/main_session.cpp b/Telegram/SourceFiles/main/main_session.cpp index bdd5b40bb..a98e2c5ed 100644 --- a/Telegram/SourceFiles/main/main_session.cpp +++ b/Telegram/SourceFiles/main/main_session.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_account.h" #include "main/main_domain.h" #include "main/main_session_settings.h" +#include "main/session/send_as_peers.h" #include "mtproto/mtproto_config.h" #include "chat_helpers/stickers_emoji_pack.h" #include "chat_helpers/stickers_dice_pack.h" @@ -77,9 +78,11 @@ Session::Session( , _uploader(std::make_unique(_api.get())) , _storage(std::make_unique()) , _data(std::make_unique(this)) +, _userId(user.c_user().vid()) , _user(_data->processUser(user)) , _emojiStickersPack(std::make_unique(this)) , _diceStickersPacks(std::make_unique(this)) +, _sendAsPeers(std::make_unique(this)) , _supportHelper(Support::Helper::Create(this)) , _saveSettingsTimer([=] { saveSettings(); }) { Expects(_settings != nullptr); @@ -211,18 +214,15 @@ uint64 Session::uniqueId() const { } UserId Session::userId() const { - return peerToUser(_user->id); + return _userId; } PeerId Session::userPeerId() const { - return _user->id; + return _userId; } -bool Session::validateSelf(const MTPUser &user) { - if (user.type() != mtpc_user || !user.c_user().is_self()) { - LOG(("API Error: bad self user received.")); - return false; - } else if (UserId(user.c_user().vid()) != userId()) { +bool Session::validateSelf(UserId id) { + if (id != userId()) { LOG(("Auth Error: wrong self user received.")); crl::on_main(this, [=] { _account->logOut(); }); return false; diff --git a/Telegram/SourceFiles/main/main_session.h b/Telegram/SourceFiles/main/main_session.h index 01142396a..b40f908b9 100644 --- a/Telegram/SourceFiles/main/main_session.h +++ b/Telegram/SourceFiles/main/main_session.h @@ -58,6 +58,7 @@ namespace Main { class Account; class Domain; class SessionSettings; +class SendAsPeers; class Session final : public base::has_weak_ptr { public: @@ -81,7 +82,7 @@ public: [[nodiscard]] not_null user() const { return _user; } - bool validateSelf(const MTPUser &user); + bool validateSelf(UserId id); [[nodiscard]] Data::Changes &changes() const { return *_changes; @@ -113,6 +114,9 @@ public: [[nodiscard]] SessionSettings &settings() const { return *_settings; } + [[nodiscard]] SendAsPeers &sendAsPeers() const { + return *_sendAsPeers; + } void saveSettings(); void saveSettingsDelayed(crl::time delay = kDefaultSaveDelay); @@ -175,11 +179,13 @@ private: // _data depends on _downloader / _uploader. const std::unique_ptr _data; + const UserId _userId; const not_null _user; // _emojiStickersPack depends on _data. const std::unique_ptr _emojiStickersPack; const std::unique_ptr _diceStickersPacks; + const std::unique_ptr _sendAsPeers; const std::unique_ptr _supportHelper; diff --git a/Telegram/SourceFiles/main/session/send_as_peers.cpp b/Telegram/SourceFiles/main/session/send_as_peers.cpp new file mode 100644 index 000000000..4d1532318 --- /dev/null +++ b/Telegram/SourceFiles/main/session/send_as_peers.cpp @@ -0,0 +1,151 @@ +/* +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 "main/session/send_as_peers.h" + +#include "data/data_user.h" +#include "data/data_channel.h" +#include "data/data_session.h" +#include "data/data_changes.h" +#include "main/main_session.h" +#include "apiwrap.h" + +namespace Main { +namespace { + +constexpr auto kRequestEach = 30 * crl::time(1000); + +} // namespace + +SendAsPeers::SendAsPeers(not_null session) +: _session(session) +, _onlyMe({ session->user() }) { + _session->changes().peerUpdates( + Data::PeerUpdate::Flag::Rights + ) | rpl::map([=](const Data::PeerUpdate &update) { + const auto peer = update.peer; + const auto channel = peer->asChannel(); + return std::tuple( + peer, + peer->amAnonymous(), + channel ? channel->isPublic() : false); + }) | rpl::distinct_until_changed( + ) | rpl::filter([=](not_null peer, bool, bool) { + return _lists.contains(peer); + }) | rpl::start_with_next([=](not_null peer, bool, bool) { + refresh(peer, true); + }, _lifetime); +} + +bool SendAsPeers::shouldChoose(not_null peer) { + refresh(peer); + return peer->canWrite() && (list(peer).size() > 1); +} + +void SendAsPeers::refresh(not_null peer, bool force) { + if (!peer->isMegagroup()) { + return; + } + const auto now = crl::now(); + const auto i = _lastRequestTime.find(peer); + const auto when = (i == end(_lastRequestTime)) ? -1 : i->second; + if (!force && (when >= 0 && now < when + kRequestEach)) { + return; + } + _lastRequestTime[peer] = now; + request(peer); +} + +const std::vector> &SendAsPeers::list( + not_null peer) const { + const auto i = _lists.find(peer); + return (i != end(_lists)) ? i->second : _onlyMe; +} + +rpl::producer> SendAsPeers::updated() const { + return _updates.events(); +} + +void SendAsPeers::saveChosen( + not_null peer, + not_null chosen) { + peer->session().api().request(MTPmessages_SaveDefaultSendAs( + peer->input, + chosen->input + )).send(); + + setChosen(peer, chosen->id); +} + +void SendAsPeers::setChosen(not_null peer, PeerId chosenId) { + if (chosen(peer) == chosenId) { + return; + } + const auto fallback = peer->amAnonymous() + ? peer + : peer->session().user(); + if (fallback->id == chosenId) { + _chosen.remove(peer); + } else { + _chosen[peer] = chosenId; + } + _updates.fire_copy(peer); +} + +PeerId SendAsPeers::chosen(not_null peer) const { + const auto i = _chosen.find(peer); + return (i != end(_chosen)) ? i->second : PeerId(); +} + +not_null SendAsPeers::resolveChosen( + not_null peer) const { + return ResolveChosen(peer, list(peer), chosen(peer)); +} + +not_null SendAsPeers::ResolveChosen( + not_null peer, + const std::vector> &list, + PeerId chosen) { + const auto i = ranges::find(list, chosen, &PeerData::id); + return (i != end(list)) + ? (*i) + : !list.empty() + ? list.front() + : (peer->isMegagroup() && peer->amAnonymous()) + ? peer + : peer->session().user(); +} + +void SendAsPeers::request(not_null peer) { + peer->session().api().request(MTPchannels_GetSendAs( + peer->input + )).done([=](const MTPchannels_SendAsPeers &result) { + auto list = std::vector>(); + auto &owner = peer->owner(); + result.match([&](const MTPDchannels_sendAsPeers &data) { + owner.processUsers(data.vusers()); + owner.processChats(data.vchats()); + for (const auto &id : data.vpeers().v) { + if (const auto peer = owner.peerLoaded(peerFromMTP(id))) { + list.push_back(peer); + } + } + }); + if (list.size() > 1) { + auto &now = _lists[peer]; + if (now != list) { + now = std::move(list); + _updates.fire_copy(peer); + } + } else if (const auto i = _lists.find(peer); i != end(_lists)) { + _lists.erase(i); + _updates.fire_copy(peer); + } + }).send(); +} + +} // namespace Main diff --git a/Telegram/SourceFiles/main/session/send_as_peers.h b/Telegram/SourceFiles/main/session/send_as_peers.h new file mode 100644 index 000000000..302bdf02d --- /dev/null +++ b/Telegram/SourceFiles/main/session/send_as_peers.h @@ -0,0 +1,57 @@ +/* +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; + +namespace Main { + +class Session; + +class SendAsPeers final { +public: + explicit SendAsPeers(not_null session); + + bool shouldChoose(not_null peer); + void refresh(not_null peer, bool force = false); + [[nodiscard]] const std::vector> &list( + not_null peer) const; + [[nodiscard]] rpl::producer> updated() const; + + void saveChosen(not_null peer, not_null chosen); + void setChosen(not_null peer, PeerId chosenId); + [[nodiscard]] PeerId chosen(not_null peer) const; + + // If !list(peer).empty() then the result will be from that list. + [[nodiscard]] not_null resolveChosen( + not_null peer) const; + + [[nodiscard]] static not_null ResolveChosen( + not_null peer, + const std::vector> &list, + PeerId chosen); + +private: + void request(not_null peer); + + const not_null _session; + const std::vector> _onlyMe; + + base::flat_map< + not_null, + std::vector>> _lists; + base::flat_map, crl::time> _lastRequestTime; + base::flat_map, PeerId> _chosen; + + rpl::event_stream> _updates; + + rpl::lifetime _lifetime; + +}; + +} // namespace Main diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 50b597e79..2ed2c486d 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -81,7 +81,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/audio/media_audio.h" #include "media/player/media_player_panel.h" #include "media/player/media_player_widget.h" -#include "media/player/media_player_volume_controller.h" +#include "media/player/media_player_dropdown.h" #include "media/player/media_player_instance.h" #include "media/player/media_player_float.h" #include "base/qthelp_regex.h" @@ -332,20 +332,8 @@ MainWidget::MainWidget( QCoreApplication::instance()->installEventFilter(this); - subscribe(Media::Player::instance()->playerWidgetOver(), [this](bool over) { - if (over) { - if (_playerPlaylist->isHidden()) { - auto position = mapFromGlobal(QCursor::pos()).x(); - auto bestPosition = _playerPlaylist->bestPositionFor(position); - if (rtl()) bestPosition = position + 2 * (position - bestPosition) - _playerPlaylist->width(); - updateMediaPlaylistPosition(bestPosition); - } - _playerPlaylist->showFromOther(); - } else { - _playerPlaylist->hideFromOther(); - } - }); - subscribe(Media::Player::instance()->tracksFinishedNotifier(), [this](AudioMsgId::Type type) { + Media::Player::instance()->tracksFinished( + ) | rpl::start_with_next([=](AudioMsgId::Type type) { if (type == AudioMsgId::Type::Voice) { const auto songState = Media::Player::instance()->getState(AudioMsgId::Type::Song); if (!songState.id || IsStoppedOrStopping(songState.state)) { @@ -357,7 +345,7 @@ MainWidget::MainWidget( closeBothPlayers(); } } - }); + }, lifetime()); _controller->adaptive().changes( ) | rpl::start_with_next([=] { @@ -816,7 +804,6 @@ void MainWidget::closeBothPlayers() { if (_player) { _player->hide(anim::type::normal); } - _playerVolume.destroyDelayed(); _playerPlaylist->hideIgnoringEnterEvents(); Media::Player::instance()->stop(AudioMsgId::Type::Voice); @@ -835,7 +822,7 @@ void MainWidget::createPlayer() { if (!_player) { _player.create( this, - object_ptr(this, &session()), + object_ptr(this, this, _controller), _controller->adaptive().oneColumnValue()); rpl::merge( _player->heightValue() | rpl::map_to(true), @@ -848,8 +835,21 @@ void MainWidget::createPlayer() { not_null item) { _controller->showPeerHistoryAtItem(item); }); - _playerVolume.create(this, _controller); - _player->entity()->volumeWidgetCreated(_playerVolume); + + _player->entity()->togglePlaylistRequests( + ) | rpl::start_with_next([=](bool shown) { + if (!shown) { + _playerPlaylist->hideFromOther(); + return; + } else if (_playerPlaylist->isHidden()) { + auto position = mapFromGlobal(QCursor::pos()).x(); + auto bestPosition = _playerPlaylist->bestPositionFor(position); + if (rtl()) bestPosition = position + 2 * (position - bestPosition) - _playerPlaylist->width(); + updateMediaPlaylistPosition(bestPosition); + } + _playerPlaylist->showFromOther(); + }, _player->lifetime()); + orderWidgets(); if (_a_show.animating()) { _player->show(anim::type::instant); @@ -883,7 +883,6 @@ void MainWidget::playerHeightUpdated() { if (!_playerHeight && _player->isHidden()) { const auto state = Media::Player::instance()->getState(Media::Player::instance()->getActiveType()); if (!state.id || Media::Player::IsStoppedOrStopping(state.state)) { - _playerVolume.destroyDelayed(); _player.destroyDelayed(); } } @@ -1077,7 +1076,7 @@ SendMenu::Type MainWidget::sendMenuType() const { } bool MainWidget::sendExistingDocument(not_null document) { - return sendExistingDocument(document, Api::SendOptions()); + return sendExistingDocument(document, {}); } bool MainWidget::sendExistingDocument( @@ -1263,8 +1262,9 @@ void MainWidget::ui_showPeerHistory( PeerId peerId, const SectionShow ¶ms, MsgId showAtMsgId) { - - if (auto peer = session().data().peerLoaded(peerId)) { + if (peerId && _controller->window().locked()) { + return; + } else if (auto peer = session().data().peerLoaded(peerId)) { if (peer->migrateTo()) { peer = peer->migrateTo(); peerId = peer->id; @@ -1392,6 +1392,7 @@ void MainWidget::ui_showPeerHistory( if (noPeer) { _controller->setActiveChatEntry(Dialogs::Key()); + _controller->setChatStyleTheme(controller()->defaultChatTheme()); } if (onlyDialogs) { @@ -1530,11 +1531,7 @@ Window::SectionSlideParams MainWidget::prepareShowAnimation( floatPlayerHideAll(); if (_player) { - _player->hideShadow(); - } - auto playerVolumeVisible = _playerVolume && !_playerVolume->isHidden(); - if (playerVolumeVisible) { - _playerVolume->hide(); + _player->entity()->hideShadowAndDropdowns(); } auto playerPlaylistVisible = !_playerPlaylist->isHidden(); if (playerPlaylistVisible) { @@ -1560,14 +1557,11 @@ Window::SectionSlideParams MainWidget::prepareShowAnimation( height() - sectionTop)); } - if (playerVolumeVisible) { - _playerVolume->show(); - } if (playerPlaylistVisible) { _playerPlaylist->show(); } if (_player) { - _player->showShadow(); + _player->entity()->showShadowAndDropdowns(); } floatPlayerShowVisible(); @@ -1591,6 +1585,9 @@ void MainWidget::showNewSection( const SectionShow ¶ms) { using Column = Window::Column; + if (_controller->window().locked()) { + return; + } auto saveInStack = (params.way == SectionShow::Way::Forward); const auto thirdSectionTop = getThirdSectionTop(); const auto newThirdGeometry = QRect( @@ -1824,9 +1821,6 @@ void MainWidget::orderWidgets() { if (_callTopBar) { _callTopBar->raise(); } - if (_playerVolume) { - _playerVolume->raise(); - } _sideShadow->raise(); if (_thirdShadow) { _thirdShadow->raise(); @@ -1838,27 +1832,19 @@ void MainWidget::orderWidgets() { _thirdColumnResizeArea->raise(); } _connecting->raise(); - _playerPlaylist->raise(); floatPlayerRaiseAll(); + _playerPlaylist->raise(); + if (_player) { + _player->entity()->raiseDropdowns(); + } if (_hider) _hider->raise(); } -QRect MainWidget::historyRect() const { - QRect r(_history->historyRect()); - r.moveLeft(r.left() + _history->x()); - r.moveTop(r.top() + _history->y()); - return r; -} - QPixmap MainWidget::grabForShowAnimation(const Window::SectionSlideParams ¶ms) { QPixmap result; floatPlayerHideAll(); if (_player) { - _player->hideShadow(); - } - auto playerVolumeVisible = _playerVolume && !_playerVolume->isHidden(); - if (playerVolumeVisible) { - _playerVolume->hide(); + _player->entity()->hideShadowAndDropdowns(); } auto playerPlaylistVisible = !_playerPlaylist->isHidden(); if (playerPlaylistVisible) { @@ -1887,14 +1873,11 @@ QPixmap MainWidget::grabForShowAnimation(const Window::SectionSlideParams ¶m _thirdShadow->show(); } } - if (playerVolumeVisible) { - _playerVolume->show(); - } if (playerPlaylistVisible) { _playerPlaylist->show(); } if (_player) { - _player->showShadow(); + _player->entity()->showShadowAndDropdowns(); } floatPlayerShowVisible(); return result; @@ -2179,7 +2162,9 @@ void MainWidget::updateControlsGeometry() { _mainSection->setGeometryWithTopMoved(mainSectionGeometry, _contentScrollAddToY); } refreshResizeAreas(); - updateMediaPlayerPosition(); + if (_player) { + _player->entity()->updateDropdownsGeometry(); + } updateMediaPlaylistPosition(_playerPlaylist->x()); _contentScrollAddToY = 0; @@ -2378,14 +2363,6 @@ void MainWidget::updateThirdColumnToCurrentChat( } } -void MainWidget::updateMediaPlayerPosition() { - if (_player && _playerVolume) { - auto relativePosition = _player->entity()->getPositionForVolumeWidget(); - auto playerMargins = _playerVolume->getMargin(); - _playerVolume->moveToLeft(_player->x() + relativePosition.x() - playerMargins.left(), _player->y() + relativePosition.y() - playerMargins.top()); - } -} - void MainWidget::updateMediaPlaylistPosition(int x) { if (_player) { auto playlistLeft = x; @@ -2407,12 +2384,10 @@ void MainWidget::returnTabbedSelector() { } } -void MainWidget::keyPressEvent(QKeyEvent *e) { -} - bool MainWidget::eventFilter(QObject *o, QEvent *e) { if (e->type() == QEvent::FocusIn) { - if (const auto widget = qobject_cast(o)) { + if (o->isWidgetType()) { + const auto widget = static_cast(o); if (_history == widget || _history->isAncestorOf(widget) || (_mainSection && (_mainSection == widget || _mainSection->isAncestorOf(widget))) || (_thirdSection && (_thirdSection == widget || _thirdSection->isAncestorOf(widget)))) { @@ -2540,9 +2515,8 @@ void MainWidget::mentionUser(PeerData *peer) { } bool MainWidget::contentOverlapped(const QRect &globalRect) { - return (_history->contentOverlapped(globalRect) - || _playerPlaylist->overlaps(globalRect) - || (_playerVolume && _playerVolume->overlaps(globalRect))); + return _history->contentOverlapped(globalRect) + || _playerPlaylist->overlaps(globalRect); } void MainWidget::activate() { diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 861dfd8da..6cf0ba7ea 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -55,7 +55,6 @@ class Widget; namespace Media { namespace Player { class Widget; -class VolumeWidget; class Panel; struct TrackState; } // namespace Player @@ -151,7 +150,6 @@ public: void showBackFromStack( const SectionShow ¶ms); void orderWidgets(); - QRect historyRect() const; QPixmap grabForShowAnimation(const Window::SectionSlideParams ¶ms); void checkMainSectionToLayer(); @@ -247,7 +245,6 @@ public Q_SLOTS: protected: void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; - void keyPressEvent(QKeyEvent *e) override; bool eventFilter(QObject *o, QEvent *e) override; private: @@ -255,7 +252,6 @@ private: void handleAdaptiveLayoutUpdate(); void updateWindowAdaptiveLayout(); void handleAudioUpdate(const Media::Player::TrackState &state); - void updateMediaPlayerPosition(); void updateMediaPlaylistPosition(int x); void updateControlsGeometry(); void updateDialogsWidthAnimated(); @@ -372,7 +368,6 @@ private: object_ptr> _player = { nullptr }; - object_ptr _playerVolume = { nullptr }; object_ptr _playerPlaylist; bool _playerUsingPanel = false; diff --git a/Telegram/SourceFiles/media/audio/media_audio.cpp b/Telegram/SourceFiles/media/audio/media_audio.cpp index fa9ac0ddb..6a7205be6 100644 --- a/Telegram/SourceFiles/media/audio/media_audio.cpp +++ b/Telegram/SourceFiles/media/audio/media_audio.cpp @@ -331,7 +331,10 @@ void Mixer::Track::createStream(AudioMsgId::Type type) { alSourcei(stream.source, AL_SOURCE_RELATIVE, 1); alSourcei(stream.source, AL_ROLLOFF_FACTOR, 0); if (alIsExtensionPresent("AL_SOFT_direct_channels_remix")) { - alSourcei(stream.source, alGetEnumValue("AL_DIRECT_CHANNELS_SOFT"), 2); + alSourcei( + stream.source, + alGetEnumValue("AL_DIRECT_CHANNELS_SOFT"), + alcGetEnumValue(nullptr, "AL_REMIX_UNMATCHED_SOFT")); } alGenBuffers(3, stream.buffers); if (speedEffect) { diff --git a/Telegram/SourceFiles/media/audio/media_openal_functions.h b/Telegram/SourceFiles/media/audio/media_openal_functions.h index 939ce982a..a11813623 100644 --- a/Telegram/SourceFiles/media/audio/media_openal_functions.h +++ b/Telegram/SourceFiles/media/audio/media_openal_functions.h @@ -7,48 +7,48 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include +#include namespace OpenAL { /* Effect object functions */ -inline LPALGENEFFECTS alGenEffects; -inline LPALDELETEEFFECTS alDeleteEffects; -inline LPALISEFFECT alIsEffect; -inline LPALEFFECTI alEffecti; -inline LPALEFFECTIV alEffectiv; -inline LPALEFFECTF alEffectf; -inline LPALEFFECTFV alEffectfv; -inline LPALGETEFFECTI alGetEffecti; -inline LPALGETEFFECTIV alGetEffectiv; -inline LPALGETEFFECTF alGetEffectf; -inline LPALGETEFFECTFV alGetEffectfv; +inline void (AL_APIENTRY *alGenEffects)(ALsizei, ALuint*); +inline void (AL_APIENTRY *alDeleteEffects)(ALsizei, const ALuint*); +inline ALboolean (AL_APIENTRY *alIsEffect)(ALuint); +inline void (AL_APIENTRY *alEffecti)(ALuint, ALenum, ALint); +inline void (AL_APIENTRY *alEffectiv)(ALuint, ALenum, const ALint*); +inline void (AL_APIENTRY *alEffectf)(ALuint, ALenum, ALfloat); +inline void (AL_APIENTRY *alEffectfv)(ALuint, ALenum, const ALfloat*); +inline void (AL_APIENTRY *alGetEffecti)(ALuint, ALenum, ALint*); +inline void (AL_APIENTRY *alGetEffectiv)(ALuint, ALenum, ALint*); +inline void (AL_APIENTRY *alGetEffectf)(ALuint, ALenum, ALfloat*); +inline void (AL_APIENTRY *alGetEffectfv)(ALuint, ALenum, ALfloat*); /* Filter object functions */ -inline LPALGENFILTERS alGenFilters; -inline LPALDELETEFILTERS alDeleteFilters; -inline LPALISFILTER alIsFilter; -inline LPALFILTERI alFilteri; -inline LPALFILTERIV alFilteriv; -inline LPALFILTERF alFilterf; -inline LPALFILTERFV alFilterfv; -inline LPALGETFILTERI alGetFilteri; -inline LPALGETFILTERIV alGetFilteriv; -inline LPALGETFILTERF alGetFilterf; -inline LPALGETFILTERFV alGetFilterfv; +inline void (AL_APIENTRY *alGenFilters)(ALsizei, ALuint*); +inline void (AL_APIENTRY *alDeleteFilters)(ALsizei, const ALuint*); +inline ALboolean (AL_APIENTRY *alIsFilter)(ALuint); +inline void (AL_APIENTRY *alFilteri)(ALuint, ALenum, ALint); +inline void (AL_APIENTRY *alFilteriv)(ALuint, ALenum, const ALint*); +inline void (AL_APIENTRY *alFilterf)(ALuint, ALenum, ALfloat); +inline void (AL_APIENTRY *alFilterfv)(ALuint, ALenum, const ALfloat*); +inline void (AL_APIENTRY *alGetFilteri)(ALuint, ALenum, ALint*); +inline void (AL_APIENTRY *alGetFilteriv)(ALuint, ALenum, ALint*); +inline void (AL_APIENTRY *alGetFilterf)(ALuint, ALenum, ALfloat*); +inline void (AL_APIENTRY *alGetFilterfv)(ALuint, ALenum, ALfloat*); /* Auxiliary Effect Slot object functions */ -inline LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots; -inline LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots; -inline LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot; -inline LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti; -inline LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv; -inline LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf; -inline LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv; -inline LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti; -inline LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv; -inline LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf; -inline LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv; +inline void (AL_APIENTRY *alGenAuxiliaryEffectSlots)(ALsizei, ALuint*); +inline void (AL_APIENTRY *alDeleteAuxiliaryEffectSlots)(ALsizei, const ALuint*); +inline ALboolean (AL_APIENTRY *alIsAuxiliaryEffectSlot)(ALuint); +inline void (AL_APIENTRY *alAuxiliaryEffectSloti)(ALuint, ALenum, ALint); +inline void (AL_APIENTRY *alAuxiliaryEffectSlotiv)(ALuint, ALenum, const ALint*); +inline void (AL_APIENTRY *alAuxiliaryEffectSlotf)(ALuint, ALenum, ALfloat); +inline void (AL_APIENTRY *alAuxiliaryEffectSlotfv)(ALuint, ALenum, const ALfloat*); +inline void (AL_APIENTRY *alGetAuxiliaryEffectSloti)(ALuint, ALenum, ALint*); +inline void (AL_APIENTRY *alGetAuxiliaryEffectSlotiv)(ALuint, ALenum, ALint*); +inline void (AL_APIENTRY *alGetAuxiliaryEffectSlotf)(ALuint, ALenum, ALfloat*); +inline void (AL_APIENTRY *alGetAuxiliaryEffectSlotfv)(ALuint, ALenum, ALfloat*); void LoadEFXExtension(); bool HasEFXExtension(); diff --git a/Telegram/SourceFiles/media/player/media_player.style b/Telegram/SourceFiles/media/player/media_player.style index 2304220f9..6c40005c5 100644 --- a/Telegram/SourceFiles/media/player/media_player.style +++ b/Telegram/SourceFiles/media/player/media_player.style @@ -29,12 +29,12 @@ mediaPlayerButton: MediaPlayerButton { mediaPlayerButtonSize: size(25px, 30px); mediaPlayerButtonPosition: point(5px, 10px); -mediaPlayerSkipIconPosition: point(5px, 12px); +mediaPlayerWideWidth: 460px; mediaPlayerHeight: 35px; mediaPlayerPadding: 8px; mediaPlayerNameTop: 22px; -mediaPlayerPlayLeft: 7px; +mediaPlayerPlayLeft: 9px; mediaPlayerPlaySkip: 1px; mediaPlayerPlayTop: 0px; mediaPlayerCloseRight: 0px; @@ -47,29 +47,63 @@ mediaPlayerTime: LabelSimple(defaultLabelSimple) { } mediaPlayerRepeatButton: IconButton { - width: 31px; - height: 30px; + width: 30px; + height: 34px; icon: icon { - { "player_repeat", mediaPlayerActiveFg, point(9px, 11px) } + { "player/player_repeat", mediaPlayerActiveFg } }; - iconPosition: point(0px, 0px); + iconPosition: point(3px, 6px); - rippleAreaPosition: point(3px, 5px); - rippleAreaSize: 25px; + rippleAreaPosition: point(2px, 5px); + rippleAreaSize: 26px; ripple: RippleAnimation(defaultRippleAnimation) { color: lightButtonBgOver; } } mediaPlayerRepeatDisabledIcon: icon { - { "player_repeat", menuIconFg, point(9px, 11px)} + { "player/player_repeat", menuIconFg } }; mediaPlayerRepeatDisabledIconOver: icon { - { "player_repeat", menuIconFgOver, point(9px, 11px)} + { "player/player_repeat", menuIconFgOver } +}; +mediaPlayerRepeatOneIcon: icon { + { "player/player_repeat_single", mediaPlayerActiveFg } +}; +mediaPlayerRepeatOneDisabledIcon: icon { + { "player/player_repeat_single", menuIconFg } +}; +mediaPlayerRepeatOneDisabledIconOver: icon { + { "player/player_repeat_single", menuIconFgOver } +}; +mediaPlayerReverseIcon: icon { + { "player/player_order", mediaPlayerActiveFg } +}; +mediaPlayerReverseDisabledIcon: icon { + { "player/player_order", menuIconFg } +}; +mediaPlayerReverseDisabledIconOver: icon { + { "player/player_order", menuIconFgOver } +}; +mediaPlayerShuffleIcon: icon { + { "player/player_shuffle", mediaPlayerActiveFg } }; mediaPlayerRepeatDisabledRippleBg: windowBgOver; -mediaPlayerRepeatInactiveIcon: icon { - { "player_repeat", mediaPlayerInactiveFg, point(9px, 11px)} + +mediaPlayerPlayButton: IconButton(mediaPlayerRepeatButton) { + width: 24px; + icon: icon{ + { "player/player_play", mediaPlayerActiveFg } + }; + iconPosition: point(0px, 5px); + rippleAreaPosition: point(0px, 5px); + rippleAreaSize: 24px; +} +mediaPlayerPauseIcon: icon{ + { "player/player_pause", mediaPlayerActiveFg } +}; +mediaPlayerCancelIcon: icon{ + { "player/panel_close", mediaPlayerActiveFg } }; mediaPlayerSpeedButton: IconButton { @@ -77,7 +111,7 @@ mediaPlayerSpeedButton: IconButton { height: 30px; icon: icon { - { "voice_speed/voice_speed2", mediaPlayerActiveFg } + { "player/voice_speed/voice_speed2", mediaPlayerActiveFg } }; iconPosition: point(3px, 10px); @@ -88,106 +122,92 @@ mediaPlayerSpeedButton: IconButton { } } mediaPlayerSpeedDisabledIcon: icon { - { "voice_speed/voice_speed2", menuIconFg } + { "player/voice_speed/voice_speed2", menuIconFg } }; mediaPlayerSpeedDisabledIconOver: icon { - { "voice_speed/voice_speed2", menuIconFgOver } + { "player/voice_speed/voice_speed2", menuIconFgOver } }; mediaPlayerSpeedSlowIcon: icon { - { "voice_speed/voice_speed0.5", mediaPlayerActiveFg } + { "player/voice_speed/voice_speed0.5", mediaPlayerActiveFg } }; mediaPlayerSpeedSlowDisabledIcon: icon { - { "voice_speed/voice_speed0.5", menuIconFg } + { "player/voice_speed/voice_speed0.5", menuIconFg } }; mediaPlayerSpeedSlowDisabledIconOver: icon { - { "voice_speed/voice_speed0.5", menuIconFgOver } + { "player/voice_speed/voice_speed0.5", menuIconFgOver } }; mediaPlayerSpeedFastIcon: icon { - { "voice_speed/voice_speed1.5", mediaPlayerActiveFg } + { "player/voice_speed/voice_speed1.5", mediaPlayerActiveFg } }; mediaPlayerSpeedFastDisabledIcon: icon { - { "voice_speed/voice_speed1.5", menuIconFg } + { "player/voice_speed/voice_speed1.5", menuIconFg } }; mediaPlayerSpeedFastDisabledIconOver: icon { - { "voice_speed/voice_speed1.5", menuIconFgOver } + { "player/voice_speed/voice_speed1.5", menuIconFgOver } }; mediaPlayerSpeedDisabledRippleBg: windowBgOver; -mediaPlayerPopupMenu: PopupMenu(defaultPopupMenu) { - menu: Menu(defaultMenu) { - itemIconPosition: point(6px, 5px); - itemPadding: margins(34px, 8px, 17px, 7px); +mediaPlayerMenu: DropdownMenu(defaultDropdownMenu) { + wrap: InnerDropdown(defaultInnerDropdown) { + scrollPadding: margins(0px, 8px, 0px, 8px); + padding: margins(10px, 2px, 10px, 10px); } } -mediaPlayerMenuCheck: icon {{ "player_check", mediaPlayerActiveFg }}; +mediaPlayerSpeedMenu: Menu(defaultMenu) { + itemIconPosition: point(6px, 5px); + itemPadding: margins(34px, 8px, 17px, 7px); +} +mediaPlayerMenuCheck: icon {{ "player/player_check", mediaPlayerActiveFg }}; mediaPlayerVolumeIcon0: icon { - { "player_volume0", mediaPlayerActiveFg }, + { "player/player_mini_off", mediaPlayerActiveFg }, }; mediaPlayerVolumeIcon1: icon { - { "player_volume1", mediaPlayerActiveFg }, + { "player/player_mini_half", mediaPlayerActiveFg }, }; -mediaPlayerVolumeIcon2: icon { - { "player_volume2", mediaPlayerActiveFg }, -}; -mediaPlayerVolumeIcon3: icon { - { "player_volume3", mediaPlayerActiveFg }, -}; -mediaPlayerVolumeToggle: IconButton { - width: 31px; - height: 30px; - - icon: mediaPlayerVolumeIcon0; - iconPosition: point(8px, 11px); - - rippleAreaPosition: point(3px, 5px); - rippleAreaSize: 25px; - ripple: RippleAnimation(defaultRippleAnimation) { - color: lightButtonBgOver; - } +mediaPlayerVolumeToggle: IconButton(mediaPlayerRepeatButton) { + width: 34px; + icon: icon { + { "player/player_mini_full", mediaPlayerActiveFg }, + }; + iconPosition: point(5px, 6px); + rippleAreaPosition: point(4px, 5px); } mediaPlayerVolumeMargin: 10px; mediaPlayerVolumeSize: size(27px, 100px); -mediaPlayerPanelPinButton: IconButton(mediaPlayerRepeatButton) { - icon: icon { - { "player_panel_pin", mediaPlayerActiveFg, point(9px, 11px) } - }; -} +mediaPlayerControlsFade: icon {{ "fade_horizontal", mediaPlayerBg }}; -mediaPlayerNextButton: IconButton(mediaPlayerRepeatButton) { - width: 25px; +mediaPlayerNextButton: IconButton(mediaPlayerPlayButton) { icon: icon { - { "player_next", mediaPlayerActiveFg, mediaPlayerSkipIconPosition }, + { "player/player_forward", mediaPlayerActiveFg }, }; - - rippleAreaPosition: point(0px, 5px); } mediaPlayerNextDisabledIcon: icon { - { "player_next", mediaPlayerInactiveFg, mediaPlayerSkipIconPosition }, + { "player/player_forward", mediaPlayerInactiveFg }, }; mediaPlayerPreviousButton: IconButton(mediaPlayerNextButton) { icon: icon { - { "player_next-flip_horizontal", mediaPlayerActiveFg, mediaPlayerSkipIconPosition }, + { "player/player_backward", mediaPlayerActiveFg }, }; - rippleAreaPosition: point(1px, 5px); } mediaPlayerPreviousDisabledIcon: icon { - { "player_next-flip_horizontal", mediaPlayerInactiveFg, mediaPlayerSkipIconPosition }, + { "player/player_backward", mediaPlayerInactiveFg }, }; -touchBarIconPlayerClose: icon {{ "player_close", windowFg }}; +touchBarIconPlayerClose: icon {{ "player/panel_close", windowFg }}; touchBarIconPlayerPlay: icon {{ "media_play", windowFg }}; touchBarIconPlayerPause: icon {{ "media_pause", windowFg }}; -touchBarIconPlayerNext: icon {{ "player_next", windowFg }}; -touchBarIconPlayerPrevious: icon {{ "player_next-flip_horizontal", windowFg }}; +touchBarIconPlayerNext: icon {{ "player/player_forward", windowFg }}; +touchBarIconPlayerPrevious: icon {{ "player/player_backward", windowFg }}; mediaPlayerClose: IconButton(mediaPlayerRepeatButton) { - width: 37px; - icon: icon {{ "player_close", menuIconFg, point(10px, 12px) }}; - iconOver: icon {{ "player_close", menuIconFgOver, point(10px, 12px) }}; + width: 39px; + icon: icon {{ "player/panel_close", menuIconFg }}; + iconOver: icon {{ "player/panel_close", menuIconFgOver }}; + iconPosition: point(5px, 6px); - rippleAreaPosition: point(3px, 5px); + rippleAreaPosition: point(4px, 5px); ripple: RippleAnimation(defaultRippleAnimation) { color: windowBgOver; } @@ -221,25 +241,18 @@ mediaPlayerPanelMarginBottom: 10px; mediaPlayerPanelWidth: 344px; mediaPlayerCoverHeight: 102px; -mediaPlayerPanelClose: IconButton(mediaPlayerClose) { - width: 43px; - height: 28px; - icon: icon {{ "player_close", menuIconFg, point(16px, 14px) }}; - iconOver: icon {{ "player_close", menuIconFgOver, point(16px, 14px) }}; -} - mediaPlayerPanelNextButton: IconButton(mediaPlayerRepeatButton) { width: 37px; - icon: icon {{ "player_panel_next", mediaPlayerActiveFg, point(10px, 10px) }}; + icon: icon {{ "player/player_forward", mediaPlayerActiveFg, point(6px, 4px) }}; } mediaPlayerPanelNextDisabledIcon: icon { - { "player_panel_next", mediaPlayerInactiveFg, point(10px, 10px) }, + { "player/player_forward", mediaPlayerInactiveFg, point(6px, 4px) }, }; mediaPlayerPanelPreviousButton: IconButton(mediaPlayerPanelNextButton) { - icon: icon {{ "player_panel_next-flip_horizontal", mediaPlayerActiveFg, point(10px, 10px) }}; + icon: icon {{ "player/player_backward", mediaPlayerActiveFg, point(6px, 4px) }}; } mediaPlayerPanelPreviousDisabledIcon: icon { - { "player_panel_next-flip_horizontal", mediaPlayerInactiveFg, point(10px, 10px) }, + { "player/player_backward", mediaPlayerInactiveFg, point(6px, 4px) }, }; mediaPlayerPanelPadding: 16px; @@ -264,7 +277,7 @@ mediaPlayerScroll: ScrollArea(defaultSolidScroll) { mediaPlayerListHeightMax: 280px; mediaPlayerListMarginBottom: 10px; mediaPlayerScrollShadow: Shadow { - bottom: icon {{ "playlist_shadow", windowShadowFg }}; + bottom: icon {{ "player/playlist_shadow", windowShadowFg }}; extend: margins(2px, 2px, 2px, 2px); } @@ -277,3 +290,18 @@ mediaPlayerFileLayout: OverviewFileLayout(overviewFileLayout) { mediaPlayerFloatSize: 128px; mediaPlayerFloatMargin: 12px; + +mediaPlayerMenuPosition: point(-2px, -2px); +mediaPlayerOrderMenu: Menu(defaultMenu) { + itemIconPosition: point(13px, 8px); + itemPadding: margins(49px, 9px, 17px, 11px); + itemStyle: boxTextStyle; +} +mediaPlayerOrderMenuActive: Menu(mediaPlayerOrderMenu) { + itemFg: windowActiveTextFg; + itemFgOver: windowActiveTextFg; +} +mediaPlayerOrderIconReverse: icon{{ "player/player_order", windowFg }}; +mediaPlayerOrderIconReverseActive: icon{{ "player/player_order", windowActiveTextFg }}; +mediaPlayerOrderIconShuffle: icon{{ "player/player_shuffle", windowFg }}; +mediaPlayerOrderIconShuffleActive: icon{{ "player/player_shuffle", windowActiveTextFg }}; diff --git a/Telegram/SourceFiles/media/player/media_player_dropdown.cpp b/Telegram/SourceFiles/media/player/media_player_dropdown.cpp new file mode 100644 index 000000000..64e4adac5 --- /dev/null +++ b/Telegram/SourceFiles/media/player/media_player_dropdown.cpp @@ -0,0 +1,171 @@ +/* +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 "media/player/media_player_dropdown.h" + +#include "ui/cached_round_corners.h" +#include "ui/widgets/shadow.h" +#include "styles/style_media_player.h" +#include "styles/style_widgets.h" + +namespace Media::Player { + +Dropdown::Dropdown(QWidget *parent) +: RpWidget(parent) +, _hideTimer([=] { startHide(); }) +, _showTimer([=] { startShow(); }) { + hide(); + + macWindowDeactivateEvents( + ) | rpl::filter([=] { + return !isHidden(); + }) | rpl::start_with_next([=] { + leaveEvent(nullptr); + }, lifetime()); + + hide(); + auto margin = getMargin(); + resize(margin.left() + st::mediaPlayerVolumeSize.width() + margin.right(), margin.top() + st::mediaPlayerVolumeSize.height() + margin.bottom()); +} + +QMargins Dropdown::getMargin() const { + const auto top1 = st::mediaPlayerHeight + + st::lineWidth + - st::mediaPlayerPlayTop + - st::mediaPlayerVolumeToggle.height; + const auto top2 = st::mediaPlayerPlayback.fullWidth; + const auto top = std::max(top1, top2); + return QMargins(st::mediaPlayerVolumeMargin, top, st::mediaPlayerVolumeMargin, st::mediaPlayerVolumeMargin); +} + +bool Dropdown::overlaps(const QRect &globalRect) { + if (isHidden() || _a_appearance.animating()) return false; + + return rect().marginsRemoved(getMargin()).contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size())); +} + +void Dropdown::paintEvent(QPaintEvent *e) { + Painter p(this); + + if (!_cache.isNull()) { + bool animating = _a_appearance.animating(); + if (animating) { + p.setOpacity(_a_appearance.value(_hiding ? 0. : 1.)); + } else if (_hiding || isHidden()) { + hidingFinished(); + return; + } + p.drawPixmap(0, 0, _cache); + if (!animating) { + showChildren(); + _cache = QPixmap(); + } + return; + } + + // draw shadow + auto shadowedRect = rect().marginsRemoved(getMargin()); + auto shadowedSides = RectPart::Left | RectPart::Right | RectPart::Bottom; + Ui::Shadow::paint(p, shadowedRect, width(), st::defaultRoundShadow, shadowedSides); + auto parts = RectPart::NoTopBottom | RectPart::FullBottom; + Ui::FillRoundRect(p, QRect(shadowedRect.x(), -st::roundRadiusSmall, shadowedRect.width(), shadowedRect.y() + shadowedRect.height() + st::roundRadiusSmall), st::menuBg, Ui::MenuCorners, nullptr, parts); +} + +void Dropdown::enterEventHook(QEnterEvent *e) { + _hideTimer.cancel(); + if (_a_appearance.animating()) { + startShow(); + } else { + _showTimer.callOnce(0); + } + return RpWidget::enterEventHook(e); +} + +void Dropdown::leaveEventHook(QEvent *e) { + _showTimer.cancel(); + if (_a_appearance.animating()) { + startHide(); + } else { + _hideTimer.callOnce(300); + } + return RpWidget::leaveEventHook(e); +} + +void Dropdown::otherEnter() { + _hideTimer.cancel(); + if (_a_appearance.animating()) { + startShow(); + } else { + _showTimer.callOnce(0); + } +} + +void Dropdown::otherLeave() { + _showTimer.cancel(); + if (_a_appearance.animating()) { + startHide(); + } else { + _hideTimer.callOnce(0); + } +} + +void Dropdown::startShow() { + if (isHidden()) { + show(); + } else if (!_hiding) { + return; + } + _hiding = false; + startAnimation(); +} + +void Dropdown::startHide() { + if (_hiding) { + return; + } + + _hiding = true; + startAnimation(); +} + +void Dropdown::startAnimation() { + if (_cache.isNull()) { + showChildren(); + _cache = Ui::GrabWidget(this); + } + hideChildren(); + _a_appearance.start( + [=] { appearanceCallback(); }, + _hiding ? 1. : 0., + _hiding ? 0. : 1., + st::defaultInnerDropdown.duration); +} + +void Dropdown::appearanceCallback() { + if (!_a_appearance.animating() && _hiding) { + _hiding = false; + hidingFinished(); + } else { + update(); + } +} + +void Dropdown::hidingFinished() { + hide(); + _cache = QPixmap(); +} + +bool Dropdown::eventFilter(QObject *obj, QEvent *e) { + if (e->type() == QEvent::Enter) { + otherEnter(); + } else if (e->type() == QEvent::Leave) { + otherLeave(); + } + return false; +} + +} // namespace Media::Player diff --git a/Telegram/SourceFiles/media/player/media_player_dropdown.h b/Telegram/SourceFiles/media/player/media_player_dropdown.h new file mode 100644 index 000000000..67e1dfe54 --- /dev/null +++ b/Telegram/SourceFiles/media/player/media_player_dropdown.h @@ -0,0 +1,52 @@ +/* +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" +#include "ui/effects/animations.h" +#include "base/timer.h" + +namespace Media::Player { + +class Dropdown final : public Ui::RpWidget { +public: + explicit Dropdown(QWidget *parent); + + bool overlaps(const QRect &globalRect); + + QMargins getMargin() const; + +protected: + void paintEvent(QPaintEvent *e) override; + void enterEventHook(QEnterEvent *e) override; + void leaveEventHook(QEvent *e) override; + + bool eventFilter(QObject *obj, QEvent *e) override; + +private: + void startHide(); + void startShow(); + + void otherEnter(); + void otherLeave(); + + void appearanceCallback(); + void hidingFinished(); + void startAnimation(); + + bool _hiding = false; + + QPixmap _cache; + Ui::Animations::Simple _a_appearance; + + base::Timer _hideTimer; + base::Timer _showTimer; + +}; + +} // namespace Media::Player diff --git a/Telegram/SourceFiles/media/player/media_player_float.cpp b/Telegram/SourceFiles/media/player/media_player_float.cpp index bcd25405b..e1899950f 100644 --- a/Telegram/SourceFiles/media/player/media_player_float.cpp +++ b/Telegram/SourceFiles/media/player/media_player_float.cpp @@ -299,12 +299,12 @@ FloatController::Item::Item( FloatController::FloatController(not_null delegate) : _delegate(delegate) , _parent(_delegate->floatPlayerWidget()) { - subscribe(Media::Player::instance()->trackChangedNotifier(), [=]( - AudioMsgId::Type type) { - if (type == AudioMsgId::Type::Voice) { - checkCurrent(); - } - }); + Media::Player::instance()->trackChanged( + ) | rpl::filter([=](AudioMsgId::Type type) { + return (type == AudioMsgId::Type::Voice); + }) | rpl::start_with_next([=] { + checkCurrent(); + }, _lifetime); startDelegateHandling(); } diff --git a/Telegram/SourceFiles/media/player/media_player_float.h b/Telegram/SourceFiles/media/player/media_player_float.h index 9edb4af41..6c3b0109e 100644 --- a/Telegram/SourceFiles/media/player/media_player_float.h +++ b/Telegram/SourceFiles/media/player/media_player_float.h @@ -262,6 +262,8 @@ private: rpl::event_stream _closeEvents; rpl::lifetime _delegateLifetime; + rpl::lifetime _lifetime; + }; } // namespace Player diff --git a/Telegram/SourceFiles/media/player/media_player_instance.cpp b/Telegram/SourceFiles/media/player/media_player_instance.cpp index 7410ed7f1..35f042f6d 100644 --- a/Telegram/SourceFiles/media/player/media_player_instance.cpp +++ b/Telegram/SourceFiles/media/player/media_player_instance.cpp @@ -9,8 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_document.h" #include "data/data_session.h" +#include "data/data_changes.h" #include "data/data_streaming.h" #include "data/data_file_click_handler.h" +#include "base/random.h" #include "media/audio/media_audio.h" #include "media/audio/media_audio_capture.h" #include "media/streaming/media_streaming_instance.h" @@ -42,6 +44,9 @@ constexpr auto kIdsLimit = 32; // Preload next messages if we went further from current than that. constexpr auto kIdsPreloadAfter = 28; +constexpr auto kShufflePlaylistLimit = 10'000; +constexpr auto kRememberShuffledOrderItems = 16; + constexpr auto kMinLengthForSavePosition = 20 * TimeId(60); // 20 minutes. auto VoicePlaybackSpeed() { @@ -62,6 +67,21 @@ struct Instance::Streamed { rpl::lifetime lifetime; }; +struct Instance::ShuffleData { + using UniversalMsgId = MsgId; + + std::vector playlist; + std::vector nonPlayedIds; + std::vector playedIds; + History *history = nullptr; + History *migrated = nullptr; + bool scheduled = false; + int indexInPlayedIds = 0; + bool allLoaded = false; + rpl::lifetime nextSliceLifetime; + rpl::lifetime lifetime; +}; + void start(not_null instance) { Audio::Start(instance); Capture::Start(); @@ -117,6 +137,23 @@ Instance::Instance() handleSongUpdate(audioId); }); + repeatChanges( + &_songData + ) | rpl::start_with_next([=](RepeatMode mode) { + if (mode == RepeatMode::All) { + refreshPlaylist(&_songData); + } + }, _lifetime); + orderChanges( + &_songData + ) | rpl::start_with_next([=](OrderMode mode) { + if (mode == OrderMode::Shuffle) { + validateShuffleData(&_songData); + } else { + _songData.shuffleData = nullptr; + } + }, _lifetime); + using namespace rpl::mappers; rpl::combine( Core::App().calls().currentCallValue(), @@ -182,7 +219,7 @@ void Instance::setCurrent(const AudioMsgId &audioId) { data->migrated = nullptr; data->session = nullptr; } - _trackChangedNotifier.notify(data->type, true); + _trackChanged.fire_copy(data->type); refreshPlaylist(data); } } @@ -203,6 +240,7 @@ void Instance::setSession(not_null data, Main::Session *session) { return; } data->playlistLifetime.destroy(); + data->playlistOtherLifetime.destroy(); data->sessionLifetime.destroy(); data->session = session; if (session) { @@ -249,6 +287,14 @@ void Instance::clearStreamed(not_null data, bool savePosition) { void Instance::refreshPlaylist(not_null data) { if (!validPlaylist(data)) { validatePlaylist(data); + } else { + refreshOtherPlaylist(data); + } +} + +void Instance::refreshOtherPlaylist(not_null data) { + if (!validOtherPlaylist(data)) { + validateOtherPlaylist(data); } playlistUpdated(data); } @@ -257,13 +303,17 @@ void Instance::playlistUpdated(not_null data) { if (data->playlistSlice) { const auto fullId = data->current.contextId(); data->playlistIndex = data->playlistSlice->indexOf(fullId); + if (order(data) == OrderMode::Shuffle) { + validateShuffleData(data); + } } else { data->playlistIndex = std::nullopt; + data->shuffleData = nullptr; } data->playlistChanges.fire({}); } -bool Instance::validPlaylist(not_null data) { +bool Instance::validPlaylist(not_null data) const { if (const auto key = playlistKey(data)) { if (!data->playlistSlice) { return false; @@ -315,16 +365,16 @@ void Instance::validatePlaylist(not_null data) { ) | rpl::start_with_next([=](SparseIdsMergedSlice &&update) { data->playlistSlice = std::move(update); data->playlistSliceKey = key; - playlistUpdated(data); + refreshOtherPlaylist(data); }, data->playlistLifetime); } else { data->playlistSlice = std::nullopt; data->playlistSliceKey = data->playlistRequestedKey = std::nullopt; - playlistUpdated(data); + refreshOtherPlaylist(data); } } -auto Instance::playlistKey(not_null data) const +auto Instance::playlistKey(not_null data) const -> std::optional { const auto contextId = data->current.contextId(); const auto history = data->history; @@ -346,6 +396,67 @@ auto Instance::playlistKey(not_null data) const item->isScheduled()); } +bool Instance::validOtherPlaylist(not_null data) const { + if (const auto key = playlistOtherKey(data)) { + return data->playlistOtherSlice + && (key == data->playlistOtherRequestedKey); + } + return !data->playlistOtherSlice; +} + +void Instance::validateOtherPlaylist(not_null data) { + data->playlistOtherLifetime.destroy(); + if (const auto key = playlistOtherKey(data)) { + data->playlistOtherRequestedKey = key; + + SharedMediaMergedViewer( + &data->history->session(), + SharedMediaMergedKey(*key, data->overview), + kIdsLimit, + kIdsLimit + ) | rpl::start_with_next([=](SparseIdsMergedSlice &&update) { + data->playlistOtherSlice = std::move(update); + playlistUpdated(data); + }, data->playlistOtherLifetime); + } else { + data->playlistOtherSlice = std::nullopt; + data->playlistOtherRequestedKey = std::nullopt; + playlistUpdated(data); + } +} + +auto Instance::playlistOtherKey(not_null data) const +-> std::optional { + if (repeat(data) != RepeatMode::All + || order(data) == OrderMode::Shuffle + || !data->playlistSlice + || (data->playlistSlice->skippedBefore() != 0 + && data->playlistSlice->skippedAfter() != 0) + || (data->playlistSlice->skippedBefore() == 0 + && data->playlistSlice->skippedAfter() == 0)) { + return {}; + } + const auto contextId = data->current.contextId(); + const auto history = data->history; + if (!contextId || !history) { + return {}; + } + const auto item = data->history->owner().message(contextId); + if (!item || !item->isRegular()) { + return {}; + } + + return SliceKey( + data->history->peer->id, + data->migrated ? data->migrated->peer->id : 0, + (data->playlistSlice->skippedBefore() == 0 + ? ServerMaxMsgId - 1 + : data->migrated + ? (1 - ServerMaxMsgId) + : 1), + false); +} + HistoryItem *Instance::itemByIndex(not_null data, int index) { if (!data->playlistSlice || index < 0 @@ -364,12 +475,11 @@ bool Instance::moveInPlaylist( if (!data->playlistIndex) { return false; } - const auto newIndex = *data->playlistIndex + delta; - if (const auto item = itemByIndex(data, newIndex)) { + const auto jumpByItem = [&](not_null item) { if (const auto media = item->media()) { if (const auto document = media->document()) { if (autonext) { - _switchToNextNotifier.notify({ + _switchToNext.fire({ data->current, item->fullId() }); @@ -382,31 +492,188 @@ bool Instance::moveInPlaylist( return true; } } + return false; + }; + const auto jumpById = [&](FullMsgId id) { + return jumpByItem(data->history->owner().message(id)); + }; + const auto repeatAll = (repeat(data) == RepeatMode::All); + + if (order(data) == OrderMode::Shuffle) { + const auto raw = data->shuffleData.get(); + if (!raw || !raw->history) { + return false; + } + const auto universal = computeCurrentUniversalId(data); + const auto byUniversal = [&](ShuffleData::UniversalMsgId id) { + return (id < 0) + ? jumpById({ ChannelId(), id + ServerMaxMsgId }) + : jumpById({ raw->history->channelId(), id }); + }; + if (universal && raw->indexInPlayedIds == raw->playedIds.size()) { + raw->playedIds.push_back(universal); + const auto i = ranges::find(raw->nonPlayedIds, universal); + if (i != end(raw->nonPlayedIds)) { + raw->nonPlayedIds.erase(i); + } + } + if (repeatAll) { + ensureShuffleMove(data, delta); + } + if (raw->nonPlayedIds.empty() + && raw->indexInPlayedIds + 1 == raw->playedIds.size()) { + raw->nonPlayedIds.push_back(raw->playedIds.back()); + raw->playedIds.pop_back(); + } + const auto shuffleCompleted = raw->nonPlayedIds.empty() + || (raw->nonPlayedIds.size() == 1 + && raw->nonPlayedIds.front() == universal); + if (delta < 0) { + return (raw->indexInPlayedIds > 0) + && byUniversal(raw->playedIds[--raw->indexInPlayedIds]); + } else if (raw->indexInPlayedIds + 1 < raw->playedIds.size()) { + return byUniversal(raw->playedIds[++raw->indexInPlayedIds]); + } + if (shuffleCompleted) { + return false; + } else if (raw->indexInPlayedIds < raw->playedIds.size()) { + ++raw->indexInPlayedIds; + } + const auto index = base::RandomIndex(raw->nonPlayedIds.size()); + return byUniversal(raw->nonPlayedIds[index]); + } + + const auto newIndex = *data->playlistIndex + + (order(data) == OrderMode::Reverse ? -delta : delta); + const auto useIndex = (!repeatAll + || !data->playlistSlice + || data->playlistSlice->skippedAfter() != 0 + || data->playlistSlice->skippedBefore() != 0 + || !data->playlistSlice->size()) + ? newIndex + : ((newIndex + int(data->playlistSlice->size())) + % int(data->playlistSlice->size())); + if (const auto item = itemByIndex(data, useIndex)) { + return jumpByItem(item); + } else if (repeatAll + && data->playlistOtherSlice + && data->playlistOtherSlice->size() > 0) { + const auto &other = *data->playlistOtherSlice; + if (newIndex < 0 && other.skippedAfter() == 0) { + return jumpById(other[other.size() - 1]); + } else if (newIndex > 0 && other.skippedBefore() == 0) { + return jumpById(other[0]); + } } return false; } +void Instance::ensureShuffleMove(not_null data, int delta) { + const auto raw = data->shuffleData.get(); + if (delta < 0) { + if (raw->indexInPlayedIds > 0) { + return; + } else if (raw->nonPlayedIds.size() < 2) { + const auto freeUp = std::max( + int(raw->playedIds.size() / 2), + int(raw->playlist.size()) - kRememberShuffledOrderItems); + const auto till = end(raw->playedIds); + const auto from = end(raw->playedIds) - freeUp; + raw->nonPlayedIds.insert(end(raw->nonPlayedIds), from, till); + raw->playedIds.erase(from, till); + } + if (raw->nonPlayedIds.empty()) { + return; + } + const auto index = base::RandomIndex(raw->nonPlayedIds.size()); + raw->playedIds.insert( + begin(raw->playedIds), + raw->nonPlayedIds[index]); + raw->nonPlayedIds.erase(begin(raw->nonPlayedIds) + index); + ++raw->indexInPlayedIds; + if (raw->nonPlayedIds.empty() && raw->playedIds.size() > 1) { + raw->nonPlayedIds.push_back(raw->playedIds.back()); + raw->playedIds.pop_back(); + } + return; + } else if (raw->indexInPlayedIds + 1 < raw->playedIds.size()) { + return; + } else if (raw->nonPlayedIds.size() < 2) { + const auto freeUp = std::max( + int(raw->playedIds.size() / 2), + int(raw->playlist.size()) - kRememberShuffledOrderItems); + const auto from = begin(raw->playedIds); + const auto till = begin(raw->playedIds) + freeUp; + raw->nonPlayedIds.insert(end(raw->nonPlayedIds), from, till); + raw->playedIds.erase(from, till); + raw->indexInPlayedIds -= freeUp; + } +} + +MsgId Instance::computeCurrentUniversalId(not_null data) const { + const auto raw = data->shuffleData.get(); + if (!raw) { + return MsgId(0); + } + const auto current = data->current.contextId(); + const auto item = raw->history->owner().message(current); + return !item + ? MsgId(0) + : (item->history() == raw->history) + ? item->id + : (item->history() == raw->migrated) + ? (item->id - ServerMaxMsgId) + : MsgId(0); +} + bool Instance::previousAvailable(AudioMsgId::Type type) const { const auto data = getData(type); Assert(data != nullptr); - return data->playlistIndex - && data->playlistSlice - && (*data->playlistIndex > 0); + + if (!data->playlistIndex || !data->playlistSlice) { + return false; + } else if (repeat(data) == RepeatMode::All) { + return true; + } else if (order(data) == OrderMode::Shuffle) { + const auto raw = data->shuffleData.get(); + return raw && (raw->indexInPlayedIds > 0); + } + return (order(data) == OrderMode::Reverse) + ? (*data->playlistIndex + 1 < data->playlistSlice->size()) + : (*data->playlistIndex > 0); } bool Instance::nextAvailable(AudioMsgId::Type type) const { const auto data = getData(type); Assert(data != nullptr); - return data->playlistIndex - && data->playlistSlice - && (*data->playlistIndex + 1 < data->playlistSlice->size()); + + if (!data->playlistIndex || !data->playlistSlice) { + return false; + } else if (repeat(data) == RepeatMode::All) { + return true; + } else if (order(data) == OrderMode::Shuffle) { + const auto raw = data->shuffleData.get(); + const auto universal = computeCurrentUniversalId(data); + return raw + && ((raw->indexInPlayedIds + 1 < raw->playedIds.size()) + || (raw->nonPlayedIds.size() > 1) + || (!raw->nonPlayedIds.empty() + && raw->nonPlayedIds.front() != universal)); + } + return (order(data) == OrderMode::Reverse) + ? (*data->playlistIndex > 0) + : (*data->playlistIndex + 1 < data->playlistSlice->size()); } rpl::producer<> Media::Player::Instance::playlistChanges( AudioMsgId::Type type) const { const auto data = getData(type); Assert(data != nullptr); - return data->playlistChanges.events(); + + return rpl::merge( + data->playlistChanges.events(), + orderChanges(data) | rpl::to_empty, + repeatChanges(data) | rpl::to_empty); } rpl::producer<> Media::Player::Instance::stops(AudioMsgId::Type type) const { @@ -558,8 +825,141 @@ void Instance::stop(AudioMsgId::Type type) { void Instance::stopAndClear(not_null data) { stop(data->type); - _tracksFinishedNotifier.notify(data->type); *data = Data(data->type, data->overview); + _tracksFinished.fire_copy(data->type); +} + +void Instance::validateShuffleData(not_null data) { + if (!data->history) { + data->shuffleData = nullptr; + return; + } else if (!data->shuffleData) { + setupShuffleData(data); + } + const auto raw = data->shuffleData.get(); + const auto key = playlistKey(data); + const auto scheduled = key && key->scheduled; + if (raw->history != data->history + || raw->migrated != data->migrated + || raw->scheduled != scheduled) { + raw->history = data->history; + raw->migrated = data->migrated; + raw->scheduled = scheduled; + raw->nextSliceLifetime.destroy(); + raw->allLoaded = false; + raw->playlist.clear(); + raw->nonPlayedIds.clear(); + raw->playedIds.clear(); + raw->indexInPlayedIds = 0; + } else if (raw->nextSliceLifetime) { + return; + } else if (raw->allLoaded) { + const auto universal = computeCurrentUniversalId(data); + if (!universal + || (raw->indexInPlayedIds < raw->playedIds.size() + ? (raw->playedIds[raw->indexInPlayedIds] == universal) + : ranges::contains(raw->nonPlayedIds, universal))) { + return; + } + // We started playing some track not from the tracks that are left. + // Start the whole playlist thing once again. + raw->playedIds.clear(); + raw->indexInPlayedIds = 0; + if (ranges::contains(raw->playlist, universal)) { + raw->nonPlayedIds = raw->playlist; + } else { + raw->allLoaded = false; + raw->playlist.clear(); + raw->nonPlayedIds.clear(); + } + } + if (raw->scheduled) { + const auto count = data->playlistSlice + ? int(data->playlistSlice->size()) + : 0; + if (raw->playlist.empty() && count > 0) { + raw->playlist.reserve(count); + for (auto i = 0; i != count; ++i) { + raw->playlist.push_back((*data->playlistSlice)[i].msg); + } + raw->nonPlayedIds = raw->playlist; + raw->allLoaded = true; + data->playlistChanges.fire({}); + } + return; + } + const auto last = raw->playlist.empty() + ? MsgId(ServerMaxMsgId - 1) + : raw->playlist.back(); + SharedMediaMergedViewer( + &raw->history->session(), + SharedMediaMergedKey( + SliceKey( + raw->history->peer->id, + raw->migrated ? raw->migrated->peer->id : 0, + last, + false), + data->overview), + kIdsLimit, + kIdsLimit + ) | rpl::start_with_next([=](SparseIdsMergedSlice &&update) { + raw->nextSliceLifetime.destroy(); + + const auto size = update.size(); + const auto channel = raw->history->channelId(); + raw->playlist.reserve(raw->playlist.size() + size); + raw->nonPlayedIds.reserve(raw->nonPlayedIds.size() + size); + for (auto i = size; i != 0;) { + const auto fullId = update[--i]; + const auto universal = (fullId.channel == channel) + ? fullId.msg + : (fullId.msg - ServerMaxMsgId); + if (raw->playlist.empty() || raw->playlist.back() > universal) { + raw->playlist.push_back(universal); + raw->nonPlayedIds.push_back(universal); + } + } + if (update.skippedBefore() == 0 + || raw->playlist.size() >= kShufflePlaylistLimit) { + raw->allLoaded = true; + } + data->playlistChanges.fire({}); + }, raw->nextSliceLifetime); +} + +void Instance::setupShuffleData(not_null data) { + data->shuffleData = std::make_unique(); + const auto raw = data->shuffleData.get(); + data->history->session().changes().messageUpdates( + ::Data::MessageUpdate::Flag::Destroyed + ) | rpl::map([=](const ::Data::MessageUpdate &update) { + const auto item = update.item; + const auto history = item->history().get(); + return (history == raw->history) + ? item->id + : (history == raw->migrated) + ? (item->id - ServerMaxMsgId) + : MsgId(0); + }) | rpl::filter( + rpl::mappers::_1 != MsgId(0) + ) | rpl::start_with_next([=](MsgId id) { + const auto i = ranges::find(raw->playlist, id); + if (i != end(raw->playlist)) { + raw->playlist.erase(i); + } + const auto j = ranges::find(raw->nonPlayedIds, id); + if (j != end(raw->nonPlayedIds)) { + raw->nonPlayedIds.erase(j); + } + const auto k = ranges::find(raw->playedIds, id); + if (k != end(raw->playedIds)) { + const auto index = (k - begin(raw->playedIds)); + raw->playedIds.erase(k); + if (raw->indexInPlayedIds > index) { + --raw->indexInPlayedIds; + } + } + }, data->shuffleData->lifetime); } void Instance::playPause(AudioMsgId::Type type) { @@ -699,6 +1099,32 @@ void Instance::emitUpdate(AudioMsgId::Type type) { emitUpdate(type, [](const AudioMsgId &playing) { return true; }); } +RepeatMode Instance::repeat(not_null data) const { + return (data->type == AudioMsgId::Type::Song) + ? Core::App().settings().playerRepeatMode() + : RepeatMode::None; +} + +rpl::producer Instance::repeatChanges( + not_null data) const { + return (data->type == AudioMsgId::Type::Song) + ? Core::App().settings().playerRepeatModeChanges() + : rpl::never(); +} + +OrderMode Instance::order(not_null data) const { + return (data->type == AudioMsgId::Type::Song) + ? Core::App().settings().playerOrderMode() + : OrderMode::Default; +} + +rpl::producer Instance::orderChanges( + not_null data) const { + return (data->type == AudioMsgId::Type::Song) + ? Core::App().settings().playerOrderModeChanges() + : rpl::never(); +} + TrackState Instance::getState(AudioMsgId::Type type) const { if (const auto data = getData(type)) { if (data->streamed) { @@ -744,15 +1170,19 @@ void Instance::emitUpdate(AudioMsgId::Type type, CheckCallback check) { streamed->progress.updateState(state); } } + auto finished = false; _updatedNotifier.fire_copy({state}); if (data->isPlaying && state.state == State::StoppedAtEnd) { - if (data->repeatEnabled) { + if (repeat(data) == RepeatMode::One) { play(data->current); } else if (!moveInPlaylist(data, 1, true)) { - _tracksFinishedNotifier.notify(type); + finished = true; } } data->isPlaying = !IsStopped(state.state); + if (finished) { + _tracksFinished.fire_copy(type); + } } } diff --git a/Telegram/SourceFiles/media/player/media_player_instance.h b/Telegram/SourceFiles/media/player/media_player_instance.h index 47eb92898..5155ac69b 100644 --- a/Telegram/SourceFiles/media/player/media_player_instance.h +++ b/Telegram/SourceFiles/media/player/media_player_instance.h @@ -38,6 +38,18 @@ enum class Error; namespace Media { namespace Player { +enum class RepeatMode { + None, + One, + All, +}; + +enum class OrderMode { + Default, + Reverse, + Shuffle, +}; + class Instance; struct TrackState; @@ -104,19 +116,6 @@ public: return AudioMsgId(); } - [[nodiscard]] bool repeatEnabled(AudioMsgId::Type type) const { - if (const auto data = getData(type)) { - return data->repeatEnabled; - } - return false; - } - void toggleRepeat(AudioMsgId::Type type) { - if (const auto data = getData(type)) { - data->repeatEnabled = !data->repeatEnabled; - _repeatChangedNotifier.notify(type); - } - } - [[nodiscard]] bool isSeeking(AudioMsgId::Type type) const { if (const auto data = getData(type)) { return (data->seeking == data->current); @@ -137,34 +136,30 @@ public: FullMsgId to; }; - base::Observable &switchToNextNotifier() { - return _switchToNextNotifier; + [[nodiscard]] rpl::producer switchToNextEvents() const { + return _switchToNext.events(); } - base::Observable &playerWidgetOver() { - return _playerWidgetOver; + [[nodiscard]] rpl::producer tracksFinished() const { + return _tracksFinished.events(); } - base::Observable &tracksFinishedNotifier() { - return _tracksFinishedNotifier; - } - base::Observable &trackChangedNotifier() { - return _trackChangedNotifier; - } - base::Observable &repeatChangedNotifier() { - return _repeatChangedNotifier; + [[nodiscard]] rpl::producer trackChanged() const { + return _trackChanged.events(); } - rpl::producer<> playlistChanges(AudioMsgId::Type type) const; + [[nodiscard]] rpl::producer<> playlistChanges( + AudioMsgId::Type type) const; - rpl::producer updatedNotifier() const { + [[nodiscard]] rpl::producer updatedNotifier() const { return _updatedNotifier.events(); } - rpl::producer<> stops(AudioMsgId::Type type) const; - rpl::producer<> startsPlay(AudioMsgId::Type type) const; + [[nodiscard]] rpl::producer<> stops(AudioMsgId::Type type) const; + [[nodiscard]] rpl::producer<> startsPlay(AudioMsgId::Type type) const; - rpl::producer seekingChanges(AudioMsgId::Type type) const; + [[nodiscard]] rpl::producer seekingChanges( + AudioMsgId::Type type) const; - bool pauseGifByRoundVideo() const; + [[nodiscard]] bool pauseGifByRoundVideo() const; void documentLoadProgress(DocumentData *document); @@ -172,6 +167,7 @@ private: using SharedMediaType = Storage::SharedMediaType; using SliceKey = SparseIdsMergedSlice::Key; struct Streamed; + struct ShuffleData; struct Data { Data(AudioMsgId::Type type, SharedMediaType overview); Data(Data &&other); @@ -185,17 +181,20 @@ private: std::optional playlistSlice; std::optional playlistSliceKey; std::optional playlistRequestedKey; + std::optional playlistOtherSlice; + std::optional playlistOtherRequestedKey; std::optional playlistIndex; rpl::lifetime playlistLifetime; + rpl::lifetime playlistOtherLifetime; rpl::lifetime sessionLifetime; rpl::event_stream<> playlistChanges; History *history = nullptr; History *migrated = nullptr; Main::Session *session = nullptr; - bool repeatEnabled = false; bool isPlaying = false; bool resumeOnCallEnd = false; std::unique_ptr streamed; + std::unique_ptr shuffleData; }; struct SeekingChanges { @@ -225,14 +224,25 @@ private: void setCurrent(const AudioMsgId &audioId); void refreshPlaylist(not_null data); - std::optional playlistKey(not_null data) const; - bool validPlaylist(not_null data); + void refreshOtherPlaylist(not_null data); + std::optional playlistKey(not_null data) const; + bool validPlaylist(not_null data) const; void validatePlaylist(not_null data); + std::optional playlistOtherKey( + not_null data) const; + bool validOtherPlaylist(not_null data) const; + void validateOtherPlaylist(not_null data); void playlistUpdated(not_null data); bool moveInPlaylist(not_null data, int delta, bool autonext); HistoryItem *itemByIndex(not_null data, int index); void stopAndClear(not_null data); + [[nodiscard]] MsgId computeCurrentUniversalId( + not_null data) const; + void validateShuffleData(not_null data); + void setupShuffleData(not_null data); + void ensureShuffleMove(not_null data, int delta); + void handleStreamingUpdate( not_null data, Streaming::Update &&update); @@ -245,6 +255,13 @@ private: template void emitUpdate(AudioMsgId::Type type, CheckCallback check); + [[nodiscard]] RepeatMode repeat(not_null data) const; + [[nodiscard]] rpl::producer repeatChanges( + not_null data) const; + [[nodiscard]] OrderMode order(not_null data) const; + [[nodiscard]] rpl::producer orderChanges( + not_null data) const; + Data *getData(AudioMsgId::Type type) { if (type == AudioMsgId::Type::Song) { return &_songData; @@ -274,12 +291,9 @@ private: Data _voiceData; bool _roundPlaying = false; - base::Observable _switchToNextNotifier; - base::Observable _playerWidgetOver; - base::Observable _tracksFinishedNotifier; - base::Observable _trackChangedNotifier; - base::Observable _repeatChangedNotifier; - + rpl::event_stream _switchToNext; + rpl::event_stream _tracksFinished; + rpl::event_stream _trackChanged; rpl::event_stream _playerStopped; rpl::event_stream _playerStartedPlay; rpl::event_stream _updatedNotifier; diff --git a/Telegram/SourceFiles/media/player/media_player_volume_controller.cpp b/Telegram/SourceFiles/media/player/media_player_volume_controller.cpp index a453f74cf..53d207a28 100644 --- a/Telegram/SourceFiles/media/player/media_player_volume_controller.cpp +++ b/Telegram/SourceFiles/media/player/media_player_volume_controller.cpp @@ -8,12 +8,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/player/media_player_volume_controller.h" #include "media/audio/media_audio.h" +#include "media/player/media_player_dropdown.h" #include "ui/widgets/buttons.h" #include "ui/widgets/shadow.h" #include "ui/widgets/continuous_sliders.h" #include "ui/ui_utility.h" #include "ui/cached_round_corners.h" -#include "base/object_ptr.h" #include "mainwindow.h" #include "main/main_session.h" #include "window/window_session_controller.h" @@ -22,8 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_media_player.h" #include "styles/style_widgets.h" -namespace Media { -namespace Player { +namespace Media::Player { VolumeController::VolumeController( QWidget *parent, @@ -58,6 +57,10 @@ void VolumeController::setIsVertical(bool vertical) { _slider->setAlwaysDisplayMarker(vertical); } +void VolumeController::outerWheelEvent(not_null e) { + QGuiApplication::sendEvent(_slider.data(), e); +} + void VolumeController::resizeEvent(QResizeEvent *e) { _slider->setGeometry(rect()); } @@ -77,168 +80,35 @@ void VolumeController::applyVolumeChange(float64 volume) { } } -VolumeWidget::VolumeWidget( - QWidget *parent, - not_null controller) -: RpWidget(parent) -, _controller(this, controller) { - hide(); - _controller->setIsVertical(true); +void PrepareVolumeDropdown( + not_null dropdown, + not_null controller, + rpl::producer> outerWheelEvents) { + const auto volume = Ui::CreateChild( + dropdown.get(), + controller); + volume->show(); + volume->setIsVertical(true); - _hideTimer.setSingleShot(true); - connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(onHideStart())); + dropdown->sizeValue( + ) | rpl::start_with_next([=](QSize size) { + const auto rect = QRect(QPoint(), size); + const auto inner = rect.marginsRemoved(dropdown->getMargin()); + volume->setGeometry( + inner.x(), + inner.y() - st::lineWidth, + inner.width(), + (inner.height() + + st::lineWidth + - ((st::mediaPlayerVolumeSize.width() + - st::mediaPlayerPanelPlayback.width) / 2))); + }, volume->lifetime()); - _showTimer.setSingleShot(true); - connect(&_showTimer, SIGNAL(timeout()), this, SLOT(onShowStart())); - - macWindowDeactivateEvents( - ) | rpl::filter([=] { - return !isHidden(); - }) | rpl::start_with_next([=] { - leaveEvent(nullptr); - }, lifetime()); - - hide(); - auto margin = getMargin(); - resize(margin.left() + st::mediaPlayerVolumeSize.width() + margin.right(), margin.top() + st::mediaPlayerVolumeSize.height() + margin.bottom()); + std::move( + outerWheelEvents + ) | rpl::start_with_next([=](not_null e) { + volume->outerWheelEvent(e); + }, volume->lifetime()); } -QMargins VolumeWidget::getMargin() const { - const auto top = st::mediaPlayerHeight - + st::lineWidth - - st::mediaPlayerPlayTop - - st::mediaPlayerVolumeToggle.height; - return QMargins(st::mediaPlayerVolumeMargin, top, st::mediaPlayerVolumeMargin, st::mediaPlayerVolumeMargin); -} - -bool VolumeWidget::overlaps(const QRect &globalRect) { - if (isHidden() || _a_appearance.animating()) return false; - - return rect().marginsRemoved(getMargin()).contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size())); -} - -void VolumeWidget::resizeEvent(QResizeEvent *e) { - auto inner = rect().marginsRemoved(getMargin()); - _controller->setGeometry(inner.x(), inner.y() - st::lineWidth, inner.width(), inner.height() + st::lineWidth - ((st::mediaPlayerVolumeSize.width() - st::mediaPlayerPanelPlayback.width) / 2)); -} - -void VolumeWidget::paintEvent(QPaintEvent *e) { - Painter p(this); - - if (!_cache.isNull()) { - bool animating = _a_appearance.animating(); - if (animating) { - p.setOpacity(_a_appearance.value(_hiding ? 0. : 1.)); - } else if (_hiding || isHidden()) { - hidingFinished(); - return; - } - p.drawPixmap(0, 0, _cache); - if (!animating) { - showChildren(); - _cache = QPixmap(); - } - return; - } - - // draw shadow - auto shadowedRect = rect().marginsRemoved(getMargin()); - auto shadowedSides = RectPart::Left | RectPart::Right | RectPart::Bottom; - Ui::Shadow::paint(p, shadowedRect, width(), st::defaultRoundShadow, shadowedSides); - auto parts = RectPart::NoTopBottom | RectPart::FullBottom; - Ui::FillRoundRect(p, QRect(shadowedRect.x(), -st::roundRadiusSmall, shadowedRect.width(), shadowedRect.y() + shadowedRect.height() + st::roundRadiusSmall), st::menuBg, Ui::MenuCorners, nullptr, parts); -} - -void VolumeWidget::enterEventHook(QEnterEvent *e) { - _hideTimer.stop(); - if (_a_appearance.animating()) { - onShowStart(); - } else { - _showTimer.start(0); - } - return RpWidget::enterEventHook(e); -} - -void VolumeWidget::leaveEventHook(QEvent *e) { - _showTimer.stop(); - if (_a_appearance.animating()) { - onHideStart(); - } else { - _hideTimer.start(300); - } - return RpWidget::leaveEventHook(e); -} - -void VolumeWidget::otherEnter() { - _hideTimer.stop(); - if (_a_appearance.animating()) { - onShowStart(); - } else { - _showTimer.start(0); - } -} - -void VolumeWidget::otherLeave() { - _showTimer.stop(); - if (_a_appearance.animating()) { - onHideStart(); - } else { - _hideTimer.start(0); - } -} - -void VolumeWidget::onShowStart() { - if (isHidden()) { - show(); - } else if (!_hiding) { - return; - } - _hiding = false; - startAnimation(); -} - -void VolumeWidget::onHideStart() { - if (_hiding) return; - - _hiding = true; - startAnimation(); -} - -void VolumeWidget::startAnimation() { - if (_cache.isNull()) { - showChildren(); - _cache = Ui::GrabWidget(this); - } - hideChildren(); - _a_appearance.start( - [=] { appearanceCallback(); }, - _hiding ? 1. : 0., - _hiding ? 0. : 1., - st::defaultInnerDropdown.duration); -} - -void VolumeWidget::appearanceCallback() { - if (!_a_appearance.animating() && _hiding) { - _hiding = false; - hidingFinished(); - } else { - update(); - } -} - -void VolumeWidget::hidingFinished() { - hide(); - _cache = QPixmap(); -} - -bool VolumeWidget::eventFilter(QObject *obj, QEvent *e) { - if (e->type() == QEvent::Enter) { - otherEnter(); - } else if (e->type() == QEvent::Leave) { - otherLeave(); - } - return false; -} - -} // namespace Player -} // namespace Media +} // namespace Media::Player diff --git a/Telegram/SourceFiles/media/player/media_player_volume_controller.h b/Telegram/SourceFiles/media/player/media_player_volume_controller.h index 386910817..49b4c8cb4 100644 --- a/Telegram/SourceFiles/media/player/media_player_volume_controller.h +++ b/Telegram/SourceFiles/media/player/media_player_volume_controller.h @@ -7,14 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "ui/effects/animations.h" #include "ui/rp_widget.h" #include "base/object_ptr.h" -#include +class QWheelEvent; namespace Ui { -class IconButton; class MediaSlider; } // namespace Ui @@ -22,8 +20,9 @@ namespace Window { class SessionController; } // namespace Window -namespace Media { -namespace Player { +namespace Media::Player { + +class Dropdown; class VolumeController final : public Ui::RpWidget { public: @@ -32,6 +31,7 @@ public: not_null controller); void setIsVertical(bool vertical); + void outerWheelEvent(not_null e); protected: void resizeEvent(QResizeEvent *e) override; @@ -44,48 +44,9 @@ private: }; -class VolumeWidget : public Ui::RpWidget { - Q_OBJECT +void PrepareVolumeDropdown( + not_null dropdown, + not_null controller, + rpl::producer> outerWheelEvents); -public: - VolumeWidget( - QWidget *parent, - not_null controller); - - bool overlaps(const QRect &globalRect); - - QMargins getMargin() const; - -protected: - void resizeEvent(QResizeEvent *e) override; - void paintEvent(QPaintEvent *e) override; - void enterEventHook(QEnterEvent *e) override; - void leaveEventHook(QEvent *e) override; - - bool eventFilter(QObject *obj, QEvent *e) override; - -private Q_SLOTS: - void onShowStart(); - void onHideStart(); - -private: - void otherEnter(); - void otherLeave(); - - void appearanceCallback(); - void hidingFinished(); - void startAnimation(); - - bool _hiding = false; - - QPixmap _cache; - Ui::Animations::Simple _a_appearance; - - QTimer _hideTimer, _showTimer; - - object_ptr _controller; - -}; - -} // namespace Player -} // namespace Media +} // namespace Media::Player diff --git a/Telegram/SourceFiles/media/player/media_player_widget.cpp b/Telegram/SourceFiles/media/player/media_player_widget.cpp index 86c9dce6e..5371b8509 100644 --- a/Telegram/SourceFiles/media/player/media_player_widget.cpp +++ b/Telegram/SourceFiles/media/player/media_player_widget.cpp @@ -18,6 +18,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/shadow.h" #include "ui/widgets/buttons.h" #include "ui/widgets/popup_menu.h" +#include "ui/widgets/dropdown_menu.h" +#include "ui/widgets/menu/menu_action.h" +#include "ui/wrap/fade_wrap.h" #include "ui/effects/ripple_animation.h" #include "ui/text/format_values.h" #include "ui/text/format_song_document_name.h" @@ -26,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/view/media_view_playback_progress.h" #include "media/player/media_player_button.h" #include "media/player/media_player_instance.h" +#include "media/player/media_player_dropdown.h" #include "media/player/media_player_volume_controller.h" #include "styles/style_media_player.h" #include "styles/style_media_view.h" @@ -37,218 +41,399 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Media { namespace Player { -using ButtonState = PlayButtonLayout::State; - -class Widget::PlayButton : public Ui::RippleButton { +class WithDropdownController { public: - PlayButton(QWidget *parent); + WithDropdownController( + not_null button, + not_null menuParent, + Fn menuOverCallback); + virtual ~WithDropdownController() = default; - void setState(PlayButtonLayout::State state) { - _layout.setState(state); - } - void finishTransform() { - _layout.finishTransform(); - } + [[nodiscard]] not_null button() const; + Ui::DropdownMenu *menu() const; + + void updateDropdownGeometry(); + + void hideTemporarily(); + void showBack(); protected: - void paintEvent(QPaintEvent *e) override; - - QImage prepareRippleMask() const override; - QPoint prepareRippleStartPosition() const override; + void showMenu(); private: - PlayButtonLayout _layout; + virtual void fillMenu(not_null menu) = 0; + + const not_null _button; + const not_null _menuParent; + const Fn _menuOverCallback; + base::unique_qptr _menu; + bool _temporarilyHidden = false; + bool _overButton = false; }; -class Widget::SpeedButton : public Ui::IconButton { +class Widget::OrderController final : public WithDropdownController { public: - SpeedButton(QWidget *parent, const style::IconButton &st); + OrderController( + not_null button, + not_null menuParent, + Fn menuOverCallback); + +private: + void fillMenu(not_null menu) override; + void updateIcon(); + +}; + +class Widget::SpeedController final : public WithDropdownController { +public: + SpeedController( + not_null button, + not_null menuParent, + Fn menuOverCallback); [[nodiscard]] rpl::producer<> saved() const; -protected: - void contextMenuEvent(QContextMenuEvent *e) override; - private: - class SpeedController final { - public: - SpeedController() { - setSpeed(Core::App().settings().voicePlaybackSpeed()); - _speed = Core::App().settings().voicePlaybackSpeed(true); - } + void fillMenu(not_null menu) override; + void updateIcon(); - [[nodiscard]] rpl::producer speedValue() const { - return _speedChanged.events_starting_with(speed()); - } - [[nodiscard]] rpl::producer<> saved() const { - return _saved.events(); - } - [[nodiscard]] float64 speed() const { - return _isDefault ? 1. : _speed; - } - [[nodiscard]] bool isDefault() const { - return _isDefault; - } - [[nodiscard]] float64 lastNonDefaultSpeed() const { - return _speed; - } - void toggleDefault() { - _isDefault = !_isDefault; - _speedChanged.fire(speed()); - } - void setSpeed(float64 newSpeed) { - if (!(_isDefault = (newSpeed == 1.))) { - _speed = newSpeed; - } - _speedChanged.fire(speed()); - } - void save() { - Core::App().settings().setVoicePlaybackSpeed(speed()); - Core::App().saveSettingsDelayed(); - _saved.fire({}); - } + [[nodiscard]] float64 speed() const; + [[nodiscard]] bool isDefault() const; + [[nodiscard]] float64 lastNonDefaultSpeed() const; + void toggleDefault(); + void setSpeed(float64 newSpeed); + void save(); - private: - float64 _speed = 2.; - bool _isDefault = true; - rpl::event_stream _speedChanged; - rpl::event_stream<> _saved; - }; - - SpeedController _speed; - - base::unique_qptr _menu; + float64 _speed = 2.; + bool _isDefault = true; + rpl::event_stream _speedChanged; + rpl::event_stream<> _saved; }; -Widget::SpeedButton::SpeedButton(QWidget *parent, const style::IconButton &st) -: IconButton(parent, st) { - setClickedCallback([=] { - _speed.toggleDefault(); - _speed.save(); +WithDropdownController::WithDropdownController( + not_null button, + not_null menuParent, + Fn menuOverCallback) +: _button(button) +, _menuParent(menuParent) +, _menuOverCallback(std::move(menuOverCallback)) { + button->events( + ) | rpl::filter([=](not_null e) { + return (e->type() == QEvent::Enter) + || (e->type() == QEvent::Leave); + }) | rpl::start_with_next([=](not_null e) { + _overButton = (e->type() == QEvent::Enter); + if (_overButton) { + InvokeQueued(button, [=] { + if (_overButton) { + showMenu(); + } + }); + } + }, button->lifetime()); +} + +not_null WithDropdownController::button() const { + return _button; +} + +Ui::DropdownMenu *WithDropdownController::menu() const { + return _menu.get(); +} + +void WithDropdownController::updateDropdownGeometry() { + if (!_menu) { + return; + } + const auto position = _menu->parentWidget()->mapFromGlobal( + _button->mapToGlobal( + QPoint(_button->width(), _button->height()))); + const auto padding = st::mediaPlayerMenu.wrap.padding; + _menu->move(position + - QPoint(_menu->width(), 0) + + QPoint(padding.right(), -padding.top()) + + st::mediaPlayerMenuPosition); +} + +void WithDropdownController::hideTemporarily() { + if (_menu && !_menu->isHidden()) { + _temporarilyHidden = true; + _menu->hide(); + } +} + +void WithDropdownController::showBack() { + if (_temporarilyHidden) { + _temporarilyHidden = false; + if (_menu && _menu->isHidden()) { + _menu->show(); + } + } +} + +void WithDropdownController::showMenu() { + if (_menu) { + return; + } + _menu.emplace(_menuParent, st::mediaPlayerMenu); + const auto raw = _menu.get(); + _menu->events( + ) | rpl::start_with_next([this](not_null e) { + const auto type = e->type(); + if (type == QEvent::Enter) { + _menuOverCallback(true); + } else if (type == QEvent::Leave) { + _menuOverCallback(false); + } + }, _menu->lifetime()); + _menu->setHiddenCallback([=]{ + Ui::PostponeCall(raw, [this] { + _menu = nullptr; + }); + }); + _button->installEventFilter(raw); + fillMenu(raw); + updateDropdownGeometry(); + _menu->showAnimated(Ui::PanelAnimation::Origin::TopRight); +} + +Widget::OrderController::OrderController( + not_null button, + not_null menuParent, + Fn menuOverCallback) +: WithDropdownController(button, menuParent, std::move(menuOverCallback)) { + button->setClickedCallback([=] { + showMenu(); }); - struct Icons { - const style::icon *icon = nullptr; - const style::icon *over = nullptr; - }; + Core::App().settings().playerOrderModeValue( + ) | rpl::start_with_next([=] { + updateIcon(); + }, button->lifetime()); +} - _speed.speedValue( - ) | rpl::start_with_next([=](float64 speed) { - const auto isDefaultSpeed = _speed.isDefault(); - const auto nonDefaultSpeed = _speed.lastNonDefaultSpeed(); - - const auto icons = [&]() -> Icons { - if (nonDefaultSpeed == .5) { - return { - .icon = isDefaultSpeed - ? &st::mediaPlayerSpeedSlowDisabledIcon - : &st::mediaPlayerSpeedSlowIcon, - .over = isDefaultSpeed - ? &st::mediaPlayerSpeedSlowDisabledIconOver - : &st::mediaPlayerSpeedSlowIcon, - }; - } else if (nonDefaultSpeed == 1.5) { - return { - .icon = isDefaultSpeed - ? &st::mediaPlayerSpeedFastDisabledIcon - : &st::mediaPlayerSpeedFastIcon, - .over = isDefaultSpeed - ? &st::mediaPlayerSpeedFastDisabledIconOver - : &st::mediaPlayerSpeedFastIcon, - }; - } else { - return { - .icon = isDefaultSpeed - ? &st::mediaPlayerSpeedDisabledIcon - : nullptr, // 2x icon. - .over = isDefaultSpeed - ? &st::mediaPlayerSpeedDisabledIconOver - : nullptr, // 2x icon. - }; +void Widget::OrderController::fillMenu(not_null menu) { + const auto addOrderAction = [&](OrderMode mode) { + struct Fields { + QString label; + const style::icon &icon; + const style::icon &activeIcon; + }; + const auto current = Core::App().settings().playerOrderMode(); + const auto active = (current == mode); + const auto callback = [=] { + Core::App().settings().setPlayerOrderMode(active + ? OrderMode::Default + : mode); + Core::App().saveSettingsDelayed(); + }; + const auto fields = [&]() -> Fields { + switch (mode) { + case OrderMode::Reverse: return { + .label = tr::lng_audio_player_reverse(tr::now), + .icon = st::mediaPlayerOrderIconReverse, + .activeIcon = st::mediaPlayerOrderIconReverseActive, + }; + case OrderMode::Shuffle: return { + .label = tr::lng_audio_player_shuffle(tr::now), + .icon = st::mediaPlayerOrderIconShuffle, + .activeIcon = st::mediaPlayerOrderIconShuffleActive, + }; } + Unexpected("Order mode in addOrderAction."); }(); - - setIconOverride(icons.icon, icons.over); - setRippleColorOverride(isDefaultSpeed - ? &st::mediaPlayerSpeedDisabledRippleBg - : nullptr); - }, lifetime()); + menu->addAction(base::make_unique_q( + menu, + (active + ? st::mediaPlayerOrderMenuActive + : st::mediaPlayerOrderMenu), + Ui::Menu::CreateAction(menu, fields.label, callback), + &(active ? fields.activeIcon : fields.icon), + &(active ? fields.activeIcon : fields.icon))); + }; + addOrderAction(OrderMode::Reverse); + addOrderAction(OrderMode::Shuffle); } -void Widget::SpeedButton::contextMenuEvent(QContextMenuEvent *e) { - _menu = base::make_unique_q( - this, - st::mediaPlayerPopupMenu); +void Widget::OrderController::updateIcon() { + switch (Core::App().settings().playerOrderMode()) { + case OrderMode::Default: + button()->setIconOverride( + &st::mediaPlayerReverseDisabledIcon, + &st::mediaPlayerReverseDisabledIconOver); + button()->setRippleColorOverride( + &st::mediaPlayerRepeatDisabledRippleBg); + break; + case OrderMode::Reverse: + button()->setIconOverride(&st::mediaPlayerReverseIcon); + button()->setRippleColorOverride(nullptr); + break; + case OrderMode::Shuffle: + button()->setIconOverride(&st::mediaPlayerShuffleIcon); + button()->setRippleColorOverride(nullptr); + break; + } +} - const auto setPlaybackSpeed = [=](float64 speed) { - _speed.setSpeed(speed); - _speed.save(); - }; - - const auto currentSpeed = _speed.speed(); - const auto addSpeed = [&](float64 speed, QString text = QString()) { - if (text.isEmpty()) { - text = QString::number(speed); +Widget::SpeedController::SpeedController( + not_null button, + not_null menuParent, + Fn menuOverCallback) +: WithDropdownController(button, menuParent, std::move(menuOverCallback)) { + button->setClickedCallback([=] { + toggleDefault(); + save(); + if (const auto current = menu()) { + current->otherEnter(); } - _menu->addAction( - text, - [=] { setPlaybackSpeed(speed); }, - (speed == currentSpeed) ? &st::mediaPlayerMenuCheck : nullptr); + }); + + setSpeed(Core::App().settings().voicePlaybackSpeed()); + _speed = Core::App().settings().voicePlaybackSpeed(true); + + _speedChanged.events_starting_with( + speed() + ) | rpl::start_with_next([=] { + updateIcon(); + }, button->lifetime()); +} + +void Widget::SpeedController::updateIcon() { + const auto isDefaultSpeed = isDefault(); + const auto nonDefaultSpeed = lastNonDefaultSpeed(); + + if (nonDefaultSpeed == .5) { + button()->setIconOverride( + (isDefaultSpeed + ? &st::mediaPlayerSpeedSlowDisabledIcon + : &st::mediaPlayerSpeedSlowIcon), + (isDefaultSpeed + ? &st::mediaPlayerSpeedSlowDisabledIconOver + : &st::mediaPlayerSpeedSlowIcon)); + } else if (nonDefaultSpeed == 1.5) { + button()->setIconOverride( + (isDefaultSpeed + ? &st::mediaPlayerSpeedFastDisabledIcon + : &st::mediaPlayerSpeedFastIcon), + (isDefaultSpeed + ? &st::mediaPlayerSpeedFastDisabledIconOver + : &st::mediaPlayerSpeedFastIcon)); + } else { + button()->setIconOverride( + isDefaultSpeed ? &st::mediaPlayerSpeedDisabledIcon : nullptr, + (isDefaultSpeed + ? &st::mediaPlayerSpeedDisabledIconOver + : nullptr)); + } + button()->setRippleColorOverride(isDefaultSpeed + ? &st::mediaPlayerSpeedDisabledRippleBg + : nullptr); +} + +rpl::producer<> Widget::SpeedController::saved() const { + return _saved.events(); +} + +float64 Widget::SpeedController::speed() const { + return _isDefault ? 1. : _speed; +} + +bool Widget::SpeedController::isDefault() const { + return _isDefault; +} + +float64 Widget::SpeedController::lastNonDefaultSpeed() const { + return _speed; +} + +void Widget::SpeedController::toggleDefault() { + _isDefault = !_isDefault; + _speedChanged.fire(speed()); +} + +void Widget::SpeedController::setSpeed(float64 newSpeed) { + if (!(_isDefault = (newSpeed == 1.))) { + _speed = newSpeed; + } + _speedChanged.fire(speed()); +} + +void Widget::SpeedController::save() { + Core::App().settings().setVoicePlaybackSpeed(speed()); + Core::App().saveSettingsDelayed(); + _saved.fire({}); +} + +void Widget::SpeedController::fillMenu(not_null menu) { + const auto currentSpeed = speed(); + const auto addSpeedAction = [&](float64 speed, QString text) { + const auto callback = [=] { + setSpeed(speed); + save(); + }; + const auto icon = (speed == currentSpeed) + ? &st::mediaPlayerMenuCheck + : nullptr; + auto action = base::make_unique_q( + menu, + st::mediaPlayerSpeedMenu, + Ui::Menu::CreateAction(menu, text, callback), + icon, + icon); + const auto raw = action.get(); + _speedChanged.events( + ) | rpl::start_with_next([=](float64 updatedSpeed) { + const auto icon = (speed == updatedSpeed) + ? &st::mediaPlayerMenuCheck + : nullptr; + raw->setIcon(icon, icon); + }, raw->lifetime()); + menu->addAction(std::move(action)); }; - addSpeed(0.5, tr::lng_voice_speed_slow(tr::now)); - addSpeed(1., tr::lng_voice_speed_normal(tr::now)); - addSpeed(1.5, tr::lng_voice_speed_fast(tr::now)); - addSpeed(2., tr::lng_voice_speed_very_fast(tr::now)); - - _menu->popup(e->globalPos()); + addSpeedAction(0.5, tr::lng_voice_speed_slow(tr::now)); + addSpeedAction(1., tr::lng_voice_speed_normal(tr::now)); + addSpeedAction(1.5, tr::lng_voice_speed_fast(tr::now)); + addSpeedAction(2., tr::lng_voice_speed_very_fast(tr::now)); } -rpl::producer<> Widget::SpeedButton::saved() const { - return _speed.saved(); -} - -Widget::PlayButton::PlayButton(QWidget *parent) : Ui::RippleButton(parent, st::mediaPlayerButton.ripple) -, _layout(st::mediaPlayerButton, [this] { update(); }) { - resize(st::mediaPlayerButtonSize); - setCursor(style::cur_pointer); -} - -void Widget::PlayButton::paintEvent(QPaintEvent *e) { - Painter p(this); - - paintRipple(p, st::mediaPlayerButton.rippleAreaPosition.x(), st::mediaPlayerButton.rippleAreaPosition.y()); - p.translate(st::mediaPlayerButtonPosition.x(), st::mediaPlayerButtonPosition.y()); - _layout.paint(p, st::mediaPlayerActiveFg); -} - -QImage Widget::PlayButton::prepareRippleMask() const { - auto size = QSize(st::mediaPlayerButton.rippleAreaSize, st::mediaPlayerButton.rippleAreaSize); - return Ui::RippleAnimation::ellipseMask(size); -} - -QPoint Widget::PlayButton::prepareRippleStartPosition() const { - return QPoint(mapFromGlobal(QCursor::pos()) - st::mediaPlayerButton.rippleAreaPosition); -} - -Widget::Widget(QWidget *parent, not_null session) +Widget::Widget( + QWidget *parent, + not_null dropdownsParent, + not_null controller) : RpWidget(parent) -, _session(session) +, _controller(controller) +, _orderMenuParent(dropdownsParent) , _nameLabel(this, st::mediaPlayerName) -, _timeLabel(this, st::mediaPlayerTime) -, _playPause(this) -, _volumeToggle(this, st::mediaPlayerVolumeToggle) -, _repeatTrack(this, st::mediaPlayerRepeatButton) -, _playbackSpeed(this, st::mediaPlayerSpeedButton) +, _rightControls(this, object_ptr(this)) +, _timeLabel(rightControls(), st::mediaPlayerTime) +, _playPause(this, st::mediaPlayerPlayButton) +, _volumeToggle(rightControls(), st::mediaPlayerVolumeToggle) +, _repeatToggle(rightControls(), st::mediaPlayerRepeatButton) +, _orderToggle(rightControls(), st::mediaPlayerRepeatButton) +, _speedToggle(rightControls(), st::mediaPlayerSpeedButton) , _close(this, st::mediaPlayerClose) , _shadow(this) , _playbackSlider(this, st::mediaPlayerPlayback) -, _playbackProgress(std::make_unique()) { +, _volume(std::in_place, dropdownsParent.get()) +, _playbackProgress(std::make_unique()) +, _orderController( + std::make_unique( + _orderToggle.data(), + dropdownsParent, + [=](bool over) { markOver(over); })) +, _speedController( + std::make_unique( + _speedToggle.data(), + dropdownsParent, + [=](bool over) { markOver(over); })) { setAttribute(Qt::WA_OpaquePaintEvent); setMouseTracking(true); resize(width(), st::mediaPlayerHeight + st::lineWidth); + setupRightControls(); + _nameLabel->setAttribute(Qt::WA_TransparentForMouseEvents); _timeLabel->setAttribute(Qt::WA_TransparentForMouseEvents); @@ -290,62 +475,115 @@ Widget::Widget(QWidget *parent, not_null session) updateVolumeToggleIcon(); }, lifetime()); - updateRepeatTrackIcon(); - _repeatTrack->setClickedCallback([=] { - instance()->toggleRepeat(AudioMsgId::Type::Song); + Core::App().settings().playerRepeatModeValue( + ) | rpl::start_with_next([=] { + updateRepeatToggleIcon(); + }, lifetime()); + + _repeatToggle->setClickedCallback([=] { + auto &settings = Core::App().settings(); + settings.setPlayerRepeatMode([&] { + switch (settings.playerRepeatMode()) { + case RepeatMode::None: return RepeatMode::One; + case RepeatMode::One: return RepeatMode::All; + case RepeatMode::All: return RepeatMode::None; + } + Unexpected("Repeat mode in Settings."); + }()); + Core::App().saveSettingsDelayed(); }); - _playbackSpeed->saved( + _speedController->saved( ) | rpl::start_with_next([=] { instance()->updateVoicePlaybackSpeed(); }, lifetime()); - subscribe(instance()->repeatChangedNotifier(), [this](AudioMsgId::Type type) { - if (type == _type) { - updateRepeatTrackIcon(); + instance()->trackChanged( + ) | rpl::filter([=](AudioMsgId::Type type) { + return (type == _type); + }) | rpl::start_with_next([=](AudioMsgId::Type type) { + handleSongChange(); + updateControlsVisibility(); + updateLabelsGeometry(); + }, lifetime()); + + instance()->tracksFinished( + ) | rpl::filter([=](AudioMsgId::Type type) { + return (type == AudioMsgId::Type::Voice); + }) | rpl::start_with_next([=](AudioMsgId::Type type) { + _voiceIsActive = false; + const auto currentSong = instance()->current(AudioMsgId::Type::Song); + const auto songState = instance()->getState(AudioMsgId::Type::Song); + if (currentSong == songState.id && !IsStoppedOrStopping(songState.state)) { + setType(AudioMsgId::Type::Song); } - }); - subscribe(instance()->trackChangedNotifier(), [this](AudioMsgId::Type type) { - if (type == _type) { - handleSongChange(); - updateControlsVisibility(); - updateLabelsGeometry(); - } - }); - subscribe(instance()->tracksFinishedNotifier(), [this](AudioMsgId::Type type) { - if (type == AudioMsgId::Type::Voice) { - _voiceIsActive = false; - const auto currentSong = instance()->current(AudioMsgId::Type::Song); - const auto songState = instance()->getState(AudioMsgId::Type::Song); - if (currentSong == songState.id && !IsStoppedOrStopping(songState.state)) { - setType(AudioMsgId::Type::Song); - } - } - }); + }, lifetime()); instance()->updatedNotifier( ) | rpl::start_with_next([=](const TrackState &state) { handleSongUpdate(state); }, lifetime()); + PrepareVolumeDropdown(_volume.get(), controller, _volumeToggle->events( + ) | rpl::filter([=](not_null e) { + return (e->type() == QEvent::Wheel); + }) | rpl::map([=](not_null e) { + return not_null{ static_cast(e.get()) }; + })); + _volumeToggle->installEventFilter(_volume.get()); + _volume->events( + ) | rpl::start_with_next([=](not_null e) { + if (e->type() == QEvent::Enter) { + markOver(true); + } else if (e->type() == QEvent::Leave) { + markOver(false); + } + }, _volume->lifetime()); + + hidePlaylistOn(_playPause); + hidePlaylistOn(_close); + hidePlaylistOn(_rightControls); + setType(AudioMsgId::Type::Song); - _playPause->finishTransform(); +} + +void Widget::hidePlaylistOn(not_null widget) { + widget->events( + ) | rpl::filter([=](not_null e) { + return (e->type() == QEvent::Enter); + }) | rpl::start_with_next([=] { + updateOverLabelsState(false); + }, widget->lifetime()); +} + +void Widget::setupRightControls() { + const auto raw = rightControls(); + raw->paintRequest( + ) | rpl::start_with_next([=](QRect clip) { + auto p = QPainter(raw); + const auto &icon = st::mediaPlayerControlsFade; + const auto fade = QRect(0, 0, icon.width(), raw->height()); + if (fade.intersects(clip)) { + icon.fill(p, fade); + } + const auto fill = clip.intersected( + { icon.width(), 0, raw->width() - icon.width(), raw->height() }); + if (!fill.isEmpty()) { + p.fillRect(fill, st::mediaPlayerBg); + } + }, raw->lifetime()); + _rightControls->show(anim::type::instant); } void Widget::updateVolumeToggleIcon() { - auto icon = []() -> const style::icon * { - auto volume = Core::App().settings().songVolume(); - if (volume > 0) { - if (volume < 1 / 3.) { - return &st::mediaPlayerVolumeIcon1; - } else if (volume < 2 / 3.) { - return &st::mediaPlayerVolumeIcon2; - } - return &st::mediaPlayerVolumeIcon3; - } - return nullptr; - }; - _volumeToggle->setIconOverride(icon()); + _volumeToggle->setIconOverride([] { + const auto volume = Core::App().settings().songVolume(); + return (volume == 0.) + ? &st::mediaPlayerVolumeIcon0 + : (volume < 0.66) + ? &st::mediaPlayerVolumeIcon1 + : nullptr; + }()); } void Widget::setCloseCallback(Fn callback) { @@ -377,29 +615,53 @@ void Widget::setShadowGeometryToLeft(int x, int y, int w, int h) { _shadow->setGeometryToLeft(x, y, w, h); } -void Widget::showShadow() { +void Widget::showShadowAndDropdowns() { _shadow->show(); _playbackSlider->setVisible(_type == AudioMsgId::Type::Song); + if (_volumeHidden) { + _volumeHidden = false; + _volume->show(); + } + _speedController->showBack(); + _orderController->showBack(); } -void Widget::hideShadow() { +void Widget::updateDropdownsGeometry() { + const auto dropdownWidth = st::mediaPlayerVolumeSize.width(); + const auto position = _volume->parentWidget()->mapFromGlobal( + _volumeToggle->mapToGlobal( + QPoint( + (_volumeToggle->width() - dropdownWidth) / 2, + height()))); + const auto playerMargins = _volume->getMargin(); + const auto shift = QPoint(playerMargins.left(), playerMargins.top()); + _volume->move(position - shift); + + _orderController->updateDropdownGeometry(); + _speedController->updateDropdownGeometry(); +} + +void Widget::hideShadowAndDropdowns() { _shadow->hide(); _playbackSlider->hide(); + if (!_volume->isHidden()) { + _volumeHidden = true; + _volume->hide(); + } + _speedController->hideTemporarily(); + _orderController->hideTemporarily(); } -QPoint Widget::getPositionForVolumeWidget() const { - auto x = _volumeToggle->x(); - x += (_volumeToggle->width() - st::mediaPlayerVolumeSize.width()) / 2; - if (rtl()) x = width() - x - st::mediaPlayerVolumeSize.width(); - return QPoint(x, height()); -} - -void Widget::volumeWidgetCreated(VolumeWidget *widget) { - _volumeToggle->installEventFilter(widget); +void Widget::raiseDropdowns() { + _volume->raise(); } Widget::~Widget() = default; +not_null Widget::rightControls() { + return _rightControls->entity(); +} + void Widget::handleSeekProgress(float64 progress) { if (!_lastDurationMs) return; @@ -425,20 +687,46 @@ void Widget::handleSeekFinished(float64 progress) { void Widget::resizeEvent(QResizeEvent *e) { updateControlsGeometry(); + _narrow = (width() < st::mediaPlayerWideWidth); + updateControlsWrapVisibility(); } void Widget::updateControlsGeometry() { - auto right = st::mediaPlayerCloseRight; - _close->moveToRight(right, st::mediaPlayerPlayTop); right += _close->width(); + _close->moveToRight(st::mediaPlayerCloseRight, st::mediaPlayerPlayTop); + auto right = 0; if (hasPlaybackSpeedControl()) { - _playbackSpeed->moveToRight(right, st::mediaPlayerPlayTop); right += _playbackSpeed->width(); + _speedToggle->moveToRight(right, 0); right += _speedToggle->width(); } - _repeatTrack->moveToRight(right, st::mediaPlayerPlayTop); right += _repeatTrack->width(); - _volumeToggle->moveToRight(right, st::mediaPlayerPlayTop); right += _volumeToggle->width(); + _repeatToggle->moveToRight(right, 0); right += _repeatToggle->width(); + _orderToggle->moveToRight(right, 0); right += _orderToggle->width(); + _volumeToggle->moveToRight(right, 0); right += _volumeToggle->width(); + + updateControlsWrapGeometry(); updatePlayPrevNextPositions(); - _playbackSlider->setGeometry(0, height() - st::mediaPlayerPlayback.fullWidth, width(), st::mediaPlayerPlayback.fullWidth); + _playbackSlider->setGeometry( + 0, + height() - st::mediaPlayerPlayback.fullWidth, + width(), + st::mediaPlayerPlayback.fullWidth); + + updateDropdownsGeometry(); +} + +void Widget::updateControlsWrapGeometry() { + const auto fade = st::mediaPlayerControlsFade.width(); + const auto controls = getTimeRight() + _timeLabel->width() + fade; + rightControls()->resize(controls, _repeatToggle->height()); + _rightControls->move( + width() - st::mediaPlayerCloseRight - _close->width() - controls, + st::mediaPlayerPlayTop); +} + +void Widget::updateControlsWrapVisibility() { + _rightControls->toggle( + _over || !_narrow, + isHidden() ? anim::type::instant : anim::type::normal); } void Widget::paintEvent(QPaintEvent *e) { @@ -449,8 +737,33 @@ void Widget::paintEvent(QPaintEvent *e) { } } +void Widget::enterEventHook(QEnterEvent *e) { + markOver(true); +} + void Widget::leaveEventHook(QEvent *e) { - updateOverLabelsState(false); + markOver(false); +} + +void Widget::markOver(bool over) { + if (over) { + _over = true; + _wontBeOver = false; + InvokeQueued(this, [=] { + updateControlsWrapVisibility(); + }); + } else { + _wontBeOver = true; + InvokeQueued(this, [=] { + if (!_wontBeOver) { + return; + } + _wontBeOver = false; + _over = false; + updateControlsWrapVisibility(); + }); + updateOverLabelsState(false); + } } void Widget::mouseMoveEvent(QMouseEvent *e) { @@ -480,10 +793,13 @@ void Widget::mouseReleaseEvent(QMouseEvent *e) { } void Widget::updateOverLabelsState(QPoint pos) { - auto left = getLabelsLeft(); - auto right = getLabelsRight(); - auto labels = myrtlrect(left, 0, width() - right - left, height() - st::mediaPlayerPlayback.fullWidth); - auto over = labels.contains(pos); + const auto left = getNameLeft(); + const auto right = width() + - _rightControls->x() + - _rightControls->width() + + getTimeRight(); + const auto labels = myrtlrect(left, 0, width() - right - left, height() - st::mediaPlayerPlayback.fullWidth); + const auto over = labels.contains(pos); updateOverLabelsState(over); } @@ -491,8 +807,7 @@ void Widget::updateOverLabelsState(bool over) { _labelsOver = over; auto pressShowsItem = _labelsOver && (_type == AudioMsgId::Type::Voice); setCursor(pressShowsItem ? style::cur_pointer : style::cur_default); - auto showPlaylist = over && (_type == AudioMsgId::Type::Song); - instance()->playerWidgetOver().notify(showPlaylist, true); + _togglePlaylistRequests.fire(over && (_type == AudioMsgId::Type::Song)); } void Widget::updatePlayPrevNextPositions() { @@ -508,7 +823,7 @@ void Widget::updatePlayPrevNextPositions() { updateLabelsGeometry(); } -int Widget::getLabelsLeft() const { +int Widget::getNameLeft() const { auto result = st::mediaPlayerPlayLeft + _playPause->width(); if (_previousTrack) { result += _previousTrack->width() + st::mediaPlayerPlaySkip + _nextTrack->width() + st::mediaPlayerPlaySkip; @@ -517,34 +832,58 @@ int Widget::getLabelsLeft() const { return result; } -int Widget::getLabelsRight() const { - auto result = st::mediaPlayerCloseRight + _close->width(); +int Widget::getNameRight() const { + return st::mediaPlayerCloseRight + + _close->width() + + st::mediaPlayerPadding; +} + +int Widget::getTimeRight() const { + auto result = 0; if (_type == AudioMsgId::Type::Song) { - result += _repeatTrack->width() + _volumeToggle->width(); + result += _repeatToggle->width() + + _orderToggle->width() + + _volumeToggle->width(); } if (hasPlaybackSpeedControl()) { - result += _playbackSpeed->width(); + result += _speedToggle->width(); } result += st::mediaPlayerPadding; return result; } void Widget::updateLabelsGeometry() { - auto left = getLabelsLeft(); - auto right = getLabelsRight(); - - auto widthForName = width() - left - right; - widthForName -= _timeLabel->width() + 2 * st::normalFont->spacew; + const auto left = getNameLeft(); + const auto widthForName = width() + - left + - getNameRight(); _nameLabel->resizeToWidth(widthForName); - _nameLabel->moveToLeft(left, st::mediaPlayerNameTop - st::mediaPlayerName.style.font->ascent); + + const auto right = getTimeRight(); _timeLabel->moveToRight(right, st::mediaPlayerNameTop - st::mediaPlayerTime.font->ascent); + + updateControlsWrapGeometry(); } -void Widget::updateRepeatTrackIcon() { - auto repeating = instance()->repeatEnabled(AudioMsgId::Type::Song); - _repeatTrack->setIconOverride(repeating ? nullptr : &st::mediaPlayerRepeatDisabledIcon, repeating ? nullptr : &st::mediaPlayerRepeatDisabledIconOver); - _repeatTrack->setRippleColorOverride(repeating ? nullptr : &st::mediaPlayerRepeatDisabledRippleBg); +void Widget::updateRepeatToggleIcon() { + switch (Core::App().settings().playerRepeatMode()) { + case RepeatMode::None: + _repeatToggle->setIconOverride( + &st::mediaPlayerRepeatDisabledIcon, + &st::mediaPlayerRepeatDisabledIconOver); + _repeatToggle->setRippleColorOverride( + &st::mediaPlayerRepeatDisabledRippleBg); + break; + case RepeatMode::One: + _repeatToggle->setIconOverride(&st::mediaPlayerRepeatOneIcon); + _repeatToggle->setRippleColorOverride(nullptr); + break; + case RepeatMode::All: + _repeatToggle->setIconOverride(nullptr); + _repeatToggle->setRippleColorOverride(nullptr); + break; + } } void Widget::checkForTypeChange() { @@ -567,9 +906,10 @@ bool Widget::hasPlaybackSpeedControl() const { } void Widget::updateControlsVisibility() { - _repeatTrack->setVisible(_type == AudioMsgId::Type::Song); + _repeatToggle->setVisible(_type == AudioMsgId::Type::Song); + _orderToggle->setVisible(_type == AudioMsgId::Type::Song); _volumeToggle->setVisible(_type == AudioMsgId::Type::Song); - _playbackSpeed->setVisible(hasPlaybackSpeedControl()); + _speedToggle->setVisible(hasPlaybackSpeedControl()); if (!_shadow->isHidden()) { _playbackSlider->setVisible(_type == AudioMsgId::Type::Song); } @@ -611,15 +951,11 @@ void Widget::handleSongUpdate(const TrackState &state) { if (instance()->isSeeking(_type)) { showPause = true; } - auto buttonState = [audio = state.id.audio(), showPause] { - if (audio->loading()) { - return ButtonState::Cancel; - } else if (showPause) { - return ButtonState::Pause; - } - return ButtonState::Play; - }; - _playPause->setState(buttonState()); + _playPause->setIconOverride(state.id.audio()->loading() + ? &st::mediaPlayerCancelIcon + : showPause + ? &st::mediaPlayerPauseIcon + : nullptr); updateTimeText(state); } @@ -730,10 +1066,10 @@ void Widget::handlePlaylistUpdate() { createPrevNextButtons(); _previousTrack->setIconOverride(previousEnabled ? nullptr : &st::mediaPlayerPreviousDisabledIcon); _previousTrack->setRippleColorOverride(previousEnabled ? nullptr : &st::mediaPlayerBg); - _previousTrack->setCursor(previousEnabled ? style::cur_pointer : style::cur_default); + _previousTrack->setPointerCursor(previousEnabled); _nextTrack->setIconOverride(nextEnabled ? nullptr : &st::mediaPlayerNextDisabledIcon); _nextTrack->setRippleColorOverride(nextEnabled ? nullptr : &st::mediaPlayerBg); - _nextTrack->setCursor(nextEnabled ? style::cur_pointer : style::cur_default); + _nextTrack->setPointerCursor(nextEnabled); } } @@ -742,13 +1078,15 @@ void Widget::createPrevNextButtons() { _previousTrack.create(this, st::mediaPlayerPreviousButton); _previousTrack->show(); _previousTrack->setClickedCallback([=]() { - instance()->previous(); + instance()->previous(_type); }); _nextTrack.create(this, st::mediaPlayerNextButton); _nextTrack->show(); _nextTrack->setClickedCallback([=]() { - instance()->next(); + instance()->next(_type); }); + hidePlaylistOn(_previousTrack); + hidePlaylistOn(_nextTrack); updatePlayPrevNextPositions(); } } diff --git a/Telegram/SourceFiles/media/player/media_player_widget.h b/Telegram/SourceFiles/media/player/media_player_widget.h index a676af2cb..bd8debcbe 100644 --- a/Telegram/SourceFiles/media/player/media_player_widget.h +++ b/Telegram/SourceFiles/media/player/media_player_widget.h @@ -19,6 +19,8 @@ class LabelSimple; class IconButton; class PlainShadow; class FilledSlider; +template +class FadeWrap; } // namespace Ui namespace Media { @@ -27,57 +29,70 @@ class PlaybackProgress; } // namespace Clip } // namespace Media -namespace Main { -class Session; -} // namespace Main +namespace Window { +class SessionController; +} // namespace Window namespace Media { namespace Player { class PlayButton; class SpeedButton; -class VolumeWidget; +class Dropdown; struct TrackState; -class Widget : public Ui::RpWidget, private base::Subscriber { +class Widget final : public Ui::RpWidget, private base::Subscriber { public: - Widget(QWidget *parent, not_null session); + Widget( + QWidget *parent, + not_null dropdownsParent, + not_null controller); void setCloseCallback(Fn callback); void setShowItemCallback(Fn)> callback); void stopAndClose(); void setShadowGeometryToLeft(int x, int y, int w, int h); - void showShadow(); - void hideShadow(); + void hideShadowAndDropdowns(); + void showShadowAndDropdowns(); + void updateDropdownsGeometry(); + void raiseDropdowns(); - QPoint getPositionForVolumeWidget() const; - void volumeWidgetCreated(VolumeWidget *widget); + [[nodiscard]] rpl::producer togglePlaylistRequests() const { + return _togglePlaylistRequests.events(); + } ~Widget(); -protected: +private: void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; + void enterEventHook(QEnterEvent *e) override; void leaveEventHook(QEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; -private: + [[nodiscard]] not_null rightControls(); + void setupRightControls(); + void handleSeekProgress(float64 progress); void handleSeekFinished(float64 progress); - int getLabelsLeft() const; - int getLabelsRight() const; + [[nodiscard]] int getNameLeft() const; + [[nodiscard]] int getNameRight() const; + [[nodiscard]] int getTimeRight() const; void updateOverLabelsState(QPoint pos); void updateOverLabelsState(bool over); + void hidePlaylistOn(not_null widget); void updatePlayPrevNextPositions(); void updateLabelsGeometry(); - void updateRepeatTrackIcon(); + void updateRepeatToggleIcon(); void updateControlsVisibility(); void updateControlsGeometry(); + void updateControlsWrapGeometry(); + void updateControlsWrapVisibility(); void createPrevNextButtons(); void destroyPrevNextButtons(); @@ -92,8 +107,10 @@ private: void updateTimeText(const TrackState &state); void updateTimeLabel(); + void markOver(bool over); - const not_null _session; + const not_null _controller; + const not_null _orderMenuParent; crl::time _seekPositionMs = -1; crl::time _lastDurationMs = 0; @@ -111,21 +128,32 @@ private: bool _labelsOver = false; bool _labelsDown = false; + rpl::event_stream _togglePlaylistRequests; + bool _narrow = false; + bool _over = false; + bool _wontBeOver = false; + bool _volumeHidden = false; class PlayButton; - class SpeedButton; + class OrderController; + class SpeedController; object_ptr _nameLabel; + object_ptr> _rightControls; object_ptr _timeLabel; object_ptr _previousTrack = { nullptr }; - object_ptr _playPause; + object_ptr _playPause; object_ptr _nextTrack = { nullptr }; object_ptr _volumeToggle; - object_ptr _repeatTrack; - object_ptr _playbackSpeed; + object_ptr _repeatToggle; + object_ptr _orderToggle; + object_ptr _speedToggle; object_ptr _close; object_ptr _shadow = { nullptr }; object_ptr _playbackSlider; + base::unique_qptr _volume; std::unique_ptr _playbackProgress; + std::unique_ptr _orderController; + std::unique_ptr _speedController; rpl::lifetime _playlistChangesLifetime; diff --git a/Telegram/SourceFiles/media/system_media_controls_manager.cpp b/Telegram/SourceFiles/media/system_media_controls_manager.cpp index 1684a3b66..22dcdb2bc 100644 --- a/Telegram/SourceFiles/media/system_media_controls_manager.cpp +++ b/Telegram/SourceFiles/media/system_media_controls_manager.cpp @@ -96,8 +96,7 @@ SystemMediaControlsManager::SystemMediaControlsManager( _lifetimeDownload.destroy(); }, _lifetime); - auto trackChanged = base::ObservableViewer( - mediaPlayer->trackChangedNotifier() + auto trackChanged = mediaPlayer->trackChanged( ) | rpl::filter([=](AudioMsgId::Type audioType) { return audioType == type; }); diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style index 81591e511..9409e3cf9 100644 --- a/Telegram/SourceFiles/media/view/media_view.style +++ b/Telegram/SourceFiles/media/view/media_view.style @@ -46,12 +46,12 @@ mediaviewPlayButton: IconButton(mediaviewControlsButton) { height: 42px; rippleAreaSize: 42px; - icon: icon {{ "player_play", mediaviewPlaybackIconFg }}; - iconOver: icon {{ "player_play", mediaviewPlaybackIconFgOver }}; + icon: icon {{ "player/player_play", mediaviewPlaybackIconFg }}; + iconOver: icon {{ "player/player_play", mediaviewPlaybackIconFgOver }}; iconPosition: point(9px, 9px); } -mediaviewPauseIcon: icon {{ "player_pause", mediaviewPlaybackIconFg }}; -mediaviewPauseIconOver: icon {{ "player_pause", mediaviewPlaybackIconFgOver }}; +mediaviewPauseIcon: icon {{ "player/player_pause", mediaviewPlaybackIconFg }}; +mediaviewPauseIconOver: icon {{ "player/player_pause", mediaviewPlaybackIconFgOver }}; mediaviewButtonsTop: 7px; @@ -60,34 +60,34 @@ mediaviewMenuToggle: IconButton(mediaviewControlsButton) { width: 34px; height: 34px; rippleAreaSize: 34px; - icon: icon {{ "player_more", mediaviewPlaybackIconFg }}; - iconOver: icon {{ "player_more", mediaviewPlaybackIconFgOver }}; + icon: icon {{ "player/player_more", mediaviewPlaybackIconFg }}; + iconOver: icon {{ "player/player_more", mediaviewPlaybackIconFgOver }}; iconPosition: point(5px, 5px); } mediaviewPipButtonSkip: 5px; mediaviewPipButton: IconButton(mediaviewMenuToggle) { - icon: icon {{ "player_pip", mediaviewPlaybackIconFg }}; - iconOver: icon {{ "player_pip", mediaviewPlaybackIconFgOver }}; + icon: icon {{ "player/player_pip", mediaviewPlaybackIconFg }}; + iconOver: icon {{ "player/player_pip", mediaviewPlaybackIconFgOver }}; } mediaviewFullScreenButtonSkip: 8px; mediaviewFullScreenButton: IconButton(mediaviewMenuToggle) { - icon: icon {{ "player_fullscreen", mediaviewPlaybackIconFg }}; - iconOver: icon {{ "player_fullscreen", mediaviewPlaybackIconFgOver }}; + icon: icon {{ "player/player_fullscreen", mediaviewPlaybackIconFg }}; + iconOver: icon {{ "player/player_fullscreen", mediaviewPlaybackIconFgOver }}; } -mediaviewFullScreenOutIcon: icon {{ "player_minimize", mediaviewPlaybackIconFg }}; -mediaviewFullScreenOutIconOver: icon {{ "player_minimize", mediaviewPlaybackIconFgOver }}; +mediaviewFullScreenOutIcon: icon {{ "player/player_minimize", mediaviewPlaybackIconFg }}; +mediaviewFullScreenOutIconOver: icon {{ "player/player_minimize", mediaviewPlaybackIconFgOver }}; mediaviewVolumeWidth: 75px; mediaviewControllerRadius: 9px; -mediaviewVolumeIcon0: icon {{ "player_volume_off", mediaviewPlaybackIconFg }}; -mediaviewVolumeIcon0Over: icon {{ "player_volume_off", mediaviewPlaybackIconFgOver }}; -mediaviewVolumeIcon1: icon {{ "player_volume_small", mediaviewPlaybackIconFg }}; -mediaviewVolumeIcon1Over: icon {{ "player_volume_small", mediaviewPlaybackIconFgOver }}; -mediaviewVolumeIcon2: icon {{ "player_volume_on", mediaviewPlaybackIconFg }}; -mediaviewVolumeIcon2Over: icon {{ "player_volume_on", mediaviewPlaybackIconFgOver }}; +mediaviewVolumeIcon0: icon {{ "player/player_volume_off", mediaviewPlaybackIconFg }}; +mediaviewVolumeIcon0Over: icon {{ "player/player_volume_off", mediaviewPlaybackIconFgOver }}; +mediaviewVolumeIcon1: icon {{ "player/player_volume_small", mediaviewPlaybackIconFg }}; +mediaviewVolumeIcon1Over: icon {{ "player/player_volume_small", mediaviewPlaybackIconFgOver }}; +mediaviewVolumeIcon2: icon {{ "player/player_volume_on", mediaviewPlaybackIconFg }}; +mediaviewVolumeIcon2Over: icon {{ "player/player_volume_on", mediaviewPlaybackIconFgOver }}; mediaviewVolumeTop: 10px; mediaviewVolumeToggleSkip: 11px; mediaviewVolumeToggle: IconButton(mediaviewControlsButton) { @@ -205,7 +205,7 @@ mediaviewControlsPopupMenu: PopupMenu(defaultPopupMenu) { menu: mediaviewControlsMenu; animation: mediaviewControlsPanelAnimation; } -mediaviewMenuCheck: icon {{ "player_check", mediaviewPlaybackProgressFg }}; +mediaviewMenuCheck: icon {{ "player/player_check", mediaviewPlaybackProgressFg }}; mediaviewSaveMsgCheck: icon {{ "mediaview_save_check", mediaviewSaveMsgFg }}; mediaviewSaveMsgPadding: margins(55px, 19px, 29px, 20px); @@ -293,19 +293,19 @@ pipPlaybackWide: 4px; pipPlaybackSkip: 4px; pipPlaybackTextSkip: 6px; pipPlaybackFont: font(11px); -pipPlayIcon: icon {{ "player_pip_play", mediaviewPipControlsFg }}; -pipPlayIconOver: icon {{ "player_pip_play", mediaviewPipControlsFgOver }}; -pipPauseIcon: icon {{ "player_pip_pause", mediaviewPipControlsFg }}; -pipPauseIconOver: icon {{ "player_pip_pause", mediaviewPipControlsFgOver }}; -pipCloseIcon: icon {{ "player_pip_close", mediaviewPipControlsFg }}; -pipCloseIconOver: icon {{ "player_pip_close", mediaviewPipControlsFgOver }}; -pipEnlargeIcon: icon {{ "player_pip_enlarge", mediaviewPipControlsFg }}; -pipEnlargeIconOver: icon {{ "player_pip_enlarge", mediaviewPipControlsFgOver }}; -pipVolumeIcon0: icon {{ "player_volume_off", mediaviewPipControlsFg }}; -pipVolumeIcon0Over: icon {{ "player_volume_off", mediaviewPipControlsFgOver }}; -pipVolumeIcon1: icon {{ "player_volume_small", mediaviewPipControlsFg }}; -pipVolumeIcon1Over: icon {{ "player_volume_small", mediaviewPipControlsFgOver }}; -pipVolumeIcon2: icon {{ "player_volume_on", mediaviewPipControlsFg }}; -pipVolumeIcon2Over: icon {{ "player_volume_on", mediaviewPipControlsFgOver }}; +pipPlayIcon: icon {{ "player/player_pip_play", mediaviewPipControlsFg }}; +pipPlayIconOver: icon {{ "player/player_pip_play", mediaviewPipControlsFgOver }}; +pipPauseIcon: icon {{ "player/player_pip_pause", mediaviewPipControlsFg }}; +pipPauseIconOver: icon {{ "player/player_pip_pause", mediaviewPipControlsFgOver }}; +pipCloseIcon: icon {{ "player/player_pip_close", mediaviewPipControlsFg }}; +pipCloseIconOver: icon {{ "player/player_pip_close", mediaviewPipControlsFgOver }}; +pipEnlargeIcon: icon {{ "player/player_pip_enlarge", mediaviewPipControlsFg }}; +pipEnlargeIconOver: icon {{ "player/player_pip_enlarge", mediaviewPipControlsFgOver }}; +pipVolumeIcon0: icon {{ "player/player_volume_off", mediaviewPipControlsFg }}; +pipVolumeIcon0Over: icon {{ "player/player_volume_off", mediaviewPipControlsFgOver }}; +pipVolumeIcon1: icon {{ "player/player_volume_small", mediaviewPipControlsFg }}; +pipVolumeIcon1Over: icon {{ "player/player_volume_small", mediaviewPipControlsFgOver }}; +pipVolumeIcon2: icon {{ "player/player_volume_on", mediaviewPipControlsFg }}; +pipVolumeIcon2Over: icon {{ "player/player_volume_on", mediaviewPipControlsFgOver }}; speedSliderDividerSize: size(2px, 8px); diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index e3be57068..740a24f82 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_utilities.h" #include "ui/platform/ui_platform_utility.h" #include "ui/toast/toast.h" +#include "ui/toasts/common_toasts.h" #include "ui/text/format_values.h" #include "ui/item_text_options.h" #include "ui/ui_utility.h" @@ -554,6 +555,24 @@ QSize OverlayWidget::flipSizeByRotation(QSize size) const { return FlipSizeByRotation(size, _rotation); } +bool OverlayWidget::hasCopyRestriction() const { + return (_history && !_history->peer->allowsForwarding()) + || (_message && _message->forbidsForward()); +} + +bool OverlayWidget::showCopyRestriction() { + if (!hasCopyRestriction()) { + return false; + } + Ui::ShowMultilineToast({ + .parentOverride = _widget, + .text = { _history->peer->isBroadcast() + ? tr::lng_error_nocopy_channel(tr::now) + : tr::lng_error_nocopy_group(tr::now) }, + }); + return true; +} + bool OverlayWidget::videoShown() const { return _streamed && !_streamed->instance.info().video.cover.isNull(); } @@ -709,7 +728,9 @@ void OverlayWidget::refreshNavVisibility() { } bool OverlayWidget::contentCanBeSaved() const { - if (_photo) { + if (hasCopyRestriction()) { + return false; + } else if (_photo) { return _photo->hasVideo() || _photoMedia->loaded(); } else if (_document) { return _document->filepath(true).isEmpty() && !_document->loading(); @@ -891,8 +912,10 @@ void OverlayWidget::fillContextMenuActions(const MenuCallback &addAction) { : tr::lng_context_show_in_folder(tr::now); addAction(text, [=] { showInFolder(); }); } - if ((_document && documentContentShown()) || (_photo && _photoMedia->loaded())) { - addAction(tr::lng_mediaview_copy(tr::now), [=] { copyMedia(); }); + if (!hasCopyRestriction()) { + if ((_document && documentContentShown()) || (_photo && _photoMedia->loaded())) { + addAction(tr::lng_mediaview_copy(tr::now), [=] { copyMedia(); }); + } } if ((_photo && _photo->hasAttachedStickers()) || (_document && _document->hasAttachedStickers())) { @@ -923,7 +946,9 @@ void OverlayWidget::fillContextMenuActions(const MenuCallback &addAction) { if (canDelete) { addAction(tr::lng_mediaview_delete(tr::now), [=] { deleteMedia(); }); } - addAction(tr::lng_mediaview_save_as(tr::now), [=] { saveAs(); }); + if (!hasCopyRestriction()) { + addAction(tr::lng_mediaview_save_as(tr::now), [=] { saveAs(); }); + } if (const auto overviewType = computeOverviewType()) { const auto text = _document @@ -1188,7 +1213,7 @@ bool OverlayWidget::radialAnimationCallback(crl::time now) { update(radialRect()); } const auto ready = _document && _documentMedia->loaded(); - const auto streamVideo = ready && _documentMedia->canBePlayed(); + const auto streamVideo = ready && _documentMedia->canBePlayed(_message); const auto tryOpenImage = ready && (_document->size < Images::kReadBytesLimit); if (ready && ((tryOpenImage && !_radial.animating()) || streamVideo)) { @@ -1669,7 +1694,7 @@ void OverlayWidget::downloadMedia() { void OverlayWidget::saveCancel() { if (_document && _document->loading()) { _document->cancel(); - if (_documentMedia->canBePlayed()) { + if (_documentMedia->canBePlayed(_message)) { redisplayContent(); } } @@ -1756,6 +1781,9 @@ void OverlayWidget::showMediaOverview() { } void OverlayWidget::copyMedia() { + if (showCopyRestriction()) { + return; + } _dropdown->hideAnimated(Ui::DropdownMenu::HideOption::IgnoreShow); if (_document) { QGuiApplication::clipboard()->setImage(transformedShownContent()); @@ -2102,18 +2130,20 @@ void OverlayWidget::refreshCaption() { using namespace HistoryView; _caption = Ui::Text::String(st::msgMinWidth); - const auto duration = (_streamed && _document/* && !videoIsGifOrUserpic()*/) - ? _document->getDuration() + const auto duration = (_streamed && _document) + ? DurationForTimestampLinks(_document) : 0; const auto base = duration - ? DocumentTimestampLinkBase(_document, _message->fullId()) + ? TimestampLinkBase(_document, _message->fullId()) : QString(); const auto context = Core::MarkedTextContext{ .session = &_message->history()->session() }; _caption.setMarkedText( st::mediaviewCaptionStyle, - AddTimestampLinks(caption, duration, base), + (base.isEmpty() + ? caption + : AddTimestampLinks(caption, duration, base)), Ui::ItemTextOptions(_message), context); } @@ -2400,7 +2430,7 @@ void OverlayWidget::displayDocument( ).toImage()); } } else { - if (_documentMedia->canBePlayed() + if (_documentMedia->canBePlayed(_message) && initStreaming(continueStreaming)) { } else if (_document->isVideoFile()) { _documentMedia->automaticLoad(fileOrigin(), _message); @@ -2548,7 +2578,7 @@ void OverlayWidget::displayFinished() { } bool OverlayWidget::canInitStreaming() const { - return (_document && _documentMedia->canBePlayed()) + return (_document && _documentMedia->canBePlayed(_message)) || (_photo && _photo->videoCanBePlayed()); } @@ -4081,7 +4111,7 @@ void OverlayWidget::preloadData(int delta) { const auto [i, ok] = documents.emplace( (*document)->createMediaView()); (*i)->thumbnailWanted(fileOrigin(entity)); - if (!(*i)->canBePlayed()) { + if (!(*i)->canBePlayed(entity.item)) { (*i)->automaticLoad(fileOrigin(entity), entity.item); } } diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h index dbd63bdf9..d6d779ebc 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h @@ -386,6 +386,9 @@ private: void validatePhotoImage(Image *image, bool blurred); void validatePhotoCurrentImage(); + [[nodiscard]] bool hasCopyRestriction() const; + [[nodiscard]] bool showCopyRestriction(); + [[nodiscard]] QSize flipSizeByRotation(QSize size) const; void applyVideoSize(); diff --git a/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp b/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp index 57026b5f7..16a24379d 100644 --- a/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp +++ b/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp @@ -65,7 +65,7 @@ QByteArray DnsUserAgent() { static const auto kResult = QByteArray( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/94.0.4606.81 Safari/537.36"); + "Chrome/96.0.4664.45 Safari/537.36"); return kResult; } diff --git a/Telegram/SourceFiles/mtproto/details/mtproto_received_ids_manager.cpp b/Telegram/SourceFiles/mtproto/details/mtproto_received_ids_manager.cpp index 28e6ad518..20bade0c4 100644 --- a/Telegram/SourceFiles/mtproto/details/mtproto_received_ids_manager.cpp +++ b/Telegram/SourceFiles/mtproto/details/mtproto_received_ids_manager.cpp @@ -9,18 +9,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace MTP::details { -bool ReceivedIdsManager::registerMsgId(mtpMsgId msgId, bool needAck) { +ReceivedIdsManager::Result ReceivedIdsManager::registerMsgId( + mtpMsgId msgId, + bool needAck) { const auto i = _idsNeedAck.find(msgId); - if (i == _idsNeedAck.end()) { - if (_idsNeedAck.size() < kIdsBufferSize || msgId > min()) { - _idsNeedAck.emplace(msgId, needAck); - return true; - } - MTP_LOG(-1, ("No need to handle - %1 < min = %2").arg(msgId).arg(min())); - } else { + if (i != _idsNeedAck.end()) { MTP_LOG(-1, ("No need to handle - %1 already is in map").arg(msgId)); + return Result::Duplicate; + } else if (_idsNeedAck.size() < kIdsBufferSize || msgId > min()) { + _idsNeedAck.emplace(msgId, needAck); + return Result::Success; } - return false; + MTP_LOG(-1, ("Reset on too old - %1 < min = %2").arg(msgId).arg(min())); + return Result::TooOld; } mtpMsgId ReceivedIdsManager::min() const { diff --git a/Telegram/SourceFiles/mtproto/details/mtproto_received_ids_manager.h b/Telegram/SourceFiles/mtproto/details/mtproto_received_ids_manager.h index 2a8d6a57a..8f7dfbb9d 100644 --- a/Telegram/SourceFiles/mtproto/details/mtproto_received_ids_manager.h +++ b/Telegram/SourceFiles/mtproto/details/mtproto_received_ids_manager.h @@ -21,8 +21,13 @@ public: NeedsAck, NoAckNeeded, }; + enum class Result { + Success, + Duplicate, + TooOld, + }; - bool registerMsgId(mtpMsgId msgId, bool needAck); + [[nodiscard]] Result registerMsgId(mtpMsgId msgId, bool needAck); [[nodiscard]] mtpMsgId min() const; [[nodiscard]] mtpMsgId max() const; [[nodiscard]] State lookup(mtpMsgId msgId) const; diff --git a/Telegram/SourceFiles/mtproto/mtp_instance.cpp b/Telegram/SourceFiles/mtproto/mtp_instance.cpp index 3d8439d61..457a60a76 100644 --- a/Telegram/SourceFiles/mtproto/mtp_instance.cpp +++ b/Telegram/SourceFiles/mtproto/mtp_instance.cpp @@ -224,9 +224,12 @@ private: std::unique_ptr _otherSessionsThread; std::vector> _fileSessionThreads; - QString _deviceModel; + QString _deviceModelDefault; QString _systemVersion; + mutable QMutex _deviceModelMutex; + QString _customDeviceModel; + rpl::variable _mainDcId = Fields::kDefaultMainDc; bool _mainDcIdForced = false; base::flat_map> _dcenters; @@ -321,9 +324,19 @@ Instance::Private::Private( restart(); }, _lifetime); - _deviceModel = std::move(fields.deviceModel); + _deviceModelDefault = std::move(fields.deviceModel); _systemVersion = std::move(fields.systemVersion); + _customDeviceModel = Core::App().settings().customDeviceModel(); + Core::App().settings().customDeviceModelChanges( + ) | rpl::start_with_next([=](const QString &value) { + QMutexLocker lock(&_deviceModelMutex); + _customDeviceModel = value; + lock.unlock(); + + reInitConnection(mainDcId()); + }, _lifetime); + for (auto &key : fields.keys) { auto dcId = key->dcId(); auto shiftedDcId = dcId; @@ -874,7 +887,10 @@ bool Instance::Private::isTestMode() const { } QString Instance::Private::deviceModel() const { - return _deviceModel; + QMutexLocker lock(&_deviceModelMutex); + return _customDeviceModel.isEmpty() + ? _deviceModelDefault + : _customDeviceModel; } QString Instance::Private::systemVersion() const { diff --git a/Telegram/SourceFiles/mtproto/session_private.cpp b/Telegram/SourceFiles/mtproto/session_private.cpp index 9f464eea0..3fb9ba519 100644 --- a/Telegram/SourceFiles/mtproto/session_private.cpp +++ b/Telegram/SourceFiles/mtproto/session_private.cpp @@ -1367,13 +1367,18 @@ void SessionPrivate::handleReceived() { ).arg(getProtocolDcId() ).arg(_encryptionKey->keyId())); - if (_receivedMessageIds.registerMsgId(msgId, needAck)) { + const auto registered = _receivedMessageIds.registerMsgId( + msgId, + needAck); + if (registered == ReceivedIdsManager::Result::Success) { res = handleOneReceived(from, end, msgId, { .outerMsgId = msgId, .serverSalt = serverSalt, .serverTime = serverTime, .badTime = badTime, }); + } else if (registered == ReceivedIdsManager::Result::TooOld) { + res = HandleResult::ResetSession; } _receivedMessageIds.shrink(); @@ -1481,9 +1486,14 @@ SessionPrivate::HandleResult SessionPrivate::handleOneReceived( } auto res = HandleResult::Success; // if no need to handle, then succeed - if (_receivedMessageIds.registerMsgId(inMsgId.v, needAck)) { + const auto registered = _receivedMessageIds.registerMsgId( + inMsgId.v, + needAck); + if (registered == ReceivedIdsManager::Result::Success) { res = handleOneReceived(from, otherEnd, inMsgId.v, info); info.badTime = false; + } else if (registered == ReceivedIdsManager::Result::TooOld) { + res = HandleResult::ResetSession; } if (res != HandleResult::Success) { return res; diff --git a/Telegram/SourceFiles/overview/overview.style b/Telegram/SourceFiles/overview/overview.style index ac4423d7f..88c972017 100644 --- a/Telegram/SourceFiles/overview/overview.style +++ b/Telegram/SourceFiles/overview/overview.style @@ -73,23 +73,23 @@ overviewFileExtTop: 24px; overviewFileExtFg: windowFgActive; overviewFileExtFont: font(18px semibold); -overviewVoicePause: icon {{ "playlist_pause", historyFileInIconFg }}; -overviewVoicePauseSelected: icon {{ "playlist_pause", historyFileInIconFgSelected }}; -overviewVoicePlay: icon {{ "playlist_play", historyFileInIconFg }}; -overviewVoicePlaySelected: icon {{ "playlist_play", historyFileInIconFgSelected }}; -overviewVoiceCancel: icon {{ "playlist_cancel", historyFileInIconFg }}; -overviewVoiceCancelSelected: icon {{ "playlist_cancel", historyFileInIconFgSelected }}; -overviewVoiceDownload: icon {{ "playlist_download", historyFileInIconFg }}; -overviewVoiceDownloadSelected: icon {{ "playlist_download", historyFileInIconFgSelected }}; +overviewVoicePause: icon {{ "player/playlist_pause", historyFileInIconFg }}; +overviewVoicePauseSelected: icon {{ "player/playlist_pause", historyFileInIconFgSelected }}; +overviewVoicePlay: icon {{ "player/playlist_play", historyFileInIconFg }}; +overviewVoicePlaySelected: icon {{ "player/playlist_play", historyFileInIconFgSelected }}; +overviewVoiceCancel: icon {{ "player/playlist_cancel", historyFileInIconFg }}; +overviewVoiceCancelSelected: icon {{ "player/playlist_cancel", historyFileInIconFgSelected }}; +overviewVoiceDownload: icon {{ "player/playlist_download", historyFileInIconFg }}; +overviewVoiceDownloadSelected: icon {{ "player/playlist_download", historyFileInIconFgSelected }}; -overviewSongPause: icon {{ "playlist_pause", historyFileThumbIconFg }}; -overviewSongPauseSelected: icon {{ "playlist_pause", historyFileThumbIconFgSelected }}; -overviewSongPlay: icon {{ "playlist_play", historyFileThumbIconFg }}; -overviewSongPlaySelected: icon {{ "playlist_play", historyFileThumbIconFgSelected }}; -overviewSongCancel: icon {{ "playlist_cancel", historyFileThumbIconFg }}; -overviewSongCancelSelected: icon {{ "playlist_cancel", historyFileThumbIconFgSelected }}; -overviewSongDownload: icon {{ "playlist_download", historyFileThumbIconFg }}; -overviewSongDownloadSelected: icon {{ "playlist_download", historyFileThumbIconFgSelected }}; +overviewSongPause: icon {{ "player/playlist_pause", historyFileThumbIconFg }}; +overviewSongPauseSelected: icon {{ "player/playlist_pause", historyFileThumbIconFgSelected }}; +overviewSongPlay: icon {{ "player/playlist_play", historyFileThumbIconFg }}; +overviewSongPlaySelected: icon {{ "player/playlist_play", historyFileThumbIconFgSelected }}; +overviewSongCancel: icon {{ "player/playlist_cancel", historyFileThumbIconFg }}; +overviewSongCancelSelected: icon {{ "player/playlist_cancel", historyFileThumbIconFgSelected }}; +overviewSongDownload: icon {{ "player/playlist_download", historyFileThumbIconFg }}; +overviewSongDownloadSelected: icon {{ "player/playlist_download", historyFileThumbIconFgSelected }}; overviewSmallCancel: icon {{ "history_audio_cancel", historyFileInIconFg }}; overviewSmallCancelSelected: icon {{ "history_audio_cancel", historyFileInIconFgSelected }}; overviewSmallDownload: icon {{ "history_audio_download", historyFileInIconFg }}; diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp index 06e1ac499..b0a6834f6 100644 --- a/Telegram/SourceFiles/overview/overview_layout.cpp +++ b/Telegram/SourceFiles/overview/overview_layout.cpp @@ -484,7 +484,7 @@ void Video::paint(Painter &p, const QRect &clip, TextSelection selection, const if (!selected && !context->selecting && radialOpacity < 1.) { if (clip.intersects(QRect(0, _height - st::normalFont->height, _width, st::normalFont->height))) { - const auto download = !loaded && !_dataMedia->canBePlayed(); + const auto download = !loaded && !_dataMedia->canBePlayed(parent()); const auto &icon = download ? (selected ? st::overviewVideoDownloadSelected : st::overviewVideoDownload) : (selected ? st::overviewVideoPlaySelected : st::overviewVideoPlay); @@ -510,7 +510,7 @@ void Video::paint(Painter &p, const QRect &clip, TextSelection selection, const if (selected) { p.setBrush(st::msgDateImgBgSelected); } else { - auto over = ClickHandler::showAsActive((_data->loading() || _data->uploading()) ? _cancell : (loaded || _dataMedia->canBePlayed()) ? _openl : _savel); + auto over = ClickHandler::showAsActive((_data->loading() || _data->uploading()) ? _cancell : (loaded || _dataMedia->canBePlayed(parent())) ? _openl : _savel); p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, _a_iconOver.value(over ? 1. : 0.))); } @@ -576,7 +576,7 @@ TextState Video::getState( ensureDataMediaCreated(); const auto link = (_data->loading() || _data->uploading()) ? _cancell - : (dataLoaded() || _dataMedia->canBePlayed()) + : (dataLoaded() || _dataMedia->canBePlayed(parent())) ? _openl : _savel; return { parent(), link }; @@ -704,7 +704,7 @@ void Voice::paint(Painter &p, const QRect &clip, TextSelection selection, const } const auto &checkLink = (_data->loading() || _data->uploading()) ? _cancell - : (_dataMedia->canBePlayed() || loaded) + : (_dataMedia->canBePlayed(parent()) || loaded) ? _openl : _savel; if (selected) { @@ -732,7 +732,7 @@ void Voice::paint(Painter &p, const QRect &clip, TextSelection selection, const return &(selected ? _st.voiceCancelSelected : _st.voiceCancel); } else if (showPause) { return &(selected ? _st.voicePauseSelected : _st.voicePause); - } else if (_dataMedia->canBePlayed()) { + } else if (_dataMedia->canBePlayed(parent())) { return &(selected ? _st.voicePlaySelected : _st.voicePlay); } return &(selected @@ -802,7 +802,7 @@ TextState Voice::getState( if (inner.contains(point)) { const auto link = (_data->loading() || _data->uploading()) ? _cancell - : (_dataMedia->canBePlayed() || loaded) + : (_dataMedia->canBePlayed(parent()) || loaded) ? _openl : _savel; return { parent(), link }; @@ -969,9 +969,9 @@ Document::Document( bool Document::downloadInCorner() const { return _data->isAudioFile() - && _data->canBeStreamed() - && !_data->inappPlaybackFailed() - && parent()->isRegular(); + && parent()->allowsForward() + && _data->canBeStreamed(parent()) + && !_data->inappPlaybackFailed(); } void Document::initDimensions() { @@ -1033,7 +1033,7 @@ void Document::paint(Painter &p, const QRect &clip, TextSelection selection, con } else { const auto over = ClickHandler::showAsActive(isLoading ? _cancell - : (loaded || _dataMedia->canBePlayed()) + : (loaded || _dataMedia->canBePlayed(parent())) ? _openl : _savel); p.setBrush(anim::brush( @@ -1055,7 +1055,7 @@ void Document::paint(Painter &p, const QRect &clip, TextSelection selection, con return &(selected ? _st.voicePauseSelected : _st.voicePause); - } else if (loaded || _dataMedia->canBePlayed()) { + } else if (loaded || _dataMedia->canBePlayed(parent())) { return &(selected ? _st.voicePlaySelected : _st.voicePlay); @@ -1068,7 +1068,7 @@ void Document::paint(Painter &p, const QRect &clip, TextSelection selection, con return &(selected ? _st.songCancelSelected : _st.songCancel); } else if (showPause) { return &(selected ? _st.songPauseSelected : _st.songPause); - } else if (loaded || _dataMedia->canBePlayed()) { + } else if (loaded || _dataMedia->canBePlayed(parent())) { return &(selected ? _st.songPlaySelected : _st.songPlay); } return &(selected ? _st.songDownloadSelected : _st.songDownload); @@ -1280,7 +1280,7 @@ TextState Document::getState( const auto link = (!downloadInCorner() && (_data->loading() || _data->uploading())) ? _cancell - : (loaded || _dataMedia->canBePlayed()) + : (loaded || _dataMedia->canBePlayed(parent())) ? _openl : _savel; return { parent(), link }; diff --git a/Telegram/SourceFiles/passport/passport_form_controller.cpp b/Telegram/SourceFiles/passport/passport_form_controller.cpp index aa54ed1df..9df983fa9 100644 --- a/Telegram/SourceFiles/passport/passport_form_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_form_controller.cpp @@ -747,7 +747,7 @@ std::vector> FormController::submitGetErrors() { MTP_bytes(credentialsEncryptedData.bytes), MTP_bytes(credentialsEncryptedData.hash), MTP_bytes(credentialsEncryptedSecret)) - )).done([=](const MTPBool &result) { + )).done([=] { _submitRequestId = 0; _submitSuccess = true; @@ -1035,10 +1035,10 @@ void FormController::cancelPassword() { return; } _passwordRequestId = _api.request(MTPaccount_CancelPasswordEmail( - )).done([=](const MTPBool &result) { + )).done([=] { _passwordRequestId = 0; reloadPassword(); - }).fail([=](const MTP::Error &error) { + }).fail([=] { _passwordRequestId = 0; reloadPassword(); }).send(); @@ -1115,7 +1115,7 @@ void FormController::resetSecret( MTP_securePasswordKdfAlgoUnknown(), // secure_algo MTP_bytes(), // secure_secret MTP_long(0))) // secure_secret_id - )).done([=](const MTPBool &result) { + )).done([=] { _saveSecretRequestId = 0; generateSecret(password); }).fail([=](const MTP::Error &error) { @@ -1259,7 +1259,7 @@ rpl::producer FormController::preferredLanguage( }); consumer.put_next({ countryCode, findLang() }); consumer.put_done(); - }).fail([=](const MTP::Error &error) { + }).fail([=] { consumer.put_next({ countryCode, QString() }); consumer.put_done(); }).send(); @@ -1581,7 +1581,7 @@ void FormController::uploadEncryptedFile( auto prepared = std::make_shared( TaskId(), file.uploadData->fileId, - FileLoadTo(PeerId(0), Api::SendOptions(), MsgId(0), MsgId(0)), + FileLoadTo(PeerId(), Api::SendOptions(), MsgId(), MsgId()), TextWithTags(), std::shared_ptr(nullptr)); prepared->type = SendMediaType::Secure; @@ -1941,7 +1941,7 @@ void FormController::deleteValueEdit(not_null value) { const auto nonconst = findValue(value); nonconst->saveRequestId = _api.request(MTPaccount_DeleteSecureValue( MTP_vector(1, ConvertType(nonconst->type)) - )).done([=](const MTPBool &result) { + )).done([=] { resetValue(*nonconst); _valueSaveFinished.fire_copy(value); }).fail([=](const MTP::Error &error) { @@ -2161,54 +2161,50 @@ QString FormController::getPlainTextFromValue( void FormController::startPhoneVerification(not_null value) { value->verification.requestId = _api.request(MTPaccount_SendVerifyPhoneCode( MTP_string(getPhoneFromValue(value)), - MTP_codeSettings(MTP_flags(0)) + MTP_codeSettings(MTP_flags(0), MTP_vector()) )).done([=](const MTPauth_SentCode &result) { - Expects(result.type() == mtpc_auth_sentCode); - - value->verification.requestId = 0; - - const auto &data = result.c_auth_sentCode(); - value->verification.phoneCodeHash = qs(data.vphone_code_hash()); - switch (data.vtype().type()) { - case mtpc_auth_sentCodeTypeApp: - LOG(("API Error: sentCodeTypeApp not expected " - "in FormController::startPhoneVerification.")); - return; - case mtpc_auth_sentCodeTypeFlashCall: - LOG(("API Error: sentCodeTypeFlashCall not expected " - "in FormController::startPhoneVerification.")); - return; - case mtpc_auth_sentCodeTypeCall: { - const auto &type = data.vtype().c_auth_sentCodeTypeCall(); - value->verification.codeLength = (type.vlength().v > 0) - ? type.vlength().v - : -1; - value->verification.call = std::make_unique( - [=] { requestPhoneCall(value); }, - [=] { _verificationUpdate.fire_copy(value); }); - value->verification.call->setStatus( - { Ui::SentCodeCall::State::Called, 0 }); - if (data.vnext_type()) { - LOG(("API Error: next_type is not supported for calls.")); - } - } break; - case mtpc_auth_sentCodeTypeSms: { - const auto &type = data.vtype().c_auth_sentCodeTypeSms(); - value->verification.codeLength = (type.vlength().v > 0) - ? type.vlength().v - : -1; + result.match([&](const MTPDauth_sentCode &data) { const auto next = data.vnext_type(); - if (next && next->type() == mtpc_auth_codeTypeCall) { + const auto timeout = data.vtimeout(); + value->verification.requestId = 0; + value->verification.phoneCodeHash = qs(data.vphone_code_hash()); + data.vtype().match([&](const MTPDauth_sentCodeTypeApp &) { + LOG(("API Error: sentCodeTypeApp not expected " + "in FormController::startPhoneVerification.")); + }, [&](const MTPDauth_sentCodeTypeFlashCall &) { + LOG(("API Error: sentCodeTypeFlashCall not expected " + "in FormController::startPhoneVerification.")); + }, [&](const MTPDauth_sentCodeTypeMissedCall &data) { + LOG(("API Error: sentCodeTypeMissedCall not expected " + "in FormController::startPhoneVerification.")); + }, [&](const MTPDauth_sentCodeTypeCall &data) { + value->verification.codeLength = (data.vlength().v > 0) + ? data.vlength().v + : -1; value->verification.call = std::make_unique( [=] { requestPhoneCall(value); }, [=] { _verificationUpdate.fire_copy(value); }); - value->verification.call->setStatus({ - Ui::SentCodeCall::State::Waiting, - data.vtimeout().value_or(60) }); - } - } break; - } - _verificationNeeded.fire_copy(value); + value->verification.call->setStatus( + { Ui::SentCodeCall::State::Called, 0 }); + if (next) { + LOG(("API Error: next_type is not supported for calls.")); + } + }, [&](const MTPDauth_sentCodeTypeSms &data) { + value->verification.codeLength = (data.vlength().v > 0) + ? data.vlength().v + : -1; + if (next && next->type() == mtpc_auth_codeTypeCall) { + value->verification.call = std::make_unique( + [=] { requestPhoneCall(value); }, + [=] { _verificationUpdate.fire_copy(value); }); + value->verification.call->setStatus({ + Ui::SentCodeCall::State::Waiting, + timeout.value_or(60), + }); + } + }); + _verificationNeeded.fire_copy(value); + }); }).fail([=](const MTP::Error &error) { value->verification.requestId = 0; valueSaveShowError(value, error); @@ -2301,7 +2297,7 @@ void FormController::saveSecret( Core::PrepareSecureSecretAlgo(_password.newSecureAlgo), MTP_bytes(encryptedSecret), MTP_long(saved.secretId))) - )).done([=](const MTPBool &result) { + )).done([=] { session().data().rememberPassportCredentials( std::move(saved), kRememberCredentialsDelay); diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_contact.cpp b/Telegram/SourceFiles/passport/passport_panel_edit_contact.cpp index c0505fe49..b9e3246b0 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_contact.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_edit_contact.cpp @@ -281,7 +281,8 @@ void PanelEditContact::setupControls( std::move(fieldPlaceholder), Countries::ExtractPhoneCode( _controller->bot()->session().user()->phone()), - data); + data, + [](const QString &s) { return Countries::Groups(s); }); } else { _field = Ui::CreateChild( wrap.data(), diff --git a/Telegram/SourceFiles/payments/ui/payments_field.cpp b/Telegram/SourceFiles/payments/ui/payments_field.cpp index 522a7aa3b..6c90059d3 100644 --- a/Telegram/SourceFiles/payments/ui/payments_field.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_field.cpp @@ -407,7 +407,8 @@ struct SimpleFieldState { st::paymentsField, std::move(config.placeholder), Countries::ExtractPhoneCode(config.defaultPhone), - Parse(config)); + Parse(config), + [](const QString &s) { return Countries::Groups(s); }); case FieldType::Money: return CreateMoneyField( wrap, diff --git a/Telegram/SourceFiles/platform/linux/launcher_linux.cpp b/Telegram/SourceFiles/platform/linux/launcher_linux.cpp index 59095226e..4d000af9b 100644 --- a/Telegram/SourceFiles/platform/linux/launcher_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/launcher_linux.cpp @@ -13,11 +13,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include -#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION -#include -#include -#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION - #include #include #include @@ -56,15 +51,10 @@ Launcher::Launcher(int argc, char *argv[]) } int Launcher::exec() { -#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION - Glib::init(); - Gio::init(); -#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION - for (auto i = begin(_arguments), e = end(_arguments); i != e; ++i) { - if (*i == "-webviewhelper" && std::distance(i, e) > 2) { - Webview::WebKit2Gtk::SetServiceName(*(i + 2)); - return Webview::WebKit2Gtk::Exec(*(i + 1)); + if (*i == "-webviewhelper" && std::distance(i, e) > 1) { + Webview::WebKit2Gtk::SetSocketPath(*(i + 1)); + return Webview::WebKit2Gtk::Exec(); } } diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index 5f6267d4f..500b5b6b1 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -42,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include +#include #include #ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION @@ -307,7 +308,7 @@ QIcon TrayIconGen(int counter, bool muted) { if (QFileInfo::exists(cWorkingDir() + "tdata/icon.png")) { currentImageBack = QImage(cWorkingDir() + "tdata/icon.png"); } else if (cCustomAppIcon() != 0) { - currentImageBack = Core::App().logo(cCustomAppIcon()); + currentImageBack = Window::Logo(cCustomAppIcon()); } else if (!iconName.isEmpty()) { if (systemIcon.isNull()) { systemIcon = QIcon::fromTheme(iconName); @@ -1101,84 +1102,89 @@ void MainWindow::createGlobalMenu() { psUndo = edit->addAction( tr::lng_linux_menu_undo(tr::now), - this, - [=] { psLinuxUndo(); }, + [] { SendKeySequence(Qt::Key_Z, Qt::ControlModifier); }, QKeySequence::Undo); psRedo = edit->addAction( tr::lng_linux_menu_redo(tr::now), - this, - [=] { psLinuxRedo(); }, + [] { + SendKeySequence( + Qt::Key_Z, + Qt::ControlModifier | Qt::ShiftModifier); + }, QKeySequence::Redo); edit->addSeparator(); psCut = edit->addAction( tr::lng_mac_menu_cut(tr::now), - this, - [=] { psLinuxCut(); }, + [] { SendKeySequence(Qt::Key_X, Qt::ControlModifier); }, QKeySequence::Cut); + psCopy = edit->addAction( tr::lng_mac_menu_copy(tr::now), - this, - [=] { psLinuxCopy(); }, + [] { SendKeySequence(Qt::Key_C, Qt::ControlModifier); }, QKeySequence::Copy); psPaste = edit->addAction( tr::lng_mac_menu_paste(tr::now), - this, - [=] { psLinuxPaste(); }, + [] { SendKeySequence(Qt::Key_V, Qt::ControlModifier); }, QKeySequence::Paste); psDelete = edit->addAction( tr::lng_mac_menu_delete(tr::now), - this, - [=] { psLinuxDelete(); }, + [] { SendKeySequence(Qt::Key_Delete); }, QKeySequence(Qt::ControlModifier | Qt::Key_Backspace)); edit->addSeparator(); psBold = edit->addAction( tr::lng_menu_formatting_bold(tr::now), - this, - [=] { psLinuxBold(); }, + [] { SendKeySequence(Qt::Key_B, Qt::ControlModifier); }, QKeySequence::Bold); psItalic = edit->addAction( tr::lng_menu_formatting_italic(tr::now), - this, - [=] { psLinuxItalic(); }, + [] { SendKeySequence(Qt::Key_I, Qt::ControlModifier); }, QKeySequence::Italic); psUnderline = edit->addAction( tr::lng_menu_formatting_underline(tr::now), - this, - [=] { psLinuxUnderline(); }, + [] { SendKeySequence(Qt::Key_U, Qt::ControlModifier); }, QKeySequence::Underline); psStrikeOut = edit->addAction( tr::lng_menu_formatting_strike_out(tr::now), - this, - [=] { psLinuxStrikeOut(); }, + [] { + SendKeySequence( + Qt::Key_X, + Qt::ControlModifier | Qt::ShiftModifier); + }, Ui::kStrikeOutSequence); psMonospace = edit->addAction( tr::lng_menu_formatting_monospace(tr::now), - this, - [=] { psLinuxMonospace(); }, + [] { + SendKeySequence( + Qt::Key_M, + Qt::ControlModifier | Qt::ShiftModifier); + }, Ui::kMonospaceSequence); psClearFormat = edit->addAction( tr::lng_menu_formatting_clear(tr::now), - this, - [=] { psLinuxClearFormat(); }, + [] { + SendKeySequence( + Qt::Key_N, + Qt::ControlModifier | Qt::ShiftModifier); + }, Ui::kClearFormatSequence); edit->addSeparator(); psSelectAll = edit->addAction( tr::lng_mac_menu_select_all(tr::now), - this, [=] { psLinuxSelectAll(); }, + [] { SendKeySequence(Qt::Key_A, Qt::ControlModifier); }, QKeySequence::SelectAll); edit->addSeparator(); @@ -1267,58 +1273,6 @@ void MainWindow::createGlobalMenu() { updateGlobalMenu(); } -void MainWindow::psLinuxUndo() { - SendKeySequence(Qt::Key_Z, Qt::ControlModifier); -} - -void MainWindow::psLinuxRedo() { - SendKeySequence(Qt::Key_Z, Qt::ControlModifier | Qt::ShiftModifier); -} - -void MainWindow::psLinuxCut() { - SendKeySequence(Qt::Key_X, Qt::ControlModifier); -} - -void MainWindow::psLinuxCopy() { - SendKeySequence(Qt::Key_C, Qt::ControlModifier); -} - -void MainWindow::psLinuxPaste() { - SendKeySequence(Qt::Key_V, Qt::ControlModifier); -} - -void MainWindow::psLinuxDelete() { - SendKeySequence(Qt::Key_Delete); -} - -void MainWindow::psLinuxSelectAll() { - SendKeySequence(Qt::Key_A, Qt::ControlModifier); -} - -void MainWindow::psLinuxBold() { - SendKeySequence(Qt::Key_B, Qt::ControlModifier); -} - -void MainWindow::psLinuxItalic() { - SendKeySequence(Qt::Key_I, Qt::ControlModifier); -} - -void MainWindow::psLinuxUnderline() { - SendKeySequence(Qt::Key_U, Qt::ControlModifier); -} - -void MainWindow::psLinuxStrikeOut() { - SendKeySequence(Qt::Key_X, Qt::ControlModifier | Qt::ShiftModifier); -} - -void MainWindow::psLinuxMonospace() { - SendKeySequence(Qt::Key_M, Qt::ControlModifier | Qt::ShiftModifier); -} - -void MainWindow::psLinuxClearFormat() { - SendKeySequence(Qt::Key_N, Qt::ControlModifier | Qt::ShiftModifier); -} - void MainWindow::updateGlobalMenuHook() { if (!positionInited()) { return; @@ -1332,8 +1286,8 @@ void MainWindow::updateGlobalMenuHook() { auto canPaste = false; auto canDelete = false; auto canSelectAll = false; - const auto clipboardHasText = QGuiApplication::clipboard() - ->ownsClipboard(); + const auto mimeData = QGuiApplication::clipboard()->mimeData(); + const auto clipboardHasText = mimeData ? mimeData->hasText() : false; auto markdownEnabled = false; if (const auto edit = qobject_cast(focused)) { canCut = canCopy = canDelete = edit->hasSelectedText(); diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.h b/Telegram/SourceFiles/platform/linux/main_window_linux.h index 6b675f8cf..d6f9839e0 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.h +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.h @@ -79,21 +79,6 @@ private: void updateIconCounters(); void handleNativeSurfaceChanged(bool exist); - void psLinuxUndo(); - void psLinuxRedo(); - void psLinuxCut(); - void psLinuxCopy(); - void psLinuxPaste(); - void psLinuxDelete(); - void psLinuxSelectAll(); - - void psLinuxBold(); - void psLinuxItalic(); - void psLinuxUnderline(); - void psLinuxStrikeOut(); - void psLinuxMonospace(); - void psLinuxClearFormat(); - }; } // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index e9be23d76..26168baa7 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -55,9 +55,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include -#ifdef Q_OS_LINUX -#include -#endif // Q_OS_LINUX #include #include #include @@ -710,6 +707,9 @@ void start() { qputenv("PULSE_PROP_application.icon_name", GetIconName().toLatin1()); #ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION + Glib::init(); + Gio::init(); + Glib::set_prgname(cExeName().toStdString()); Glib::set_application_name(std::string(AppName)); @@ -743,8 +743,11 @@ void start() { char h[33] = { 0 }; hashMd5Hex(d.constData(), d.size(), h); - Webview::WebKit2Gtk::SetServiceName( - kWebviewService.utf16().arg(h).arg("%1").toStdString()); + Webview::WebKit2Gtk::SetSocketPath(qsl("%1/%2-%3-webview-%4").arg( + QDir::tempPath(), + h, + cGUIDStr(), + qsl("%1")).toStdString()); } void finish() { @@ -873,14 +876,6 @@ void psNewVersion() { void psSendToMenu(bool send, bool silent) { } -void sendfileFallback(FILE *out, FILE *in) { - static const int BufSize = 65536; - char buf[BufSize]; - while (size_t size = fread(buf, 1, BufSize, in)) { - fwrite(buf, 1, size, out); - } -} - bool linuxMoveFile(const char *from, const char *to) { FILE *ffrom = fopen(from, "rb"), *fto = fopen(to, "wb"); if (!ffrom) { @@ -891,6 +886,11 @@ bool linuxMoveFile(const char *from, const char *to) { fclose(ffrom); return false; } + static const int BufSize = 65536; + char buf[BufSize]; + while (size_t size = fread(buf, 1, BufSize, ffrom)) { + fwrite(buf, 1, size, fto); + } struct stat fst; // from http://stackoverflow.com/questions/5486774/keeping-fileowner-and-permissions-after-copying-file-in-c //let's say this wont fail since you already worked OK on that fp @@ -899,32 +899,6 @@ bool linuxMoveFile(const char *from, const char *to) { fclose(fto); return false; } - -#ifdef Q_OS_LINUX - ssize_t copied = sendfile( - fileno(fto), - fileno(ffrom), - nullptr, - fst.st_size); - if (copied == -1) { - DEBUG_LOG(("Update Error: " - "Copy by sendfile '%1' to '%2' failed, error: %3, fallback now." - ).arg(from - ).arg(to - ).arg(errno)); - sendfileFallback(fto, ffrom); - } else { - DEBUG_LOG(("Update Info: " - "Copy by sendfile '%1' to '%2' done, size: %3, result: %4." - ).arg(from - ).arg(to - ).arg(fst.st_size - ).arg(copied)); - } -#else // Q_OS_LINUX - sendfileFallback(fto, ffrom); -#endif // Q_OS_LINUX - //update to the same uid/gid if (fchown(fileno(fto), fst.st_uid, fst.st_gid) != 0) { fclose(ffrom); diff --git a/Telegram/SourceFiles/platform/mac/main_window_mac.mm b/Telegram/SourceFiles/platform/mac/main_window_mac.mm index 0a1fab3b6..c34cc5ae7 100644 --- a/Telegram/SourceFiles/platform/mac/main_window_mac.mm +++ b/Telegram/SourceFiles/platform/mac/main_window_mac.mm @@ -498,17 +498,15 @@ void MainWindow::createGlobalMenu() { QMenu *window = psMainMenu.addMenu(tr::lng_mac_menu_window(tr::now)); psContacts = window->addAction(tr::lng_mac_menu_contacts(tr::now)); connect(psContacts, &QAction::triggered, psContacts, crl::guard(this, [=] { - if (isHidden()) { - showFromTray(); - } - if (!sessionController()) { - return; - } + Expects(sessionController() != nullptr && !controller().locked()); + + ensureWindowShown(); sessionController()->show(PrepareContactsBox(sessionController())); })); { auto callback = [=] { - Expects(sessionController() != nullptr); + Expects(sessionController() != nullptr && !controller().locked()); + ensureWindowShown(); sessionController()->showAddContact(); }; @@ -520,7 +518,8 @@ void MainWindow::createGlobalMenu() { window->addSeparator(); { auto callback = [=] { - Expects(sessionController() != nullptr); + Expects(sessionController() != nullptr && !controller().locked()); + ensureWindowShown(); sessionController()->showNewGroup(); }; @@ -531,7 +530,8 @@ void MainWindow::createGlobalMenu() { } { auto callback = [=] { - Expects(sessionController() != nullptr); + Expects(sessionController() != nullptr && !controller().locked()); + ensureWindowShown(); sessionController()->showNewChannel(); }; diff --git a/Telegram/SourceFiles/platform/mac/touchbar/items/mac_scrubber_item.mm b/Telegram/SourceFiles/platform/mac/touchbar/items/mac_scrubber_item.mm index 36be91b02..ad42f4fbc 100644 --- a/Telegram/SourceFiles/platform/mac/touchbar/items/mac_scrubber_item.mm +++ b/Telegram/SourceFiles/platform/mac/touchbar/items/mac_scrubber_item.mm @@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/sandbox.h" #include "core/application.h" #include "core/core_settings.h" +#include "data/data_chat_participant_status.h" #include "data/data_document.h" #include "data/data_document_media.h" #include "data/data_file_origin.h" @@ -466,7 +467,8 @@ void AppendEmojiPacks( return true; } Api::SendExistingDocument( - Api::MessageToSend(ActiveChat(_controller).history()), + Api::MessageToSend( + Api::SendAction(ActiveChat(_controller).history())), document); return true; } else if (emoji) { diff --git a/Telegram/SourceFiles/profile/profile_block_group_members.cpp b/Telegram/SourceFiles/profile/profile_block_group_members.cpp index 79e737687..1322edabe 100644 --- a/Telegram/SourceFiles/profile/profile_block_group_members.cpp +++ b/Telegram/SourceFiles/profile/profile_block_group_members.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "profile/profile_block_group_members.h" #include "kotato/kotato_lang.h" +#include "api/api_chat_participants.h" #include "styles/style_profile.h" #include "ui/widgets/labels.h" #include "ui/boxes/confirm_box.h" @@ -90,10 +91,10 @@ void GroupMembersWidget::removePeer(PeerData *selectedPeer) { const auto callback = [=] { Ui::hideLayer(); if (const auto chat = peer->asChat()) { - chat->session().api().kickParticipant(chat, user); + chat->session().api().chatParticipants().kick(chat, user); Ui::showPeerHistory(chat, ShowAtTheEndMsgId); } else if (const auto channel = peer->asChannel()) { - channel->session().api().kickParticipant( + channel->session().api().chatParticipants().kick( channel, user, currentRestrictedRights); @@ -159,7 +160,7 @@ void GroupMembersWidget::preloadMore() { //if (auto megagroup = peer()->asMegagroup()) { // auto &megagroupInfo = megagroup->mgInfo; // if (!megagroupInfo->lastParticipants.isEmpty() && megagroupInfo->lastParticipants.size() < megagroup->membersCount()) { - // peer()->session().api().requestLastParticipants(megagroup, false); + // peer()->session().api().requestLast(megagroup, false); // } //} } @@ -201,7 +202,8 @@ void GroupMembersWidget::refreshMembers() { fillChatMembers(chat); } else if (const auto megagroup = peer()->asMegagroup()) { if (megagroup->lastParticipantsRequestNeeded()) { - megagroup->session().api().requestLastParticipants(megagroup); + megagroup->session().api().chatParticipants().requestLast( + megagroup); } fillMegagroupMembers(megagroup); } diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index 9d84f3ae5..e7b0d8e92 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -221,6 +221,117 @@ settingsAccentColorLine: 3px; settingsFilterIconSkip: 68px; settingsFilterIconLeft: 17px; +settingsDeviceName: InputField(defaultInputField) { + textBg: transparent; + textMargins: margins(1px, 3px, 1px, 4px); + + placeholderFg: placeholderFg; + placeholderFgActive: placeholderFgActive; + placeholderFgError: placeholderFgActive; + placeholderMargins: margins(1px, 0px, 1px, 0px); + placeholderScale: 0.; + placeholderFont: normalFont; + + heightMin: 29px; + + font: boxTextFont; +} + dictionariesSectionButton: SettingsButton(settingsUpdateToggle) { font: font(14px semibold); } + +sessionsScroll: boxScroll; +sessionsHeight: 350px; +sessionsTerminateAll: SettingsButton(defaultSettingsButton) { + textFg: attentionButtonFg; + textFgOver: attentionButtonFgOver; + font: font(boxFontSize semibold); + height: 20px; + padding: margins(77px, 12px, 22px, 10px); +} +sessionsTerminateAllIcon: icon {{ "settings/devices/terminate_all", attentionButtonFg }}; +sessionsTerminateAllIconLeft: 30px; +sessionLocationTop: 54px; +sessionCurrentSkip: 8px; +sessionSubtitleSkip: 14px; +sessionLocationFg: windowSubTextFg; +sessionInfoFg: windowSubTextFg; +sessionTerminateTop: 9px; +sessionTerminateSkip: 12px; +sessionTerminate: IconButton { + width: 20px; + height: 20px; + + icon: smallCloseIcon; + iconOver: smallCloseIconOver; + iconPosition: point(5px, 5px); + + rippleAreaPosition: point(0px, 0px); + rippleAreaSize: 20px; + ripple: RippleAnimation(defaultRippleAnimation) { + color: windowBgOver; + } +} +sessionIconWindows: icon{{ "settings/devices/device_desktop_win", historyPeerUserpicFg }}; +sessionIconMac: icon{{ "settings/devices/device_desktop_mac", historyPeerUserpicFg }}; +sessionIconUbuntu: icon{{ "settings/devices/device_linux_ubuntu", historyPeerUserpicFg }}; +sessionIconLinux: icon{{ "settings/devices/device_linux", historyPeerUserpicFg }}; +sessionIconiPhone: icon{{ "settings/devices/device_phone_ios", historyPeerUserpicFg }}; +sessionIconiPad: icon{{ "settings/devices/device_tablet_ios", historyPeerUserpicFg }}; +sessionIconAndroid: icon{{ "settings/devices/device_phone_android", historyPeerUserpicFg }}; +sessionIconWeb: icon{{ "settings/devices/device_web_other", historyPeerUserpicFg }}; +sessionIconChrome: icon{{ "settings/devices/device_web_chrome", historyPeerUserpicFg }}; +sessionIconEdge: icon{{ "settings/devices/device_web_edge", historyPeerUserpicFg }}; +sessionIconFirefox: icon{{ "settings/devices/device_web_firefox", historyPeerUserpicFg }}; +sessionIconSafari: icon{{ "settings/devices/device_web_safari", historyPeerUserpicFg }}; +sessionIconOther: icon{{ "settings/devices/device_other", historyPeerUserpicFg }}; +sessionBigUserpicSize: 70px; +sessionBigLottieSize: 52px; +sessionBigIconOther: icon{{ "settings/devices/device_other_large", historyPeerUserpicFg }}; +sessionBigIconWeb: icon{{ "settings/devices/device_web_other_large", historyPeerUserpicFg }}; +sessionBigCoverPadding: margins(0px, 18px, 0px, 7px); +sessionBigName: FlatLabel(defaultFlatLabel) { + textFg: boxTitleFg; + maxHeight: 29px; + style: TextStyle(defaultTextStyle) { + font: font(20px semibold); + linkFont: font(20px semibold); + linkFontOver: font(20px semibold underline); + } + align: align(top); +} +sessionDateLabel: FlatLabel(defaultFlatLabel) { + textFg: windowSubTextFg; + align: align(top); +} +sessionDateSkip: 19px; +sessionValuePadding: margins(0px, 5px, 0px, 2px); +sessionValueLabel: FlatLabel(defaultFlatLabel) { + textFg: windowSubTextFg; +} +sessionValueSkip: 8px; + +sessionListItem: PeerListItem(defaultPeerListItem) { + button: OutlineButton(defaultPeerListButton) { + font: normalFont; + padding: margins(11px, 5px, 11px, 5px); + } + height: 84px; + photoPosition: point(21px, 10px); + nameStyle: TextStyle(defaultTextStyle) { + font: msgNameFont; + } + namePosition: point(77px, 11px); + statusPosition: point(77px, 32px); + photoSize: 42px; + statusFg: boxTextFg; + statusFgOver: boxTextFg; +} +sessionList: PeerList(defaultPeerList) { + item: sessionListItem; + padding: margins(0px, 4px, 0px, 0px); +} +sessionListThreeDotsIcon: icon {{ "info/edit/dotsmini", dialogsMenuIconFg }}; +sessionListThreeDotsIconOver: icon {{ "info/edit/dotsmini", dialogsMenuIconFgOver }}; +sessionListThreeDotsSkip: 12px; diff --git a/Telegram/SourceFiles/settings/settings_calls.cpp b/Telegram/SourceFiles/settings/settings_calls.cpp index 56951855a..ce05046ee 100644 --- a/Telegram/SourceFiles/settings/settings_calls.cpp +++ b/Telegram/SourceFiles/settings/settings_calls.cpp @@ -28,6 +28,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/calls_call.h" #include "calls/calls_instance.h" #include "calls/calls_video_bubble.h" +#include "apiwrap.h" +#include "api/api_authorizations.h" #include "webrtc/webrtc_media_devices.h" #include "webrtc/webrtc_video_track.h" #include "webrtc/webrtc_audio_input_tester.h" @@ -269,19 +271,21 @@ void Calls::setupContent() { AddSkip(content); AddSubsectionTitle(content, tr::lng_settings_call_section_other()); + const auto api = &_controller->session().api(); AddButton( content, tr::lng_settings_call_accept_calls(), st::settingsButton - )->toggleOn(rpl::single( - !settings.disableCalls() - ))->toggledChanges( - ) | rpl::filter([&settings](bool value) { - return (settings.disableCalls() == value); - }) | rpl::start_with_next([=](bool value) { - Core::App().settings().setDisableCalls(!value); - Core::App().saveSettingsDelayed(); + )->toggleOn( + api->authorizations().callsDisabledHereValue( + ) | rpl::map(!rpl::mappers::_1) + )->toggledChanges( + ) | rpl::filter([=](bool value) { + return (value == api->authorizations().callsDisabledHere()); + }) | start_with_next([=](bool value) { + api->authorizations().toggleCallsDisabledHere(!value); }, content->lifetime()); + AddButton( content, tr::lng_settings_call_open_system_prefs(), diff --git a/Telegram/SourceFiles/settings/settings_common.cpp b/Telegram/SourceFiles/settings/settings_common.cpp index 6415a77b2..02388ca54 100644 --- a/Telegram/SourceFiles/settings/settings_common.cpp +++ b/Telegram/SourceFiles/settings/settings_common.cpp @@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/box_content_divider.h" #include "ui/widgets/buttons.h" #include "boxes/abstract_box.h" +#include "boxes/sessions_box.h" #include "ui/boxes/confirm_box.h" #include "window/themes/window_theme_editor_box.h" #include "window/window_session_controller.h" @@ -52,6 +53,8 @@ object_ptr
CreateSection( return object_ptr(parent, controller); case Type::PrivacySecurity: return object_ptr(parent, controller); + case Type::Sessions: + return object_ptr(parent, controller); case Type::Advanced: return object_ptr(parent, controller); case Type::Folders: diff --git a/Telegram/SourceFiles/settings/settings_common.h b/Telegram/SourceFiles/settings/settings_common.h index afafe43ce..f795ed4ef 100644 --- a/Telegram/SourceFiles/settings/settings_common.h +++ b/Telegram/SourceFiles/settings/settings_common.h @@ -36,6 +36,7 @@ enum class Type { Information, Notifications, PrivacySecurity, + Sessions, Advanced, Chat, Folders, diff --git a/Telegram/SourceFiles/settings/settings_main.cpp b/Telegram/SourceFiles/settings/settings_main.cpp index 475ac4e06..1bd8bb20e 100644 --- a/Telegram/SourceFiles/settings/settings_main.cpp +++ b/Telegram/SourceFiles/settings/settings_main.cpp @@ -342,7 +342,7 @@ void SetupHelp( Ui::showPeerHistory(user, ShowAtUnreadMsgId); } }); - }).fail([=](const MTP::Error &error) { + }).fail([=] { *requestId = 0; }).send(); }); diff --git a/Telegram/SourceFiles/settings/settings_notifications.cpp b/Telegram/SourceFiles/settings/settings_notifications.cpp index db9ed6f8d..a746dad36 100644 --- a/Telegram/SourceFiles/settings/settings_notifications.cpp +++ b/Telegram/SourceFiles/settings/settings_notifications.cpp @@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "main/main_account.h" #include "main/main_domain.h" +#include "api/api_authorizations.h" #include "apiwrap.h" #include "facades.h" #include "styles/style_settings.h" @@ -670,15 +671,19 @@ void SetupNotificationsContent( AddSubsectionTitle( container, tr::lng_settings_notifications_calls_title()); - addCheckbox( + const auto authorizations = &session->api().authorizations(); + const auto acceptCalls = addCheckbox( tr::lng_settings_call_accept_calls(tr::now), - !settings.disableCalls() - )->checkedChanges( - ) | rpl::filter([&settings](bool value) { - return (settings.disableCalls() == value); - }) | rpl::start_with_next([=](bool value) { - Core::App().settings().setDisableCalls(!value); - Core::App().saveSettingsDelayed(); + !authorizations->callsDisabledHere()); + session->api().authorizations().callsDisabledHereChanges( + ) | rpl::start_with_next([=](bool disabled) { + acceptCalls->setChecked( + !disabled, + Ui::Checkbox::NotifyAboutChange::DontNotify); + }, acceptCalls->lifetime()); + acceptCalls->checkedChanges( + ) | rpl::start_with_next([=](bool value) { + authorizations->toggleCallsDisabledHere(!value); }, container->lifetime()); AddSkip(container, st::settingsCheckboxesSkip); diff --git a/Telegram/SourceFiles/settings/settings_privacy_security.cpp b/Telegram/SourceFiles/settings/settings_privacy_security.cpp index 18119334f..a4e736626 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_security.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_security.cpp @@ -694,6 +694,7 @@ void SetupSelfDestruction( )->addClickHandler([=] { controller->show(Box( session, + SelfDestructionBox::Type::Account, session->api().selfDestruct().days())); }); @@ -780,7 +781,8 @@ void SetupBotsAndWebsites( void SetupSessionsList( not_null controller, not_null container, - rpl::producer<> updateTrigger) { + rpl::producer<> updateTrigger, + Fn showOther) { AddSkip(container); AddSubsectionTitle(container, tr::lng_settings_sessions_title()); @@ -801,7 +803,7 @@ void SetupSessionsList( std::move(count), st::settingsButton )->addClickHandler([=] { - controller->show(Box(&controller->session())); + showOther(Type::Sessions); }); AddSkip(container, st::settingsPrivacySecurityPadding); AddDividerText(container, tr::lng_settings_sessions_about()); @@ -929,6 +931,10 @@ PrivacySecurity::PrivacySecurity( setupContent(controller); } +rpl::producer PrivacySecurity::sectionShowOther() { + return _showOther.events(); +} + void PrivacySecurity::setupContent( not_null controller) { const auto content = Ui::CreateChild(this); @@ -940,8 +946,9 @@ void PrivacySecurity::setupContent( }; SetupPrivacy(controller, content, trigger()); - SetupArchiveAndMute(controller, content); - SetupSessionsList(controller, content, trigger()); + SetupSessionsList(controller, content, trigger(), [=](Type type) { + _showOther.fire_copy(type); + }); SetupLocalPasscode(controller, content); SetupCloudPassword(controller, content); #if !defined OS_MAC_STORE && !defined OS_WIN_STORE @@ -949,6 +956,7 @@ void PrivacySecurity::setupContent( #else // !OS_MAC_STORE && !OS_WIN_STORE AddDivider(content); #endif // !OS_MAC_STORE && !OS_WIN_STORE + SetupArchiveAndMute(controller, content); SetupSelfDestruction(controller, content, trigger()); AddDivider(content); SetupBotsAndWebsites(controller, content); diff --git a/Telegram/SourceFiles/settings/settings_privacy_security.h b/Telegram/SourceFiles/settings/settings_privacy_security.h index 52070759e..698148b4a 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_security.h +++ b/Telegram/SourceFiles/settings/settings_privacy_security.h @@ -39,9 +39,13 @@ public: QWidget *parent, not_null controller); + rpl::producer sectionShowOther() override; + private: void setupContent(not_null controller); + rpl::event_stream _showOther; + }; } // namespace Settings diff --git a/Telegram/SourceFiles/storage/file_upload.cpp b/Telegram/SourceFiles/storage/file_upload.cpp index 06b75c566..b89ad7d0c 100644 --- a/Telegram/SourceFiles/storage/file_upload.cpp +++ b/Telegram/SourceFiles/storage/file_upload.cpp @@ -158,40 +158,34 @@ Uploader::Uploader(not_null api) , _stopSessionsTimer([=] { stopSessions(); }) { const auto session = &_api->session(); photoReady( - ) | rpl::start_with_next([=](const UploadedPhoto &data) { + ) | rpl::start_with_next([=](UploadedMedia &&data) { if (data.edit) { const auto item = session->data().message(data.fullId); Api::EditMessageWithUploadedPhoto( item, - data.file, - data.options, - data.attachedStickers); + std::move(data.info), + data.options); } else { _api->sendUploadedPhoto( data.fullId, - data.file, - data.options, - data.attachedStickers); + std::move(data.info), + data.options); } }, _lifetime); documentReady( - ) | rpl::start_with_next([=](const UploadedDocument &data) { + ) | rpl::start_with_next([=](UploadedMedia &&data) { if (data.edit) { const auto item = session->data().message(data.fullId); Api::EditMessageWithUploadedDocument( item, - data.file, - data.thumb, - data.options, - data.attachedStickers); + std::move(data.info), + data.options); } else { _api->sendUploadedDocument( data.fullId, - data.file, - data.thumb, - data.options, - data.attachedStickers); + std::move(data.info), + data.options); } }, _lifetime); @@ -472,11 +466,14 @@ void Uploader::sendNext() { MTP_string(photoFilename), MTP_bytes(md5)); _photoReady.fire({ - uploadingId, - options, - file, - edit, - attachedStickers }); + .fullId = uploadingId, + .info = { + .file = file, + .attachedStickers = attachedStickers, + }, + .options = options, + .edit = edit, + }); } else if (uploadingData.type() == SendMediaType::File || uploadingData.type() == SendMediaType::ThemeFile || uploadingData.type() == SendMediaType::Audio) { @@ -510,12 +507,15 @@ void Uploader::sendNext() { MTP_bytes(thumbMd5)); }(); _documentReady.fire({ - uploadingId, - options, - file, - thumb, - edit, - attachedStickers }); + .fullId = uploadingId, + .info = { + .file = file, + .thumb = thumb, + .attachedStickers = attachedStickers, + }, + .options = options, + .edit = edit, + }); } else if (uploadingData.type() == SendMediaType::Secure) { _secureReady.fire({ uploadingId, diff --git a/Telegram/SourceFiles/storage/file_upload.h b/Telegram/SourceFiles/storage/file_upload.h index b8f0f8f43..32ef6e662 100644 --- a/Telegram/SourceFiles/storage/file_upload.h +++ b/Telegram/SourceFiles/storage/file_upload.h @@ -28,21 +28,11 @@ namespace Storage { // MTP big files methods used for files greater than 10mb. constexpr auto kUseBigFilesFrom = 10 * 1024 * 1024; -struct UploadedPhoto { +struct UploadedMedia { FullMsgId fullId; + Api::RemoteFileInfo info; Api::SendOptions options; - MTPInputFile file; bool edit = false; - std::vector attachedStickers; -}; - -struct UploadedDocument { - FullMsgId fullId; - Api::SendOptions options; - MTPInputFile file; - std::optional thumb; - bool edit = false; - std::vector attachedStickers; }; struct UploadSecureProgress { @@ -75,10 +65,10 @@ public: void clear(); - rpl::producer photoReady() const { + rpl::producer photoReady() const { return _photoReady.events(); } - rpl::producer documentReady() const { + rpl::producer documentReady() const { return _documentReady.events(); } rpl::producer secureReady() const { @@ -138,8 +128,8 @@ private: std::map uploaded; base::Timer _nextTimer, _stopSessionsTimer; - rpl::event_stream _photoReady; - rpl::event_stream _documentReady; + rpl::event_stream _photoReady; + rpl::event_stream _documentReady; rpl::event_stream _secureReady; rpl::event_stream _photoProgress; rpl::event_stream _documentProgress; diff --git a/Telegram/SourceFiles/storage/localimageloader.cpp b/Telegram/SourceFiles/storage/localimageloader.cpp index 61b46fc65..923cef274 100644 --- a/Telegram/SourceFiles/storage/localimageloader.cpp +++ b/Telegram/SourceFiles/storage/localimageloader.cpp @@ -432,6 +432,10 @@ void SendingAlbum::removeItem(not_null item) { } } +SendingAlbum::Item::Item(TaskId taskId) +: taskId(taskId) { +} + FileLoadResult::FileLoadResult( TaskId taskId, uint64 id, diff --git a/Telegram/SourceFiles/storage/localimageloader.h b/Telegram/SourceFiles/storage/localimageloader.h index d0ebbc8ae..6bb5ba88e 100644 --- a/Telegram/SourceFiles/storage/localimageloader.h +++ b/Telegram/SourceFiles/storage/localimageloader.h @@ -168,8 +168,7 @@ private: struct SendingAlbum { struct Item { - explicit Item(TaskId taskId) : taskId(taskId) { - } + explicit Item(TaskId taskId); TaskId taskId; uint64 randomId = 0; @@ -194,7 +193,7 @@ struct SendingAlbum { struct FileLoadTo { FileLoadTo( - const PeerId &peer, + PeerId peer, Api::SendOptions options, MsgId replyTo, MsgId replaceMediaOf) diff --git a/Telegram/SourceFiles/support/support_helper.cpp b/Telegram/SourceFiles/support/support_helper.cpp index 21b9455ab..ca112783e 100644 --- a/Telegram/SourceFiles/support/support_helper.cpp +++ b/Telegram/SourceFiles/support/support_helper.cpp @@ -263,7 +263,7 @@ Helper::Helper(not_null session) result.match([&](const MTPDhelp_supportName &data) { setSupportName(qs(data.vname())); }); - }).fail([=](const MTP::Error &error) { + }).fail([=] { setSupportName( qsl("[rand^") + QString::number(Core::Sandbox::Instance().installationTag()) @@ -529,7 +529,7 @@ void Helper::saveInfo( )).done([=](const MTPhelp_UserInfo &result) { applyInfo(user, result); done(true); - }).fail([=](const MTP::Error &error) { + }).fail([=] { done(false); }).send(); } diff --git a/Telegram/SourceFiles/ui/boxes/calendar_box.cpp b/Telegram/SourceFiles/ui/boxes/calendar_box.cpp index 62d132007..4a588231a 100644 --- a/Telegram/SourceFiles/ui/boxes/calendar_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/calendar_box.cpp @@ -8,16 +8,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/boxes/calendar_box.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/scroll_area.h" #include "ui/effects/ripple_animation.h" +#include "ui/chat/chat_style.h" #include "ui/ui_utility.h" +#include "ui/cached_round_corners.h" #include "lang/lang_keys.h" #include "styles/style_boxes.h" -#include "styles/style_dialogs.h" namespace Ui { namespace { constexpr auto kDaysInWeek = 7; +constexpr auto kMaxDaysForScroll = kDaysInWeek * 1000; +constexpr auto kTooltipDelay = crl::time(1000); +constexpr auto kJumpDelay = 2 * crl::time(1000); } // namespace @@ -25,70 +30,87 @@ class CalendarBox::Context { public: Context(QDate month, QDate highlighted); - void start() { - _month.setForced(_month.value(), true); - } - - void setBeginningButton(bool enabled); - bool hasBeginningButton() const { - return _beginningButton; + void setAllowsSelection(bool allowsSelection); + [[nodiscard]] bool allowsSelection() const { + return _allowsSelection; } void setMinDate(QDate date); void setMaxDate(QDate date); - int minDayIndex() const { + [[nodiscard]] int minDayIndex() const { return _minDayIndex; } - int maxDayIndex() const { + [[nodiscard]] int maxDayIndex() const { return _maxDayIndex; } void skipMonth(int skip); void showMonth(QDate month); void skipDays(int skip); + [[nodiscard]] bool showsMonthOf(QDate date) const; - int highlightedIndex() const { + [[nodiscard]] int highlightedIndex() const { return _highlightedIndex; } - int rowsCount() const { + [[nodiscard]] int rowsCount() const { return _rowsCount; } - int daysShift() const { + [[nodiscard]] int rowsCountMax() const { + return 6; + } + [[nodiscard]] int daysShift() const { return _daysShift; } - int daysCount() const { + [[nodiscard]] int daysCount() const { return _daysCount; } - bool isEnabled(int index) const { + [[nodiscard]] bool isEnabled(int index) const { return (index >= _minDayIndex) && (index <= _maxDayIndex); } - bool atBeginning() const { - return _highlighted == _min; - } QDate highlighted() const { return _highlighted; } - const base::Variable &month() { - return _month; + [[nodiscard]] QDate month() const { + return _month.current(); + } + [[nodiscard]] rpl::producer monthValue() const { + return _month.value(); } - QDate dateFromIndex(int index) const; - QString labelFromIndex(int index) const; + [[nodiscard]] QDate dateFromIndex(int index) const; + [[nodiscard]] QString labelFromIndex(int index) const; + + void toggleSelectionMode(bool enabled); + [[nodiscard]] bool selectionMode() const; + [[nodiscard]] rpl::producer<> selectionUpdates() const; + [[nodiscard]] std::optional selectedMin() const; + [[nodiscard]] std::optional selectedMax() const; + + void startSelection(int index); + void updateSelection(int index); private: + struct Selection { + QDate min; + QDate max; + int minIndex = 0; + int maxIndex = 0; + }; void applyMonth(const QDate &month, bool forced = false); - static int daysShiftForMonth(QDate month); - static int rowsCountForMonth(QDate month); + static int DaysShiftForMonth(QDate month, QDate min); + static int RowsCountForMonth(QDate month, QDate min, QDate max); void setHighlightedDate(QDate highlighted); - bool _beginningButton = false; + bool _allowsSelection = false; - base::Variable _month; + rpl::variable _month; QDate _min, _max; QDate _highlighted; + Fn _dayOfWeek; + Fn _monthOfYear; int _highlightedIndex = 0; int _minDayIndex = 0; @@ -97,24 +119,31 @@ private: int _daysShift = 0; int _rowsCount = 0; + Selection _selection; + QDate _selectionStart; + int _selectionStartIndex = 0; + rpl::event_stream<> _selectionUpdates; + bool _selectionMode = false; + }; -CalendarBox::Context::Context(QDate month, QDate highlighted) : _highlighted(highlighted) { +CalendarBox::Context::Context(QDate month, QDate highlighted) +: _highlighted(highlighted) { showMonth(month); } -void CalendarBox::Context::setBeginningButton(bool enabled) { - _beginningButton = enabled; +void CalendarBox::Context::setAllowsSelection(bool allows) { + _allowsSelection = allows; } void CalendarBox::Context::setMinDate(QDate date) { _min = date; - applyMonth(_month.value(), true); + applyMonth(_month.current(), true); } void CalendarBox::Context::setMaxDate(QDate date) { _max = date; - applyMonth(_month.value(), true); + applyMonth(_month.current(), true); } void CalendarBox::Context::showMonth(QDate month) { @@ -124,23 +153,42 @@ void CalendarBox::Context::showMonth(QDate month) { applyMonth(month); } +bool CalendarBox::Context::showsMonthOf(QDate date) const { + const auto shown = _month.current(); + return (shown.year() == date.year()) && (shown.month() == date.month()); +} + void CalendarBox::Context::applyMonth(const QDate &month, bool forced) { + const auto was = _month.current(); _daysCount = month.daysInMonth(); - _daysShift = daysShiftForMonth(month); - _rowsCount = rowsCountForMonth(month); + _daysShift = DaysShiftForMonth(month, _min); + _rowsCount = RowsCountForMonth(month, _min, _max); _highlightedIndex = month.daysTo(_highlighted); _minDayIndex = _min.isNull() ? INT_MIN : month.daysTo(_min); _maxDayIndex = _max.isNull() ? INT_MAX : month.daysTo(_max); + const auto shift = was.isNull() ? 0 : month.daysTo(was); + auto updated = false; + const auto update = [&](const QDate &date, int &index) { + if (shift && !date.isNull()) { + index += shift; + } + }; + update(_selection.min, _selection.minIndex); + update(_selection.max, _selection.maxIndex); + update(_selectionStart, _selectionStartIndex); if (forced) { - _month.setForced(month, true); + _month.force_assign(month); } else { - _month.set(month, true); + _month = month; + } + if (updated) { + _selectionUpdates.fire({}); } } void CalendarBox::Context::skipMonth(int skip) { - auto year = _month.value().year(); - auto month = _month.value().month(); + auto year = _month.current().year(); + auto month = _month.current().month(); month += skip; while (month < 1) { --year; @@ -156,18 +204,18 @@ void CalendarBox::Context::skipMonth(int skip) { void CalendarBox::Context::skipDays(int skip) { auto date = _highlighted; - if (_month.value().month() == _highlighted.month() - && _month.value().year() == _highlighted.year()) { + if (_month.current().month() == _highlighted.month() + && _month.current().year() == _highlighted.year()) { date = date.addDays(skip); } else if (skip < 0) { date = QDate( - _month.value().year(), - _month.value().month(), - _month.value().daysInMonth()); + _month.current().year(), + _month.current().month(), + _month.current().daysInMonth()); } else { date = QDate( - _month.value().year(), - _month.value().month(), + _month.current().year(), + _month.current().month(), 1); } @@ -183,34 +231,64 @@ void CalendarBox::Context::skipDays(int skip) { } } -int CalendarBox::Context::daysShiftForMonth(QDate month) { - Assert(!month.isNull()); +int CalendarBox::Context::DaysShiftForMonth(QDate month, QDate min) { + Expects(!month.isNull()); + constexpr auto kMaxRows = 6; - auto inMonthIndex = month.day() - 1; - auto inWeekIndex = month.dayOfWeek() - 1; - return ((kMaxRows * kDaysInWeek) + inWeekIndex - inMonthIndex) % kDaysInWeek; + const auto inMonthIndex = month.day() - 1; + const auto inWeekIndex = month.dayOfWeek() - 1; + const auto from = ((kMaxRows * kDaysInWeek) + inWeekIndex - inMonthIndex) + % kDaysInWeek; + if (min.isNull()) { + min = month.addYears(-1); + } else if (min >= month) { + return from; + } + if (min.day() != 1) { + min = QDate(min.year(), min.month(), 1); + } + const auto add = min.daysTo(month) - inWeekIndex + (min.dayOfWeek() - 1); + return from + add; } -int CalendarBox::Context::rowsCountForMonth(QDate month) { - Assert(!month.isNull()); - auto daysShift = daysShiftForMonth(month); - auto daysCount = month.daysInMonth(); - auto cellsCount = daysShift + daysCount; +int CalendarBox::Context::RowsCountForMonth( + QDate month, + QDate min, + QDate max) { + Expects(!month.isNull()); + + const auto daysShift = DaysShiftForMonth(month, min); + const auto daysCount = month.daysInMonth(); + const auto cellsCount = daysShift + daysCount; auto result = (cellsCount / kDaysInWeek); - if (cellsCount % kDaysInWeek) ++result; - return result; + if (cellsCount % kDaysInWeek) { + ++result; + } + if (max.isNull()) { + max = month.addYears(1); + } + if (max < month.addMonths(1)) { + return result; + } + if (max.day() != 1) { + max = QDate(max.year(), max.month(), 1); + } + max = max.addMonths(1); + max = max.addDays(1 - max.dayOfWeek()); + const auto cellsFull = daysShift + (month.day() - 1) + month.daysTo(max); + return cellsFull / kDaysInWeek; } void CalendarBox::Context::setHighlightedDate(QDate highlighted) { _highlighted = highlighted; - _highlightedIndex = _month.value().daysTo(_highlighted); - applyMonth(_month.value(), true); + _highlightedIndex = _month.current().daysTo(_highlighted); + applyMonth(_month.current(), true); } QDate CalendarBox::Context::dateFromIndex(int index) const { constexpr auto kMonthsCount = 12; - auto month = _month.value().month(); - auto year = _month.value().year(); + auto month = _month.current().month(); + auto year = _month.current().year(); while (index < 0) { if (!--month) { month += kMonthsCount; @@ -238,14 +316,78 @@ QString CalendarBox::Context::labelFromIndex(int index) const { return QString::number(day()); } -class CalendarBox::Inner : public TWidget, private base::Subscriber { +void CalendarBox::Context::toggleSelectionMode(bool enabled) { + if (_selectionMode == enabled) { + return; + } + _selectionMode = enabled; + _selectionStart = {}; + _selection = {}; + _selectionUpdates.fire({}); +} + +bool CalendarBox::Context::selectionMode() const { + return _selectionMode; +} + +rpl::producer<> CalendarBox::Context::selectionUpdates() const { + return _selectionUpdates.events(); +} + +std::optional CalendarBox::Context::selectedMin() const { + return _selection.min.isNull() + ? std::optional() + : _selection.minIndex; +} + +std::optional CalendarBox::Context::selectedMax() const { + return _selection.max.isNull() + ? std::optional() + : _selection.maxIndex; +} + +void CalendarBox::Context::startSelection(int index) { + Expects(_selectionMode); + + if (!_selectionStart.isNull() && _selectionStartIndex == index) { + return; + } + _selectionStartIndex = index; + _selectionStart = dateFromIndex(index); + updateSelection(index); +} + +void CalendarBox::Context::updateSelection(int index) { + Expects(_selectionMode); + Expects(!_selectionStart.isNull()); + + index = std::clamp(index, minDayIndex(), maxDayIndex()); + const auto start = _selectionStartIndex; + const auto min = std::min(index, start); + const auto max = std::max(index, start); + if (!_selection.min.isNull() + && _selection.minIndex == min + && !_selection.max.isNull() + && _selection.maxIndex == max) { + return; + } + _selection = Selection{ + .min = dateFromIndex(min), + .max = dateFromIndex(max), + .minIndex = min, + .maxIndex = max, + }; + _selectionUpdates.fire({}); +} + +class CalendarBox::Inner final : public RpWidget { public: Inner( QWidget *parent, not_null context, const style::CalendarSizes &st); - int countHeight(); + [[nodiscard]] int countMaxHeight() const; void setDateChosenCallback(Fn callback); void selectBeginning(); void selectEnd(); @@ -267,33 +409,117 @@ private: int rowsLeft() const; int rowsTop() const; void resizeToCurrent(); - void paintDayNames(Painter &p, QRect clip); void paintRows(Painter &p, QRect clip); const style::CalendarSizes &_st; - not_null _context; + const not_null _context; + bool _twoPressSelectionStarted = false; std::map> _ripples; Fn _dateChosenCallback; - static constexpr auto kEmptySelection = -kDaysInWeek; + static constexpr auto kEmptySelection = INT_MIN / 2; int _selected = kEmptySelection; int _pressed = kEmptySelection; + bool _pointerCursor = false; + bool _cursorSetWithoutMouseMove = false; + + QPoint _lastGlobalPosition; + bool _mouseMoved = false; }; +class CalendarBox::FloatingDate final { +public: + FloatingDate(QWidget *parent, not_null context); + + [[nodiscard]] rpl::producer widthValue() const; + void move(int x, int y); + + [[nodiscard]] rpl::lifetime &lifetime(); + +private: + void paint(); + + const not_null _context; + RpWidget _widget; + CornersPixmaps _corners; + QString _text; + +}; + +CalendarBox::FloatingDate::FloatingDate( + QWidget *parent, + not_null context) +: _context(context) +, _widget(parent) +, _corners( + PrepareCornerPixmaps( + HistoryServiceMsgRadius(), + st::roundedBg, + nullptr)) { + _context->monthValue( + ) | rpl::start_with_next([=](QDate month) { + _text = langMonthOfYearFull(month.month(), month.year()); + const auto width = st::msgServiceFont->width(_text); + const auto rect = QRect(0, 0, width, st::msgServiceFont->height); + _widget.resize(rect.marginsAdded(st::msgServicePadding).size()); + _widget.update(); + }, _widget.lifetime()); + + _widget.paintRequest( + ) | rpl::start_with_next([=] { + paint(); + }, _widget.lifetime()); + + _widget.setAttribute(Qt::WA_TransparentForMouseEvents); + _widget.show(); +} + +rpl::producer CalendarBox::FloatingDate::widthValue() const { + return _widget.widthValue(); +} + +void CalendarBox::FloatingDate::move(int x, int y) { + _widget.move(x, y); +} + +rpl::lifetime &CalendarBox::FloatingDate::lifetime() { + return _widget.lifetime(); +} + +void CalendarBox::FloatingDate::paint() { + auto p = Painter(&_widget); + + FillRoundRect(p, _widget.rect(), st::roundedBg, _corners); + + p.setFont(st::msgServiceFont); + p.setPen(st::roundedFg); + p.drawText( + st::msgServicePadding.left(), + st::msgServicePadding.top() + st::msgServiceFont->ascent, + _text); +} + CalendarBox::Inner::Inner( QWidget *parent, not_null context, const style::CalendarSizes &st) -: TWidget(parent) +: RpWidget(parent) , _st(st) , _context(context) { setMouseTracking(true); - subscribe(context->month(), [this](QDate month) { + + context->monthValue( + ) | rpl::start_with_next([=](QDate month) { monthChanged(month); - }); + }, lifetime()); + + context->selectionUpdates( + ) | rpl::start_with_next([=] { + update(); + }, lifetime()); } void CalendarBox::Inner::monthChanged(QDate month) { @@ -305,7 +531,8 @@ void CalendarBox::Inner::monthChanged(QDate month) { } void CalendarBox::Inner::resizeToCurrent() { - resize(_st.width, countHeight()); + const auto height = _context->rowsCount() * _st.cellSize.height(); + resize(_st.width, _st.padding.top() + height + _st.padding.bottom()); } void CalendarBox::Inner::paintEvent(QPaintEvent *e) { @@ -313,88 +540,108 @@ void CalendarBox::Inner::paintEvent(QPaintEvent *e) { auto clip = e->rect(); - paintDayNames(p, clip); paintRows(p, clip); } -void CalendarBox::Inner::paintDayNames(Painter &p, QRect clip) { - p.setFont(st::calendarDaysFont); - p.setPen(st::calendarDaysFg); - auto y = _st.padding.top(); - auto x = rowsLeft(); - if (!myrtlrect(x, y, _st.cellSize.width() * kDaysInWeek, _st.daysHeight).intersects(clip)) { - return; - } - for (auto i = 0; i != kDaysInWeek; ++i, x += _st.cellSize.width()) { - auto rect = myrtlrect(x, y, _st.cellSize.width(), _st.daysHeight); - if (!rect.intersects(clip)) { - continue; - } - p.drawText(rect, langDayOfWeek(i + 1), style::al_top); - } -} - int CalendarBox::Inner::rowsLeft() const { return _st.padding.left(); } int CalendarBox::Inner::rowsTop() const { - return _st.padding.top() + _st.daysHeight; + return _st.padding.top(); } void CalendarBox::Inner::paintRows(Painter &p, QRect clip) { p.setFont(st::calendarDaysFont); auto y = rowsTop(); auto index = -_context->daysShift(); - auto highlightedIndex = _context->highlightedIndex(); - for (auto row = 0, rowsCount = _context->rowsCount(), daysCount = _context->daysCount() - ; row != rowsCount - ; ++row, y += _st.cellSize.height()) { + const auto selectionMode = _context->selectionMode(); + const auto impossible = index - 45; + const auto selectedMin = _context->selectedMin().value_or(impossible); + const auto selectedMax = _context->selectedMax().value_or(impossible); + const auto highlightedIndex = selectionMode + ? impossible + : _context->highlightedIndex(); + const auto daysCount = _context->daysCount(); + const auto rowsCount = _context->rowsCount(); + const auto rowHeight = _st.cellSize.height(); + const auto fromRow = std::max(clip.y() - y, 0) / rowHeight; + const auto tillRow = std::min( + (clip.y() + clip.height() + rowHeight - 1) / rowHeight, + rowsCount); + y += fromRow * rowHeight; + index += fromRow * kDaysInWeek; + const auto innerSkipLeft = (_st.cellSize.width() - _st.cellInner) / 2; + const auto innerSkipTop = (_st.cellSize.height() - _st.cellInner) / 2; + for (auto row = fromRow; row != tillRow; ++row, y += rowHeight) { auto x = rowsLeft(); - if (!myrtlrect(x, y, _st.cellSize.width() * kDaysInWeek, _st.cellSize.height()).intersects(clip)) { - index += kDaysInWeek; - continue; + const auto fromIndex = index; + const auto tillIndex = (index + kDaysInWeek); + const auto selectedFrom = std::max(fromIndex, selectedMin); + const auto selectedTill = std::min(tillIndex, selectedMax + 1); + const auto selectedInRow = (selectedTill - selectedFrom); + if (selectedInRow > 0) { + auto hq = PainterHighQualityEnabler(p); + p.setPen(Qt::NoPen); + p.setBrush(st::activeButtonBg); + p.drawRoundedRect( + (x + + (selectedFrom - index) * _st.cellSize.width() + + innerSkipLeft + - st::lineWidth), + y + innerSkipTop - st::lineWidth, + ((selectedInRow - 1) * _st.cellSize.width() + + 2 * st::lineWidth + + _st.cellInner), + _st.cellInner + 2 * st::lineWidth, + (_st.cellInner / 2.) + st::lineWidth, + (_st.cellInner / 2.) + st::lineWidth); + p.setBrush(Qt::NoBrush); } for (auto col = 0; col != kDaysInWeek; ++col, ++index, x += _st.cellSize.width()) { - auto rect = myrtlrect(x, y, _st.cellSize.width(), _st.cellSize.height()); - auto grayedOut = (index < 0 || index >= daysCount || !rect.intersects(clip)); - auto highlighted = (index == highlightedIndex); - auto enabled = _context->isEnabled(index); - auto innerLeft = x + (_st.cellSize.width() - _st.cellInner) / 2; - auto innerTop = y + (_st.cellSize.height() - _st.cellInner) / 2; + const auto rect = myrtlrect(x, y, _st.cellSize.width(), _st.cellSize.height()); + const auto selected = (index >= selectedMin) && (index <= selectedMax); + const auto grayedOut = !selected && (index < 0 || index >= daysCount); + const auto highlighted = (index == highlightedIndex); + const auto enabled = _context->isEnabled(index); + const auto innerLeft = x + innerSkipLeft; + const auto innerTop = y + innerSkipTop; if (highlighted) { - PainterHighQualityEnabler hq(p); + auto hq = PainterHighQualityEnabler(p); p.setPen(Qt::NoPen); p.setBrush(grayedOut ? st::windowBgOver : st::dialogsBgActive); p.drawEllipse(myrtlrect(innerLeft, innerTop, _st.cellInner, _st.cellInner)); p.setBrush(Qt::NoBrush); } - auto it = _ripples.find(index); - if (it != _ripples.cend()) { - auto colorOverride = [highlighted, grayedOut] { - if (highlighted) { - return grayedOut ? st::windowBgRipple : st::dialogsRippleBgActive; - } - return st::windowBgOver; - }; - it->second->paint(p, innerLeft, innerTop, width(), &(colorOverride()->c)); + const auto it = _ripples.find(index); + if (it != _ripples.cend() && !selectionMode) { + const auto colorOverride = (!highlighted + ? st::windowBgOver + : grayedOut + ? st::windowBgRipple + : st::dialogsRippleBgActive)->c; + it->second->paint(p, innerLeft, innerTop, width(), &colorOverride); if (it->second->empty()) { _ripples.erase(it); } } - if (highlighted) { - p.setPen(grayedOut ? st::windowSubTextFg : st::dialogsNameFgActive); - } else if (enabled) { - p.setPen(grayedOut ? st::windowSubTextFg : st::boxTextFg); - } else { - p.setPen(st::windowSubTextFg); - } + p.setPen(selected + ? st::activeButtonFg + : highlighted + ? (grayedOut ? st::windowSubTextFg : st::dialogsNameFgActive) + : enabled + ? (grayedOut ? st::windowSubTextFg : st::boxTextFg) + : st::windowSubTextFg); p.drawText(rect, _context->labelFromIndex(index), style::al_center); } } } void CalendarBox::Inner::mouseMoveEvent(QMouseEvent *e) { + const auto globalPosition = e->globalPos(); + _mouseMoved = (_lastGlobalPosition != globalPosition); + _lastGlobalPosition = globalPosition; + const auto size = _st.cellSize; const auto point = e->pos(); const auto inner = QRect( @@ -410,6 +657,20 @@ void CalendarBox::Inner::mouseMoveEvent(QMouseEvent *e) { } else { setSelected(kEmptySelection); } + if (_pressed != kEmptySelection && _context->selectionMode()) { + const auto row = (point.y() >= rowsTop()) + ? (point.y() - rowsTop()) / size.height() + : -1; + const auto col = (point.y() < rowsTop()) + ? 0 + : (point.x() >= rowsLeft()) + ? std::min( + (point.x() - rowsLeft()) / size.width(), + kDaysInWeek - 1) + : 0; + const auto index = row * kDaysInWeek + col - _context->daysShift(); + _context->updateSelection(index); + } } void CalendarBox::Inner::setSelected(int selected) { @@ -417,9 +678,20 @@ void CalendarBox::Inner::setSelected(int selected) { selected = kEmptySelection; } _selected = selected; - setCursor((_selected == kEmptySelection) - ? style::cur_default - : style::cur_pointer); + const auto pointer = (_selected != kEmptySelection); + const auto force = (_mouseMoved && _cursorSetWithoutMouseMove); + if (_pointerCursor != pointer || force) { + if (force) { + // Workaround some strange bug. When I call setCursor while + // scrolling by touchpad the new cursor is not applied and + // then it is not applied until it is changed. + setCursor(pointer ? style::cur_default : style::cur_pointer); + } + setCursor(pointer ? style::cur_pointer : style::cur_default); + _cursorSetWithoutMouseMove = !_mouseMoved; + _pointerCursor = pointer; + } + _mouseMoved = false; } void CalendarBox::Inner::mousePressEvent(QMouseEvent *e) { @@ -439,13 +711,29 @@ void CalendarBox::Inner::mousePressEvent(QMouseEvent *e) { } auto ripplePosition = QPoint(cell.x() + (_st.cellSize.width() - _st.cellInner) / 2, cell.y() + (_st.cellSize.height() - _st.cellInner) / 2); it->second->add(e->pos() - ripplePosition); + + if (_context->selectionMode()) { + if (_context->selectedMin().has_value() + && ((e->modifiers() & Qt::ShiftModifier) + || (_twoPressSelectionStarted + && (_context->selectedMin() + == _context->selectedMax())))) { + _context->updateSelection(_selected); + _twoPressSelectionStarted = false; + } else { + _context->startSelection(_selected); + _twoPressSelectionStarted = true; + } + } } } void CalendarBox::Inner::mouseReleaseEvent(QMouseEvent *e) { auto pressed = _pressed; setPressed(kEmptySelection); - if (pressed != kEmptySelection && pressed == _selected) { + if (pressed != kEmptySelection + && pressed == _selected + && !_context->selectionMode()) { crl::on_main(this, [=] { const auto onstack = _dateChosenCallback; onstack(_context->dateFromIndex(pressed)); @@ -465,9 +753,8 @@ void CalendarBox::Inner::setPressed(int pressed) { } } -int CalendarBox::Inner::countHeight() { - const auto innerHeight = _st.daysHeight - + _context->rowsCount() * _st.cellSize.height(); +int CalendarBox::Inner::countMaxHeight() const { + const auto innerHeight = _context->rowsCountMax() * _st.cellSize.height(); return _st.padding.top() + innerHeight + _st.padding.bottom(); @@ -491,29 +778,68 @@ void CalendarBox::Inner::selectHighlighted() { CalendarBox::Inner::~Inner() = default; -class CalendarBox::Title : public TWidget, private base::Subscriber { +class CalendarBox::Title final : public RpWidget { public: - Title(QWidget *parent, not_null context) - : TWidget(parent) - , _context(context) { - subscribe(_context->month(), [this](QDate date) { monthChanged(date); }); - } + Title( + QWidget *parent, + not_null context, + const style::CalendarSizes &st); protected: void paintEvent(QPaintEvent *e); private: - void monthChanged(QDate month); + void setTextFromMonth(QDate month); + void setText(QString text); + void paintDayNames(Painter &p, QRect clip); - not_null _context; + const style::CalendarSizes &_st; + const not_null _context; QString _text; int _textWidth = 0; + int _textLeft = 0; }; -void CalendarBox::Title::monthChanged(QDate month) { - _text = langMonthOfYearFull(month.month(), month.year()); +CalendarBox::Title::Title( + QWidget *parent, + not_null context, + const style::CalendarSizes &st) +: RpWidget(parent) +, _st(st) +, _context(context) { + const auto dayWidth = st::calendarDaysFont->width(langDayOfWeek(1)); + _textLeft = _st.padding.left() + (_st.cellSize.width() - dayWidth) / 2; + + _context->monthValue( + ) | rpl::filter([=] { + return !_context->selectionMode(); + }) | rpl::start_with_next([=](QDate date) { + setTextFromMonth(date); + }, lifetime()); + + _context->selectionUpdates( + ) | rpl::start_with_next([=] { + if (!_context->selectionMode()) { + setTextFromMonth(_context->month()); + } else if (!_context->selectedMin()) { + setText(tr::lng_calendar_select_days(tr::now)); + } else { + setText(tr::lng_calendar_days( + tr::now, + lt_count, + (1 + *_context->selectedMax() - *_context->selectedMin()))); + } + }, lifetime()); +} + +void CalendarBox::Title::setTextFromMonth(QDate month) { + setText(langMonthOfYearFull(month.month(), month.year())); +} + +void CalendarBox::Title::setText(QString text) { + _text = std::move(text); _textWidth = st::calendarTitleFont->width(_text); update(); } @@ -521,78 +847,189 @@ void CalendarBox::Title::monthChanged(QDate month) { void CalendarBox::Title::paintEvent(QPaintEvent *e) { Painter p(this); + const auto clip = e->rect(); + p.setFont(st::calendarTitleFont); p.setPen(st::boxTitleFg); - p.drawTextLeft((width() - _textWidth) / 2, (height() - st::calendarTitleFont->height) / 2, width(), _text, _textWidth); + p.drawTextLeft( + _textLeft, + (st::calendarTitleHeight - st::calendarTitleFont->height) / 2, + width(), + _text, + _textWidth); + + paintDayNames(p, clip); } -CalendarBox::CalendarBox( - QWidget*, - QDate month, - QDate highlighted, - Fn callback, - FnMut)> finalize) -: CalendarBox( - nullptr, - month, - highlighted, - std::move(callback), - std::move(finalize), - st::defaultCalendarSizes) { +void CalendarBox::Title::paintDayNames(Painter &p, QRect clip) { + p.setFont(st::calendarDaysFont); + p.setPen(st::calendarDaysFg); + auto y = st::calendarTitleHeight + _st.padding.top(); + auto x = _st.padding.left(); + if (!myrtlrect(x, y, _st.cellSize.width() * kDaysInWeek, _st.daysHeight).intersects(clip)) { + return; + } + for (auto i = 0; i != kDaysInWeek; ++i, x += _st.cellSize.width()) { + auto rect = myrtlrect(x, y, _st.cellSize.width(), _st.daysHeight); + if (!rect.intersects(clip)) { + continue; + } + p.drawText(rect, langDayOfWeek(i + 1), style::al_top); + } } -CalendarBox::CalendarBox( - QWidget*, - QDate month, - QDate highlighted, - Fn callback, - FnMut)> finalize, - const style::CalendarSizes &st) -: _st(st) -, _context(std::make_unique(month, highlighted)) -, _inner(this, _context.get(), _st) -, _title(this, _context.get()) +CalendarBox::CalendarBox(QWidget*, CalendarBoxArgs &&args) +: _st(args.st) +, _context( + std::make_unique(args.month.value(), args.highlighted.value())) +, _scroll(std::make_unique(this, st::calendarScroll)) +, _inner( + _scroll->setOwnedWidget(object_ptr(this, _context.get(), _st))) +, _title(this, _context.get(), _st) , _previous(this, st::calendarPrevious) , _next(this, st::calendarNext) -, _callback(std::move(callback)) -, _finalize(std::move(finalize)) { +, _callback(std::move(args.callback.value())) +, _finalize(std::move(args.finalize)) +, _jumpTimer([=] { jump(_jumpButton); }) +, _selectionChanged(std::move(args.selectionChanged)) { + _title->setAttribute(Qt::WA_TransparentForMouseEvents); + _context->setAllowsSelection(args.allowsSelection); + _context->setMinDate(args.minDate); + _context->setMaxDate(args.maxDate); + + _scroll->scrolls( + ) | rpl::filter([=] { + return _watchScroll; + }) | rpl::start_with_next([=] { + processScroll(); + }, lifetime()); + + const auto setupJumps = [&]( + not_null button, + not_null enabled) { + button->events( + ) | rpl::filter([=] { + return *enabled; + }) | rpl::start_with_next([=](not_null e) { + const auto type = e->type(); + if (type == QEvent::MouseMove + && !(static_cast(e.get())->buttons() + & Qt::LeftButton)) { + showJumpTooltip(button); + } else if (type == QEvent::Leave) { + Ui::Tooltip::Hide(); + } else if (type == QEvent::MouseButtonPress + && (static_cast(e.get())->button() + == Qt::LeftButton)) { + jumpAfterDelay(button); + } else if (type == QEvent::MouseButtonRelease + && (static_cast(e.get())->button() + == Qt::LeftButton)) { + _jumpTimer.cancel(); + } + }, lifetime()); + }; + setupJumps(_previous.data(), &_previousEnabled); + setupJumps(_next.data(), &_nextEnabled); + + _context->selectionUpdates( + ) | rpl::start_with_next([=] { + if (!_context->selectionMode()) { + _floatingDate = nullptr; + } else if (!_floatingDate) { + _floatingDate = std::make_unique( + this, + _context.get()); + rpl::combine( + _scroll->geometryValue(), + _floatingDate->widthValue() + ) | rpl::start_with_next([=](QRect scroll, int width) { + const auto shift = _st.daysHeight + - _st.padding.top() + - st::calendarDaysFont->height; + _floatingDate->move( + scroll.x() + (scroll.width() - width) / 2, + scroll.y() - shift); + }, _floatingDate->lifetime()); + } + }, lifetime()); } -void CalendarBox::setMinDate(QDate date) { - _context->setMinDate(date); +CalendarBox::~CalendarBox() = default; + +void CalendarBox::toggleSelectionMode(bool enabled) { + _context->toggleSelectionMode(enabled); } -void CalendarBox::setMaxDate(QDate date) { - _context->setMaxDate(date); +QDate CalendarBox::selectedFirstDate() const { + const auto min = _context->selectedMin(); + return min.has_value() ? _context->dateFromIndex(*min) : QDate(); } -bool CalendarBox::hasBeginningButton() const { - return _context->hasBeginningButton(); +QDate CalendarBox::selectedLastDate() const { + const auto max = _context->selectedMax(); + return max.has_value() ? _context->dateFromIndex(*max) : QDate(); } -void CalendarBox::setBeginningButton(bool enabled) { - _context->setBeginningButton(enabled); +void CalendarBox::showJumpTooltip(not_null button) { + _tooltipButton = button; + Ui::Tooltip::Show(kTooltipDelay, this); +} + +void CalendarBox::jumpAfterDelay(not_null button) { + _jumpButton = button; + _jumpTimer.callOnce(kJumpDelay); + Ui::Tooltip::Hide(); +} + +void CalendarBox::jump(QPointer button) { + const auto jumpToIndex = [&](int index) { + _watchScroll = false; + _context->showMonth(_context->dateFromIndex(index)); + setExactScroll(); + }; + if (_jumpButton == _previous.data() && _previousEnabled) { + jumpToIndex(_context->minDayIndex()); + } else if (_jumpButton == _next.data() && _nextEnabled) { + jumpToIndex(_context->maxDayIndex()); + } + _jumpButton = nullptr; + _jumpTimer.cancel(); } void CalendarBox::prepare() { - _previous->setClickedCallback([this] { goPreviousMonth(); }); - _next->setClickedCallback([this] { goNextMonth(); }); + _previous->setClickedCallback([=] { goPreviousMonth(); }); + _next->setClickedCallback([=] { goNextMonth(); }); -// _inner = setInnerWidget(object_ptr(this, _context.get()), st::calendarScroll, st::calendarTitleHeight); _inner->setDateChosenCallback(std::move(_callback)); - addButton(tr::lng_close(), [this] { closeBox(); }); + _context->monthValue( + ) | rpl::start_with_next([=](QDate month) { + monthChanged(month); + }, lifetime()); + setExactScroll(); - subscribe(_context->month(), [this](QDate month) { monthChanged(month); }); - - _context->start(); + _context->selectionUpdates( + ) | rpl::start_with_next([=] { + _selectionMode = _context->selectionMode(); + if (_selectionChanged) { + const auto count = !_selectionMode + ? std::optional() + : !_context->selectedMin() + ? 0 + : (1 + *_context->selectedMax() - *_context->selectedMin()); + _selectionChanged(this, count); + } + if (!_selectionMode) { + clearButtons(); + createButtons(); + } + }, lifetime()); + createButtons(); if (_finalize) { _finalize(this); } - if (!_context->atBeginning() && hasBeginningButton()) { - addLeftButton(tr::lng_calendar_beginning(), [this] { _inner->selectBeginning(); }); - } } bool CalendarBox::isPreviousEnabled() const { @@ -605,33 +1042,115 @@ bool CalendarBox::isNextEnabled() const { void CalendarBox::goPreviousMonth() { if (isPreviousEnabled()) { + _watchScroll = false; _context->skipMonth(-1); + setExactScroll(); } } void CalendarBox::goNextMonth() { if (isNextEnabled()) { + _watchScroll = false; _context->skipMonth(1); + setExactScroll(); } } +void CalendarBox::setExactScroll() { + const auto top = _st.padding.top() + + (_context->daysShift() / kDaysInWeek) * _st.cellSize.height(); + _scroll->scrollToY(top); + _watchScroll = true; +} + +void CalendarBox::processScroll() { + const auto wasTop = _scroll->scrollTop(); + const auto wasShift = _context->daysShift(); + const auto point = _scroll->rect().center() + QPoint(0, wasTop); + const auto row = (point.y() - _st.padding.top()) / _st.cellSize.height(); + const auto col = (point.x() - _st.padding.left()) / _st.cellSize.width(); + const auto index = row * kDaysInWeek + col; + const auto date = _context->dateFromIndex(index - wasShift); + if (_context->showsMonthOf(date)) { + return; + } + const auto wasFirst = _context->dateFromIndex(-wasShift); + const auto month = QDate(date.year(), date.month(), 1); + _watchScroll = false; + _context->showMonth(month); + const auto nowShift = _context->daysShift(); + const auto nowFirst = _context->dateFromIndex(-nowShift); + const auto delta = nowFirst.daysTo(wasFirst) / kDaysInWeek; + _scroll->scrollToY(wasTop + delta * _st.cellSize.height()); + _watchScroll = true; +} + +void CalendarBox::createButtons() { + if (!_context->allowsSelection()) { + addButton(tr::lng_close(), [=] { closeBox(); }); + } else if (!_context->selectionMode()) { + addButton(tr::lng_close(), [=] { closeBox(); }); + addLeftButton(tr::lng_calendar_select_days(), [=] { + _context->toggleSelectionMode(true); + }); + } else { + addButton(tr::lng_cancel(), [=] { + _context->toggleSelectionMode(false); + }); + } +} + +QString CalendarBox::tooltipText() const { + if (_tooltipButton == _previous.data()) { + return tr::lng_calendar_start_tip(tr::now); + } else if (_tooltipButton == _next.data()) { + return tr::lng_calendar_end_tip(tr::now); + } + return QString(); +} + +QPoint CalendarBox::tooltipPos() const { + return QCursor::pos(); +} + +bool CalendarBox::tooltipWindowActive() const { + return window()->isActiveWindow(); +} + void CalendarBox::monthChanged(QDate month) { - setDimensions(_st.width, st::calendarTitleHeight + _inner->countHeight()); - auto previousEnabled = isPreviousEnabled(); - _previous->setIconOverride(previousEnabled ? nullptr : &st::calendarPreviousDisabled); - _previous->setRippleColorOverride(previousEnabled ? nullptr : &st::boxBg); - _previous->setCursor(previousEnabled ? style::cur_pointer : style::cur_default); - auto nextEnabled = isNextEnabled(); - _next->setIconOverride(nextEnabled ? nullptr : &st::calendarNextDisabled); - _next->setRippleColorOverride(nextEnabled ? nullptr : &st::boxBg); - _next->setCursor(nextEnabled ? style::cur_pointer : style::cur_default); + setDimensions(_st.width, st::calendarTitleHeight + _st.daysHeight + _inner->countMaxHeight()); + + _previousEnabled = isPreviousEnabled(); + _previous->setIconOverride(_previousEnabled ? nullptr : &st::calendarPreviousDisabled); + _previous->setRippleColorOverride(_previousEnabled ? nullptr : &st::boxBg); + _previous->setPointerCursor(_previousEnabled); + if (!_previousEnabled) { + _previous->clearState(); + } + _nextEnabled = isNextEnabled(); + _next->setIconOverride(_nextEnabled ? nullptr : &st::calendarNextDisabled); + _next->setRippleColorOverride(_nextEnabled ? nullptr : &st::boxBg); + _next->setPointerCursor(_nextEnabled); + if (!_nextEnabled) { + _next->clearState(); + } } void CalendarBox::resizeEvent(QResizeEvent *e) { - _previous->moveToLeft(0, 0); - _next->moveToRight(0, 0); - _title->setGeometryToLeft(_previous->width(), 0, width() - _previous->width() - _next->width(), st::calendarTitleHeight); - _inner->setGeometryToLeft(0, st::calendarTitleHeight, width(), height() - st::calendarTitleHeight); + const auto dayWidth = st::calendarDaysFont->width(langDayOfWeek(7)); + const auto skip = _st.padding.left() + + _st.cellSize.width() * (kDaysInWeek - 1) + + (_st.cellSize.width() - dayWidth) / 2 + + dayWidth; + const auto right = width() - skip; + const auto shift = _next->width() + - (_next->width() - st::calendarPrevious.icon.width()) / 2 + - st::calendarPrevious.icon.width(); + _next->moveToRight(right - shift, 0); + _previous->moveToRight(right - shift + _next->width(), 0); + const auto title = st::calendarTitleHeight + _st.daysHeight; + _title->setGeometryToLeft(0, 0, width(), title); + _scroll->setGeometryToLeft(0, title, width(), height() - title); BoxContent::resizeEvent(e); } @@ -659,16 +1178,4 @@ void CalendarBox::keyPressEvent(QKeyEvent *e) { } } -void CalendarBox::wheelEvent(QWheelEvent *e) { - const auto direction = Ui::WheelDirection(e); - - if (direction < 0) { - goPreviousMonth(); - } else if (direction > 0) { - goNextMonth(); - } -} - -CalendarBox::~CalendarBox() = default; - } // namespace Ui diff --git a/Telegram/SourceFiles/ui/boxes/calendar_box.h b/Telegram/SourceFiles/ui/boxes/calendar_box.h index b5e398b6b..a1f4d927d 100644 --- a/Telegram/SourceFiles/ui/boxes/calendar_box.h +++ b/Telegram/SourceFiles/ui/boxes/calendar_box.h @@ -8,46 +8,56 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "ui/layers/box_content.h" -#include "base/observer.h" +#include "ui/widgets/tooltip.h" +#include "base/required.h" +#include "base/timer.h" namespace style { struct CalendarSizes; } // namespace style +namespace st { +extern const style::CalendarSizes &defaultCalendarSizes; +} // namespace st + namespace Ui { class IconButton; +class ScrollArea; +class CalendarBox; -class CalendarBox : public BoxContent, private base::Subscriber { +struct CalendarBoxArgs { + template + using required = base::required; + + required month; + required highlighted; + required> callback; + FnMut)> finalize; + const style::CalendarSizes &st = st::defaultCalendarSizes; + QDate minDate; + QDate maxDate; + bool allowsSelection = false; + Fn, + std::optional)> selectionChanged; +}; + +class CalendarBox final : public BoxContent, private AbstractTooltipShower { public: - CalendarBox( - QWidget*, - QDate month, - QDate highlighted, - Fn callback, - FnMut)> finalize = nullptr); - CalendarBox( - QWidget*, - QDate month, - QDate highlighted, - Fn callback, - FnMut)> finalize, - const style::CalendarSizes &st); - - void setBeginningButton(bool enabled); - bool hasBeginningButton() const; - - void setMinDate(QDate date); - void setMaxDate(QDate date); - + CalendarBox(QWidget*, CalendarBoxArgs &&args); ~CalendarBox(); + void toggleSelectionMode(bool enabled); + + [[nodiscard]] QDate selectedFirstDate() const; + [[nodiscard]] QDate selectedLastDate() const; + protected: void prepare() override; void keyPressEvent(QKeyEvent *e) override; void resizeEvent(QResizeEvent *e) override; - void wheelEvent(QWheelEvent *e) override; private: void monthChanged(QDate month); @@ -57,22 +67,50 @@ private: void goPreviousMonth(); void goNextMonth(); + void setExactScroll(); + void processScroll(); + void createButtons(); + + void showJumpTooltip(not_null button); + void jumpAfterDelay(not_null button); + void jump(QPointer button); + + QString tooltipText() const override; + QPoint tooltipPos() const override; + bool tooltipWindowActive() const override; const style::CalendarSizes &_st; class Context; std::unique_ptr _context; + std::unique_ptr _scroll; + class Inner; - object_ptr _inner; + not_null _inner; + + class FloatingDate; + std::unique_ptr _floatingDate; class Title; object_ptr _title; object_ptr<IconButton> _previous; object_ptr<IconButton> _next; + bool _previousEnabled = false; + bool _nextEnabled = false; Fn<void(QDate date)> _callback; FnMut<void(not_null<CalendarBox*>)> _finalize; + bool _watchScroll = false; + + QPointer<IconButton> _tooltipButton; + QPointer<IconButton> _jumpButton; + base::Timer _jumpTimer; + + bool _selectionMode = false; + Fn<void( + not_null<Ui::CalendarBox*>, + std::optional<int>)> _selectionChanged; }; diff --git a/Telegram/SourceFiles/ui/boxes/choose_date_time.cpp b/Telegram/SourceFiles/ui/boxes/choose_date_time.cpp index f1532f8a2..023f7a94c 100644 --- a/Telegram/SourceFiles/ui/boxes/choose_date_time.cpp +++ b/Telegram/SourceFiles/ui/boxes/choose_date_time.cpp @@ -159,19 +159,17 @@ ChooseDateTimeBoxDescriptor ChooseDateTimeBox( if (*calendar) { return; } - const auto chosen = [=](QDate chosen) { - state->date = chosen; - (*calendar)->closeBox(); - }; - const auto finalize = [=](not_null<CalendarBox*> box) { - box->setMinDate(minDate()); - box->setMaxDate(maxDate()); - }; - *calendar = box->getDelegate()->show(Box<CalendarBox>( - state->date.current(), - state->date.current(), - crl::guard(box, chosen), - finalize)); + *calendar = box->getDelegate()->show( + Box<CalendarBox>(Ui::CalendarBoxArgs{ + .month = state->date.current(), + .highlighted = state->date.current(), + .callback = crl::guard(box, [=](QDate chosen) { + state->date = chosen; + (*calendar)->closeBox(); + }), + .minDate = minDate(), + .maxDate = maxDate(), + })); (*calendar)->boxClosing( ) | rpl::start_with_next(crl::guard(state->time, [=] { state->time->setFocusFast(); diff --git a/Telegram/SourceFiles/ui/boxes/single_choice_box.cpp b/Telegram/SourceFiles/ui/boxes/single_choice_box.cpp index a68e1cfa4..a75d8a030 100644 --- a/Telegram/SourceFiles/ui/boxes/single_choice_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/single_choice_box.cpp @@ -44,7 +44,7 @@ void SingleChoiceBox( st::boxPadding.right(), st::boxOptionListSkip)); } - const auto callback = args.callback; + const auto callback = args.callback.value(); group->setChangedCallback([=](int value) { const auto weak = Ui::MakeWeak(box); callback(value); diff --git a/Telegram/SourceFiles/ui/boxes/single_choice_box.h b/Telegram/SourceFiles/ui/boxes/single_choice_box.h index b33d13575..4964d3b05 100644 --- a/Telegram/SourceFiles/ui/boxes/single_choice_box.h +++ b/Telegram/SourceFiles/ui/boxes/single_choice_box.h @@ -22,7 +22,7 @@ struct SingleChoiceBoxArgs { required<rpl::producer<QString>> title; const std::vector<QString> &options; int initialSelection = 0; - Fn<void(int)> callback; + required<Fn<void(int)>> callback; const style::Checkbox *st = nullptr; const style::Radio *radioSt = nullptr; }; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index dd7fa942f..ae43b82ce 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -268,6 +268,9 @@ historyContactStatusBlock: FlatButton(historyContactStatusButton) { color: attentionButtonFg; overColor: attentionButtonFg; } +historyContactStatusLabel: FlatLabel(defaultFlatLabel) { + minWidth: 240px; +} historyContactStatusMinSkip: 16px; historySendIcon: icon {{ "chat/input_send", historySendIconFg }}; @@ -311,7 +314,7 @@ historyMessagesTTLRU2Icon: icon {{ "chat/input_autodelete_7d_ru", historyCompose historyMessagesTTLRU2IconOver: icon {{ "chat/input_autodelete_7d_ru", historyComposeIconFgOver }}; historyMessagesTTLRU3Icon: icon {{ "chat/input_autodelete_30d_ru", historyComposeIconFg }}; historyMessagesTTLRU3IconOver: icon {{ "chat/input_autodelete_30d_ru", historyComposeIconFgOver }}; -historyAttachEmojiFgActive: windowActiveTextFg; +historyAttachEmojiFgActive: windowBgActive; historyAttachEmojiActive: icon {{ "chat/input_smile_face", historyAttachEmojiFgActive }}; historyAttachEmojiTooltipDelta: 4px; historyEmojiCircle: size(20px, 20px); @@ -811,10 +814,10 @@ historyGroupAboutSkip: 8px; historyVideoDownloadSize: 44px; historyVideoMuteSize: 22px; -historyVideoCancel: icon {{ "playlist_cancel", historyFileThumbIconFg }}; -historyVideoCancelSelected: icon {{ "playlist_cancel", historyFileThumbIconFgSelected }}; -historyVideoDownload: icon {{ "playlist_download", historyFileThumbIconFg }}; -historyVideoDownloadSelected: icon {{ "playlist_download", historyFileThumbIconFgSelected }}; +historyVideoCancel: icon {{ "player/playlist_cancel", historyFileThumbIconFg }}; +historyVideoCancelSelected: icon {{ "player/playlist_cancel", historyFileThumbIconFgSelected }}; +historyVideoDownload: icon {{ "player/playlist_download", historyFileThumbIconFg }}; +historyVideoDownloadSelected: icon {{ "player/playlist_download", historyFileThumbIconFgSelected }}; historyVideoRadialLine: msgFileRadialLine; historyAudioDownloadSize: 20px; @@ -946,3 +949,28 @@ historyRequestsUserpics: GroupCallUserpics { align: align(left); } historyRequestsHeight: 33px; + +SendAsButton { + width: pixels; + height: pixels; + size: pixels; + activeBg: color; + activeFg: color; + cross: CrossAnimation; + duration: int; +} + +sendAsButton: SendAsButton { + width: 44px; + height: 46px; + size: 28px; + activeBg: activeButtonBg; + activeFg: activeButtonFg; + cross: CrossAnimation { + size: 28px; + skip: 10px; + stroke: 2px; + minScale: 0.3; + } + duration: 150; +} diff --git a/Telegram/SourceFiles/ui/chat/choose_send_as.cpp b/Telegram/SourceFiles/ui/chat/choose_send_as.cpp new file mode 100644 index 000000000..4daea9956 --- /dev/null +++ b/Telegram/SourceFiles/ui/chat/choose_send_as.cpp @@ -0,0 +1,220 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/chat/choose_send_as.h" + +#include "boxes/peer_list_box.h" +#include "data/data_peer.h" +#include "data/data_channel.h" +#include "data/data_peer_values.h" +#include "history/history.h" +#include "ui/controls/send_as_button.h" +#include "window/window_session_controller.h" +#include "main/main_session.h" +#include "main/session/send_as_peers.h" +#include "lang/lang_keys.h" +#include "styles/style_calls.h" +#include "styles/style_boxes.h" +#include "styles/style_chat.h" + +namespace Ui { +namespace { + +class ListController final : public PeerListController { +public: + ListController( + std::vector<not_null<PeerData*>> list, + not_null<PeerData*> selected); + + Main::Session &session() const override; + void prepare() override; + void rowClicked(not_null<PeerListRow*> row) override; + + [[nodiscard]] rpl::producer<not_null<PeerData*>> clicked() const; + +private: + std::unique_ptr<PeerListRow> createRow(not_null<PeerData*> peer); + + std::vector<not_null<PeerData*>> _list; + not_null<PeerData*> _selected; + rpl::event_stream<not_null<PeerData*>> _clicked; + +}; + +ListController::ListController( + std::vector<not_null<PeerData*>> list, + not_null<PeerData*> selected) +: PeerListController() +, _list(std::move(list)) +, _selected(selected) { +} + +Main::Session &ListController::session() const { + return _selected->session(); +} + +std::unique_ptr<PeerListRow> ListController::createRow( + not_null<PeerData*> peer) { + auto result = std::make_unique<PeerListRow>(peer); + if (peer->isSelf()) { + result->setCustomStatus( + tr::lng_group_call_join_as_personal(tr::now)); + } else if (peer->isMegagroup()) { + result->setCustomStatus(tr::lng_send_as_anonymous_admin(tr::now)); + } else if (const auto channel = peer->asChannel()) { + result->setCustomStatus(tr::lng_chat_status_subscribers( + tr::now, + lt_count, + channel->membersCount())); + } + return result; +} + +void ListController::prepare() { + delegate()->peerListSetSearchMode(PeerListSearchMode::Disabled); + for (const auto &peer : _list) { + auto row = createRow(peer); + const auto raw = row.get(); + delegate()->peerListAppendRow(std::move(row)); + if (peer == _selected) { + delegate()->peerListSetRowChecked(raw, true); + raw->finishCheckedAnimation(); + } + } + delegate()->peerListRefreshRows(); +} + +void ListController::rowClicked(not_null<PeerListRow*> row) { + const auto peer = row->peer(); + if (peer == _selected) { + return; + } + _clicked.fire_copy(peer); +} + +rpl::producer<not_null<PeerData*>> ListController::clicked() const { + return _clicked.events(); +} + +} // namespace + +void ChooseSendAsBox( + not_null<GenericBox*> box, + std::vector<not_null<PeerData*>> list, + not_null<PeerData*> chosen, + Fn<void(not_null<PeerData*>)> done) { + Expects(ranges::contains(list, chosen)); + Expects(done != nullptr); + + box->setWidth(st::groupCallJoinAsWidth); + box->setTitle(tr::lng_send_as_title()); + const auto &labelSt = st::confirmPhoneAboutLabel; + box->addRow(object_ptr<Ui::FlatLabel>( + box, + tr::lng_group_call_join_as_about(), + labelSt)); + + auto &lifetime = box->lifetime(); + const auto delegate = lifetime.make_state< + PeerListContentDelegateSimple + >(); + const auto controller = lifetime.make_state<ListController>( + list, + chosen); + controller->setStyleOverrides( + &st::peerListJoinAsList, + nullptr); + + controller->clicked( + ) | rpl::start_with_next([=](not_null<PeerData*> peer) { + const auto weak = MakeWeak(box); + done(peer); + if (weak) { + box->closeBox(); + } + }, box->lifetime()); + + const auto content = box->addRow( + object_ptr<PeerListContent>(box, controller), + style::margins()); + delegate->setContent(content); + controller->setDelegate(delegate); + box->addButton(tr::lng_box_done(), [=] { box->closeBox(); }); +} + +void SetupSendAsButton( + not_null<SendAsButton*> button, + rpl::producer<PeerData*> active, + not_null<Window::SessionController*> window) { + using namespace rpl::mappers; + const auto current = button->lifetime().make_state< + rpl::variable<PeerData*> + >(std::move(active)); + button->setClickedCallback([=] { + const auto peer = current->current(); + if (!peer) { + return; + } + const auto session = &peer->session(); + const auto &list = session->sendAsPeers().list(peer); + if (list.size() < 2) { + return; + } + const auto done = [=](not_null<PeerData*> sendAs) { + session->sendAsPeers().saveChosen(peer, sendAs); + }; + window->show(Box( + Ui::ChooseSendAsBox, + list, + session->sendAsPeers().resolveChosen(peer), + done)); + }); + + auto userpic = current->value( + ) | rpl::filter([=](PeerData *peer) { + return peer && peer->isMegagroup(); + }) | rpl::map([=](not_null<PeerData*> peer) { + const auto channel = peer->asMegagroup(); + + auto updates = rpl::single( + rpl::empty_value() + ) | rpl::then(channel->session().sendAsPeers().updated( + ) | rpl::filter( + _1 == channel + ) | rpl::to_empty); + + return rpl::combine( + std::move(updates), + channel->adminRightsValue() + ) | rpl::map([=] { + return channel->session().sendAsPeers().resolveChosen(channel); + }) | rpl::distinct_until_changed( + ) | rpl::map([=](not_null<PeerData*> chosen) { + return Data::PeerUserpicImageValue( + chosen, + st::sendAsButton.size * style::DevicePixelRatio()); + }) | rpl::flatten_latest(); + }) | rpl::flatten_latest(); + + std::move( + userpic + ) | rpl::start_with_next([=](QImage &&userpic) { + button->setUserpic(std::move(userpic)); + }, button->lifetime()); +} + +void SetupSendAsButton( + not_null<SendAsButton*> button, + not_null<Window::SessionController*> window) { + auto active = window->activeChatValue( + ) | rpl::map([=](const Dialogs::Key &key) { + return key.history() ? key.history()->peer.get() : nullptr; + }); + SetupSendAsButton(button, std::move(active), window); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/chat/choose_send_as.h b/Telegram/SourceFiles/ui/chat/choose_send_as.h new file mode 100644 index 000000000..1af733e1a --- /dev/null +++ b/Telegram/SourceFiles/ui/chat/choose_send_as.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 "base/object_ptr.h" +#include "ui/layers/generic_box.h" + +class PeerData; + +namespace Window { +class SessionController; +} // namespace Window + +namespace Ui { + +class SendAsButton; + +void ChooseSendAsBox( + not_null<GenericBox*> box, + std::vector<not_null<PeerData*>> list, + not_null<PeerData*> chosen, + Fn<void(not_null<PeerData*>)> done); + +void SetupSendAsButton( + not_null<SendAsButton*> button, + rpl::producer<PeerData*> active, + not_null<Window::SessionController*> window); + +void SetupSendAsButton( + not_null<SendAsButton*> button, + not_null<Window::SessionController*> window); + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/controls/call_mute_button.cpp b/Telegram/SourceFiles/ui/controls/call_mute_button.cpp index 9084f0faa..523e22a04 100644 --- a/Telegram/SourceFiles/ui/controls/call_mute_button.cpp +++ b/Telegram/SourceFiles/ui/controls/call_mute_button.cpp @@ -590,13 +590,13 @@ void CallMuteButton::refreshLabels() { void CallMuteButton::refreshIcons() { _icons[0].emplace(Lottie::IconDescriptor{ - .path = u":/gui/icons/calls/voice.lottie"_q, + .path = u":/icons/calls/voice.lottie"_q, .color = st::groupCallIconFg, .sizeOverride = _st->lottieSize, .frame = (_iconState.index ? 0 : _iconState.frameTo), }); _icons[1].emplace(Lottie::IconDescriptor{ - .path = u":/gui/icons/calls/hands.lottie"_q, + .path = u":/icons/calls/hands.lottie"_q, .color = st::groupCallIconFg, .sizeOverride = _st->lottieSize, .frame = (_iconState.index ? _iconState.frameTo : 0), diff --git a/Telegram/SourceFiles/ui/controls/send_as_button.cpp b/Telegram/SourceFiles/ui/controls/send_as_button.cpp new file mode 100644 index 000000000..8743abe73 --- /dev/null +++ b/Telegram/SourceFiles/ui/controls/send_as_button.cpp @@ -0,0 +1,91 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/controls/send_as_button.h" + +#include "ui/effects/cross_animation.h" +#include "styles/style_chat.h" + +namespace Ui { + +SendAsButton::SendAsButton(QWidget *parent, const style::SendAsButton &st, int radius) +: AbstractButton(parent) +, _st(st) +, _radius(radius) { + resize(_st.width, _st.height); +} + +void SendAsButton::setUserpic(QImage userpic) { + _userpic = std::move(userpic); + update(); +} + +void SendAsButton::setActive(bool active) { + if (_active == active) { + return; + } + _active = active; + _activeAnimation.start( + [=] { update(); }, + _active ? 0. : 1., + _active ? 1. : 0., + _st.duration); +} + +void SendAsButton::paintEvent(QPaintEvent *e) { + auto p = Painter(this); + + const auto left = (width() - _st.size) / 2; + const auto top = (height() - _st.size) / 2; + + const auto active = _activeAnimation.value(_active ? 1. : 0.); + if (active < 1. && !_userpic.isNull()) { + p.drawImage(QRect(left, top, _st.size, _st.size), _userpic); + } + if (active > 0.) { + p.setOpacity(active); + + p.setPen(Qt::NoPen); + p.setBrush(_st.activeBg); + { + PainterHighQualityEnabler hq(p); + switch (_radius) { + case 0: + p.drawRoundedRect( + left, top, _st.size, _st.size, + 0, 0); + break; + + case 1: + p.drawRoundedRect( + left, top, _st.size, _st.size, + st::buttonRadius, st::buttonRadius); + break; + + case 2: + p.drawRoundedRect( + left, top, _st.size, _st.size, + st::dateRadius, st::dateRadius); + break; + + default: + p.drawEllipse(left, top, _st.size, _st.size); + } + } + + CrossAnimation::paint( + p, + _st.cross, + _st.activeFg, + left, + top, + width(), + active); + } +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/controls/send_as_button.h b/Telegram/SourceFiles/ui/controls/send_as_button.h new file mode 100644 index 000000000..500012ec7 --- /dev/null +++ b/Telegram/SourceFiles/ui/controls/send_as_button.h @@ -0,0 +1,40 @@ +/* +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/widgets/buttons.h" +#include "ui/effects/animations.h" + +namespace style { +struct SendAsButton; +} // namespace style + +namespace Ui { + +class SendAsButton final : public AbstractButton { +public: + SendAsButton(QWidget *parent, const style::SendAsButton &st, int radius); + + void setUserpic(QImage userpic); + + void setActive(bool active); + +private: + void paintEvent(QPaintEvent *e) override; + + const style::SendAsButton &_st; + + Animations::Simple _activeAnimation; + bool _active = false; + + QImage _userpic; + int _radius; + +}; + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/controls/send_button.h b/Telegram/SourceFiles/ui/controls/send_button.h index da67bcbf5..0c174c8b3 100644 --- a/Telegram/SourceFiles/ui/controls/send_button.h +++ b/Telegram/SourceFiles/ui/controls/send_button.h @@ -13,7 +13,7 @@ namespace Ui { class SendButton final : public RippleButton { public: - SendButton(QWidget *parent); + explicit SendButton(QWidget *parent); static constexpr auto kSlowmodeDelayLimit = 100 * 60; diff --git a/Telegram/SourceFiles/ui/special_fields.cpp b/Telegram/SourceFiles/ui/special_fields.cpp index 61cb4198a..8a741b5df 100644 --- a/Telegram/SourceFiles/ui/special_fields.cpp +++ b/Telegram/SourceFiles/ui/special_fields.cpp @@ -96,8 +96,12 @@ void CountryCodeInput::correctValue( } } -PhonePartInput::PhonePartInput(QWidget *parent, const style::InputField &st) -: MaskedInputField(parent, st/*, tr::lng_phone_ph(tr::now)*/) { +PhonePartInput::PhonePartInput( + QWidget *parent, + const style::InputField &st, + PhonePartInput::GroupsCallback groupsCallback) +: MaskedInputField(parent, st/*, tr::lng_phone_ph(tr::now)*/) +, _groupsCallback(std::move(groupsCallback)) { } void PhonePartInput::paintAdditionalPlaceholder(Painter &p) { @@ -208,11 +212,7 @@ void PhonePartInput::addedToNumber(const QString &added) { } void PhonePartInput::chooseCode(const QString &code) { - _pattern = Countries::Instance().format({ - .phone = code, - .onlyGroups = true, - .incomplete = true, - }).groups; + _pattern = _groupsCallback(code); if (!_pattern.isEmpty() && _pattern.at(0) == code.size()) { _pattern.pop_front(); } else { @@ -297,9 +297,11 @@ PhoneInput::PhoneInput( const style::InputField &st, rpl::producer<QString> placeholder, const QString &defaultValue, - QString value) + QString value, + PhoneInput::GroupsCallback groupsCallback) : MaskedInputField(parent, st, std::move(placeholder), value) -, _defaultValue(defaultValue) { +, _defaultValue(defaultValue) +, _groupsCallback(std::move(groupsCallback)) { if (value.isEmpty()) { clearText(); } else { @@ -344,11 +346,7 @@ void PhoneInput::correctValue( int &nowCursor) { auto digits = now; digits.replace(QRegularExpression("[^\\d]"), QString()); - _pattern = Countries::Instance().format({ - .phone = digits, - .onlyGroups = true, - .incomplete = true, - }).groups; + _pattern = _groupsCallback(digits); QString newPlaceholder; if (_pattern.isEmpty()) { diff --git a/Telegram/SourceFiles/ui/special_fields.h b/Telegram/SourceFiles/ui/special_fields.h index 39be5c162..78327d87e 100644 --- a/Telegram/SourceFiles/ui/special_fields.h +++ b/Telegram/SourceFiles/ui/special_fields.h @@ -42,7 +42,12 @@ private: class PhonePartInput : public MaskedInputField { public: - PhonePartInput(QWidget *parent, const style::InputField &st); + using GroupsCallback = Fn<QVector<int>(const QString &)>; + + PhonePartInput( + QWidget *parent, + const style::InputField &st, + GroupsCallback groupsCallback); [[nodiscard]] auto frontBackspaceEvent() const -> rpl::producer<not_null<QKeyEvent*>> { @@ -66,6 +71,7 @@ private: QVector<int> _pattern; QString _additionalPlaceholder; rpl::event_stream<not_null<QKeyEvent*>> _frontBackspaceEvent; + GroupsCallback _groupsCallback; }; @@ -95,12 +101,15 @@ private: class PhoneInput : public MaskedInputField { public: + using GroupsCallback = Fn<QVector<int>(const QString &)>; + PhoneInput( QWidget *parent, const style::InputField &st, rpl::producer<QString> placeholder, const QString &defaultValue, - QString value); + QString value, + GroupsCallback groupsCallback); void clearText(); @@ -119,6 +128,8 @@ private: QVector<int> _pattern; QString _additionalPlaceholder; + GroupsCallback _groupsCallback; + }; } // namespace Ui diff --git a/Telegram/SourceFiles/window/main_window.h b/Telegram/SourceFiles/window/main_window.h index b40154607..1a1d11571 100644 --- a/Telegram/SourceFiles/window/main_window.h +++ b/Telegram/SourceFiles/window/main_window.h @@ -42,10 +42,13 @@ struct TermsLock; void ConvertIconToBlack(QImage &image); struct CounterLayerArgs { - base::required<int> size = 16; - base::required<int> count = 1; - base::required<style::color> bg; - base::required<style::color> fg; + template <typename T> + using required = base::required<T>; + + required<int> size = 16; + required<int> count = 1; + required<style::color> bg; + required<style::color> fg; }; [[nodiscard]] QImage GenerateCounterLayer(CounterLayerArgs &&args); diff --git a/Telegram/SourceFiles/window/notifications_manager.cpp b/Telegram/SourceFiles/window/notifications_manager.cpp index 291b0763f..29e9a67d0 100644 --- a/Telegram/SourceFiles/window/notifications_manager.cpp +++ b/Telegram/SourceFiles/window/notifications_manager.cpp @@ -714,7 +714,7 @@ void Manager::notificationReplied( } const auto history = session->data().history(id.full.peerId); - auto message = Api::MessageToSend(history); + auto message = Api::MessageToSend(Api::SendAction(history)); message.textWithTags = reply; message.action.replyTo = (id.msgId > 0 && !history->peer->isUser()) ? id.msgId diff --git a/Telegram/SourceFiles/window/themes/window_theme.cpp b/Telegram/SourceFiles/window/themes/window_theme.cpp index 3813ac809..24f9aca68 100644 --- a/Telegram/SourceFiles/window/themes/window_theme.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme.cpp @@ -608,14 +608,14 @@ void ChatBackground::checkUploadWallPaper() { return; } _wallPaperUploadLifetime = _session->uploader().documentReady( - ) | rpl::start_with_next([=](const Storage::UploadedDocument &data) { + ) | rpl::start_with_next([=](const Storage::UploadedMedia &data) { if (data.fullId != _wallPaperUploadId) { return; } _wallPaperUploadId = FullMsgId(); _wallPaperRequestId = _session->api().request( MTPaccount_UploadWallPaper( - data.file, + data.info.file, MTP_string("image/jpeg"), _paper.mtpSettings() ) diff --git a/Telegram/SourceFiles/window/themes/window_theme_editor_box.cpp b/Telegram/SourceFiles/window/themes/window_theme_editor_box.cpp index 6615a38e2..f2b47d39e 100644 --- a/Telegram/SourceFiles/window/themes/window_theme_editor_box.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme_editor_box.cpp @@ -466,7 +466,7 @@ Fn<void()> SavePreparedTheme( Fn<void(SaveErrorType,QString)> fail) { Expects(window->account().sessionExists()); - using Storage::UploadedDocument; + using Storage::UploadedMedia; struct State { FullMsgId id; bool generating = false; @@ -549,11 +549,11 @@ Fn<void()> SavePreparedTheme( }).send(); }; - const auto uploadTheme = [=](const UploadedDocument &data) { + const auto uploadTheme = [=](const UploadedMedia &data) { state->requestId = api->request(MTPaccount_UploadTheme( MTP_flags(MTPaccount_UploadTheme::Flag::f_thumb), - data.file, - *data.thumb, + data.info.file, + *data.info.thumb, MTP_string(state->filename), MTP_string("application/x-tgtheme-tdesktop") )).done([=](const MTPDocument &result) { @@ -576,9 +576,9 @@ Fn<void()> SavePreparedTheme( state->themeContent = theme; session->uploader().documentReady( - ) | rpl::filter([=](const UploadedDocument &data) { - return (data.fullId == state->id) && data.thumb.has_value(); - }) | rpl::start_with_next([=](const UploadedDocument &data) { + ) | rpl::filter([=](const UploadedMedia &data) { + return (data.fullId == state->id) && data.info.thumb.has_value(); + }) | rpl::start_with_next([=](const UploadedMedia &data) { uploadTheme(data); }, state->lifetime); @@ -744,7 +744,7 @@ void SaveTheme( result.match([&](const MTPDtheme &data) { save(CloudTheme::Parse(&window->account().session(), data)); }); - }).fail([=](const MTP::Error &error) { + }).fail([=] { save(CloudTheme()); }).send(); } else { diff --git a/Telegram/SourceFiles/window/window.style b/Telegram/SourceFiles/window/window.style index 6f5589a37..562f9a881 100644 --- a/Telegram/SourceFiles/window/window.style +++ b/Telegram/SourceFiles/window/window.style @@ -287,17 +287,17 @@ windowFiltersButton: SideBarButton(defaultSideBarButton) { iconPosition: point(-1px, 6px); } windowFiltersMainMenu: SideBarButton(windowFiltersButton) { - icon: icon {{ "dialogs_menu", sideBarIconFg }}; + icon: icon {{ "dialogs/dialogs_menu", sideBarIconFg }}; iconPosition: point(-1px, -1px); minHeight: 54px; } windowFiltersMainMenuUnread: icon { - { "dialogs_menu_unread", sideBarIconFg }, - { "dialogs_menu_unread_dot", sideBarBadgeBg }, + { "dialogs/dialogs_menu_unread", sideBarIconFg }, + { "dialogs/dialogs_menu_unread_dot", sideBarBadgeBg }, }; windowFiltersMainMenuUnreadMuted: icon { - { "dialogs_menu_unread", sideBarIconFg }, - { "dialogs_menu_unread_dot", sideBarBadgeBgMuted }, + { "dialogs/dialogs_menu_unread", sideBarIconFg }, + { "dialogs/dialogs_menu_unread_dot", sideBarBadgeBgMuted }, }; windowFilterSmallItem: PeerListItem(defaultPeerListItem) { height: 44px; diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index cfef358fd..6dbba84d3 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_peer_menu.h" #include "kotato/kotato_lang.h" +#include "api/api_chat_participants.h" #include "lang/lang_keys.h" #include "boxes/delete_messages_box.h" #include "boxes/max_invite_box.h" @@ -227,7 +228,7 @@ void TogglePinnedDialog( history->session().api().request(MTPmessages_ToggleDialogPin( MTP_flags(flags), MTP_inputDialogPeer(key.history()->peer->input) - )).done([=](const MTPBool &result) { + )).done([=] { owner->notifyPinnedDialogsOrderUpdated(); }).send(); } else if (const auto folder = key.folder()) { @@ -890,9 +891,10 @@ void PeerMenuCreatePoll( if (std::exchange(*lock, true)) { return; } - auto action = Api::SendAction(peer->owner().history(peer)); + auto action = Api::SendAction( + peer->owner().history(peer), + result.options); action.clearDraft = false; - action.options = result.options; action.replyTo = replyToId; if (const auto localDraft = action.history->localDraft()) { action.clearDraft = localDraft->textWithTags.text.isEmpty(); @@ -1222,18 +1224,17 @@ QPointer<Ui::RpWidget> ShowForwardMessagesBox( for (const auto peer : result) { const auto history = owner->history(peer); + auto action = Api::SendAction(history); + action.options = options; + action.clearDraft = false; + if (!comment.text.isEmpty()) { - auto message = ApiWrap::MessageToSend(history); + auto message = ApiWrap::MessageToSend(action); message.textWithTags = comment; - message.action.options = options; - message.action.clearDraft = false; api.sendMessage(std::move(message)); } data->requestsLeft++; - auto action = Api::SendAction(history); - action.options = options; - action.clearDraft = false; auto resolved = history->resolveForwardDraft(data->draft); api.forwardMessages(std::move(resolved), action, [=] { @@ -1368,35 +1369,25 @@ void PeerMenuAddChannelMembers( return; } const auto api = &channel->session().api(); - api->requestChannelMembersForAdd(channel, crl::guard(navigation, [=]( - const MTPchannels_ChannelParticipants &result) { - api->parseChannelParticipants(channel, result, [&]( - int availableCount, - const QVector<MTPChannelParticipant> &list) { - auto already = ( - list - ) | ranges::views::transform([](const MTPChannelParticipant &p) { - return p.match([](const MTPDchannelParticipantBanned &data) { - return peerFromMTP(data.vpeer()); - }, [](const MTPDchannelParticipantLeft &data) { - return peerFromMTP(data.vpeer()); - }, [](const auto &data) { - return peerFromUser(data.vuser_id()); - }); - }) | ranges::views::transform([&](PeerId participantId) { - return peerIsUser(participantId) - ? channel->owner().userLoaded( - peerToUser(participantId)) - : nullptr; - }) | ranges::views::filter([](UserData *user) { - return (user != nullptr); - }) | ranges::to_vector; + api->chatParticipants().requestForAdd(channel, crl::guard(navigation, [=]( + const Api::ChatParticipants::TLMembers &data) { + const auto &[availableCount, list] = Api::ChatParticipants::Parse( + channel, + data); + const auto already = ( + list + ) | ranges::views::transform([&](const Api::ChatParticipant &p) { + return p.isUser() + ? channel->owner().userLoaded(p.userId()) + : nullptr; + }) | ranges::views::filter([](UserData *user) { + return (user != nullptr); + }) | ranges::to_vector; - AddParticipantsBoxController::Start( - navigation, - channel, - { already.begin(), already.end() }); - }); + AddParticipantsBoxController::Start( + navigation, + channel, + { already.begin(), already.end() }); })); } diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 4bcec5fd9..8b3ca9a23 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/add_contact_box.h" #include "boxes/peers/edit_peer_info_box.h" #include "boxes/peer_list_controllers.h" +#include "boxes/delete_messages_box.h" #include "window/window_adaptive.h" #include "window/window_controller.h" #include "window/main_window.h" @@ -152,7 +153,7 @@ void DateClickHandler::setDate(QDate date) { void DateClickHandler::onClick(ClickContext context) const { const auto my = context.other.value<ClickHandlerContext>(); if (const auto window = my.sessionWindow.get()) { - window->showJumpToDate(_chat, _date); + window->showCalendar(_chat, _date); } } @@ -240,9 +241,7 @@ void SessionNavigation::resolveChannelById( fail(); } }); - }).fail([=](const MTP::Error &error) { - fail(); - }).send(); + }).fail(fail).send(); } void SessionNavigation::showPeerByLinkResolved( @@ -301,9 +300,7 @@ void SessionNavigation::showPeerByLinkResolved( } else { bad(); } - }).fail([=](const MTP::Error &error) { - bad(); - }).send(); + }).fail(bad).send(); }).send(); return; } @@ -1206,81 +1203,136 @@ void SessionController::startOrJoinGroupCall( } } -void SessionController::showJumpToDate(Dialogs::Key chat, QDate requestedDate) { +void SessionController::showCalendar(Dialogs::Key chat, QDate requestedDate) { + const auto history = chat.history(); + if (!history) { + return; + } const auto currentPeerDate = [&] { - if (const auto history = chat.history()) { - if (history->scrollTopItem) { - return history->scrollTopItem->dateTime().date(); - } else if (history->loadedAtTop() - && !history->isEmpty() - && history->peer->migrateFrom()) { - if (const auto migrated = history->owner().historyLoaded(history->peer->migrateFrom())) { - if (migrated->scrollTopItem) { - // We're up in the migrated history. - // So current date is the date of first message here. - return history->blocks.front()->messages.front()->dateTime().date(); - } + if (history->scrollTopItem) { + return history->scrollTopItem->dateTime().date(); + } else if (history->loadedAtTop() + && !history->isEmpty() + && history->peer->migrateFrom()) { + if (const auto migrated = history->owner().historyLoaded(history->peer->migrateFrom())) { + if (migrated->scrollTopItem) { + // We're up in the migrated history. + // So current date is the date of first message here. + return history->blocks.front()->messages.front()->dateTime().date(); } - } else if (const auto item = history->lastMessage()) { - return base::unixtime::parse(item->date()).date(); } + } else if (const auto item = history->lastMessage()) { + return base::unixtime::parse(item->date()).date(); } return QDate(); }(); - const auto maxPeerDate = [](Dialogs::Key chat) { - if (auto history = chat.history()) { - if (const auto channel = history->peer->migrateTo()) { - history = channel->owner().historyLoaded(channel); - } - if (const auto item = history ? history->lastMessage() : nullptr) { - return base::unixtime::parse(item->date()).date(); - } + const auto maxPeerDate = [&] { + const auto check = history->peer->migrateTo() + ? history->owner().historyLoaded(history->peer->migrateTo()) + : history; + if (const auto item = check ? check->lastMessage() : nullptr) { + return base::unixtime::parse(item->date()).date(); } - return QDate::currentDate(); - }; - const auto minPeerDate = [](Dialogs::Key chat) { + return QDate(); + }(); + const auto minPeerDate = [&] { const auto startDate = [] { // Telegram was launched in August 2013 :) return QDate(2013, 8, 1); }; - if (const auto history = chat.history()) { - if (const auto chat = history->peer->migrateFrom()) { - if (const auto history = chat->owner().historyLoaded(chat)) { - if (history->loadedAtTop()) { - if (!history->isEmpty()) { - return history->blocks.front()->messages.front()->dateTime().date(); - } - } else { - return startDate(); + if (const auto chat = history->peer->migrateFrom()) { + if (const auto history = chat->owner().historyLoaded(chat)) { + if (history->loadedAtTop()) { + if (!history->isEmpty()) { + return history->blocks.front()->messages.front()->dateTime().date(); } + } else { + return startDate(); } } - if (history->loadedAtTop()) { - if (!history->isEmpty()) { - return history->blocks.front()->messages.front()->dateTime().date(); - } - return QDate::currentDate(); - } + } + if (history->loadedAtTop()) { + if (!history->isEmpty()) { + return history->blocks.front()->messages.front()->dateTime().date(); + } + return QDate::currentDate(); } return startDate(); - }; + }(); const auto highlighted = !requestedDate.isNull() ? requestedDate : !currentPeerDate.isNull() ? currentPeerDate : QDate::currentDate(); - const auto month = highlighted; - auto callback = [=](const QDate &date) { - session().api().jumpToDate(chat, date); + struct ButtonState { + enum class Type { + None, + Disabled, + Active, + }; + Type type = Type::None; + style::complex_color disabledFg = style::complex_color([] { + auto result = st::attentionBoxButton.textFg->c; + result.setAlpha(result.alpha() / 2); + return result; + }); + style::RoundButton disabled = st::attentionBoxButton; }; - auto box = Box<Ui::CalendarBox>( - month, - highlighted, - std::move(callback)); - box->setMinDate(minPeerDate(chat)); - box->setMaxDate(maxPeerDate(chat)); - box->setBeginningButton(true); - show(std::move(box)); + const auto buttonState = std::make_shared<ButtonState>(); + buttonState->disabled.textFg + = buttonState->disabled.textFgOver + = buttonState->disabledFg.color(); + buttonState->disabled.ripple.color + = buttonState->disabled.textBgOver + = buttonState->disabled.textBg; + const auto selectionChanged = [=]( + not_null<Ui::CalendarBox*> box, + std::optional<int> selected) { + if (!selected.has_value()) { + buttonState->type = ButtonState::Type::None; + return; + } + const auto type = (*selected > 0) + ? ButtonState::Type::Active + : ButtonState::Type::Disabled; + if (buttonState->type == type) { + return; + } + buttonState->type = type; + box->clearButtons(); + box->addButton(tr::lng_cancel(), [=] { + box->toggleSelectionMode(false); + }); + auto text = tr::lng_profile_clear_history(); + const auto button = box->addLeftButton(std::move(text), [=] { + const auto firstDate = box->selectedFirstDate(); + const auto lastDate = box->selectedLastDate(); + if (!firstDate.isNull()) { + auto confirm = Box<DeleteMessagesBox>( + history->peer, + firstDate, + lastDate); + confirm->setDeleteConfirmedCallback(crl::guard(box, [=] { + box->closeBox(); + })); + box->getDelegate()->show(std::move(confirm)); + } + }, (*selected > 0) ? st::attentionBoxButton : buttonState->disabled); + if (!*selected) { + button->setPointerCursor(false); + } + }; + show(Box<Ui::CalendarBox>(Ui::CalendarBoxArgs{ + .month = highlighted, + .highlighted = highlighted, + .callback = [=](const QDate &date) { + session().api().jumpToDate(chat, date); + }, + .minDate = minPeerDate, + .maxDate = maxPeerDate, + .allowsSelection = history->peer->isUser(), + .selectionChanged = selectionChanged, + })); } void SessionController::showPassportForm(const Passport::FormRequest &request) { diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 0bfecbb69..cd5af0249 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -364,7 +364,7 @@ public: } void removeLayerBlackout(); - void showJumpToDate( + void showCalendar( Dialogs::Key chat, QDate requestedDate); diff --git a/Telegram/ThirdParty/libtgvoip b/Telegram/ThirdParty/libtgvoip index 373e41668..2cffda622 160000 --- a/Telegram/ThirdParty/libtgvoip +++ b/Telegram/ThirdParty/libtgvoip @@ -1 +1 @@ -Subproject commit 373e41668b265864f8976b83bb66dd6e9a583915 +Subproject commit 2cffda6222f07cd7d0aa4627a06fa99b05a3956d diff --git a/Telegram/ThirdParty/rlottie b/Telegram/ThirdParty/rlottie index cbd43984e..8c69fc20c 160000 --- a/Telegram/ThirdParty/rlottie +++ b/Telegram/ThirdParty/rlottie @@ -1 +1 @@ -Subproject commit cbd43984ebdf783e94c8303c41385bf82aa36d5b +Subproject commit 8c69fc20cf2e150db304311f1233a4b55a8892d7 diff --git a/Telegram/ThirdParty/tgcalls b/Telegram/ThirdParty/tgcalls index f76a9290f..6dbefa5bf 160000 --- a/Telegram/ThirdParty/tgcalls +++ b/Telegram/ThirdParty/tgcalls @@ -1 +1 @@ -Subproject commit f76a9290fa502a8df473dd872aedf9a553b089cc +Subproject commit 6dbefa5bfc0fdf07eb42af103d8af0f481511c2a diff --git a/Telegram/build/changelog2appdata.py b/Telegram/build/changelog2appdata.py index d97817eaa..4ba34b677 100755 --- a/Telegram/build/changelog2appdata.py +++ b/Telegram/build/changelog2appdata.py @@ -62,7 +62,7 @@ def update_appdata(appdata_path, changelog, max_items=None): def main(): ap = argparse.ArgumentParser("Parse Telegram changelog") ap.add_argument("-c", "--changelog-path", default="changelog.txt") - ap.add_argument("-a", "--appdata-path", default="lib/xdg/telegramdesktop.appdata.xml") + ap.add_argument("-a", "--appdata-path", default="lib/xdg/telegramdesktop.metainfo.xml") ap.add_argument("-n", "--num-releases", type=int, default=None) args = ap.parse_args() update_appdata(args.appdata_path, diff --git a/Telegram/build/docker/build.sh b/Telegram/build/docker/build.sh index 3c8e19e6a..a85e4894f 100755 --- a/Telegram/build/docker/build.sh +++ b/Telegram/build/docker/build.sh @@ -16,7 +16,7 @@ if [ ! -d "$FullScriptPath/../../../../DesktopPrivate" ]; then fi Run () { - scl enable llvm-toolset-7.0 -- scl enable devtoolset-9 -- "$@" + scl enable llvm-toolset-7.0 -- scl enable devtoolset-10 -- "$@" } HomePath="$FullScriptPath/../.." @@ -33,7 +33,7 @@ fi Run ./configure.sh cd $ProjectPath -Run cmake --build . --config Release --target Telegram -- -j8 +Run cmake --build . --config Release --target Telegram cd $ReleasePath echo "$BinaryName build complete!" diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index 3d33100d3..559a61a5e 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -26,16 +26,17 @@ RUN yum -y install centos-release-scl && yum clean all RUN yum -y install git meson ninja-build autoconf automake libtool \ fontconfig-devel freetype-devel libX11-devel at-spi2-core-devel alsa-lib-devel \ - pulseaudio-libs-devel mesa-libGL-devel mesa-libEGL-devel libudev-devel \ - webkitgtk4-devel pkgconfig bison yasm file which xorg-x11-util-macros \ - devtoolset-9-make devtoolset-9-gcc devtoolset-9-gcc-c++ \ - devtoolset-9-binutils llvm-toolset-7.0 llvm-toolset-7.0-clang-devel \ - llvm-toolset-7.0-llvm-devel perl-XML-Parser && yum clean all + pulseaudio-libs-devel mesa-libGL-devel mesa-libEGL-devel gtk3-devel \ + perl-XML-Parser pkgconfig bison yasm file which xorg-x11-util-macros \ + devtoolset-10-make devtoolset-10-gcc devtoolset-10-gcc-c++ \ + devtoolset-10-binutils llvm-toolset-7.0 llvm-toolset-7.0-clang-devel \ + llvm-toolset-7.0-llvm-devel && yum clean all -SHELL [ "bash", "-c", ". /opt/rh/devtoolset-9/enable; exec bash -c \"$@\"", "-s"] +SHELL [ "bash", "-c", ". /opt/rh/devtoolset-10/enable; exec bash -c \"$@\"", "-s"] ENV LibrariesPath /usr/src/Libraries -ENV HFLAGS "-fstack-protector-all -fstack-clash-protection -fPIC -D_FORTIFY_SOURCE=2" +ENV HFLAGS_DEBUG "-fstack-protector-all -fstack-clash-protection -fPIC" +ENV HFLAGS "$HFLAGS_DEBUG -D_FORTIFY_SOURCE=2" WORKDIR $LibrariesPath RUN mkdir /opt/cmake @@ -45,15 +46,15 @@ RUN ln -s /opt/cmake/bin/cmake /usr/local/bin/cmake RUN rm $CMAKE_FILE FROM builder AS patches -RUN git clone $GIT/desktop-app/patches.git && cd patches && git checkout be978cc050 +RUN git clone $GIT/desktop-app/patches.git && cd patches && git checkout b6c29e99da FROM builder AS extra-cmake-modules RUN git clone -b v5.87.0 --depth=1 $GIT/KDE/extra-cmake-modules.git WORKDIR extra-cmake-modules -RUN cmake -B build . -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF -RUN cmake --build build -j$(nproc) +RUN cmake -GNinja -B build . -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF +RUN cmake --build build --parallel RUN DESTDIR="$LibrariesPath/extra-cmake-modules-cache" cmake --install build WORKDIR .. @@ -87,8 +88,8 @@ RUN git clone -b v5.2.5 https://git.tukaani.org/xz.git WORKDIR xz RUN CFLAGS="$HFLAGS" \ - cmake -B build . -DCMAKE_BUILD_TYPE=Release -RUN cmake --build build -j$(nproc) + cmake -GNinja -B build . -DCMAKE_BUILD_TYPE=Release +RUN cmake --build build --parallel RUN DESTDIR="$LibrariesPath/xz-cache" cmake --install build WORKDIR .. @@ -104,7 +105,7 @@ RUN meson build \ --default-library=both \ -Dtests=false -RUN meson compile -C build -j$(nproc) +RUN meson compile -C build RUN DESTDIR="$LibrariesPath/libepoxy-cache" meson install -C build WORKDIR .. @@ -115,13 +116,13 @@ RUN git clone -b 0.4.17 --depth=1 $GIT/libproxy/libproxy.git WORKDIR libproxy RUN git apply ../patches/libproxy.patch -RUN CFLAGS="$HFLAGS" CXXFLAGS="$HFLAGS" cmake -B build . \ +RUN CFLAGS="$HFLAGS" CXXFLAGS="$HFLAGS" cmake -GNinja -B build . \ -DCMAKE_BUILD_TYPE=Release \ -DWITH_DBUS=OFF \ -DWITH_NM=OFF \ -DWITH_NMold=OFF -RUN cmake --build build -j$(nproc) +RUN cmake --build build --parallel RUN DESTDIR="$LibrariesPath/libproxy-cache" cmake --install build WORKDIR .. @@ -131,13 +132,13 @@ FROM builder AS mozjpeg RUN git clone -b v4.0.3 --depth=1 $GIT/mozilla/mozjpeg.git WORKDIR mozjpeg -RUN CFLAGS="$HFLAGS" cmake -B build . \ +RUN CFLAGS="$HFLAGS" cmake -GNinja -B build . \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=/usr/local \ -DWITH_JPEG8=ON \ -DPNG_SUPPORTED=OFF -RUN cmake --build build -j$(nproc) +RUN cmake --build build --parallel RUN DESTDIR="$LibrariesPath/mozjpeg-cache" cmake --install build WORKDIR .. @@ -159,10 +160,10 @@ FROM builder AS rnnoise RUN git clone -b master --depth=1 $GIT/desktop-app/rnnoise WORKDIR rnnoise -RUN CFLAGS="$HFLAGS" cmake -B build . \ +RUN CFLAGS="$HFLAGS" cmake -GNinja -B build . \ -DCMAKE_BUILD_TYPE=Release -RUN cmake --build build -j$(nproc) +RUN cmake --build build --parallel RUN mkdir -p "$LibrariesPath/rnnoise-cache/usr/local/include" RUN cp "include/rnnoise.h" "$LibrariesPath/rnnoise-cache/usr/local/include/" RUN mkdir -p "$LibrariesPath/rnnoise-cache/usr/local/lib" @@ -367,8 +368,8 @@ COPY --from=extra-cmake-modules ${LibrariesPath}/extra-cmake-modules-cache / RUN git clone -b v1.4.0 --depth=1 $GIT/KDE/plasma-wayland-protocols.git WORKDIR plasma-wayland-protocols -RUN cmake -B build . -DCMAKE_BUILD_TYPE=Release -RUN cmake --build build -j$(nproc) +RUN cmake -GNinja -B build . -DCMAKE_BUILD_TYPE=Release +RUN cmake --build build --parallel RUN DESTDIR="$LibrariesPath/plasma-wayland-protocols-cache" cmake --install build WORKDIR .. @@ -396,7 +397,7 @@ RUN meson build \ --buildtype=release \ --default-library=both -RUN meson compile -C build -j$(nproc) +RUN meson compile -C build RUN DESTDIR="$LibrariesPath/drm-cache" meson install -C build WORKDIR .. @@ -437,7 +438,7 @@ RUN meson build \ -Ddocumentation=false \ -Dmoduledir=/usr/lib/vdpau -RUN meson compile -C build -j$(nproc) +RUN meson compile -C build RUN DESTDIR="$LibrariesPath/libvdpau-cache" meson install -C build WORKDIR .. @@ -568,7 +569,7 @@ ADD https://api.github.com/repos/telegramdesktop/openal-soft/git/refs/heads/fix_ RUN git clone -b fix_pulse_default --depth=1 $GIT/telegramdesktop/openal-soft.git WORKDIR openal-soft -RUN CFLAGS="$HFLAGS" CXXFLAGS="$HFLAGS" cmake -B build . \ +RUN CFLAGS="$HFLAGS" CXXFLAGS="$HFLAGS" cmake -GNinja -B build . \ -DCMAKE_BUILD_TYPE=Release \ -DLIBTYPE:STRING=STATIC \ -DALSOFT_EXAMPLES=OFF \ @@ -576,7 +577,7 @@ RUN CFLAGS="$HFLAGS" CXXFLAGS="$HFLAGS" cmake -B build . \ -DALSOFT_UTILS=OFF \ -DALSOFT_CONFIG=OFF -RUN cmake --build build -j$(nproc) +RUN cmake --build build --parallel RUN DESTDIR="$LibrariesPath/openal-cache" cmake --install build WORKDIR .. @@ -616,7 +617,7 @@ RUN meson build \ -Dxkb-config-extra-path=/etc/xkb \ -Dx-locale-root=/usr/share/X11/locale -RUN meson compile -C build -j$(nproc) +RUN meson compile -C build RUN DESTDIR="$LibrariesPath/xkbcommon-cache" meson install -C build WORKDIR .. @@ -759,7 +760,7 @@ RUN [ -z "${QT6}" ] || ./configure -prefix "$QT6_PREFIX" \ -nomake examples \ -nomake tests -RUN [ -z "${QT6}" ] || cmake --build . -j$(nproc) +RUN [ -z "${QT6}" ] || cmake --build . --parallel RUN [ -z "${QT6}" ] || DESTDIR="$LibrariesPath/qt-cache" cmake --install . WORKDIR .. @@ -790,8 +791,8 @@ RUN git clone -b v5.87.0 --depth=1 $GIT/KDE/kwayland.git WORKDIR kwayland RUN [ -z "${QT6}" ] || git apply ../patches/kwayland-qt6.patch -RUN cmake -B build . -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DBUILD_TESTING=OFF -RUN cmake --build build --target KF5WaylandClient -j$(nproc) +RUN cmake -GNinja -B build . -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DBUILD_TESTING=OFF +RUN cmake --build build --target KF5WaylandClient --parallel RUN DESTDIR="$LibrariesPath/kwayland-cache" cmake --install build/src/client WORKDIR .. @@ -801,40 +802,19 @@ FROM patches AS breakpad RUN git clone https://chromium.googlesource.com/breakpad/breakpad.git WORKDIR breakpad -RUN git checkout bc8fb886 +RUN git checkout dfcb7b6799 +RUN git apply ../patches/breakpad.diff RUN git clone https://chromium.googlesource.com/linux-syscall-support.git src/third_party/lss WORKDIR src/third_party/lss -RUN git checkout 8048ece -WORKDIR ${LibrariesPath} +RUN git checkout e1e7b0ad8e -ENV BreakpadCache ${LibrariesPath}/breakpad-cache -RUN git clone https://chromium.googlesource.com/external/gyp.git - -WORKDIR gyp -RUN git checkout 9f2a7bb1 -RUN git apply ../patches/gyp.diff - -WORKDIR ../breakpad +WORKDIR ../../.. RUN ./configure RUN make -j$(nproc) -RUN make DESTDIR="$BreakpadCache" install - -WORKDIR src -RUN rm -rf testing -RUN git clone --depth=1 $GIT/google/googletest.git testing - -WORKDIR tools -RUN sed -i 's/minidump_upload.m/minidump_upload.cc/' linux/tools_linux.gypi -RUN ../../../gyp/gyp --depth=. --generator-output=.. -Goutput_dir=../out tools.gyp --format=cmake - -WORKDIR ../../out/Default -RUN cmake . -RUN cmake --build . --target dump_syms -j$(nproc) -RUN mv dump_syms $BreakpadCache +RUN make DESTDIR="${LibrariesPath}/breakpad-cache" install WORKDIR .. -RUN rm -rf gyp FROM builder AS webrtc @@ -849,18 +829,21 @@ RUN mkdir tg_owt WORKDIR tg_owt RUN git init RUN git remote add origin $GIT/desktop-app/tg_owt.git -RUN git fetch --depth=1 origin d578c760dc6f1ae5f0f3bb5317b0b2ed04b79138 +RUN git fetch --depth=1 origin b02478677baac6d563589f216800ff9cea0fd65c RUN git reset --hard FETCH_HEAD RUN git submodule init RUN git submodule update WORKDIR src/third_party/pipewire -RUN meson build +RUN meson build -Dspa-plugins=disabled WORKDIR ../../.. -RUN CFLAGS="$HFLAGS" CXXFLAGS="$HFLAGS" cmake -B out/Release . \ - -DCMAKE_BUILD_TYPE=Release \ +RUN cmake -G"Ninja Multi-Config" -B out . \ + -DCMAKE_C_FLAGS_RELEASE="-O3 -DNDEBUG $HFLAGS" \ + -DCMAKE_C_FLAGS_DEBUG="-g $HFLAGS_DEBUG" \ + -DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG $HFLAGS" \ + -DCMAKE_CXX_FLAGS_DEBUG="-g $HFLAGS_DEBUG" \ -DTG_OWT_BUILD_AUDIO_BACKENDS=OFF \ -DTG_OWT_SPECIAL_TARGET=linux \ -DTG_OWT_LIBJPEG_INCLUDE_PATH=/usr/local/include \ @@ -868,18 +851,8 @@ RUN CFLAGS="$HFLAGS" CXXFLAGS="$HFLAGS" cmake -B out/Release . \ -DTG_OWT_OPUS_INCLUDE_PATH=/usr/local/include/opus \ -DTG_OWT_FFMPEG_INCLUDE_PATH=/usr/local/include -RUN cmake --build out/Release -- -j$(nproc) - -ENV HFLAGS_DEBUG="-fstack-protector-all -fstack-clash-protection -fPIC" -RUN CFLAGS="$HFLAGS_DEBUG" CXXFLAGS="$HFLAGS_DEBUG" cmake -B out/Debug . \ - -DCMAKE_BUILD_TYPE=Debug \ - -DTG_OWT_SPECIAL_TARGET=linux \ - -DTG_OWT_LIBJPEG_INCLUDE_PATH=/usr/local/include \ - -DTG_OWT_OPENSSL_INCLUDE_PATH=$OPENSSL_PREFIX/include \ - -DTG_OWT_OPUS_INCLUDE_PATH=/usr/local/include/opus \ - -DTG_OWT_FFMPEG_INCLUDE_PATH=/usr/local/include - -RUN cmake --build out/Debug -- -j$(nproc) +RUN cmake --build out --config Release --parallel +RUN cmake --build out --config Debug --parallel FROM builder diff --git a/Telegram/build/docker/centos_env/build.sh b/Telegram/build/docker/centos_env/build.sh index 7eebb7fb7..8fa1e6ae0 100755 --- a/Telegram/build/docker/centos_env/build.sh +++ b/Telegram/build/docker/centos_env/build.sh @@ -1,10 +1,10 @@ -#!/usr/bin/scl enable llvm-toolset-7.0 -- scl enable devtoolset-9 -- bash +#!/usr/bin/scl enable llvm-toolset-7.0 -- scl enable devtoolset-10 -- bash cd Telegram ./configure.sh "$@" if [ -n "$DEBUG" ]; then - cmake --build ../out/Debug -j$(nproc) + cmake --build ../out --config Debug --parallel else - cmake --build ../out/Release -j$(nproc) + cmake --build ../out --config Release --parallel fi diff --git a/Telegram/build/docker/centos_env/run.sh b/Telegram/build/docker/centos_env/run.sh index 4d1451f7a..3df128ded 100755 --- a/Telegram/build/docker/centos_env/run.sh +++ b/Telegram/build/docker/centos_env/run.sh @@ -15,7 +15,7 @@ fi Command="$1" if [ "$Command" == "" ]; then - Command="scl enable llvm-toolset-7.0 -- scl enable devtoolset-9 -- bash" + Command="scl enable llvm-toolset-7.0 -- scl enable devtoolset-10 -- bash" fi docker run -it --rm --cpus=8 --memory=22g -v $HOME/Telegram/DesktopPrivate:/usr/src/DesktopPrivate -v $HOME/Telegram/tdesktop:/usr/src/tdesktop tdesktop:centos_env $Command diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index 2bd682f7d..ef45c3d3e 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -401,7 +401,7 @@ if customRunCommand: stage('patches', """ git clone https://github.com/desktop-app/patches.git cd patches - git checkout 85bce38ec7 + git checkout 4c21dfa0db """) stage('depot_tools', """ @@ -474,7 +474,6 @@ stage('mozjpeg', """ cd mozjpeg win: cmake . ^ - -G "Visual Studio 16 2019" ^ -A %WIN32X64% ^ -DWITH_JPEG8=ON ^ -DPNG_SUPPORTED=OFF @@ -870,7 +869,6 @@ version: 2 cd build win: cmake .. ^ - -G "Visual Studio 16 2019" ^ -A %WIN32X64% ^ -D LIBTYPE:STRING=STATIC ^ -D FORCE_STATIC_VCRT=ON @@ -1154,7 +1152,7 @@ mac: stage('tg_owt', """ git clone https://github.com/desktop-app/tg_owt.git cd tg_owt - git checkout d578c760dc + git checkout b02478677b git submodule init git submodule update src/third_party/libvpx/source/libvpx src/third_party/libyuv win: diff --git a/Telegram/build/version b/Telegram/build/version index 8de5acb38..f8102b229 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 3002005 -AppVersionStrMajor 3.2 -AppVersionStrSmall 3.2.5 -AppVersionStr 3.2.5 +AppVersion 3003000 +AppVersionStrMajor 3.3 +AppVersionStrSmall 3.3 +AppVersionStr 3.3.0 BetaChannel 0 AlphaVersion 0 -AppVersionOriginal 3.2.5 +AppVersionOriginal 3.3 diff --git a/Telegram/cmake/lib_tgcalls.cmake b/Telegram/cmake/lib_tgcalls.cmake index 6a4496246..029954370 100644 --- a/Telegram/cmake/lib_tgcalls.cmake +++ b/Telegram/cmake/lib_tgcalls.cmake @@ -5,14 +5,7 @@ # https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL add_library(lib_tgcalls STATIC) - -if (WIN32) - init_target(lib_tgcalls cxx_std_17) # Small amount of patches required here. -elseif (LINUX) - init_target(lib_tgcalls) # All C++20 on Linux, because otherwise ODR violation. -else() - init_target(lib_tgcalls cxx_std_14) # Can't use std::optional::value on macOS. -endif() +init_target(lib_tgcalls) # Can't use std::optional::value on macOS. add_library(tdesktop::lib_tgcalls ALIAS lib_tgcalls) @@ -62,6 +55,15 @@ PRIVATE VideoCaptureInterfaceImpl.h VideoCapturerInterface.h + v2/InstanceV2Impl.cpp + v2/InstanceV2Impl.h + v2/NativeNetworkingImpl.cpp + v2/NativeNetworkingImpl.h + v2/Signaling.cpp + v2/Signaling.h + v2/SignalingEncryption.cpp + v2/SignalingEncryption.h + # Desktop capturer desktop_capturer/DesktopCaptureSource.h desktop_capturer/DesktopCaptureSource.cpp @@ -103,8 +105,6 @@ PRIVATE platform/darwin/DarwinInterface.mm platform/darwin/DarwinVideoSource.h platform/darwin/DarwinVideoSource.mm - platform/darwin/DesktopCaptureSourceView.h - platform/darwin/DesktopCaptureSourceView.mm platform/darwin/DesktopSharingCapturer.h platform/darwin/DesktopSharingCapturer.mm platform/darwin/GLVideoView.h @@ -145,7 +145,7 @@ PRIVATE platform/darwin/VideoMetalView.mm platform/darwin/VideoMetalViewMac.h platform/darwin/VideoMetalViewMac.mm - + # POSIX # Teleram Desktop @@ -158,10 +158,6 @@ PRIVATE platform/tdesktop/VideoCameraCapturer.cpp platform/tdesktop/VideoCameraCapturer.h - # All - reference/InstanceImplReference.cpp - reference/InstanceImplReference.h - # third-party third-party/json11.cpp third-party/json11.hpp @@ -190,8 +186,6 @@ elseif (APPLE) -fobjc-arc ) remove_target_sources(lib_tgcalls ${tgcalls_loc} - platform/darwin/DesktopCaptureSourceView.h - platform/darwin/DesktopCaptureSourceView.mm platform/darwin/GLVideoView.h platform/darwin/GLVideoView.mm platform/darwin/GLVideoViewMac.h @@ -242,14 +236,7 @@ PRIVATE ) add_library(lib_tgcalls_legacy STATIC) - -if (WIN32) - init_target(lib_tgcalls_legacy cxx_std_17) # Small amount of patches required here. -elseif (LINUX) - init_target(lib_tgcalls_legacy) # All C++20 on Linux, because otherwise ODR violation. -else() - init_target(lib_tgcalls_legacy cxx_std_14) # Can't use std::optional::value on macOS. -endif() +init_target(lib_tgcalls_legacy) add_library(tdesktop::lib_tgcalls_legacy ALIAS lib_tgcalls_legacy) diff --git a/Telegram/cmake/lib_tgvoip.cmake b/Telegram/cmake/lib_tgvoip.cmake index f332b1807..68a64ba60 100644 --- a/Telegram/cmake/lib_tgvoip.cmake +++ b/Telegram/cmake/lib_tgvoip.cmake @@ -18,14 +18,7 @@ endif() if (NOT TGVOIP_FOUND) add_library(lib_tgvoip_bundled STATIC) - - if (WIN32) - init_target(lib_tgvoip_bundled cxx_std_17) # Small amount of patches required here. - elseif (LINUX) - init_target(lib_tgvoip_bundled) # All C++20 on Linux, because otherwise ODR violation. - else() - init_target(lib_tgvoip_bundled cxx_std_14) # Can't use std::optional::value on macOS. - endif() + init_target(lib_tgvoip_bundled) option(LIBTGVOIP_DISABLE_ALSA "Disable libtgvoip's ALSA backend (Linux only)." OFF) option(LIBTGVOIP_DISABLE_PULSEAUDIO "Disable libtgvoip's PulseAudio backend (Linux only)." OFF) diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index ab57ef8ad..071ee65e8 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -80,6 +80,9 @@ PRIVATE media/clip/media_clip_reader.cpp media/clip/media_clip_reader.h + media/player/media_player_dropdown.cpp + media/player/media_player_dropdown.h + passport/ui/passport_details_row.cpp passport/ui/passport_details_row.h passport/ui/passport_form_row.cpp @@ -176,6 +179,8 @@ PRIVATE ui/controls/invite_link_buttons.h ui/controls/invite_link_label.cpp ui/controls/invite_link_label.h + ui/controls/send_as_button.cpp + ui/controls/send_as_button.h ui/controls/send_button.cpp ui/controls/send_button.h ui/controls/who_read_context_action.cpp diff --git a/Telegram/configure.py b/Telegram/configure.py index 5f4349132..22de084a5 100644 --- a/Telegram/configure.py +++ b/Telegram/configure.py @@ -59,9 +59,4 @@ if officialTarget != '': arguments.append('-DTDESKTOP_API_HASH=' + apiHashMatch.group(1)) if arch != '': arguments.append(arch) - finish(run_cmake.run(scriptName, arguments)) -elif 'linux' in sys.platform: - debugCode = run_cmake.run(scriptName, arguments, "Debug") - finish(debugCode if debugCode else run_cmake.run(scriptName, arguments, "Release")) -else: - finish(run_cmake.run(scriptName, arguments)) +finish(run_cmake.run(scriptName, arguments)) diff --git a/Telegram/lib_base b/Telegram/lib_base index 29ecc271c..3b0decd74 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 29ecc271c04e71eecf88ac2f9ed1c110955e0be9 +Subproject commit 3b0decd74b8aa63756e1e51b1d9ad32e8737e3f2 diff --git a/Telegram/lib_lottie b/Telegram/lib_lottie index c75d91f75..ad7fce76f 160000 --- a/Telegram/lib_lottie +++ b/Telegram/lib_lottie @@ -1 +1 @@ -Subproject commit c75d91f75ef87077f07ea6f7087343274b3eb5ff +Subproject commit ad7fce76f3b403471a296c928bae67cd36b8b2cf diff --git a/Telegram/lib_rpl b/Telegram/lib_rpl index df721be3f..94a42b775 160000 --- a/Telegram/lib_rpl +++ b/Telegram/lib_rpl @@ -1 +1 @@ -Subproject commit df721be3fa14a27dfc230d2e3c42bb1a7c9d0617 +Subproject commit 94a42b775ab4e46e5edeb88d8ed6c06f9e869c61 diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 9b56cbd4a..c6b55d9c3 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 9b56cbd4abb4ed9c797c9d73083a00cb85051fe6 +Subproject commit c6b55d9c3769baa312b62e9333cfe2acd018deb7 diff --git a/Telegram/lib_webrtc b/Telegram/lib_webrtc index 9d617f174..04cc1ff4a 160000 --- a/Telegram/lib_webrtc +++ b/Telegram/lib_webrtc @@ -1 +1 @@ -Subproject commit 9d617f17463d07ff45515800c8fd865939144413 +Subproject commit 04cc1ff4a6fdade551e26441488ac0d0a208e96c diff --git a/Telegram/lib_webview b/Telegram/lib_webview index d533a9c40..8be9c0ff2 160000 --- a/Telegram/lib_webview +++ b/Telegram/lib_webview @@ -1 +1 @@ -Subproject commit d533a9c404c45b350bd056ee86f4efab72f02ff7 +Subproject commit 8be9c0ff274569cbb1bc9dd4ea9ed9089c37ca93 diff --git a/changelog.txt b/changelog.txt index 698a2d8aa..cecace3f0 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,27 @@ +3.3 (07.12.21) + +- Content creators can restrict the ability to save media and forward messages from their groups and channels. +- Clear messages in one-on-one chats from a specific day or date range. +- Comment as one of your channels in public groups and channel comments. +- When you request to join a community and its admin or bot-admin contacts you with a message, you will see which chat they are from at the top of the chat. +- Bot-admins can now ask users to complete tasks before they are allowed to join - like accepting community rules, passing a test, or making a donation to the content creators. + +3.2.8 beta (01.12.21) + +- Fix crash in opening shared media with another user. + +3.2.7 beta (30.11.21) + +- Active sessions list redesign. +- Fix disappearing emoji selector button. +- Fix a crash in archived stickers loading. +- Fix a crash in calls to old Telegram versions. + +3.2.6 beta (26.11.21) + +- Try out the new audio player with playlist shuffle and repeat. +- Give a custom name to your desktop session to distinguish it in the sessions list. + 3.2.5 (16.11.21) [Windows, macOS] - Fix connecting in case bad characters appear in device name on Windows. diff --git a/cmake b/cmake index e2f26c05d..8caa6fcbf 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit e2f26c05ded48968b493835546fb06f9df499fad +Subproject commit 8caa6fcbf9963c1d7ef19f3fb34766757d4cf631 diff --git a/docs/building-win-x64.md b/docs/building-win-x64.md index e855e609a..c20f873ce 100644 --- a/docs/building-win-x64.md +++ b/docs/building-win-x64.md @@ -8,9 +8,11 @@ ## Prepare folder +The build is done in **Visual Studio 2022** with **10.0.22000.0** SDK version. + Choose an empty folder for the future build, for example **D:\\TBuild**. It will be named ***BuildPath*** in the rest of this document. Create two folders there, ***BuildPath*\\ThirdParty** and ***BuildPath*\\Libraries**. -All commands (if not stated otherwise) will be launched from **x64 Native Tools Command Prompt for VS 2019.bat** (should be in **Start Menu > Visual Studio 2019** menu folder). Pay attention not to use any other Command Prompt. +All commands (if not stated otherwise) will be launched from **x64 Native Tools Command Prompt for VS 2022.bat** (should be in **Start Menu > Visual Studio 2022** menu folder). Pay attention not to use any other Command Prompt. ### Obtain your API credentials @@ -24,18 +26,18 @@ You will require **api_id** and **api_hash** to access the Telegram API servers. * Download **MSYS2** installer from [http://www.msys2.org/](http://www.msys2.org/) and install to ***BuildPath*\\ThirdParty\\msys64** * Download **jom** archive from [http://download.qt.io/official_releases/jom/jom.zip](http://download.qt.io/official_releases/jom/jom.zip) and unpack to ***BuildPath*\\ThirdParty\\jom** * Download **Python 3.9** installer from [https://www.python.org/downloads/](https://www.python.org/downloads/) and install to ***BuildPath*\\ThirdParty\\Python39** -* Download **CMake** installer from [https://cmake.org/download/](https://cmake.org/download/) and install to ***BuildPath*\\ThirdParty\\cmake** +* Download **CMake 3.21 or later** installer from [https://cmake.org/download/](https://cmake.org/download/) and install to ***BuildPath*\\ThirdParty\\cmake** * Download **Ninja** executable from [https://github.com/ninja-build/ninja/releases/download/v1.7.2/ninja-win.zip](https://github.com/ninja-build/ninja/releases/download/v1.7.2/ninja-win.zip) and unpack to ***BuildPath*\\ThirdParty\\Ninja** * Download **Git** installer from [https://git-scm.com/download/win](https://git-scm.com/download/win) and install it. * Download **NuGet** executable from [https://dist.nuget.org/win-x86-commandline/latest/nuget.exe](https://dist.nuget.org/win-x86-commandline/latest/nuget.exe) and put to ***BuildPath*\\ThirdParty\\NuGet** -Open **x64 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** and run +Open **x64 Native Tools Command Prompt for VS 2022.bat**, go to ***BuildPath*** and run python -m pip install pywin32 ## Clone source code and prepare libraries -Open **x64 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** and run +Open **x64 Native Tools Command Prompt for VS 2022.bat**, go to ***BuildPath*** and run SET PATH=%cd%\ThirdParty\NuGet;%cd%\ThirdParty\Python39;%PATH% @@ -50,7 +52,7 @@ Go to ***BuildPath*\\kotatogram-desktop\\Telegram** and run (using [your **api_i If you want to build with crash reporter, use `-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF` instead of `-D DESKTOP_APP_DISABLE_CRASH_REPORTS=ON`. -* Open ***BuildPath*\\kotatogram-desktop\\out64\\Telegram.sln** in Visual Studio 2019 +* Open ***BuildPath*\\kotatogram-desktop\\out64\\Telegram.sln** in Visual Studio 2022 * Select Telegram project and press Build > Build Telegram (Debug and Release configurations) * The result Kotatogram.exe will be located in **D:\TBuild\kotatogram-desktop\out64\Debug** (and **Release**) diff --git a/docs/building-win.md b/docs/building-win.md index f568833b0..cc3045a77 100644 --- a/docs/building-win.md +++ b/docs/building-win.md @@ -8,9 +8,11 @@ ## Prepare folder +The build is done in **Visual Studio 2022** with **10.0.22000.0** SDK version. + Choose an empty folder for the future build, for example **D:\\TBuild**. It will be named ***BuildPath*** in the rest of this document. Create two folders there, ***BuildPath*\\ThirdParty** and ***BuildPath*\\Libraries**. -All commands (if not stated otherwise) will be launched from **x86 Native Tools Command Prompt for VS 2019.bat** (should be in **Start Menu > Visual Studio 2019** menu folder). Pay attention not to use any other Command Prompt. +All commands (if not stated otherwise) will be launched from **x86 Native Tools Command Prompt for VS 2022.bat** (should be in **Start Menu > Visual Studio 2022** menu folder). Pay attention not to use any other Command Prompt. ### Obtain your API credentials @@ -24,18 +26,18 @@ You will require **api_id** and **api_hash** to access the Telegram API servers. * Download **MSYS2** installer from [http://www.msys2.org/](http://www.msys2.org/) and install to ***BuildPath*\\ThirdParty\\msys64** * Download **jom** archive from [http://download.qt.io/official_releases/jom/jom.zip](http://download.qt.io/official_releases/jom/jom.zip) and unpack to ***BuildPath*\\ThirdParty\\jom** * Download **Python 3.9** installer from [https://www.python.org/downloads/](https://www.python.org/downloads/) and install to ***BuildPath*\\ThirdParty\\Python39** -* Download **CMake** installer from [https://cmake.org/download/](https://cmake.org/download/) and install to ***BuildPath*\\ThirdParty\\cmake** +* Download **CMake 3.21 or later** installer from [https://cmake.org/download/](https://cmake.org/download/) and install to ***BuildPath*\\ThirdParty\\cmake** * Download **Ninja** executable from [https://github.com/ninja-build/ninja/releases/download/v1.7.2/ninja-win.zip](https://github.com/ninja-build/ninja/releases/download/v1.7.2/ninja-win.zip) and unpack to ***BuildPath*\\ThirdParty\\Ninja** * Download **Git** installer from [https://git-scm.com/download/win](https://git-scm.com/download/win) and install it. * Download **NuGet** executable from [https://dist.nuget.org/win-x86-commandline/latest/nuget.exe](https://dist.nuget.org/win-x86-commandline/latest/nuget.exe) and put to ***BuildPath*\\ThirdParty\\NuGet** -Open **x86 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** and run +Open **x86 Native Tools Command Prompt for VS 2022.bat**, go to ***BuildPath*** and run python -m pip install pywin32 ## Clone source code and prepare libraries -Open **x86 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** and run +Open **x86 Native Tools Command Prompt for VS 2022.bat**, go to ***BuildPath*** and run SET PATH=%cd%\ThirdParty\NuGet;%cd%\ThirdParty\Python39;%PATH% @@ -50,7 +52,7 @@ Go to ***BuildPath*\\kotatogram-desktop\\Telegram** and run (using [your **api_i If you want to build with crash reporter, use `-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF` instead of `-D DESKTOP_APP_DISABLE_CRASH_REPORTS=ON`. -* Open ***BuildPath*\\kotatogram-desktop\\out\\Telegram.sln** in Visual Studio 2019 +* Open ***BuildPath*\\kotatogram-desktop\\out\\Telegram.sln** in Visual Studio 2022 * Select Telegram project and press Build > Build Telegram (Debug and Release configurations) * The result Kotatogram.exe will be located in **D:\TBuild\kotatogram-desktop\out\Debug** (and **Release**) diff --git a/lib/xdg/kotatogramdesktop.appdata.xml.in b/lib/xdg/kotatogramdesktop.metainfo.xml.in similarity index 100% rename from lib/xdg/kotatogramdesktop.appdata.xml.in rename to lib/xdg/kotatogramdesktop.metainfo.xml.in diff --git a/lib/xdg/telegramdesktop.appdata.xml.in b/lib/xdg/telegramdesktop.metainfo.xml.in similarity index 100% rename from lib/xdg/telegramdesktop.appdata.xml.in rename to lib/xdg/telegramdesktop.metainfo.xml.in diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 8d79fabfc..80272f789 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -68,10 +68,6 @@ plugs: default-provider: gtk-common-themes slots: - tdesktop-dbus: - interface: dbus - bus: session - name: org.telegram.desktop tdesktop-mpris: interface: mpris name: tdesktop @@ -89,11 +85,14 @@ parts: plugin: cmake source: . source-type: git - parse-info: [usr/share/metainfo/kotatogram-desktop_kotatogram-desktop.appdata.xml] + parse-info: [usr/share/metainfo/kotatogram-desktop_kotatogram-desktop.metainfo.xml] build-environment: + - CC: gcc-10 + - CXX: g++-10 - LD_LIBRARY_PATH: $SNAPCRAFT_STAGE/usr/lib/$SNAPCRAFT_ARCH_TRIPLET:$LD_LIBRARY_PATH build-packages: - clang + - g++-10 - python - libasound2-dev - libglib2.0-dev @@ -101,7 +100,6 @@ parts: - libopus-dev - libpulse-dev - libssl-dev - - libwebkit2gtk-4.0-dev - libxcb1-dev - libxcb-keysyms1-dev - libxcb-record0-dev @@ -140,6 +138,8 @@ parts: rm -rf "$SNAPCRAFT_PART_INSTALL/usr/share/icons" stage: - -./usr/lib/$SNAPCRAFT_ARCH_TRIPLET/libjpeg.so.8.2.2 + prime: + - -./lib/systemd after: - desktop-qt - extra-cmake-modules @@ -465,7 +465,7 @@ parts: webrtc: source: https://github.com/desktop-app/tg_owt.git source-depth: 1 - source-commit: d578c760dc6f1ae5f0f3bb5317b0b2ed04b79138 + source-commit: b02478677baac6d563589f216800ff9cea0fd65c plugin: cmake build-packages: - yasm