Merge remote-tracking branch 'tdesktop/dev' into dev
This commit is contained in:
commit
6aa631cba9
132 changed files with 4587 additions and 2228 deletions
102
.github/workflows/linux.yml
vendored
102
.github/workflows/linux.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
6
.github/workflows/mac.yml
vendored
6
.github/workflows/mac.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
19
.github/workflows/snap.yml
vendored
19
.github/workflows/snap.yml
vendored
|
|
@ -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}
|
||||
|
|
|
|||
6
.github/workflows/win.yml
vendored
6
.github/workflows/win.yml
vendored
|
|
@ -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.
|
||||
|
|
|
|||
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="1.9.10.0" />
|
||||
Version="1.9.19.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram FZ-LLC</PublisherDisplayName>
|
||||
|
|
|
|||
|
|
@ -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<Fn<void(const RPCError&, QByteArray)>>();
|
||||
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<void()> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<bool> ApiWrap::dialogsLoadBlockedByDate() const {
|
|||
return _dialogsLoadBlockedByDate.value();
|
||||
}
|
||||
|
||||
void ApiWrap::requestDialogEntry(not_null<Data::Folder*> folder) {
|
||||
if (_dialogFolderRequests.contains(folder)) {
|
||||
return;
|
||||
}
|
||||
_dialogFolderRequests.emplace(folder);
|
||||
|
||||
auto peers = QVector<MTPInputDialogPeer>(
|
||||
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*> history,
|
||||
Fn<void()> 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<not_null<History*>>();
|
||||
ranges::transform(
|
||||
_dialogRequestsPending,
|
||||
ranges::back_inserter(histories),
|
||||
[](const auto &pair) { return pair.first; });
|
||||
auto peers = QVector<MTPInputDialogPeer>();
|
||||
const auto dialogPeer = [](not_null<History*> 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) {
|
||||
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*> 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*> 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<MTPMessage>(0),
|
||||
MTP_vector<MTPChat>(0),
|
||||
MTP_vector<MTPUser>(0)));
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ApiWrap::requestWallPaper(
|
||||
const QString &slug,
|
||||
Fn<void(const Data::WallPaper &)> done,
|
||||
|
|
@ -1786,7 +1631,7 @@ void ApiWrap::requestSelfParticipant(not_null<ChannelData*> 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<PeerData*> 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<PeerData*> peer, bool revoke) {
|
||||
|
|
@ -2473,30 +2281,69 @@ void ApiWrap::deleteConversation(not_null<PeerData*> 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<PeerData*> 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<PeerData*> 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<PeerData*> peer,
|
||||
const QVector<MTPint> &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<MTPint>(ids)
|
||||
)).done(done).send();
|
||||
} else {
|
||||
using Flag = MTPmessages_DeleteMessages::Flag;
|
||||
request(MTPmessages_DeleteMessages(
|
||||
MTP_flags(revoke ? Flag::f_revoke : Flag(0)),
|
||||
MTP_vector<MTPint>(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<void()> 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<void()> &&successCallback) {
|
||||
Expects(!items.empty());
|
||||
|
||||
auto &histories = session().data().histories();
|
||||
|
||||
struct SharedCallback {
|
||||
int requestsLeft = 0;
|
||||
FnMut<void()> 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<MTPint>();
|
||||
auto randomIds = QVector<MTPlong>();
|
||||
auto localIds = std::unique_ptr<base::flat_map<uint64, FullMsgId>>();
|
||||
auto localIds = std::shared_ptr<base::flat_map<uint64, FullMsgId>>();
|
||||
|
||||
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<MTPint>(ids),
|
||||
MTP_vector<MTPlong>(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<void()> finish) {
|
||||
history->sendRequestId = request(MTPmessages_ForwardMessages(
|
||||
MTP_flags(finalFlags),
|
||||
forwardFrom->input,
|
||||
MTP_vector<MTPint>(ids),
|
||||
MTP_vector<MTPlong>(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<base::flat_map<uint64, FullMsgId>>();
|
||||
localIds = std::make_shared<base::flat_map<uint64, FullMsgId>>();
|
||||
}
|
||||
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<MTPRestrictionReason>()),
|
||||
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<void()> 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<void()> 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<void()> 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<SendingAlbum*> 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<MTPInputSingleMedia>(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<void()> finish) {
|
||||
const auto peer = history->peer;
|
||||
history->sendRequestId = request(MTPmessages_SendMultiMedia(
|
||||
MTP_flags(flags),
|
||||
peer->input,
|
||||
MTP_int(replyTo),
|
||||
MTP_vector<MTPInputSingleMedia>(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<void()> done,
|
||||
FnMut<void(const RPCError &error)> fail) {
|
||||
Fn<void()> done,
|
||||
Fn<void(const RPCError &error)> 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<MTPbytes>(correct)),
|
||||
MTP_string(),
|
||||
MTP_long(rand_value<uint64>()),
|
||||
MTPReplyMarkup(),
|
||||
MTPVector<MTPMessageEntity>(),
|
||||
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<void()> 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<MTPbytes>(correct)),
|
||||
MTP_string(),
|
||||
MTP_long(rand_value<uint64>()),
|
||||
MTPReplyMarkup(),
|
||||
MTPVector<MTPMessageEntity>(),
|
||||
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<HistoryItem*> item) {
|
|||
_pollReloadRequestIds.emplace(itemId, requestId);
|
||||
}
|
||||
|
||||
void ApiWrap::readServerHistory(not_null<History*> history) {
|
||||
if (history->unreadCount()) {
|
||||
readServerHistoryForce(history);
|
||||
}
|
||||
if (history->unreadMark()) {
|
||||
changeDialogUnreadMark(history, false);
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::readServerHistoryForce(not_null<History*> 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<Data::Feed*> feed,
|
||||
|
|
@ -6090,39 +5925,3 @@ void ApiWrap::readServerHistoryForce(not_null<History*> history) {
|
|||
// _feedReadTimer.callOnce(delay);
|
||||
// }
|
||||
//}
|
||||
|
||||
void ApiWrap::sendReadRequest(not_null<PeerData*> 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,6 +141,9 @@ public:
|
|||
void applyUpdates(
|
||||
const MTPUpdates &updates,
|
||||
uint64 sentMessageRandomId = 0);
|
||||
int applyAffectedHistory(
|
||||
not_null<PeerData*> peer,
|
||||
const MTPmessages_AffectedHistory &result);
|
||||
|
||||
void registerModifyRequest(const QString &key, mtpRequestId requestId);
|
||||
void clearModifyRequest(const QString &key);
|
||||
|
|
@ -170,18 +173,10 @@ public:
|
|||
rpl::producer<bool> dialogsLoadMayBlockByDate() const;
|
||||
rpl::producer<bool> dialogsLoadBlockedByDate() const;
|
||||
|
||||
void requestDialogEntry(not_null<Data::Folder*> folder);
|
||||
void requestDialogEntry(
|
||||
not_null<History*> history,
|
||||
Fn<void()> callback = nullptr);
|
||||
void dialogEntryApplied(not_null<History*> history);
|
||||
//void applyFeedSources(const MTPDchannels_feedSources &data); // #feed
|
||||
//void setFeedChannels(
|
||||
// not_null<Data::Feed*> feed,
|
||||
// const std::vector<not_null<ChannelData*>> &channels);
|
||||
void changeDialogUnreadMark(not_null<History*> history, bool unread);
|
||||
//void changeDialogUnreadMark(not_null<Data::Feed*> feed, bool unread); // #feed
|
||||
void requestFakeChatListMessage(not_null<History*> history);
|
||||
|
||||
void requestWallPaper(
|
||||
const QString &slug,
|
||||
|
|
@ -305,10 +300,6 @@ public:
|
|||
|
||||
void clearHistory(not_null<PeerData*> peer, bool revoke);
|
||||
void deleteConversation(not_null<PeerData*> peer, bool revoke);
|
||||
void deleteMessages(
|
||||
not_null<PeerData*> peer,
|
||||
const QVector<MTPint> &ids,
|
||||
bool revoke);
|
||||
|
||||
base::Observable<PeerData*> &fullPeerUpdated() {
|
||||
return _fullPeerUpdated;
|
||||
|
|
@ -389,11 +380,12 @@ public:
|
|||
const QString &lastName,
|
||||
const SendAction &action);
|
||||
void shareContact(not_null<UserData*> user, const SendAction &action);
|
||||
void readServerHistory(not_null<History*> history);
|
||||
void readServerHistoryForce(not_null<History*> history);
|
||||
//void readFeed( // #feed
|
||||
// not_null<Data::Feed*> feed,
|
||||
// Data::MessagePosition position);
|
||||
void applyAffectedMessages(
|
||||
not_null<PeerData*> peer,
|
||||
const MTPmessages_AffectedMessages &result);
|
||||
|
||||
void sendVoiceMessage(
|
||||
QByteArray result,
|
||||
|
|
@ -475,8 +467,8 @@ public:
|
|||
void createPoll(
|
||||
const PollData &data,
|
||||
const SendAction &action,
|
||||
FnMut<void()> done,
|
||||
FnMut<void(const RPCError &error)> fail);
|
||||
Fn<void()> done,
|
||||
Fn<void(const RPCError &error)> fail);
|
||||
void sendPollVotes(
|
||||
FullMsgId itemId,
|
||||
const std::vector<QByteArray> &options);
|
||||
|
|
@ -531,7 +523,6 @@ private:
|
|||
|
||||
QVector<MTPInputMessage> collectMessageIds(const MessageDataRequests &requests);
|
||||
MessageDataRequests *messageDataRequests(ChannelData *channel, bool onlyExisting = false);
|
||||
void applyPeerDialogs(const MTPmessages_PeerDialogs &dialogs);
|
||||
|
||||
void gotChatFull(
|
||||
not_null<PeerData*> peer,
|
||||
|
|
@ -623,14 +614,7 @@ private:
|
|||
not_null<PeerData*> peer,
|
||||
bool justClear,
|
||||
bool revoke);
|
||||
void sendReadRequest(not_null<PeerData*> peer, MsgId upTo);
|
||||
int applyAffectedHistory(
|
||||
not_null<PeerData*> peer,
|
||||
const MTPmessages_AffectedHistory &result);
|
||||
void applyAffectedMessages(const MTPmessages_AffectedMessages &result);
|
||||
void applyAffectedMessages(
|
||||
not_null<PeerData*> peer,
|
||||
const MTPmessages_AffectedMessages &result);
|
||||
|
||||
void deleteAllFromUserSend(
|
||||
not_null<ChannelData*> channel,
|
||||
|
|
@ -685,8 +669,6 @@ private:
|
|||
not_null<ChannelData*> channel);
|
||||
void migrateFail(not_null<PeerData*> peer, const RPCError &error);
|
||||
|
||||
void sendDialogRequests();
|
||||
|
||||
not_null<Main::Session*> _session;
|
||||
|
||||
base::flat_map<QString, int> _modifyRequests;
|
||||
|
|
@ -756,22 +738,14 @@ private:
|
|||
|
||||
mtpRequestId _contactsRequestId = 0;
|
||||
mtpRequestId _contactsStatusesRequestId = 0;
|
||||
base::flat_set<not_null<Data::Folder*>> _dialogFolderRequests;
|
||||
base::flat_map<
|
||||
not_null<History*>,
|
||||
std::vector<Fn<void()>>> _dialogRequests;
|
||||
base::flat_map<
|
||||
not_null<History*>,
|
||||
std::vector<Fn<void()>>> _dialogRequestsPending;
|
||||
base::flat_set<not_null<History*>> _fakeChatListRequests;
|
||||
|
||||
base::flat_map<not_null<History*>, mtpRequestId> _unreadMentionsRequests;
|
||||
|
||||
base::flat_map<std::tuple<
|
||||
base::flat_set<std::tuple<
|
||||
not_null<PeerData*>,
|
||||
SharedMediaType,
|
||||
MsgId,
|
||||
SliceType>, mtpRequestId> _sharedMediaRequests;
|
||||
SliceType>> _sharedMediaRequests;
|
||||
|
||||
base::flat_map<not_null<UserData*>, mtpRequestId> _userPhotosRequests;
|
||||
|
||||
|
|
@ -800,18 +774,6 @@ private:
|
|||
|
||||
rpl::event_stream<SendAction> _sendActions;
|
||||
|
||||
struct ReadRequest {
|
||||
ReadRequest(mtpRequestId requestId, MsgId upTo)
|
||||
: requestId(requestId)
|
||||
, upTo(upTo) {
|
||||
}
|
||||
|
||||
mtpRequestId requestId = 0;
|
||||
MsgId upTo = 0;
|
||||
};
|
||||
base::flat_map<not_null<PeerData*>, ReadRequest> _readRequests;
|
||||
base::flat_map<not_null<PeerData*>, MsgId> _readRequestsPending;
|
||||
|
||||
std::unique_ptr<TaskQueue> _fileLoader;
|
||||
base::flat_map<uint64, std::shared_ptr<SendingAlbum>> _sendingAlbums;
|
||||
|
||||
|
|
|
|||
|
|
@ -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(); });
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<not_null<PeerData*>, QVector<MTPint>> idsByPeer;
|
||||
auto remove = std::vector<not_null<HistoryItem*>>();
|
||||
remove.reserve(_ids.size());
|
||||
base::flat_map<not_null<History*>, QVector<MTPint>> idsByPeer;
|
||||
base::flat_map<not_null<PeerData*>, QVector<MTPint>> 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();
|
||||
|
|
|
|||
|
|
@ -767,6 +767,7 @@ not_null<Ui::InputField*> CreatePollBox::setupQuestion(
|
|||
st::createPollFieldPadding);
|
||||
InitField(getDelegate()->outerContainer(), question, _session);
|
||||
question->setMaxLength(kQuestionLimit + kErrorLimit);
|
||||
question->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
|
||||
|
||||
const auto warning = CreateWarningLabel(
|
||||
container,
|
||||
|
|
|
|||
442
Telegram/SourceFiles/boxes/dictionaries_manager.cpp
Normal file
442
Telegram/SourceFiles/boxes/dictionaries_manager.cpp
Normal file
|
|
@ -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<int>;
|
||||
using namespace Storage::CloudBlob;
|
||||
|
||||
using Loading = MTP::DedicatedLoader::Progress;
|
||||
using DictState = BlobState;
|
||||
using QueryCallback = Fn<void(const QString &)>;
|
||||
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<Ui::MultiSelect>(
|
||||
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<Ui::VerticalLayout*> content,
|
||||
const Spellchecker::Dict &dict,
|
||||
bool buttonEnabled,
|
||||
rpl::producer<QStringView> query) {
|
||||
const auto id = dict.id;
|
||||
buttonEnabled &= DictExists(id);
|
||||
|
||||
const auto locale = Spellchecker::LocaleFromLangId(id);
|
||||
const std::vector<QString> indexList = {
|
||||
dict.name,
|
||||
QLocale::languageToString(locale.language()),
|
||||
QLocale::countryToString(locale.country())
|
||||
};
|
||||
|
||||
const auto wrap = content->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
|
||||
content,
|
||||
object_ptr<Ui::SettingsButton>(
|
||||
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<base::unique_qptr<Loader>>;
|
||||
|
||||
const auto localLoader = button->lifetime()
|
||||
.make_state<base::unique_qptr<Loader>>();
|
||||
const auto localLoaderValues = button->lifetime()
|
||||
.make_state<rpl::event_stream<Loader*>>();
|
||||
const auto setLocalLoader = [=](base::unique_qptr<Loader> loader) {
|
||||
*localLoader = std::move(loader);
|
||||
localLoaderValues->fire(localLoader->get());
|
||||
};
|
||||
const auto destroyLocalLoader = [=] {
|
||||
setLocalLoader(nullptr);
|
||||
};
|
||||
|
||||
const auto buttonState = button->lifetime()
|
||||
.make_state<rpl::variable<DictState>>();
|
||||
const auto dictionaryRemoved = button->lifetime()
|
||||
.make_state<rpl::event_stream<>>();
|
||||
const auto dictionaryFromGlobalLoader = button->lifetime()
|
||||
.make_state<rpl::event_stream<>>();
|
||||
|
||||
const auto globalLoader = button->lifetime()
|
||||
.make_state<GlobalLoaderPtr>();
|
||||
|
||||
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<Ui::FlatLabel>(
|
||||
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<Active>();
|
||||
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<Failed>();
|
||||
}) | 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<Failed>() || !state.is<Available>();
|
||||
});
|
||||
|
||||
button->toggledValue(
|
||||
) | rpl::start_with_next([=](bool toggled) {
|
||||
const auto &state = buttonState->current();
|
||||
if (toggled && (state.is<Available>() || state.is<Failed>())) {
|
||||
const auto weak = Ui::MakeWeak(button);
|
||||
setLocalLoader(base::make_unique_q<Loader>(
|
||||
App::main(),
|
||||
id,
|
||||
Spellchecker::GetDownloadLocation(id),
|
||||
Spellchecker::DictPathByLangId(id),
|
||||
Spellchecker::GetDownloadSize(id),
|
||||
crl::guard(weak, destroyLocalLoader)));
|
||||
} else if (!toggled && state.is<Loading>()) {
|
||||
if (const auto g = rawGlobalLoaderPtr()) {
|
||||
g->destroy();
|
||||
return;
|
||||
}
|
||||
if (localLoader && localLoader->get()->id() == id) {
|
||||
destroyLocalLoader();
|
||||
}
|
||||
}
|
||||
}, button->lifetime());
|
||||
|
||||
const auto contextMenu = button->lifetime()
|
||||
.make_state<base::unique_qptr<Ui::PopupMenu>>();
|
||||
const auto showMenu = [=] {
|
||||
if (!DictExists(id)) {
|
||||
return false;
|
||||
}
|
||||
*contextMenu = base::make_unique_q<Ui::PopupMenu>(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<QEvent*> 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<Ui::VerticalLayout>(this);
|
||||
|
||||
const auto queryStream = content->lifetime()
|
||||
.make_state<rpl::event_stream<QStringView>>();
|
||||
|
||||
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<Main::Session*> session)
|
||||
: _session(session) {
|
||||
}
|
||||
|
||||
void ManageDictionariesBox::setInnerFocus() {
|
||||
_setInnerFocus();
|
||||
}
|
||||
|
||||
void ManageDictionariesBox::prepare() {
|
||||
const auto multiSelect = CreateMultiSelect(this);
|
||||
|
||||
const auto inner = setInnerWidget(
|
||||
object_ptr<Inner>(
|
||||
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<int>(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
|
||||
38
Telegram/SourceFiles/boxes/dictionaries_manager.h
Normal file
38
Telegram/SourceFiles/boxes/dictionaries_manager.h
Normal file
|
|
@ -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<Main::Session*> session);
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
void setInnerFocus() override;
|
||||
|
||||
private:
|
||||
const not_null<Main::Session*> _session;
|
||||
Fn<void()> _setInnerFocus;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
#endif // !TDESKTOP_DISABLE_SPELLCHECK
|
||||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ void MuteSettingsBox::prepare() {
|
|||
|
||||
_save = [=] {
|
||||
const auto muteForSeconds = group->value() * 3600;
|
||||
_peer->session().data().updateNotifySettings(
|
||||
_peer->owner().updateNotifySettings(
|
||||
_peer,
|
||||
muteForSeconds);
|
||||
closeBox();
|
||||
|
|
|
|||
|
|
@ -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<UserData*> bot, not_null<PeerData*> chat) {
|
||||
const auto history = chat->owner().historyLoaded(chat);
|
||||
const auto randomId = rand_value<uint64>();
|
||||
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<MTPMessageEntity>(),
|
||||
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<void()> finish) {
|
||||
const auto randomId = rand_value<uint64>();
|
||||
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<MTPMessageEntity>(),
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -500,6 +500,7 @@ object_ptr<Ui::RpWidget> 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(),
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ private:
|
|||
MTP::Sender _api;
|
||||
|
||||
MsgId _offsetId = 0;
|
||||
mtpRequestId _loadRequestId = 0;
|
||||
int _loadRequestId = 0; // Not a real mtpRequestId.
|
||||
bool _allLoaded = false;
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<SetState> state() const;
|
||||
void destroy();
|
||||
|
||||
private:
|
||||
void setImplementation(std::unique_ptr<MTP::DedicatedLoader> loader);
|
||||
void unpack(const QString &path);
|
||||
void finalize(const QString &path);
|
||||
void fail();
|
||||
|
||||
int _id = 0;
|
||||
int _size = 0;
|
||||
rpl::variable<SetState> _state;
|
||||
|
||||
MTP::WeakInstance _mtproto;
|
||||
std::unique_ptr<MTP::DedicatedLoader> _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> 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<const Loading*> 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<MTP::DedicatedLoader> 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<SetState> Loader::state() const {
|
||||
return _state.value();
|
||||
}
|
||||
|
||||
void Loader::setImplementation(
|
||||
std::unique_ptr<MTP::DedicatedLoader> 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<Ui::VerticalLayout>(this);
|
||||
|
||||
const auto sets = Sets();
|
||||
for (const auto &set : sets) {
|
||||
for (const auto &set : kSets) {
|
||||
content->add(object_ptr<Row>(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<Loader>(App::main(), _id));
|
||||
SetGlobalLoader(base::make_unique_q<Loader>(
|
||||
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<Failed>() ? 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<Loading>(&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<Ui::RadialAnimation>(
|
||||
[=](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<Failed>() ? 0. : 1.,
|
||||
true,
|
||||
crl::now());
|
||||
updateLoadingToFinished();
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
|
|
|
|||
|
|
@ -280,17 +280,13 @@ void InitSpellchecker(
|
|||
not_null<Main::Session*> session,
|
||||
not_null<Ui::InputField*> field) {
|
||||
#ifndef TDESKTOP_DISABLE_SPELLCHECK
|
||||
if (!Platform::Spellchecker::IsAvailable()) {
|
||||
return;
|
||||
}
|
||||
const auto s = Ui::CreateChild<Spellchecker::SpellingHighlighter>(
|
||||
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<Ui::ManageDictionariesBox>(session)); }
|
||||
});
|
||||
field->setExtendedContextMenu(s->contextMenuCreated());
|
||||
#endif // TDESKTOP_DISABLE_SPELLCHECK
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
447
Telegram/SourceFiles/chat_helpers/spellchecker_common.cpp
Normal file
447
Telegram/SourceFiles/chat_helpers/spellchecker_common.cpp
Normal file
|
|
@ -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 <QtGui/QGuiApplication>
|
||||
#include <QtGui/QInputMethod>
|
||||
|
||||
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<base::unique_qptr<DictLoader>>;
|
||||
|
||||
DictLoaderPtr BackgroundLoader;
|
||||
rpl::event_stream<int> BackgroundLoaderChanged;
|
||||
|
||||
void SetBackgroundLoader(DictLoaderPtr loader) {
|
||||
BackgroundLoader = std::move(loader);
|
||||
}
|
||||
|
||||
void DownloadDictionaryInBackground(
|
||||
not_null<Main::Session*> session,
|
||||
int counter,
|
||||
std::vector<int> 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<base::unique_qptr<DictLoader>>();
|
||||
*sharedLoader = base::make_unique_q<DictLoader>(
|
||||
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<int> GlobalLoaderChanged() {
|
||||
return BackgroundLoaderChanged.events();
|
||||
}
|
||||
|
||||
DictLoader::DictLoader(
|
||||
QObject *parent,
|
||||
int id,
|
||||
MTP::DedicatedLoader::Location location,
|
||||
const QString &folder,
|
||||
int size,
|
||||
Fn<void()> 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<Dict> 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<QString> ButtonManageDictsState(
|
||||
not_null<Main::Session*> 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<int> DefaultLanguages() {
|
||||
std::vector<int> 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<Main::Session*> 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<int>());
|
||||
}, 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
|
||||
74
Telegram/SourceFiles/chat_helpers/spellchecker_common.h
Normal file
74
Telegram/SourceFiles/chat_helpers/spellchecker_common.h
Normal file
|
|
@ -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<Dict> Dictionaries();
|
||||
|
||||
void Start(not_null<Main::Session*> session);
|
||||
[[nodiscard]] rpl::producer<QString> ButtonManageDictsState(
|
||||
not_null<Main::Session*> session);
|
||||
|
||||
std::vector<int> DefaultLanguages();
|
||||
|
||||
class DictLoader : public Storage::CloudBlob::BlobLoader {
|
||||
public:
|
||||
DictLoader(
|
||||
QObject *parent,
|
||||
int id,
|
||||
MTP::DedicatedLoader::Location location,
|
||||
const QString &folder,
|
||||
int size,
|
||||
Fn<void()> destroyCallback);
|
||||
|
||||
void destroy() override;
|
||||
|
||||
rpl::lifetime &lifetime() {
|
||||
return _lifetime;
|
||||
}
|
||||
|
||||
private:
|
||||
void unpack(const QString &path) override;
|
||||
void fail() override;
|
||||
|
||||
Fn<void()> _destroyCallback;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
std::shared_ptr<base::unique_qptr<DictLoader>> GlobalLoader();
|
||||
rpl::producer<int> GlobalLoaderChanged();
|
||||
|
||||
} // namespace Spellchecker
|
||||
|
||||
#endif // !TDESKTOP_DISABLE_SPELLCHECK
|
||||
|
|
@ -230,10 +230,6 @@ void Application::run() {
|
|||
|
||||
_window = std::make_unique<Window::Controller>(&activeAccount());
|
||||
|
||||
const auto currentGeometry = _window->widget()->geometry();
|
||||
_mediaView = std::make_unique<Media::View::OverlayWidget>();
|
||||
_window->widget()->setGeometry(currentGeometry);
|
||||
|
||||
QCoreApplication::instance()->installEventFilter(this);
|
||||
connect(
|
||||
static_cast<QGuiApplication*>(QCoreApplication::instance()),
|
||||
|
|
@ -262,8 +258,15 @@ void Application::run() {
|
|||
_window->setupIntro();
|
||||
}
|
||||
}
|
||||
|
||||
_window->widget()->show();
|
||||
|
||||
const auto currentGeometry = _window->widget()->geometry();
|
||||
_mediaView = std::make_unique<Media::View::OverlayWidget>();
|
||||
_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<const PhotoOpenClickHandler*> link) {
|
|||
}
|
||||
|
||||
void Application::showPhoto(not_null<PhotoData*> photo, HistoryItem *item) {
|
||||
Expects(_mediaView != nullptr);
|
||||
|
||||
_mediaView->showPhoto(photo, item);
|
||||
_mediaView->activateWindow();
|
||||
_mediaView->setFocus();
|
||||
|
|
@ -305,12 +310,16 @@ void Application::showPhoto(not_null<PhotoData*> photo, HistoryItem *item) {
|
|||
void Application::showPhoto(
|
||||
not_null<PhotoData*> photo,
|
||||
not_null<PeerData*> peer) {
|
||||
Expects(_mediaView != nullptr);
|
||||
|
||||
_mediaView->showPhoto(photo, peer);
|
||||
_mediaView->activateWindow();
|
||||
_mediaView->setFocus();
|
||||
}
|
||||
|
||||
void Application::showDocument(not_null<DocumentData*> document, HistoryItem *item) {
|
||||
Expects(_mediaView != nullptr);
|
||||
|
||||
if (cUseExternalVideoPlayer()
|
||||
&& document->isVideoFile()
|
||||
&& document->loaded()) {
|
||||
|
|
@ -325,6 +334,8 @@ void Application::showDocument(not_null<DocumentData*> document, HistoryItem *it
|
|||
void Application::showTheme(
|
||||
not_null<DocumentData*> document,
|
||||
const Data::CloudTheme &cloud) {
|
||||
Expects(_mediaView != nullptr);
|
||||
|
||||
_mediaView->showTheme(document, cloud);
|
||||
_mediaView->activateWindow();
|
||||
_mediaView->setFocus();
|
||||
|
|
|
|||
|
|
@ -55,6 +55,20 @@ std::map<int, const char*> 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."
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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*> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
683
Telegram/SourceFiles/data/data_histories.cpp
Normal file
683
Telegram/SourceFiles/data/data_histories.cpp
Normal file
|
|
@ -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<Session*> 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<History*> 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<History>(&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*> 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<HistoryItem*> 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*> history, MsgId tillId) {
|
||||
readInboxTill(history, tillId, false);
|
||||
}
|
||||
|
||||
void Histories::readInboxTill(
|
||||
not_null<History*> 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<HistoryItem*> item) {
|
||||
if (!IsServerMsgId(item->id)) {
|
||||
readClientSideMessage(item);
|
||||
} else {
|
||||
readInboxTill(item->history(), item->id, true);
|
||||
}
|
||||
}
|
||||
|
||||
void Histories::readClientSideMessage(not_null<HistoryItem*> 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<Data::Folder*> folder) {
|
||||
if (_dialogFolderRequests.contains(folder)) {
|
||||
return;
|
||||
}
|
||||
_dialogFolderRequests.emplace(folder);
|
||||
|
||||
auto peers = QVector<MTPInputDialogPeer>(
|
||||
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*> history,
|
||||
Fn<void()> 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*> 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<MTPInputDialogPeer>();
|
||||
const auto dialogPeer = [](not_null<History*> 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*> 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*> 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*> history) {
|
||||
if (_fakeChatListRequests.contains(history)) {
|
||||
return;
|
||||
}
|
||||
|
||||
_fakeChatListRequests.emplace(history);
|
||||
sendRequest(history, RequestType::History, [=](Fn<void()> 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<MTPMessage>(0),
|
||||
MTP_vector<MTPChat>(0),
|
||||
MTP_vector<MTPUser>(0)));
|
||||
finish();
|
||||
}).send();
|
||||
});
|
||||
}
|
||||
|
||||
void Histories::sendPendingReadInbox(not_null<History*> 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<crl::time>();
|
||||
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*> 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<void()> 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*> 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*> history,
|
||||
const QVector<MTPint> &ids,
|
||||
bool revoke) {
|
||||
sendRequest(history, RequestType::Delete, [=](Fn<void()> 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<MTPint>(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<MTPint>(ids)
|
||||
)).done(done).fail(fail).send();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Histories::deleteAllMessages(
|
||||
not_null<History*> history,
|
||||
MsgId deleteTillId,
|
||||
bool justClear,
|
||||
bool revoke) {
|
||||
sendRequest(history, RequestType::Delete, [=](Fn<void()> 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*> history,
|
||||
RequestType type,
|
||||
Fn<mtpRequestId(Fn<void()> 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*> 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*> history,
|
||||
not_null<State*> 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*> history) {
|
||||
const auto i = _states.find(history);
|
||||
return (i != end(_states)) ? &i->second : nullptr;
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
133
Telegram/SourceFiles/data/data_histories.h
Normal file
133
Telegram/SourceFiles/data/data_histories.h
Normal file
|
|
@ -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<Session*> owner);
|
||||
|
||||
[[nodiscard]] Session &owner() const;
|
||||
[[nodiscard]] Main::Session &session() const;
|
||||
|
||||
[[nodiscard]] History *find(PeerId peerId);
|
||||
[[nodiscard]] not_null<History*> findOrCreate(PeerId peerId);
|
||||
|
||||
void unloadAll();
|
||||
void clearAll();
|
||||
|
||||
void readInbox(not_null<History*> history);
|
||||
void readInboxTill(not_null<HistoryItem*> item);
|
||||
void readInboxTill(not_null<History*> history, MsgId tillId);
|
||||
void readInboxOnNewMessage(not_null<HistoryItem*> item);
|
||||
void readClientSideMessage(not_null<HistoryItem*> item);
|
||||
void sendPendingReadInbox(not_null<History*> history);
|
||||
|
||||
void requestDialogEntry(not_null<Data::Folder*> folder);
|
||||
void requestDialogEntry(
|
||||
not_null<History*> history,
|
||||
Fn<void()> callback = nullptr);
|
||||
void dialogEntryApplied(not_null<History*> history);
|
||||
void changeDialogUnreadMark(not_null<History*> history, bool unread);
|
||||
//void changeDialogUnreadMark(not_null<Data::Feed*> feed, bool unread); // #feed
|
||||
void requestFakeChatListMessage(not_null<History*> history);
|
||||
|
||||
void deleteMessages(
|
||||
not_null<History*> history,
|
||||
const QVector<MTPint> &ids,
|
||||
bool revoke);
|
||||
void deleteAllMessages(
|
||||
not_null<History*> history,
|
||||
MsgId deleteTillId,
|
||||
bool justClear,
|
||||
bool revoke);
|
||||
|
||||
int sendRequest(
|
||||
not_null<History*> history,
|
||||
RequestType type,
|
||||
Fn<mtpRequestId(Fn<void()> finish)> generator);
|
||||
void cancelRequest(int id);
|
||||
|
||||
private:
|
||||
struct PostponedHistoryRequest {
|
||||
Fn<mtpRequestId(Fn<void()> finish)> generator;
|
||||
};
|
||||
struct SentRequest {
|
||||
Fn<mtpRequestId(Fn<void()> finish)> generator;
|
||||
mtpRequestId id = 0;
|
||||
RequestType type = RequestType::None;
|
||||
};
|
||||
struct State {
|
||||
base::flat_map<int, PostponedHistoryRequest> postponed;
|
||||
base::flat_map<int, SentRequest> sent;
|
||||
MsgId willReadTill = 0;
|
||||
MsgId sentReadTill = 0;
|
||||
crl::time willReadWhen = 0;
|
||||
bool sentReadDone = false;
|
||||
bool postponedRequestEntry = false;
|
||||
};
|
||||
|
||||
void readInboxTill(not_null<History*> history, MsgId tillId, bool force);
|
||||
void sendReadRequests();
|
||||
void sendReadRequest(not_null<History*> history, State &state);
|
||||
[[nodiscard]] State *lookup(not_null<History*> history);
|
||||
void checkEmptyState(not_null<History*> history);
|
||||
void checkPostponed(not_null<History*> history, int id);
|
||||
void finishSentRequest(
|
||||
not_null<History*> history,
|
||||
not_null<State*> 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<Session*> _owner;
|
||||
|
||||
std::unordered_map<PeerId, std::unique_ptr<History>> _map;
|
||||
base::flat_map<not_null<History*>, State> _states;
|
||||
base::flat_map<int, not_null<History*>> _historyByRequest;
|
||||
int _requestAutoincrement = 0;
|
||||
base::Timer _readRequestsTimer;
|
||||
|
||||
base::flat_set<not_null<Data::Folder*>> _dialogFolderRequests;
|
||||
base::flat_map<
|
||||
not_null<History*>,
|
||||
std::vector<Fn<void()>>> _dialogRequests;
|
||||
base::flat_map<
|
||||
not_null<History*>,
|
||||
std::vector<Fn<void()>>> _dialogRequestsPending;
|
||||
|
||||
base::flat_set<not_null<History*>> _fakeChatListRequests;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Data
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Main::Session*> 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<void()> 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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ private:
|
|||
const Query &query,
|
||||
Data *listData);
|
||||
|
||||
MTP::Sender _api;
|
||||
const not_null<Main::Session*> _session;
|
||||
Cache _cache;
|
||||
Cache::iterator _current = _cache.end();
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Main::Session*> session)
|
|||
, _scheduledMessages(std::make_unique<ScheduledMessages>(this))
|
||||
, _cloudThemes(std::make_unique<CloudThemes>(session))
|
||||
, _streaming(std::make_unique<Streaming>(this))
|
||||
, _mediaRotation(std::make_unique<MediaRotation>()) {
|
||||
, _mediaRotation(std::make_unique<MediaRotation>())
|
||||
, _histories(std::make_unique<Histories>(this)) {
|
||||
_cache->open(Local::cacheKey());
|
||||
_bigFileCache->open(Local::cacheBigFileKey());
|
||||
|
||||
|
|
@ -215,9 +217,7 @@ Session::Session(not_null<Main::Session*> 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<PeerData*> Session::peer(PeerId id) {
|
||||
|
|
@ -768,20 +768,11 @@ void Session::enumerateChannels(
|
|||
}
|
||||
|
||||
not_null<History*> 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<History>(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<History*> Session::history(not_null<const PeerData*> 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<HistoryItem> 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<HistoryItem*> 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<HistoryItem*> item) {
|
|||
}
|
||||
}
|
||||
|
||||
void Session::destroyMessage(not_null<HistoryItem*> item) {
|
||||
Expects(item->isHistoryEntry() || !item->mainView());
|
||||
|
||||
void Session::unregisterMessage(not_null<HistoryItem*> 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 {
|
||||
|
|
|
|||
|
|
@ -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 <typename ...Args>
|
||||
not_null<HistoryMessage*> makeMessage(Args &&...args) {
|
||||
return static_cast<HistoryMessage*>(
|
||||
registerMessage(
|
||||
std::make_unique<HistoryMessage>(
|
||||
std::forward<Args>(args)...)));
|
||||
}
|
||||
|
||||
template <typename ...Args>
|
||||
not_null<HistoryService*> makeServiceMessage(Args &&...args) {
|
||||
return static_cast<HistoryService*>(
|
||||
registerMessage(
|
||||
std::make_unique<HistoryService>(
|
||||
std::forward<Args>(args)...)));
|
||||
}
|
||||
void destroyMessage(not_null<HistoryItem*> item);
|
||||
void registerMessage(not_null<HistoryItem*> item);
|
||||
void unregisterMessage(not_null<HistoryItem*> 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<MsgId, std::unique_ptr<HistoryItem>>;
|
||||
using Messages = std::unordered_map<MsgId, not_null<HistoryItem*>>;
|
||||
|
||||
void suggestStartExport();
|
||||
|
||||
|
|
@ -715,7 +705,8 @@ private:
|
|||
|
||||
const Messages *messagesList(ChannelId channelId) const;
|
||||
not_null<Messages*> messagesListForInsert(ChannelId channelId);
|
||||
HistoryItem *registerMessage(std::unique_ptr<HistoryItem> item);
|
||||
not_null<HistoryItem*> registerMessage(
|
||||
std::unique_ptr<HistoryItem> item);
|
||||
void changeMessageId(ChannelId channel, MsgId wasId, MsgId nowId);
|
||||
void removeDependencyMessage(not_null<HistoryItem*> item);
|
||||
|
||||
|
|
@ -968,7 +959,6 @@ private:
|
|||
base::Timer _unmuteByFinishedTimer;
|
||||
|
||||
std::unordered_map<PeerId, std::unique_ptr<PeerData>> _peers;
|
||||
std::unordered_map<PeerId, std::unique_ptr<History>> _histories;
|
||||
|
||||
MessageIdsList _mimeForwardIds;
|
||||
|
||||
|
|
@ -989,6 +979,7 @@ private:
|
|||
std::unique_ptr<CloudThemes> _cloudThemes;
|
||||
std::unique_ptr<Streaming> _streaming;
|
||||
std::unique_ptr<MediaRotation> _mediaRotation;
|
||||
std::unique_ptr<Histories> _histories;
|
||||
MsgId _nonHistoryEntryId = ServerMaxMsgId;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<void()> 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<void()> 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<void()> 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()) {
|
||||
|
|
|
|||
|
|
@ -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<QString, MTPmessages_Messages> _searchCache;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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<HistoryItem*> 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<HistoryItem*> History::insertItem(
|
||||
std::unique_ptr<HistoryItem> 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<HistoryItem*> 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<HistoryItem>(item.get());
|
||||
const auto i = _messages.find(hack);
|
||||
hack.release();
|
||||
|
||||
Assert(i != end(_messages));
|
||||
_messages.erase(i);
|
||||
}
|
||||
|
||||
not_null<HistoryItem*> History::addNewItem(
|
||||
not_null<HistoryItem*> 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<HistoryItem*> History::addNewLocalMessage(
|
|||
const QString &postAuthor,
|
||||
not_null<HistoryMessage*> forwardOriginal) {
|
||||
return addNewItem(
|
||||
owner().makeMessage(
|
||||
this,
|
||||
makeMessage(
|
||||
id,
|
||||
flags,
|
||||
clientFlags,
|
||||
|
|
@ -714,8 +761,7 @@ not_null<HistoryItem*> History::addNewLocalMessage(
|
|||
const TextWithEntities &caption,
|
||||
const MTPReplyMarkup &markup) {
|
||||
return addNewItem(
|
||||
owner().makeMessage(
|
||||
this,
|
||||
makeMessage(
|
||||
id,
|
||||
flags,
|
||||
clientFlags,
|
||||
|
|
@ -743,8 +789,7 @@ not_null<HistoryItem*> History::addNewLocalMessage(
|
|||
const TextWithEntities &caption,
|
||||
const MTPReplyMarkup &markup) {
|
||||
return addNewItem(
|
||||
owner().makeMessage(
|
||||
this,
|
||||
makeMessage(
|
||||
id,
|
||||
flags,
|
||||
clientFlags,
|
||||
|
|
@ -771,8 +816,7 @@ not_null<HistoryItem*> History::addNewLocalMessage(
|
|||
not_null<GameData*> game,
|
||||
const MTPReplyMarkup &markup) {
|
||||
return addNewItem(
|
||||
owner().makeMessage(
|
||||
this,
|
||||
makeMessage(
|
||||
id,
|
||||
flags,
|
||||
clientFlags,
|
||||
|
|
@ -1241,7 +1285,7 @@ void History::newItemAdded(not_null<HistoryItem*> item) {
|
|||
if (unreadCountKnown()) {
|
||||
setUnreadCount(unreadCount() + 1);
|
||||
} else {
|
||||
session().api().requestDialogEntry(this);
|
||||
owner().histories().requestDialogEntry(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1254,7 +1298,7 @@ void History::newItemAdded(not_null<HistoryItem*> 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<const Element*> 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<int> 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<int> 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<int> 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<Data::Folder*> 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<HistoryView::UnreadBar>()) {
|
||||
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<not_null<HistoryItem*>>();
|
||||
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<UserId> &changes) {
|
||||
|
|
|
|||
|
|
@ -90,6 +90,25 @@ public:
|
|||
|
||||
void applyGroupAdminChanges(const base::flat_set<UserId> &changes);
|
||||
|
||||
template <typename ...Args>
|
||||
not_null<HistoryMessage*> makeMessage(Args &&...args) {
|
||||
return static_cast<HistoryMessage*>(
|
||||
insertItem(
|
||||
std::make_unique<HistoryMessage>(
|
||||
this,
|
||||
std::forward<Args>(args)...)).get());
|
||||
}
|
||||
|
||||
template <typename ...Args>
|
||||
not_null<HistoryService*> makeServiceMessage(Args &&...args) {
|
||||
return static_cast<HistoryService*>(
|
||||
insertItem(
|
||||
std::make_unique<HistoryService>(
|
||||
this,
|
||||
std::forward<Args>(args)...)).get());
|
||||
}
|
||||
void destroyMessage(not_null<HistoryItem*> item);
|
||||
|
||||
HistoryItem *addNewMessage(
|
||||
const MTPMessage &msg,
|
||||
MTPDmessage_ClientFlags clientFlags,
|
||||
|
|
@ -158,7 +177,7 @@ public:
|
|||
void unregisterLocalMessage(not_null<HistoryItem*> 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<const HistoryItem*> 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<int> countStillUnreadLocal(MsgId readTillId) const;
|
||||
|
||||
// Still public data.
|
||||
std::deque<std::unique_ptr<HistoryBlock>> blocks;
|
||||
|
||||
|
|
@ -395,6 +424,7 @@ private:
|
|||
void removeBlock(not_null<HistoryBlock*> block);
|
||||
void clearSharedMedia();
|
||||
|
||||
not_null<HistoryItem*> insertItem(std::unique_ptr<HistoryItem> item);
|
||||
not_null<HistoryItem*> addNewItem(
|
||||
not_null<HistoryItem*> 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<int> 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<int> _unreadMentionsCount;
|
||||
base::flat_set<MsgId> _unreadMentions;
|
||||
std::optional<HistoryItem*> _lastMessage;
|
||||
std::optional<HistoryItem*> _lastServerMessage;
|
||||
base::flat_set<not_null<HistoryItem*>> _localMessages;
|
||||
std::unordered_set<std::unique_ptr<HistoryItem>> _messages;
|
||||
|
||||
// This almost always is equal to _lastMessage. The only difference is
|
||||
// for a group that migrated to a supergroup. Then _lastMessage can
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ public:
|
|||
|
||||
void touchScrollUpdated(const QPoint &screenPos);
|
||||
|
||||
void checkHistoryActivation();
|
||||
void recountHistoryGeometry();
|
||||
void updateSize();
|
||||
|
||||
|
|
|
|||
|
|
@ -75,8 +75,7 @@ not_null<HistoryItem*> 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*> 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);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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<HistoryItem*> 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<HistoryItem*> 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<MTPint>(msgIds),
|
||||
MTP_vector<MTPlong>(generateRandom()),
|
||||
peer->input,
|
||||
MTP_int(options.scheduled)),
|
||||
rpcDone(base::duplicate(doneCallback)),
|
||||
nullptr,
|
||||
0,
|
||||
0,
|
||||
history->sendRequestId);
|
||||
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
||||
auto &api = history->session().api();
|
||||
history->sendRequestId = api.request(MTPmessages_ForwardMessages(
|
||||
MTP_flags(sendFlags),
|
||||
data->peer->input,
|
||||
MTP_vector<MTPint>(msgIds),
|
||||
MTP_vector<MTPlong>(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);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -525,8 +525,8 @@ HistoryService::HistoryService(
|
|||
|
||||
HistoryService::HistoryService(
|
||||
not_null<History*> history,
|
||||
MTPDmessage_ClientFlags clientFlags,
|
||||
MsgId id,
|
||||
MTPDmessage_ClientFlags clientFlags,
|
||||
TimeId date,
|
||||
const PreparedText &message,
|
||||
MTPDmessage::Flags flags,
|
||||
|
|
@ -797,10 +797,9 @@ not_null<HistoryService*> GenerateJoinedMessage(
|
|||
TimeId inviteDate,
|
||||
not_null<UserData*> 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);
|
||||
|
|
|
|||
|
|
@ -68,8 +68,8 @@ public:
|
|||
MTPDmessage_ClientFlags clientFlags);
|
||||
HistoryService(
|
||||
not_null<History*> history,
|
||||
MTPDmessage_ClientFlags clientFlags,
|
||||
MsgId id,
|
||||
MTPDmessage_ClientFlags clientFlags,
|
||||
TimeId date,
|
||||
const PreparedText &message,
|
||||
MTPDmessage::Flags flags = 0,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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<MTPMessage> &messages);
|
||||
void addMessagesToBack(PeerData *peer, const QVector<MTPMessage> &messages);
|
||||
|
||||
|
|
@ -586,15 +592,12 @@ private:
|
|||
std::optional<int> unreadBarTop() const;
|
||||
int itemTopForHighlight(not_null<HistoryView::Element*> 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<HistoryView::TopBarWidget> _topBar;
|
||||
object_ptr<Ui::ScrollArea> _scroll;
|
||||
|
|
|
|||
|
|
@ -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(); });
|
||||
|
|
|
|||
|
|
@ -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<Element*> previous) {
|
|||
return false;
|
||||
}
|
||||
|
||||
void Element::createUnreadBar() {
|
||||
if (!AddComponents(UnreadBar::Bit())) {
|
||||
return;
|
||||
}
|
||||
const auto bar = Get<UnreadBar>();
|
||||
bar->init();
|
||||
if (data()->mainView() == this) {
|
||||
recountAttachToPreviousInBlocks();
|
||||
}
|
||||
history()->owner().requestViewResize(this);
|
||||
}
|
||||
|
||||
void Element::destroyUnreadBar() {
|
||||
if (!Has<UnreadBar>()) {
|
||||
return;
|
||||
|
|
@ -431,29 +437,6 @@ void Element::destroyUnreadBar() {
|
|||
}
|
||||
}
|
||||
|
||||
void Element::setUnreadBarCount(int count) {
|
||||
const auto changed = AddComponents(UnreadBar::Bit());
|
||||
const auto bar = Get<UnreadBar>();
|
||||
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<UnreadBar>()) {
|
||||
bar->freezed = true;
|
||||
}
|
||||
}
|
||||
|
||||
int Element::displayedDateHeight() const {
|
||||
if (auto date = Get<DateBadge>()) {
|
||||
return date->height();
|
||||
|
|
|
|||
|
|
@ -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<UnreadBar, Element> {
|
||||
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<int>::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<Element*> 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;
|
||||
|
|
|
|||
|
|
@ -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<const Element*> was, Element *now) {
|
|||
if (_overElement == was) _overElement = now;
|
||||
if (_unreadBarElement == was) {
|
||||
const auto bar = _unreadBarElement->Get<UnreadBar>();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -482,8 +482,9 @@ void Sticker::setupLottie(not_null<DocumentData*> 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,9 +70,11 @@ void PhoneWidget::setupQrLogin() {
|
|||
) | rpl::then(
|
||||
account().appConfig().refreshed()
|
||||
) | rpl::map([=] {
|
||||
return account().appConfig().get<QString>(
|
||||
const auto result = account().appConfig().get<QString>(
|
||||
"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()
|
||||
|
|
|
|||
|
|
@ -33,7 +33,8 @@ void StartWidget::submit() {
|
|||
account().destroyStaleAuthorizationKeys();
|
||||
const auto qrLogin = account().appConfig().get<QString>(
|
||||
"qr_login_code",
|
||||
"disabled");
|
||||
"[not-set]");
|
||||
DEBUG_LOG(("qr_login_code: %1").arg(qrLogin));
|
||||
if (qrLogin == "primary") {
|
||||
goNext<QrWidget>();
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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<std::pair<DocumentId, crl::time>> mediaLastPlaybackPosition;
|
||||
qint32 videoPlaybackSpeed = SerializePlaybackSpeed(_variables.videoPlaybackSpeed.current());
|
||||
QByteArray videoPipGeometry = _variables.videoPipGeometry;
|
||||
std::vector<int> 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) {
|
||||
|
|
|
|||
|
|
@ -241,6 +241,31 @@ public:
|
|||
return _variables.spellcheckerEnabled.changes();
|
||||
}
|
||||
|
||||
void setDictionariesEnabled(std::vector<int> dictionaries) {
|
||||
_variables.dictionariesEnabled = std::move(dictionaries);
|
||||
}
|
||||
|
||||
std::vector<int> dictionariesEnabled() const {
|
||||
return _variables.dictionariesEnabled.current();
|
||||
}
|
||||
|
||||
rpl::producer<std::vector<int>> dictionariesEnabledChanges() const {
|
||||
return _variables.dictionariesEnabled.changes();
|
||||
}
|
||||
|
||||
void setAutoDownloadDictionaries(bool value) {
|
||||
_variables.autoDownloadDictionaries = value;
|
||||
}
|
||||
bool autoDownloadDictionaries() const {
|
||||
return _variables.autoDownloadDictionaries.current();
|
||||
}
|
||||
rpl::producer<bool> autoDownloadDictionariesValue() const {
|
||||
return _variables.autoDownloadDictionaries.value();
|
||||
}
|
||||
rpl::producer<bool> 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<float64> dialogsWidthRatio
|
||||
= kDefaultDialogsWidthRatio; // per-window
|
||||
rpl::variable<float64> dialogsWidthRatio; // per-window
|
||||
rpl::variable<int> thirdColumnWidth
|
||||
= kDefaultThirdColumnWidth; // per-window
|
||||
Ui::InputSubmitSettings sendSubmitWay;
|
||||
|
|
@ -296,6 +323,8 @@ private:
|
|||
std::vector<std::pair<DocumentId, crl::time>> mediaLastPlaybackPosition;
|
||||
rpl::variable<float64> videoPlaybackSpeed = 1.;
|
||||
QByteArray videoPipGeometry;
|
||||
rpl::variable<std::vector<int>> dictionariesEnabled;
|
||||
rpl::variable<bool> autoDownloadDictionaries = true;
|
||||
|
||||
static constexpr auto kDefaultSupportChatsLimitSlice
|
||||
= 7 * 24 * 60 * 60;
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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<QMouseEvent*>(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()
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ public:
|
|||
explicit MainWindow(not_null<Window::Controller*> 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<Window::PasscodeLockWidget> _passcodeLock = { nullptr };
|
||||
object_ptr<Intro::Widget> _intro = { nullptr };
|
||||
|
|
|
|||
|
|
@ -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<uchar*>(data);
|
||||
|
|
|
|||
|
|
@ -189,14 +189,16 @@ void Instance::setCurrent(const AudioMsgId &audioId) {
|
|||
}
|
||||
}
|
||||
|
||||
void Instance::clearStreamed(not_null<Data*> data) {
|
||||
void Instance::clearStreamed(not_null<Data*> 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<Streamed>(
|
||||
audioId,
|
||||
std::move(shared));
|
||||
|
|
|
|||
|
|
@ -225,7 +225,7 @@ private:
|
|||
not_null<Data*> data,
|
||||
Streaming::Error &&error);
|
||||
|
||||
void clearStreamed(not_null<Data *> data);
|
||||
void clearStreamed(not_null<Data*> data, bool savePosition = true);
|
||||
void emitUpdate(AudioMsgId::Type type);
|
||||
template <typename CheckCallback>
|
||||
void emitUpdate(AudioMsgId::Type type, CheckCallback check);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ struct Stream {
|
|||
QImage storage);
|
||||
[[nodiscard]] QImage PrepareByRequest(
|
||||
const QImage &original,
|
||||
bool alpha,
|
||||
int rotation,
|
||||
const FrameRequest &request,
|
||||
QImage storage);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -83,6 +83,8 @@ private:
|
|||
crl::time display = kTimeUnknown;
|
||||
|
||||
base::flat_map<const Instance*, Prepared> prepared;
|
||||
|
||||
bool alpha = false;
|
||||
};
|
||||
|
||||
class Shared {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "facades.h"
|
||||
#include "app.h"
|
||||
|
||||
#include <QtCore/QSize>
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
|
||||
#include <QtDBus>
|
||||
#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<int, QImage> 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<QTemporaryFile> TrayIconFile(
|
||||
const QPixmap &icon, QObject *parent) {
|
||||
const QIcon &icon,
|
||||
int size,
|
||||
QObject *parent) {
|
||||
auto ret = std::make_unique<QTemporaryFile>(
|
||||
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<IconPixmap>();
|
||||
qDBusRegisterMetaType<IconPixmapList>();
|
||||
#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<QString> 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() {
|
||||
|
|
|
|||
|
|
@ -24,8 +24,6 @@ class MainWindow : public Window::MainWindow {
|
|||
public:
|
||||
explicit MainWindow(not_null<Window::Controller*> 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<QTemporaryFile> _trayIconFile = nullptr;
|
||||
std::unique_ptr<QTemporaryFile> _trayToolTipIconFile = nullptr;
|
||||
|
||||
void setSNITrayIcon(const QIcon &icon, const QPixmap &iconPixmap);
|
||||
void setSNITrayIcon(const QIcon &icon);
|
||||
void attachToSNITrayIcon();
|
||||
#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION
|
||||
|
||||
|
|
|
|||
|
|
@ -88,8 +88,12 @@ QVersionNumber ParseSpecificationVersion(
|
|||
NotificationData::NotificationData(
|
||||
const std::shared_ptr<QDBusInterface> ¬ificationInterface,
|
||||
const base::weak_ptr<Manager> &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<uint> 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<MsgId, Notification>());
|
||||
}
|
||||
_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);
|
||||
|
|
|
|||
|
|
@ -38,15 +38,19 @@ public:
|
|||
NotificationData(
|
||||
const std::shared_ptr<QDBusInterface> ¬ificationInterface,
|
||||
const base::weak_ptr<Manager> &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<NotificationData>;
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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<QString>{
|
||||
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"));
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ bool InSandbox();
|
|||
bool InSnap();
|
||||
|
||||
bool IsXDGDesktopPortalPresent();
|
||||
bool UseXDGDesktopPortal();
|
||||
|
||||
QString ProcessNameByPID(const QString &pid);
|
||||
QString CurrentExecutablePath(int argc, char *argv[]);
|
||||
|
|
|
|||
|
|
@ -23,8 +23,6 @@ class MainWindow : public Window::MainWindow {
|
|||
public:
|
||||
explicit MainWindow(not_null<Window::Controller*> 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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -216,3 +216,7 @@ settingsForwardPrivacyTooltipPadding: margins(8px, 6px, 8px, 6px);
|
|||
settingsAccentColorSize: 24px;
|
||||
settingsAccentColorSkip: 4px;
|
||||
settingsAccentColorLine: 3px;
|
||||
|
||||
dictionariesSectionButton: SettingsButton(settingsUpdateToggle) {
|
||||
font: font(14px semibold);
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue