diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index d98bfc07d..7586a3241 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -31,8 +31,9 @@ jobs: CMAKE_VER: "3.16.3" UPLOAD_ARTIFACT: "false" ONLY_CACHE: "false" - MANUAL_CACHING: "2" + MANUAL_CACHING: "6" DOC_PATH: "docs/building-cmake.md" + AUTO_CACHING: "1" steps: - name: Get repository name. @@ -43,7 +44,20 @@ jobs: with: submodules: recursive + - name: Disable man for further package installs. + run: | + cfgFile="/etc/dpkg/dpkg.cfg.d/no_man" + sudo touch $cfgFile + p() { + sudo echo "path-exclude=/usr/share/$1/*" >> $cfgFile + } + + p man + p locale + p doc + - name: First set up. + shell: bash run: | cd .. mv $REPO_NAME temp @@ -55,13 +69,13 @@ jobs: sudo apt-get install software-properties-common -y && \ sudo apt-get install git libexif-dev liblzma-dev libz-dev libssl-dev \ libgtk2.0-dev libice-dev libsm-dev libicu-dev libdrm-dev dh-autoreconf \ - autoconf automake build-essential libass-dev libfreetype6-dev \ + autoconf automake build-essential libxml2-dev libass-dev libfreetype6-dev \ libgpac-dev libsdl1.2-dev libtheora-dev libtool libva-dev libvdpau-dev \ libvorbis-dev libenchant-dev libxcb1-dev libxcb-image0-dev libxcb-shm0-dev \ libxcb-xfixes0-dev libxcb-keysyms1-dev libxcb-icccm4-dev libatspi2.0-dev \ libxcb-render-util0-dev libxcb-util0-dev libxcb-xkb-dev libxrender-dev \ - libasound-dev libpulse-dev libxcb-sync0-dev libxcb-randr0-dev bison \ - libx11-xcb-dev libffi-dev libncurses5-dev pkg-config texi2html yasm \ + libasound-dev libpulse-dev libxcb-sync0-dev libxcb-randr0-dev libegl1-mesa-dev \ + libx11-xcb-dev libffi-dev libncurses5-dev pkg-config texi2html bison yasm \ zlib1g-dev xutils-dev python-xcbgen chrpath gperf wget -y --force-yes && \ sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y && \ sudo apt-get update && \ @@ -75,6 +89,10 @@ jobs: gcc --version > CACHE_KEY.txt echo $MANUAL_CACHING >> CACHE_KEY.txt + if [ "$AUTO_CACHING" == "1" ]; then + thisFile=$REPO_NAME/.github/workflows/linux.yml + echo `md5sum $thisFile | cut -c -32` >> CACHE_KEY.txt + fi md5cache=$(md5sum CACHE_KEY.txt | cut -c -32) echo ::set-env name=CACHE_KEY::$md5cache @@ -111,16 +129,6 @@ jobs: cmake --version - - name: Zlib. - run: | - cd $LibrariesPath - - git clone $GIT/madler/zlib.git - cd zlib - ./configure - make -j$(nproc) - sudo make install - - name: Opus cache. id: cache-opus uses: actions/cache@v1 @@ -132,9 +140,8 @@ jobs: run: | cd $LibrariesPath - git clone $GIT/xiph/opus + git clone -b v1.3 --depth=1 $GIT/xiph/opus cd opus - git checkout v1.3 ./autogen.sh ./configure make -j$(nproc) @@ -152,17 +159,20 @@ jobs: ./autogen.sh --enable-static make -j$(nproc) sudo make install + cd .. + rm -rf libva - name: Libvdpau. run: | cd $LibrariesPath - git clone git://anongit.freedesktop.org/vdpau/libvdpau + git clone -b libvdpau-1.2 --depth=1 https://gitlab.freedesktop.org/vdpau/libvdpau.git cd libvdpau - git checkout libvdpau-1.2 ./autogen.sh --enable-static make -j$(nproc) sudo make install + cd .. + rm -rf libvdpau - name: FFmpeg cache. id: cache-ffmpeg @@ -278,6 +288,8 @@ jobs: make -j$(nproc) sudo make install + cd .. + rm -rf ffmpeg - name: FFmpeg install. run: | cd $LibrariesPath @@ -304,18 +316,20 @@ jobs: ./configure make -j$(nproc) sudo make install + cd .. + rm -rf portaudio - name: OpenAL Soft. run: | cd $LibrariesPath - git clone $GIT/kcat/openal-soft.git - cd openal-soft - git checkout openal-soft-1.19.1 - cd build + git clone -b openal-soft-1.19.1 --depth=1 $GIT/kcat/openal-soft.git + cd openal-soft/build cmake -D LIBTYPE:STRING=STATIC .. make -j$(nproc) sudo make install + cd - + rm -rf openal-soft - name: OpenSSL cache. id: cache-openssl @@ -328,12 +342,14 @@ jobs: run: | cd $LibrariesPath - git clone $GIT/openssl/openssl openssl_$OPENSSL_VER - cd openssl_$OPENSSL_VER - git checkout OpenSSL_1_1_1-stable + git clone -b OpenSSL_${OPENSSL_VER}-stable --depth=1 \ + $GIT/openssl/openssl openssl_${OPENSSL_VER} + cd openssl_${OPENSSL_VER} ./config --prefix=$LibrariesPath/openssl-cache make -j$(nproc) sudo make install + cd .. + rm -rf openssl_${OPENSSL_VER} - name: OpenSSL install. run: | cd $LibrariesPath @@ -344,12 +360,25 @@ jobs: run: | cd $LibrariesPath - git clone $GIT/xkbcommon/libxkbcommon.git + git clone -b xkbcommon-0.8.4 --depth=1 $GIT/xkbcommon/libxkbcommon.git cd libxkbcommon - git checkout xkbcommon-0.8.4 ./autogen.sh make -j$(nproc) sudo make install + cd .. + rm -rf libxkbcommon + + - name: Libwayland. + run: | + cd $LibrariesPath + + git clone -b 1.16 https://gitlab.freedesktop.org/wayland/wayland + cd wayland + ./autogen.sh --enable-static --disable-documentation + make -j$(nproc) + sudo make install + cd .. + rm -rf wayland - name: Qt 5.12.5 cache. id: cache-qt @@ -362,14 +391,12 @@ jobs: run: | cd $LibrariesPath - git clone git://code.qt.io/qt/qt5.git qt_$QT - cd qt_$QT - perl init-repository --module-subset=qtbase,qtimageformats - git checkout v5.12.5 - git submodule update qtbase - git submodule update qtimageformats + git clone -b v5.12.5 --depth=1 git://code.qt.io/qt/qt5.git qt_${QT} + cd qt_${QT} + perl init-repository --module-subset=qtbase,qtwayland,qtimageformats,qtsvg + git submodule update qtbase qtwayland qtimageformats qtsvg cd qtbase - git apply ../../patches/qtbase_$QT.diff + git apply ../../patches/qtbase_${QT}.diff cd src/plugins/platforminputcontexts git clone $GIT/desktop-app/fcitx.git git clone $GIT/desktop-app/hime.git @@ -389,10 +416,9 @@ jobs: -qt-xcb \ -system-freetype \ -fontconfig \ - -no-opengl \ - -no-glib \ -no-gtk \ -static \ + -dbus-runtime \ -openssl-linked \ -I "$OPENSSL_PREFIX/include" OPENSSL_LIBS="$OPENSSL_PREFIX/lib/libssl.a $OPENSSL_PREFIX/lib/libcrypto.a -ldl -lpthread" \ -nomake examples \ @@ -400,6 +426,8 @@ jobs: make -j$(nproc) sudo make install + cd .. + rm -rf qt_${QT} - name: Qt 5.12.5 install. run: | cd $LibrariesPath @@ -450,6 +478,8 @@ jobs: make -j$(nproc) dump_syms mv dump_syms $BreakpadCache/ + cd .. + rm -rf gyp breakpad - name: Breakpad install. run: | cd $LibrariesPath diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 6ca226e38..53beba810 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -35,6 +35,8 @@ jobs: ONLY_CACHE: "false" MANUAL_CACHING: "2" DOC_PATH: "docs/building-xcode.md" + AUTO_CACHING: "1" + steps: - name: Get repository name. run: echo ::set-env name=REPO_NAME::${GITHUB_REPOSITORY##*/} @@ -61,6 +63,10 @@ jobs: echo $MIN_MAC >> CACHE_KEY.txt echo $PREFIX >> CACHE_KEY.txt echo $MANUAL_CACHING >> CACHE_KEY.txt + if [ "$AUTO_CACHING" == "1" ]; then + thisFile=$REPO_NAME/.github/workflows/mac.yml + echo `md5 -q $thisFile` >> CACHE_KEY.txt + fi echo ::set-env name=CACHE_KEY::`md5 -q CACHE_KEY.txt` echo ::add-path::$PWD/Libraries/depot_tools diff --git a/.github/workflows/snap.yml b/.github/workflows/snap.yml index 6949d72ec..ec73a14d8 100644 --- a/.github/workflows/snap.yml +++ b/.github/workflows/snap.yml @@ -47,10 +47,6 @@ jobs: md5cache=$(md5sum CMAKE_CACHE_KEY.txt | cut -c -32) echo ::set-env name=CMAKE_CACHE_KEY::$md5cache - awk -v RS="" -v ORS="\n\n" '/^ enchant:/' snap/snapcraft.yaml > ENCHANT_CACHE_KEY.txt - md5cache=$(md5sum ENCHANT_CACHE_KEY.txt | cut -c -32) - echo ::set-env name=ENCHANT_CACHE_KEY::$md5cache - - name: CMake cache. id: cache-cmake uses: actions/cache@v1 @@ -62,17 +58,6 @@ jobs: if: steps.cache-cmake.outputs.cache-hit != 'true' run: snapcraft build --destructive-mode cmake - - name: Enchant cache. - id: cache-enchant - uses: actions/cache@v1 - with: - path: parts/enchant - key: ${{ runner.OS }}-enchant-${{ env.CACHE_KEY }}-${{ env.ENCHANT_CACHE_KEY }} - - - name: Enchant build. - if: steps.cache-enchant.outputs.cache-hit != 'true' - run: snapcraft build --destructive-mode enchant - - name: Kotatogram Desktop snap build. if: env.ONLY_CACHE == 'false' run: snapcraft --destructive-mode @@ -95,5 +80,5 @@ jobs: - name: Remove unneeded directories for cache. run: | - rm -rf parts/{cmake,enchant}/{build,src,ubuntu} - rm -rf parts/{cmake,enchant}/state/{stage,prime} + rm -rf parts/cmake/{build,src,ubuntu} + rm -rf parts/cmake/state/{stage,prime} diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 28d457ba0..1cb303ffa 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -30,6 +30,8 @@ jobs: ONLY_CACHE: "false" MANUAL_CACHING: "2" DOC_PATH: "docs/building-msvc.md" + AUTO_CACHING: "1" + steps: - name: Get repository name. shell: bash @@ -69,6 +71,10 @@ jobs: - name: Generate cache key. shell: bash run: | + if [ "$AUTO_CACHING" == "1" ]; then + thisFile=$REPO_NAME/.github/workflows/win.yml + echo `md5sum $thisFile | awk '{ print $1 }'` >> CACHE_KEY.txt + fi echo ::set-env name=CACHE_KEY::`md5sum CACHE_KEY.txt | awk '{ print $1 }'` - name: Choco installs. diff --git a/.gitmodules b/.gitmodules index c5f0782d7..1ef8dddfd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -64,3 +64,6 @@ [submodule "Telegram/ThirdParty/libdbusmenu-qt"] path = Telegram/ThirdParty/libdbusmenu-qt url = https://github.com/desktop-app/libdbusmenu-qt.git +[submodule "Telegram/ThirdParty/hunspell"] + path = Telegram/ThirdParty/hunspell + url = https://github.com/hunspell/hunspell diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index c08709ddf..4ae453987 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -66,18 +66,7 @@ generate_numbers(Telegram ${res_loc}/numbers.txt) set_target_properties(Telegram PROPERTIES AUTOMOC ON AUTORCC ON) -if (DESKTOP_APP_USE_PACKAGED) - set(CMAKE_THREAD_PREFER_PTHREAD TRUE) - find_package(Threads) - - target_link_libraries(Telegram - PRIVATE - ${CMAKE_DL_LIBS} - Threads::Threads - ) -endif() - -if (LINUX AND NOT TDESKTOP_DISABLE_DBUS_INTEGRATION) +if (LINUX AND NOT DESKTOP_APP_DISABLE_DBUS_INTEGRATION) target_link_libraries(Telegram PRIVATE desktop-app::external_statusnotifieritem @@ -85,6 +74,10 @@ if (LINUX AND NOT TDESKTOP_DISABLE_DBUS_INTEGRATION) ) endif() +if (add_hunspell_library) + target_link_libraries(Telegram PRIVATE desktop-app::external_hunspell) +endif() + target_link_libraries(Telegram PRIVATE tdesktop::lib_mtproto @@ -124,6 +117,17 @@ if (NOT HAVE_LONG_ATOMIC_WITHOUT_LIB) target_link_libraries(Telegram PRIVATE atomic) endif() +if (DESKTOP_APP_USE_PACKAGED) + set(CMAKE_THREAD_PREFER_PTHREAD TRUE) + find_package(Threads) + + target_link_libraries(Telegram + PRIVATE + ${CMAKE_DL_LIBS} + Threads::Threads + ) +endif() + target_precompile_headers(Telegram PRIVATE ${src_loc}/stdafx.h) nice_target_sources(Telegram ${src_loc} PRIVATE @@ -185,6 +189,8 @@ PRIVATE boxes/connection_box.h boxes/create_poll_box.cpp boxes/create_poll_box.h + boxes/dictionaries_manager.cpp + boxes/dictionaries_manager.h boxes/download_path_box.cpp boxes/download_path_box.h boxes/edit_caption_box.cpp @@ -261,6 +267,8 @@ PRIVATE chat_helpers/gifs_list_widget.h chat_helpers/message_field.cpp chat_helpers/message_field.h + chat_helpers/spellchecker_common.cpp + chat_helpers/spellchecker_common.h chat_helpers/stickers.cpp chat_helpers/stickers.h chat_helpers/stickers_emoji_pack.cpp @@ -341,6 +349,8 @@ PRIVATE data/data_game.h data/data_groups.cpp data/data_groups.h + data/data_histories.cpp + data/data_histories.h data/data_location.cpp data/data_location.h data/data_media_rotation.cpp @@ -854,6 +864,8 @@ PRIVATE storage/serialize_common.h storage/serialize_document.cpp storage/serialize_document.h + storage/storage_cloud_blob.cpp + storage/storage_cloud_blob.h storage/storage_facade.cpp storage/storage_facade.h # storage/storage_feed_messages.cpp @@ -1084,8 +1096,8 @@ endif() if (build_macstore) set(bundle_identifier "io.github.kotatogram") - set(bundle_entitlements "Telegram Desktop.entitlements") - set(output_name "Kotatogram Desktop") + set(bundle_entitlements "Telegram Lite.entitlements") + set(output_name "Kotatogram Lite") set_target_properties(Telegram PROPERTIES XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${libs_loc}/breakpad/src/client/mac/build/Release ) @@ -1129,7 +1141,7 @@ set_target_properties(Telegram PROPERTIES ) set(entitlement_sources "${CMAKE_CURRENT_SOURCE_DIR}/Telegram/Telegram.entitlements" - "${CMAKE_CURRENT_SOURCE_DIR}/Telegram/Telegram Desktop.entitlements" + "${CMAKE_CURRENT_SOURCE_DIR}/Telegram/Telegram Lite.entitlements" ) target_sources(Telegram PRIVATE ${entitlement_sources}) source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/Telegram PREFIX Resources FILES ${entitlement_sources}) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index dd036799f..8b58fe5c7 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -422,6 +422,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_spellchecker" = "Spell checker"; "lng_settings_system_spellchecker" = "Use system spell checker"; +"lng_settings_custom_spellchecker" = "Use spell checker"; +"lng_settings_auto_download_dictionaries" = "Automatic dictionaries download"; +"lng_settings_manage_dictionaries" = "Manage dictionaries"; +"lng_settings_manage_enabled_dictionary" = "Dictionary is enabled"; +"lng_settings_manage_remove_dictionary" = "Remove Dictionary"; "lng_backgrounds_header" = "Choose your new chat background"; "lng_theme_sure_keep" = "Keep this theme?"; @@ -1369,7 +1374,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_user_action_upload_file" = "{user} is sending a file"; "lng_unread_bar#one" = "{count} unread message"; "lng_unread_bar#other" = "{count} unread messages"; -//"lng_unread_bar_some" = "Unread messages"; +"lng_unread_bar_some" = "Unread messages"; "lng_maps_point" = "Location"; "lng_save_photo" = "Save image"; @@ -1581,6 +1586,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_mediaview_video_loading" = "Loading - {percent}"; "lng_mediaview_playback_speed" = "Playback speed"; "lng_mediaview_playback_speed_normal" = "Normal"; +"lng_mediaview_rotate_video" = "Rotate video"; "lng_theme_preview_title" = "Theme Preview"; "lng_theme_preview_generating" = "Generating color theme preview..."; @@ -1610,6 +1616,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_formatting_link_url" = "URL"; "lng_formatting_link_create" = "Create"; +"lng_spellchecker_submenu" = "Spelling"; "lng_spellchecker_add" = "Add to Dictionary"; "lng_spellchecker_remove" = "Remove from Dictionary"; "lng_spellchecker_ignore" = "Ignore word"; diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 1cc443d70..474eae12b 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -9,7 +9,7 @@ + Version="1.9.19.0" /> Telegram Desktop Telegram FZ-LLC diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index 45476bfd5..082826664 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" // UserData::name #include "data/data_session.h" #include "data/data_file_origin.h" +#include "data/data_histories.h" #include "history/history.h" #include "history/history_message.h" // NewMessageFlags. #include "chat_helpers/message_field.h" // ConvertTextTagsToEntities. @@ -110,23 +111,30 @@ void SendExistingMedia( auto failHandler = std::make_shared>(); auto performRequest = [=] { - const auto usedFileReference = media->fileReference(); - history->sendRequestId = api->request(MTPmessages_SendMedia( - MTP_flags(sendFlags), - peer->input, - MTP_int(replyTo), - inputMedia(), - MTP_string(captionText), - MTP_long(randomId), - MTPReplyMarkup(), - sentEntities, - MTP_int(message.action.options.scheduled) - )).done([=](const MTPUpdates &result) { - api->applyUpdates(result, randomId); - }).fail([=](const RPCError &error) { - (*failHandler)(error, usedFileReference); - }).afterRequest(history->sendRequestId - ).send(); + auto &histories = history->owner().histories(); + const auto requestType = Data::Histories::RequestType::Send; + histories.sendRequest(history, requestType, [=](Fn finish) { + const auto usedFileReference = media->fileReference(); + history->sendRequestId = api->request(MTPmessages_SendMedia( + MTP_flags(sendFlags), + peer->input, + MTP_int(replyTo), + inputMedia(), + MTP_string(captionText), + MTP_long(randomId), + MTPReplyMarkup(), + sentEntities, + MTP_int(message.action.options.scheduled) + )).done([=](const MTPUpdates &result) { + api->applyUpdates(result, randomId); + finish(); + }).fail([=](const RPCError &error) { + (*failHandler)(error, usedFileReference); + finish(); + }).afterRequest(history->sendRequestId + ).send(); + return history->sendRequestId; + }); }; *failHandler = [=](const RPCError &error, QByteArray usedFileReference) { if (error.code() == 400 @@ -169,7 +177,7 @@ void SendExistingDocument( if (document->sticker()) { if (const auto main = App::main()) { main->incrementSticker(document); - document->session().data().notifyRecentStickersUpdated(); + document->owner().notifyRecentStickersUpdated(); } } } diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 121dccca2..a0feaa15b 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_chat.h" #include "data/data_user.h" #include "data/data_cloud_themes.h" +#include "data/data_histories.h" #include "dialogs/dialogs_key.h" #include "core/core_cloud_password.h" #include "core/application.h" @@ -343,7 +344,7 @@ void ApiWrap::proxyPromotionDone(const MTPhelp_ProxyData &proxy) { const auto peer = _session->data().peer(peerId); _session->data().setProxyPromoted(peer); if (const auto history = _session->data().historyLoaded(peer)) { - requestDialogEntry(history); + history->owner().histories().requestDialogEntry(history); } }); } @@ -1011,162 +1012,6 @@ rpl::producer ApiWrap::dialogsLoadBlockedByDate() const { return _dialogsLoadBlockedByDate.value(); } -void ApiWrap::requestDialogEntry(not_null folder) { - if (_dialogFolderRequests.contains(folder)) { - return; - } - _dialogFolderRequests.emplace(folder); - - auto peers = QVector( - 1, - MTP_inputDialogPeerFolder(MTP_int(folder->id()))); - request(MTPmessages_GetPeerDialogs( - MTP_vector(std::move(peers)) - )).done([=](const MTPmessages_PeerDialogs &result) { - applyPeerDialogs(result); - _dialogFolderRequests.remove(folder); - }).fail([=](const RPCError &error) { - _dialogFolderRequests.remove(folder); - }).send(); -} - -void ApiWrap::requestDialogEntry( - not_null history, - Fn callback) { - const auto i = _dialogRequests.find(history); - if (i != end(_dialogRequests)) { - if (callback) { - i->second.push_back(std::move(callback)); - } - return; - } - - const auto [j, ok] = _dialogRequestsPending.try_emplace(history); - if (callback) { - j->second.push_back(std::move(callback)); - } - if (!ok) { - return; - } - if (_dialogRequestsPending.size() > 1) { - return; - } - Core::App().postponeCall(crl::guard(_session, [=] { - sendDialogRequests(); - })); -} - -void ApiWrap::sendDialogRequests() { - if (_dialogRequestsPending.empty()) { - return; - } - auto histories = std::vector>(); - ranges::transform( - _dialogRequestsPending, - ranges::back_inserter(histories), - [](const auto &pair) { return pair.first; }); - auto peers = QVector(); - const auto dialogPeer = [](not_null history) { - return MTP_inputDialogPeer(history->peer->input); - }; - ranges::transform( - histories, - ranges::back_inserter(peers), - dialogPeer); - for (auto &[history, callbacks] : base::take(_dialogRequestsPending)) { - _dialogRequests.emplace(history, std::move(callbacks)); - } - - const auto finalize = [=] { - for (const auto history : histories) { - dialogEntryApplied(history); - history->updateChatListExistence(); - } - }; - request(MTPmessages_GetPeerDialogs( - MTP_vector(std::move(peers)) - )).done([=](const MTPmessages_PeerDialogs &result) { - applyPeerDialogs(result); - finalize(); - }).fail([=](const RPCError &error) { - finalize(); - }).send(); -} - -void ApiWrap::dialogEntryApplied(not_null history) { - history->dialogEntryApplied(); - if (const auto callbacks = _dialogRequestsPending.take(history)) { - for (const auto &callback : *callbacks) { - callback(); - } - } - if (const auto callbacks = _dialogRequests.take(history)) { - for (const auto &callback : *callbacks) { - callback(); - } - } -} - -void ApiWrap::applyPeerDialogs(const MTPmessages_PeerDialogs &dialogs) { - Expects(dialogs.type() == mtpc_messages_peerDialogs); - - const auto &data = dialogs.c_messages_peerDialogs(); - _session->data().processUsers(data.vusers()); - _session->data().processChats(data.vchats()); - _session->data().processMessages(data.vmessages(), NewMessageType::Last); - for (const auto &dialog : data.vdialogs().v) { - dialog.match([&](const MTPDdialog &data) { - if (const auto peerId = peerFromMTP(data.vpeer())) { - _session->data().history(peerId)->applyDialog(nullptr, data); - } - }, [&](const MTPDdialogFolder &data) { - const auto folder = _session->data().processFolder(data.vfolder()); - folder->applyDialog(data); - }); - } - _session->data().sendHistoryChangeNotifications(); -} - -void ApiWrap::changeDialogUnreadMark( - not_null history, - bool unread) { - history->setUnreadMark(unread); - - using Flag = MTPmessages_MarkDialogUnread::Flag; - request(MTPmessages_MarkDialogUnread( - MTP_flags(unread ? Flag::f_unread : Flag(0)), - MTP_inputDialogPeer(history->peer->input) - )).send(); -} - -void ApiWrap::requestFakeChatListMessage( - not_null history) { - if (_fakeChatListRequests.contains(history)) { - return; - } - - _fakeChatListRequests.emplace(history); - request(MTPmessages_GetHistory( - history->peer->input, - MTP_int(0), // offset_id - MTP_int(0), // offset_date - MTP_int(0), // add_offset - MTP_int(2), // limit - MTP_int(0), // max_id - MTP_int(0), // min_id - MTP_int(0) - )).done([=](const MTPmessages_Messages &result) { - _fakeChatListRequests.erase(history); - history->setFakeChatListMessageFrom(result); - }).fail([=](const RPCError &error) { - _fakeChatListRequests.erase(history); - history->setFakeChatListMessageFrom(MTP_messages_messages( - MTP_vector(0), - MTP_vector(0), - MTP_vector(0))); - }).send(); -} - void ApiWrap::requestWallPaper( const QString &slug, Fn done, @@ -1786,7 +1631,7 @@ void ApiWrap::requestSelfParticipant(not_null channel) { history->checkLocalMessages(); history->owner().sendHistoryChangeNotifications(); } else { - requestDialogEntry(history); + history->owner().histories().requestDialogEntry(history); } } }; @@ -2422,44 +2267,7 @@ int ApiWrap::OnlineTillFromStatus( } void ApiWrap::clearHistory(not_null peer, bool revoke) { - auto deleteTillId = MsgId(0); - if (const auto history = _session->data().historyLoaded(peer)) { - while (history->lastMessageKnown()) { - const auto last = history->lastMessage(); - if (!last) { - // History is empty. - return; - } else if (!IsServerMsgId(last->id)) { - // Destroy client-side message locally. - last->destroy(); - } else { - break; - } - } - if (!history->lastMessageKnown()) { - requestDialogEntry(history, [=] { - Expects(history->lastMessageKnown()); - - clearHistory(peer, revoke); - }); - return; - } - deleteTillId = history->lastMessage()->id; - history->clear(History::ClearType::ClearHistory); - } - if (const auto channel = peer->asChannel()) { - if (const auto migrated = peer->migrateFrom()) { - clearHistory(migrated, revoke); - } - if (IsServerMsgId(deleteTillId)) { - request(MTPchannels_DeleteHistory( - channel->inputChannel, - MTP_int(deleteTillId) - )).send(); - } - } else { - deleteHistory(peer, true, revoke); - } + deleteHistory(peer, true, revoke); } void ApiWrap::deleteConversation(not_null peer, bool revoke) { @@ -2473,30 +2281,69 @@ void ApiWrap::deleteConversation(not_null peer, bool revoke) { }).fail([=](const RPCError &error) { deleteHistory(peer, false, revoke); }).send(); - } else if (const auto channel = peer->asChannel()) { - channel->ptsWaitingForShortPoll(-1); - leaveChannel(channel); } else { deleteHistory(peer, false, revoke); } - _session->data().deleteConversationLocally(peer); } -void ApiWrap::deleteHistory(not_null peer, bool justClear, bool revoke) { - using Flag = MTPmessages_DeleteHistory::Flag; - const auto flags = Flag(0) - | (justClear ? Flag::f_just_clear : Flag(0)) - | ((peer->isUser() && revoke) ? Flag::f_revoke : Flag(0)); - request(MTPmessages_DeleteHistory( - MTP_flags(flags), - peer->input, - MTP_int(0) - )).done([=](const MTPmessages_AffectedHistory &result) { - const auto offset = applyAffectedHistory(peer, result); - if (offset > 0) { - deleteHistory(peer, justClear, revoke); +void ApiWrap::deleteHistory( + not_null peer, + bool justClear, + bool revoke) { + auto deleteTillId = MsgId(0); + const auto history = _session->data().history(peer); + if (justClear) { + // In case of clear history we need to know the last server message. + while (history->lastMessageKnown()) { + const auto last = history->lastMessage(); + if (!last) { + // History is empty. + return; + } else if (!IsServerMsgId(last->id)) { + // Destroy client-side message locally. + last->destroy(); + } else { + break; + } } - }).send(); + if (!history->lastMessageKnown()) { + history->owner().histories().requestDialogEntry(history, [=] { + Expects(history->lastMessageKnown()); + + deleteHistory(peer, justClear, revoke); + }); + return; + } + deleteTillId = history->lastMessage()->id; + } + if (const auto channel = peer->asChannel()) { + if (!justClear) { + channel->ptsWaitingForShortPoll(-1); + leaveChannel(channel); + } else { + if (const auto migrated = peer->migrateFrom()) { + deleteHistory(migrated, justClear, revoke); + } + if (IsServerMsgId(deleteTillId)) { + history->owner().histories().deleteAllMessages( + history, + deleteTillId, + justClear, + revoke); + } + } + } else { + history->owner().histories().deleteAllMessages( + history, + deleteTillId, + justClear, + revoke); + } + if (!justClear) { + _session->data().deleteConversationLocally(peer); + } else if (history) { + history->clear(History::ClearType::ClearHistory); + } } int ApiWrap::applyAffectedHistory( @@ -2528,30 +2375,6 @@ void ApiWrap::applyAffectedMessages( App::main()->ptsUpdateAndApply(data.vpts().v, data.vpts_count().v); } -void ApiWrap::deleteMessages( - not_null peer, - const QVector &ids, - bool revoke) { - const auto done = [=](const MTPmessages_AffectedMessages & result) { - applyAffectedMessages(peer, result); - if (const auto history = peer->owner().historyLoaded(peer)) { - history->requestChatListMessage(); - } - }; - if (const auto channel = peer->asChannel()) { - request(MTPchannels_DeleteMessages( - channel->inputChannel, - MTP_vector(ids) - )).done(done).send(); - } else { - using Flag = MTPmessages_DeleteMessages::Flag; - request(MTPmessages_DeleteMessages( - MTP_flags(revoke ? Flag::f_revoke : Flag(0)), - MTP_vector(ids) - )).done(done).send(); - } -} - void ApiWrap::saveDraftsToCloud() { for (auto i = _draftsSaveRequestIds.begin(), e = _draftsSaveRequestIds.end(); i != e; ++i) { if (i->second) continue; // sent already @@ -4057,12 +3880,12 @@ void ApiWrap::requestSharedMedia( SharedMediaType type, MsgId messageId, SliceType slice) { - auto key = std::make_tuple(peer, type, messageId, slice); + const auto key = std::make_tuple(peer, type, messageId, slice); if (_sharedMediaRequests.contains(key)) { return; } - auto prepared = Api::PrepareSearchRequest( + const auto prepared = Api::PrepareSearchRequest( peer, type, QString(), @@ -4072,17 +3895,23 @@ void ApiWrap::requestSharedMedia( return; } - auto requestId = request( - std::move(*prepared) - ).done([this, peer, type, messageId, slice]( - const MTPmessages_Messages &result) { - auto key = std::make_tuple(peer, type, messageId, slice); - _sharedMediaRequests.remove(key); - sharedMediaDone(peer, type, messageId, slice, result); - }).fail([this, key](const RPCError &error) { - _sharedMediaRequests.remove(key); - }).send(); - _sharedMediaRequests.emplace(key, requestId); + const auto history = session().data().history(peer); + auto &histories = history->owner().histories(); + const auto requestType = Data::Histories::RequestType::History; + histories.sendRequest(history, requestType, [=](Fn finish) { + return request( + std::move(*prepared) + ).done([=](const MTPmessages_Messages &result) { + const auto key = std::make_tuple(peer, type, messageId, slice); + _sharedMediaRequests.remove(key); + sharedMediaDone(peer, type, messageId, slice, result); + finish(); + }).fail([=](const RPCError &error) { + _sharedMediaRequests.remove(key); + finish(); + }).send(); + }); + _sharedMediaRequests.emplace(key); } void ApiWrap::sharedMediaDone( @@ -4432,7 +4261,7 @@ void ApiWrap::userPhotosDone( //} void ApiWrap::sendAction(const SendAction &action) { - readServerHistory(action.history); + session().data().histories().readInbox(action.history); action.history->getReadyFor(ShowAtTheEndMsgId); _sendActions.fire_copy(action); } @@ -4443,6 +4272,8 @@ void ApiWrap::forwardMessages( FnMut &&successCallback) { Expects(!items.empty()); + auto &histories = session().data().histories(); + struct SharedCallback { int requestsLeft = 0; FnMut callback; @@ -4459,7 +4290,7 @@ void ApiWrap::forwardMessages( const auto history = action.history; const auto peer = history->peer; - readServerHistory(history); + histories.readInbox(history); const auto channelPost = peer->isChannel() && !peer->isMegagroup(); const auto silentPost = action.options.silent @@ -4491,7 +4322,7 @@ void ApiWrap::forwardMessages( auto currentGroupId = items.front()->groupId(); auto ids = QVector(); auto randomIds = QVector(); - auto localIds = std::unique_ptr>(); + auto localIds = std::shared_ptr>(); const auto sendAccumulated = [&] { if (shared) { @@ -4501,30 +4332,36 @@ void ApiWrap::forwardMessages( | (currentGroupId == MessageGroupId() ? MTPmessages_ForwardMessages::Flag(0) : MTPmessages_ForwardMessages::Flag::f_grouped); - history->sendRequestId = request(MTPmessages_ForwardMessages( - MTP_flags(finalFlags), - forwardFrom->input, - MTP_vector(ids), - MTP_vector(randomIds), - peer->input, - MTP_int(action.options.scheduled) - )).done([=, callback = std::move(successCallback)]( - const MTPUpdates &updates) { - applyUpdates(updates); - if (shared && !--shared->requestsLeft) { - shared->callback(); - } - }).fail([=, ids = std::move(localIds)](const RPCError &error) { - if (ids) { - for (const auto &[randomId, itemId] : *ids) { - sendMessageFail(error, peer, randomId, itemId); + const auto requestType = Data::Histories::RequestType::Send; + histories.sendRequest(history, requestType, [=](Fn finish) { + history->sendRequestId = request(MTPmessages_ForwardMessages( + MTP_flags(finalFlags), + forwardFrom->input, + MTP_vector(ids), + MTP_vector(randomIds), + peer->input, + MTP_int(action.options.scheduled) + )).done([=]( + const MTPUpdates &updates) { + applyUpdates(updates); + if (shared && !--shared->requestsLeft) { + shared->callback(); } - } else { - sendMessageFail(error, peer); - } - }).afterRequest( - history->sendRequestId - ).send(); + finish(); + }).fail([=, ids = localIds](const RPCError &error) { + if (ids) { + for (const auto &[randomId, itemId] : *ids) { + sendMessageFail(error, peer, randomId, itemId); + } + } else { + sendMessageFail(error, peer); + } + finish(); + }).afterRequest( + history->sendRequestId + ).send(); + return history->sendRequestId; + }); ids.resize(0); randomIds.resize(0); @@ -4557,7 +4394,7 @@ void ApiWrap::forwardMessages( message); _session->data().registerMessageRandomId(randomId, newId); if (!localIds) { - localIds = std::make_unique>(); + localIds = std::make_shared>(); } localIds->emplace(randomId, newId); } @@ -4972,6 +4809,9 @@ void ApiWrap::sendMessage(MessageToSend &&message) { HistoryItem *lastMessage = nullptr; + auto &histories = history->owner().histories(); + const auto requestType = Data::Histories::RequestType::Send; + while (TextUtilities::CutPart(sending, left, MaxMessageSize)) { auto newId = FullMsgId( peerToChannel(peer->id), @@ -5062,27 +4902,32 @@ void ApiWrap::sendMessage(MessageToSend &&message) { MTPVector()), clientFlags, NewMessageType::Unread); - history->sendRequestId = request(MTPmessages_SendMessage( - MTP_flags(sendFlags), - peer->input, - MTP_int(action.replyTo), - msgText, - MTP_long(randomId), - MTPReplyMarkup(), - sentEntities, - MTP_int(action.options.scheduled) - )).done([=](const MTPUpdates &result) { - applyUpdates(result, randomId); - history->clearSentDraftText(QString()); - }).fail([=](const RPCError &error) { - if (error.type() == qstr("MESSAGE_EMPTY")) { - lastMessage->destroy(); - } else { - sendMessageFail(error, peer, randomId, newId); - } - history->clearSentDraftText(QString()); - }).afterRequest(history->sendRequestId - ).send(); + histories.sendRequest(history, requestType, [=](Fn finish) { + history->sendRequestId = request(MTPmessages_SendMessage( + MTP_flags(sendFlags), + peer->input, + MTP_int(action.replyTo), + msgText, + MTP_long(randomId), + MTPReplyMarkup(), + sentEntities, + MTP_int(action.options.scheduled) + )).done([=](const MTPUpdates &result) { + applyUpdates(result, randomId); + history->clearSentDraftText(QString()); + finish(); + }).fail([=](const RPCError &error) { + if (error.type() == qstr("MESSAGE_EMPTY")) { + lastMessage->destroy(); + } else { + sendMessageFail(error, peer, randomId, newId); + } + history->clearSentDraftText(QString()); + finish(); + }).afterRequest(history->sendRequestId + ).send(); + return history->sendRequestId; + }); } if (const auto main = App::main()) { @@ -5188,23 +5033,29 @@ void ApiWrap::sendInlineResult( history->clearCloudDraft(); history->setSentDraftText(QString()); - history->sendRequestId = request(MTPmessages_SendInlineBotResult( - MTP_flags(sendFlags), - peer->input, - MTP_int(action.replyTo), - MTP_long(randomId), - MTP_long(data->getQueryId()), - MTP_string(data->getId()), - MTP_int(action.options.scheduled) - )).done([=](const MTPUpdates &result) { - applyUpdates(result, randomId); - history->clearSentDraftText(QString()); - }).fail([=](const RPCError &error) { - sendMessageFail(error, peer, randomId, newId); - history->clearSentDraftText(QString()); - }).afterRequest(history->sendRequestId - ).send(); - + auto &histories = history->owner().histories(); + const auto requestType = Data::Histories::RequestType::Send; + histories.sendRequest(history, requestType, [=](Fn finish) { + history->sendRequestId = request(MTPmessages_SendInlineBotResult( + MTP_flags(sendFlags), + peer->input, + MTP_int(action.replyTo), + MTP_long(randomId), + MTP_long(data->getQueryId()), + MTP_string(data->getId()), + MTP_int(action.options.scheduled) + )).done([=](const MTPUpdates &result) { + applyUpdates(result, randomId); + history->clearSentDraftText(QString()); + finish(); + }).fail([=](const RPCError &error) { + sendMessageFail(error, peer, randomId, newId); + history->clearSentDraftText(QString()); + finish(); + }).afterRequest(history->sendRequestId + ).send(); + return history->sendRequestId; + }); if (const auto main = App::main()) { main->finishForwarding(action); } @@ -5323,25 +5174,32 @@ void ApiWrap::sendMediaWithRandomId( ? MTPmessages_SendMedia::Flag::f_schedule_date : MTPmessages_SendMedia::Flag(0)); - const auto peer = history->peer; - const auto itemId = item->fullId(); - history->sendRequestId = request(MTPmessages_SendMedia( - MTP_flags(flags), - peer->input, - MTP_int(replyTo), - media, - MTP_string(caption.text), - MTP_long(randomId), - MTPReplyMarkup(), - sentEntities, - MTP_int(options.scheduled) - )).done([=](const MTPUpdates &result) { - applyUpdates(result); - }).fail([=](const RPCError &error) { - sendMessageFail(error, peer, randomId, itemId); - }).afterRequest( - history->sendRequestId - ).send(); + auto &histories = history->owner().histories(); + const auto requestType = Data::Histories::RequestType::Send; + histories.sendRequest(history, requestType, [=](Fn finish) { + const auto peer = history->peer; + const auto itemId = item->fullId(); + history->sendRequestId = request(MTPmessages_SendMedia( + MTP_flags(flags), + peer->input, + MTP_int(replyTo), + media, + MTP_string(caption.text), + MTP_long(randomId), + MTPReplyMarkup(), + sentEntities, + MTP_int(options.scheduled) + )).done([=](const MTPUpdates &result) { + applyUpdates(result); + finish(); + }).fail([=](const RPCError &error) { + sendMessageFail(error, peer, randomId, itemId); + finish(); + }).afterRequest( + history->sendRequestId + ).send(); + return history->sendRequestId; + }); } void ApiWrap::sendAlbumWithUploaded( @@ -5419,27 +5277,34 @@ void ApiWrap::sendAlbumIfReady(not_null album) { | (album->options.scheduled ? MTPmessages_SendMultiMedia::Flag::f_schedule_date : MTPmessages_SendMultiMedia::Flag(0)); - const auto peer = history->peer; - history->sendRequestId = request(MTPmessages_SendMultiMedia( - MTP_flags(flags), - peer->input, - MTP_int(replyTo), - MTP_vector(medias), - MTP_int(album->options.scheduled) - )).done([=](const MTPUpdates &result) { - _sendingAlbums.remove(groupId); - applyUpdates(result); - }).fail([=](const RPCError &error) { - if (const auto album = _sendingAlbums.take(groupId)) { - for (const auto &item : (*album)->items) { - sendMessageFail(error, peer, item.randomId, item.msgId); + auto &histories = history->owner().histories(); + const auto requestType = Data::Histories::RequestType::Send; + histories.sendRequest(history, requestType, [=](Fn finish) { + const auto peer = history->peer; + history->sendRequestId = request(MTPmessages_SendMultiMedia( + MTP_flags(flags), + peer->input, + MTP_int(replyTo), + MTP_vector(medias), + MTP_int(album->options.scheduled) + )).done([=](const MTPUpdates &result) { + _sendingAlbums.remove(groupId); + applyUpdates(result); + finish(); + }).fail([=](const RPCError &error) { + if (const auto album = _sendingAlbums.take(groupId)) { + for (const auto &item : (*album)->items) { + sendMessageFail(error, peer, item.randomId, item.msgId); + } + } else { + sendMessageFail(error, peer); } - } else { - sendMessageFail(error, peer); - } - }).afterRequest( - history->sendRequestId - ).send(); + finish(); + }).afterRequest( + history->sendRequestId + ).send(); + return history->sendRequestId; + }); } FileLoadTo ApiWrap::fileLoadTaskOptions(const SendAction &action) const { @@ -5836,8 +5701,8 @@ Api::SensitiveContent &ApiWrap::sensitiveContent() { void ApiWrap::createPoll( const PollData &data, const SendAction &action, - FnMut done, - FnMut fail) { + Fn done, + Fn fail) { sendAction(action); const auto history = action.history; @@ -5870,27 +5735,34 @@ void ApiWrap::createPoll( correct.push_back(MTP_bytes(answer.option)); } } - const auto replyTo = action.replyTo; - history->sendRequestId = request(MTPmessages_SendMedia( - MTP_flags(sendFlags), - peer->input, - MTP_int(replyTo), - MTP_inputMediaPoll( - MTP_flags(inputFlags), - PollDataToMTP(&data), - MTP_vector(correct)), - MTP_string(), - MTP_long(rand_value()), - MTPReplyMarkup(), - MTPVector(), - MTP_int(action.options.scheduled) - )).done([=, done = std::move(done)](const MTPUpdates &result) mutable { - applyUpdates(result); - done(); - }).fail([=, fail = std::move(fail)](const RPCError &error) mutable { - fail(error); - }).afterRequest(history->sendRequestId - ).send(); + auto &histories = history->owner().histories(); + const auto requestType = Data::Histories::RequestType::Send; + histories.sendRequest(history, requestType, [=](Fn finish) { + const auto replyTo = action.replyTo; + history->sendRequestId = request(MTPmessages_SendMedia( + MTP_flags(sendFlags), + peer->input, + MTP_int(replyTo), + MTP_inputMediaPoll( + MTP_flags(inputFlags), + PollDataToMTP(&data), + MTP_vector(correct)), + MTP_string(), + MTP_long(rand_value()), + MTPReplyMarkup(), + MTPVector(), + MTP_int(action.options.scheduled) + )).done([=](const MTPUpdates &result) mutable { + applyUpdates(result); + done(); + finish(); + }).fail([=](const RPCError &error) mutable { + fail(error); + finish(); + }).afterRequest(history->sendRequestId + ).send(); + return history->sendRequestId; + }); } void ApiWrap::sendPollVotes( @@ -6000,43 +5872,6 @@ void ApiWrap::reloadPollResults(not_null item) { _pollReloadRequestIds.emplace(itemId, requestId); } -void ApiWrap::readServerHistory(not_null history) { - if (history->unreadCount()) { - readServerHistoryForce(history); - } - if (history->unreadMark()) { - changeDialogUnreadMark(history, false); - } -} - -void ApiWrap::readServerHistoryForce(not_null history) { - const auto peer = history->peer; - const auto upTo = history->readInbox(); - if (!upTo) { - return; - } - - if (const auto channel = peer->asChannel()) { - if (!channel->amIn()) { - return; // no read request for channels that I didn't join - } else if (const auto migrateFrom = channel->migrateFrom()) { - if (const auto migrated = _session->data().historyLoaded(migrateFrom)) { - readServerHistory(migrated); - } - } - } - - if (_readRequests.contains(peer)) { - const auto i = _readRequestsPending.find(peer); - if (i == _readRequestsPending.cend()) { - _readRequestsPending.emplace(peer, upTo); - } else if (i->second < upTo) { - i->second = upTo; - } - } else { - sendReadRequest(peer, upTo); - } -} // // #feed //void ApiWrap::readFeed( // not_null feed, @@ -6090,39 +5925,3 @@ void ApiWrap::readServerHistoryForce(not_null history) { // _feedReadTimer.callOnce(delay); // } //} - -void ApiWrap::sendReadRequest(not_null peer, MsgId upTo) { - const auto requestId = [&] { - const auto finished = [=] { - _readRequests.remove(peer); - if (const auto next = _readRequestsPending.take(peer)) { - sendReadRequest(peer, *next); - } else if (const auto history - = _session->data().historyLoaded(peer)) { - if (!history->unreadCountKnown()) { - requestDialogEntry(history); - } - } - }; - if (const auto channel = peer->asChannel()) { - return request(MTPchannels_ReadHistory( - channel->inputChannel, - MTP_int(upTo) - )).done([=](const MTPBool &result) { - finished(); - }).fail([=](const RPCError &error) { - finished(); - }).send(); - } - return request(MTPmessages_ReadHistory( - peer->input, - MTP_int(upTo) - )).done([=](const MTPmessages_AffectedMessages &result) { - applyAffectedMessages(peer, result); - finished(); - }).fail([=](const RPCError &error) { - finished(); - }).send(); - }(); - _readRequests.emplace(peer, requestId, upTo); -} diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 0a57a619b..c48ef2b01 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -141,6 +141,9 @@ public: void applyUpdates( const MTPUpdates &updates, uint64 sentMessageRandomId = 0); + int applyAffectedHistory( + not_null peer, + const MTPmessages_AffectedHistory &result); void registerModifyRequest(const QString &key, mtpRequestId requestId); void clearModifyRequest(const QString &key); @@ -170,18 +173,10 @@ public: rpl::producer dialogsLoadMayBlockByDate() const; rpl::producer dialogsLoadBlockedByDate() const; - void requestDialogEntry(not_null folder); - void requestDialogEntry( - not_null history, - Fn callback = nullptr); - void dialogEntryApplied(not_null history); //void applyFeedSources(const MTPDchannels_feedSources &data); // #feed //void setFeedChannels( // not_null feed, // const std::vector> &channels); - void changeDialogUnreadMark(not_null history, bool unread); - //void changeDialogUnreadMark(not_null feed, bool unread); // #feed - void requestFakeChatListMessage(not_null history); void requestWallPaper( const QString &slug, @@ -305,10 +300,6 @@ public: void clearHistory(not_null peer, bool revoke); void deleteConversation(not_null peer, bool revoke); - void deleteMessages( - not_null peer, - const QVector &ids, - bool revoke); base::Observable &fullPeerUpdated() { return _fullPeerUpdated; @@ -389,11 +380,12 @@ public: const QString &lastName, const SendAction &action); void shareContact(not_null user, const SendAction &action); - void readServerHistory(not_null history); - void readServerHistoryForce(not_null history); //void readFeed( // #feed // not_null feed, // Data::MessagePosition position); + void applyAffectedMessages( + not_null peer, + const MTPmessages_AffectedMessages &result); void sendVoiceMessage( QByteArray result, @@ -475,8 +467,8 @@ public: void createPoll( const PollData &data, const SendAction &action, - FnMut done, - FnMut fail); + Fn done, + Fn fail); void sendPollVotes( FullMsgId itemId, const std::vector &options); @@ -531,7 +523,6 @@ private: QVector collectMessageIds(const MessageDataRequests &requests); MessageDataRequests *messageDataRequests(ChannelData *channel, bool onlyExisting = false); - void applyPeerDialogs(const MTPmessages_PeerDialogs &dialogs); void gotChatFull( not_null peer, @@ -623,14 +614,7 @@ private: not_null peer, bool justClear, bool revoke); - void sendReadRequest(not_null peer, MsgId upTo); - int applyAffectedHistory( - not_null peer, - const MTPmessages_AffectedHistory &result); void applyAffectedMessages(const MTPmessages_AffectedMessages &result); - void applyAffectedMessages( - not_null peer, - const MTPmessages_AffectedMessages &result); void deleteAllFromUserSend( not_null channel, @@ -685,8 +669,6 @@ private: not_null channel); void migrateFail(not_null peer, const RPCError &error); - void sendDialogRequests(); - not_null _session; base::flat_map _modifyRequests; @@ -756,22 +738,14 @@ private: mtpRequestId _contactsRequestId = 0; mtpRequestId _contactsStatusesRequestId = 0; - base::flat_set> _dialogFolderRequests; - base::flat_map< - not_null, - std::vector>> _dialogRequests; - base::flat_map< - not_null, - std::vector>> _dialogRequestsPending; - base::flat_set> _fakeChatListRequests; base::flat_map, mtpRequestId> _unreadMentionsRequests; - base::flat_map, SharedMediaType, MsgId, - SliceType>, mtpRequestId> _sharedMediaRequests; + SliceType>> _sharedMediaRequests; base::flat_map, mtpRequestId> _userPhotosRequests; @@ -800,18 +774,6 @@ private: rpl::event_stream _sendActions; - struct ReadRequest { - ReadRequest(mtpRequestId requestId, MsgId upTo) - : requestId(requestId) - , upTo(upTo) { - } - - mtpRequestId requestId = 0; - MsgId upTo = 0; - }; - base::flat_map, ReadRequest> _readRequests; - base::flat_map, MsgId> _readRequestsPending; - std::unique_ptr _fileLoader; base::flat_map> _sendingAlbums; diff --git a/Telegram/SourceFiles/boxes/add_contact_box.cpp b/Telegram/SourceFiles/boxes/add_contact_box.cpp index 06e10fc37..f303c4b93 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.cpp +++ b/Telegram/SourceFiles/boxes/add_contact_box.cpp @@ -494,6 +494,7 @@ void GroupInfoBox::prepare() { _description->setInstantReplaces(Ui::InstantReplaces::Default()); _description->setInstantReplacesEnabled( _navigation->session().settings().replaceEmojiValue()); + _description->setSubmitSettings(_navigation->session().settings().sendSubmitWay()); connect(_description, &Ui::InputField::resized, [=] { descriptionResized(); }); connect(_description, &Ui::InputField::submitted, [=] { submit(); }); diff --git a/Telegram/SourceFiles/boxes/background_preview_box.cpp b/Telegram/SourceFiles/boxes/background_preview_box.cpp index e72908a01..475c04009 100644 --- a/Telegram/SourceFiles/boxes/background_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/background_preview_box.cpp @@ -293,8 +293,7 @@ AdminLog::OwnedItem GenerateTextItem( const auto clientFlags = MTPDmessage_ClientFlag::f_fake_history_item; const auto replyTo = 0; const auto viaBotId = 0; - const auto item = history->owner().makeMessage( - history, + const auto item = history->makeMessage( ++id, flags, clientFlags, diff --git a/Telegram/SourceFiles/boxes/confirm_box.cpp b/Telegram/SourceFiles/boxes/confirm_box.cpp index c8b6efcd2..c580d3716 100644 --- a/Telegram/SourceFiles/boxes/confirm_box.cpp +++ b/Telegram/SourceFiles/boxes/confirm_box.cpp @@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_chat.h" #include "data/data_user.h" #include "data/data_file_origin.h" +#include "data/data_histories.h" #include "base/unixtime.h" #include "main/main_session.h" #include "observer_peer.h" @@ -785,7 +786,9 @@ void DeleteMessagesBox::deleteAndClear() { _deleteConfirmedCallback(); } - base::flat_map, QVector> idsByPeer; + auto remove = std::vector>(); + remove.reserve(_ids.size()); + base::flat_map, QVector> idsByPeer; base::flat_map, QVector> scheduledIdsByPeer; for (const auto itemId : _ids) { if (const auto item = _session->data().message(itemId)) { @@ -801,21 +804,15 @@ void DeleteMessagesBox::deleteAndClear() { } continue; } - const auto wasOnServer = IsServerMsgId(item->id); - const auto wasLast = (history->lastMessage() == item); - const auto wasInChats = (history->chatListMessage() == item); - item->destroy(); - - if (wasOnServer) { - idsByPeer[history->peer].push_back(MTP_int(itemId.msg)); - } else if (wasLast || wasInChats) { - history->requestChatListMessage(); + remove.push_back(item); + if (IsServerMsgId(item->id)) { + idsByPeer[history].push_back(MTP_int(itemId.msg)); } } } - for (const auto &[peer, ids] : idsByPeer) { - peer->session().api().deleteMessages(peer, ids, revoke); + for (const auto &[history, ids] : idsByPeer) { + history->owner().histories().deleteMessages(history, ids, revoke); } for (const auto &[peer, ids] : scheduledIdsByPeer) { peer->session().api().request(MTPmessages_DeleteScheduledMessages( @@ -826,6 +823,17 @@ void DeleteMessagesBox::deleteAndClear() { }).send(); } + for (const auto item : remove) { + const auto history = item->history(); + const auto wasLast = (history->lastMessage() == item); + const auto wasInChats = (history->chatListMessage() == item); + item->destroy(); + + if (wasLast || wasInChats) { + history->requestChatListMessage(); + } + } + const auto session = _session; Ui::hideLayer(); session->data().sendHistoryChangeNotifications(); diff --git a/Telegram/SourceFiles/boxes/create_poll_box.cpp b/Telegram/SourceFiles/boxes/create_poll_box.cpp index 040f5fd09..510b91bea 100644 --- a/Telegram/SourceFiles/boxes/create_poll_box.cpp +++ b/Telegram/SourceFiles/boxes/create_poll_box.cpp @@ -767,6 +767,7 @@ not_null CreatePollBox::setupQuestion( st::createPollFieldPadding); InitField(getDelegate()->outerContainer(), question, _session); question->setMaxLength(kQuestionLimit + kErrorLimit); + question->setSubmitSettings(Ui::InputField::SubmitSettings::Both); const auto warning = CreateWarningLabel( container, diff --git a/Telegram/SourceFiles/boxes/dictionaries_manager.cpp b/Telegram/SourceFiles/boxes/dictionaries_manager.cpp new file mode 100644 index 000000000..1d8940251 --- /dev/null +++ b/Telegram/SourceFiles/boxes/dictionaries_manager.cpp @@ -0,0 +1,442 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "boxes/dictionaries_manager.h" + +#ifndef TDESKTOP_DISABLE_SPELLCHECK + +#include "base/event_filter.h" +#include "chat_helpers/spellchecker_common.h" +#include "core/application.h" +#include "main/main_account.h" +#include "main/main_session.h" +#include "mainwidget.h" +#include "mtproto/dedicated_file_loader.h" +#include "spellcheck/spellcheck_utils.h" +#include "styles/style_layers.h" +#include "styles/style_settings.h" +#include "styles/style_boxes.h" +#include "ui/wrap/vertical_layout.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/labels.h" +#include "ui/widgets/multi_select.h" +#include "ui/widgets/popup_menu.h" +#include "ui/wrap/slide_wrap.h" +#include "ui/effects/animations.h" + +namespace Ui { +namespace { + +using Dictionaries = std::vector; +using namespace Storage::CloudBlob; + +using Loading = MTP::DedicatedLoader::Progress; +using DictState = BlobState; +using QueryCallback = Fn; +constexpr auto kMaxQueryLength = 15; + +#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) +#define OLD_QT +using QStringView = QString; +#endif + +class Inner : public Ui::RpWidget { +public: + Inner(QWidget *parent, Dictionaries enabledDictionaries); + + Dictionaries enabledRows() const; + QueryCallback queryCallback() const; + +private: + void setupContent(Dictionaries enabledDictionaries); + + Dictionaries _enabledRows; + QueryCallback _queryCallback; + +}; + +inline auto DictExists(int langId) { + return Spellchecker::DictionaryExists(langId); +} + +inline auto FilterEnabledDict(Dictionaries dicts) { + return dicts | ranges::views::filter( + DictExists + ) | ranges::to_vector; +} + +DictState ComputeState(int id, bool enabled) { + const auto result = enabled ? DictState(Active()) : DictState(Ready()); + if (DictExists(id)) { + return result; + } + return Available{ Spellchecker::GetDownloadSize(id) }; +} + +QString StateDescription(const DictState &state) { + return StateDescription( + state, + tr::lng_settings_manage_enabled_dictionary); +} + +auto CreateMultiSelect(QWidget *parent) { + const auto result = Ui::CreateChild( + parent, + st::contactsMultiSelect, + tr::lng_participant_filter()); + + result->resizeToWidth(st::boxWidth); + result->moveToLeft(0, 0); + return result; +} + +Inner::Inner( + QWidget *parent, + Dictionaries enabledDictionaries) : RpWidget(parent) { + setupContent(std::move(enabledDictionaries)); +} + +QueryCallback Inner::queryCallback() const { + return _queryCallback; +} + +Dictionaries Inner::enabledRows() const { + return _enabledRows; +} + +auto AddButtonWithLoader( + not_null content, + const Spellchecker::Dict &dict, + bool buttonEnabled, + rpl::producer query) { + const auto id = dict.id; + buttonEnabled &= DictExists(id); + + const auto locale = Spellchecker::LocaleFromLangId(id); + const std::vector indexList = { + dict.name, + QLocale::languageToString(locale.language()), + QLocale::countryToString(locale.country()) + }; + + const auto wrap = content->add( + object_ptr>( + content, + object_ptr( + content, + rpl::single(dict.name), + st::dictionariesSectionButton + ) + ) + ); + const auto button = wrap->entity(); + + std::move( + query + ) | rpl::start_with_next([=](auto string) { + wrap->toggle( + ranges::any_of(indexList, [&](const QString &s) { + return s.startsWith(string, Qt::CaseInsensitive); + }), + anim::type::instant); + }, button->lifetime()); + + using Loader = Spellchecker::DictLoader; + using GlobalLoaderPtr = std::shared_ptr>; + + const auto localLoader = button->lifetime() + .make_state>(); + const auto localLoaderValues = button->lifetime() + .make_state>(); + const auto setLocalLoader = [=](base::unique_qptr loader) { + *localLoader = std::move(loader); + localLoaderValues->fire(localLoader->get()); + }; + const auto destroyLocalLoader = [=] { + setLocalLoader(nullptr); + }; + + const auto buttonState = button->lifetime() + .make_state>(); + const auto dictionaryRemoved = button->lifetime() + .make_state>(); + const auto dictionaryFromGlobalLoader = button->lifetime() + .make_state>(); + + const auto globalLoader = button->lifetime() + .make_state(); + + const auto rawGlobalLoaderPtr = [=]() -> Loader* { + if (!globalLoader || !*globalLoader || !*globalLoader->get()) { + return nullptr; + } + return globalLoader->get()->get(); + }; + + const auto setGlobalLoaderPtr = [=](GlobalLoaderPtr loader) { + if (localLoader->get()) { + if (loader && loader->get()) { + loader->get()->destroy(); + } + return; + } + *globalLoader = std::move(loader); + localLoaderValues->fire(rawGlobalLoaderPtr()); + if (rawGlobalLoaderPtr()) { + dictionaryFromGlobalLoader->fire({}); + } + }; + + Spellchecker::GlobalLoaderChanged( + ) | rpl::start_with_next([=](int langId) { + if (!langId && rawGlobalLoaderPtr()) { + setGlobalLoaderPtr(nullptr); + } else if (langId == id) { + setGlobalLoaderPtr(Spellchecker::GlobalLoader()); + } + }, button->lifetime()); + + const auto label = Ui::CreateChild( + button, + buttonState->value() | rpl::map(StateDescription), + st::settingsUpdateState); + label->setAttribute(Qt::WA_TransparentForMouseEvents); + + rpl::combine( + button->widthValue(), + label->widthValue() + ) | rpl::start_with_next([=] { + label->moveToLeft( + st::settingsUpdateStatePosition.x(), + st::settingsUpdateStatePosition.y()); + }, label->lifetime()); + + buttonState->value( + ) | rpl::start_with_next([=](const DictState &state) { + const auto isToggledSet = state.is(); + const auto toggled = isToggledSet ? 1. : 0.; + const auto over = !button->isDisabled() + && (button->isDown() || button->isOver()); + + if (toggled == 0. && !over) { + label->setTextColorOverride(std::nullopt); + } else { + label->setTextColorOverride(anim::color( + over ? st::contactsStatusFgOver : st::contactsStatusFg, + st::contactsStatusFgOnline, + toggled)); + } + }, label->lifetime()); + + button->toggleOn( + rpl::single( + buttonEnabled + ) | rpl::then( + rpl::merge( + // Events to toggle on. + dictionaryFromGlobalLoader->events( + ) | rpl::map([] { + return true; + }), + // Events to toggle off. + rpl::merge( + dictionaryRemoved->events(), + buttonState->value( + ) | rpl::filter([](const DictState &state) { + return state.is(); + }) | rpl::map([] { + return rpl::empty_value(); + }) + ) | rpl::map([] { + return false; + }) + ) + ) + ); + + *buttonState = localLoaderValues->events_starting_with( + rawGlobalLoaderPtr() ? rawGlobalLoaderPtr() : localLoader->get() + ) | rpl::map([=](Loader *loader) { + return (loader && loader->id() == id) + ? loader->state() + : rpl::single( + buttonEnabled + ) | rpl::then( + rpl::merge( + dictionaryRemoved->events( + ) | rpl::map([] { + return false; + }), + button->toggledValue() + ) + ) | rpl::map([=](auto enabled) { + return ComputeState(id, enabled); + }); + }) | rpl::flatten_latest( + ) | rpl::filter([=](const DictState &state) { + return !buttonState->current().is() || !state.is(); + }); + + button->toggledValue( + ) | rpl::start_with_next([=](bool toggled) { + const auto &state = buttonState->current(); + if (toggled && (state.is() || state.is())) { + const auto weak = Ui::MakeWeak(button); + setLocalLoader(base::make_unique_q( + App::main(), + id, + Spellchecker::GetDownloadLocation(id), + Spellchecker::DictPathByLangId(id), + Spellchecker::GetDownloadSize(id), + crl::guard(weak, destroyLocalLoader))); + } else if (!toggled && state.is()) { + if (const auto g = rawGlobalLoaderPtr()) { + g->destroy(); + return; + } + if (localLoader && localLoader->get()->id() == id) { + destroyLocalLoader(); + } + } + }, button->lifetime()); + + const auto contextMenu = button->lifetime() + .make_state>(); + const auto showMenu = [=] { + if (!DictExists(id)) { + return false; + } + *contextMenu = base::make_unique_q(button); + contextMenu->get()->addAction( + tr::lng_settings_manage_remove_dictionary(tr::now), [=] { + Spellchecker::RemoveDictionary(id); + dictionaryRemoved->fire({}); + }); + contextMenu->get()->popup(QCursor::pos()); + return true; + }; + + base::install_event_filter(button, [=](not_null e) { + if (e->type() == QEvent::ContextMenu && showMenu()) { + return base::EventFilterResult::Cancel; + } + return base::EventFilterResult::Continue; + }); + + if (const auto g = Spellchecker::GlobalLoader()) { + if (g.get() && g->get()->id() == id) { + setGlobalLoaderPtr(g); + } + } + + return button; +} + +void Inner::setupContent(Dictionaries enabledDictionaries) { + const auto content = Ui::CreateChild(this); + + const auto queryStream = content->lifetime() + .make_state>(); + + for (const auto &dict : Spellchecker::Dictionaries()) { + const auto id = dict.id; + const auto row = AddButtonWithLoader( + content, + dict, + ranges::contains(enabledDictionaries, id), + queryStream->events()); + row->toggledValue( + ) | rpl::start_with_next([=](auto enabled) { + if (enabled) { + _enabledRows.push_back(id); + } else { + auto &rows = _enabledRows; + rows.erase(ranges::remove(rows, id), end(rows)); + } + }, row->lifetime()); + } + + _queryCallback = [=](const QString &query) { + if (query.size() >= kMaxQueryLength) { + return; + } + queryStream->fire_copy(query); + }; + + content->resizeToWidth(st::boxWidth); + Ui::ResizeFitChild(this, content); +} + +} // namespace + +ManageDictionariesBox::ManageDictionariesBox( + QWidget*, + not_null session) +: _session(session) { +} + +void ManageDictionariesBox::setInnerFocus() { + _setInnerFocus(); +} + +void ManageDictionariesBox::prepare() { + const auto multiSelect = CreateMultiSelect(this); + + const auto inner = setInnerWidget( + object_ptr( + this, + _session->settings().dictionariesEnabled()), + st::boxScroll, + multiSelect->height() + ); + + multiSelect->setQueryChangedCallback(inner->queryCallback()); + _setInnerFocus = [=] { + multiSelect->setInnerFocus(); + }; + + // The initial list of enabled rows may differ from the list of languages + // in settings, so we should store it when box opens + // and save it when box closes (don't do it when "Save" was pressed). + const auto initialEnabledRows = inner->enabledRows(); + + setTitle(tr::lng_settings_manage_dictionaries()); + + addButton(tr::lng_settings_save(), [=] { + _session->settings().setDictionariesEnabled( + FilterEnabledDict(inner->enabledRows())); + _session->saveSettingsDelayed(); + // Ignore boxClosing() when the Save button was pressed. + lifetime().destroy(); + closeBox(); + }); + addButton(tr::lng_close(), [=] { closeBox(); }); + + boxClosing() | rpl::start_with_next([=] { + _session->settings().setDictionariesEnabled( + FilterEnabledDict(initialEnabledRows)); + _session->saveSettingsDelayed(); + }, lifetime()); + + setDimensionsToContent(st::boxWidth, inner); + + using namespace rpl::mappers; + const auto max = lifetime().make_state(0); + rpl::combine( + inner->heightValue(), + multiSelect->heightValue(), + _1 + _2 + ) | rpl::start_with_next([=](int height) { + using std::min; + accumulate_max(*max, height); + setDimensions(st::boxWidth, min(*max, st::boxMaxListHeight), true); + }, inner->lifetime()); +} + +} // namespace Ui + +#endif // !TDESKTOP_DISABLE_SPELLCHECK diff --git a/Telegram/SourceFiles/boxes/dictionaries_manager.h b/Telegram/SourceFiles/boxes/dictionaries_manager.h new file mode 100644 index 000000000..f137a76ca --- /dev/null +++ b/Telegram/SourceFiles/boxes/dictionaries_manager.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 + +#ifndef TDESKTOP_DISABLE_SPELLCHECK + +#include "boxes/abstract_box.h" + +namespace Main { +class Session; +} // namespace Main + +namespace Ui { + +class ManageDictionariesBox : public Ui::BoxContent { +public: + ManageDictionariesBox( + QWidget*, + not_null session); + +protected: + void prepare() override; + void setInnerFocus() override; + +private: + const not_null _session; + Fn _setInnerFocus; + +}; + +} // namespace Ui + +#endif // !TDESKTOP_DISABLE_SPELLCHECK diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp index dd20abb01..f00f74c3b 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp @@ -260,7 +260,7 @@ EditCaptionBox::EditCaptionBox( tr::lng_photo_caption(), editData); _field->setMaxLength(Global::CaptionLengthMax()); - _field->setSubmitSettings(Ui::InputField::SubmitSettings::Both); + _field->setSubmitSettings(_controller->session().settings().sendSubmitWay()); _field->setInstantReplaces(Ui::InstantReplaces::Default()); _field->setInstantReplacesEnabled( _controller->session().settings().replaceEmojiValue()); diff --git a/Telegram/SourceFiles/boxes/mute_settings_box.cpp b/Telegram/SourceFiles/boxes/mute_settings_box.cpp index fb8b67b09..7fd659c9b 100644 --- a/Telegram/SourceFiles/boxes/mute_settings_box.cpp +++ b/Telegram/SourceFiles/boxes/mute_settings_box.cpp @@ -76,7 +76,7 @@ void MuteSettingsBox::prepare() { _save = [=] { const auto muteForSeconds = group->value() * 3600; - _peer->session().data().updateNotifySettings( + _peer->owner().updateNotifySettings( _peer, muteForSeconds); closeBox(); diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp index 606ecc4f4..5fab61356 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_chat.h" #include "data/data_user.h" #include "data/data_folder.h" +#include "data/data_histories.h" #include "apiwrap.h" #include "mainwidget.h" #include "lang/lang_keys.h" @@ -30,33 +31,36 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace { void ShareBotGame(not_null bot, not_null chat) { - const auto history = chat->owner().historyLoaded(chat); - const auto randomId = rand_value(); - const auto api = &chat->session().api(); - const auto requestId = api->request(MTPmessages_SendMedia( - MTP_flags(0), - chat->input, - MTP_int(0), - MTP_inputMediaGame( - MTP_inputGameShortName( - bot->inputUser, - MTP_string(bot->botInfo->shareGameShortName))), - MTP_string(), - MTP_long(randomId), - MTPReplyMarkup(), - MTPVector(), - MTP_int(0) // schedule_date - )).done([=](const MTPUpdates &result) { - api->applyUpdates(result, randomId); - }).fail([=](const RPCError &error) { - api->sendMessageFail(error, chat); - }).afterRequest( - history ? history->sendRequestId : 0 - ).send(); - - if (history) { - history->sendRequestId = requestId; - } + const auto history = chat->owner().history(chat); + auto &histories = history->owner().histories(); + const auto requestType = Data::Histories::RequestType::Send; + histories.sendRequest(history, requestType, [=](Fn finish) { + const auto randomId = rand_value(); + const auto api = &chat->session().api(); + history->sendRequestId = api->request(MTPmessages_SendMedia( + MTP_flags(0), + chat->input, + MTP_int(0), + MTP_inputMediaGame( + MTP_inputGameShortName( + bot->inputUser, + MTP_string(bot->botInfo->shareGameShortName))), + MTP_string(), + MTP_long(randomId), + MTPReplyMarkup(), + MTPVector(), + MTP_int(0) // schedule_date + )).done([=](const MTPUpdates &result) { + api->applyUpdates(result, randomId); + finish(); + }).fail([=](const RPCError &error) { + api->sendMessageFail(error, chat); + finish(); + }).afterRequest( + history->sendRequestId + ).send(); + return history->sendRequestId; + }); Ui::hideLayer(); Ui::showPeerHistory(chat, ShowAtUnreadMsgId); } diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index 5ee2cd89a..3885ec4aa 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -500,6 +500,7 @@ object_ptr Controller::createDescriptionEdit() { result->entity()->setInstantReplaces(Ui::InstantReplaces::Default()); result->entity()->setInstantReplacesEnabled( _peer->session().settings().replaceEmojiValue()); + result->entity()->setSubmitSettings(_peer->session().settings().sendSubmitWay()); Ui::Emoji::SuggestionsController::Init( _wrap->window(), result->entity(), diff --git a/Telegram/SourceFiles/boxes/rate_call_box.cpp b/Telegram/SourceFiles/boxes/rate_call_box.cpp index 85294a3fe..e1c97e6e8 100644 --- a/Telegram/SourceFiles/boxes/rate_call_box.cpp +++ b/Telegram/SourceFiles/boxes/rate_call_box.cpp @@ -85,7 +85,7 @@ void RateCallBox::ratingChanged(int value) { Ui::InputField::Mode::MultiLine, tr::lng_call_rate_comment()); _comment->show(); - _comment->setSubmitSettings(Ui::InputField::SubmitSettings::Both); + _comment->setSubmitSettings(_session->settings().sendSubmitWay()); _comment->setMaxLength(kRateCallCommentLengthMax); _comment->resize(width() - (st::callRatingPadding.left() + st::callRatingPadding.right()), _comment->height()); diff --git a/Telegram/SourceFiles/boxes/report_box.cpp b/Telegram/SourceFiles/boxes/report_box.cpp index 28697df52..ff95cc5aa 100644 --- a/Telegram/SourceFiles/boxes/report_box.cpp +++ b/Telegram/SourceFiles/boxes/report_box.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "data/data_peer.h" +#include "main/main_session.h" #include "boxes/confirm_box.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" @@ -106,7 +107,7 @@ void ReportBox::reasonChanged(Reason reason) { Ui::InputField::Mode::MultiLine, tr::lng_report_reason_description()); _reasonOtherText->show(); - _reasonOtherText->setSubmitSettings(Ui::InputField::SubmitSettings::Both); + _reasonOtherText->setSubmitSettings(_peer->session().settings().sendSubmitWay()); _reasonOtherText->setMaxLength(kReportReasonLengthMax); _reasonOtherText->resize(width() - (st::boxPadding.left() + st::boxOptionListPadding.left() + st::boxPadding.right()), _reasonOtherText->height()); diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index 04376241a..ec573c695 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -2046,7 +2046,7 @@ void SendFilesBox::applyAlbumOrder() { void SendFilesBox::setupCaption() { _caption->setMaxLength(Global::CaptionLengthMax()); - _caption->setSubmitSettings(Ui::InputField::SubmitSettings::Both); + _caption->setSubmitSettings(_controller->session().settings().sendSubmitWay()); connect(_caption, &Ui::InputField::resized, [=] { captionResized(); }); diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index 1608c8557..9719bee0b 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -206,6 +206,7 @@ void ShareBox::prepareCommentField() { field->setMarkdownReplacesEnabled(rpl::single(true)); field->setEditLinkCallback( DefaultEditLinkCallback(&_navigation->session(), field)); + field->setSubmitSettings(_navigation->session().settings().sendSubmitWay()); InitSpellchecker(&_navigation->session(), field); Ui::SendPendingMoveResizeEvents(_comment); diff --git a/Telegram/SourceFiles/calls/calls_box_controller.h b/Telegram/SourceFiles/calls/calls_box_controller.h index d67e22c04..ae5894df4 100644 --- a/Telegram/SourceFiles/calls/calls_box_controller.h +++ b/Telegram/SourceFiles/calls/calls_box_controller.h @@ -44,7 +44,7 @@ private: MTP::Sender _api; MsgId _offsetId = 0; - mtpRequestId _loadRequestId = 0; + int _loadRequestId = 0; // Not a real mtpRequestId. bool _allLoaded = false; }; diff --git a/Telegram/SourceFiles/chat_helpers/emoji_sets_manager.cpp b/Telegram/SourceFiles/chat_helpers/emoji_sets_manager.cpp index ad1f896fc..a59a2a2e9 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_sets_manager.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_sets_manager.cpp @@ -15,13 +15,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/animations.h" #include "ui/effects/radial_animation.h" #include "ui/emoji_config.h" -#include "lang/lang_keys.h" -#include "base/zlib_help.h" -#include "layout.h" #include "core/application.h" #include "main/main_account.h" #include "mainwidget.h" #include "app.h" +#include "storage/storage_cloud_blob.h" #include "styles/style_layers.h" #include "styles/style_boxes.h" #include "styles/style_chat_helpers.h" @@ -30,69 +28,37 @@ namespace Ui { namespace Emoji { namespace { -struct Available { - int size = 0; +using namespace Storage::CloudBlob; - inline bool operator<(const Available &other) const { - return size < other.size; - } - inline bool operator==(const Available &other) const { - return size == other.size; - } +struct Set : public Blob { + QString previewPath; }; -struct Ready { - inline bool operator<(const Ready &other) const { - return false; - } - inline bool operator==(const Ready &other) const { - return true; - } -}; -struct Active { - inline bool operator<(const Active &other) const { - return false; - } - inline bool operator==(const Active &other) const { - return true; - } + +inline auto PreviewPath(int i) { + return qsl(":/gui/emoji/set%1_preview.webp").arg(i); +} + +const auto kSets = { + Set{ {0, 0, 0, "Mac"}, PreviewPath(0) }, + Set{ {1, 246, 7'336'383, "Android"}, PreviewPath(1) }, + Set{ {2, 206, 5'038'738, "Twemoji"}, PreviewPath(2) }, + Set{ {3, 238, 6'992'260, "JoyPixels"}, PreviewPath(3) }, }; + using Loading = MTP::DedicatedLoader::Progress; -struct Failed { - inline bool operator<(const Failed &other) const { - return false; - } - inline bool operator==(const Failed &other) const { - return true; - } -}; -using SetState = base::variant< - Available, - Ready, - Active, - Loading, - Failed>; +using SetState = BlobState; -class Loader : public QObject { +class Loader : public BlobLoader { public: - Loader(QObject *parent, int id); + Loader( + QObject *parent, + int id, + MTP::DedicatedLoader::Location location, + const QString &folder, + int size); - int id() const; - - rpl::producer state() const; - void destroy(); - -private: - void setImplementation(std::unique_ptr loader); - void unpack(const QString &path); - void finalize(const QString &path); - void fail(); - - int _id = 0; - int _size = 0; - rpl::variable _state; - - MTP::WeakInstance _mtproto; - std::unique_ptr _implementation; + void destroy() override; + void unpack(const QString &path) override; }; @@ -127,6 +93,7 @@ private: void setupHandler(); void load(); void radialAnimationCallback(crl::time now); + void updateLoadingToFinished(); int _id = 0; bool _switching = false; @@ -148,15 +115,19 @@ void SetGlobalLoader(base::unique_qptr loader) { } int GetDownloadSize(int id) { - const auto sets = Sets(); - return ranges::find(sets, id, &Set::id)->size; + return ranges::find(kSets, id, &Set::id)->size; +} + +[[nodiscard]] float64 CountProgress(not_null loading) { + return (loading->size > 0) + ? (loading->already / float64(loading->size)) + : 0.; } MTP::DedicatedLoader::Location GetDownloadLocation(int id) { - constexpr auto kUsername = "tdhbcfiles"; - const auto sets = Sets(); - const auto i = ranges::find(sets, id, &Set::id); - return MTP::DedicatedLoader::Location{ kUsername, i->postId }; + const auto username = kCloudLocationUsername.utf16(); + const auto i = ranges::find(kSets, id, &Set::id); + return MTP::DedicatedLoader::Location{ username, i->postId }; } SetState ComputeState(int id) { @@ -169,45 +140,9 @@ SetState ComputeState(int id) { } QString StateDescription(const SetState &state) { - return state.match([](const Available &data) { - return tr::lng_emoji_set_download(tr::now, lt_size, formatSizeText(data.size)); - }, [](const Ready &data) -> QString { - return tr::lng_emoji_set_ready(tr::now); - }, [](const Active &data) -> QString { - return tr::lng_emoji_set_active(tr::now); - }, [](const Loading &data) { - const auto percent = (data.size > 0) - ? snap((data.already * 100) / float64(data.size), 0., 100.) - : 0.; - return tr::lng_emoji_set_loading( - tr::now, - lt_percent, - QString::number(int(std::round(percent))) + '%', - lt_progress, - formatDownloadText(data.already, data.size)); - }, [](const Failed &data) { - return tr::lng_attach_failed(tr::now); - }); -} - -QByteArray ReadFinalFile(const QString &path) { - constexpr auto kMaxZipSize = 10 * 1024 * 1024; - auto file = QFile(path); - if (file.size() > kMaxZipSize || !file.open(QIODevice::ReadOnly)) { - return QByteArray(); - } - return file.readAll(); -} - -bool ExtractZipFile(zlib::FileToRead &zip, const QString path) { - constexpr auto kMaxSize = 10 * 1024 * 1024; - const auto content = zip.readCurrentFileContent(kMaxSize); - if (content.isEmpty() || zip.error() != UNZ_OK) { - return false; - } - auto file = QFile(path); - return file.open(QIODevice::WriteOnly) - && (file.write(content) == content.size()); + return StateDescription( + state, + tr::lng_emoji_set_active); } bool GoodSetPartName(const QString &name) { @@ -217,89 +152,24 @@ bool GoodSetPartName(const QString &name) { } bool UnpackSet(const QString &path, const QString &folder) { - const auto bytes = ReadFinalFile(path); - if (bytes.isEmpty()) { - return false; - } - - auto zip = zlib::FileToRead(bytes); - if (zip.goToFirstFile() != UNZ_OK) { - return false; - } - do { - const auto name = zip.getCurrentFileName(); - const auto path = folder + '/' + name; - if (GoodSetPartName(name) && !ExtractZipFile(zip, path)) { - return false; - } - - const auto jump = zip.goToNextFile(); - if (jump == UNZ_END_OF_LIST_OF_FILE) { - break; - } else if (jump != UNZ_OK) { - return false; - } - } while (true); - return true; + return UnpackBlob(path, folder, GoodSetPartName); } -Loader::Loader(QObject *parent, int id) -: QObject(parent) -, _id(id) -, _size(GetDownloadSize(_id)) -, _state(Loading{ 0, _size }) -, _mtproto(Core::App().activeAccount().mtp()) { - const auto ready = [=](std::unique_ptr loader) { - if (loader) { - setImplementation(std::move(loader)); - } else { - fail(); - } - }; - const auto location = GetDownloadLocation(id); - const auto folder = internal::SetDataPath(id); - MTP::StartDedicatedLoader(&_mtproto, location, folder, ready); -} - -int Loader::id() const { - return _id; -} - -rpl::producer Loader::state() const { - return _state.value(); -} - -void Loader::setImplementation( - std::unique_ptr loader) { - _implementation = std::move(loader); - auto convert = [](auto value) { - return SetState(value); - }; - _state = _implementation->progress( - ) | rpl::map([](const Loading &state) { - return SetState(state); - }); - _implementation->failed( - ) | rpl::start_with_next([=] { - fail(); - }, _implementation->lifetime()); - - _implementation->ready( - ) | rpl::start_with_next([=](const QString &filepath) { - unpack(filepath); - }, _implementation->lifetime()); - - QDir(internal::SetDataPath(_id)).removeRecursively(); - _implementation->start(); +Loader::Loader( + QObject *parent, + int id, + MTP::DedicatedLoader::Location location, + const QString &folder, + int size) : BlobLoader(parent, id, location, folder, size) { } void Loader::unpack(const QString &path) { - const auto folder = internal::SetDataPath(_id); + const auto folder = internal::SetDataPath(id()); const auto weak = Ui::MakeWeak(this); crl::async([=] { if (UnpackSet(path, folder)) { QFile(path).remove(); - SwitchToSet(_id, crl::guard(weak, [=](bool success) { + SwitchToSet(id(), crl::guard(weak, [=](bool success) { if (success) { destroy(); } else { @@ -314,13 +184,6 @@ void Loader::unpack(const QString &path) { }); } -void Loader::finalize(const QString &path) { -} - -void Loader::fail() { - _state = Failed(); -} - void Loader::destroy() { Expects(GlobalLoader == this); @@ -334,8 +197,7 @@ Inner::Inner(QWidget *parent) : RpWidget(parent) { void Inner::setupContent() { const auto content = Ui::CreateChild(this); - const auto sets = Sets(); - for (const auto &set : sets) { + for (const auto &set : kSets) { content->add(object_ptr(content, set)); } @@ -379,6 +241,9 @@ void Row::paintPreview(Painter &p) const { } void Row::paintRadio(Painter &p) { + if (_loading && !_loading->animating()) { + _loading = nullptr; + } const auto loading = _loading ? _loading->computeState() : Ui::RadialState{ 0., 0, FullArcLength }; @@ -536,7 +401,12 @@ void Row::setupHandler() { } void Row::load() { - SetGlobalLoader(base::make_unique_q(App::main(), _id)); + SetGlobalLoader(base::make_unique_q( + App::main(), + _id, + GetDownloadLocation(_id), + internal::SetDataPath(_id), + GetDownloadSize(_id))); } void Row::setupLabels(const Set &set) { @@ -580,14 +450,20 @@ void Row::setupPreview(const Set &set) { } } +void Row::updateLoadingToFinished() { + _loading->update( + _state.current().is() ? 0. : 1., + true, + crl::now()); +} + void Row::radialAnimationCallback(crl::time now) { const auto updated = [&] { const auto state = _state.current(); if (const auto loading = base::get_if(&state)) { - const auto progress = (loading->size > 0) - ? (loading->already / float64(loading->size)) - : 0.; - return _loading->update(progress, false, now); + return _loading->update(CountProgress(loading), false, now); + } else { + updateLoadingToFinished(); } return false; }(); @@ -636,15 +512,9 @@ void Row::setupAnimation() { if (loading && !_loading) { _loading = std::make_unique( [=](crl::time now) { radialAnimationCallback(now); }); - const auto progress = (loading->size > 0) - ? (loading->already / float64(loading->size)) - : 0.; - _loading->start(progress); + _loading->start(CountProgress(loading)); } else if (!loading && _loading) { - _loading->update( - _state.current().is() ? 0. : 1., - true, - crl::now()); + updateLoadingToFinished(); } }, lifetime()); diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index 1b2d53083..215932831 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -280,17 +280,13 @@ void InitSpellchecker( not_null session, not_null field) { #ifndef TDESKTOP_DISABLE_SPELLCHECK - if (!Platform::Spellchecker::IsAvailable()) { - return; - } const auto s = Ui::CreateChild( field.get(), - session->settings().spellcheckerEnabledValue()); - Spellchecker::SetPhrases({ { - { &ph::lng_spellchecker_add, tr::lng_spellchecker_add() }, - { &ph::lng_spellchecker_remove, tr::lng_spellchecker_remove() }, - { &ph::lng_spellchecker_ignore, tr::lng_spellchecker_ignore() }, - } }); + session->settings().spellcheckerEnabledValue(), + Spellchecker::SpellingHighlighter::CustomContextMenuItem{ + tr::lng_settings_manage_dictionaries(tr::now), + [=] { Ui::show(Box(session)); } + }); field->setExtendedContextMenu(s->contextMenuCreated()); #endif // TDESKTOP_DISABLE_SPELLCHECK } diff --git a/Telegram/SourceFiles/chat_helpers/message_field.h b/Telegram/SourceFiles/chat_helpers/message_field.h index 8f95a9323..812ca859b 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.h +++ b/Telegram/SourceFiles/chat_helpers/message_field.h @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/qt_connection.h" #ifndef TDESKTOP_DISABLE_SPELLCHECK +#include "boxes/dictionaries_manager.h" #include "spellcheck/spelling_highlighter.h" #endif // TDESKTOP_DISABLE_SPELLCHECK diff --git a/Telegram/SourceFiles/chat_helpers/spellchecker_common.cpp b/Telegram/SourceFiles/chat_helpers/spellchecker_common.cpp new file mode 100644 index 000000000..ee4b0d7c6 --- /dev/null +++ b/Telegram/SourceFiles/chat_helpers/spellchecker_common.cpp @@ -0,0 +1,447 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "chat_helpers/spellchecker_common.h" + +#ifndef TDESKTOP_DISABLE_SPELLCHECK + +#include "base/platform/base_platform_info.h" +#include "base/zlib_help.h" +#include "data/data_session.h" +#include "lang/lang_instance.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "mainwidget.h" +#include "spellcheck/platform/platform_spellcheck.h" +#include "spellcheck/spellcheck_utils.h" +#include "spellcheck/spellcheck_value.h" + +#include +#include + +namespace Spellchecker { + +namespace { + +using namespace Storage::CloudBlob; + +constexpr auto kDictExtensions = { "dic", "aff" }; + +// 31 - QLocale::English, 91 - QLocale::Portuguese. +constexpr auto kLangsForLWC = { 31, 91 }; +// 225 - QLocale::UnitesStates, 30 - QLocale::Brazil. +constexpr auto kDefaultCountries = { 225, 30 }; + +// Language With Country. +inline auto LWC(QLocale::Country country) { + const auto l = QLocale::matchingLocales( + QLocale::AnyLanguage, + QLocale::AnyScript, + country)[0]; + if (ranges::contains(kDefaultCountries, country)) { + return int(l.language()); + } + return (l.language() * 1000) + country; +} + +inline auto LanguageFromLocale(QLocale loc) { + const auto locLang = int(loc.language()); + return (ranges::contains(kLangsForLWC, locLang) + && (loc.country() != QLocale::AnyCountry)) + ? LWC(loc.country()) + : locLang; +} + +const auto kDictionaries = { + Dict{{ QLocale::English, 649, 174'516, "English" }}, // en_US + Dict{{ QLocale::Bulgarian, 594, 229'658, "\xd0\x91\xd1\x8a\xd0\xbb\xd0\xb3\xd0\xb0\xd1\x80\xd1\x81\xd0\xba\xd0\xb8" }}, // bg_BG + Dict{{ QLocale::Catalan, 595, 417'611, "\x43\x61\x74\x61\x6c\xc3\xa0" }}, // ca_ES + Dict{{ QLocale::Czech, 596, 860'286, "\xc4\x8c\x65\xc5\xa1\x74\x69\x6e\x61" }}, // cs_CZ + Dict{{ QLocale::Welsh, 597, 177'305, "\x43\x79\x6d\x72\x61\x65\x67" }}, // cy_GB + Dict{{ QLocale::Danish, 598, 345'874, "\x44\x61\x6e\x73\x6b" }}, // da_DK + Dict{{ QLocale::German, 599, 2'412'780, "\x44\x65\x75\x74\x73\x63\x68" }}, // de_DE + Dict{{ QLocale::Greek, 600, 1'389'160, "\xce\x95\xce\xbb\xce\xbb\xce\xb7\xce\xbd\xce\xb9\xce\xba\xce\xac" }}, // el_GR + Dict{{ LWC(QLocale::Australia), 601, 175'266, "English (Australia)" }}, // en_AU + Dict{{ LWC(QLocale::Canada), 602, 174'295, "English (Canada)" }}, // en_CA + Dict{{ LWC(QLocale::UnitedKingdom), 603, 174'433, "English (United Kingdom)" }}, // en_GB + Dict{{ QLocale::Spanish, 604, 264'717, "\x45\x73\x70\x61\xc3\xb1\x6f\x6c" }}, // es_ES + Dict{{ QLocale::Estonian, 605, 757'394, "\x45\x65\x73\x74\x69" }}, // et_EE + Dict{{ QLocale::Persian, 606, 333'911, "\xd9\x81\xd8\xa7\xd8\xb1\xd8\xb3\xdb\x8c" }}, // fa_IR + Dict{{ QLocale::French, 607, 321'391, "\x46\x72\x61\x6e\xc3\xa7\x61\x69\x73" }}, // fr_FR + Dict{{ QLocale::Hebrew, 608, 622'550, "\xd7\xa2\xd7\x91\xd7\xa8\xd7\x99\xd7\xaa" }}, // he_IL + Dict{{ QLocale::Hindi, 609, 56'105, "\xe0\xa4\xb9\xe0\xa4\xbf\xe0\xa4\xa8\xe0\xa5\x8d\xe0\xa4\xa6\xe0\xa5\x80" }}, // hi_IN + Dict{{ QLocale::Croatian, 610, 668'876, "\x48\x72\x76\x61\x74\x73\x6b\x69" }}, // hr_HR + Dict{{ QLocale::Hungarian, 611, 660'402, "\x4d\x61\x67\x79\x61\x72" }}, // hu_HU + Dict{{ QLocale::Armenian, 612, 928'746, "\xd5\x80\xd5\xa1\xd5\xb5\xd5\xa5\xd6\x80\xd5\xa5\xd5\xb6" }}, // hy_AM + Dict{{ QLocale::Indonesian, 613, 100'134, "\x49\x6e\x64\x6f\x6e\x65\x73\x69\x61" }}, // id_ID + Dict{{ QLocale::Italian, 614, 324'613, "\x49\x74\x61\x6c\x69\x61\x6e\x6f" }}, // it_IT + Dict{{ QLocale::Korean, 615, 1'256'987, "\xed\x95\x9c\xea\xb5\xad\xec\x96\xb4" }}, // ko_KR + Dict{{ QLocale::Lithuanian, 616, 267'427, "\x4c\x69\x65\x74\x75\x76\x69\xc5\xb3" }}, // lt_LT + Dict{{ QLocale::Latvian, 617, 641'602, "\x4c\x61\x74\x76\x69\x65\xc5\xa1\x75" }}, // lv_LV + Dict{{ QLocale::Norwegian, 618, 588'650, "\x4e\x6f\x72\x73\x6b" }}, // nb_NO + Dict{{ QLocale::Dutch, 619, 743'406, "\x4e\x65\x64\x65\x72\x6c\x61\x6e\x64\x73" }}, // nl_NL + Dict{{ QLocale::Polish, 620, 1'015'747, "\x50\x6f\x6c\x73\x6b\x69" }}, // pl_PL + Dict{{ QLocale::Portuguese, 621, 1'231'999, "\x50\x6f\x72\x74\x75\x67\x75\xc3\xaa\x73 (Brazil)" }}, // pt_BR + Dict{{ LWC(QLocale::Portugal), 622, 138'571, "\x50\x6f\x72\x74\x75\x67\x75\xc3\xaa\x73" }}, // pt_PT + Dict{{ QLocale::Romanian, 623, 455'643, "\x52\x6f\x6d\xc3\xa2\x6e\xc4\x83" }}, // ro_RO + Dict{{ QLocale::Russian, 624, 463'194, "\xd0\xa0\xd1\x83\xd1\x81\xd1\x81\xd0\xba\xd0\xb8\xd0\xb9" }}, // ru_RU + Dict{{ QLocale::Slovak, 625, 525'328, "\x53\x6c\x6f\x76\x65\x6e\xc4\x8d\x69\x6e\x61" }}, // sk_SK + Dict{{ QLocale::Slovenian, 626, 1'143'710, "\x53\x6c\x6f\x76\x65\x6e\xc5\xa1\xc4\x8d\x69\x6e\x61" }}, // sl_SI + Dict{{ QLocale::Albanian, 627, 583'412, "\x53\x68\x71\x69\x70" }}, // sq_AL + Dict{{ QLocale::Swedish, 628, 593'877, "\x53\x76\x65\x6e\x73\x6b\x61" }}, // sv_SE + Dict{{ QLocale::Tamil, 629, 323'193, "\xe0\xae\xa4\xe0\xae\xae\xe0\xae\xbf\xe0\xae\xb4\xe0\xaf\x8d" }}, // ta_IN + Dict{{ QLocale::Tajik, 630, 369'931, "\xd0\xa2\xd0\xbe\xd2\xb7\xd0\xb8\xd0\xba\xd3\xa3" }}, // tg_TG + Dict{{ QLocale::Turkish, 631, 4'301'099, "\x54\xc3\xbc\x72\x6b\xc3\xa7\x65" }}, // tr_TR + Dict{{ QLocale::Ukrainian, 632, 445'711, "\xd0\xa3\xd0\xba\xd1\x80\xd0\xb0\xd1\x97\xd0\xbd\xd1\x81\xd1\x8c\xd0\xba\xd0\xb0" }}, // uk_UA + Dict{{ QLocale::Vietnamese, 633, 12'949, "\x54\x69\xe1\xba\xbf\x6e\x67\x20\x56\x69\xe1\xbb\x87\x74" }}, // vi_VN + // The Tajik code is 'tg_TG' in Chromium, but QT has only 'tg_TJ'. +}; + +inline auto IsSupportedLang(int lang) { + return ranges::contains(kDictionaries, lang, &Dict::id); +} + +void EnsurePath() { + if (!QDir::current().mkpath(Spellchecker::DictionariesPath())) { + LOG(("App Error: Could not create dictionaries path.")); + } +} + +bool IsGoodPartName(const QString &name) { + return ranges::find_if(kDictExtensions, [&](const auto &ext) { + return name.endsWith(ext); + }) != end(kDictExtensions); +} + +using DictLoaderPtr = std::shared_ptr>; + +DictLoaderPtr BackgroundLoader; +rpl::event_stream BackgroundLoaderChanged; + +void SetBackgroundLoader(DictLoaderPtr loader) { + BackgroundLoader = std::move(loader); +} + +void DownloadDictionaryInBackground( + not_null session, + int counter, + std::vector langs) { + const auto id = langs[counter]; + counter++; + const auto destroyer = [=] { + // This is a temporary workaround. + const auto copyId = id; + const auto copyLangs = langs; + const auto copySession = session; + const auto copyCounter = counter; + BackgroundLoader = nullptr; + BackgroundLoaderChanged.fire(0); + + if (DictionaryExists(copyId)) { + auto dicts = copySession->settings().dictionariesEnabled(); + if (!ranges::contains(dicts, copyId)) { + dicts.push_back(copyId); + copySession->settings().setDictionariesEnabled(std::move(dicts)); + copySession->saveSettingsDelayed(); + } + } + + if (copyCounter >= copyLangs.size()) { + return; + } + DownloadDictionaryInBackground(copySession, copyCounter, copyLangs); + }; + if (DictionaryExists(id)) { + destroyer(); + return; + } + + auto sharedLoader = std::make_shared>(); + *sharedLoader = base::make_unique_q( + App::main(), + id, + GetDownloadLocation(id), + DictPathByLangId(id), + GetDownloadSize(id), + crl::guard(session, destroyer)); + SetBackgroundLoader(std::move(sharedLoader)); + BackgroundLoaderChanged.fire_copy(id); +} + +} // namespace + +DictLoaderPtr GlobalLoader() { + return BackgroundLoader; +} + +rpl::producer GlobalLoaderChanged() { + return BackgroundLoaderChanged.events(); +} + +DictLoader::DictLoader( + QObject *parent, + int id, + MTP::DedicatedLoader::Location location, + const QString &folder, + int size, + Fn destroyCallback) +: BlobLoader(parent, id, location, folder, size) +, _destroyCallback(std::move(destroyCallback)) { +} + +void DictLoader::unpack(const QString &path) { + Expects(_destroyCallback); + crl::async([=] { + const auto success = Spellchecker::UnpackDictionary(path, id()); + if (success) { + QFile(path).remove(); + } + crl::on_main(success ? _destroyCallback : [=] { fail(); }); + }); +} + +void DictLoader::destroy() { + Expects(_destroyCallback); + + _destroyCallback(); +} + +void DictLoader::fail() { + BlobLoader::fail(); + destroy(); +} + +std::vector Dictionaries() { + return kDictionaries | ranges::to_vector; +} + +int GetDownloadSize(int id) { + return ranges::find(kDictionaries, id, &Spellchecker::Dict::id)->size; +} + +MTP::DedicatedLoader::Location GetDownloadLocation(int id) { + const auto username = kCloudLocationUsername.utf16(); + const auto i = ranges::find(kDictionaries, id, &Spellchecker::Dict::id); + return MTP::DedicatedLoader::Location{ username, i->postId }; +} + +QString DictPathByLangId(int langId) { + EnsurePath(); + return qsl("%1/%2") + .arg(DictionariesPath()) + .arg(Spellchecker::LocaleFromLangId(langId).name()); +} + +QString DictionariesPath() { + return cWorkingDir() + qsl("tdata/dictionaries"); +} + +bool UnpackDictionary(const QString &path, int langId) { + const auto folder = DictPathByLangId(langId); + return UnpackBlob(path, folder, IsGoodPartName); +} + +bool DictionaryExists(int langId) { + if (!langId) { + return true; + } + const auto folder = DictPathByLangId(langId) + '/'; + const auto bad = ranges::find_if(kDictExtensions, [&](const auto &ext) { + const auto name = Spellchecker::LocaleFromLangId(langId).name(); + return !QFile(folder + name + '.' + ext).exists(); + }); + return (bad == end(kDictExtensions)); +} + +bool RemoveDictionary(int langId) { + if (!langId) { + return true; + } + const auto fileName = Spellchecker::LocaleFromLangId(langId).name(); + const auto folder = qsl("%1/%2/") + .arg(DictionariesPath()) + .arg(fileName); + return QDir(folder).removeRecursively(); +} + +bool WriteDefaultDictionary() { + // This is an unused function. + const auto en = QLocale::English; + if (DictionaryExists(en)) { + return false; + } + const auto fileName = QLocale(en).name(); + const auto folder = qsl("%1/%2/") + .arg(DictionariesPath()) + .arg(fileName); + QDir(folder).removeRecursively(); + + const auto path = folder + fileName; + QDir().mkpath(folder); + auto input = QFile(qsl(":/misc/en_US_dictionary")); + auto output = QFile(path); + if (input.open(QIODevice::ReadOnly) + && output.open(QIODevice::WriteOnly)) { + output.write(input.readAll()); + const auto result = Spellchecker::UnpackDictionary(path, en); + output.remove(); + return result; + } + return false; +} + +rpl::producer ButtonManageDictsState( + not_null session) { + if (Platform::Spellchecker::IsSystemSpellchecker()) { + return rpl::single(QString()); + } + const auto computeString = [=] { + if (!session->settings().spellcheckerEnabled()) { + return QString(); + } + if (!session->settings().dictionariesEnabled().size()) { + return QString(); + } + const auto dicts = session->settings().dictionariesEnabled(); + const auto filtered = ranges::view::all( + dicts + ) | ranges::views::filter( + DictionaryExists + ) | ranges::to_vector; + const auto active = Platform::Spellchecker::ActiveLanguages(); + + return (active.size() == filtered.size()) + ? QString::number(filtered.size()) + : tr::lng_contacts_loading(tr::now); + }; + const auto emptyValue = [] { return rpl::empty_value(); }; + return rpl::single( + computeString() + ) | rpl::then( + rpl::merge( + Spellchecker::SupportedScriptsChanged(), + session->settings().dictionariesEnabledChanges( + ) | rpl::map(emptyValue), + session->settings().spellcheckerEnabledChanges( + ) | rpl::map(emptyValue) + ) | rpl::map(computeString) + ); +} + +std::vector DefaultLanguages() { + std::vector langs; + + const auto append = [&](const auto loc) { + const auto l = LanguageFromLocale(loc); + if (!ranges::contains(langs, l) && IsSupportedLang(l)) { + langs.push_back(l); + } + }; + + const auto method = QGuiApplication::inputMethod(); + langs.reserve(method ? 3 : 2); + if (method) { + append(method->locale()); + } + append(QLocale(Platform::SystemLanguage())); + append(QLocale(Lang::LanguageIdOrDefault(Lang::Current().id()))); + + return langs; +} + +void Start(not_null session) { + Spellchecker::SetPhrases({ { + { &ph::lng_spellchecker_submenu, tr::lng_spellchecker_submenu() }, + { &ph::lng_spellchecker_add, tr::lng_spellchecker_add() }, + { &ph::lng_spellchecker_remove, tr::lng_spellchecker_remove() }, + { &ph::lng_spellchecker_ignore, tr::lng_spellchecker_ignore() }, + } }); + const auto settings = &session->settings(); + + const auto guard = gsl::finally([=]{ + if (settings->spellcheckerEnabled()) { + Platform::Spellchecker::UpdateLanguages( + settings->dictionariesEnabled()); + } + }); + + if (Platform::Spellchecker::IsSystemSpellchecker()) { + return; + } + + Spellchecker::SetWorkingDirPath(DictionariesPath()); + + settings->dictionariesEnabledChanges( + ) | rpl::start_with_next([](auto dictionaries) { + Platform::Spellchecker::UpdateLanguages(dictionaries); + }, session->lifetime()); + + settings->spellcheckerEnabledChanges( + ) | rpl::start_with_next([=](auto enabled) { + Platform::Spellchecker::UpdateLanguages( + enabled + ? settings->dictionariesEnabled() + : std::vector()); + }, session->lifetime()); + + const auto method = QGuiApplication::inputMethod(); + + const auto connectInput = [=] { + if (!method || !settings->spellcheckerEnabled()) { + return; + } + auto callback = [=] { + if (BackgroundLoader) { + return; + } + const auto l = LanguageFromLocale(method->locale()); + if (!IsSupportedLang(l) || DictionaryExists(l)) { + return; + } + crl::on_main(session, [=] { + DownloadDictionaryInBackground(session, 0, { l }); + }); + }; + QObject::connect( + method, + &QInputMethod::localeChanged, + std::move(callback)); + }; + + if (settings->autoDownloadDictionaries()) { + session->data().contactsLoaded().changes( + ) | rpl::start_with_next([=](bool loaded) { + if (!loaded) { + return; + } + + DownloadDictionaryInBackground(session, 0, DefaultLanguages()); + }, session->lifetime()); + + connectInput(); + } + + rpl::combine( + settings->spellcheckerEnabledValue(), + settings->autoDownloadDictionariesValue() + ) | rpl::start_with_next([=](bool spell, bool download) { + if (spell && download) { + connectInput(); + return; + } + QObject::disconnect( + method, + &QInputMethod::localeChanged, + nullptr, + nullptr); + }, session->lifetime()); + +} + +} // namespace Spellchecker + +#endif // !TDESKTOP_DISABLE_SPELLCHECK diff --git a/Telegram/SourceFiles/chat_helpers/spellchecker_common.h b/Telegram/SourceFiles/chat_helpers/spellchecker_common.h new file mode 100644 index 000000000..203c4e047 --- /dev/null +++ b/Telegram/SourceFiles/chat_helpers/spellchecker_common.h @@ -0,0 +1,74 @@ +/* +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 + +#ifndef TDESKTOP_DISABLE_SPELLCHECK + +#include "storage/storage_cloud_blob.h" +#include "base/unique_qptr.h" + +namespace Main { +class Session; +} // namespace Main + +namespace Spellchecker { + +struct Dict : public Storage::CloudBlob::Blob { +}; + +int GetDownloadSize(int id); +MTP::DedicatedLoader::Location GetDownloadLocation(int id); + +[[nodiscard]] QString DictionariesPath(); +[[nodiscard]] QString DictPathByLangId(int langId); +bool UnpackDictionary(const QString &path, int langId); +[[nodiscard]] bool DictionaryExists(int langId); +bool RemoveDictionary(int langId); +[[nodiscard]] bool IsEn(int langId); + +bool WriteDefaultDictionary(); +std::vector Dictionaries(); + +void Start(not_null session); +[[nodiscard]] rpl::producer ButtonManageDictsState( + not_null session); + +std::vector DefaultLanguages(); + +class DictLoader : public Storage::CloudBlob::BlobLoader { +public: + DictLoader( + QObject *parent, + int id, + MTP::DedicatedLoader::Location location, + const QString &folder, + int size, + Fn destroyCallback); + + void destroy() override; + + rpl::lifetime &lifetime() { + return _lifetime; + } + +private: + void unpack(const QString &path) override; + void fail() override; + + Fn _destroyCallback; + + rpl::lifetime _lifetime; + +}; + +std::shared_ptr> GlobalLoader(); +rpl::producer GlobalLoaderChanged(); + +} // namespace Spellchecker + +#endif // !TDESKTOP_DISABLE_SPELLCHECK diff --git a/Telegram/SourceFiles/core/application.cpp b/Telegram/SourceFiles/core/application.cpp index 09ef3cf96..cb3c40911 100644 --- a/Telegram/SourceFiles/core/application.cpp +++ b/Telegram/SourceFiles/core/application.cpp @@ -230,10 +230,6 @@ void Application::run() { _window = std::make_unique(&activeAccount()); - const auto currentGeometry = _window->widget()->geometry(); - _mediaView = std::make_unique(); - _window->widget()->setGeometry(currentGeometry); - QCoreApplication::instance()->installEventFilter(this); connect( static_cast(QCoreApplication::instance()), @@ -262,8 +258,15 @@ void Application::run() { _window->setupIntro(); } } + + _window->widget()->show(); + + const auto currentGeometry = _window->widget()->geometry(); + _mediaView = std::make_unique(); + _window->widget()->setGeometry(currentGeometry); + DEBUG_LOG(("Application Info: showing.")); - _window->firstShow(); + _window->finishFirstShow(); if (!locked() && cStartToSettings()) { _window->showSettings(); @@ -297,6 +300,8 @@ void Application::showPhoto(not_null link) { } void Application::showPhoto(not_null photo, HistoryItem *item) { + Expects(_mediaView != nullptr); + _mediaView->showPhoto(photo, item); _mediaView->activateWindow(); _mediaView->setFocus(); @@ -305,12 +310,16 @@ void Application::showPhoto(not_null photo, HistoryItem *item) { void Application::showPhoto( not_null photo, not_null peer) { + Expects(_mediaView != nullptr); + _mediaView->showPhoto(photo, peer); _mediaView->activateWindow(); _mediaView->setFocus(); } void Application::showDocument(not_null document, HistoryItem *item) { + Expects(_mediaView != nullptr); + if (cUseExternalVideoPlayer() && document->isVideoFile() && document->loaded()) { @@ -325,6 +334,8 @@ void Application::showDocument(not_null document, HistoryItem *it void Application::showTheme( not_null document, const Data::CloudTheme &cloud) { + Expects(_mediaView != nullptr); + _mediaView->showTheme(document, cloud); _mediaView->activateWindow(); _mediaView->setFocus(); diff --git a/Telegram/SourceFiles/core/changelogs.cpp b/Telegram/SourceFiles/core/changelogs.cpp index c8c711ebb..783dd333f 100644 --- a/Telegram/SourceFiles/core/changelogs.cpp +++ b/Telegram/SourceFiles/core/changelogs.cpp @@ -55,6 +55,20 @@ std::map BetaLogs() { "\xE2\x80\xA2 Rotate photos and videos in the media viewer " "using the rotate button in the bottom right corner.\n" + }, + { + 1009015, + "\xE2\x80\xA2 Mark new messages as read " + "while scrolling down through them.\n" + + "\xE2\x80\xA2 Bug fixes and other minor improvements." + }, + + { + 1009017, + "\xE2\x80\xA2 Spell checker on Windows 7.\n" + + "\xE2\x80\xA2 Bug fixes and other minor improvements." } }; }; diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 93be97e10..4f9c505ac 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -22,8 +22,8 @@ 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 = 1009010; -constexpr auto AppVersionStr = "1.9.10"; +constexpr auto AppVersion = 1009019; +constexpr auto AppVersionStr = "1.9.19"; constexpr auto AppBetaVersion = true; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; constexpr auto AppKotatoVersion = 1001007; diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index d002653af..2eae51e78 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_folder.h" #include "data/data_location.h" +#include "data/data_histories.h" #include "lang/lang_keys.h" #include "base/unixtime.h" #include "history/history.h" @@ -709,13 +710,14 @@ void ApplyChannelUpdate( const auto folder = folderId ? channel->owner().folderLoaded(folderId) : nullptr; + auto &histories = channel->owner().histories(); if (folder && history->folder() != folder) { // If history folder is unknown or not synced, request both. - channel->session().api().requestDialogEntry(history); - channel->session().api().requestDialogEntry(folder); + histories.requestDialogEntry(history); + histories.requestDialogEntry(folder); } else if (!history->folderKnown() || channel->pts() != update.vpts().v) { - channel->session().api().requestDialogEntry(history); + histories.requestDialogEntry(history); } else { history->applyDialogFields( history->folder(), @@ -761,6 +763,9 @@ void ApplyChannelUpdate( channel->session().api().applyNotifySettings( MTP_inputNotifyPeer(channel->input), update.vnotify_settings()); + + // For clearUpTill() call. + channel->owner().sendHistoryChangeNotifications(); } void ApplyMegagroupAdmins( diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index 7aeb0d124..56b6d5ff6 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -569,7 +569,7 @@ void DocumentData::validateLottieSticker() { void DocumentData::setDataAndCache(const QByteArray &data) { setData(data); if (saveToCache() && data.size() <= Storage::kMaxFileInMemory) { - session().data().cache().put( + owner().cache().put( cacheKey(), Storage::Cache::Database::TaggedValue( base::duplicate(data), diff --git a/Telegram/SourceFiles/data/data_folder.cpp b/Telegram/SourceFiles/data/data_folder.cpp index b42ac116c..5229de94b 100644 --- a/Telegram/SourceFiles/data/data_folder.cpp +++ b/Telegram/SourceFiles/data/data_folder.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_channel.h" +#include "data/data_histories.h" #include "dialogs/dialogs_key.h" #include "history/history.h" #include "history/history_item.h" @@ -108,7 +109,7 @@ void Folder::registerOne(not_null history) { if (_chatsList.indexed()->size() == 1) { updateChatListSortPosition(); if (!_cloudUnread.known) { - session().api().requestDialogEntry(this); + owner().histories().requestDialogEntry(this); } } else { updateChatListEntry(); @@ -323,7 +324,7 @@ uint32 Folder::chatListViewVersion() const { void Folder::requestChatListMessage() { if (!chatListMessageKnown()) { - session().api().requestDialogEntry(this); + owner().histories().requestDialogEntry(this); } } diff --git a/Telegram/SourceFiles/data/data_histories.cpp b/Telegram/SourceFiles/data/data_histories.cpp new file mode 100644 index 000000000..ee3da9421 --- /dev/null +++ b/Telegram/SourceFiles/data/data_histories.cpp @@ -0,0 +1,683 @@ +/* +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_histories.h" + +#include "data/data_session.h" +#include "data/data_channel.h" +#include "data/data_folder.h" +#include "main/main_session.h" +#include "window/notifications_manager.h" +#include "history/history.h" +#include "history/history_item.h" +#include "history/view/history_view_element.h" +#include "core/application.h" +#include "apiwrap.h" + +namespace Data { +namespace { + +constexpr auto kReadRequestTimeout = 3 * crl::time(1000); + +} // namespace + +Histories::Histories(not_null owner) +: _owner(owner) +, _readRequestsTimer([=] { sendReadRequests(); }) { +} + +Session &Histories::owner() const { + return *_owner; +} + +Main::Session &Histories::session() const { + return _owner->session(); +} + +History *Histories::find(PeerId peerId) { + const auto i = peerId ? _map.find(peerId) : end(_map); + return (i != end(_map)) ? i->second.get() : nullptr; +} + +not_null Histories::findOrCreate(PeerId peerId) { + Expects(peerId != 0); + + if (const auto result = find(peerId)) { + return result; + } + const auto [i, ok] = _map.emplace( + peerId, + std::make_unique(&owner(), peerId)); + return i->second.get(); +} + +void Histories::unloadAll() { + for (const auto &[peerId, history] : _map) { + history->clear(History::ClearType::Unload); + } +} + +void Histories::clearAll() { + _map.clear(); +} + +void Histories::readInbox(not_null history) { + if (history->lastServerMessageKnown()) { + const auto last = history->lastServerMessage(); + readInboxTill(history, last ? last->id : 0); + return; + } else if (history->loadedAtBottom()) { + if (const auto lastId = history->maxMsgId()) { + readInboxTill(history, lastId); + return; + } else if (history->loadedAtTop()) { + readInboxTill(history, 0); + return; + } + } + requestDialogEntry(history, [=] { + Expects(history->lastServerMessageKnown()); + + const auto last = history->lastServerMessage(); + readInboxTill(history, last ? last->id : 0); + }); +} + +void Histories::readInboxTill(not_null item) { + const auto history = item->history(); + if (!IsServerMsgId(item->id)) { + readClientSideMessage(item); + auto view = item->mainView(); + if (!view) { + return; + } + auto block = view->block(); + auto blockIndex = block->indexInHistory(); + auto itemIndex = view->indexInBlock(); + while (blockIndex > 0 || itemIndex > 0) { + if (itemIndex > 0) { + view = block->messages[--itemIndex].get(); + } else { + while (blockIndex > 0) { + block = history->blocks[--blockIndex].get(); + itemIndex = block->messages.size(); + if (itemIndex > 0) { + view = block->messages[--itemIndex].get(); + break; + } + } + } + item = view->data(); + if (IsServerMsgId(item->id)) { + break; + } + } + if (!IsServerMsgId(item->id)) { + LOG(("App Error: " + "Can't read history till unknown local message.")); + return; + } + } + readInboxTill(history, item->id); +} + +void Histories::readInboxTill(not_null history, MsgId tillId) { + readInboxTill(history, tillId, false); +} + +void Histories::readInboxTill( + not_null history, + MsgId tillId, + bool force) { + Expects(IsServerMsgId(tillId) || (!tillId && !force)); + + const auto syncGuard = gsl::finally([&] { + if (history->unreadCount() > 0) { + if (const auto last = history->lastServerMessage()) { + if (last->id == tillId) { + history->setUnreadCount(0); + history->updateChatListEntry(); + } + } + } + }); + + history->session().notifications().clearIncomingFromHistory(history); + + const auto needsRequest = history->readInboxTillNeedsRequest(tillId); + if (!needsRequest && !force) { + return; + } else if (!history->trackUnreadMessages()) { + return; + } + const auto maybeState = lookup(history); + if (maybeState && maybeState->sentReadTill >= tillId) { + return; + } else if (maybeState && maybeState->willReadTill >= tillId) { + if (force) { + sendPendingReadInbox(history); + } + return; + } else if (!needsRequest + && (!maybeState || !maybeState->willReadTill)) { + return; + } + const auto stillUnread = history->countStillUnreadLocal(tillId); + if (!force + && stillUnread + && history->unreadCountKnown() + && *stillUnread == history->unreadCount()) { + history->setInboxReadTill(tillId); + return; + } + auto &state = maybeState ? *maybeState : _states[history]; + state.willReadTill = tillId; + if (force || !stillUnread || !*stillUnread) { + state.willReadWhen = 0; + sendReadRequests(); + if (!stillUnread) { + return; + } + } else if (!state.willReadWhen) { + state.willReadWhen = crl::now() + kReadRequestTimeout; + if (!_readRequestsTimer.isActive()) { + _readRequestsTimer.callOnce(kReadRequestTimeout); + } + } + history->setInboxReadTill(tillId); + history->setUnreadCount(*stillUnread); + history->updateChatListEntry(); +} + +void Histories::readInboxOnNewMessage(not_null item) { + if (!IsServerMsgId(item->id)) { + readClientSideMessage(item); + } else { + readInboxTill(item->history(), item->id, true); + } +} + +void Histories::readClientSideMessage(not_null item) { + if (item->out() || !item->unread()) { + return; + } + const auto history = item->history(); + item->markClientSideAsRead(); + if (const auto unread = history->unreadCount()) { + history->setUnreadCount(unread - 1); + } +} + +void Histories::requestDialogEntry(not_null folder) { + if (_dialogFolderRequests.contains(folder)) { + return; + } + _dialogFolderRequests.emplace(folder); + + auto peers = QVector( + 1, + MTP_inputDialogPeerFolder(MTP_int(folder->id()))); + session().api().request(MTPmessages_GetPeerDialogs( + MTP_vector(std::move(peers)) + )).done([=](const MTPmessages_PeerDialogs &result) { + applyPeerDialogs(result); + _dialogFolderRequests.remove(folder); + }).fail([=](const RPCError &error) { + _dialogFolderRequests.remove(folder); + }).send(); +} + +void Histories::requestDialogEntry( + not_null history, + Fn callback) { + const auto i = _dialogRequests.find(history); + if (i != end(_dialogRequests)) { + if (callback) { + i->second.push_back(std::move(callback)); + } + return; + } + + const auto [j, ok] = _dialogRequestsPending.try_emplace(history); + if (callback) { + j->second.push_back(std::move(callback)); + } + if (!ok) { + return; + } + postponeRequestDialogEntries(); +} + +void Histories::postponeRequestDialogEntries() { + if (_dialogRequestsPending.size() > 1) { + return; + } + Core::App().postponeCall(crl::guard(&session(), [=] { + sendDialogRequests(); + })); +} + +void Histories::sendDialogRequests() { + if (_dialogRequestsPending.empty()) { + return; + } + const auto histories = ranges::view::all( + _dialogRequestsPending + ) | ranges::view::transform([](const auto &pair) { + return pair.first; + }) | ranges::view::filter([&](not_null history) { + const auto state = lookup(history); + if (!state) { + return true; + } else if (!postponeEntryRequest(*state)) { + return true; + } + state->postponedRequestEntry = true; + return false; + }) | ranges::to_vector; + + auto peers = QVector(); + const auto dialogPeer = [](not_null history) { + return MTP_inputDialogPeer(history->peer->input); + }; + ranges::transform( + histories, + ranges::back_inserter(peers), + dialogPeer); + for (auto &[history, callbacks] : base::take(_dialogRequestsPending)) { + _dialogRequests.emplace(history, std::move(callbacks)); + } + + const auto finalize = [=] { + for (const auto history : histories) { + const auto state = lookup(history); + if (!state || !state->postponedRequestEntry) { + dialogEntryApplied(history); + history->updateChatListExistence(); + } + } + }; + session().api().request(MTPmessages_GetPeerDialogs( + MTP_vector(std::move(peers)) + )).done([=](const MTPmessages_PeerDialogs &result) { + applyPeerDialogs(result); + finalize(); + }).fail([=](const RPCError &error) { + finalize(); + }).send(); +} + +void Histories::dialogEntryApplied(not_null history) { + const auto state = lookup(history); + if (state && state->postponedRequestEntry) { + return; + } + history->dialogEntryApplied(); + if (const auto callbacks = _dialogRequestsPending.take(history)) { + for (const auto &callback : *callbacks) { + callback(); + } + } + if (const auto callbacks = _dialogRequests.take(history)) { + for (const auto &callback : *callbacks) { + callback(); + } + } + if (state && state->sentReadTill && state->sentReadDone) { + history->setInboxReadTill(base::take(state->sentReadTill)); + checkEmptyState(history); + } +} + +void Histories::applyPeerDialogs(const MTPmessages_PeerDialogs &dialogs) { + Expects(dialogs.type() == mtpc_messages_peerDialogs); + + const auto &data = dialogs.c_messages_peerDialogs(); + _owner->processUsers(data.vusers()); + _owner->processChats(data.vchats()); + _owner->processMessages(data.vmessages(), NewMessageType::Last); + for (const auto &dialog : data.vdialogs().v) { + dialog.match([&](const MTPDdialog &data) { + if (const auto peerId = peerFromMTP(data.vpeer())) { + _owner->history(peerId)->applyDialog(nullptr, data); + } + }, [&](const MTPDdialogFolder &data) { + const auto folder = _owner->processFolder(data.vfolder()); + folder->applyDialog(data); + }); + } + _owner->sendHistoryChangeNotifications(); +} + +void Histories::changeDialogUnreadMark( + not_null history, + bool unread) { + history->setUnreadMark(unread); + + using Flag = MTPmessages_MarkDialogUnread::Flag; + session().api().request(MTPmessages_MarkDialogUnread( + MTP_flags(unread ? Flag::f_unread : Flag(0)), + MTP_inputDialogPeer(history->peer->input) + )).send(); +} + +void Histories::requestFakeChatListMessage( + not_null history) { + if (_fakeChatListRequests.contains(history)) { + return; + } + + _fakeChatListRequests.emplace(history); + sendRequest(history, RequestType::History, [=](Fn finish) { + return session().api().request(MTPmessages_GetHistory( + history->peer->input, + MTP_int(0), // offset_id + MTP_int(0), // offset_date + MTP_int(0), // add_offset + MTP_int(2), // limit + MTP_int(0), // max_id + MTP_int(0), // min_id + MTP_int(0) + )).done([=](const MTPmessages_Messages &result) { + _fakeChatListRequests.erase(history); + history->setFakeChatListMessageFrom(result); + finish(); + }).fail([=](const RPCError &error) { + _fakeChatListRequests.erase(history); + history->setFakeChatListMessageFrom(MTP_messages_messages( + MTP_vector(0), + MTP_vector(0), + MTP_vector(0))); + finish(); + }).send(); + }); +} + +void Histories::sendPendingReadInbox(not_null history) { + if (const auto state = lookup(history)) { + if (state->willReadTill && state->willReadWhen) { + state->willReadWhen = 0; + sendReadRequests(); + } + } +} + +void Histories::sendReadRequests() { + if (_states.empty()) { + return; + } + const auto now = crl::now(); + auto next = std::optional(); + for (auto &[history, state] : _states) { + if (!state.willReadTill) { + continue; + } else if (state.willReadWhen <= now) { + sendReadRequest(history, state); + } else if (!next || *next > state.willReadWhen) { + next = state.willReadWhen; + } + } + if (next.has_value()) { + _readRequestsTimer.callOnce(*next - now); + } else { + _readRequestsTimer.cancel(); + } +} + +void Histories::sendReadRequest(not_null history, State &state) { + Expects(state.willReadTill > state.sentReadTill); + + const auto tillId = state.sentReadTill = base::take(state.willReadTill); + state.willReadWhen = 0; + state.sentReadDone = false; + sendRequest(history, RequestType::ReadInbox, [=](Fn finish) { + const auto finished = [=] { + const auto state = lookup(history); + Assert(state != nullptr); + + if (state->sentReadTill == tillId) { + state->sentReadDone = true; + if (history->unreadCountRefreshNeeded(tillId)) { + requestDialogEntry(history); + } else { + state->sentReadTill = 0; + } + } else { + Assert(!state->sentReadTill || state->sentReadTill > tillId); + } + sendReadRequests(); + finish(); + }; + if (const auto channel = history->peer->asChannel()) { + return session().api().request(MTPchannels_ReadHistory( + channel->inputChannel, + MTP_int(tillId) + )).done([=](const MTPBool &result) { + finished(); + }).fail([=](const RPCError &error) { + finished(); + }).send(); + } else { + return session().api().request(MTPmessages_ReadHistory( + history->peer->input, + MTP_int(tillId) + )).done([=](const MTPmessages_AffectedMessages &result) { + session().api().applyAffectedMessages(history->peer, result); + finished(); + }).fail([=](const RPCError &error) { + finished(); + }).send(); + } + }); +} + +void Histories::checkEmptyState(not_null history) { + const auto empty = [](const State &state) { + return state.postponed.empty() + && !state.postponedRequestEntry + && state.sent.empty() + && (state.willReadTill == 0) + && (state.sentReadTill == 0); + }; + const auto i = _states.find(history); + if (i != end(_states) && empty(i->second)) { + _states.erase(i); + } +} + +bool Histories::postponeHistoryRequest(const State &state) const { + const auto proj = [](const auto &pair) { + return pair.second.type; + }; + const auto i = ranges::find(state.sent, RequestType::Delete, proj); + return (i != end(state.sent)); +} + +bool Histories::postponeEntryRequest(const State &state) const { + const auto i = ranges::find_if(state.sent, [](const auto &pair) { + return pair.second.type != RequestType::History; + }); + return (i != end(state.sent)); +} + +void Histories::deleteMessages( + not_null history, + const QVector &ids, + bool revoke) { + sendRequest(history, RequestType::Delete, [=](Fn finish) { + const auto done = [=](const MTPmessages_AffectedMessages &result) { + session().api().applyAffectedMessages(history->peer, result); + finish(); + history->requestChatListMessage(); + }; + const auto fail = [=](const RPCError &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(); + } 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(); + } + }); +} + +void Histories::deleteAllMessages( + not_null history, + MsgId deleteTillId, + bool justClear, + bool revoke) { + sendRequest(history, RequestType::Delete, [=](Fn finish) { + const auto peer = history->peer; + const auto fail = [=](const RPCError &error) { + finish(); + }; + if (const auto channel = peer->asChannel()) { + return session().api().request(MTPchannels_DeleteHistory( + channel->inputChannel, + MTP_int(deleteTillId) + )).done([=](const MTPBool &result) { + finish(); + }).fail(fail).send(); + } else { + using Flag = MTPmessages_DeleteHistory::Flag; + const auto flags = Flag(0) + | (justClear ? Flag::f_just_clear : Flag(0)) + | ((peer->isUser() && revoke) ? Flag::f_revoke : Flag(0)); + return session().api().request(MTPmessages_DeleteHistory( + MTP_flags(flags), + peer->input, + MTP_int(0) + )).done([=](const MTPmessages_AffectedHistory &result) { + const auto offset = session().api().applyAffectedHistory( + peer, + result); + if (offset > 0) { + deleteAllMessages( + history, + deleteTillId, + justClear, + revoke); + } + finish(); + }).fail(fail).send(); + } + }); +} + +int Histories::sendRequest( + not_null history, + RequestType type, + Fn finish)> generator) { + Expects(type != RequestType::None); + + auto &state = _states[history]; + const auto id = ++_requestAutoincrement; + _historyByRequest.emplace(id, history); + if (type == RequestType::History && postponeHistoryRequest(state)) { + state.postponed.emplace( + id, + PostponedHistoryRequest{ std::move(generator) }); + return id; + } + const auto requestId = generator([=] { checkPostponed(history, id); }); + state.sent.emplace(id, SentRequest{ + std::move(generator), + requestId, + type + }); + if (!state.postponedRequestEntry + && postponeEntryRequest(state) + && _dialogRequests.contains(history)) { + state.postponedRequestEntry = true; + } + if (postponeHistoryRequest(state)) { + const auto resendHistoryRequest = [&](auto &pair) { + auto &[id, sent] = pair; + if (sent.type != RequestType::History) { + return false; + } + state.postponed.emplace( + id, + PostponedHistoryRequest{ std::move(sent.generator) }); + session().api().request(sent.id).cancel(); + return true; + }; + state.sent.erase( + ranges::remove_if(state.sent, resendHistoryRequest), + end(state.sent)); + } + return id; +} + +void Histories::checkPostponed(not_null history, int id) { + if (const auto state = lookup(history)) { + finishSentRequest(history, state, id); + } +} + +void Histories::cancelRequest(int id) { + const auto history = _historyByRequest.take(id); + if (!history) { + return; + } + const auto state = lookup(*history); + if (!state) { + return; + } + state->postponed.remove(id); + finishSentRequest(*history, state, id); +} + +void Histories::finishSentRequest( + not_null history, + not_null state, + int id) { + _historyByRequest.remove(id); + const auto i = state->sent.find(id); + if (i != end(state->sent)) { + session().api().request(i->second.id).cancel(); + state->sent.erase(i); + } + if (!state->postponed.empty() && !postponeHistoryRequest(*state)) { + for (auto &[id, postponed] : base::take(state->postponed)) { + const auto requestId = postponed.generator([=, id=id] { + checkPostponed(history, id); + }); + state->sent.emplace(id, SentRequest{ + std::move(postponed.generator), + requestId, + RequestType::History + }); + } + } + if (state->postponedRequestEntry && !postponeEntryRequest(*state)) { + const auto i = _dialogRequests.find(history); + Assert(i != end(_dialogRequests)); + const auto [j, ok] = _dialogRequestsPending.emplace( + history, + std::move(i->second)); + Assert(ok); + _dialogRequests.erase(i); + state->postponedRequestEntry = false; + postponeRequestDialogEntries(); + } + checkEmptyState(history); +} + +Histories::State *Histories::lookup(not_null history) { + const auto i = _states.find(history); + return (i != end(_states)) ? &i->second : nullptr; +} + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_histories.h b/Telegram/SourceFiles/data/data_histories.h new file mode 100644 index 000000000..61079634d --- /dev/null +++ b/Telegram/SourceFiles/data/data_histories.h @@ -0,0 +1,133 @@ +/* +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/timer.h" + +class History; +class HistoryItem; + +namespace Main { +class Session; +} // namespace Main + +namespace Data { + +class Session; +class Folder; + +class Histories final { +public: + enum class RequestType : uchar { + None, + History, + ReadInbox, + Delete, + Send, + }; + + explicit Histories(not_null owner); + + [[nodiscard]] Session &owner() const; + [[nodiscard]] Main::Session &session() const; + + [[nodiscard]] History *find(PeerId peerId); + [[nodiscard]] not_null findOrCreate(PeerId peerId); + + void unloadAll(); + void clearAll(); + + void readInbox(not_null history); + void readInboxTill(not_null item); + void readInboxTill(not_null history, MsgId tillId); + void readInboxOnNewMessage(not_null item); + void readClientSideMessage(not_null item); + void sendPendingReadInbox(not_null history); + + void requestDialogEntry(not_null folder); + void requestDialogEntry( + not_null history, + Fn callback = nullptr); + void dialogEntryApplied(not_null history); + void changeDialogUnreadMark(not_null history, bool unread); + //void changeDialogUnreadMark(not_null feed, bool unread); // #feed + void requestFakeChatListMessage(not_null history); + + void deleteMessages( + not_null history, + const QVector &ids, + bool revoke); + void deleteAllMessages( + not_null history, + MsgId deleteTillId, + bool justClear, + bool revoke); + + int sendRequest( + not_null history, + RequestType type, + Fn finish)> generator); + void cancelRequest(int id); + +private: + struct PostponedHistoryRequest { + Fn finish)> generator; + }; + struct SentRequest { + Fn finish)> generator; + mtpRequestId id = 0; + RequestType type = RequestType::None; + }; + struct State { + base::flat_map postponed; + base::flat_map sent; + MsgId willReadTill = 0; + MsgId sentReadTill = 0; + crl::time willReadWhen = 0; + bool sentReadDone = false; + bool postponedRequestEntry = false; + }; + + void readInboxTill(not_null history, MsgId tillId, bool force); + void sendReadRequests(); + void sendReadRequest(not_null history, State &state); + [[nodiscard]] State *lookup(not_null history); + void checkEmptyState(not_null history); + void checkPostponed(not_null history, int id); + void finishSentRequest( + not_null history, + not_null state, + int id); + [[nodiscard]] bool postponeHistoryRequest(const State &state) const; + [[nodiscard]] bool postponeEntryRequest(const State &state) const; + void postponeRequestDialogEntries(); + + void sendDialogRequests(); + void applyPeerDialogs(const MTPmessages_PeerDialogs &dialogs); + + const not_null _owner; + + std::unordered_map> _map; + base::flat_map, State> _states; + base::flat_map> _historyByRequest; + int _requestAutoincrement = 0; + base::Timer _readRequestsTimer; + + base::flat_set> _dialogFolderRequests; + base::flat_map< + not_null, + std::vector>> _dialogRequests; + base::flat_map< + not_null, + std::vector>> _dialogRequestsPending; + + base::flat_set> _fakeChatListRequests; + +}; + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 8ccfa500b..1044fcc3c 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_folder.h" #include "data/data_session.h" #include "data/data_file_origin.h" +#include "data/data_histories.h" #include "base/unixtime.h" #include "base/crc32hash.h" #include "lang/lang_keys.h" @@ -457,7 +458,7 @@ void PeerData::checkFolder(FolderId folderId) { : nullptr; if (const auto history = owner().historyLoaded(this)) { if (folder && history->folder() != folder) { - session().api().requestDialogEntry(history); + owner().histories().requestDialogEntry(history); } } } diff --git a/Telegram/SourceFiles/data/data_search_controller.cpp b/Telegram/SourceFiles/data/data_search_controller.cpp index 243654287..90fbd3676 100644 --- a/Telegram/SourceFiles/data/data_search_controller.cpp +++ b/Telegram/SourceFiles/data/data_search_controller.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_messages.h" #include "data/data_channel.h" +#include "data/data_histories.h" #include "history/history.h" #include "history/history_item.h" #include "apiwrap.h" @@ -193,7 +194,7 @@ SearchController::CacheEntry::CacheEntry(const Query &query) } SearchController::SearchController(not_null session) -: _api(session->api().instance()) { +: _session(session) { } bool SearchController::hasInCache(const Query &query) const { @@ -366,23 +367,31 @@ void SearchController::requestMore( if (!prepared) { return; } - auto requestId = _api.request( - std::move(*prepared) - ).done([=](const MTPmessages_Messages &result) { - listData->requests.remove(key); - auto parsed = ParseSearchResult( - listData->peer, - query.type, - key.aroundId, - key.direction, - result); - listData->list.addSlice( - std::move(parsed.messageIds), - parsed.noSkipRange, - parsed.fullCount); - }).send(); + auto &histories = _session->data().histories(); + const auto type = ::Data::Histories::RequestType::History; + const auto history = _session->data().history(listData->peer); + auto requestId = histories.sendRequest(history, type, [=](Fn finish) { + return _session->api().request( + std::move(*prepared) + ).done([=](const MTPmessages_Messages &result) { + listData->requests.remove(key); + auto parsed = ParseSearchResult( + listData->peer, + query.type, + key.aroundId, + key.direction, + result); + listData->list.addSlice( + std::move(parsed.messageIds), + parsed.noSkipRange, + parsed.fullCount); + finish(); + }).fail([=](const RPCError &error) { + finish(); + }).send(); + }); listData->requests.emplace(key, [=] { - _api.request(requestId).cancel(); + _session->data().histories().cancelRequest(requestId); }); } diff --git a/Telegram/SourceFiles/data/data_search_controller.h b/Telegram/SourceFiles/data/data_search_controller.h index fcc2b8853..f9341ac4d 100644 --- a/Telegram/SourceFiles/data/data_search_controller.h +++ b/Telegram/SourceFiles/data/data_search_controller.h @@ -129,7 +129,7 @@ private: const Query &query, Data *listData); - MTP::Sender _api; + const not_null _session; Cache _cache; Cache::iterator _current = _cache.end(); diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 6638aa870..7fa9f0a06 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -48,6 +48,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_cloud_themes.h" #include "data/data_streaming.h" #include "data/data_media_rotation.h" +#include "data/data_histories.h" #include "base/platform/base_platform_info.h" #include "base/unixtime.h" #include "base/call_delayed.h" @@ -194,7 +195,8 @@ Session::Session(not_null session) , _scheduledMessages(std::make_unique(this)) , _cloudThemes(std::make_unique(session)) , _streaming(std::make_unique(this)) -, _mediaRotation(std::make_unique()) { +, _mediaRotation(std::make_unique()) +, _histories(std::make_unique(this)) { _cache->open(Local::cacheKey()); _bigFileCache->open(Local::cacheBigFileKey()); @@ -215,9 +217,7 @@ Session::Session(not_null session) void Session::clear() { _sendActions.clear(); - for (const auto &[peerId, history] : _histories) { - history->clear(History::ClearType::Unload); - } + _histories->unloadAll(); _scheduledMessages = nullptr; _dependentMessages.clear(); base::take(_messages); @@ -227,7 +227,7 @@ void Session::clear() { cSetRecentInlineBots(RecentInlineBots()); cSetRecentStickers(RecentStickerPack()); App::clearMousedItems(); - _histories.clear(); + _histories->clearAll(); } not_null Session::peer(PeerId id) { @@ -768,20 +768,11 @@ void Session::enumerateChannels( } not_null Session::history(PeerId peerId) { - Expects(peerId != 0); - - if (const auto result = historyLoaded(peerId)) { - return result; - } - const auto [i, ok] = _histories.emplace( - peerId, - std::make_unique(this, peerId)); - return i->second.get(); + return _histories->findOrCreate(peerId); } History *Session::historyLoaded(PeerId peerId) const { - const auto i = peerId ? _histories.find(peerId) : end(_histories); - return (i != end(_histories)) ? i->second.get() : nullptr; + return _histories->find(peerId); } not_null Session::history(not_null peer) { @@ -1539,7 +1530,7 @@ void Session::applyDialog( return; } - const auto history = session().data().history(peerId); + const auto history = this->history(peerId); history->applyDialog(requestFolder, data); setPinnedFromDialog(history, data.is_pinned()); @@ -1733,18 +1724,15 @@ auto Session::messagesListForInsert(ChannelId channelId) : &_channelMessages[channelId]; } -HistoryItem *Session::registerMessage(std::unique_ptr item) { - Expects(item != nullptr); - - const auto result = item.get(); - const auto list = messagesListForInsert(result->channelId()); - const auto i = list->find(result->id); +void Session::registerMessage(not_null item) { + const auto list = messagesListForInsert(item->channelId()); + const auto itemId = item->id; + const auto i = list->find(itemId); if (i != list->end()) { LOG(("App Error: Trying to re-registerMessage().")); i->second->destroy(); } - list->emplace(result->id, std::move(item)); - return result; + list->emplace(itemId, item); } void Session::processMessagesDeleted( @@ -1763,7 +1751,7 @@ void Session::processMessagesDeleted( const auto i = list ? list->find(messageId.v) : Messages::iterator(); if (list && i != list->end()) { const auto history = i->second->history(); - destroyMessage(i->second.get()); + i->second->destroy(); if (!history->chatListMessageKnown()) { historiesToCheck.emplace(history); } @@ -1789,32 +1777,12 @@ void Session::removeDependencyMessage(not_null item) { } } -void Session::destroyMessage(not_null item) { - Expects(item->isHistoryEntry() || !item->mainView()); - +void Session::unregisterMessage(not_null item) { const auto peerId = item->history()->peer->id; - if (item->isHistoryEntry()) { - // All this must be done for all items manually in History::clear()! - item->eraseFromUnreadMentions(); - if (IsServerMsgId(item->id)) { - if (const auto types = item->sharedMediaTypes()) { - session().storage().remove(Storage::SharedMediaRemoveOne( - peerId, - types, - item->id)); - } - } else { - session().api().cancelLocalItem(item); - } - item->history()->itemRemoved(item); - } _itemRemoved.fire_copy(item); groups().unregisterMessage(item); removeDependencyMessage(item); - session().notifications().clearFromItem(item); - - const auto list = messagesListForInsert(peerToChannel(peerId)); - list->erase(item->id); + messagesListForInsert(peerToChannel(peerId))->erase(item->id); } MsgId Session::nextLocalMessageId() { @@ -3629,7 +3597,7 @@ void Session::serviceNotification( } const auto history = this->history(PeerData::kServiceNotificationsId); if (!history->folderKnown()) { - _session->api().requestDialogEntry(history, [=] { + histories().requestDialogEntry(history, [=] { insertCheckedServiceNotification(message, media, date); }); } else { diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 9b24d5294..f7d7a95f7 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -61,6 +61,7 @@ class ScheduledMessages; class CloudThemes; class Streaming; class MediaRotation; +class Histories; class Session final { public: @@ -96,6 +97,9 @@ public: [[nodiscard]] MediaRotation &mediaRotation() const { return *_mediaRotation; } + [[nodiscard]] Histories &histories() const { + return *_histories; + } [[nodiscard]] MsgId nextNonHistoryEntryId() { return ++_nonHistoryEntryId; } @@ -367,22 +371,8 @@ public: const Dialogs::Key &key1, const Dialogs::Key &key2); - template - not_null makeMessage(Args &&...args) { - return static_cast( - registerMessage( - std::make_unique( - std::forward(args)...))); - } - - template - not_null makeServiceMessage(Args &&...args) { - return static_cast( - registerMessage( - std::make_unique( - std::forward(args)...))); - } - void destroyMessage(not_null item); + void registerMessage(not_null item); + void unregisterMessage(not_null item); // Returns true if item found and it is not detached. bool checkEntitiesAndViewsUpdate(const MTPDmessage &data); @@ -694,7 +684,7 @@ public: void clearLocalStorage(); private: - using Messages = std::unordered_map>; + using Messages = std::unordered_map>; void suggestStartExport(); @@ -715,7 +705,8 @@ private: const Messages *messagesList(ChannelId channelId) const; not_null messagesListForInsert(ChannelId channelId); - HistoryItem *registerMessage(std::unique_ptr item); + not_null registerMessage( + std::unique_ptr item); void changeMessageId(ChannelId channel, MsgId wasId, MsgId nowId); void removeDependencyMessage(not_null item); @@ -968,7 +959,6 @@ private: base::Timer _unmuteByFinishedTimer; std::unordered_map> _peers; - std::unordered_map> _histories; MessageIdsList _mimeForwardIds; @@ -989,6 +979,7 @@ private: std::unique_ptr _cloudThemes; std::unique_ptr _streaming; std::unique_ptr _mediaRotation; + std::unique_ptr _histories; MsgId _nonHistoryEntryId = ServerMaxMsgId; rpl::lifetime _lifetime; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index dad87cb7d..5175955be 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_chat.h" #include "data/data_user.h" #include "data/data_peer_values.h" +#include "data/data_histories.h" #include "base/unixtime.h" #include "lang/lang_keys.h" #include "mainwindow.h" @@ -2071,7 +2072,7 @@ bool InnerWidget::searchReceived( _searchInChat, item)); if (uniquePeers && !history->unreadCountKnown()) { - history->session().api().requestDialogEntry(history); + history->owner().histories().requestDialogEntry(history); } } lastDateFound = lastDate; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index bf2124ac8..58ec287fa 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -38,6 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_chat.h" #include "data/data_user.h" #include "data/data_folder.h" +#include "data/data_histories.h" #include "facades.h" #include "app.h" #include "styles/style_dialogs.h" @@ -392,6 +393,7 @@ void Widget::fullSearchRefreshOn(rpl::producer<> events) { _searchQueries.clear(); _searchQuery = QString(); _scroll->scrollToY(0); + cancelSearchRequest(); onSearchMessages(); }, lifetime()); } @@ -736,7 +738,7 @@ bool Widget::onSearchMessages(bool searchCache) { auto result = false; auto q = _filter->getLastText().trimmed(); if (q.isEmpty() && !_searchFromUser) { - MTP::cancel(base::take(_searchRequest)); + cancelSearchRequest(); MTP::cancel(base::take(_peerSearchRequest)); return true; } @@ -753,7 +755,7 @@ bool Widget::onSearchMessages(bool searchCache) { _searchQueryFrom = _searchFromUser; _searchNextRate = 0; _searchFull = _searchFullMigrated = false; - MTP::cancel(base::take(_searchRequest)); + cancelSearchRequest(); searchReceived( _searchInChat ? SearchRequestType::PeerFromStart @@ -767,19 +769,23 @@ bool Widget::onSearchMessages(bool searchCache) { _searchQueryFrom = _searchFromUser; _searchNextRate = 0; _searchFull = _searchFullMigrated = false; - MTP::cancel(base::take(_searchRequest)); + cancelSearchRequest(); if (const auto peer = _searchInChat.peer()) { - const auto flags = _searchQueryFrom - ? MTP_flags(MTPmessages_Search::Flag::f_from_id) - : MTP_flags(0); - _searchRequest = MTP::send( - MTPmessages_Search( + auto &histories = session().data().histories(); + const auto type = Data::Histories::RequestType::History; + const auto history = session().data().history(peer); + _searchInHistoryRequest = histories.sendRequest(history, type, [=](Fn finish) { + const auto type = SearchRequestType::PeerFromStart; + const auto flags = _searchQueryFrom + ? MTP_flags(MTPmessages_Search::Flag::f_from_id) + : MTP_flags(0); + _searchRequest = session().api().request(MTPmessages_Search( flags, peer->input, MTP_string(_searchQuery), - _searchQueryFrom + (_searchQueryFrom ? _searchQueryFrom->inputUser - : MTP_inputUserEmpty(), + : MTP_inputUserEmpty()), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), @@ -788,9 +794,19 @@ bool Widget::onSearchMessages(bool searchCache) { MTP_int(SearchPerPage), MTP_int(0), MTP_int(0), - MTP_int(0)), - rpcDone(&Widget::searchReceived, SearchRequestType::PeerFromStart), - rpcFail(&Widget::searchFailed, SearchRequestType::PeerFromStart)); + MTP_int(0) + )).done([=](const MTPmessages_Messages &result) { + searchReceived(type, result, _searchRequest); + _searchInHistoryRequest = 0; + finish(); + }).fail([=](const RPCError &error) { + searchFailed(type, error, _searchRequest); + _searchInHistoryRequest = 0; + finish(); + }).send(); + _searchQueries.insert(_searchRequest, _searchQuery); + return _searchRequest; + }); //} else if (const auto feed = _searchInChat.feed()) { // #feed // _searchRequest = MTP::send( // MTPchannels_SearchFeed( @@ -802,6 +818,7 @@ bool Widget::onSearchMessages(bool searchCache) { // MTP_int(SearchPerPage)), // rpcDone(&Widget::searchReceived, SearchRequestType::FromStart), // rpcFail(&Widget::searchFailed, SearchRequestType::FromStart)); + // _searchQueries.insert(_searchRequest, _searchQuery); } else { const auto flags = session().settings().skipArchiveInSearch() ? MTPmessages_SearchGlobal::Flag::f_folder_id @@ -818,8 +835,8 @@ bool Widget::onSearchMessages(bool searchCache) { MTP_int(SearchPerPage)), rpcDone(&Widget::searchReceived, SearchRequestType::FromStart), rpcFail(&Widget::searchFailed, SearchRequestType::FromStart)); + _searchQueries.insert(_searchRequest, _searchQuery); } - _searchQueries.insert(_searchRequest, _searchQuery); } const auto query = Api::ConvertPeerSearchQuery(q); if (searchForPeersRequired(query)) { @@ -906,93 +923,130 @@ void Widget::searchMessages( } void Widget::onSearchMore() { - if (!_searchRequest) { - if (!_searchFull) { - auto offsetPeer = _inner->lastSearchPeer(); - auto offsetId = _inner->lastSearchId(); - if (const auto peer = _searchInChat.peer()) { + if (_searchRequest || _searchInHistoryRequest) { + return; + } + if (!_searchFull) { + auto offsetPeer = _inner->lastSearchPeer(); + auto offsetId = _inner->lastSearchId(); + if (const auto peer = _searchInChat.peer()) { + auto &histories = session().data().histories(); + const auto type = Data::Histories::RequestType::History; + const auto history = session().data().history(peer); + _searchInHistoryRequest = histories.sendRequest(history, type, [=](Fn finish) { + const auto type = offsetId + ? SearchRequestType::PeerFromOffset + : SearchRequestType::PeerFromStart; auto flags = _searchQueryFrom ? MTP_flags(MTPmessages_Search::Flag::f_from_id) : MTP_flags(0); - _searchRequest = MTP::send( - MTPmessages_Search( - flags, - peer->input, - MTP_string(_searchQuery), - _searchQueryFrom - ? _searchQueryFrom->inputUser - : MTP_inputUserEmpty(), - MTP_inputMessagesFilterEmpty(), - MTP_int(0), - MTP_int(0), - MTP_int(offsetId), - MTP_int(0), - MTP_int(SearchPerPage), - MTP_int(0), - MTP_int(0), - MTP_int(0)), - rpcDone(&Widget::searchReceived, offsetId ? SearchRequestType::PeerFromOffset : SearchRequestType::PeerFromStart), - rpcFail(&Widget::searchFailed, offsetId ? SearchRequestType::PeerFromOffset : SearchRequestType::PeerFromStart)); - //} else if (const auto feed = _searchInChat.feed()) { // #feed - // _searchRequest = MTP::send( - // MTPchannels_SearchFeed( - // MTP_int(feed->id()), - // MTP_string(_searchQuery), - // MTP_int(offsetDate), - // offsetPeer - // ? offsetPeer->input - // : MTP_inputPeerEmpty(), - // MTP_int(offsetId), - // MTP_int(SearchPerPage)), - // rpcDone(&Widget::searchReceived, offsetId ? SearchRequestType::FromOffset : SearchRequestType::FromStart), - // rpcFail(&Widget::searchFailed, offsetId ? SearchRequestType::FromOffset : SearchRequestType::FromStart)); - } else { - const auto flags = session().settings().skipArchiveInSearch() - ? MTPmessages_SearchGlobal::Flag::f_folder_id - : MTPmessages_SearchGlobal::Flag(0); - const auto folderId = 0; - _searchRequest = MTP::send( - MTPmessages_SearchGlobal( - MTP_flags(flags), - MTP_int(folderId), - MTP_string(_searchQuery), - MTP_int(_searchNextRate), - offsetPeer - ? offsetPeer->input - : MTP_inputPeerEmpty(), - MTP_int(offsetId), - MTP_int(SearchPerPage)), - rpcDone(&Widget::searchReceived, offsetId ? SearchRequestType::FromOffset : SearchRequestType::FromStart), - rpcFail(&Widget::searchFailed, offsetId ? SearchRequestType::FromOffset : SearchRequestType::FromStart)); - } - if (!offsetId) { - _searchQueries.insert(_searchRequest, _searchQuery); - } - } else if (_searchInMigrated && !_searchFullMigrated) { - auto offsetMigratedId = _inner->lastSearchMigratedId(); - auto flags = _searchQueryFrom - ? MTP_flags(MTPmessages_Search::Flag::f_from_id) - : MTP_flags(0); - _searchRequest = MTP::send( - MTPmessages_Search( + _searchRequest = session().api().request(MTPmessages_Search( flags, - _searchInMigrated->peer->input, + peer->input, MTP_string(_searchQuery), - _searchQueryFrom + (_searchQueryFrom ? _searchQueryFrom->inputUser - : MTP_inputUserEmpty(), + : MTP_inputUserEmpty()), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), - MTP_int(offsetMigratedId), + MTP_int(offsetId), MTP_int(0), MTP_int(SearchPerPage), MTP_int(0), MTP_int(0), - MTP_int(0)), - rpcDone(&Widget::searchReceived, offsetMigratedId ? SearchRequestType::MigratedFromOffset : SearchRequestType::MigratedFromStart), - rpcFail(&Widget::searchFailed, offsetMigratedId ? SearchRequestType::MigratedFromOffset : SearchRequestType::MigratedFromStart)); + MTP_int(0) + )).done([=](const MTPmessages_Messages &result) { + searchReceived(type, result, _searchRequest); + _searchInHistoryRequest = 0; + finish(); + }).fail([=](const RPCError &error) { + searchFailed(type, error, _searchRequest); + _searchInHistoryRequest = 0; + finish(); + }).send(); + if (!offsetId) { + _searchQueries.insert(_searchRequest, _searchQuery); + } + return _searchRequest; + }); + //} else if (const auto feed = _searchInChat.feed()) { // #feed + // _searchRequest = MTP::send( + // MTPchannels_SearchFeed( + // MTP_int(feed->id()), + // MTP_string(_searchQuery), + // MTP_int(offsetDate), + // offsetPeer + // ? offsetPeer->input + // : MTP_inputPeerEmpty(), + // MTP_int(offsetId), + // MTP_int(SearchPerPage)), + // rpcDone(&Widget::searchReceived, offsetId ? SearchRequestType::FromOffset : SearchRequestType::FromStart), + // rpcFail(&Widget::searchFailed, offsetId ? SearchRequestType::FromOffset : SearchRequestType::FromStart)); + // if (!offsetId) { + // _searchQueries.insert(_searchRequest, _searchQuery); + // } + } else { + const auto flags = session().settings().skipArchiveInSearch() + ? MTPmessages_SearchGlobal::Flag::f_folder_id + : MTPmessages_SearchGlobal::Flag(0); + const auto folderId = 0; + _searchRequest = MTP::send( + MTPmessages_SearchGlobal( + MTP_flags(flags), + MTP_int(folderId), + MTP_string(_searchQuery), + MTP_int(_searchNextRate), + offsetPeer + ? offsetPeer->input + : MTP_inputPeerEmpty(), + MTP_int(offsetId), + MTP_int(SearchPerPage)), + rpcDone(&Widget::searchReceived, offsetId ? SearchRequestType::FromOffset : SearchRequestType::FromStart), + rpcFail(&Widget::searchFailed, offsetId ? SearchRequestType::FromOffset : SearchRequestType::FromStart)); + if (!offsetId) { + _searchQueries.insert(_searchRequest, _searchQuery); + } } + } else if (_searchInMigrated && !_searchFullMigrated) { + auto offsetMigratedId = _inner->lastSearchMigratedId(); + auto &histories = session().data().histories(); + const auto type = Data::Histories::RequestType::History; + const auto history = _searchInMigrated; + _searchInHistoryRequest = histories.sendRequest(history, type, [=](Fn finish) { + const auto type = offsetMigratedId + ? SearchRequestType::MigratedFromOffset + : SearchRequestType::MigratedFromStart; + const auto flags = _searchQueryFrom + ? MTP_flags(MTPmessages_Search::Flag::f_from_id) + : MTP_flags(0); + _searchRequest = session().api().request(MTPmessages_Search( + flags, + _searchInMigrated->peer->input, + MTP_string(_searchQuery), + (_searchQueryFrom + ? _searchQueryFrom->inputUser + : MTP_inputUserEmpty()), + MTP_inputMessagesFilterEmpty(), + MTP_int(0), + MTP_int(0), + MTP_int(offsetMigratedId), + MTP_int(0), + MTP_int(SearchPerPage), + MTP_int(0), + MTP_int(0), + MTP_int(0) + )).done([=](const MTPmessages_Messages &result) { + searchReceived(type, result, _searchRequest); + _searchInHistoryRequest = 0; + finish(); + }).fail([=](const RPCError &error) { + searchFailed(type, error, _searchRequest); + _searchInHistoryRequest = 0; + finish(); + }).send(); + return _searchRequest; + }); } } @@ -1015,94 +1069,95 @@ void Widget::searchReceived( ? *_singleMessageSearch.lookup(_searchQuery) : nullptr; - if (_searchRequest == requestId) { - switch (result.type()) { - case mtpc_messages_messages: { - auto &d = result.c_messages_messages(); - if (_searchRequest != 0) { - // Don't apply cached data! - session().data().processUsers(d.vusers()); - session().data().processChats(d.vchats()); - } - auto &msgs = d.vmessages().v; - _inner->searchReceived(msgs, inject, type, msgs.size()); + if (_searchRequest != requestId) { + return; + } + switch (result.type()) { + case mtpc_messages_messages: { + auto &d = result.c_messages_messages(); + if (_searchRequest != 0) { + // Don't apply cached data! + session().data().processUsers(d.vusers()); + session().data().processChats(d.vchats()); + } + auto &msgs = d.vmessages().v; + _inner->searchReceived(msgs, inject, type, msgs.size()); + if (type == SearchRequestType::MigratedFromStart || type == SearchRequestType::MigratedFromOffset) { + _searchFullMigrated = true; + } else { + _searchFull = true; + } + } break; + + case mtpc_messages_messagesSlice: { + auto &d = result.c_messages_messagesSlice(); + if (_searchRequest != 0) { + // Don't apply cached data! + session().data().processUsers(d.vusers()); + session().data().processChats(d.vchats()); + } + auto &msgs = d.vmessages().v; + const auto someAdded = _inner->searchReceived(msgs, inject, type, d.vcount().v); + const auto nextRate = d.vnext_rate(); + const auto rateUpdated = nextRate && (nextRate->v != _searchNextRate); + const auto finished = (type == SearchRequestType::FromStart || type == SearchRequestType::FromOffset) + ? !rateUpdated + : !someAdded; + if (rateUpdated) { + _searchNextRate = nextRate->v; + } + if (finished) { if (type == SearchRequestType::MigratedFromStart || type == SearchRequestType::MigratedFromOffset) { _searchFullMigrated = true; } else { _searchFull = true; } - } break; + } + } break; - case mtpc_messages_messagesSlice: { - auto &d = result.c_messages_messagesSlice(); - if (_searchRequest != 0) { - // Don't apply cached data! - session().data().processUsers(d.vusers()); - session().data().processChats(d.vchats()); - } - auto &msgs = d.vmessages().v; - const auto someAdded = _inner->searchReceived(msgs, inject, type, d.vcount().v); - const auto nextRate = d.vnext_rate(); - const auto rateUpdated = nextRate && (nextRate->v != _searchNextRate); - const auto finished = (type == SearchRequestType::FromStart || type == SearchRequestType::FromOffset) - ? !rateUpdated - : !someAdded; - if (rateUpdated) { - _searchNextRate = nextRate->v; - } - if (finished) { - if (type == SearchRequestType::MigratedFromStart || type == SearchRequestType::MigratedFromOffset) { - _searchFullMigrated = true; - } else { - _searchFull = true; - } - } - } break; - - case mtpc_messages_channelMessages: { - auto &d = result.c_messages_channelMessages(); - if (const auto peer = _searchInChat.peer()) { - if (const auto channel = peer->asChannel()) { - channel->ptsReceived(d.vpts().v); - } else { - LOG(("API Error: " - "received messages.channelMessages when no channel " - "was passed! (Widget::searchReceived)")); - } + case mtpc_messages_channelMessages: { + auto &d = result.c_messages_channelMessages(); + if (const auto peer = _searchInChat.peer()) { + if (const auto channel = peer->asChannel()) { + channel->ptsReceived(d.vpts().v); } else { LOG(("API Error: " "received messages.channelMessages when no channel " "was passed! (Widget::searchReceived)")); } - if (_searchRequest != 0) { - // Don't apply cached data! - session().data().processUsers(d.vusers()); - session().data().processChats(d.vchats()); - } - auto &msgs = d.vmessages().v; - if (!_inner->searchReceived(msgs, inject, type, d.vcount().v)) { - if (type == SearchRequestType::MigratedFromStart || type == SearchRequestType::MigratedFromOffset) { - _searchFullMigrated = true; - } else { - _searchFull = true; - } - } - } break; - - case mtpc_messages_messagesNotModified: { - LOG(("API Error: received messages.messagesNotModified! (Widget::searchReceived)")); + } else { + LOG(("API Error: " + "received messages.channelMessages when no channel " + "was passed! (Widget::searchReceived)")); + } + if (_searchRequest != 0) { + // Don't apply cached data! + session().data().processUsers(d.vusers()); + session().data().processChats(d.vchats()); + } + auto &msgs = d.vmessages().v; + if (!_inner->searchReceived(msgs, inject, type, d.vcount().v)) { if (type == SearchRequestType::MigratedFromStart || type == SearchRequestType::MigratedFromOffset) { _searchFullMigrated = true; } else { _searchFull = true; } - } break; } + } break; - _searchRequest = 0; - onListScroll(); - update(); + case mtpc_messages_messagesNotModified: { + LOG(("API Error: received messages.messagesNotModified! (Widget::searchReceived)")); + if (type == SearchRequestType::MigratedFromStart || type == SearchRequestType::MigratedFromOffset) { + _searchFullMigrated = true; + } else { + _searchFull = true; + } + } break; } + + _searchRequest = 0; + onListScroll(); + update(); } void Widget::peerSearchReceived( @@ -1318,7 +1373,7 @@ void Widget::clearSearchCache() { _searchQueries.clear(); _searchQuery = QString(); _searchQueryFrom = nullptr; - MTP::cancel(base::take(_searchRequest)); + cancelSearchRequest(); } void Widget::showJumpToDate() { @@ -1614,12 +1669,20 @@ void Widget::removeDialog(Key key) { _inner->removeDialog(key); } -bool Widget::onCancelSearch() { - bool clearing = !_filter->getLastText().isEmpty(); +void Widget::cancelSearchRequest() { if (_searchRequest) { MTP::cancel(_searchRequest); _searchRequest = 0; } + if (_searchInHistoryRequest) { + session().data().histories().cancelRequest(_searchInHistoryRequest); + _searchInHistoryRequest = 0; + } +} + +bool Widget::onCancelSearch() { + bool clearing = !_filter->getLastText().isEmpty(); + cancelSearchRequest(); if (_searchInChat && !clearing) { if (Adaptive::OneColumn()) { if (const auto peer = _searchInChat.peer()) { @@ -1641,10 +1704,7 @@ bool Widget::onCancelSearch() { } void Widget::onCancelSearchInChat() { - if (_searchRequest) { - MTP::cancel(_searchRequest); - _searchRequest = 0; - } + cancelSearchRequest(); if (_searchInChat) { if (Adaptive::OneColumn() && !App::main()->selectingPeer()) { if (const auto peer = _searchInChat.peer()) { diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index 42799d2dd..710f32c66 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -136,6 +136,7 @@ private: const MTPcontacts_Found &result, mtpRequestId requestId); void escape(); + void cancelSearchRequest(); void setupSupportMode(); void setupConnectingWidget(); @@ -219,6 +220,7 @@ private: int32 _searchNextRate = 0; bool _searchFull = false; bool _searchFullMigrated = false; + int _searchInHistoryRequest = 0; // Not real mtpRequestId. mtpRequestId _searchRequest = 0; QMap _searchCache; diff --git a/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp b/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp index a03d191ca..6cb988a28 100644 --- a/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp +++ b/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp @@ -23,7 +23,8 @@ extern "C" { namespace FFmpeg { namespace { -constexpr auto kAlignImageBy = 16; +// See https://github.com/telegramdesktop/tdesktop/issues/7225 +constexpr auto kAlignImageBy = 64; constexpr auto kImageFormat = QImage::Format_ARGB32_Premultiplied; constexpr auto kMaxScaleByAspectRatio = 16; constexpr auto kAvioBlockSize = 4096; 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 41615d44a..04e029f3f 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp @@ -392,10 +392,9 @@ void GenerateItems( auto addSimpleServiceMessage = [&](const QString &text, PhotoData *photo = nullptr) { auto message = HistoryService::PreparedText { text }; message.links.push_back(fromLink); - addPart(history->owner().makeServiceMessage( - history, - MTPDmessage_ClientFlag::f_admin_log_entry, + addPart(history->makeServiceMessage( history->nextNonHistoryEntryId(), + MTPDmessage_ClientFlag::f_admin_log_entry, date, message, MTPDmessage::Flags(0), @@ -433,8 +432,7 @@ void GenerateItems( auto bodyReplyTo = 0; auto bodyViaBotId = 0; auto newDescription = PrepareText(newValue, QString()); - auto body = history->owner().makeMessage( - history, + auto body = history->makeMessage( history->nextNonHistoryEntryId(), bodyFlags, bodyClientFlags, @@ -469,8 +467,7 @@ void GenerateItems( auto bodyReplyTo = 0; auto bodyViaBotId = 0; auto newLink = newValue.isEmpty() ? TextWithEntities() : PrepareText(Core::App().createInternalLinkFull(newValue), QString()); - auto body = history->owner().makeMessage( - history, + auto body = history->makeMessage( history->nextNonHistoryEntryId(), bodyFlags, bodyClientFlags, @@ -609,8 +606,7 @@ void GenerateItems( auto bodyReplyTo = 0; auto bodyViaBotId = 0; auto bodyText = GenerateParticipantChangeText(channel, action.vparticipant()); - addPart(history->owner().makeMessage( - history, + addPart(history->makeMessage( history->nextNonHistoryEntryId(), bodyFlags, bodyClientFlags, @@ -628,8 +624,7 @@ void GenerateItems( auto bodyReplyTo = 0; auto bodyViaBotId = 0; auto bodyText = GenerateParticipantChangeText(channel, action.vnew_participant(), &action.vprev_participant()); - addPart(history->owner().makeMessage( - history, + addPart(history->makeMessage( history->nextNonHistoryEntryId(), bodyFlags, bodyClientFlags, @@ -653,8 +648,7 @@ void GenerateItems( auto bodyReplyTo = 0; auto bodyViaBotId = 0; auto bodyText = GenerateParticipantChangeText(channel, action.vnew_participant(), &action.vprev_participant()); - addPart(history->owner().makeMessage( - history, + addPart(history->makeMessage( history->nextNonHistoryEntryId(), bodyFlags, bodyClientFlags, @@ -687,10 +681,9 @@ void GenerateItems( auto message = HistoryService::PreparedText { text }; message.links.push_back(fromLink); message.links.push_back(setLink); - addPart(history->owner().makeServiceMessage( - history, - MTPDmessage_ClientFlag::f_admin_log_entry, + addPart(history->makeServiceMessage( history->nextNonHistoryEntryId(), + MTPDmessage_ClientFlag::f_admin_log_entry, date, message, MTPDmessage::Flags(0), @@ -712,8 +705,7 @@ void GenerateItems( auto bodyReplyTo = 0; auto bodyViaBotId = 0; auto bodyText = GenerateDefaultBannedRightsChangeText(channel, action.vnew_banned_rights(), action.vprev_banned_rights()); - addPart(history->owner().makeMessage( - history, + addPart(history->makeMessage( history->nextNonHistoryEntryId(), bodyFlags, bodyClientFlags, @@ -763,10 +755,9 @@ void GenerateItems( auto message = HistoryService::PreparedText{ text }; message.links.push_back(fromLink); message.links.push_back(chatLink); - addPart(history->owner().makeServiceMessage( - history, - MTPDmessage_ClientFlag::f_admin_log_entry, + addPart(history->makeServiceMessage( history->nextNonHistoryEntryId(), + MTPDmessage_ClientFlag::f_admin_log_entry, date, message, MTPDmessage::Flags(0), diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index f1a2559ce..7b0767027 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -23,6 +23,7 @@ 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_histories.h" #include "lang/lang_keys.h" #include "apiwrap.h" #include "mainwidget.h" @@ -135,6 +136,9 @@ void History::itemRemoved(not_null item) { _joinedMessage = nullptr; } item->removeMainView(); + if (_lastServerMessage == item) { + _lastServerMessage = std::nullopt; + } if (lastMessage() == item) { _lastMessage = std::nullopt; if (loadedAtBottom()) { @@ -633,11 +637,55 @@ HistoryItem *History::addNewMessage( return addNewItem(item, unread); } +not_null History::insertItem( + std::unique_ptr item) { + Expects(item != nullptr); + + const auto [i, ok] = _messages.insert(std::move(item)); + + const auto result = i->get(); + owner().registerMessage(result); + + Ensures(ok); + return result; +} + +void History::destroyMessage(not_null item) { + Expects(item->isHistoryEntry() || !item->mainView()); + + const auto peerId = peer->id; + if (item->isHistoryEntry()) { + // All this must be done for all items manually in History::clear()! + item->eraseFromUnreadMentions(); + if (IsServerMsgId(item->id)) { + if (const auto types = item->sharedMediaTypes()) { + session().storage().remove(Storage::SharedMediaRemoveOne( + peerId, + types, + item->id)); + } + } else { + session().api().cancelLocalItem(item); + } + itemRemoved(item); + } + + owner().unregisterMessage(item); + session().notifications().clearFromItem(item); + + auto hack = std::unique_ptr(item.get()); + const auto i = _messages.find(hack); + hack.release(); + + Assert(i != end(_messages)); + _messages.erase(i); +} + not_null History::addNewItem( not_null item, bool unread) { if (item->isScheduled()) { - session().data().scheduledMessages().appendSending(item); + owner().scheduledMessages().appendSending(item); return item; } else if (!item->isHistoryEntry()) { return item; @@ -689,8 +737,7 @@ not_null History::addNewLocalMessage( const QString &postAuthor, not_null forwardOriginal) { return addNewItem( - owner().makeMessage( - this, + makeMessage( id, flags, clientFlags, @@ -714,8 +761,7 @@ not_null History::addNewLocalMessage( const TextWithEntities &caption, const MTPReplyMarkup &markup) { return addNewItem( - owner().makeMessage( - this, + makeMessage( id, flags, clientFlags, @@ -743,8 +789,7 @@ not_null History::addNewLocalMessage( const TextWithEntities &caption, const MTPReplyMarkup &markup) { return addNewItem( - owner().makeMessage( - this, + makeMessage( id, flags, clientFlags, @@ -771,8 +816,7 @@ not_null History::addNewLocalMessage( not_null game, const MTPReplyMarkup &markup) { return addNewItem( - owner().makeMessage( - this, + makeMessage( id, flags, clientFlags, @@ -1241,7 +1285,7 @@ void History::newItemAdded(not_null item) { if (unreadCountKnown()) { setUnreadCount(unreadCount() + 1); } else { - session().api().requestDialogEntry(this); + owner().histories().requestDialogEntry(this); } } } @@ -1254,7 +1298,7 @@ void History::newItemAdded(not_null item) { outboxRead(item); } if (!folderKnown()) { - session().api().requestDialogEntry(this); + owner().histories().requestDialogEntry(this); } } @@ -1323,8 +1367,8 @@ HistoryBlock *History::prepareBlockForAddingItem() { } void History::viewReplaced(not_null was, Element *now) { - if (scrollTopItem == was) scrollTopItem= now; - if (_firstUnreadView == was) _firstUnreadView= now; + if (scrollTopItem == was) scrollTopItem = now; + if (_firstUnreadView == was) _firstUnreadView = now; if (_unreadBarView == was) _unreadBarView = now; } @@ -1545,44 +1589,23 @@ void History::addToSharedMedia( } } -std::optional History::countUnread(MsgId upTo) const { - if (!folderKnown() || !loadedAtBottom()) { - return std::nullopt; - } - auto result = 0; - for (auto i = blocks.cend(), e = blocks.cbegin(); i != e;) { - --i; - const auto &messages = (*i)->messages; - for (auto j = messages.cend(), en = messages.cbegin(); j != en;) { - --j; - const auto item = (*j)->data(); - if (item->id > 0 && item->id <= upTo) { - return result; - } else if (!item->out() && item->unread()) { - ++result; - } - } - } - return std::nullopt; -} - void History::calculateFirstUnreadMessage() { - if (_firstUnreadView || !_inboxReadBefore) { + if (!_inboxReadBefore) { return; } - for (auto i = blocks.cend(); i != blocks.cbegin();) { - --i; - const auto &messages = (*i)->messages; - for (auto j = messages.cend(); j != messages.cbegin();) { - --j; - const auto view = j->get(); - const auto item = view->data(); + _firstUnreadView = nullptr; + if (!unreadCount() || !trackUnreadMessages()) { + return; + } + for (const auto &block : ranges::view::reverse(blocks)) { + for (const auto &message : ranges::view::reverse(block->messages)) { + const auto item = message->data(); if (!IsServerMsgId(item->id)) { continue; - } else if (!item->out() || !_firstUnreadView) { + } else if (!item->out()) { if (item->id >= *_inboxReadBefore) { - _firstUnreadView = view; + _firstUnreadView = message.get(); } else { return; } @@ -1591,27 +1614,73 @@ void History::calculateFirstUnreadMessage() { } } +bool History::readInboxTillNeedsRequest(MsgId tillId) { + Expects(!tillId || IsServerMsgId(tillId)); + + readClientSideMessages(); + if (unreadMark()) { + owner().histories().changeDialogUnreadMark(this, false); + } + return IsServerMsgId(tillId) && (_inboxReadBefore.value_or(1) <= tillId); +} + void History::readClientSideMessages() { - for (const auto &block : blocks) { - for (const auto &view : block->messages) { - const auto item = view->data(); - if (!item->out()) { - item->markClientSideAsRead(); - } - } + auto &histories = owner().histories(); + for (const auto item : _localMessages) { + histories.readClientSideMessage(item); } } -MsgId History::readInbox() { - const auto upTo = msgIdForRead(); - if (unreadCountKnown()) { - setUnreadCount(0); +bool History::unreadCountRefreshNeeded(MsgId readTillId) const { + return !unreadCountKnown() + || ((readTillId + 1) > _inboxReadBefore.value_or(0)); +} + +std::optional History::countStillUnreadLocal(MsgId readTillId) const { + if (isEmpty() || !folderKnown()) { + return std::nullopt; } - readClientSideMessages(); - if (upTo) { - inboxRead(upTo); + if (_inboxReadBefore) { + const auto before = *_inboxReadBefore; + if (minMsgId() <= before && maxMsgId() >= readTillId) { + auto result = 0; + for (const auto &block : blocks) { + for (const auto &message : block->messages) { + const auto item = message->data(); + if (item->out() || !IsServerMsgId(item->id)) { + continue; + } else if (item->id > readTillId) { + break; + } else if (item->id >= before) { + ++result; + } + } + } + if (_unreadCount) { + return std::max(*_unreadCount - result, 0); + } + } } - return upTo; + const auto minimalServerId = minMsgId(); + if (!loadedAtBottom() + || (!loadedAtTop() && !minimalServerId) + || minimalServerId > readTillId) { + return std::nullopt; + } + auto result = 0; + for (const auto &block : ranges::view::reverse(blocks)) { + for (const auto &message : ranges::view::reverse(block->messages)) { + const auto item = message->data(); + if (IsServerMsgId(item->id)) { + if (item->id <= readTillId) { + return result; + } else if (!item->out()) { + ++result; + } + } + } + } + return result; } void History::applyInboxReadUpdate( @@ -1622,13 +1691,15 @@ void History::applyInboxReadUpdate( const auto folder = folderId ? owner().folderLoaded(folderId) : nullptr; if (folder && this->folder() != folder) { // If history folder is unknown or not synced, request both. - session().api().requestDialogEntry(this); - session().api().requestDialogEntry(folder); + owner().histories().requestDialogEntry(this); + owner().histories().requestDialogEntry(folder); } - if (!peer->isChannel() || peer->asChannel()->pts() == channelPts) { - inboxRead(upTo, stillUnread); - } else { - inboxRead(upTo); + if (_inboxReadBefore.value_or(1) <= upTo) { + if (!peer->isChannel() || peer->asChannel()->pts() == channelPts) { + inboxRead(upTo, stillUnread); + } else { + inboxRead(upTo); + } } } @@ -1638,16 +1709,16 @@ void History::inboxRead(MsgId upTo, std::optional stillUnread) { } if (stillUnread.has_value() && folderKnown()) { setUnreadCount(*stillUnread); - } else if (const auto still = countUnread(upTo)) { + } else if (const auto still = countStillUnreadLocal(upTo)) { setUnreadCount(*still); } else { - session().api().requestDialogEntry(this); + owner().histories().requestDialogEntry(this); } setInboxReadTill(upTo); updateChatListEntry(); - if (peer->migrateTo()) { - if (auto migrateTo = peer->owner().historyLoaded(peer->migrateTo()->id)) { - migrateTo->updateChatListEntry(); + if (const auto to = peer->migrateTo()) { + if (const auto migrated = peer->owner().historyLoaded(to->id)) { + migrated->updateChatListEntry(); } } @@ -1727,18 +1798,8 @@ void History::setUnreadCount(int newUnreadCount) { if (const auto last = msgIdForRead()) { setInboxReadTill(last); } - } else { - if (!_firstUnreadView && !_unreadBarView && loadedAtBottom()) { - calculateFirstUnreadMessage(); - } - } - if (_unreadBarView) { - const auto count = chatListUnreadCount(); - if (count > 0) { - _unreadBarView->setUnreadBarCount(count); - } else { - _unreadBarView->setUnreadBarFreezed(); - } + } else if (!_firstUnreadView && !_unreadBarView && loadedAtBottom()) { + calculateFirstUnreadMessage(); } Notify::peerUpdatedDelayed( peer, @@ -1841,8 +1902,8 @@ void History::setFolder( not_null folder, HistoryItem *folderDialogItem) { setFolderPointer(folder); - if (folderDialogItem && _lastMessage != folderDialogItem) { - setLastMessage(folderDialogItem); + if (folderDialogItem) { + setLastServerMessage(folderDialogItem); } } @@ -1993,7 +2054,7 @@ void History::addUnreadBar() { } if (const auto count = chatListUnreadCount()) { _unreadBarView = _firstUnreadView; - _unreadBarView->setUnreadBarCount(count); + _unreadBarView->createUnreadBar(); } } @@ -2003,17 +2064,6 @@ void History::destroyUnreadBar() { } } -bool History::hasNotFreezedUnreadBar() const { - if (_firstUnreadView) { - if (const auto view = _unreadBarView) { - if (const auto bar = view->Get()) { - return !bar->freezed; - } - } - } - return false; -} - void History::unsetFirstUnreadMessage() { _firstUnreadView = nullptr; } @@ -2273,18 +2323,25 @@ void History::clearSharedMedia() { //} } +void History::setLastServerMessage(HistoryItem *item) { + _lastServerMessage = item; + if (_lastMessage + && *_lastMessage + && !IsServerMsgId((*_lastMessage)->id) + && (!item || (*_lastMessage)->date() > item->date())) { + return; + } + setLastMessage(item); +} + void History::setLastMessage(HistoryItem *item) { - if (_lastMessage) { - if (*_lastMessage == item) { - return; - } else if (*_lastMessage - && item - && !IsServerMsgId((*_lastMessage)->id) - && (*_lastMessage)->date() > item->date()) { - return; - } + if (_lastMessage && *_lastMessage == item) { + return; } _lastMessage = item; + if (!item || IsServerMsgId(item->id)) { + _lastServerMessage = item; + } if (peer->migrateTo()) { // We don't want to request last message for all deactivated chats. // This is a heavy request for them, because we need to get last @@ -2406,7 +2463,7 @@ void History::setChatListMessageUnknown() { void History::requestChatListMessage() { if (!lastMessageKnown()) { - session().api().requestDialogEntry(this, [=] { + owner().histories().requestDialogEntry(this, [=] { requestChatListMessage(); }); return; @@ -2423,7 +2480,7 @@ void History::setFakeChatListMessage() { if (const auto chat = peer->asChat()) { // In chats we try to take the item before the 'last', which // is the empty-displayed migration message. - session().api().requestFakeChatListMessage(this); + owner().histories().requestFakeChatListMessage(this); } else if (const auto from = migrateFrom()) { // In megagroups we just try to use // the message from the original group. @@ -2484,6 +2541,14 @@ bool History::lastMessageKnown() const { return _lastMessage.has_value(); } +HistoryItem *History::lastServerMessage() const { + return _lastServerMessage.value_or(nullptr); +} + +bool History::lastServerMessageKnown() const { + return _lastServerMessage.has_value(); +} + void History::updateChatListExistence() { Entry::updateChatListExistence(); //if (const auto channel = peer->asChannel()) { // #feed @@ -2491,7 +2556,7 @@ void History::updateChatListExistence() { // // After ungrouping from a feed we need to load dialog. // requestChatListMessage(); // if (!unreadCountKnown()) { - // session().api().requestDialogEntry(this); + // owner().histories().requestDialogEntry(this); // } // } //} @@ -2510,6 +2575,13 @@ int History::fixedOnTopIndex() const { return useProxyPromotion() ? kProxyPromotionFixOnTopIndex : 0; } +bool History::trackUnreadMessages() const { + if (const auto channel = peer->asChannel()) { + return channel->amIn(); + } + return true; +} + bool History::shouldBeInChatList() const { if (peer->migrateTo() || !folderKnown()) { return false; @@ -2536,7 +2608,7 @@ bool History::toImportant() const { void History::unknownMessageDeleted(MsgId messageId) { if (_inboxReadBefore && messageId >= *_inboxReadBefore) { - session().api().requestDialogEntry(this); + owner().histories().requestDialogEntry(this); } } @@ -2588,11 +2660,13 @@ void History::applyDialog( if (draft && draft->type() == mtpc_draftMessage) { Data::applyPeerCloudDraft(peer->id, draft->c_draftMessage()); } - session().api().dialogEntryApplied(this); + owner().histories().dialogEntryApplied(this); } void History::dialogEntryApplied() { - if (!lastMessageKnown()) { + if (!lastServerMessageKnown()) { + setLastServerMessage(nullptr); + } else if (!lastMessageKnown()) { setLastMessage(nullptr); } if (peer->migrateTo()) { @@ -2656,7 +2730,8 @@ void History::applyDialogFields( } else { clearFolder(); } - if (!skipUnreadUpdate()) { + if (!skipUnreadUpdate() + && maxInboxRead + 1 >= _inboxReadBefore.value_or(1)) { setUnreadCount(unreadCount); setInboxReadTill(maxInboxRead); } @@ -2669,12 +2744,12 @@ void History::applyDialogTopMessage(MsgId topMessageId) { channelId(), topMessageId); if (const auto item = owner().message(itemId)) { - setLastMessage(item); + setLastServerMessage(item); } else { - setLastMessage(nullptr); + setLastServerMessage(nullptr); } } else { - setLastMessage(nullptr); + setLastServerMessage(nullptr); } if (clearUnreadOnClientSide()) { setUnreadCount(0); @@ -3048,10 +3123,10 @@ void History::clear(ClearType type) { removeJoinedMessage(); forgetScrollState(); + blocks.clear(); + owner().notifyHistoryUnloaded(this); + lastKeyboardInited = false; if (type == ClearType::Unload) { - blocks.clear(); - owner().notifyHistoryUnloaded(this); - lastKeyboardInited = false; _loadedAtTop = _loadedAtBottom = false; } else { // Leave the 'sending' messages in local messages. @@ -3070,7 +3145,7 @@ void History::clear(ClearType type) { setUnreadCount(0); } if (type == ClearType::DeleteChat) { - setLastMessage(nullptr); + setLastServerMessage(nullptr); } else if (_lastMessage && *_lastMessage) { if (IsServerMsgId((*_lastMessage)->id)) { (*_lastMessage)->applyEditionToHistoryCleared(); @@ -3096,7 +3171,6 @@ void History::clear(ClearType type) { //} } } - owner().notifyHistoryChangeDelayed(this); if (const auto chat = peer->asChat()) { chat->lastAuthors.clear(); @@ -3104,27 +3178,28 @@ void History::clear(ClearType type) { } else if (const auto channel = peer->asMegagroup()) { channel->mgInfo->markupSenders.clear(); } + + owner().notifyHistoryChangeDelayed(this); + owner().sendHistoryChangeNotifications(); } void History::clearUpTill(MsgId availableMinId) { - auto minId = minMsgId(); - if (!minId || minId > availableMinId) { - return; - } - do { - const auto item = blocks.front()->messages.front()->data(); + auto remove = std::vector>(); + remove.reserve(_messages.size()); + for (const auto &item : _messages) { const auto itemId = item->id; - if (IsServerMsgId(itemId) && itemId >= availableMinId) { - if (itemId == availableMinId) { - item->applyEditionToHistoryCleared(); - } - break; + if (!IsServerMsgId(itemId)) { + continue; + } else if (itemId == availableMinId) { + item->applyEditionToHistoryCleared(); + } else if (itemId < availableMinId) { + remove.push_back(item.get()); } + } + for (const auto item : remove) { item->destroy(); - } while (!isEmpty()); - + } requestChatListMessage(); - owner().sendHistoryChangeNotifications(); } void History::applyGroupAdminChanges(const base::flat_set &changes) { diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index 327445a3f..8ef86d070 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -90,6 +90,25 @@ public: void applyGroupAdminChanges(const base::flat_set &changes); + template + not_null makeMessage(Args &&...args) { + return static_cast( + insertItem( + std::make_unique( + this, + std::forward(args)...)).get()); + } + + template + not_null makeServiceMessage(Args &&...args) { + return static_cast( + insertItem( + std::make_unique( + this, + std::forward(args)...)).get()); + } + void destroyMessage(not_null item); + HistoryItem *addNewMessage( const MTPMessage &msg, MTPDmessage_ClientFlags clientFlags, @@ -158,7 +177,7 @@ public: void unregisterLocalMessage(not_null item); [[nodiscard]] HistoryItem *latestSendingMessage() const; - MsgId readInbox(); + [[nodiscard]] bool readInboxTillNeedsRequest(MsgId tillId); void applyInboxReadUpdate( FolderId folderId, MsgId upTo, @@ -172,8 +191,13 @@ public: not_null item) const; [[nodiscard]] MsgId loadAroundId() const; + [[nodiscard]] bool trackUnreadMessages() const; [[nodiscard]] int unreadCount() const; [[nodiscard]] bool unreadCountKnown() const; + + // Some old unread count is known, but we read history till some place. + [[nodiscard]] bool unreadCountRefreshNeeded(MsgId readTillId) const; + void setUnreadCount(int newUnreadCount); void setUnreadMark(bool unread); [[nodiscard]] bool unreadMark() const; @@ -182,7 +206,6 @@ public: bool changeMute(bool newMute); void addUnreadBar(); void destroyUnreadBar(); - [[nodiscard]] bool hasNotFreezedUnreadBar() const; [[nodiscard]] Element *unreadBar() const; void calculateFirstUnreadMessage(); void unsetFirstUnreadMessage(); @@ -197,7 +220,9 @@ public: void getReadyFor(MsgId msgId); [[nodiscard]] HistoryItem *lastMessage() const; + [[nodiscard]] HistoryItem *lastServerMessage() const; [[nodiscard]] bool lastMessageKnown() const; + [[nodiscard]] bool lastServerMessageKnown() const; void unknownMessageDeleted(MsgId messageId); void applyDialogTopMessage(MsgId topMessageId); void applyDialog(Data::Folder *requestFolder, const MTPDdialog &data); @@ -344,6 +369,10 @@ public: HistoryItem *folderDialogItem = nullptr); void clearFolder(); + // Interface for Data::Histories. + void setInboxReadTill(MsgId upTo); + std::optional countStillUnreadLocal(MsgId readTillId) const; + // Still public data. std::deque> blocks; @@ -395,6 +424,7 @@ private: void removeBlock(not_null block); void clearSharedMedia(); + not_null insertItem(std::unique_ptr item); not_null addNewItem( not_null item, bool unread); @@ -432,7 +462,6 @@ private: TimeId adjustedChatListTimeId() const override; void changedChatListPinHook() override; - void setInboxReadTill(MsgId upTo); void setOutboxReadTill(MsgId upTo); void readClientSideMessages(); @@ -446,6 +475,7 @@ private: // After adding a new history slice check lastMessage / loadedAtBottom. void checkLastMessage(); void setLastMessage(HistoryItem *item); + void setLastServerMessage(HistoryItem *item); void refreshChatListMessage(); void setChatListMessage(HistoryItem *item); @@ -468,7 +498,6 @@ private: HistoryItem *lastAvailableMessage() const; void getNextFirstUnreadMessage(); bool nonEmptyCountMoreThan(int count) const; - std::optional countUnread(MsgId upTo) const; // Creates if necessary a new block for adding item. // Depending on isBuildingFrontBlock() gets front or back block. @@ -501,7 +530,9 @@ private: std::optional _unreadMentionsCount; base::flat_set _unreadMentions; std::optional _lastMessage; + std::optional _lastServerMessage; base::flat_set> _localMessages; + std::unordered_set> _messages; // This almost always is equal to _lastMessage. The only difference is // for a group that migrated to a supergroup. Then _lastMessage can diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 5f2e8c104..e4f8ec9f6 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_session_controller.h" #include "window/window_peer_menu.h" #include "window/window_controller.h" +#include "window/notifications_manager.h" #include "boxes/confirm_box.h" #include "boxes/report_box.h" #include "boxes/sticker_set_box.h" @@ -53,6 +54,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_photo.h" #include "data/data_user.h" #include "data/data_file_origin.h" +#include "data/data_histories.h" #include "facades.h" #include "app.h" @@ -655,13 +657,13 @@ void HistoryInner::paintEvent(QPaintEvent *e) { auto iItem = (_curHistory == _history ? _curItem : 0); auto view = block->messages[iItem].get(); auto item = view->data(); - + auto readTill = (HistoryItem*)nullptr; auto hclip = clip.intersected(QRect(0, hdrawtop, width(), clip.top() + clip.height())); auto y = htop + block->y() + view->y(); p.save(); p.translate(0, y); while (y < drawToY) { - auto h = view->height(); + const auto h = view->height(); if (hclip.y() < y + h && hdrawtop < y + h) { const auto selection = itemRenderSelection( view, @@ -669,12 +671,23 @@ void HistoryInner::paintEvent(QPaintEvent *e) { seltoy - htop); view->draw(p, hclip.translated(0, -y), selection, ms); - if (item->hasViews()) { - App::main()->scheduleViewIncrement(item); + const auto middle = y + h / 2; + const auto bottom = y + h; + if (_visibleAreaBottom >= bottom) { + const auto item = view->data(); + if (!item->out() && item->unread()) { + readTill = item; + } } - if (item->isUnreadMention() && !item->isUnreadMedia()) { - readMentions.insert(item); - _widget->enqueueMessageHighlight(view); + if (_visibleAreaBottom >= middle + && _visibleAreaTop <= middle) { + if (item->hasViews()) { + App::main()->scheduleViewIncrement(item); + } + if (item->isUnreadMention() && !item->isUnreadMedia()) { + readMentions.insert(item); + _widget->enqueueMessageHighlight(view); + } } } p.translate(0, h); @@ -693,9 +706,13 @@ void HistoryInner::paintEvent(QPaintEvent *e) { item = view->data(); } p.restore(); + + if (readTill && _widget->doWeReadServerHistory()) { + session().data().histories().readInboxTill(readTill); + } } - if (!readMentions.empty() && App::wnd()->doWeReadMentions()) { + if (!readMentions.empty() && _widget->doWeReadMentions()) { session().api().markMediaRead(readMentions); } @@ -2013,6 +2030,42 @@ void HistoryInner::keyPressEvent(QKeyEvent *e) { } } +void HistoryInner::checkHistoryActivation() { + if (!_widget->doWeReadServerHistory()) { + return; + } + adjustCurrent(_visibleAreaBottom); + if (_history->loadedAtBottom() && _visibleAreaBottom >= height()) { + // Clear possible scheduled messages notifications. + session().notifications().clearFromHistory(_history); + } + if (_curHistory != _history || _history->isEmpty()) { + return; + } + auto block = _history->blocks[_curBlock].get(); + auto view = block->messages[_curItem].get(); + while (_curBlock > 0 || _curItem > 0) { + const auto top = itemTop(view); + const auto bottom = itemTop(view) + view->height(); + if (_visibleAreaBottom >= bottom) { + break; + } + if (_curItem > 0) { + view = block->messages[--_curItem].get(); + } else { + while (_curBlock > 0) { + block = _history->blocks[--_curBlock].get(); + _curItem = block->messages.size(); + if (_curItem > 0) { + view = block->messages[--_curItem].get(); + break; + } + } + } + } + session().data().histories().readInboxTill(view->data()); +} + void HistoryInner::recountHistoryGeometry() { _contentWidth = _scroll->width(); @@ -2178,6 +2231,7 @@ void HistoryInner::visibleAreaUpdated(int top, int bottom) { const auto from = _visibleAreaTop - pages * visibleAreaHeight; const auto till = _visibleAreaBottom + pages * visibleAreaHeight; session().data().unloadHeavyViewParts(ElementDelegate(), from, till); + checkHistoryActivation(); } bool HistoryInner::displayScrollDate() const { @@ -2326,7 +2380,8 @@ void HistoryInner::adjustCurrent(int32 y) const { } void HistoryInner::adjustCurrent(int32 y, History *history) const { - Assert(!history->isEmpty()); + Expects(!history->isEmpty()); + _curHistory = history; if (_curBlock >= history->blocks.size()) { _curBlock = history->blocks.size() - 1; diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index 4a4773bcf..a0b551450 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -62,6 +62,7 @@ public: void touchScrollUpdated(const QPoint &screenPos); + void checkHistoryActivation(); void recountHistoryGeometry(); void updateSize(); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 5826c25a3..7a8e95457 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -75,8 +75,7 @@ not_null CreateUnsupportedMessage( EntityInText(EntityType::Italic, 0, text.text.size())); flags &= ~MTPDmessage::Flag::f_post_author; flags |= MTPDmessage::Flag::f_legacy; - return history->owner().makeMessage( - history, + return history->makeMessage( msgId, flags, clientFlags, @@ -413,7 +412,7 @@ bool HistoryItem::isScheduled() const { } void HistoryItem::destroy() { - _history->owner().destroyMessage(this); + _history->destroyMessage(this); } void HistoryItem::refreshMainView() { @@ -929,37 +928,29 @@ not_null HistoryItem::Create( const auto text = HistoryService::PreparedText { tr::lng_message_empty(tr::now) }; - return history->owner().makeServiceMessage( - history, - clientFlags, + return history->makeServiceMessage( data.vid().v, + clientFlags, data.vdate().v, text, data.vflags().v, data.vfrom_id().value_or_empty()); } else if (checked == MediaCheckResult::HasTimeToLive) { - return history->owner().makeServiceMessage( - history, - data, - clientFlags); + return history->makeServiceMessage(data, clientFlags); } - return history->owner().makeMessage(history, data, clientFlags); + return history->makeMessage(data, clientFlags); }, [&](const MTPDmessageService &data) -> HistoryItem* { if (data.vaction().type() == mtpc_messageActionPhoneCall) { - return history->owner().makeMessage(history, data, clientFlags); + return history->makeMessage(data, clientFlags); } - return history->owner().makeServiceMessage( - history, - data, - clientFlags); + return history->makeServiceMessage(data, clientFlags); }, [&](const MTPDmessageEmpty &data) -> HistoryItem* { const auto text = HistoryService::PreparedText{ tr::lng_message_empty(tr::now) }; - return history->owner().makeServiceMessage( - history, - clientFlags, + return history->makeServiceMessage( data.vid().v, + clientFlags, TimeId(0), text); }); diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index 0c9ee4d34..7e2974d66 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -38,6 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_media_types.h" #include "data/data_channel.h" #include "data/data_user.h" +#include "data/data_histories.h" #include "facades.h" #include "app.h" #include "styles/style_dialogs.h" @@ -265,15 +266,6 @@ void FastShareMessage(not_null item) { return; } - auto doneCallback = [=](const MTPUpdates &updates, mtpRequestId requestId) { - history->session().api().applyUpdates(updates); - data->requests.remove(requestId); - if (data->requests.empty()) { - Ui::Toast::Show(tr::lng_share_done(tr::now)); - Ui::hideLayer(); - } - }; - const auto sendFlags = MTPmessages_ForwardMessages::Flag(0) | MTPmessages_ForwardMessages::Flag::f_with_my_score | (isGroup @@ -297,28 +289,40 @@ void FastShareMessage(not_null item) { } return result; }; + auto &api = owner->session().api(); + auto &histories = owner->histories(); + const auto requestType = Data::Histories::RequestType::Send; for (const auto peer : result) { - const auto history = peer->owner().history(peer); + const auto history = owner->history(peer); if (!comment.text.isEmpty()) { auto message = ApiWrap::MessageToSend(history); message.textWithTags = comment; message.action.options = options; message.action.clearDraft = false; - history->session().api().sendMessage(std::move(message)); + api.sendMessage(std::move(message)); } - history->sendRequestId = MTP::send( - MTPmessages_ForwardMessages( - MTP_flags(sendFlags), - data->peer->input, - MTP_vector(msgIds), - MTP_vector(generateRandom()), - peer->input, - MTP_int(options.scheduled)), - rpcDone(base::duplicate(doneCallback)), - nullptr, - 0, - 0, - history->sendRequestId); + histories.sendRequest(history, requestType, [=](Fn finish) { + auto &api = history->session().api(); + history->sendRequestId = api.request(MTPmessages_ForwardMessages( + MTP_flags(sendFlags), + data->peer->input, + MTP_vector(msgIds), + MTP_vector(generateRandom()), + peer->input, + MTP_int(options.scheduled) + )).done([=](const MTPUpdates &updates, mtpRequestId requestId) { + history->session().api().applyUpdates(updates); + data->requests.remove(requestId); + if (data->requests.empty()) { + Ui::Toast::Show(tr::lng_share_done(tr::now)); + Ui::hideLayer(); + } + finish(); + }).fail([=](const RPCError &error) { + finish(); + }).afterRequest(history->sendRequestId).send(); + return history->sendRequestId; + }); data->requests.insert(history->sendRequestId); } }; diff --git a/Telegram/SourceFiles/history/history_service.cpp b/Telegram/SourceFiles/history/history_service.cpp index 48c905d37..4856847d2 100644 --- a/Telegram/SourceFiles/history/history_service.cpp +++ b/Telegram/SourceFiles/history/history_service.cpp @@ -525,8 +525,8 @@ HistoryService::HistoryService( HistoryService::HistoryService( not_null history, - MTPDmessage_ClientFlags clientFlags, MsgId id, + MTPDmessage_ClientFlags clientFlags, TimeId date, const PreparedText &message, MTPDmessage::Flags flags, @@ -797,10 +797,9 @@ not_null GenerateJoinedMessage( TimeId inviteDate, not_null inviter, MTPDmessage::Flags flags) { - return new HistoryService( - history, - MTPDmessage_ClientFlag::f_local_history_entry, + return history->makeServiceMessage( history->owner().nextLocalMessageId(), + MTPDmessage_ClientFlag::f_local_history_entry, inviteDate, GenerateJoinedText(history, inviter), flags); diff --git a/Telegram/SourceFiles/history/history_service.h b/Telegram/SourceFiles/history/history_service.h index e1a50c71a..fe06d26ad 100644 --- a/Telegram/SourceFiles/history/history_service.h +++ b/Telegram/SourceFiles/history/history_service.h @@ -68,8 +68,8 @@ public: MTPDmessage_ClientFlags clientFlags); HistoryService( not_null history, - MTPDmessage_ClientFlags clientFlags, MsgId id, + MTPDmessage_ClientFlags clientFlags, TimeId date, const PreparedText &message, MTPDmessage::Flags flags = 0, diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index aee9f0fd7..f911161a2 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -41,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "data/data_scheduled_messages.h" #include "data/data_file_origin.h" +#include "data/data_histories.h" #include "history/history.h" #include "history/history_item.h" #include "history/history_message.h" @@ -144,22 +145,6 @@ void ActivateWindow(not_null controller) { Ui::ActivateWindowDelayed(window); } -bool ShowHistoryEndInsteadOfUnread( - not_null session, - PeerId peerId) { - // Ignore unread messages in case of unread changelogs. - // We must show this history at end for the changelog to be visible. - if (peerId != PeerData::kServiceNotificationsId) { - return false; - } - const auto history = session->history(peerId); - if (!history->unreadCount()) { - return false; - } - const auto last = history->lastMessage(); - return (last != nullptr) && !IsServerMsgId(last->id); -} - object_ptr SetupDiscussButton( not_null parent, not_null controller) { @@ -839,6 +824,7 @@ void HistoryWidget::scrollToCurrentVoiceMessage(FullMsgId fromId, FullMsgId toId void HistoryWidget::animatedScrollToItem(MsgId msgId) { Expects(_history != nullptr); + if (hasPendingResizedItems()) { updateListSize(); } @@ -857,6 +843,7 @@ void HistoryWidget::animatedScrollToItem(MsgId msgId) { void HistoryWidget::animatedScrollToY(int scrollTo, HistoryItem *attachTo) { Expects(_history != nullptr); + if (hasPendingResizedItems()) { updateListSize(); } @@ -1623,10 +1610,7 @@ void HistoryWidget::fastShowAtEnd(not_null history) { } clearAllLoadRequests(); - setMsgId(ShowAtUnreadMsgId); - _historyInited = false; - if (_history->isReadyFor(_showAtMsgId)) { historyLoaded(); } else { @@ -1692,6 +1676,18 @@ void HistoryWidget::applyCloudDraft(History *history) { } } +bool HistoryWidget::insideJumpToEndInsteadOfToUnread() const { + if (session().supportMode()) { + return true; + } else if (!_historyInited) { + return false; + } + _history->calculateFirstUnreadMessage(); + const auto unread = _history->firstUnreadMessage(); + const auto visibleBottom = _scroll->scrollTop() + _scroll->height(); + return unread && _list->itemTop(unread) <= visibleBottom; +} + void HistoryWidget::showHistory( const PeerId &peerId, MsgId showAtMsgId, @@ -1702,9 +1698,6 @@ void HistoryWidget::showHistory( const auto startBot = (showAtMsgId == ShowAndStartBotMsgId); if (startBot) { showAtMsgId = ShowAtTheEndMsgId; - } else if ((showAtMsgId == ShowAtUnreadMsgId) - && ShowHistoryEndInsteadOfUnread(&session().data(), peerId)) { - showAtMsgId = ShowAtTheEndMsgId; } clearHighlightMessages(); @@ -1712,7 +1705,16 @@ void HistoryWidget::showHistory( if (_peer->id == peerId && !reload) { updateForwarding(); - bool canShowNow = _history->isReadyFor(showAtMsgId); + if (showAtMsgId == ShowAtUnreadMsgId + && insideJumpToEndInsteadOfToUnread()) { + showAtMsgId = ShowAtTheEndMsgId; + } + if (!IsServerMsgId(showAtMsgId) + && !IsServerMsgId(-showAtMsgId)) { + // To end or to unread. + destroyUnreadBar(); + } + const auto canShowNow = _history->isReadyFor(showAtMsgId); if (!canShowNow) { delayedShowAt(showAtMsgId); } else { @@ -1734,11 +1736,12 @@ void HistoryWidget::showHistory( setMsgId(showAtMsgId); if (_historyInited) { - countHistoryShowFrom(); - destroyUnreadBar(); - - auto item = getItemFromHistoryOrMigrated(_showAtMsgId); - animatedScrollToY(countInitialScrollTop(), item); + const auto to = countInitialScrollTop(); + const auto item = getItemFromHistoryOrMigrated( + _showAtMsgId); + animatedScrollToY( + std::clamp(to, 0, _scroll->scrollTopMax()), + item); } else { historyLoaded(); } @@ -1763,14 +1766,13 @@ void HistoryWidget::showHistory( return; } updateSendAction(_history, SendAction::Type::Typing, -1); + session().data().histories().sendPendingReadInbox(_history); cancelTypingAction(); } session().data().stopPlayingVideoFiles(); clearReplyReturns(); - clearAllLoadRequests(); - if (_history) { if (Ui::InFocusChain(_list)) { // Removing focus from list clears selected and updates top bar. @@ -1786,10 +1788,12 @@ void HistoryWidget::showHistory( _history->showAtMsgId = _showAtMsgId; - destroyUnreadBar(); + destroyUnreadBarOnClose(); destroyPinnedBar(); _membersDropdown.destroy(); _scrollToAnimation.stop(); + + clearAllLoadRequests(); _history = _migrated = nullptr; _list = nullptr; _peer = nullptr; @@ -1896,7 +1900,9 @@ void HistoryWidget::showHistory( _updateHistoryItems.stop(); pinnedMsgVisibilityUpdated(); - if (_history->scrollTopItem || (_migrated && _migrated->scrollTopItem) || _history->isReadyFor(_showAtMsgId)) { + if (_history->scrollTopItem + || (_migrated && _migrated->scrollTopItem) + || _history->isReadyFor(_showAtMsgId)) { historyLoaded(); } else { firstLoadMessages(); @@ -1928,10 +1934,17 @@ void HistoryWidget::showHistory( } } } + if (!_history->folderKnown()) { + session().data().histories().requestDialogEntry(_history); + } if (_history->chatListUnreadMark()) { - session().api().changeDialogUnreadMark(_history, false); + _history->owner().histories().changeDialogUnreadMark( + _history, + false); if (_migrated) { - session().api().changeDialogUnreadMark(_migrated, false); + _migrated->owner().histories().changeDialogUnreadMark( + _migrated, + false); } // Must be done before unreadCountUpdated(), or we auto-close. @@ -1965,18 +1978,35 @@ void HistoryWidget::showHistory( void HistoryWidget::clearDelayedShowAt() { _delayedShowAtMsgId = -1; + clearDelayedShowAtRequest(); +} + +void HistoryWidget::clearDelayedShowAtRequest() { + Expects(_history != nullptr); + if (_delayedShowAtRequest) { - MTP::cancel(_delayedShowAtRequest); + _history->owner().histories().cancelRequest(_delayedShowAtRequest); _delayedShowAtRequest = 0; } } void HistoryWidget::clearAllLoadRequests() { - clearDelayedShowAt(); - if (_firstLoadRequest) MTP::cancel(_firstLoadRequest); - if (_preloadRequest) MTP::cancel(_preloadRequest); - if (_preloadDownRequest) MTP::cancel(_preloadDownRequest); - _preloadRequest = _preloadDownRequest = _firstLoadRequest = 0; + Expects(_history != nullptr); + + auto &histories = _history->owner().histories(); + clearDelayedShowAtRequest(); + if (_firstLoadRequest) { + histories.cancelRequest(_firstLoadRequest); + _firstLoadRequest = 0; + } + if (_preloadRequest) { + histories.cancelRequest(_preloadRequest); + _preloadRequest = 0; + } + if (_preloadDownRequest) { + histories.cancelRequest(_preloadDownRequest); + _preloadDownRequest = 0; + } } void HistoryWidget::updateFieldSubmitSettings() { @@ -2314,8 +2344,22 @@ void HistoryWidget::destroyUnreadBar() { if (_migrated) _migrated->destroyUnreadBar(); } +void HistoryWidget::destroyUnreadBarOnClose() { + if (!_history || !_historyInited) { + return; + } else if (_scroll->scrollTop() == _scroll->scrollTopMax()) { + destroyUnreadBar(); + return; + } + const auto top = unreadBarTop(); + if (top && *top < _scroll->scrollTop()) { + destroyUnreadBar(); + return; + } +} + void HistoryWidget::unreadMessageAdded(not_null item) { - if (_history != item->history()) { + if (_history != item->history() || !_historyInited) { return; } @@ -2326,16 +2370,18 @@ void HistoryWidget::unreadMessageAdded(not_null item) { // - on second we get wrong doWeReadServerHistory() and read both. session().data().sendHistoryChangeNotifications(); - if (_scroll->scrollTop() + 1 > _scroll->scrollTopMax()) { - destroyUnreadBar(); + const auto atBottom = (_scroll->scrollTop() >= _scroll->scrollTopMax()); + if (!atBottom) { + return; } - if (!App::wnd()->doWeReadServerHistory()) { + destroyUnreadBar(); + if (!doWeReadServerHistory()) { return; } if (item->isUnreadMention() && !item->isUnreadMedia()) { session().api().markMediaRead(item); } - session().api().readServerHistoryForce(_history); + session().data().histories().readInboxOnNewMessage(item); // Also clear possible scheduled messages notifications. session().notifications().clearFromHistory(_history); @@ -2365,8 +2411,10 @@ void HistoryWidget::unreadCountUpdated() { } } -bool HistoryWidget::messagesFailed(const RPCError &error, mtpRequestId requestId) { - if (MTP::isDefaultHandledError(error)) return false; +bool HistoryWidget::messagesFailed(const RPCError &error, int requestId) { + if (MTP::isDefaultHandledError(error)) { + return false; + } if (error.type() == qstr("CHANNEL_PRIVATE") || error.type() == qstr("CHANNEL_PUBLIC_GROUP_NA") @@ -2391,15 +2439,20 @@ bool HistoryWidget::messagesFailed(const RPCError &error, mtpRequestId requestId return true; } -void HistoryWidget::messagesReceived(PeerData *peer, const MTPmessages_Messages &messages, mtpRequestId requestId) { - if (!_history) { - _preloadRequest = _preloadDownRequest = _firstLoadRequest = _delayedShowAtRequest = 0; - return; - } +void HistoryWidget::messagesReceived(PeerData *peer, const MTPmessages_Messages &messages, int requestId) { + Expects(_history != nullptr); bool toMigrated = (peer == _peer->migrateFrom()); if (peer != _peer && !toMigrated) { - _preloadRequest = _preloadDownRequest = _firstLoadRequest = _delayedShowAtRequest = 0; + if (_preloadRequest == requestId) { + _preloadRequest = 0; + } else if (_preloadDownRequest == requestId) { + _preloadDownRequest = 0; + } else if (_firstLoadRequest == requestId) { + _firstLoadRequest = 0; + } else if (_delayedShowAtRequest == requestId) { + _delayedShowAtRequest = 0; + } return; } @@ -2464,7 +2517,9 @@ void HistoryWidget::messagesReceived(PeerData *peer, const MTPmessages_Messages addMessagesToBack(peer, *histList); _preloadDownRequest = 0; preloadHistoryIfNeeded(); - if (_history->loadedAtBottom() && App::wnd()) App::wnd()->checkHistoryActivation(); + if (_history->loadedAtBottom()) { + checkHistoryActivation(); + } } else if (_firstLoadRequest == requestId) { if (toMigrated) { _history->clear(History::ClearType::Unload); @@ -2486,22 +2541,19 @@ void HistoryWidget::messagesReceived(PeerData *peer, const MTPmessages_Messages _migrated->clear(History::ClearType::Unload); } - _delayedShowAtRequest = 0; + clearAllLoadRequests(); + _firstLoadRequest = -1; // hack - don't updateListSize yet _history->getReadyFor(_delayedShowAtMsgId); if (_history->isEmpty()) { - if (_preloadRequest) MTP::cancel(_preloadRequest); - if (_preloadDownRequest) MTP::cancel(_preloadDownRequest); - if (_firstLoadRequest) MTP::cancel(_firstLoadRequest); - _preloadRequest = _preloadDownRequest = 0; - _firstLoadRequest = -1; // hack - don't updateListSize yet addMessagesToFront(peer, *histList); - _firstLoadRequest = 0; - if (_history->loadedAtTop() - && _history->isEmpty() - && count > 0) { - firstLoadMessages(); - return; - } + } + _firstLoadRequest = 0; + + if (_history->loadedAtTop() + && _history->isEmpty() + && count > 0) { + firstLoadMessages(); + return; } while (_replyReturn) { if (_replyReturn->history() == _history @@ -2515,16 +2567,14 @@ void HistoryWidget::messagesReceived(PeerData *peer, const MTPmessages_Messages } } + _delayedShowAtRequest = 0; setMsgId(_delayedShowAtMsgId); - - _historyInited = false; historyLoaded(); } } void HistoryWidget::historyLoaded() { - countHistoryShowFrom(); - destroyUnreadBar(); + _historyInited = false; doneShow(); } @@ -2533,43 +2583,38 @@ void HistoryWidget::windowShown() { } bool HistoryWidget::doWeReadServerHistory() const { - if (!_history || !_list) return true; - if (_firstLoadRequest || _a_show.animating()) return false; - if (_history->loadedAtBottom()) { - int scrollTop = _scroll->scrollTop(); - if (scrollTop + 1 > _scroll->scrollTopMax()) return true; - - if (const auto unread = firstUnreadMessage()) { - const auto scrollBottom = scrollTop + _scroll->height(); - if (scrollBottom > _list->itemTop(unread)) { - return true; - } - } - } - if (_history->hasNotFreezedUnreadBar() - || (_migrated && _migrated->hasNotFreezedUnreadBar())) { - return true; - } - return false; + return doWeReadMentions() && !session().supportMode(); } bool HistoryWidget::doWeReadMentions() const { - if (!_history || !_list) return true; - if (_firstLoadRequest || _a_show.animating()) return false; - return true; + return _history + && _list + && _historyInited + && !_firstLoadRequest + && !_delayedShowAtRequest + && !_a_show.animating() + && App::wnd()->doWeMarkAsRead(); +} + +void HistoryWidget::checkHistoryActivation() { + if (_list) { + _list->checkHistoryActivation(); + } } void HistoryWidget::firstLoadMessages() { - if (!_history || _firstLoadRequest) return; + if (!_history || _firstLoadRequest) { + return; + } - auto from = _peer; + auto from = _history; auto offsetId = 0; auto offset = 0; auto loadCount = kMessagesPerPage; if (_showAtMsgId == ShowAtUnreadMsgId) { if (const auto around = _migrated ? _migrated->loadAroundId() : 0) { _history->getReadyFor(_showAtMsgId); - from = _migrated->peer; + from = _migrated; offset = -loadCount / 2; offsetId = around; } else if (const auto around = _history->loadAroundId()) { @@ -2589,7 +2634,7 @@ void HistoryWidget::firstLoadMessages() { } else if (_showAtMsgId < 0 && _history->isChannel()) { if (_showAtMsgId < 0 && -_showAtMsgId < ServerMaxMsgId && _migrated) { _history->getReadyFor(_showAtMsgId); - from = _migrated->peer; + from = _migrated; offset = -loadCount / 2; offsetId = -_showAtMsgId; } else if (_showAtMsgId == SwitchAtTopMsgId) { @@ -2602,28 +2647,42 @@ void HistoryWidget::firstLoadMessages() { auto minId = 0; auto historyHash = 0; - _firstLoadRequest = MTP::send( - MTPmessages_GetHistory( - from->input, + const auto history = from; + const auto type = Data::Histories::RequestType::History; + auto &histories = history->owner().histories(); + _firstLoadRequest = histories.sendRequest(history, type, [=](Fn finish) { + return history->session().api().request(MTPmessages_GetHistory( + history->peer->input, MTP_int(offsetId), MTP_int(offsetDate), MTP_int(offset), MTP_int(loadCount), MTP_int(maxId), MTP_int(minId), - MTP_int(historyHash)), - rpcDone(&HistoryWidget::messagesReceived, from), - rpcFail(&HistoryWidget::messagesFailed)); + MTP_int(historyHash) + )).done([=](const MTPmessages_Messages &result) { + messagesReceived(history->peer, result, _firstLoadRequest); + finish(); + }).fail([=](const RPCError &error) { + messagesFailed(error, _firstLoadRequest); + finish(); + }).send(); + }); } void HistoryWidget::loadMessages() { - if (!_history || _preloadRequest) return; + if (!_history || _preloadRequest) { + return; + } if (_history->isEmpty() && _migrated && _migrated->isEmpty()) { return firstLoadMessages(); } - auto loadMigrated = _migrated && (_history->isEmpty() || _history->loadedAtTop() || (!_migrated->isEmpty() && !_migrated->loadedAtBottom())); + auto loadMigrated = _migrated + && (_history->isEmpty() + || _history->loadedAtTop() + || (!_migrated->isEmpty() && !_migrated->loadedAtBottom())); auto from = loadMigrated ? _migrated : _history; if (from->loadedAtTop()) { return; @@ -2639,22 +2698,33 @@ void HistoryWidget::loadMessages() { auto minId = 0; auto historyHash = 0; - _preloadRequest = MTP::send( - MTPmessages_GetHistory( - from->peer->input, + const auto history = from; + const auto type = Data::Histories::RequestType::History; + auto &histories = history->owner().histories(); + _preloadRequest = histories.sendRequest(history, type, [=](Fn finish) { + return history->session().api().request(MTPmessages_GetHistory( + history->peer->input, MTP_int(offsetId), MTP_int(offsetDate), MTP_int(addOffset), MTP_int(loadCount), MTP_int(maxId), MTP_int(minId), - MTP_int(historyHash)), - rpcDone(&HistoryWidget::messagesReceived, from->peer.get()), - rpcFail(&HistoryWidget::messagesFailed)); + MTP_int(historyHash) + )).done([=](const MTPmessages_Messages &result) { + messagesReceived(history->peer, result, _preloadRequest); + finish(); + }).fail([=](const RPCError &error) { + messagesFailed(error, _preloadRequest); + finish(); + }).send(); + }); } void HistoryWidget::loadMessagesDown() { - if (!_history || _preloadDownRequest) return; + if (!_history || _preloadDownRequest) { + return; + } if (_history->isEmpty() && _migrated && _migrated->isEmpty()) { return firstLoadMessages(); @@ -2679,33 +2749,45 @@ void HistoryWidget::loadMessagesDown() { auto minId = 0; auto historyHash = 0; - _preloadDownRequest = MTP::send( - MTPmessages_GetHistory( - from->peer->input, + const auto history = from; + const auto type = Data::Histories::RequestType::History; + auto &histories = history->owner().histories(); + _preloadDownRequest = histories.sendRequest(history, type, [=](Fn finish) { + return history->session().api().request(MTPmessages_GetHistory( + history->peer->input, MTP_int(offsetId + 1), MTP_int(offsetDate), MTP_int(addOffset), MTP_int(loadCount), MTP_int(maxId), MTP_int(minId), - MTP_int(historyHash)), - rpcDone(&HistoryWidget::messagesReceived, from->peer.get()), - rpcFail(&HistoryWidget::messagesFailed)); + MTP_int(historyHash) + )).done([=](const MTPmessages_Messages &result) { + messagesReceived(history->peer, result, _preloadDownRequest); + finish(); + }).fail([=](const RPCError &error) { + messagesFailed(error, _preloadDownRequest); + finish(); + }).send(); + }); } void HistoryWidget::delayedShowAt(MsgId showAtMsgId) { - if (!_history || (_delayedShowAtRequest && _delayedShowAtMsgId == showAtMsgId)) return; + if (!_history + || (_delayedShowAtRequest && _delayedShowAtMsgId == showAtMsgId)) { + return; + } - clearDelayedShowAt(); + clearAllLoadRequests(); _delayedShowAtMsgId = showAtMsgId; - auto from = _peer; + auto from = _history; auto offsetId = 0; auto offset = 0; auto loadCount = kMessagesPerPage; if (_delayedShowAtMsgId == ShowAtUnreadMsgId) { if (const auto around = _migrated ? _migrated->loadAroundId() : 0) { - from = _migrated->peer; + from = _migrated; offset = -loadCount / 2; offsetId = around; } else if (const auto around = _history->loadAroundId()) { @@ -2721,7 +2803,7 @@ void HistoryWidget::delayedShowAt(MsgId showAtMsgId) { offsetId = _delayedShowAtMsgId; } else if (_delayedShowAtMsgId < 0 && _history->isChannel()) { if (_delayedShowAtMsgId < 0 && -_delayedShowAtMsgId < ServerMaxMsgId && _migrated) { - from = _migrated->peer; + from = _migrated; offset = -loadCount / 2; offsetId = -_delayedShowAtMsgId; } @@ -2731,18 +2813,27 @@ void HistoryWidget::delayedShowAt(MsgId showAtMsgId) { auto minId = 0; auto historyHash = 0; - _delayedShowAtRequest = MTP::send( - MTPmessages_GetHistory( - from->input, + const auto history = from; + const auto type = Data::Histories::RequestType::History; + auto &histories = history->owner().histories(); + _delayedShowAtRequest = histories.sendRequest(history, type, [=](Fn finish) { + return history->session().api().request(MTPmessages_GetHistory( + history->peer->input, MTP_int(offsetId), MTP_int(offsetDate), MTP_int(offset), MTP_int(loadCount), MTP_int(maxId), MTP_int(minId), - MTP_int(historyHash)), - rpcDone(&HistoryWidget::messagesReceived, from), - rpcFail(&HistoryWidget::messagesFailed)); + MTP_int(historyHash) + )).done([=](const MTPmessages_Messages &result) { + messagesReceived(history->peer, result, _delayedShowAtRequest); + finish(); + }).fail([=](const RPCError &error) { + messagesFailed(error, _delayedShowAtRequest); + finish(); + }).send(); + }); } void HistoryWidget::onScroll() { @@ -2775,29 +2866,15 @@ void HistoryWidget::visibleAreaUpdated() { const auto scrollBottom = scrollTop + _scroll->height(); _list->visibleAreaUpdated(scrollTop, scrollBottom); controller()->floatPlayerAreaUpdated().notify(true); - - const auto atBottom = (scrollTop >= _scroll->scrollTopMax()); - if (_history->loadedAtBottom() - && atBottom - && App::wnd()->doWeReadServerHistory()) { - // Clear possible scheduled messages notifications. - session().api().readServerHistory(_history); - session().notifications().clearFromHistory(_history); - } else if (_history->loadedAtBottom() - && (_history->unreadCount() > 0 - || (_migrated && _migrated->unreadCount() > 0))) { - const auto unread = firstUnreadMessage(); - const auto unreadVisible = unread - && (scrollBottom > _list->itemTop(unread)); - if (unreadVisible && App::wnd()->doWeReadServerHistory()) { - session().api().readServerHistory(_history); - } - } } } void HistoryWidget::preloadHistoryIfNeeded() { - if (_firstLoadRequest || _scroll->isHidden() || !_peer) { + if (_firstLoadRequest + || _delayedShowAtRequest + || _scroll->isHidden() + || !_peer + || !_historyInited) { return; } @@ -2816,7 +2893,11 @@ void HistoryWidget::preloadHistoryIfNeeded() { } void HistoryWidget::preloadHistoryByScroll() { - if (_firstLoadRequest || _scroll->isHidden() || !_peer) { + if (_firstLoadRequest + || _delayedShowAtRequest + || _scroll->isHidden() + || !_peer + || !_historyInited) { return; } @@ -2832,7 +2913,10 @@ void HistoryWidget::preloadHistoryByScroll() { } void HistoryWidget::checkReplyReturns() { - if (_firstLoadRequest || _scroll->isHidden() || !_peer) { + if (_firstLoadRequest + || _scroll->isHidden() + || !_peer + || !_historyInited) { return; } auto scrollTop = _scroll->scrollTop(); @@ -2881,9 +2965,7 @@ void HistoryWidget::historyDownClicked() { } else if (_replyReturn && _replyReturn->history() == _migrated) { showHistory(_peer->id, -_replyReturn->id); } else if (_peer) { - showHistory( - _peer->id, - session().supportMode() ? ShowAtTheEndMsgId : ShowAtUnreadMsgId); + showHistory(_peer->id, ShowAtUnreadMsgId); } } @@ -2936,7 +3018,8 @@ void HistoryWidget::saveEditMsg() { sendFlags |= MTPmessages_EditMessage::Flag::f_entities; } - _saveEditMsgRequestId = MTP::send( + const auto history = _history; + _saveEditMsgRequestId = history->session().api().request( MTPmessages_EditMessage( MTP_flags(sendFlags), _history->peer->input, @@ -2945,9 +3028,12 @@ void HistoryWidget::saveEditMsg() { MTPInputMedia(), MTPReplyMarkup(), sentEntities, - MTP_int(0)), // schedule_date - rpcDone(&HistoryWidget::saveEditMsgDone, _history), - rpcFail(&HistoryWidget::saveEditMsgFail, _history)); + MTP_int(0) + )).done([=](const MTPUpdates &result, mtpRequestId requestId) { + saveEditMsgDone(history, result, requestId); + }).fail([=](const RPCError &error, mtpRequestId requestId) { + saveEditMsgFail(history, error, requestId); + }).send(); } void HistoryWidget::saveEditMsgDone(History *history, const MTPUpdates &updates, mtpRequestId req) { @@ -2965,7 +3051,9 @@ void HistoryWidget::saveEditMsgDone(History *history, const MTPUpdates &updates, } bool HistoryWidget::saveEditMsgFail(History *history, const RPCError &error, mtpRequestId req) { - if (MTP::isDefaultHandledError(error)) return false; + if (MTP::isDefaultHandledError(error)) { + return false; + } if (req == _saveEditMsgRequestId) { _saveEditMsgRequestId = 0; } @@ -3243,10 +3331,8 @@ void HistoryWidget::doneShow() { handlePendingHistoryUpdate(); } preloadHistoryIfNeeded(); - if (App::wnd()) { - App::wnd()->checkHistoryActivation(); - App::wnd()->setInnerFocus(); - } + checkHistoryActivation(); + App::wnd()->setInnerFocus(); } void HistoryWidget::finishAnimating() { @@ -3791,7 +3877,9 @@ void HistoryWidget::inlineBotResolveDone( } bool HistoryWidget::inlineBotResolveFail(QString name, const RPCError &error) { - if (MTP::isDefaultHandledError(error)) return false; + if (MTP::isDefaultHandledError(error)) { + return false; + } _inlineBotResolveRequestId = 0; // Notify::inlineBotRequesting(false); @@ -5053,12 +5141,13 @@ MsgId HistoryWidget::replyToId() const { } int HistoryWidget::countInitialScrollTop() { - auto result = ScrollMax; if (_history->scrollTopItem || (_migrated && _migrated->scrollTopItem)) { - result = _list->historyScrollTop(); - } else if (_showAtMsgId && (_showAtMsgId > 0 || -_showAtMsgId < ServerMaxMsgId)) { - auto item = getItemFromHistoryOrMigrated(_showAtMsgId); - auto itemTop = _list->itemTop(item); + return _list->historyScrollTop(); + } else if (_showAtMsgId + && (IsServerMsgId(_showAtMsgId) + || IsServerMsgId(-_showAtMsgId))) { + const auto item = getItemFromHistoryOrMigrated(_showAtMsgId); + const auto itemTop = _list->itemTop(item); if (itemTop < 0) { setMsgId(0); return countInitialScrollTop(); @@ -5066,42 +5155,63 @@ int HistoryWidget::countInitialScrollTop() { const auto view = item->mainView(); Assert(view != nullptr); - result = itemTopForHighlight(view); enqueueMessageHighlight(view); + const auto result = itemTopForHighlight(view); + createUnreadBarIfBelowVisibleArea(result); + return result; } + } else if (_showAtMsgId == ShowAtTheEndMsgId) { + return ScrollMax; } else if (const auto top = unreadBarTop()) { - result = *top; + return *top; } else { + _history->calculateFirstUnreadMessage(); return countAutomaticScrollTop(); } - return qMin(result, _scroll->scrollTopMax()); +} + +void HistoryWidget::createUnreadBarIfBelowVisibleArea(int withScrollTop) { + if (_history->unreadBar()) { + return; + } + _history->calculateFirstUnreadMessage(); + if (const auto unread = _history->firstUnreadMessage()) { + if (_list->itemTop(unread) > withScrollTop) { + createUnreadBarAndResize(); + } + } +} + +void HistoryWidget::createUnreadBarAndResize() { + if (!_history->firstUnreadMessage()) { + return; + } + const auto was = base::take(_historyInited); + _history->addUnreadBar(); + if (hasPendingResizedItems()) { + updateListSize(); + } + _historyInited = was; } int HistoryWidget::countAutomaticScrollTop() { - auto result = ScrollMax; - if (const auto unread = firstUnreadMessage()) { - result = _list->itemTop(unread); + Expects(_history != nullptr); + Expects(_list != nullptr); + + if (const auto unread = _history->firstUnreadMessage()) { + const auto firstUnreadTop = _list->itemTop(unread); const auto possibleUnreadBarTop = _scroll->scrollTopMax() + HistoryView::UnreadBar::height() - HistoryView::UnreadBar::marginTop(); - if (result < possibleUnreadBarTop) { - const auto history = unread->data()->history(); - history->addUnreadBar(); - if (hasPendingResizedItems()) { - updateListSize(); - } - if (history->unreadBar() != nullptr) { + if (firstUnreadTop < possibleUnreadBarTop) { + createUnreadBarAndResize(); + if (_history->unreadBar() != nullptr) { setMsgId(ShowAtUnreadMsgId); - result = countInitialScrollTop(); - App::wnd()->checkHistoryActivation(); - if (session().supportMode()) { - history->unsetFirstUnreadMessage(); - } - return result; + return countInitialScrollTop(); } } } - return qMin(result, _scroll->scrollTopMax()); + return ScrollMax; } void HistoryWidget::updateHistoryGeometry( @@ -5145,10 +5255,10 @@ void HistoryWidget::updateHistoryGeometry( if (newScrollHeight <= 0) { return; } - auto wasScrollTop = _scroll->scrollTop(); - auto wasScrollTopMax = _scroll->scrollTopMax(); - auto wasAtBottom = wasScrollTop + 1 > wasScrollTopMax; - auto needResize = (_scroll->width() != width()) || (_scroll->height() != newScrollHeight); + const auto wasScrollTop = _scroll->scrollTop(); + const auto wasAtBottom = (wasScrollTop == _scroll->scrollTopMax()); + const auto needResize = (_scroll->width() != width()) + || (_scroll->height() != newScrollHeight); if (needResize) { _scroll->resize(width(), newScrollHeight); // on initial updateListSize we didn't put the _scroll->scrollTop correctly yet @@ -5177,52 +5287,30 @@ void HistoryWidget::updateHistoryGeometry( updateListSize(); _updateHistoryGeometryRequired = false; - if ((!initial && !wasAtBottom) - || (loadedDown - && (!_history->firstUnreadMessage() - || _history->unreadBar() - || _history->loadedAtBottom()) - && (!_migrated - || !_migrated->firstUnreadMessage() - || _migrated->unreadBar() - || _history->loadedAtBottom()))) { - const auto historyScrollTop = _list->historyScrollTop(); - if (!wasAtBottom && historyScrollTop == ScrollMax) { - // History scroll top was not inited yet. - // If we're showing locally unread messages, we get here - // from destroyUnreadBar() before we have time to scroll - // to good initial position, like top of an unread bar. - return; - } - auto toY = qMin(_list->historyScrollTop(), _scroll->scrollTopMax()); - if (change.type == ScrollChangeAdd) { - toY += change.value; - } else if (change.type == ScrollChangeNoJumpToBottom) { - toY = wasScrollTop; - } else if (_addToScroll) { - toY += _addToScroll; - _addToScroll = 0; - } - toY = snap(toY, 0, _scroll->scrollTopMax()); - if (_scroll->scrollTop() == toY) { - visibleAreaUpdated(); - } else { - synteticScrollToY(toY); - } - return; - } - + auto newScrollTop = 0; if (initial) { + newScrollTop = countInitialScrollTop(); _historyInited = true; _scrollToAnimation.stop(); + } else if (wasAtBottom && !loadedDown) { + newScrollTop = countAutomaticScrollTop(); + } else { + newScrollTop = std::min( + _list->historyScrollTop(), + _scroll->scrollTopMax()); + if (change.type == ScrollChangeAdd) { + newScrollTop += change.value; + } else if (change.type == ScrollChangeNoJumpToBottom) { + newScrollTop = wasScrollTop; + } else if (const auto add = base::take(_addToScroll)) { + newScrollTop += add; + } } - auto newScrollTop = initial - ? countInitialScrollTop() - : countAutomaticScrollTop(); - if (_scroll->scrollTop() == newScrollTop) { + const auto toY = std::clamp(newScrollTop, 0, _scroll->scrollTopMax()); + if (_scroll->scrollTop() == toY) { visibleAreaUpdated(); } else { - synteticScrollToY(newScrollTop); + synteticScrollToY(toY); } } @@ -5245,15 +5333,13 @@ bool HistoryWidget::hasPendingResizedItems() const { } std::optional HistoryWidget::unreadBarTop() const { - auto getUnreadBar = [this]() -> HistoryView::Element* { + const auto bar = [&]() -> HistoryView::Element* { if (const auto bar = _migrated ? _migrated->unreadBar() : nullptr) { return bar; - } else if (const auto bar = _history->unreadBar()) { - return bar; } - return nullptr; - }; - if (const auto bar = getUnreadBar()) { + return _history->unreadBar(); + }(); + if (bar) { const auto result = _list->itemTop(bar) + HistoryView::UnreadBar::marginTop(); if (bar->Has()) { @@ -5264,15 +5350,6 @@ std::optional HistoryWidget::unreadBarTop() const { return std::nullopt; } -HistoryView::Element *HistoryWidget::firstUnreadMessage() const { - if (_migrated) { - if (const auto result = _migrated->firstUnreadMessage()) { - return result; - } - } - return _history ? _history->firstUnreadMessage() : nullptr; -} - void HistoryWidget::addMessagesToFront(PeerData *peer, const QVector &messages) { _list->messagesReceived(peer, messages); if (!_firstLoadRequest) { @@ -5281,28 +5358,26 @@ void HistoryWidget::addMessagesToFront(PeerData *peer, const QVector } } -void HistoryWidget::addMessagesToBack(PeerData *peer, const QVector &messages) { +void HistoryWidget::addMessagesToBack( + PeerData *peer, + const QVector &messages) { + const auto checkForUnreadStart = [&] { + if (_history->unreadBar() || !_history->trackUnreadMessages()) { + return false; + } + _history->calculateFirstUnreadMessage(); + return !_history->firstUnreadMessage(); + }(); _list->messagesReceivedDown(peer, messages); + if (checkForUnreadStart) { + _history->calculateFirstUnreadMessage(); + createUnreadBarAndResize(); + } if (!_firstLoadRequest) { updateHistoryGeometry(false, true, { ScrollChangeNoJumpToBottom, 0 }); } } -void HistoryWidget::countHistoryShowFrom() { - if (_migrated - && _showAtMsgId == ShowAtUnreadMsgId - && _migrated->unreadCount()) { - _migrated->calculateFirstUnreadMessage(); - } - if ((_migrated && _migrated->firstUnreadMessage()) - || (_showAtMsgId != ShowAtUnreadMsgId) - || !_history->unreadCount()) { - _history->unsetFirstUnreadMessage(); - } else { - _history->calculateFirstUnreadMessage(); - } -} - void HistoryWidget::updateBotKeyboard(History *h, bool force) { if (h && h != _history && h != _migrated) { return; @@ -5419,7 +5494,7 @@ void HistoryWidget::updateHistoryDownPosition() { void HistoryWidget::updateHistoryDownVisibility() { if (_a_show.animating()) return; - auto haveUnreadBelowBottom = [&](History *history) { + const auto haveUnreadBelowBottom = [&](History *history) { if (!_list || !history || history->unreadCount() <= 0) { return false; } @@ -5430,7 +5505,7 @@ void HistoryWidget::updateHistoryDownVisibility() { const auto top = _list->itemTop(unread); return (top >= _scroll->scrollTop() + _scroll->height()); }; - auto historyDownIsVisible = [&] { + const auto historyDownIsVisible = [&] { if (!_list || _firstLoadRequest) { return false; } @@ -6192,7 +6267,7 @@ void HistoryWidget::cancelEdit() { applyDraft(); if (_saveEditMsgRequestId) { - MTP::cancel(_saveEditMsgRequestId); + _history->session().api().request(_saveEditMsgRequestId).cancel(); _saveEditMsgRequestId = 0; } @@ -7077,5 +7152,8 @@ void HistoryWidget::synteticScrollToY(int y) { } HistoryWidget::~HistoryWidget() { + if (_history) { + clearAllLoadRequests(); + } setTabbedPanel(nullptr); } diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 52548bac4..d98e41109 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -105,13 +105,13 @@ public: void start(); - void messagesReceived(PeerData *peer, const MTPmessages_Messages &messages, mtpRequestId requestId); void historyLoaded(); void windowShown(); - bool doWeReadServerHistory() const; - bool doWeReadMentions() const; + [[nodiscard]] bool doWeReadServerHistory() const; + [[nodiscard]] bool doWeReadMentions() const; bool skipItemRepaint(); + void checkHistoryActivation(); void leaveToChildEvent(QEvent *e, QWidget *child) override; void dragEnterEvent(QDragEnterEvent *e) override; @@ -216,8 +216,9 @@ public: void applyDraft( FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear); void showHistory(const PeerId &peer, MsgId showAtMsgId, bool reload = false); - void clearDelayedShowAt(); void clearAllLoadRequests(); + void clearDelayedShowAtRequest(); + void clearDelayedShowAt(); void saveFieldToHistoryLocalDraft(); void applyCloudDraft(History *history); @@ -560,6 +561,10 @@ private: // destroys _history and _migrated unread bars void destroyUnreadBar(); + void destroyUnreadBarOnClose(); + void createUnreadBarIfBelowVisibleArea(int withScrollTop); + [[nodiscard]] bool insideJumpToEndInsteadOfToUnread() const; + void createUnreadBarAndResize(); void saveEditMsg(); void saveEditMsgDone(History *history, const MTPUpdates &updates, mtpRequestId req); @@ -568,7 +573,8 @@ private: void checkPreview(); void requestPreview(); void gotPreview(QString links, const MTPMessageMedia &media, mtpRequestId req); - bool messagesFailed(const RPCError &error, mtpRequestId requestId); + void messagesReceived(PeerData *peer, const MTPmessages_Messages &messages, int requestId); + bool messagesFailed(const RPCError &error, int requestId); void addMessagesToFront(PeerData *peer, const QVector &messages); void addMessagesToBack(PeerData *peer, const QVector &messages); @@ -586,15 +592,12 @@ private: std::optional unreadBarTop() const; int itemTopForHighlight(not_null view) const; void scrollToCurrentVoiceMessage(FullMsgId fromId, FullMsgId toId); - HistoryView::Element *firstUnreadMessage() const; // Scroll to current y without updating the _lastUserScrolled time. // Used to distinguish between user scrolls and syntetic scrolls. // This one is syntetic. void synteticScrollToY(int y); - void countHistoryShowFrom(); - void writeDrafts(Data::Draft **localDraft, Data::Draft **editDraft); void writeDrafts(History *history); void setFieldText( @@ -683,12 +686,12 @@ private: bool _canSendMessages = false; MsgId _showAtMsgId = ShowAtUnreadMsgId; - mtpRequestId _firstLoadRequest = 0; - mtpRequestId _preloadRequest = 0; - mtpRequestId _preloadDownRequest = 0; + int _firstLoadRequest = 0; // Not real mtpRequestId. + int _preloadRequest = 0; // Not real mtpRequestId. + int _preloadDownRequest = 0; // Not real mtpRequestId. MsgId _delayedShowAtMsgId = -1; - mtpRequestId _delayedShowAtRequest = 0; + int _delayedShowAtRequest = 0; // Not real mtpRequestId. object_ptr _topBar; object_ptr _scroll; diff --git a/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp index b6c27a34e..20d3ba4cb 100644 --- a/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp @@ -201,6 +201,7 @@ void ComposeControls::init() { void ComposeControls::initField() { _field->setMaxHeight(st::historyComposeFieldMaxHeight); + _field->setSubmitSettings(_window->session().settings().sendSubmitWay()); //Ui::Connect(_field, &Ui::InputField::submitted, [=] { send(); }); Ui::Connect(_field, &Ui::InputField::cancelled, [=] { escape(); }); //Ui::Connect(_field, &Ui::InputField::tabbed, [=] { fieldTabbed(); }); diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index fe005b2a9..0b0f7458d 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -127,14 +127,8 @@ TextSelection ShiftItemSelection( return ShiftItemSelection(selection, byText.length()); } -void UnreadBar::init(int newCount) { - if (freezed) { - return; - } - count = newCount; - text = /*(count == kCountUnknown) // #feed - ? tr::lng_unread_bar_some(tr::now) - : */tr::lng_unread_bar(tr::now, lt_count, count); +void UnreadBar::init() { + text = tr::lng_unread_bar_some(tr::now); width = st::semiboldFont->width(text); } @@ -420,6 +414,18 @@ bool Element::computeIsAttachToPrevious(not_null previous) { return false; } +void Element::createUnreadBar() { + if (!AddComponents(UnreadBar::Bit())) { + return; + } + const auto bar = Get(); + bar->init(); + if (data()->mainView() == this) { + recountAttachToPreviousInBlocks(); + } + history()->owner().requestViewResize(this); +} + void Element::destroyUnreadBar() { if (!Has()) { return; @@ -431,29 +437,6 @@ void Element::destroyUnreadBar() { } } -void Element::setUnreadBarCount(int count) { - const auto changed = AddComponents(UnreadBar::Bit()); - const auto bar = Get(); - if (bar->freezed) { - return; - } - bar->init(count); - if (changed) { - if (data()->mainView() == this) { - recountAttachToPreviousInBlocks(); - } - history()->owner().requestViewResize(this); - } else { - history()->owner().requestViewRepaint(this); - } -} - -void Element::setUnreadBarFreezed() { - if (const auto bar = Get()) { - bar->freezed = true; - } -} - int Element::displayedDateHeight() const { if (auto date = Get()) { return date->height(); diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index cc1b75ddf..ced627482 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -96,26 +96,15 @@ TextSelection ShiftItemSelection( // Any HistoryView::Element can have this Component for // displaying the unread messages bar above the message. struct UnreadBar : public RuntimeComponent { - void init(int newCount); + void init(); static int height(); static int marginTop(); void paint(Painter &p, int y, int w) const; - static constexpr auto kCountUnknown = std::numeric_limits::max(); - QString text; int width = 0; - int count = 0; - - // If unread bar is freezed the new messages do not - // increment the counter displayed by this bar. - // - // It happens when we've opened the conversation and - // we've seen the bar and new messages are marked as read - // as soon as they are added to the chat history. - bool freezed = false; }; @@ -193,14 +182,9 @@ public: bool computeIsAttachToPrevious(not_null previous); - void setUnreadBarCount(int count); + void createUnreadBar(); void destroyUnreadBar(); - // marks the unread bar as freezed so that unread - // messages count will not change for this bar - // when the new messages arrive in this chat history - void setUnreadBarFreezed(); - int displayedDateHeight() const; bool displayDate() const; bool isInOneDayWithPrevious() const; diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 1c744c480..aa780918a 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -462,7 +462,7 @@ void ListWidget::checkUnreadBarCreation() { if (!_unreadBarElement) { if (const auto index = _delegate->listUnreadBarView(_items)) { _unreadBarElement = _items[*index].get(); - _unreadBarElement->setUnreadBarCount(UnreadBar::kCountUnknown); + _unreadBarElement->createUnreadBar(); refreshAttachmentsAtIndex(*index); } } @@ -2497,14 +2497,9 @@ void ListWidget::viewReplaced(not_null was, Element *now) { if (_overElement == was) _overElement = now; if (_unreadBarElement == was) { const auto bar = _unreadBarElement->Get(); - const auto count = bar ? bar->count : 0; - const auto freezed = bar ? bar->freezed : false; _unreadBarElement = now; - if (now && count) { - _unreadBarElement->setUnreadBarCount(count); - if (freezed) { - _unreadBarElement->setUnreadBarFreezed(); - } + if (now && bar) { + _unreadBarElement->createUnreadBar(); } } } diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 14628bbe5..f063d9f92 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -1170,6 +1170,9 @@ void Gif::validateGroupedCache( && thumb->height() < kUseNonBlurredThreshold)); if (good && !useGood) { good->load({}); + if (!useThumb) { + _data->loadThumbnail(_realParent->fullId()); + } } const auto loadLevel = useGood ? 3 : useThumb ? 2 : image ? 1 : 0; diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp index 960ec1078..a29cadc62 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp @@ -482,8 +482,9 @@ void Sticker::setupLottie(not_null document) const { void Sticker::prepareThumbnail() const { if (const auto document = getShownDocument()) { - if (document->sticker()->animated - && !_lottie + if (!_lottie + && document->sticker() + && document->sticker()->animated && document->loaded()) { setupLottie(document); } diff --git a/Telegram/SourceFiles/intro/intro_phone.cpp b/Telegram/SourceFiles/intro/intro_phone.cpp index c7f70c521..d31b3fea7 100644 --- a/Telegram/SourceFiles/intro/intro_phone.cpp +++ b/Telegram/SourceFiles/intro/intro_phone.cpp @@ -70,9 +70,11 @@ void PhoneWidget::setupQrLogin() { ) | rpl::then( account().appConfig().refreshed() ) | rpl::map([=] { - return account().appConfig().get( + const auto result = account().appConfig().get( "qr_login_code", - "disabled"); + "[not-set]"); + DEBUG_LOG(("PhoneWidget.qr_login_code: %1").arg(result)); + return result; }) | rpl::filter([](const QString &value) { return (value != "disabled"); }) | rpl::take(1) | rpl::start_with_next([=] { @@ -81,6 +83,8 @@ void PhoneWidget::setupQrLogin() { tr::lng_phone_to_qr(tr::now)); qrLogin->show(); + DEBUG_LOG(("PhoneWidget.qrLogin link created and shown.")); + rpl::combine( sizeValue(), qrLogin->widthValue() diff --git a/Telegram/SourceFiles/intro/intro_start.cpp b/Telegram/SourceFiles/intro/intro_start.cpp index 9f0fee57e..9d86a7197 100644 --- a/Telegram/SourceFiles/intro/intro_start.cpp +++ b/Telegram/SourceFiles/intro/intro_start.cpp @@ -33,7 +33,8 @@ void StartWidget::submit() { account().destroyStaleAuthorizationKeys(); const auto qrLogin = account().appConfig().get( "qr_login_code", - "disabled"); + "[not-set]"); + DEBUG_LOG(("qr_login_code: %1").arg(qrLogin)); if (qrLogin == "primary") { goNext(); } else { diff --git a/Telegram/SourceFiles/main/main_app_config.cpp b/Telegram/SourceFiles/main/main_app_config.cpp index 0f09e2ea0..08254a15a 100644 --- a/Telegram/SourceFiles/main/main_app_config.cpp +++ b/Telegram/SourceFiles/main/main_app_config.cpp @@ -47,6 +47,7 @@ void AppConfig::refresh() { _data.emplace_or_assign(qs(data.vkey()), data.vvalue()); }); } + DEBUG_LOG(("getAppConfig result handled.")); } _refreshed.fire({}); }).fail([=](const RPCError &error) { diff --git a/Telegram/SourceFiles/main/main_session.cpp b/Telegram/SourceFiles/main/main_session.cpp index 39fb80754..90de3897d 100644 --- a/Telegram/SourceFiles/main/main_session.cpp +++ b/Telegram/SourceFiles/main/main_session.cpp @@ -27,6 +27,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "observer_peer.h" #include "facades.h" +#ifndef TDESKTOP_DISABLE_SPELLCHECK +#include "chat_helpers/spellchecker_common.h" +#endif // TDESKTOP_DISABLE_SPELLCHECK + namespace Main { namespace { @@ -97,6 +101,10 @@ Session::Session( }); Window::Theme::Background()->start(); + +#ifndef TDESKTOP_DISABLE_SPELLCHECK + Spellchecker::Start(this); +#endif // TDESKTOP_DISABLE_SPELLCHECK } Session::~Session() { diff --git a/Telegram/SourceFiles/main/main_settings.cpp b/Telegram/SourceFiles/main/main_settings.cpp index 00c24c45d..b9f1f07b1 100644 --- a/Telegram/SourceFiles/main/main_settings.cpp +++ b/Telegram/SourceFiles/main/main_settings.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "support/support_common.h" #include "storage/serialize_common.h" #include "boxes/send_files_box.h" +#include "base/platform/base_platform_info.h" namespace Main { namespace { @@ -38,10 +39,17 @@ Settings::Variables::Variables() , selectorTab(ChatHelpers::SelectorTab::Emoji) , floatPlayerColumn(Window::Column::Second) , floatPlayerCorner(RectPart::TopRight) +, dialogsWidthRatio(ThirdColumnByDefault() + ? kDefaultBigDialogsWidthRatio + : kDefaultDialogsWidthRatio) , sendSubmitWay(Ui::InputSubmitSettings::Enter) , supportSwitch(Support::SwitchSettings::Next) { } +bool Settings::ThirdColumnByDefault() { + return Platform::IsMacStoreBuild(); +} + QByteArray Settings::serialize() const { const auto autoDownload = _variables.autoDownload.serialize(); auto size = sizeof(qint32) * 38; @@ -110,6 +118,11 @@ QByteArray Settings::serialize() const { } stream << qint32(SerializePlaybackSpeed(_variables.videoPlaybackSpeed.current())); stream << _variables.videoPipGeometry; + stream << qint32(_variables.dictionariesEnabled.current().size()); + for (const auto i : _variables.dictionariesEnabled.current()) { + stream << quint64(i); + } + stream << qint32(_variables.autoDownloadDictionaries.current() ? 1 : 0); } return result; } @@ -162,6 +175,8 @@ void Settings::constructFromSerialized(const QByteArray &serialized) { std::vector> mediaLastPlaybackPosition; qint32 videoPlaybackSpeed = SerializePlaybackSpeed(_variables.videoPlaybackSpeed.current()); QByteArray videoPipGeometry = _variables.videoPipGeometry; + std::vector dictionariesEnabled; + qint32 autoDownloadDictionaries = _variables.autoDownloadDictionaries.current() ? 1 : 0; stream >> versionTag; if (versionTag == kVersionTag) { @@ -288,6 +303,20 @@ void Settings::constructFromSerialized(const QByteArray &serialized) { if (!stream.atEnd()) { stream >> videoPipGeometry; } + if (!stream.atEnd()) { + auto count = qint32(0); + stream >> count; + if (stream.status() == QDataStream::Ok) { + for (auto i = 0; i != count; ++i) { + qint64 langId; + stream >> langId; + dictionariesEnabled.emplace_back(langId); + } + } + } + if (!stream.atEnd()) { + stream >> autoDownloadDictionaries; + } if (stream.status() != QDataStream::Ok) { LOG(("App Error: " "Bad data for Main::Settings::constructFromSerialized()")); @@ -377,6 +406,8 @@ void Settings::constructFromSerialized(const QByteArray &serialized) { _variables.mediaLastPlaybackPosition = std::move(mediaLastPlaybackPosition); _variables.videoPlaybackSpeed = DeserializePlaybackSpeed(videoPlaybackSpeed); _variables.videoPipGeometry = videoPipGeometry; + _variables.dictionariesEnabled = std::move(dictionariesEnabled); + _variables.autoDownloadDictionaries = (autoDownloadDictionaries == 1); } void Settings::setSupportChatsTimeSlice(int slice) { diff --git a/Telegram/SourceFiles/main/main_settings.h b/Telegram/SourceFiles/main/main_settings.h index c8cedc9d4..264f51f05 100644 --- a/Telegram/SourceFiles/main/main_settings.h +++ b/Telegram/SourceFiles/main/main_settings.h @@ -241,6 +241,31 @@ public: return _variables.spellcheckerEnabled.changes(); } + void setDictionariesEnabled(std::vector dictionaries) { + _variables.dictionariesEnabled = std::move(dictionaries); + } + + std::vector dictionariesEnabled() const { + return _variables.dictionariesEnabled.current(); + } + + rpl::producer> dictionariesEnabledChanges() const { + return _variables.dictionariesEnabled.changes(); + } + + void setAutoDownloadDictionaries(bool value) { + _variables.autoDownloadDictionaries = value; + } + bool autoDownloadDictionaries() const { + return _variables.autoDownloadDictionaries.current(); + } + rpl::producer autoDownloadDictionariesValue() const { + return _variables.autoDownloadDictionaries.value(); + } + rpl::producer autoDownloadDictionariesChanges() const { + return _variables.autoDownloadDictionaries.changes(); + } + [[nodiscard]] float64 videoPlaybackSpeed() const { return _variables.videoPlaybackSpeed.current(); } @@ -254,11 +279,14 @@ public: _variables.videoPipGeometry = geometry; } + [[nodiscard]] static bool ThirdColumnByDefault(); + private: struct Variables { Variables(); static constexpr auto kDefaultDialogsWidthRatio = 5. / 14; + static constexpr auto kDefaultBigDialogsWidthRatio = 0.275; static constexpr auto kDefaultThirdColumnWidth = 0; bool lastSeenWarningSeen = false; @@ -273,8 +301,7 @@ private: bool thirdSectionInfoEnabled = true; // per-window bool smallDialogsList = false; // per-window int thirdSectionExtendedBy = -1; // per-window - rpl::variable dialogsWidthRatio - = kDefaultDialogsWidthRatio; // per-window + rpl::variable dialogsWidthRatio; // per-window rpl::variable thirdColumnWidth = kDefaultThirdColumnWidth; // per-window Ui::InputSubmitSettings sendSubmitWay; @@ -296,6 +323,8 @@ private: std::vector> mediaLastPlaybackPosition; rpl::variable videoPlaybackSpeed = 1.; QByteArray videoPipGeometry; + rpl::variable> dictionariesEnabled; + rpl::variable autoDownloadDictionaries = true; static constexpr auto kDefaultSupportChatsLimitSlice = 7 * 24 * 60 * 60; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 7fde147f6..04478412b 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "data/data_scheduled_messages.h" #include "data/data_file_origin.h" +#include "data/data_histories.h" #include "api/api_text_entities.h" #include "ui/special_buttons.h" #include "ui/widgets/buttons.h" @@ -2225,10 +2226,8 @@ void MainWidget::dialogsToUp() { _dialogs->jumpToTop(); } -void MainWidget::markActiveHistoryAsRead() { - if (const auto activeHistory = _history->history()) { - session().api().readServerHistory(activeHistory); - } +void MainWidget::checkHistoryActivation() { + _history->checkHistoryActivation(); } void MainWidget::showAnimated(const QPixmap &bgAnimCache, bool back) { @@ -3527,15 +3526,8 @@ bool MainWidget::isActive() const { return !_isIdle && isVisible() && !_a_show.animating(); } -bool MainWidget::doWeReadServerHistory() const { - return isActive() - && !session().supportMode() - && !_mainSection - && _history->doWeReadServerHistory(); -} - -bool MainWidget::doWeReadMentions() const { - return isActive() && !_mainSection && _history->doWeReadMentions(); +bool MainWidget::doWeMarkAsRead() const { + return isActive() && !_mainSection; } bool MainWidget::lastWasOnline() const { @@ -3815,8 +3807,8 @@ void MainWidget::feedUpdates(const MTPUpdates &updates, uint64 randomId) { : nullptr; }; if (const auto id = owner.messageIdByRandomId(randomId)) { - if (const auto local = owner.message(id); - local->isScheduled()) { + const auto local = owner.message(id); + if (local && local->isScheduled()) { owner.scheduledMessages().sendNowSimpleMessage(d, local); } } @@ -4044,7 +4036,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { // d.vunread_count()->v, // d.vunread_muted_count()->v); // } else { - // session().api().requestDialogEntry(feed); + // session().data().histories().requestDialogEntry(feed); // } // } //} break; @@ -4420,7 +4412,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { session().api().requestPinnedDialogs(folder); } if (!loaded) { - session().api().requestDialogEntry(folder); + session().data().histories().requestDialogEntry(folder); } } break; @@ -4478,12 +4470,12 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { //if (const auto feed = channel->feed()) { // #feed // feed->requestChatListMessage(); // if (!feed->unreadCountKnown()) { - // feed->session().api().requestDialogEntry(feed); + // feed->owner().histories().requestDialogEntry(feed); // } //} else { history->requestChatListMessage(); if (!history->unreadCountKnown()) { - history->session().api().requestDialogEntry(history); + history->owner().histories().requestDialogEntry(history); } //} if (!channel->amCreator()) { diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index eba80811f..ea8963730 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -148,7 +148,7 @@ public: bool deleteChannelFailed(const RPCError &error); void historyToDown(History *hist); void dialogsToUp(); - void markActiveHistoryAsRead(); + void checkHistoryActivation(); PeerData *peer(); @@ -173,8 +173,7 @@ public: void updateOnlineDisplayIn(int32 msecs); bool isActive() const; - bool doWeReadServerHistory() const; - bool doWeReadMentions() const; + [[nodiscard]] bool doWeMarkAsRead() const; bool lastWasOnline() const; crl::time lastSetOnline() const; diff --git a/Telegram/SourceFiles/mainwindow.cpp b/Telegram/SourceFiles/mainwindow.cpp index 72c65438d..213a2e68a 100644 --- a/Telegram/SourceFiles/mainwindow.cpp +++ b/Telegram/SourceFiles/mainwindow.cpp @@ -130,7 +130,7 @@ void MainWindow::initHook() { Qt::QueuedConnection); } -void MainWindow::firstShow() { +void MainWindow::createTrayIconMenu() { #ifdef Q_OS_WIN trayIconMenu = new Ui::PopupMenu(nullptr); trayIconMenu->deleteOnHide(false); @@ -148,9 +148,48 @@ void MainWindow::firstShow() { trayIconMenu->addAction(tr::lng_minimize_to_tray(tr::now), this, SLOT(minimizeToTray())); trayIconMenu->addAction(notificationActionText, this, SLOT(toggleDisplayNotifyFromTray())); trayIconMenu->addAction(tr::ktg_quit_from_tray(tr::now), this, SLOT(quitFromTray())); + + initTrayMenuHook(); +} + +void MainWindow::applyInitialWorkMode() { Global::RefWorkMode().setForced(Global::WorkMode().value(), true); - psFirstShow(); + if (cWindowPos().maximized) { + DEBUG_LOG(("Window Pos: First show, setting maximized.")); + setWindowState(Qt::WindowMaximized); + } + if (cStartInTray() + || (cLaunchMode() == LaunchModeAutoStart + && cStartMinimized() + && !Core::App().passcodeLocked())) { + const auto minimizeAndHide = [=] { + DEBUG_LOG(("Window Pos: First show, setting minimized after.")); + setWindowState(windowState() | Qt::WindowMinimized); + if (Global::WorkMode().value() == dbiwmTrayOnly + || Global::WorkMode().value() == dbiwmWindowAndTray) { + hide(); + } + }; + + if (Platform::IsLinux()) { + // If I call hide() synchronously here after show() then on Ubuntu 14.04 + // it will show a window frame with transparent window body, without content. + // And to be able to "Show from tray" one more hide() will be required. + crl::on_main(this, minimizeAndHide); + } else { + minimizeAndHide(); + } + } + setPositionInited(); +} + +void MainWindow::finishFirstShow() { + createTrayIconMenu(); + initShadows(); + applyInitialWorkMode(); + createGlobalMenu(); + firstShadowsUpdate(); updateTrayMenu(); windowDeactivateEvents( @@ -194,6 +233,7 @@ void MainWindow::setupPasscodeLock() { if (animated) { _passcodeLock->showAnimated(bg); } else { + _passcodeLock->showFinished(); setInnerFocus(); } } @@ -463,23 +503,17 @@ void MainWindow::themeUpdated(const Window::Theme::BackgroundUpdate &data) { } } -bool MainWindow::doWeReadServerHistory() { +bool MainWindow::doWeMarkAsRead() { + if (!_main || Ui::isLayerShown()) { + return false; + } updateIsActive(0); - return isActive() - && !Ui::isLayerShown() - && (_main ? _main->doWeReadServerHistory() : false); -} - -bool MainWindow::doWeReadMentions() { - updateIsActive(0); - return isActive() - && !Ui::isLayerShown() - && (_main ? _main->doWeReadMentions() : false); + return isActive() && _main->doWeMarkAsRead(); } void MainWindow::checkHistoryActivation() { - if (doWeReadServerHistory()) { - _main->markActiveHistoryAsRead(); + if (_main) { + _main->checkHistoryActivation(); } } @@ -515,10 +549,12 @@ bool MainWindow::eventFilter(QObject *object, QEvent *e) { } break; case QEvent::MouseMove: { - if (_main && _main->isIdle()) { + const auto position = static_cast(e)->globalPos(); + if (_main && _main->isIdle() && _lastMousePosition != position) { Core::App().updateNonIdle(); _main->checkIdleFinish(); } + _lastMousePosition = position; } break; case QEvent::MouseButtonRelease: { @@ -556,8 +592,7 @@ bool MainWindow::eventFilter(QObject *object, QEvent *e) { void MainWindow::updateTrayMenu(bool force) { if (!trayIconMenu || (Platform::IsWindows() && !force)) return; - auto iconMenu = trayIconMenu; - auto actions = iconMenu->actions(); + auto actions = trayIconMenu->actions(); if (Platform::IsLinux()) { auto minimizeAction = actions.at(1); minimizeAction->setEnabled(isVisible()); @@ -571,12 +606,6 @@ void MainWindow::updateTrayMenu(bool force) { toggleAction->setText(active ? tr::lng_minimize_to_tray(tr::now) : tr::ktg_open_from_tray(tr::now)); - - // On macOS just remove trayIcon menu if the window is not active. - // So we will activate the window on click instead of showing the menu. - if (!active && Platform::IsMac()) { - iconMenu = nullptr; - } } auto notificationAction = actions.at(Platform::IsLinux() ? 2 : 1); auto notificationActionText = Global::DesktopNotify() diff --git a/Telegram/SourceFiles/mainwindow.h b/Telegram/SourceFiles/mainwindow.h index 11ba00c6d..d9b6fae0c 100644 --- a/Telegram/SourceFiles/mainwindow.h +++ b/Telegram/SourceFiles/mainwindow.h @@ -49,7 +49,7 @@ public: explicit MainWindow(not_null controller); ~MainWindow(); - void firstShow(); + void finishFirstShow(); void setupPasscodeLock(); void clearPasscodeLock(); @@ -62,8 +62,7 @@ public: MainWidget *mainWidget(); - bool doWeReadServerHistory(); - bool doWeReadMentions(); + [[nodiscard]] bool doWeMarkAsRead(); void activate(); @@ -152,9 +151,11 @@ signals: private: [[nodiscard]] bool skipTrayClick() const; + void createTrayIconMenu(); void handleTrayIconActication( QSystemTrayIcon::ActivationReason reason) override; + void applyInitialWorkMode(); void ensureLayerCreated(); void destroyLayer(); @@ -166,6 +167,7 @@ private: QImage icon16, icon32, icon64, iconbig16, iconbig32, iconbig64; crl::time _lastTrayClickTime = 0; + QPoint _lastMousePosition; object_ptr _passcodeLock = { nullptr }; object_ptr _intro = { nullptr }; diff --git a/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.cpp b/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.cpp index 68b56a388..e8fda438a 100644 --- a/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.cpp +++ b/Telegram/SourceFiles/media/clip/media_clip_ffmpeg.cpp @@ -16,8 +16,10 @@ namespace Clip { namespace internal { namespace { -constexpr int kSkipInvalidDataPackets = 10; -constexpr int kAlignImageBy = 16; +constexpr auto kSkipInvalidDataPackets = 10; + +// See https://github.com/telegramdesktop/tdesktop/issues/7225 +constexpr auto kAlignImageBy = 64; void alignedImageBufferCleanupHandler(void *data) { auto buffer = static_cast(data); diff --git a/Telegram/SourceFiles/media/player/media_player_instance.cpp b/Telegram/SourceFiles/media/player/media_player_instance.cpp index e150eb08b..214f1252c 100644 --- a/Telegram/SourceFiles/media/player/media_player_instance.cpp +++ b/Telegram/SourceFiles/media/player/media_player_instance.cpp @@ -189,14 +189,16 @@ void Instance::setCurrent(const AudioMsgId &audioId) { } } -void Instance::clearStreamed(not_null data) { +void Instance::clearStreamed(not_null data, bool savePosition) { if (!data->streamed || data->streamed->clearing) { return; } data->streamed->clearing = true; - SaveLastPlaybackPosition( - data->current.audio(), - data->streamed->instance.player().prepareLegacyState()); + if (savePosition) { + SaveLastPlaybackPosition( + data->current.audio(), + data->streamed->instance.player().prepareLegacyState()); + } data->streamed->instance.stop(); data->isPlaying = false; requestRoundVideoResize(); @@ -414,7 +416,7 @@ void Instance::playStreamed( const auto data = getData(audioId.type()); Assert(data != nullptr); - clearStreamed(data); + clearStreamed(data, data->current.audio() != audioId.audio()); data->streamed = std::make_unique( audioId, std::move(shared)); diff --git a/Telegram/SourceFiles/media/player/media_player_instance.h b/Telegram/SourceFiles/media/player/media_player_instance.h index d5472e40f..be8aa3a7d 100644 --- a/Telegram/SourceFiles/media/player/media_player_instance.h +++ b/Telegram/SourceFiles/media/player/media_player_instance.h @@ -225,7 +225,7 @@ private: not_null data, Streaming::Error &&error); - void clearStreamed(not_null data); + void clearStreamed(not_null data, bool savePosition = true); void emitUpdate(AudioMsgId::Type type); template void emitUpdate(AudioMsgId::Type type, CheckCallback check); diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_player.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_player.cpp index 643d2fd90..c2bc13648 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_player.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_player.cpp @@ -827,7 +827,6 @@ float64 Player::speed() const { } void Player::setSpeed(float64 speed) { - Expects(active()); Expects(speed >= 0.5 && speed <= 2.); if (!Media::Audio::SupportsSpeedControl()) { @@ -835,11 +834,13 @@ void Player::setSpeed(float64 speed) { } if (_options.speed != speed) { _options.speed = speed; - if (_audio) { - _audio->setSpeed(speed); - } - if (_video) { - _video->setSpeed(speed); + if (active()) { + if (_audio) { + _audio->setSpeed(speed); + } + if (_video) { + _video->setSpeed(speed); + } } } } diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_utility.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_utility.cpp index 297475c95..44fe63b8a 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_utility.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_utility.cpp @@ -212,6 +212,7 @@ void PaintFrameInner( QPainter &p, QRect to, const QImage &original, + bool alpha, int rotation) { const auto rotated = [](QRect rect, int rotation) { switch (rotation) { @@ -239,22 +240,32 @@ void PaintFrameInner( if (rotation) { p.rotate(rotation); } - p.drawImage(rotated(to, rotation), original); + const auto rect = rotated(to, rotation); + if (alpha) { + p.fillRect(rect, Qt::white); + } + p.drawImage(rect, original); } void PaintFrameContent( QPainter &p, const QImage &original, + bool alpha, int rotation, const FrameRequest &request) { - const auto full = request.outer; + const auto full = request.outer.isEmpty() + ? original.size() + : request.outer; + const auto size = request.resize.isEmpty() + ? original.size() + : request.resize; const auto to = QRect( - (full.width() - request.resize.width()) / 2, - (full.height() - request.resize.height()) / 2, - request.resize.width(), - request.resize.height()); + (full.width() - size.width()) / 2, + (full.height() - size.height()) / 2, + size.width(), + size.height()); PaintFrameOuter(p, to, full); - PaintFrameInner(p, to, original, rotation); + PaintFrameInner(p, to, original, alpha, rotation); } void ApplyFrameRounding(QImage &storage, const FrameRequest &request) { @@ -267,17 +278,21 @@ void ApplyFrameRounding(QImage &storage, const FrameRequest &request) { QImage PrepareByRequest( const QImage &original, + bool alpha, int rotation, const FrameRequest &request, QImage storage) { - Expects(!request.outer.isEmpty()); + Expects(!request.outer.isEmpty() || alpha); - if (!FFmpeg::GoodStorageForFrame(storage, request.outer)) { - storage = FFmpeg::CreateFrameStorage(request.outer); + const auto outer = request.outer.isEmpty() + ? original.size() + : request.outer; + if (!FFmpeg::GoodStorageForFrame(storage, outer)) { + storage = FFmpeg::CreateFrameStorage(outer); } QPainter p(&storage); - PaintFrameContent(p, original, rotation, request); + PaintFrameContent(p, original, alpha, rotation, request); p.end(); ApplyFrameRounding(storage, request); diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_utility.h b/Telegram/SourceFiles/media/streaming/media_streaming_utility.h index 0b28584e2..3a39eb7f9 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_utility.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_utility.h @@ -60,6 +60,7 @@ struct Stream { QImage storage); [[nodiscard]] QImage PrepareByRequest( const QImage &original, + bool alpha, int rotation, const FrameRequest &request, QImage storage); diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_video_track.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_video_track.cpp index 1feaa6852..b2cc6ff00 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_video_track.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_video_track.cpp @@ -361,6 +361,7 @@ void VideoTrackObject::presentFrameIfNeeded() { Expects(frame->position != kFinishedPosition); fillRequests(frame); + frame->alpha = (frame->decoded->format == AV_PIX_FMT_BGRA); frame->original = ConvertFrame( _stream, frame->decoded.get(), @@ -982,7 +983,8 @@ QImage VideoTrack::frame( unwrapped.updateFrameRequest(instance, useRequest); }); } - if (GoodForRequest(frame->original, _streamRotation, useRequest)) { + if (!frame->alpha + && GoodForRequest(frame->original, _streamRotation, useRequest)) { return frame->original; } else if (changed || none || i->second.image.isNull()) { const auto j = none @@ -1002,6 +1004,7 @@ QImage VideoTrack::frame( } j->second.image = PrepareByRequest( frame->original, + frame->alpha, _streamRotation, useRequest, std::move(j->second.image)); @@ -1025,7 +1028,8 @@ void VideoTrack::PrepareFrameByRequests( const auto end = frame->prepared.end(); for (auto i = begin; i != end; ++i) { auto &prepared = i->second; - if (!GoodForRequest(frame->original, rotation, prepared.request)) { + if (frame->alpha + || !GoodForRequest(frame->original, rotation, prepared.request)) { auto j = begin; for (; j != i; ++j) { if (j->second.request == prepared.request) { @@ -1036,6 +1040,7 @@ void VideoTrack::PrepareFrameByRequests( if (j == i) { prepared.image = PrepareByRequest( frame->original, + frame->alpha, rotation, prepared.request, std::move(prepared.image)); diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_video_track.h b/Telegram/SourceFiles/media/streaming/media_streaming_video_track.h index 434e061ee..e92ddc566 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_video_track.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_video_track.h @@ -83,6 +83,8 @@ private: crl::time display = kTimeUnknown; base::flat_map prepared; + + bool alpha = false; }; class Shared { diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 5588115cc..9e660ff11 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -920,8 +920,8 @@ void OverlayWidget::resizeContentByScreenSize() { _w = _width; _h = _height; } - _x = skipWidth + (usew - _w) / 2; - _y = skipHeight + (useh - _h) / 2; + _x = (width() - _w) / 2; + _y = (height() - _h) / 2; } float64 OverlayWidget::radialProgress() const { @@ -2573,17 +2573,26 @@ void OverlayWidget::playbackControlsVolumeChangeFinished() { } void OverlayWidget::playbackControlsSpeedChanged(float64 speed) { + DEBUG_LOG(("Media playback speed: change to %1.").arg(speed)); if (_doc) { + DEBUG_LOG(("Media playback speed: %1 to settings.").arg(speed)); _doc->session().settings().setVideoPlaybackSpeed(speed); _doc->session().saveSettingsDelayed(); } if (_streamed && !videoIsGifv()) { + DEBUG_LOG(("Media playback speed: %1 to _streamed.").arg(speed)); _streamed->instance.setSpeed(speed); } } float64 OverlayWidget::playbackControlsCurrentSpeed() { - return _doc ? _doc->session().settings().videoPlaybackSpeed() : 1.; + const auto result = _doc + ? _doc->session().settings().videoPlaybackSpeed() + : 1.; + DEBUG_LOG(("Media playback speed: now %1 (doc %2)." + ).arg(result + ).arg(Logs::b(_doc != nullptr))); + return result; } void OverlayWidget::switchToPip() { @@ -2682,12 +2691,12 @@ void OverlayWidget::validatePhotoImage(Image *image, bool blurred) { } else if (!_staticContent.isNull() && (blurred || !_blurred)) { return; } - const auto w = _width * cIntRetinaFactor(); - const auto h = _height * cIntRetinaFactor(); + const auto use = flipSizeByRotation({ _width, _height }) + * cIntRetinaFactor(); _staticContent = image->pixNoCache( fileOrigin(), - w, - h, + use.width(), + use.height(), Images::Option::Smooth | (blurred ? Images::Option::Blurred : Images::Option(0))); _staticContent.setDevicePixelRatio(cRetinaFactor()); @@ -3681,7 +3690,7 @@ void OverlayWidget::updateOver(QPoint pos) { } else if (documentContentShown() && contentRect().contains(pos)) { if ((_doc->isVideoFile() || _doc->isVideoMessage()) && _streamed) { updateOverState(OverVideo); - } else if (!_doc->loaded()) { + } else if (!_streamed && !_doc->loaded()) { updateOverState(OverIcon); } else if (_over != OverNone) { updateOverState(OverNone); diff --git a/Telegram/SourceFiles/media/view/media_view_pip.cpp b/Telegram/SourceFiles/media/view/media_view_pip.cpp index 040561f08..639f47f37 100644 --- a/Telegram/SourceFiles/media/view/media_view_pip.cpp +++ b/Telegram/SourceFiles/media/view/media_view_pip.cpp @@ -47,7 +47,7 @@ constexpr auto kMsInSecond = 1000; [[nodiscard]] QRect ScreenFromPosition(QPoint point) { const auto screen = [&]() -> QScreen* { for (const auto screen : QGuiApplication::screens()) { - if (screen->virtualGeometry().contains(point)) { + if (screen->geometry().contains(point)) { return screen; } } @@ -1391,6 +1391,7 @@ QImage Pip::videoFrame(const FrameRequest &request) const { if (state == ThumbState::Cover) { _preparedCoverStorage = Streaming::PrepareByRequest( _instance.info().video.cover, + false, _instance.info().video.rotation, request, std::move(_preparedCoverStorage)); diff --git a/Telegram/SourceFiles/media/view/media_view_playback_controls.cpp b/Telegram/SourceFiles/media/view/media_view_playback_controls.cpp index 8228db670..3b57c63a6 100644 --- a/Telegram/SourceFiles/media/view/media_view_playback_controls.cpp +++ b/Telegram/SourceFiles/media/view/media_view_playback_controls.cpp @@ -223,7 +223,7 @@ void PlaybackControls::showMenu() { addSpeed(1.75); addSpeed(2.); _menu.emplace(this, st::mediaviewControlsPopupMenu); - _menu->addAction("Rotate video", [=] { + _menu->addAction(tr::lng_mediaview_rotate_video(tr::now), [=] { _delegate->playbackControlsRotate(); }); _menu->addSeparator(); @@ -235,6 +235,7 @@ void PlaybackControls::showMenu() { } void PlaybackControls::updatePlaybackSpeed(float64 speed) { + DEBUG_LOG(("Media playback speed: update to %1.").arg(speed)); _delegate->playbackControlsSpeedChanged(speed); resizeEvent(nullptr); } diff --git a/Telegram/SourceFiles/mtproto/session_private.cpp b/Telegram/SourceFiles/mtproto/session_private.cpp index 830984cf0..1cba199f1 100644 --- a/Telegram/SourceFiles/mtproto/session_private.cpp +++ b/Telegram/SourceFiles/mtproto/session_private.cpp @@ -189,8 +189,9 @@ void SessionPrivate::appendTestConnection( }); }); + const auto protocolDcId = getProtocolDcId(); InvokeQueued(_testConnections.back().data, [=] { - weak->connectToServer(ip, port, protocolSecret, getProtocolDcId()); + weak->connectToServer(ip, port, protocolSecret, protocolDcId); }); } diff --git a/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp b/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp index d1676cdba..47dd56a41 100644 --- a/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp @@ -87,7 +87,7 @@ bool NativeSupported() { #ifndef TDESKTOP_FORCE_GTK_FILE_DIALOG return false; #endif // TDESKTOP_FORCE_GTK_FILE_DIALOG - return !Platform::IsXDGDesktopPortalPresent() + return !Platform::UseXDGDesktopPortal() && Platform::internal::GdkHelperLoaded() && (Libs::gtk_widget_hide_on_delete != nullptr) && (Libs::gtk_clipboard_store != nullptr) diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index 54ffe260d..4043235de 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -21,6 +21,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "facades.h" #include "app.h" +#include + #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION #include #endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION @@ -36,24 +38,13 @@ constexpr auto kAttentionPanelTrayIconName = "kotatogram-attention-panel"_cs; constexpr auto kSNIWatcherService = "org.kde.StatusNotifierWatcher"_cs; constexpr auto kTrayIconFilename = "kdesktop-trayicon-XXXXXX.png"_cs; -int32 _trayIconSize = 48; -bool _trayIconMuted = true; -int32 _trayIconCount = 0; -QImage _trayIconImageBack, _trayIconImage; -QString _trayIconThemeName, _trayIconName; +bool TrayIconMuted = true; +int32 TrayIconCount = 0; +base::flat_map TrayIconImageBack; +QIcon TrayIcon; +QString TrayIconThemeName, TrayIconName; -#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION -bool UseUnityCount = false; -QString UnityCountDesktopFile; -QString UnityCountDBusPath = "/"; -#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION - -#define QT_RED 0 -#define QT_GREEN 1 -#define QT_BLUE 2 -#define QT_ALPHA 3 - -QString GetTrayIconName() { +QString GetPanelIconName() { const auto counter = Core::App().unreadBadge(); const auto muted = Core::App().unreadBadgeMuted(); @@ -64,108 +55,175 @@ QString GetTrayIconName() { : kPanelTrayIconName.utf16(); } -QImage TrayIconImageGen() { +QString GetTrayIconName() { + const auto panelIconName = GetPanelIconName(); + + if (QIcon::hasThemeIcon(panelIconName)) { + return panelIconName; + } else if (InSandbox()) { + const auto launcherBasename = GetLauncherBasename(); + + if (QIcon::hasThemeIcon(launcherBasename)) { + return launcherBasename; + } + } else if (QIcon::hasThemeIcon(kTrayIconName.utf16())) { + return kTrayIconName.utf16(); + } + + return QString(); +} + +QIcon TrayIconGen() { + const auto iconThemeName = QIcon::themeName(); + const auto iconName = GetTrayIconName(); + + if (qEnvironmentVariableIsSet(kDisableTrayCounter.utf8()) + && !iconName.isEmpty()) { + if (TrayIcon.isNull() + || iconThemeName != TrayIconThemeName + || iconName != TrayIconName) { + TrayIcon = QIcon::fromTheme(iconName); + TrayIconThemeName = iconThemeName; + TrayIconName = iconName; + } + + return TrayIcon; + } + const auto counter = Core::App().unreadBadge(); const auto muted = Core::App().unreadBadgeMuted(); const auto counterSlice = (counter >= 1000) ? (1000 + (counter % 100)) : counter; - const auto iconThemeName = QIcon::themeName(); - const auto iconName = GetTrayIconName(); + if (TrayIcon.isNull() + || iconThemeName != TrayIconThemeName + || iconName != TrayIconName + || muted != TrayIconMuted + || counterSlice != TrayIconCount) { + QIcon result; + QIcon systemIcon; - if (_trayIconImage.isNull() - || _trayIconImage.width() != _trayIconSize - || iconThemeName != _trayIconThemeName - || iconName != _trayIconName - || muted != _trayIconMuted - || counterSlice != _trayIconCount) { - if (_trayIconImageBack.isNull() - || _trayIconImageBack.width() != _trayIconSize - || iconThemeName != _trayIconThemeName - || iconName != _trayIconName) { - _trayIconImageBack = Core::App().logo(); + const auto iconSizes = { + 16, + 22, + 24, + 32, + 48, + 64 + }; - _trayIconImageBack = QIcon::fromTheme( - iconName, - QIcon::fromTheme( - kTrayIconName.utf16(), - QIcon(QPixmap::fromImage(_trayIconImageBack))) - ).pixmap(_trayIconSize, _trayIconSize).toImage(); + for (const auto iconSize : iconSizes) { + auto ¤tImageBack = TrayIconImageBack[iconSize]; + const auto desiredSize = QSize(iconSize, iconSize); - auto w = _trayIconImageBack.width(), - h = _trayIconImageBack.height(); + if (currentImageBack.isNull() + || iconThemeName != TrayIconThemeName + || iconName != TrayIconName) { + if (!iconName.isEmpty()) { + if(systemIcon.isNull()) { + systemIcon = QIcon::fromTheme(iconName); + } - if (w != _trayIconSize || h != _trayIconSize) { - _trayIconImageBack = _trayIconImageBack.scaled( - _trayIconSize, - _trayIconSize, - Qt::IgnoreAspectRatio, - Qt::SmoothTransformation); + if (systemIcon.actualSize(desiredSize) == desiredSize) { + currentImageBack = systemIcon + .pixmap(desiredSize) + .toImage(); + } else { + const auto availableSizes = systemIcon + .availableSizes(); + + const auto biggestSize = ranges::max_element( + availableSizes, + std::less<>(), + &QSize::width); + + currentImageBack = systemIcon + .pixmap(*biggestSize) + .toImage(); + } + } else { + currentImageBack = Core::App().logo(); + } + + if (currentImageBack.size() != desiredSize) { + currentImageBack = currentImageBack.scaled( + desiredSize, + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation); + } } - _trayIconImageBack = _trayIconImageBack.convertToFormat( - QImage::Format_ARGB32); - } + auto iconImage = currentImageBack; + TrayIconMuted = muted; + TrayIconCount = counterSlice; + TrayIconThemeName = iconThemeName; + TrayIconName = iconName; - _trayIconImage = _trayIconImageBack; - _trayIconMuted = muted; - _trayIconCount = counterSlice; - _trayIconThemeName = iconThemeName; - _trayIconName = iconName; + if (!qEnvironmentVariableIsSet(kDisableTrayCounter.utf8()) + && counter > 0) { + QPainter p(&iconImage); + int32 layerSize = -16; - if (counter > 0) { - QPainter p(&_trayIconImage); - int32 layerSize = -16; + if (iconSize >= 48) { + layerSize = -32; + } else if (iconSize >= 36) { + layerSize = -24; + } else if (iconSize >= 32) { + layerSize = -20; + } - if (_trayIconSize >= 48) { - layerSize = -32; - } else if (_trayIconSize >= 36) { - layerSize = -24; - } else if (_trayIconSize >= 32) { - layerSize = -20; + auto &bg = muted + ? st::trayCounterBgMute + : st::trayCounterBg; + + auto &fg = st::trayCounterFg; + + auto layer = App::wnd()->iconWithCounter( + layerSize, + counter, + bg, + fg, + false); + + p.drawImage( + iconImage.width() - layer.width() - 1, + iconImage.height() - layer.height() - 1, + layer); } - auto &bg = (muted ? st::trayCounterBgMute : st::trayCounterBg); - auto &fg = st::trayCounterFg; - - auto layer = App::wnd()->iconWithCounter( - layerSize, - counter, - bg, - fg, - false); - - p.drawImage( - _trayIconImage.width() - layer.width() - 1, - _trayIconImage.height() - layer.height() - 1, - layer); + result.addPixmap(App::pixmapFromImageInPlace( + std::move(iconImage))); } + + TrayIcon = result; } - return _trayIconImage; + return TrayIcon; +} + +bool IsAppIndicator() { +#ifdef TDESKTOP_DISABLE_DBUS_INTEGRATION + static const auto AppIndicator = false; +#else // TDESKTOP_DISABLE_DBUS_INTEGRATION + static const auto AppIndicator = QDBusInterface( + qsl("com.canonical.indicator.application"), + qsl("/com/canonical/indicator/application/service"), + qsl("com.canonical.indicator.application.service")).isValid() + || QDBusInterface( + qsl("org.ayatana.indicator.application"), + qsl("/org/ayatana/indicator/application/service"), + qsl("org.ayatana.indicator.application.service")).isValid(); +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION + + return AppIndicator; } #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION static bool NeedTrayIconFile() { // Hack for indicator-application, which doesn't handle icons sent across D-Bus: // save the icon to a temp file and set the icon name to that filename. - static const auto TrayIconFileNeeded = [&] { - auto necessary = false; - const auto session = QDBusConnection::sessionBus(); - const auto pid = session.interface() - ->servicePid(kSNIWatcherService.utf16()).value(); - const auto processName = ProcessNameByPID(QString::number(pid)); - necessary = processName.endsWith( - qsl("indicator-application-service")); - if (!necessary) { - // Accessing to process name might be not allowed if the application - // is confined, thus we can just rely on the current desktop in use - necessary = DesktopEnvironment::IsUnity() - || DesktopEnvironment::IsMATE(); - } - return necessary; - }(); + static const auto TrayIconFileNeeded = IsAppIndicator(); return TrayIconFileNeeded; } @@ -176,12 +234,14 @@ static inline QString TrayIconFileTemplate() { } std::unique_ptr TrayIconFile( - const QPixmap &icon, QObject *parent) { + const QIcon &icon, + int size, + QObject *parent) { auto ret = std::make_unique( TrayIconFileTemplate(), parent); ret->open(); - icon.save(ret.get()); + icon.pixmap(size).save(ret.get()); ret->close(); return ret; } @@ -207,6 +267,18 @@ bool IsSNIAvailable() { return SNIAvailable; } +bool UseUnityCounter() { +#ifdef TDESKTOP_DISABLE_DBUS_INTEGRATION + static const auto UnityCounter = false; +#else // TDESKTOP_DISABLE_DBUS_INTEGRATION + static const auto UnityCounter = QDBusInterface( + "com.canonical.Unity", + "/").isValid(); +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION + + return UnityCounter; +} + quint32 djbStringHash(QString string) { quint32 hash = 5381; QByteArray chars = string.toLatin1(); @@ -247,24 +319,25 @@ void MainWindow::psTrayMenuUpdated() { } #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION -void MainWindow::setSNITrayIcon( - const QIcon &icon, const QPixmap &iconPixmap) { - if (!NeedTrayIconFile()) { - _sniTrayIcon->setIconByPixmap(icon); - _sniTrayIcon->setToolTipIconByPixmap(icon); - } +void MainWindow::setSNITrayIcon(const QIcon &icon) { + const auto iconName = GetTrayIconName(); - if (qEnvironmentVariableIsSet(kDisableTrayCounter.utf8())) { - const auto iconName = GetTrayIconName(); + if (qEnvironmentVariableIsSet(kDisableTrayCounter.utf8()) + && !iconName.isEmpty()) { _sniTrayIcon->setIconByName(iconName); _sniTrayIcon->setToolTipIconByName(iconName); } else if (NeedTrayIconFile()) { - _trayIconFile = TrayIconFile(iconPixmap, this); + _trayIconFile = TrayIconFile(icon, 22, this); + _trayToolTipIconFile = TrayIconFile(icon, 48, this); if (_trayIconFile) { _sniTrayIcon->setIconByName(_trayIconFile->fileName()); - _sniTrayIcon->setToolTipIconByName(_trayIconFile->fileName()); + _sniTrayIcon->setToolTipIconByName( + _trayToolTipIconFile->fileName()); } + } else { + _sniTrayIcon->setIconByPixmap(icon); + _sniTrayIcon->setToolTipIconByPixmap(icon); } } @@ -291,9 +364,6 @@ void MainWindow::attachToSNITrayIcon() { #endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION void MainWindow::psSetupTrayIcon() { - const auto iconPixmap = QPixmap::fromImage(TrayIconImageGen()); - const auto icon = QIcon(iconPixmap); - if (IsSNIAvailable()) { #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION LOG(("Using SNI tray icon.")); @@ -302,8 +372,8 @@ void MainWindow::psSetupTrayIcon() { QCoreApplication::applicationName(), this); - _sniTrayIcon->setTitle(QCoreApplication::applicationName()); - setSNITrayIcon(icon, iconPixmap); + _sniTrayIcon->setTitle(AppName.utf16()); + setSNITrayIcon(TrayIconGen()); attachToSNITrayIcon(); } @@ -319,7 +389,7 @@ void MainWindow::psSetupTrayIcon() { if (!trayIcon) { trayIcon = new QSystemTrayIcon(this); - trayIcon->setIcon(icon); + trayIcon->setIcon(TrayIconGen()); attachToTrayIcon(trayIcon); } @@ -362,8 +432,9 @@ void MainWindow::updateIconCounters() { updateWindowIcon(); #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION - if (UseUnityCount) { + if (UseUnityCounter()) { const auto counter = Core::App().unreadBadge(); + const auto launcherUrl = "application://" + GetLauncherFilename(); QVariantMap dbusUnityProperties; if (counter > 0) { // Gnome requires that count is a 64bit integer @@ -371,32 +442,30 @@ void MainWindow::updateIconCounters() { "count", (qint64) ((counter > 9999) ? 9999 - : (counter))); + : counter)); dbusUnityProperties.insert("count-visible", true); } else { dbusUnityProperties.insert("count-visible", false); } QDBusMessage signal = QDBusMessage::createSignal( - UnityCountDBusPath, + "/com/canonical/unity/launcherentry/" + + QString::number(djbStringHash(launcherUrl)), "com.canonical.Unity.LauncherEntry", "Update"); - signal << "application://" + UnityCountDesktopFile; + signal << launcherUrl; signal << dbusUnityProperties; QDBusConnection::sessionBus().send(signal); } #endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION - const auto iconPixmap = QPixmap::fromImage(TrayIconImageGen()); - const auto icon = QIcon(iconPixmap); - if (IsSNIAvailable()) { #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION if (_sniTrayIcon) { - setSNITrayIcon(icon, iconPixmap); + setSNITrayIcon(TrayIconGen()); } #endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION } else if (trayIcon) { - trayIcon->setIcon(icon); + trayIcon->setIcon(TrayIconGen()); } } @@ -406,71 +475,20 @@ void MainWindow::LibsLoaded() { qDBusRegisterMetaType(); qDBusRegisterMetaType(); #endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION - - if (!IsSNIAvailable()) { - _trayIconSize = 22; - } } -void MainWindow::psFirstShow() { +void MainWindow::initTrayMenuHook() { const auto trayAvailable = IsSNIAvailable() || QSystemTrayIcon::isSystemTrayAvailable(); LOG(("System tray available: %1").arg(Logs::b(trayAvailable))); cSetSupportTray(trayAvailable); -#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION - if (QDBusInterface("com.canonical.Unity", "/").isValid()) { - const std::vector possibleDesktopFiles = { - GetLauncherFilename(), - "Kotatogram.desktop" - }; - - for (auto it = possibleDesktopFiles.begin(); - it != possibleDesktopFiles.end(); it++) { - if (!QStandardPaths::locate( - QStandardPaths::ApplicationsLocation, *it).isEmpty()) { - UnityCountDesktopFile = *it; - LOG(("Found Unity Launcher entry %1!") - .arg(UnityCountDesktopFile)); - UseUnityCount = true; - break; - } - } - if (!UseUnityCount) { - LOG(("Could not get Unity Launcher entry!")); - } - UnityCountDBusPath = "/com/canonical/unity/launcherentry/" - + QString::number( - djbStringHash("application://" + UnityCountDesktopFile)); + if (UseUnityCounter()) { + LOG(("Using Unity launcher counter.")); } else { - LOG(("Not using Unity Launcher count.")); + LOG(("Not using Unity launcher counter.")); } -#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION - - show(); - if (cWindowPos().maximized) { - DEBUG_LOG(("Window Pos: First show, setting maximized.")); - setWindowState(Qt::WindowMaximized); - } - - if ((cLaunchMode() == LaunchModeAutoStart && cStartMinimized()) - || cStartInTray()) { - // If I call hide() synchronously here after show() then on Ubuntu 14.04 - // it will show a window frame with transparent window body, without content. - // And to be able to "Show from tray" one more hide() will be required. - crl::on_main(this, [=] { - setWindowState(Qt::WindowMinimized); - if (Global::WorkMode().value() == dbiwmTrayOnly - || Global::WorkMode().value() == dbiwmWindowAndTray) { - hide(); - } else { - show(); - } - }); - } - - setPositionInited(); } MainWindow::~MainWindow() { diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.h b/Telegram/SourceFiles/platform/linux/main_window_linux.h index b83132280..f6eadc5d1 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.h +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.h @@ -24,8 +24,6 @@ class MainWindow : public Window::MainWindow { public: explicit MainWindow(not_null controller); - void psFirstShow(); - virtual QImage iconWithCounter( int size, int count, @@ -43,6 +41,7 @@ public slots: protected: void unreadCounterChangedHook() override; + void initTrayMenuHook() override; bool hasTrayIcon() const override; void workmodeUpdated(DBIWorkMode mode) override; @@ -69,8 +68,9 @@ private: #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION StatusNotifierItem *_sniTrayIcon = nullptr; std::unique_ptr _trayIconFile = nullptr; + std::unique_ptr _trayToolTipIconFile = nullptr; - void setSNITrayIcon(const QIcon &icon, const QPixmap &iconPixmap); + void setSNITrayIcon(const QIcon &icon); void attachToSNITrayIcon(); #endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index c399f97b2..7f77c0235 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -88,8 +88,12 @@ QVersionNumber ParseSpecificationVersion( NotificationData::NotificationData( const std::shared_ptr ¬ificationInterface, const base::weak_ptr &manager, - const QString &title, const QString &subtitle, - const QString &msg, PeerId peerId, MsgId msgId) + const QString &title, + const QString &subtitle, + const QString &msg, + PeerId peerId, + MsgId msgId, + bool hideReplyButton) : _notificationInterface(notificationInterface) , _manager(manager) , _title(title) @@ -118,9 +122,9 @@ NotificationData::NotificationData( kInterface.utf16(), qsl("ActionInvoked"), this, - SLOT(notificationClicked(uint))); + SLOT(notificationClicked(uint,QString))); - if (capabilities.contains(qsl("inline-reply"))) { + if (capabilities.contains(qsl("inline-reply")) && !hideReplyButton) { _actions << qsl("inline-reply") << tr::lng_notification_reply(tr::now); @@ -169,12 +173,14 @@ NotificationData::NotificationData( SLOT(notificationClosed(uint))); } -bool NotificationData::show() { +bool NotificationData::show(bool hideNameAndPhoto) { const QDBusReply notifyReply = _notificationInterface->call( qsl("Notify"), AppName.utf16(), uint(0), - QString(), + hideNameAndPhoto + ? qsl("telegram") + : QString(), _title, _body, _actions, @@ -261,13 +267,19 @@ void NotificationData::notificationClosed(uint id) { } } -void NotificationData::notificationClicked(uint id) { - if (id == _notificationId) { - const auto manager = _manager; - crl::on_main(manager, [=] { - manager->notificationActivated(_peerId, _msgId); - }); +void NotificationData::notificationClicked(uint id, const QString &actionId) { + if (id != _notificationId) { + return; } + + if (actionId != qsl("default") && actionId != qsl("mail-reply-sender")) { + return; + } + + const auto manager = _manager; + crl::on_main(manager, [=] { + manager->notificationActivated(_peerId, _msgId); + }); } void NotificationData::notificationReplied(uint id, const QString &text) { @@ -279,7 +291,8 @@ void NotificationData::notificationReplied(uint id, const QString &text) { } } -QDBusArgument &operator<<(QDBusArgument &argument, +QDBusArgument &operator<<( + QDBusArgument &argument, const NotificationData::ImageData &imageData) { argument.beginStructure(); argument << imageData.width @@ -293,7 +306,8 @@ QDBusArgument &operator<<(QDBusArgument &argument, return argument; } -const QDBusArgument &operator>>(const QDBusArgument &argument, +const QDBusArgument &operator>>( + const QDBusArgument &argument, NotificationData::ImageData &imageData) { argument.beginStructure(); argument >> imageData.width @@ -374,12 +388,13 @@ void Manager::Private::showNotification( subtitle, msg, peer->id, - msgId); + msgId, + hideReplyButton); - const auto key = hideNameAndPhoto - ? InMemoryKey() - : peer->userpicUniqueKey(); - notification->setImage(_cachedUserpics.get(key, peer)); + if (!hideNameAndPhoto) { + const auto key = peer->userpicUniqueKey(); + notification->setImage(_cachedUserpics.get(key, peer)); + } auto i = _notifications.find(peer->id); if (i != _notifications.cend()) { @@ -395,7 +410,7 @@ void Manager::Private::showNotification( i = _notifications.insert(peer->id, QMap()); } _notifications[peer->id].insert(msgId, notification); - if (!notification->show()) { + if (!notification->show(hideNameAndPhoto)) { i = _notifications.find(peer->id); if (i != _notifications.cend()) { i->remove(msgId); diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h index 1addbb701..ef641b4a9 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h @@ -38,15 +38,19 @@ public: NotificationData( const std::shared_ptr ¬ificationInterface, const base::weak_ptr &manager, - const QString &title, const QString &subtitle, - const QString &msg, PeerId peerId, MsgId msgId); + const QString &title, + const QString &subtitle, + const QString &msg, + PeerId peerId, + MsgId msgId, + bool hideReplyButton); NotificationData(const NotificationData &other) = delete; NotificationData &operator=(const NotificationData &other) = delete; NotificationData(NotificationData &&other) = delete; NotificationData &operator=(NotificationData &&other) = delete; - bool show(); + bool show(bool hideNameAndPhoto); bool close(); void setImage(const QString &imagePath); @@ -72,16 +76,18 @@ private: private slots: void notificationClosed(uint id); - void notificationClicked(uint id); + void notificationClicked(uint id, const QString &actionId); void notificationReplied(uint id, const QString &text); }; using Notification = std::shared_ptr; -QDBusArgument &operator<<(QDBusArgument &argument, +QDBusArgument &operator<<( + QDBusArgument &argument, const NotificationData::ImageData &imageData); -const QDBusArgument &operator>>(const QDBusArgument &argument, +const QDBusArgument &operator>>( + const QDBusArgument &argument, NotificationData::ImageData &imageData); class Manager diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 6e7ced8c6..c1b80e28a 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -43,8 +43,7 @@ using Platform::File::internal::EscapeShell; namespace { constexpr auto kDesktopFile = ":/misc/kotatogramdesktop.desktop"_cs; - -bool XDGDesktopPortalPresent = false; +constexpr auto kSnapLauncherDir = "/var/lib/snapd/desktop/applications/"_cs; #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION void SandboxAutostart(bool autostart) { @@ -125,19 +124,25 @@ bool GenerateDesktopFile(const QString &targetPath, const QString &args) { DEBUG_LOG(("App Info: placing .desktop file to %1").arg(targetPath)); if (!QDir(targetPath).exists()) QDir().mkpath(targetPath); + const auto sourceFile = [&] { + if (InSnap()) { + return kSnapLauncherDir.utf16() + GetLauncherFilename(); + } else { + return kDesktopFile.utf16(); + } + }(); + const auto targetFile = targetPath + GetLauncherFilename(); QString fileText; - QFile source(kDesktopFile.utf16()); + QFile source(sourceFile); if (source.open(QIODevice::ReadOnly)) { QTextStream s(&source); fileText = s.readAll(); source.close(); } else { - LOG(("App Error: Could not open '%1' for read") - .arg(kDesktopFile.utf16())); - + LOG(("App Error: Could not open '%1' for read").arg(sourceFile)); return false; } @@ -196,9 +201,22 @@ bool InSnap() { } bool IsXDGDesktopPortalPresent() { +#ifdef TDESKTOP_DISABLE_DBUS_INTEGRATION + static const auto XDGDesktopPortalPresent = false; +#else // TDESKTOP_DISABLE_DBUS_INTEGRATION + static const auto XDGDesktopPortalPresent = QDBusInterface( + "org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop").isValid(); +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION return XDGDesktopPortalPresent; } +bool UseXDGDesktopPortal() { + static const auto UsePortal = qEnvironmentVariableIsSet("TDESKTOP_USE_PORTAL") + && IsXDGDesktopPortalPresent(); + return UsePortal; +} + QString ProcessNameByPID(const QString &pid) { constexpr auto kMaxPath = 1024; char result[kMaxPath] = { 0 }; @@ -264,24 +282,34 @@ QString SingleInstanceLocalServerName(const QString &hash) { QString GetLauncherBasename() { static const auto LauncherBasename = [&] { - QString launcherBasename; - if (InSnap()) { - launcherBasename = qsl("%1_%2") - .arg(QString::fromLatin1(qgetenv("SNAP_NAME"))) - .arg(qsl(MACRO_TO_STRING(TDESKTOP_LAUNCHER_BASENAME))); + const auto snapNameKey = + qEnvironmentVariableIsSet("SNAP_INSTANCE_NAME") + ? "SNAP_INSTANCE_NAME" + : "SNAP_NAME"; - LOG(("SNAP Environment detected, " - "launcher filename is %1.desktop") - .arg(launcherBasename)); - } else { - launcherBasename = - qsl(MACRO_TO_STRING(TDESKTOP_LAUNCHER_BASENAME)); + return qsl("%1_%2") + .arg(QString::fromLatin1(qgetenv(snapNameKey))) + .arg(cExeName()); } - return launcherBasename; + const auto possibleBasenames = std::vector{ + qsl(MACRO_TO_STRING(TDESKTOP_LAUNCHER_BASENAME)), + qsl("Telegram") + }; + + for (const auto &it : possibleBasenames) { + if (!QStandardPaths::locate( + QStandardPaths::ApplicationsLocation, + it + qsl(".desktop")).isEmpty()) { + return it; + } + } + + return possibleBasenames[0]; }(); + LOG(("Launcher filename is %1.desktop").arg(LauncherBasename)); return LauncherBasename; } @@ -406,20 +434,21 @@ namespace Platform { void start() { FallbackFontConfig(); -#if !defined(TDESKTOP_DISABLE_DBUS_INTEGRATION) && defined(TDESKTOP_FORCE_GTK_FILE_DIALOG) +#ifdef TDESKTOP_FORCE_GTK_FILE_DIALOG LOG(("Checking for XDG Desktop Portal...")); - XDGDesktopPortalPresent = QDBusInterface( - "org.freedesktop.portal.Desktop", - "/org/freedesktop/portal/desktop").isValid(); - // this can give us a chance to use a proper file dialog for current session - if(XDGDesktopPortalPresent) { + if(IsXDGDesktopPortalPresent()) { LOG(("XDG Desktop Portal is present!")); - qputenv("QT_QPA_PLATFORMTHEME", "xdgdesktopportal"); + if(UseXDGDesktopPortal()) { + LOG(("Usage of XDG Desktop Portal is enabled.")); + qputenv("QT_QPA_PLATFORMTHEME", "xdgdesktopportal"); + } else { + LOG(("Usage of XDG Desktop Portal is disabled.")); + } } else { LOG(("XDG Desktop Portal is not present :(")); } -#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION && TDESKTOP_FORCE_GTK_FILE_DIALOG +#endif // TDESKTOP_FORCE_GTK_FILE_DIALOG } void finish() { @@ -539,10 +568,19 @@ void psAutoStart(bool start, bool silent) { SandboxAutostart(start); #endif } else { - const auto autostart = - QStandardPaths::writableLocation( - QStandardPaths::GenericConfigLocation) - + qsl("/autostart/"); + const auto autostart = [&] { + if (InSnap()) { + QDir realHomeDir(home); + realHomeDir.cd(qsl("../../..")); + + return realHomeDir + .absoluteFilePath(qsl(".config/autostart/")); + } else { + return QStandardPaths::writableLocation( + QStandardPaths::GenericConfigLocation) + + qsl("/autostart/"); + } + }(); if (start) { GenerateDesktopFile(autostart, qsl("-autostart")); diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.h b/Telegram/SourceFiles/platform/linux/specific_linux.h index 3ce0640ff..f878dac71 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.h +++ b/Telegram/SourceFiles/platform/linux/specific_linux.h @@ -24,6 +24,7 @@ bool InSandbox(); bool InSnap(); bool IsXDGDesktopPortalPresent(); +bool UseXDGDesktopPortal(); QString ProcessNameByPID(const QString &pid); QString CurrentExecutablePath(int argc, char *argv[]); diff --git a/Telegram/SourceFiles/platform/mac/main_window_mac.h b/Telegram/SourceFiles/platform/mac/main_window_mac.h index f02027294..f8a90c9d7 100644 --- a/Telegram/SourceFiles/platform/mac/main_window_mac.h +++ b/Telegram/SourceFiles/platform/mac/main_window_mac.h @@ -23,8 +23,6 @@ class MainWindow : public Window::MainWindow { public: explicit MainWindow(not_null controller); - void psFirstShow(); - bool psFilterNativeEvent(void *event); virtual QImage iconWithCounter(int size, int count, style::color bg, style::color fg, bool smallIcon) = 0; @@ -87,14 +85,15 @@ protected: QTimer psUpdatedPositionTimer; + void initShadows() override; void closeWithoutDestroy() override; + void createGlobalMenu() override; private: friend class Private; void initTouchBar(); void hideAndDeactivate(); - void createGlobalMenu(); void updateTitleCounter(); void updateIconCounters(); diff --git a/Telegram/SourceFiles/platform/mac/main_window_mac.mm b/Telegram/SourceFiles/platform/mac/main_window_mac.mm index bc438c4b6..4ccfc5ee1 100644 --- a/Telegram/SourceFiles/platform/mac/main_window_mac.mm +++ b/Telegram/SourceFiles/platform/mac/main_window_mac.mm @@ -548,8 +548,15 @@ void MainWindow::psShowTrayMenu() { } void MainWindow::psTrayMenuUpdated() { - if (trayIcon && trayIconMenu && trayIcon->contextMenu() != trayIconMenu) { - trayIcon->setContextMenu(trayIconMenu); + // On macOS just remove trayIcon menu if the window is not active. + // So we will activate the window on click instead of showing the menu. + if (isActive()) { + if (trayIcon && trayIconMenu + && trayIcon->contextMenu() != trayIconMenu) { + trayIcon->setContextMenu(trayIconMenu); + } + } else if (trayIcon) { + trayIcon->setContextMenu(nullptr); } } @@ -656,31 +663,8 @@ void MainWindow::updateIconCounters() { } } -void MainWindow::psFirstShow() { - bool showShadows = true; - - show(); +void MainWindow::initShadows() { _private->enableShadow(winId()); - if (cWindowPos().maximized) { - DEBUG_LOG(("Window Pos: First show, setting maximized.")); - setWindowState(Qt::WindowMaximized); - } - - if ((cLaunchMode() == LaunchModeAutoStart && cStartMinimized()) || cStartInTray()) { - setWindowState(Qt::WindowMinimized); - if (Global::WorkMode().value() == dbiwmTrayOnly || Global::WorkMode().value() == dbiwmWindowAndTray) { - hide(); - } else { - show(); - } - showShadows = false; - } else { - show(); - } - - setPositionInited(); - - createGlobalMenu(); } void MainWindow::createGlobalMenu() { diff --git a/Telegram/SourceFiles/platform/win/main_window_win.cpp b/Telegram/SourceFiles/platform/win/main_window_win.cpp index 591c41194..b9688624a 100644 --- a/Telegram/SourceFiles/platform/win/main_window_win.cpp +++ b/Telegram/SourceFiles/platform/win/main_window_win.cpp @@ -808,41 +808,15 @@ void MainWindow::initHook() { psInitSysMenu(); } -Q_DECLARE_METATYPE(QMargins); -void MainWindow::psFirstShow() { +void MainWindow::initShadows() { _psShadowWindows.init(this, st::windowShadowFg->c); _shadowsWorking = true; - psUpdateMargins(); - shadowsUpdate(ShadowsChange::Hidden); - bool showShadows = true; +} - show(); - if (cWindowPos().maximized) { - DEBUG_LOG(("Window Pos: First show, setting maximized.")); - setWindowState(Qt::WindowMaximized); - } - - if (cStartInTray() - || (cLaunchMode() == LaunchModeAutoStart - && cStartMinimized() - && !Core::App().passcodeLocked())) { - DEBUG_LOG(("Window Pos: First show, setting minimized after.")); - setWindowState(windowState() | Qt::WindowMinimized); - if (Global::WorkMode().value() == dbiwmTrayOnly - || Global::WorkMode().value() == dbiwmWindowAndTray) { - hide(); - } else { - show(); - } - showShadows = false; - } else { - show(); - } - - setPositionInited(); - if (showShadows) { +void MainWindow::firstShadowsUpdate() { + if (!(windowState() & Qt::WindowMinimized) && !isHidden()) { shadowsUpdate(ShadowsChange::Moved | ShadowsChange::Resized | ShadowsChange::Shown); } } @@ -900,6 +874,7 @@ void MainWindow::updateSystemMenu(Qt::WindowState state) { } } +Q_DECLARE_METATYPE(QMargins); void MainWindow::psUpdateMargins() { if (!ps_hWnd || _inUpdateMargins) return; diff --git a/Telegram/SourceFiles/platform/win/main_window_win.h b/Telegram/SourceFiles/platform/win/main_window_win.h index 6ec9648d1..f599de881 100644 --- a/Telegram/SourceFiles/platform/win/main_window_win.h +++ b/Telegram/SourceFiles/platform/win/main_window_win.h @@ -28,7 +28,6 @@ public: HWND psHwnd() const; HMENU psMenu() const; - void psFirstShow(); void psInitSysMenu(); void updateSystemMenu(Qt::WindowState state); void psUpdateMargins(); @@ -77,6 +76,8 @@ protected: int32 screenNameChecksum(const QString &name) const override; void unreadCounterChangedHook() override; + void initShadows() override; + void firstShadowsUpdate() override; void stateChangedHook(Qt::WindowState state) override; bool hasTrayIcon() const override { diff --git a/Telegram/SourceFiles/qt_static_plugins.cpp b/Telegram/SourceFiles/qt_static_plugins.cpp index fb68f2439..6cfb24559 100644 --- a/Telegram/SourceFiles/qt_static_plugins.cpp +++ b/Telegram/SourceFiles/qt_static_plugins.cpp @@ -20,9 +20,22 @@ Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin) Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin) Q_IMPORT_PLUGIN(QGenericEnginePlugin) #elif defined Q_OS_LINUX // Q_OS_WIN | Q_OS_MAC +Q_IMPORT_PLUGIN(ShmServerBufferPlugin) +Q_IMPORT_PLUGIN(DmaBufServerBufferPlugin) +Q_IMPORT_PLUGIN(DrmEglServerBufferPlugin) +Q_IMPORT_PLUGIN(QWaylandEglClientBufferPlugin) +Q_IMPORT_PLUGIN(QWaylandIviShellIntegrationPlugin) +Q_IMPORT_PLUGIN(QWaylandWlShellIntegrationPlugin) +Q_IMPORT_PLUGIN(QWaylandXdgShellV5IntegrationPlugin) +Q_IMPORT_PLUGIN(QWaylandXdgShellV6IntegrationPlugin) +Q_IMPORT_PLUGIN(QWaylandXdgShellIntegrationPlugin) +Q_IMPORT_PLUGIN(QWaylandBradientDecorationPlugin) Q_IMPORT_PLUGIN(QXcbIntegrationPlugin) +Q_IMPORT_PLUGIN(QWaylandIntegrationPlugin) +Q_IMPORT_PLUGIN(QWaylandEglPlatformIntegrationPlugin) Q_IMPORT_PLUGIN(QGenericEnginePlugin) Q_IMPORT_PLUGIN(QComposePlatformInputContextPlugin) +Q_IMPORT_PLUGIN(QSvgIconPlugin) #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION Q_IMPORT_PLUGIN(QConnmanEnginePlugin) Q_IMPORT_PLUGIN(QNetworkManagerEnginePlugin) diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index 29f4b75f0..e4e22d777 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -216,3 +216,7 @@ settingsForwardPrivacyTooltipPadding: margins(8px, 6px, 8px, 6px); settingsAccentColorSize: 24px; settingsAccentColorSkip: 4px; settingsAccentColorLine: 3px; + +dictionariesSectionButton: SettingsButton(settingsUpdateToggle) { + font: font(14px semibold); +} diff --git a/Telegram/SourceFiles/settings/settings_advanced.cpp b/Telegram/SourceFiles/settings/settings_advanced.cpp index 6777ea0f1..c6f2809bb 100644 --- a/Telegram/SourceFiles/settings/settings_advanced.cpp +++ b/Telegram/SourceFiles/settings/settings_advanced.cpp @@ -33,6 +33,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_settings.h" #ifndef TDESKTOP_DISABLE_SPELLCHECK +#include "boxes/dictionaries_manager.h" +#include "chat_helpers/spellchecker_common.h" #include "spellcheck/platform/platform_spellcheck.h" #endif // !TDESKTOP_DISABLE_SPELLCHECK @@ -250,28 +252,72 @@ void SetupUpdate(not_null container) { bool HasSystemSpellchecker() { #ifdef TDESKTOP_DISABLE_SPELLCHECK return false; -#else - return Platform::Spellchecker::IsAvailable(); #endif // TDESKTOP_DISABLE_SPELLCHECK + return true; } void SetupSpellchecker( not_null controller, not_null container) { +#ifndef TDESKTOP_DISABLE_SPELLCHECK const auto session = &controller->session(); - AddButton( + const auto settings = &session->settings(); + const auto isSystem = Platform::Spellchecker::IsSystemSpellchecker(); + const auto button = AddButton( container, - tr::lng_settings_system_spellchecker(), + isSystem + ? tr::lng_settings_system_spellchecker() + : tr::lng_settings_custom_spellchecker(), st::settingsButton )->toggleOn( - rpl::single(session->settings().spellcheckerEnabled()) - )->toggledValue( + rpl::single(settings->spellcheckerEnabled()) + ); + + button->toggledValue( ) | rpl::filter([=](bool enabled) { - return (enabled != session->settings().spellcheckerEnabled()); + return (enabled != settings->spellcheckerEnabled()); }) | rpl::start_with_next([=](bool enabled) { - session->settings().setSpellcheckerEnabled(enabled); + settings->setSpellcheckerEnabled(enabled); session->saveSettingsDelayed(); }, container->lifetime()); + + if (isSystem) { + return; + } + + const auto sliding = container->add( + object_ptr>( + container, + object_ptr(container))); + + AddButton( + sliding->entity(), + tr::lng_settings_auto_download_dictionaries(), + st::settingsButton + )->toggleOn( + rpl::single(settings->autoDownloadDictionaries()) + )->toggledValue( + ) | rpl::filter([=](bool enabled) { + return (enabled != settings->autoDownloadDictionaries()); + }) | rpl::start_with_next([=](bool enabled) { + settings->setAutoDownloadDictionaries(enabled); + session->saveSettingsDelayed(); + }, sliding->entity()->lifetime()); + + AddButtonWithLabel( + sliding->entity(), + tr::lng_settings_manage_dictionaries(), + Spellchecker::ButtonManageDictsState(session), + st::settingsButton + )->addClickHandler([=] { + Ui::show(Box(session)); + }); + + button->toggledValue( + ) | rpl::start_with_next([=](bool enabled) { + sliding->toggle(enabled, anim::type::normal); + }, container->lifetime()); +#endif // !TDESKTOP_DISABLE_SPELLCHECK } bool HasTray() { diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp index 219e44f53..ecdf11066 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp @@ -165,8 +165,7 @@ AdminLog::OwnedItem GenerateForwardedItem( //MTPMessageReactions(), MTPVector() ).match([&](const MTPDmessage &data) { - return history->owner().makeMessage( - history, + return history->makeMessage( data, MTPDmessage_ClientFlag::f_fake_history_item); }, [](auto &&) -> not_null { diff --git a/Telegram/SourceFiles/stdafx.h b/Telegram/SourceFiles/stdafx.h index ccba4b9bc..5e5d6ead2 100644 --- a/Telegram/SourceFiles/stdafx.h +++ b/Telegram/SourceFiles/stdafx.h @@ -78,10 +78,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #endif // OS_MAC_OLD -// Fix Google Breakpad build for Mac App Store version -#ifdef Q_OS_MAC +// Fix Google Breakpad build for Mac App Store and Linux version +#if defined Q_OS_MAC || defined Q_OS_LINUX #define __STDC_FORMAT_MACROS -#endif // Q_OS_MAC +#endif // Q_OS_MAC || Q_OS_LINUX #include #include diff --git a/Telegram/SourceFiles/storage/localstorage.cpp b/Telegram/SourceFiles/storage/localstorage.cpp index 65f8556cc..f42220469 100644 --- a/Telegram/SourceFiles/storage/localstorage.cpp +++ b/Telegram/SourceFiles/storage/localstorage.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_drafts.h" #include "data/data_user.h" #include "boxes/send_files_box.h" +#include "base/platform/base_platform_info.h" #include "ui/widgets/input_fields.h" #include "ui/emoji_config.h" #include "export/export_settings.h" @@ -40,9 +41,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "facades.h" #include +#include #include #include +#ifndef Q_OS_WIN +#include +#endif // Q_OS_WIN + extern "C" { #include } // extern "C" @@ -113,10 +119,18 @@ inline constexpr auto is_flag_type(FileOption) { return true; }; bool keyAlreadyUsed(QString &name, FileOptions options = FileOption::User | FileOption::Safe) { name += '0'; - if (QFileInfo(name).exists()) return true; + if (QFileInfo(name).exists()) { + return true; + } if (options & (FileOption::Safe)) { name[name.size() - 1] = '1'; - return QFileInfo(name).exists(); + if (QFileInfo(name).exists()) { + return true; + } + name[name.size() - 1] = 's'; + if (QFileInfo(name).exists()) { + return true; + } } return false; } @@ -155,6 +169,8 @@ void clearKey(const FileKey &key, FileOptions options = FileOption::User | FileO if (options & FileOption::Safe) { name[name.size() - 1] = '1'; QFile::remove(name); + name[name.size() - 1] = 's'; + QFile::remove(name); } } @@ -235,10 +251,12 @@ struct EncryptedDescriptor { }; struct FileWriteDescriptor { - FileWriteDescriptor(const FileKey &key, FileOptions options = FileOption::User | FileOption::Safe) { + FileWriteDescriptor(const FileKey &key, FileOptions options = FileOption::User | FileOption::Safe) + : file((options & FileOption::Safe) ? (QFileDevice&)saveFile : plainFile) { init(toFilePart(key), options); } - FileWriteDescriptor(const QString &name, FileOptions options = FileOption::User | FileOption::Safe) { + FileWriteDescriptor(const QString &name, FileOptions options = FileOption::User | FileOption::Safe) + : file((options & FileOption::Safe) ? (QFileDevice&)saveFile : plainFile) { init(name, options); } void init(const QString &name, FileOptions options) { @@ -248,29 +266,13 @@ struct FileWriteDescriptor { if (!_working()) return; } - // detect order of read attempts and file version - QString toTry[2]; - toTry[0] = ((options & FileOption::User) ? _userBasePath : _basePath) + name + '0'; + const auto base = ((options & FileOption::User) ? _userBasePath : _basePath) + name; if (options & FileOption::Safe) { - toTry[1] = ((options & FileOption::User) ? _userBasePath : _basePath) + name + '1'; - QFileInfo toTry0(toTry[0]); - QFileInfo toTry1(toTry[1]); - if (toTry0.exists()) { - if (toTry1.exists()) { - QDateTime mod0 = toTry0.lastModified(), mod1 = toTry1.lastModified(); - if (mod0 > mod1) { - qSwap(toTry[0], toTry[1]); - } - } else { - qSwap(toTry[0], toTry[1]); - } - toDelete = toTry[1]; - } else if (toTry1.exists()) { - toDelete = toTry[1]; - } + toDelete = base; + saveFile.setFileName(base + 's'); + } else { + plainFile.setFileName(base + '0'); } - - file.setFileName(toTry[0]); if (file.open(QIODevice::WriteOnly)) { file.write(tdfMagic, tdfMagicLen); qint32 version = AppVersion; @@ -325,13 +327,21 @@ struct FileWriteDescriptor { md5.feed(&version, sizeof(version)); md5.feed(tdfMagic, tdfMagicLen); file.write((const char*)md5.result(), 0x10); - file.close(); + + if (saveFile.isOpen()) { + saveFile.commit(); + } else { + plainFile.close(); + } if (!toDelete.isEmpty()) { - QFile::remove(toDelete); + QFile::remove(toDelete + '0'); + QFile::remove(toDelete + '1'); } } - QFile file; + QFile plainFile; + QSaveFile saveFile; + QFileDevice &file; QDataStream stream; QString toDelete; @@ -351,25 +361,35 @@ bool readFile(FileReadDescriptor &result, const QString &name, FileOptions optio if (!_working()) return false; } + const auto base = ((options & FileOption::User) ? _userBasePath : _basePath) + name; + // detect order of read attempts QString toTry[2]; - toTry[0] = ((options & FileOption::User) ? _userBasePath : _basePath) + name + '0'; if (options & FileOption::Safe) { - QFileInfo toTry0(toTry[0]); - if (toTry0.exists()) { - toTry[1] = ((options & FileOption::User) ? _userBasePath : _basePath) + name + '1'; - QFileInfo toTry1(toTry[1]); - if (toTry1.exists()) { - QDateTime mod0 = toTry0.lastModified(), mod1 = toTry1.lastModified(); - if (mod0 < mod1) { - qSwap(toTry[0], toTry[1]); + const auto modern = base + 's'; + if (QFileInfo(modern).exists()) { + toTry[0] = modern; + } else { + // Legacy way. + toTry[0] = base + '0'; + QFileInfo toTry0(toTry[0]); + if (toTry0.exists()) { + toTry[1] = ((options & FileOption::User) ? _userBasePath : _basePath) + name + '1'; + QFileInfo toTry1(toTry[1]); + if (toTry1.exists()) { + QDateTime mod0 = toTry0.lastModified(), mod1 = toTry1.lastModified(); + if (mod0 < mod1) { + qSwap(toTry[0], toTry[1]); + } + } else { + toTry[1] = QString(); } } else { - toTry[1] = QString(); + toTry[0][toTry[0].size() - 1] = '1'; } - } else { - toTry[0][toTry[0].size() - 1] = '1'; } + } else { + toTry[0] = base + '0'; } for (int32 i = 0; i < 2; ++i) { QString fname(toTry[i]); @@ -2050,7 +2070,10 @@ bool _readOldMtpData(bool remove, ReadSettingsContext &context) { } void _writeUserSettings() { - if (_readingUserSettings) { + if (!_userWorking()) { + LOG(("App Error: attempt to write user settings too early!")); + return; + } else if (_readingUserSettings) { LOG(("App Error: attempt to write settings while reading them!")); return; } @@ -2574,6 +2597,7 @@ void finish() { } void InitialLoadTheme(); +bool ApplyDefaultNightMode(); void readLangPack(); void start() { @@ -2596,7 +2620,10 @@ void start() { _readOldMtpData(false, context); // needed further in _readMtpData applyReadContext(std::move(context)); - return writeSettings(); + if (!ApplyDefaultNightMode()) { + writeSettings(); + } + return; } LOG(("App Info: reading settings...")); @@ -2873,7 +2900,7 @@ base::flat_set CollectGoodNames() { _exportSettingsKey, _trustedBotsKey }; - auto result = base::flat_set{ "map0", "map1" }; + auto result = base::flat_set{ "map0", "map1", "maps" }; const auto push = [&](FileKey key) { if (!key) { return; @@ -2882,6 +2909,8 @@ base::flat_set CollectGoodNames() { result.emplace(name); name[name.size() - 1] = '1'; result.emplace(name); + name[name.size() - 1] = 's'; + result.emplace(name); }; for (const auto &value : _draftsMap) { push(value); @@ -4380,6 +4409,20 @@ void InitialLoadTheme() { } } +bool ApplyDefaultNightMode() { + const auto NightByDefault = Platform::IsMacStoreBuild(); + if (!NightByDefault + || Window::Theme::IsNightMode() + || _themeKeyDay + || _themeKeyNight + || _themeKeyLegacy) { + return false; + } + Window::Theme::ToggleNightMode(); + Window::Theme::KeepApplied(); + return true; +} + Window::Theme::Saved readThemeAfterSwitch() { const auto key = Window::Theme::IsNightMode() ? _themeKeyNight @@ -5113,7 +5156,7 @@ void ClearManager::onStart() { if (!QDir(di.filePath()).removeRecursively()) result = false; } else { QString path = di.filePath(); - if (!path.endsWith(qstr("map0")) && !path.endsWith(qstr("map1"))) { + if (!path.endsWith(qstr("map0")) && !path.endsWith(qstr("map1")) && !path.endsWith(qstr("maps"))) { if (!QFile::remove(di.filePath())) result = false; } } diff --git a/Telegram/SourceFiles/storage/storage_cloud_blob.cpp b/Telegram/SourceFiles/storage/storage_cloud_blob.cpp new file mode 100644 index 000000000..41d7d1c9d --- /dev/null +++ b/Telegram/SourceFiles/storage/storage_cloud_blob.cpp @@ -0,0 +1,153 @@ +/* +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 "storage/storage_cloud_blob.h" + +#include "base/zlib_help.h" +#include "core/application.h" +#include "lang/lang_keys.h" +#include "layout.h" +#include "main/main_account.h" + +namespace Storage::CloudBlob { + +namespace { + +QByteArray ReadFinalFile(const QString &path) { + constexpr auto kMaxZipSize = 10 * 1024 * 1024; + auto file = QFile(path); + if (file.size() > kMaxZipSize || !file.open(QIODevice::ReadOnly)) { + return QByteArray(); + } + return file.readAll(); +} + +bool ExtractZipFile(zlib::FileToRead &zip, const QString path) { + constexpr auto kMaxSize = 25 * 1024 * 1024; + const auto content = zip.readCurrentFileContent(kMaxSize); + if (content.isEmpty() || zip.error() != UNZ_OK) { + return false; + } + auto file = QFile(path); + return file.open(QIODevice::WriteOnly) + && (file.write(content) == content.size()); +} + +} // namespace + +bool UnpackBlob( + const QString &path, + const QString &folder, + Fn checkNameCallback) { + const auto bytes = ReadFinalFile(path); + if (bytes.isEmpty()) { + return false; + } + auto zip = zlib::FileToRead(bytes); + if (zip.goToFirstFile() != UNZ_OK) { + return false; + } + do { + const auto name = zip.getCurrentFileName(); + const auto path = folder + '/' + name; + if (checkNameCallback(name) && !ExtractZipFile(zip, path)) { + return false; + } + + const auto jump = zip.goToNextFile(); + if (jump == UNZ_END_OF_LIST_OF_FILE) { + break; + } else if (jump != UNZ_OK) { + return false; + } + } while (true); + return true; +} + +QString StateDescription(const BlobState &state, tr::phrase<> activeText) { + return state.match([](const Available &data) { + return tr::lng_emoji_set_download( + tr::now, + lt_size, + formatSizeText(data.size)); + }, [](const Ready &data) -> QString { + return tr::lng_emoji_set_ready(tr::now); + }, [&](const Active &data) -> QString { + return activeText(tr::now); + }, [](const Loading &data) { + const auto percent = (data.size > 0) + ? snap((data.already * 100) / float64(data.size), 0., 100.) + : 0.; + return tr::lng_emoji_set_loading( + tr::now, + lt_percent, + QString::number(int(std::round(percent))) + '%', + lt_progress, + formatDownloadText(data.already, data.size)); + }, [](const Failed &data) { + return tr::lng_attach_failed(tr::now); + }); +} + +BlobLoader::BlobLoader( + QObject *parent, + int id, + MTP::DedicatedLoader::Location location, + const QString &folder, + int size) +: QObject(parent) +, _folder(folder) +, _id(id) +, _state(Loading{ 0, size }) +, _mtproto(Core::App().activeAccount().mtp()) { + const auto ready = [=](std::unique_ptr loader) { + if (loader) { + setImplementation(std::move(loader)); + } else { + fail(); + } + }; + MTP::StartDedicatedLoader(&_mtproto, location, _folder, ready); +} + +int BlobLoader::id() const { + return _id; +} + +rpl::producer BlobLoader::state() const { + return _state.value(); +} + +void BlobLoader::setImplementation( + std::unique_ptr loader) { + _implementation = std::move(loader); + auto convert = [](auto value) { + return BlobState(value); + }; + _state = _implementation->progress( + ) | rpl::map([](const Loading &state) { + return BlobState(state); + }); + _implementation->failed( + ) | rpl::start_with_next([=] { + fail(); + }, _implementation->lifetime()); + + _implementation->ready( + ) | rpl::start_with_next([=](const QString &filepath) { + unpack(filepath); + }, _implementation->lifetime()); + + QDir(_folder).removeRecursively(); + _implementation->start(); +} + +void BlobLoader::fail() { + _state = Failed(); +} + +} // namespace Storage::CloudBlob diff --git a/Telegram/SourceFiles/storage/storage_cloud_blob.h b/Telegram/SourceFiles/storage/storage_cloud_blob.h new file mode 100644 index 000000000..af6301ca0 --- /dev/null +++ b/Telegram/SourceFiles/storage/storage_cloud_blob.h @@ -0,0 +1,109 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "mtproto/dedicated_file_loader.h" + +namespace tr { +template +struct phrase; +} // namespace tr + +namespace Storage::CloudBlob { + +constexpr auto kCloudLocationUsername = "tdhbcfiles"_cs; + +struct Blob { + int id = 0; + int postId = 0; + int size = 0; + QString name; +}; + +struct Available { + int size = 0; + + inline bool operator<(const Available &other) const { + return size < other.size; + } + inline bool operator==(const Available &other) const { + return size == other.size; + } +}; +struct Ready { + inline bool operator<(const Ready &other) const { + return false; + } + inline bool operator==(const Ready &other) const { + return true; + } +}; +struct Active { + inline bool operator<(const Active &other) const { + return false; + } + inline bool operator==(const Active &other) const { + return true; + } +}; +struct Failed { + inline bool operator<(const Failed &other) const { + return false; + } + inline bool operator==(const Failed &other) const { + return true; + } +}; + +using Loading = MTP::DedicatedLoader::Progress; +using BlobState = base::variant< + Available, + Ready, + Active, + Failed, + Loading>; + +bool UnpackBlob( + const QString &path, + const QString &folder, + Fn checkNameCallback); + +QString StateDescription(const BlobState &state, tr::phrase<> activeText); + +class BlobLoader : public QObject { +public: + BlobLoader( + QObject *parent, + int id, + MTP::DedicatedLoader::Location location, + const QString &folder, + int size); + + int id() const; + + rpl::producer state() const; + virtual void destroy() = 0; + virtual void unpack(const QString &path) = 0; + +protected: + virtual void fail(); + + const QString _folder; + +private: + void setImplementation(std::unique_ptr loader); + + int _id = 0; + rpl::variable _state; + + MTP::WeakInstance _mtproto; + std::unique_ptr _implementation; + +}; + +} // namespace Storage::CloudBlob diff --git a/Telegram/SourceFiles/support/support_autocomplete.cpp b/Telegram/SourceFiles/support/support_autocomplete.cpp index ddc65146d..a8724c310 100644 --- a/Telegram/SourceFiles/support/support_autocomplete.cpp +++ b/Telegram/SourceFiles/support/support_autocomplete.cpp @@ -275,8 +275,7 @@ AdminLog::OwnedItem GenerateCommentItem( const auto clientFlags = MTPDmessage_ClientFlag::f_fake_history_item; const auto replyTo = 0; const auto viaBotId = 0; - const auto item = history->owner().makeMessage( - history, + const auto item = history->makeMessage( id, flags, clientFlags, @@ -322,8 +321,7 @@ AdminLog::OwnedItem GenerateContactItem( MTP_long(0), //MTPMessageReactions(), MTPVector()); - const auto item = history->owner().makeMessage( - history, + const auto item = history->makeMessage( message.c_message(), MTPDmessage_ClientFlag::f_fake_history_item); return AdminLog::OwnedItem(delegate, item); diff --git a/Telegram/SourceFiles/support/support_helper.cpp b/Telegram/SourceFiles/support/support_helper.cpp index 00d3cf6dc..79010e8b4 100644 --- a/Telegram/SourceFiles/support/support_helper.cpp +++ b/Telegram/SourceFiles/support/support_helper.cpp @@ -77,7 +77,7 @@ EditInfoBox::EditInfoBox( text) , _submit(std::move(submit)) { _field->setMaxLength(kMaxSupportInfoLength); - _field->setSubmitSettings(Ui::InputField::SubmitSettings::Both); + _field->setSubmitSettings(session->settings().sendSubmitWay()); _field->setInstantReplaces(Ui::InstantReplaces::Default()); _field->setInstantReplacesEnabled( session->settings().replaceEmojiValue()); diff --git a/Telegram/SourceFiles/window/main_window.cpp b/Telegram/SourceFiles/window/main_window.cpp index 4347d6258..285cf8d17 100644 --- a/Telegram/SourceFiles/window/main_window.cpp +++ b/Telegram/SourceFiles/window/main_window.cpp @@ -392,17 +392,27 @@ void MainWindow::initSize() { auto position = cWindowPos(); DEBUG_LOG(("Window Pos: Initializing first %1, %2, %3, %4 (maximized %5)").arg(position.x).arg(position.y).arg(position.w).arg(position.h).arg(Logs::b(position.maximized))); - auto avail = QDesktopWidget().availableGeometry(); + const auto primaryScreen = QGuiApplication::primaryScreen(); + auto geometryScreen = primaryScreen; + const auto available = primaryScreen + ? primaryScreen->availableGeometry() + : QRect(0, 0, st::windowDefaultWidth, st::windowDefaultHeight); bool maximized = false; - auto geom = QRect( - avail.x() + std::max( - (avail.width() - st::windowDefaultWidth) / 2, + const auto initialWidth = Main::Settings::ThirdColumnByDefault() + ? st::windowBigDefaultWidth + : st::windowDefaultWidth; + const auto initialHeight = Main::Settings::ThirdColumnByDefault() + ? st::windowBigDefaultHeight + : st::windowDefaultHeight; + auto geometry = QRect( + available.x() + std::max( + (available.width() - initialWidth) / 2, 0), - avail.y() + std::max( - (avail.height() - st::windowDefaultHeight) / 2, + available.y() + std::max( + (available.height() - initialHeight) / 2, 0), - st::windowDefaultWidth, - st::windowDefaultHeight); + initialWidth, + initialHeight); if (position.w && position.h) { for (auto screen : QGuiApplication::screens()) { if (position.moncrc == screenNameChecksum(screen->name())) { @@ -420,7 +430,8 @@ void MainWindow::initSize() { if (position.x + st::windowMinWidth <= screenGeometry.x() + screenGeometry.width() && position.y + st::windowMinHeight <= screenGeometry.y() + screenGeometry.height()) { DEBUG_LOG(("Window Pos: Resulting geometry is %1, %2, %3, %4").arg(position.x).arg(position.y).arg(position.w).arg(position.h)); - geom = QRect(position.x, position.y, position.w, position.h); + geometry = QRect(position.x, position.y, position.w, position.h); + geometryScreen = screen; } } break; @@ -428,8 +439,8 @@ void MainWindow::initSize() { } maximized = position.maximized; } - DEBUG_LOG(("Window Pos: Setting first %1, %2, %3, %4").arg(geom.x()).arg(geom.y()).arg(geom.width()).arg(geom.height())); - setGeometry(geom); + DEBUG_LOG(("Window Pos: Setting first %1, %2, %3, %4").arg(geometry.x()).arg(geometry.y()).arg(geometry.width()).arg(geometry.height())); + setGeometry(geometry); } void MainWindow::positionUpdated() { @@ -520,6 +531,7 @@ void MainWindow::savePosition(Qt::WindowState state) { if (state == Qt::WindowMaximized) { realPosition.maximized = 1; + DEBUG_LOG(("Window Pos: Saving maximized position.")); } else { auto r = geometry(); realPosition.x = r.x(); @@ -528,29 +540,29 @@ void MainWindow::savePosition(Qt::WindowState state) { realPosition.h = r.height(); realPosition.maximized = 0; realPosition.moncrc = 0; - } - DEBUG_LOG(("Window Pos: Saving position: %1, %2, %3, %4 (maximized %5)").arg(realPosition.x).arg(realPosition.y).arg(realPosition.w).arg(realPosition.h).arg(Logs::b(realPosition.maximized))); - auto centerX = realPosition.x + realPosition.w / 2; - auto centerY = realPosition.y + realPosition.h / 2; - int minDelta = 0; - QScreen *chosen = nullptr; - auto screens = QGuiApplication::screens(); - for (auto screen : QGuiApplication::screens()) { - auto delta = (screen->geometry().center() - QPoint(centerX, centerY)).manhattanLength(); - if (!chosen || delta < minDelta) { - minDelta = delta; - chosen = screen; + DEBUG_LOG(("Window Pos: Saving non-maximized position: %1, %2, %3, %4").arg(realPosition.x).arg(realPosition.y).arg(realPosition.w).arg(realPosition.h)); + + auto centerX = realPosition.x + realPosition.w / 2; + auto centerY = realPosition.y + realPosition.h / 2; + int minDelta = 0; + QScreen *chosen = nullptr; + auto screens = QGuiApplication::screens(); + for (auto screen : QGuiApplication::screens()) { + auto delta = (screen->geometry().center() - QPoint(centerX, centerY)).manhattanLength(); + if (!chosen || delta < minDelta) { + minDelta = delta; + chosen = screen; + } + } + if (chosen) { + auto screenGeometry = chosen->geometry(); + DEBUG_LOG(("Window Pos: Screen found, geometry: %1, %2, %3, %4").arg(screenGeometry.x()).arg(screenGeometry.y()).arg(screenGeometry.width()).arg(screenGeometry.height())); + realPosition.x -= screenGeometry.x(); + realPosition.y -= screenGeometry.y(); + realPosition.moncrc = screenNameChecksum(chosen->name()); } } - if (chosen) { - auto screenGeometry = chosen->geometry(); - DEBUG_LOG(("Window Pos: Screen found, geometry: %1, %2, %3, %4").arg(screenGeometry.x()).arg(screenGeometry.y()).arg(screenGeometry.width()).arg(screenGeometry.height())); - realPosition.x -= screenGeometry.x(); - realPosition.y -= screenGeometry.y(); - realPosition.moncrc = screenNameChecksum(chosen->name()); - } - if (realPosition.w >= st::windowMinWidth && realPosition.h >= st::windowMinHeight) { if (realPosition.x != savedPosition.x || realPosition.y != savedPosition.y diff --git a/Telegram/SourceFiles/window/main_window.h b/Telegram/SourceFiles/window/main_window.h index a8eedea57..3282483f0 100644 --- a/Telegram/SourceFiles/window/main_window.h +++ b/Telegram/SourceFiles/window/main_window.h @@ -137,6 +137,8 @@ protected: virtual void updateGlobalMenuHook() { } + virtual void initTrayMenuHook() { + } virtual bool hasTrayIcon() const { return false; } @@ -148,6 +150,13 @@ protected: virtual void updateControlsGeometry(); + virtual void createGlobalMenu() { + } + virtual void initShadows() { + } + virtual void firstShadowsUpdate() { + } + // This one is overriden in Windows for historical reasons. virtual int32 screenNameChecksum(const QString &name) const; diff --git a/Telegram/SourceFiles/window/window.style b/Telegram/SourceFiles/window/window.style index a880acb20..4b3f0a033 100644 --- a/Telegram/SourceFiles/window/window.style +++ b/Telegram/SourceFiles/window/window.style @@ -14,6 +14,8 @@ windowMinWidth: 380px; windowMinHeight: 480px; windowDefaultWidth: 800px; windowDefaultHeight: 600px; +windowBigDefaultWidth: 1024px; +windowBigDefaultHeight: 768px; columnMinimalWidthLeft: 260px; columnMaximalWidthLeft: 540px; diff --git a/Telegram/SourceFiles/window/window_controller.cpp b/Telegram/SourceFiles/window/window_controller.cpp index a6e4c8da9..69afd5e88 100644 --- a/Telegram/SourceFiles/window/window_controller.cpp +++ b/Telegram/SourceFiles/window/window_controller.cpp @@ -42,8 +42,8 @@ Controller::~Controller() { _widget.clearWidgets(); } -void Controller::firstShow() { - _widget.firstShow(); +void Controller::finishFirstShow() { + _widget.finishFirstShow(); checkThemeEditor(); } diff --git a/Telegram/SourceFiles/window/window_controller.h b/Telegram/SourceFiles/window/window_controller.h index d8adfc2e6..7ce23ca04 100644 --- a/Telegram/SourceFiles/window/window_controller.h +++ b/Telegram/SourceFiles/window/window_controller.h @@ -34,7 +34,7 @@ public: return _sessionController.get(); } - void firstShow(); + void finishFirstShow(); void setupPasscodeLock(); void clearPasscodeLock(); diff --git a/Telegram/SourceFiles/window/window_lock_widgets.cpp b/Telegram/SourceFiles/window/window_lock_widgets.cpp index 8f3af9398..6489273b8 100644 --- a/Telegram/SourceFiles/window/window_lock_widgets.cpp +++ b/Telegram/SourceFiles/window/window_lock_widgets.cpp @@ -69,15 +69,17 @@ void LockWidget::showAnimated(const QPixmap &bgAnimCache, bool back) { void LockWidget::animationCallback() { update(); if (!_a_show.animating()) { - showChildren(); - _window->widget()->setInnerFocus(); - - Ui::showChatsList(); - - _cacheUnder = _cacheOver = QPixmap(); + showFinished(); } } +void LockWidget::showFinished() { + showChildren(); + _window->widget()->setInnerFocus(); + Ui::showChatsList(); + _cacheUnder = _cacheOver = QPixmap(); +} + void LockWidget::paintEvent(QPaintEvent *e) { Painter p(this); diff --git a/Telegram/SourceFiles/window/window_lock_widgets.h b/Telegram/SourceFiles/window/window_lock_widgets.h index 9e6d83400..848fbafd8 100644 --- a/Telegram/SourceFiles/window/window_lock_widgets.h +++ b/Telegram/SourceFiles/window/window_lock_widgets.h @@ -32,6 +32,7 @@ public: virtual void setInnerFocus(); void showAnimated(const QPixmap &bgAnimCache, bool back = false); + void showFinished(); protected: void paintEvent(QPaintEvent *e) override; diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index e6c61434d..60a73d81e 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -45,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_drafts.h" #include "data/data_user.h" #include "data/data_scheduled_messages.h" +#include "data/data_histories.h" #include "dialogs/dialogs_key.h" #include "boxes/peers/edit_peer_info_box.h" #include "facades.h" @@ -304,9 +305,9 @@ void Filler::addToggleUnreadMark() { const auto markAsRead = isUnread(peer); const auto handle = [&](not_null history) { if (markAsRead) { - peer->session().api().readServerHistory(history); + peer->owner().histories().readInbox(history); } else { - peer->session().api().changeDialogUnreadMark( + peer->owner().histories().changeDialogUnreadMark( history, !markAsRead); } diff --git a/Telegram/Telegram/Telegram Desktop.entitlements b/Telegram/Telegram/Telegram Lite.entitlements similarity index 100% rename from Telegram/Telegram/Telegram Desktop.entitlements rename to Telegram/Telegram/Telegram Lite.entitlements diff --git a/Telegram/ThirdParty/hunspell b/Telegram/ThirdParty/hunspell new file mode 160000 index 000000000..8c773334f --- /dev/null +++ b/Telegram/ThirdParty/hunspell @@ -0,0 +1 @@ +Subproject commit 8c773334f314201b79bd8a6d40369ada9d7056d4 diff --git a/Telegram/ThirdParty/libtgvoip b/Telegram/ThirdParty/libtgvoip index ade4434f1..522550a1e 160000 --- a/Telegram/ThirdParty/libtgvoip +++ b/Telegram/ThirdParty/libtgvoip @@ -1 +1 @@ -Subproject commit ade4434f1c6efabecc3b548ca1f692f8d103d22a +Subproject commit 522550a1e975b17e9048d7a2ab2d5b97cfc2f5d4 diff --git a/Telegram/ThirdParty/statusnotifieritem/statusnotifieritem.cpp b/Telegram/ThirdParty/statusnotifieritem/statusnotifieritem.cpp index 96a3a537b..a55079740 100644 --- a/Telegram/ThirdParty/statusnotifieritem/statusnotifieritem.cpp +++ b/Telegram/ThirdParty/statusnotifieritem/statusnotifieritem.cpp @@ -42,6 +42,7 @@ StatusNotifierItem::StatusNotifierItem(QString id, QObject *parent) mId(id), mTitle(QLatin1String("Test")), mStatus(QLatin1String("Active")), + mCategory(QLatin1String("ApplicationStatus")), mMenu(nullptr), mMenuPath(QLatin1String("/NO_DBUSMENU")), mMenuExporter(nullptr), @@ -116,6 +117,14 @@ void StatusNotifierItem::setStatus(const QString &status) Q_EMIT mAdaptor->NewStatus(mStatus); } +void StatusNotifierItem::setCategory(const QString &category) +{ + if (mCategory == category) + return; + + mCategory = category; +} + void StatusNotifierItem::setMenuPath(const QString& path) { mMenuPath.setPath(path); diff --git a/Telegram/ThirdParty/statusnotifieritem/statusnotifieritem.h b/Telegram/ThirdParty/statusnotifieritem/statusnotifieritem.h index 3a689d11d..60739ea53 100644 --- a/Telegram/ThirdParty/statusnotifieritem/statusnotifieritem.h +++ b/Telegram/ThirdParty/statusnotifieritem/statusnotifieritem.h @@ -43,6 +43,7 @@ class StatusNotifierItem : public QObject { Q_OBJECT + Q_PROPERTY(QString Category READ category) Q_PROPERTY(QString Title READ title) Q_PROPERTY(QString Id READ id) Q_PROPERTY(QString Status READ status) @@ -74,6 +75,10 @@ public: { return mStatus; } void setStatus(const QString &status); + QString category() const + { return mCategory; } + void setCategory(const QString &category); + QDBusObjectPath menu() const { return mMenuPath; } void setMenuPath(const QString &path); @@ -162,6 +167,7 @@ private: QString mId; QString mTitle; QString mStatus; + QString mCategory; // icons QString mIconName, mOverlayIconName, mAttentionIconName; diff --git a/Telegram/build/build.sh b/Telegram/build/build.sh index da5439714..3de5e19cf 100755 --- a/Telegram/build/build.sh +++ b/Telegram/build/build.sh @@ -87,7 +87,7 @@ elif [ "$BuildTarget" == "macstore" ]; then echo "Building version $AppVersionStrFull for Mac App Store.." ProjectPath="$HomePath/../out" ReleasePath="$ProjectPath/Release" - BinaryName="Telegram Desktop" + BinaryName="Telegram Lite" else Error "Invalid target!" fi @@ -289,7 +289,7 @@ if [ "$BuildTarget" == "mac" ] || [ "$BuildTarget" == "osx" ] || [ "$BuildTarget fi echo "Dumping debug symbols.." - "$HomePath/../../Libraries/breakpad/src/tools/mac/dump_syms/build/Release/dump_syms" "$ReleasePath/$BinaryName.app.dSYM" > "$ReleasePath/$BinaryName.sym" 2>/dev/null + "$HomePath/../../Libraries/macos/breakpad/src/tools/mac/dump_syms/build/Release/dump_syms" "$ReleasePath/$BinaryName.app.dSYM" > "$ReleasePath/$BinaryName.sym" 2>/dev/null echo "Done!" echo "Stripping the executable.." @@ -300,7 +300,7 @@ if [ "$BuildTarget" == "mac" ] || [ "$BuildTarget" == "osx" ] || [ "$BuildTarget if [ "$BuildTarget" == "mac" ] || [ "$BuildTarget" == "osx" ]; then codesign --force --deep --timestamp --options runtime --sign "Developer ID Application: John Preston" "$ReleasePath/$BinaryName.app" --entitlements "$HomePath/Telegram/Telegram.entitlements" elif [ "$BuildTarget" == "macstore" ]; then - codesign --force --deep --sign "3rd Party Mac Developer Application: TELEGRAM MESSENGER LLP (6N38VWS5BX)" "$ReleasePath/$BinaryName.app" --entitlements "$HomePath/Telegram/Telegram Desktop.entitlements" + codesign --force --deep --sign "3rd Party Mac Developer Application: TELEGRAM MESSENGER LLP (6N38VWS5BX)" "$ReleasePath/$BinaryName.app" --entitlements "$HomePath/Telegram/Telegram Lite.entitlements" echo "Making an installer.." productbuild --sign "3rd Party Mac Developer Installer: TELEGRAM MESSENGER LLP (6N38VWS5BX)" --component "$ReleasePath/$BinaryName.app" /Applications "$ReleasePath/$BinaryName.pkg" fi @@ -378,7 +378,7 @@ if [ "$BuildTarget" == "mac" ] || [ "$BuildTarget" == "osx" ] || [ "$BuildTarget if [ "$BuildTarget" == "mac" ]; then echo "Beginning notarization process." set +e - xcrun altool --notarize-app --primary-bundle-id "com.tdesktop.Telegram" --username "$AC_USERNAME" --password "@keychain:AC_PASSWORD" --file "$SetupFile" 2> request_uuid.txt + xcrun altool --notarize-app --primary-bundle-id "com.tdesktop.Telegram" --username "$AC_USERNAME" --password "@keychain:AC_PASSWORD" --file "$SetupFile" > request_uuid.txt set -e while IFS='' read -r line || [[ -n "$line" ]]; do Prefix=$(echo $line | cut -d' ' -f 1) @@ -398,7 +398,7 @@ if [ "$BuildTarget" == "mac" ] || [ "$BuildTarget" == "osx" ] || [ "$BuildTarget LogFile= while [[ "$RequestStatus" == "" ]]; do sleep 5 - xcrun altool --notarization-info "$RequestUUID" --username "$AC_USERNAME" --password "@keychain:AC_PASSWORD" 2> request_result.txt + xcrun altool --notarization-info "$RequestUUID" --username "$AC_USERNAME" --password "@keychain:AC_PASSWORD" > request_result.txt while IFS='' read -r line || [[ -n "$line" ]]; do Prefix=$(echo $line | cut -d' ' -f 1) Value=$(echo $line | cut -d' ' -f 2) diff --git a/Telegram/build/version b/Telegram/build/version index bd2a78228..37dc9a595 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 1009010 +AppVersion 1009019 AppVersionStrMajor 1.9 -AppVersionStrSmall 1.9.10 -AppVersionStr 1.9.10 +AppVersionStrSmall 1.9.19 +AppVersionStr 1.9.19 BetaChannel 1 AlphaVersion 0 -AppVersionOriginal 1.9.10.beta +AppVersionOriginal 1.9.19.beta diff --git a/Telegram/cmake/telegram_options.cmake b/Telegram/cmake/telegram_options.cmake index 82a9deb72..4c18955f4 100644 --- a/Telegram/cmake/telegram_options.cmake +++ b/Telegram/cmake/telegram_options.cmake @@ -9,7 +9,6 @@ option(TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME "Disable automatic 'tg://' URL sc option(TDESKTOP_DISABLE_NETWORK_PROXY "Disable all code for working through Socks5 or MTProxy." OFF) option(TDESKTOP_DISABLE_DESKTOP_FILE_GENERATION "Disable automatic '.desktop' file generation (Linux only)." ${DESKTOP_APP_USE_PACKAGED}) option(TDESKTOP_DISABLE_GTK_INTEGRATION "Disable all code for GTK integration (Linux only)." ON) -option(TDESKTOP_DISABLE_DBUS_INTEGRATION "Disable all code for D-Bus integration (Linux only)." OFF) option(TDESKTOP_USE_PACKAGED_TGVOIP "Find libtgvoip using CMake instead of bundled one." ${DESKTOP_APP_USE_PACKAGED}) option(TDESKTOP_API_TEST "Use test API credentials." OFF) set(TDESKTOP_API_ID "0" CACHE STRING "Provide 'api_id' for the Telegram API access.") @@ -86,7 +85,7 @@ if (TDESKTOP_DISABLE_GTK_INTEGRATION) target_compile_definitions(Telegram PRIVATE TDESKTOP_DISABLE_GTK_INTEGRATION) endif() -if (TDESKTOP_DISABLE_DBUS_INTEGRATION) +if (DESKTOP_APP_DISABLE_DBUS_INTEGRATION) target_compile_definitions(Telegram PRIVATE TDESKTOP_DISABLE_DBUS_INTEGRATION) endif() diff --git a/Telegram/lib_lottie b/Telegram/lib_lottie index 2d75b1a35..17b6a6d53 160000 --- a/Telegram/lib_lottie +++ b/Telegram/lib_lottie @@ -1 +1 @@ -Subproject commit 2d75b1a35a984f2a0379acbd0869b50b66acdf3c +Subproject commit 17b6a6d53252b3e3ff02b113e352c152bd697896 diff --git a/Telegram/lib_spellcheck b/Telegram/lib_spellcheck index 691cda5b2..dbb92ddbe 160000 --- a/Telegram/lib_spellcheck +++ b/Telegram/lib_spellcheck @@ -1 +1 @@ -Subproject commit 691cda5b223133396a53246c14f613ff04542628 +Subproject commit dbb92ddbef82988d426ef7a0ffa40a698cdc3fd3 diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 2216daa58..eacf58854 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 2216daa5874a7a53eb48839486cdc73a4fdaf0e1 +Subproject commit eacf588546baba7599316147c63d1cf0bc038fb6 diff --git a/changelog.txt b/changelog.txt index 31099cc48..990ae6a71 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,43 @@ +1.9.19 beta (25.02.20) + +- Bug fixes and other minor improvements. + +1.9.18 beta (25.02.20) + +- Bug fixes and other minor improvements. + +1.9.17 beta (24.02.20) + +- Spell checker on Windows 7. +- Bug fixes and other minor improvements. + +1.9.16 beta (23.02.20) + +- Bug fixes and other minor improvements. + +1.9.15 beta (21.02.20) + +- Mark new messages as read while scrolling down through them. +- Bug fixes and other minor improvements. + +1.9.14 (17.02.20) + +- Bug fixes and other minor improvements. + +1.9.13 (12.02.20) + +- Bug fixes and other minor improvements. + +1.9.12 (11.02.20) + +- Switch to Picture-in-Picture mode to watch your video in a small window while doing something else. +- Change playback speed in the '...' menu when watching videos. +- Rotate photos and videos in the media viewer using the rotate button in the bottom right corner. + +1.9.11 beta (10.02.20) + +- Bug fixes and other minor improvements. + 1.9.10 beta (05.02.20) - Switch to the Picture-in-Picture mode to watch your video in a small window. diff --git a/cmake b/cmake index 4efeb76e0..81e27ccc0 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 4efeb76e0c1a8b9e1dfc6b830e57a9a2426b0f0d +Subproject commit 81e27ccc0e7bf27405569ce98582860dfc9ea9bb diff --git a/docs/building-cmake.md b/docs/building-cmake.md index f1b4b8763..af59c2d30 100644 --- a/docs/building-cmake.md +++ b/docs/building-cmake.md @@ -15,13 +15,13 @@ You will need GCC 8 installed. To install them and all the required dependencies sudo apt-get install software-properties-common -y && \ sudo apt-get install git libexif-dev liblzma-dev libz-dev libssl-dev \ libgtk2.0-dev libice-dev libsm-dev libicu-dev libdrm-dev dh-autoreconf \ - autoconf automake build-essential libass-dev libfreetype6-dev \ + autoconf automake build-essential libxml2-dev libass-dev libfreetype6-dev \ libgpac-dev libsdl1.2-dev libtheora-dev libtool libva-dev libvdpau-dev \ libvorbis-dev libenchant-dev libxcb1-dev libxcb-image0-dev libxcb-shm0-dev \ libxcb-xfixes0-dev libxcb-keysyms1-dev libxcb-icccm4-dev libatspi2.0-dev \ libxcb-render-util0-dev libxcb-util0-dev libxcb-xkb-dev libxrender-dev \ - libasound-dev libpulse-dev libxcb-sync0-dev libxcb-randr0-dev bison \ - libx11-xcb-dev libffi-dev libncurses5-dev pkg-config texi2html yasm \ + libasound-dev libpulse-dev libxcb-sync0-dev libxcb-randr0-dev libegl1-mesa-dev \ + libx11-xcb-dev libffi-dev libncurses5-dev pkg-config texi2html bison yasm \ zlib1g-dev xutils-dev python-xcbgen chrpath gperf -y --force-yes && \ sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y && \ sudo apt-get update && \ @@ -58,13 +58,6 @@ Go to ***BuildPath*** and run cd ../ git clone --branch 0.10.0 https://github.com/ericniebler/range-v3 - git clone https://github.com/madler/zlib.git - cd zlib - ./configure - make $MAKE_THREADS_CNT - sudo make install - cd .. - git clone https://github.com/xiph/opus cd opus git checkout v1.3 @@ -81,9 +74,8 @@ Go to ***BuildPath*** and run sudo make install cd .. - git clone git://anongit.freedesktop.org/vdpau/libvdpau + git clone https://gitlab.freedesktop.org/vdpau/libvdpau.git --depth=1 -b libvdpau-1.2 cd libvdpau - git checkout libvdpau-1.2 ./autogen.sh --enable-static make $MAKE_THREADS_CNT sudo make install @@ -233,12 +225,21 @@ Go to ***BuildPath*** and run sudo make install cd .. + git clone -b 1.16 https://gitlab.freedesktop.org/wayland/wayland + cd wayland + ./autogen.sh --enable-static --disable-documentation + make -j$(nproc) + sudo make install + cd .. + git clone git://code.qt.io/qt/qt5.git qt_5_12_5 cd qt_5_12_5 - perl init-repository --module-subset=qtbase,qtimageformats + perl init-repository --module-subset=qtbase,qtwayland,qtimageformats,qtsvg git checkout v5.12.5 git submodule update qtbase + git submodule update qtwayland git submodule update qtimageformats + git submodule update qtsvg cd qtbase git apply ../../patches/qtbase_5_12_5.diff cd src/plugins/platforminputcontexts @@ -253,18 +254,17 @@ Go to ***BuildPath*** and run -force-debug-info \ -opensource \ -confirm-license \ + -qt-zlib \ -qt-libpng \ -qt-libjpeg \ -qt-harfbuzz \ -qt-pcre \ -qt-xcb \ - -system-zlib \ -system-freetype \ -fontconfig \ - -no-opengl \ - -no-glib \ -no-gtk \ -static \ + -dbus-runtime \ -openssl-linked \ -I "$OPENSSL_DIR/include" OPENSSL_LIBS="$OPENSSL_DIR/lib/libssl.a $OPENSSL_DIR/lib/libcrypto.a -ldl -lpthread" \ -nomake examples \ diff --git a/docs/building-osx.md b/docs/building-osx.md index 23f11b64b..7bfd9a9ef 100644 --- a/docs/building-osx.md +++ b/docs/building-osx.md @@ -207,7 +207,7 @@ Go to ***BuildPath*** and run cd openal-soft git checkout v1.19 cd build - CFLAGS='-Werror=unguarded-availability-new' CPPFLAGS='-Werror=unguarded-availability-new' cmake -D -D ALSOFT_EXAMPLES=OFF -D LIBTYPE:STRING=STATIC -D CMAKE_OSX_DEPLOYMENT_TARGET:STRING=10.10 .. + CFLAGS='-Werror=unguarded-availability-new' CPPFLAGS='-Werror=unguarded-availability-new' cmake -D ALSOFT_EXAMPLES=OFF -D LIBTYPE:STRING=STATIC -D CMAKE_OSX_DEPLOYMENT_TARGET:STRING=10.10 .. make $MAKE_THREADS_CNT sudo make install cd ../.. diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 65d5956f6..2629e4f36 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -14,6 +14,8 @@ apps: environment: # Use GTK3 cursor theme, icon theme and open/save file dialogs. QT_QPA_PLATFORMTHEME: gtk3 + # Wayland support is still too bad. + DISABLE_WAYLAND: 1 plugs: - desktop - desktop-legacy @@ -60,6 +62,7 @@ parts: - libswscale-dev - libswresample-dev - libdbusmenu-qt5-dev + - libhunspell-dev - liblz4-dev - liblzma-dev - libminizip-dev @@ -77,6 +80,7 @@ parts: - libswscale4 - libswresample2 - libdbusmenu-qt5-2 + - libhunspell-1.6-0 - liblz4-1 - liblzma5 - libminizip1 @@ -92,6 +96,7 @@ parts: - -DTDESKTOP_API_ID=611335 - -DTDESKTOP_API_HASH=d524b414d21f4d37f08684c1df41ac9c - -DDESKTOP_APP_USE_PACKAGED_FONTS=OFF + - -DDESKTOP_APP_USE_PACKAGED_GSL=OFF - -DDESKTOP_APP_USE_PACKAGED_EXPECTED=OFF - -DDESKTOP_APP_USE_PACKAGED_RLOTTIE=OFF - -DTDESKTOP_USE_PACKAGED_TGVOIP=OFF @@ -114,27 +119,9 @@ parts: after: - cmake - desktop-qt5 - - enchant - - gsl - range-v3 - xxhash - spellchecking: - plugin: nil - stage-packages: - - hunspell-de-de - - hunspell-en-au - - hunspell-en-ca - - hunspell-en-gb - - hunspell-en-us - - hunspell-en-za - - hunspell-fr-classical - - hunspell-it - - hunspell-pl - - hunspell-es - - hunspell-pt-br - - hunspell-pt-pt - desktop-qt5: source: https://github.com/ubuntu/snapcraft-desktop-helpers.git source-subdir: qt @@ -168,7 +155,7 @@ parts: cmake: source: "https://gitlab.kitware.com/cmake/cmake.git" source-depth: 1 - source-branch: master + source-tag: v3.16.4 source-type: git plugin: make override-build: | @@ -189,31 +176,6 @@ parts: - libtinfo5 prime: [-./*] - enchant: - source: https://github.com/AbiWord/enchant.git - source-depth: 1 - source-tag: v2.2.7 - plugin: autotools - build-packages: - - libltdl-dev - - libglib2.0-dev - - libhunspell-dev - stage-packages: - - libglib2.0-0 - - libhunspell-1.6-0 - configflags: - - --enable-relocatable - prime: [-./bin/*] - - gsl: - source: https://github.com/microsoft/GSL.git - source-depth: 1 - source-tag: v2.1.0 - plugin: cmake - configflags: - - -DGSL_TEST=OFF - prime: [-./*] - range-v3: source: https://github.com/ericniebler/range-v3.git source-depth: 1