Compare commits
186 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f50fdd0236 | ||
|
|
d4f2b8dd0e | ||
|
|
f4042d5ad5 | ||
|
|
1c77b9c16f | ||
|
|
06629ad171 | ||
|
|
05df4f832b | ||
|
|
6c2a29b83f | ||
|
|
a586b18dfb | ||
|
|
d0994019ca | ||
|
|
23f94c61a4 | ||
|
|
2b9e4a8ddf | ||
|
|
e1d36cfd50 | ||
|
|
fbb2bae99f | ||
|
|
6bc7fa9ef4 | ||
|
|
bf06d4d545 | ||
|
|
bfafdd5b38 | ||
|
|
f581a15b6e | ||
|
|
c868cd6036 | ||
|
|
9d1a4cdbfe | ||
|
|
383e6dec43 | ||
|
|
85904e3022 | ||
|
|
f88b97553e | ||
|
|
63c6a1db82 | ||
|
|
ca97e3c375 | ||
|
|
ef30c776bf | ||
|
|
d45e74619d | ||
|
|
d92b5eebcc | ||
|
|
5c6b4d95b0 | ||
|
|
0fbec5eba1 | ||
|
|
ab13d9bdaf | ||
|
|
0165e31ca7 | ||
|
|
f1e75d809a | ||
|
|
c776f81dc7 | ||
|
|
9fd62d3892 | ||
|
|
793906ca9a | ||
|
|
35e575c2d7 | ||
|
|
f5e84220eb | ||
|
|
1d622fb3c0 | ||
|
|
d8d3dda2f3 | ||
|
|
e098922a4b | ||
|
|
413ddf285e | ||
|
|
7ac78be984 | ||
|
|
4c546156da | ||
|
|
db528b39e1 | ||
|
|
586744c112 | ||
|
|
7b106761be | ||
|
|
8fb7f0fc73 | ||
|
|
10b169f9f6 | ||
|
|
c83b8d4043 | ||
|
|
1fc2b19c94 | ||
|
|
fb97940cac | ||
|
|
16c38b54e2 | ||
|
|
7f29f57c3d | ||
|
|
1fb1d57a27 | ||
|
|
47d7bd95ae | ||
|
|
368eeaf754 | ||
|
|
1686eb394d | ||
|
|
02586ebe4b | ||
|
|
8f80c19ae1 | ||
|
|
1598165e2b | ||
|
|
f4cd84c313 | ||
|
|
9b574e497d | ||
|
|
6660338ccc | ||
|
|
423ea5b499 | ||
|
|
4695ebae6e | ||
|
|
aaa4db7b27 | ||
|
|
0965b06fa3 | ||
|
|
be96bf2812 | ||
|
|
b7aa60bedf | ||
|
|
d5b3fa017b | ||
|
|
36fbdfb380 | ||
|
|
d0c78eaddd | ||
|
|
6513422e40 | ||
|
|
f066e0f05a | ||
|
|
249f7813c1 | ||
|
|
29a498b959 | ||
|
|
ae9ed820ee | ||
|
|
803593cd8d | ||
|
|
897e432f40 | ||
|
|
50e0c3ee4d | ||
|
|
056945d9f5 | ||
|
|
a9b70a7d63 | ||
|
|
6dabd87df3 | ||
|
|
b35b6c4449 | ||
|
|
74ef8104a7 | ||
|
|
af0eebb6f1 | ||
|
|
dbb46ce9b0 | ||
|
|
700d3db4cc | ||
|
|
64cf0e1a44 | ||
|
|
7ad660a0e7 | ||
|
|
e27d2bc2d5 | ||
|
|
24fed8105c | ||
|
|
9ce59730ff | ||
|
|
3f26fc9f55 | ||
|
|
0834920db8 | ||
|
|
f4ed2c26ba | ||
|
|
c63e2c01ac | ||
|
|
c61f3a0aba | ||
|
|
3c9ca2eb94 | ||
|
|
33c1c48ad9 | ||
|
|
a27aea3887 | ||
|
|
ea4044e38c | ||
|
|
c967a72dcb | ||
|
|
7d386b164b | ||
|
|
ccbbf6f5f3 | ||
|
|
9725d4272e | ||
|
|
eb75859dc0 | ||
|
|
ad5507f2c8 | ||
|
|
58f82620e0 | ||
|
|
053eace154 | ||
|
|
d64014c995 | ||
|
|
44ec55b6a8 | ||
|
|
9dba723643 | ||
|
|
97a82762ef | ||
|
|
1542311d89 | ||
|
|
fb322b5fc5 | ||
|
|
581a21dbd9 | ||
|
|
3d431a27cb | ||
|
|
cbb9657044 | ||
|
|
3797753d16 | ||
|
|
37aabc0da9 | ||
|
|
956c3af0ae | ||
|
|
1329870c8e | ||
|
|
ff6365ec72 | ||
|
|
1e9c79ca85 | ||
|
|
40f12a2584 | ||
|
|
97bab388ea | ||
|
|
bf616036b3 | ||
|
|
669b79588e | ||
|
|
33f4946242 | ||
|
|
888e42df34 | ||
|
|
70c79eb6bd | ||
|
|
bdd3c51ab8 | ||
|
|
6ca43153bb | ||
|
|
7db53599e8 | ||
|
|
61647275e8 | ||
|
|
a37138aa52 | ||
|
|
1504136828 | ||
|
|
c12356a032 | ||
|
|
126ed6e6e3 | ||
|
|
fa4236e9ea | ||
|
|
b19dcf0653 | ||
|
|
77d1f64e0e | ||
|
|
3479a4ec59 | ||
|
|
bdf28370f9 | ||
|
|
cd81fc6727 | ||
|
|
7351641034 | ||
|
|
e0669e222d | ||
|
|
4c1f83daca | ||
|
|
ced2652deb | ||
|
|
8d1db85a28 | ||
|
|
97305c8cb5 | ||
|
|
1ef5d81270 | ||
|
|
9ff427afad | ||
|
|
1c5eadcd79 | ||
|
|
bc6c01de7f | ||
|
|
41255cab44 | ||
|
|
ccbc63cd6e | ||
|
|
97446ae783 | ||
|
|
5a75dd2b6f | ||
|
|
6559e83e83 | ||
|
|
d679703bbf | ||
|
|
66a3e36024 | ||
|
|
31e38e1690 | ||
|
|
da10059f45 | ||
|
|
cb5863177f | ||
|
|
84399286c1 | ||
|
|
2e92441b3a | ||
|
|
7883f97c94 | ||
|
|
297b5d6a76 | ||
|
|
492dc2568c | ||
|
|
547c657b1a | ||
|
|
c478d96385 | ||
|
|
2ede53e0ee | ||
|
|
6f760d513e | ||
|
|
f4f6550d66 | ||
|
|
c7878f9d21 | ||
|
|
cd75a45673 | ||
|
|
07e3671ca8 | ||
|
|
295aa644bf | ||
|
|
b5b78c0ade | ||
|
|
f5c0e5d31d | ||
|
|
246ed43046 | ||
|
|
701e1d7b4d | ||
|
|
9cbe899688 | ||
|
|
7409d615a3 |
47
.github/workflows/linux.yml
vendored
47
.github/workflows/linux.yml
vendored
@@ -221,6 +221,7 @@ jobs:
|
||||
--disable-autodetect \
|
||||
--disable-everything \
|
||||
--disable-neon \
|
||||
--disable-alsa \
|
||||
--disable-iconv \
|
||||
--enable-libopus \
|
||||
--enable-vaapi \
|
||||
@@ -378,36 +379,43 @@ jobs:
|
||||
cd $LibrariesPath
|
||||
sudo cp -R openssl-cache/. /
|
||||
|
||||
- name: Libxkbcommon.
|
||||
run: |
|
||||
cd $LibrariesPath
|
||||
|
||||
git clone -b xkbcommon-0.8.4 --depth=1 $GIT/xkbcommon/libxkbcommon.git
|
||||
cd libxkbcommon
|
||||
./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 --disable-dtd-validation
|
||||
./autogen.sh \
|
||||
--enable-static \
|
||||
--disable-documentation \
|
||||
--disable-dtd-validation
|
||||
make -j$(nproc)
|
||||
sudo make install
|
||||
cd ..
|
||||
rm -rf wayland
|
||||
|
||||
- name: Libxkbcommon.
|
||||
run: |
|
||||
cd $LibrariesPath
|
||||
|
||||
git clone -b xkbcommon-0.8.4 --depth=1 $GIT/xkbcommon/libxkbcommon.git
|
||||
cd libxkbcommon
|
||||
./autogen.sh \
|
||||
--disable-docs \
|
||||
--disable-wayland \
|
||||
--with-xkb-config-root=/usr/share/X11/xkb \
|
||||
--with-x-locale-root=/usr/share/X11/locale
|
||||
make -j$(nproc)
|
||||
sudo make install
|
||||
cd ..
|
||||
rm -rf libxkbcommon
|
||||
|
||||
- name: Qt 5.12.8 cache.
|
||||
id: cache-qt
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/qt-cache
|
||||
key: ${{ runner.OS }}-qt-${{ env.CACHE_KEY }}-${{ hashFiles('**/qtbase_5_12_8.diff') }}
|
||||
key: ${{ runner.OS }}-qt-${{ env.CACHE_KEY }}-${{ hashFiles('**/qt*_5_12_8/*') }}
|
||||
- name: Qt 5.12.8 build.
|
||||
if: steps.cache-qt.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
@@ -415,11 +423,14 @@ jobs:
|
||||
|
||||
git clone -b v5.12.8 --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
|
||||
perl init-repository --module-subset=qtbase,qtwayland,qtimageformats,qtsvg,qtx11extras
|
||||
git submodule update qtbase qtwayland qtimageformats qtsvg qtx11extras
|
||||
cd qtbase
|
||||
git apply ../../patches/qtbase_${QT}.diff
|
||||
cd ../
|
||||
find ../../patches/qtbase_${QT} -type f -print0 | sort -z | xargs -r0 git apply
|
||||
cd ..
|
||||
cd qtwayland
|
||||
find ../../patches/qtwayland_${QT} -type f -print0 | sort -z | xargs -r0 git apply
|
||||
cd ..
|
||||
|
||||
./configure -prefix "$QT_PREFIX" \
|
||||
-release \
|
||||
|
||||
3
.github/workflows/mac.yml
vendored
3
.github/workflows/mac.yml
vendored
@@ -88,6 +88,7 @@ jobs:
|
||||
echo $MIN_MAC >> CACHE_KEY.txt
|
||||
echo $PREFIX >> CACHE_KEY.txt
|
||||
echo $MANUAL_CACHING >> CACHE_KEY.txt
|
||||
echo "$GITHUB_WORKSPACE" >> CACHE_KEY.txt
|
||||
if [ "$AUTO_CACHING" == "1" ]; then
|
||||
thisFile=$REPO_NAME/.github/workflows/mac.yml
|
||||
echo `md5 -q $thisFile` >> CACHE_KEY.txt
|
||||
@@ -423,7 +424,7 @@ jobs:
|
||||
git submodule update qtbase
|
||||
git submodule update qtimageformats
|
||||
cd qtbase
|
||||
git apply ../../patches/qtbase_$QT.diff
|
||||
find ../../patches/qtbase_$QT -type f -print0 | sort -z | xargs -0 git apply
|
||||
cd ..
|
||||
|
||||
./configure \
|
||||
|
||||
10
.github/workflows/master_updater.yml
vendored
10
.github/workflows/master_updater.yml
vendored
@@ -12,9 +12,17 @@ jobs:
|
||||
to_branch: "master"
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
if: env.SKIP == '0'
|
||||
- name: Push the code to the master branch.
|
||||
if: env.SKIP == '0'
|
||||
run: |
|
||||
url=https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY
|
||||
token=${{ secrets.TOKEN_FOR_MASTER_UPDATER }}
|
||||
if [ -z "${token}" ]; then
|
||||
echo "Token is unset. Nothing to do."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
url=https://x-access-token:$token@github.com/$GITHUB_REPOSITORY
|
||||
latest_tag=$(git describe --tags --abbrev=0)
|
||||
echo "Latest tag: $latest_tag"
|
||||
|
||||
|
||||
52
.github/workflows/snap.yml
vendored
52
.github/workflows/snap.yml
vendored
@@ -48,8 +48,6 @@ jobs:
|
||||
|
||||
env:
|
||||
UPLOAD_ARTIFACT: "false"
|
||||
ONLY_CACHE: "false"
|
||||
MANUAL_CACHING: "5"
|
||||
|
||||
steps:
|
||||
- name: Clone.
|
||||
@@ -59,56 +57,17 @@ jobs:
|
||||
|
||||
- name: First set up.
|
||||
run: |
|
||||
# Workaround for permanent problems with third-party repository keys
|
||||
sudo rm -rf /etc/apt/sources.list.d/*
|
||||
|
||||
sudo apt-get update
|
||||
sudo apt-get install gcc-8 g++-8 -y
|
||||
sudo snap install --classic snapcraft
|
||||
|
||||
# Workaround for snapcraft
|
||||
# See https://forum.snapcraft.io/t/13258
|
||||
sudo chown root:root /
|
||||
|
||||
md5() {
|
||||
md5cache=$(md5sum $1.txt | cut -c -32)
|
||||
echo ::set-env name=$1::$md5cache
|
||||
}
|
||||
keyFor() {
|
||||
keyName="${1^^}_CACHE_KEY"
|
||||
awk -v RS="" -v ORS="\n\n" '/^ '"$1"':/' snap/snapcraft.yaml > $keyName.txt
|
||||
md5 $keyName
|
||||
}
|
||||
|
||||
snap run snapcraft --version > CACHE_KEY.txt
|
||||
gcc-8 --version >> CACHE_KEY.txt
|
||||
echo $MANUAL_CACHING >> CACHE_KEY.txt
|
||||
md5 CACHE_KEY
|
||||
|
||||
keyFor cmake
|
||||
keyFor ffmpeg
|
||||
|
||||
- name: CMake cache.
|
||||
id: cache-cmake
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: parts/cmake
|
||||
key: ${{ runner.OS }}-cmake-${{ env.CACHE_KEY }}-${{ env.CMAKE_CACHE_KEY }}
|
||||
|
||||
- name: CMake build.
|
||||
if: steps.cache-cmake.outputs.cache-hit != 'true'
|
||||
run: sudo snap run snapcraft build --destructive-mode cmake
|
||||
|
||||
- name: FFmpeg cache.
|
||||
id: cache-ffmpeg
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: parts/ffmpeg
|
||||
key: ${{ runner.OS }}-ffmpeg-${{ env.CACHE_KEY }}-${{ env.FFMPEG_CACHE_KEY }}
|
||||
|
||||
- name: FFmpeg build.
|
||||
if: steps.cache-ffmpeg.outputs.cache-hit != 'true'
|
||||
run: sudo snap run snapcraft build --destructive-mode ffmpeg
|
||||
|
||||
- name: Telegram Desktop snap build.
|
||||
if: env.ONLY_CACHE == 'false'
|
||||
run: sudo snap run snapcraft --destructive-mode
|
||||
|
||||
- name: Move artifact.
|
||||
@@ -126,8 +85,3 @@ jobs:
|
||||
with:
|
||||
name: ${{ env.ARTIFACT_NAME }}
|
||||
path: artifact
|
||||
|
||||
- name: Remove unneeded directories for cache.
|
||||
run: |
|
||||
sudo rm -rf parts/*/{build,src,ubuntu}
|
||||
sudo rm -rf parts/*/state/{stage,prime}
|
||||
|
||||
2
.github/workflows/win.yml
vendored
2
.github/workflows/win.yml
vendored
@@ -308,7 +308,7 @@ jobs:
|
||||
git submodule update qtbase
|
||||
git submodule update qtimageformats
|
||||
cd qtbase
|
||||
git apply ../../patches/qtbase_%QT%.diff
|
||||
for /r %%i in (..\..\patches\qtbase_%QT%\*) do git apply %%i
|
||||
cd ..
|
||||
|
||||
SET SSL=%LibrariesPath%\openssl_1_1_1
|
||||
|
||||
5
.gitmodules
vendored
5
.gitmodules
vendored
@@ -3,7 +3,7 @@
|
||||
url = https://github.com/telegramdesktop/libtgvoip
|
||||
[submodule "Telegram/ThirdParty/variant"]
|
||||
path = Telegram/ThirdParty/variant
|
||||
url = https://github.com/mapbox/variant
|
||||
url = https://github.com/desktop-app/variant.git
|
||||
[submodule "Telegram/ThirdParty/GSL"]
|
||||
path = Telegram/ThirdParty/GSL
|
||||
url = https://github.com/Microsoft/GSL.git
|
||||
@@ -91,3 +91,6 @@
|
||||
[submodule "Telegram/ThirdParty/libqtxdg"]
|
||||
path = Telegram/ThirdParty/libqtxdg
|
||||
url = https://github.com/lxqt/libqtxdg.git
|
||||
[submodule "Telegram/ThirdParty/fcitx5-qt"]
|
||||
path = Telegram/ThirdParty/fcitx5-qt
|
||||
url = https://github.com/fcitx/fcitx5-qt.git
|
||||
|
||||
@@ -91,6 +91,7 @@ if (LINUX)
|
||||
desktop-app::external_statusnotifieritem
|
||||
desktop-app::external_dbusmenu_qt
|
||||
desktop-app::external_fcitx_qt5
|
||||
desktop-app::external_fcitx5_qt5
|
||||
desktop-app::external_hime_qt
|
||||
)
|
||||
endif()
|
||||
@@ -138,7 +139,7 @@ endif()
|
||||
|
||||
if (DESKTOP_APP_USE_PACKAGED)
|
||||
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
|
||||
find_package(Threads)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
@@ -263,6 +264,9 @@ PRIVATE
|
||||
calls/calls_box_controller.h
|
||||
calls/calls_call.cpp
|
||||
calls/calls_call.h
|
||||
calls/calls_controller.cpp
|
||||
calls/calls_controller.h
|
||||
calls/calls_controller_tgvoip.h
|
||||
calls/calls_emoji_fingerprint.cpp
|
||||
calls/calls_emoji_fingerprint.h
|
||||
calls/calls_instance.cpp
|
||||
@@ -297,6 +301,8 @@ PRIVATE
|
||||
chat_helpers/stickers_dice_pack.h
|
||||
chat_helpers/stickers_list_widget.cpp
|
||||
chat_helpers/stickers_list_widget.h
|
||||
chat_helpers/stickers_set.cpp
|
||||
chat_helpers/stickers_set.h
|
||||
chat_helpers/tabbed_panel.cpp
|
||||
chat_helpers/tabbed_panel.h
|
||||
chat_helpers/tabbed_section.cpp
|
||||
@@ -325,7 +331,6 @@ PRIVATE
|
||||
core/launcher.h
|
||||
core/local_url_handlers.cpp
|
||||
core/local_url_handlers.h
|
||||
core/media_active_cache.h
|
||||
core/mime_type.cpp
|
||||
core/mime_type.h
|
||||
core/sandbox.cpp
|
||||
@@ -351,14 +356,16 @@ PRIVATE
|
||||
data/data_channel.h
|
||||
data/data_channel_admins.cpp
|
||||
data/data_channel_admins.h
|
||||
data/data_cloud_file.cpp
|
||||
data/data_cloud_file.h
|
||||
data/data_cloud_themes.cpp
|
||||
data/data_cloud_themes.h
|
||||
data/data_countries.cpp
|
||||
data/data_countries.h
|
||||
data/data_document.cpp
|
||||
data/data_document.h
|
||||
data/data_document_good_thumbnail.cpp
|
||||
data/data_document_good_thumbnail.h
|
||||
data/data_document_media.cpp
|
||||
data/data_document_media.h
|
||||
data/data_drafts.cpp
|
||||
data/data_drafts.h
|
||||
data/data_folder.cpp
|
||||
@@ -389,10 +396,14 @@ PRIVATE
|
||||
data/data_peer_values.h
|
||||
data/data_photo.cpp
|
||||
data/data_photo.h
|
||||
data/data_photo_media.cpp
|
||||
data/data_photo_media.h
|
||||
data/data_poll.cpp
|
||||
data/data_poll.h
|
||||
data/data_pts_waiter.cpp
|
||||
data/data_pts_waiter.h
|
||||
data/data_reply_preview.cpp
|
||||
data/data_reply_preview.h
|
||||
data/data_search_controller.cpp
|
||||
data/data_search_controller.h
|
||||
data/data_session.cpp
|
||||
@@ -753,6 +764,7 @@ PRIVATE
|
||||
mtproto/type_utils.h
|
||||
overview/overview_layout.cpp
|
||||
overview/overview_layout.h
|
||||
overview/overview_layout_delegate.h
|
||||
passport/passport_encryption.cpp
|
||||
passport/passport_encryption.h
|
||||
passport/passport_form_controller.cpp
|
||||
@@ -795,7 +807,6 @@ PRIVATE
|
||||
platform/mac/file_utilities_mac.h
|
||||
platform/mac/launcher_mac.mm
|
||||
platform/mac/launcher_mac.h
|
||||
platform/mac/mac_iconv_helper.c
|
||||
platform/mac/main_window_mac.mm
|
||||
platform/mac/main_window_mac.h
|
||||
platform/mac/notifications_manager_mac.mm
|
||||
@@ -922,8 +933,8 @@ PRIVATE
|
||||
ui/image/image.h
|
||||
ui/image/image_location.cpp
|
||||
ui/image/image_location.h
|
||||
ui/image/image_source.cpp
|
||||
ui/image/image_source.h
|
||||
ui/image/image_location_factory.cpp
|
||||
ui/image/image_location_factory.h
|
||||
ui/widgets/continuous_sliders.cpp
|
||||
ui/widgets/continuous_sliders.h
|
||||
ui/widgets/discrete_sliders.cpp
|
||||
@@ -1033,6 +1044,8 @@ PRIVATE
|
||||
|
||||
if (DESKTOP_APP_USE_PACKAGED)
|
||||
nice_target_sources(Telegram ${src_loc} PRIVATE qt_functions.cpp)
|
||||
else()
|
||||
nice_target_sources(Telegram ${src_loc} PRIVATE platform/mac/mac_iconv_helper.c)
|
||||
endif()
|
||||
|
||||
nice_target_sources(Telegram ${res_loc}
|
||||
@@ -1068,11 +1081,11 @@ if (WIN32)
|
||||
# $<IF:${release},"Appending compatibility manifest.","Finalizing build.">
|
||||
# )
|
||||
elseif (APPLE)
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
desktop-app::external_sp_media_key_tap
|
||||
desktop-app::external_iconv
|
||||
)
|
||||
target_link_libraries(Telegram PRIVATE desktop-app::external_sp_media_key_tap)
|
||||
|
||||
if (NOT DESKTOP_APP_USE_PACKAGED)
|
||||
target_link_libraries(Telegram PRIVATE desktop-app::external_iconv)
|
||||
endif()
|
||||
|
||||
set(icons_path ${CMAKE_CURRENT_SOURCE_DIR}/Telegram/Images.xcassets)
|
||||
set_target_properties(Telegram PROPERTIES RESOURCE ${icons_path})
|
||||
@@ -1212,6 +1225,10 @@ if ((NOT DESKTOP_APP_DISABLE_AUTOUPDATE OR NOT LINUX) AND NOT build_macstore AND
|
||||
|
||||
set_target_properties(Updater PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_folder})
|
||||
|
||||
if (WIN32 AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
target_link_options(Updater PRIVATE -municode)
|
||||
endif()
|
||||
|
||||
if (LINUX)
|
||||
target_link_options(Updater PRIVATE -static-libstdc++)
|
||||
endif()
|
||||
|
||||
@@ -300,6 +300,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_notifications_position" = "Location on the screen";
|
||||
"lng_settings_notifications_count" = "Notifications count";
|
||||
"lng_settings_sound_notify" = "Play sound";
|
||||
"lng_settings_alert_windows" = "Flash the taskbar icon";
|
||||
"lng_settings_alert_mac" = "Bounce the dock icon";
|
||||
"lng_settings_alert_linux" = "Draw attention to the window";
|
||||
"lng_settings_badge_title" = "Badge counter";
|
||||
"lng_settings_include_muted" = "Include muted chats in unread count";
|
||||
"lng_settings_count_unread" = "Count unread messages";
|
||||
@@ -1451,6 +1454,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_context_edit_msg" = "Edit";
|
||||
"lng_context_forward_msg" = "Forward Message";
|
||||
"lng_context_send_now_msg" = "Send now";
|
||||
"lng_context_reschedule" = "Reschedule";
|
||||
"lng_context_delete_msg" = "Delete Message";
|
||||
"lng_context_select_msg" = "Select Message";
|
||||
"lng_context_report_msg" = "Report Message";
|
||||
@@ -2146,6 +2150,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_export_option_size_limit" = "Size limit: {size}";
|
||||
"lng_export_header_format" = "Location and format";
|
||||
"lng_export_option_location" = "Download path: {path}";
|
||||
"lng_export_option_format_location" = "Format: {format}, Path: {path}";
|
||||
"lng_export_option_choose_format" = "Choose export format";
|
||||
"lng_export_option_html" = "Human-readable HTML";
|
||||
"lng_export_option_json" = "Machine-readable JSON";
|
||||
"lng_export_limits" = "From: {from}, to: {till}";
|
||||
@@ -2245,6 +2251,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_polls_votes_collapse" = "Collapse";
|
||||
|
||||
"lng_outdated_title" = "PLEASE UPDATE YOUR OPERATING SYSTEM.";
|
||||
"lng_outdated_title_bits" = "PLEASE SWITCH TO 64 BIT OPERATING SYSTEM.";
|
||||
"lng_outdated_soon" = "Otherwise, Telegram Desktop will stop updating on {date}.";
|
||||
"lng_outdated_now" = "So that Telegram Desktop can update to newer versions.";
|
||||
|
||||
|
||||
@@ -357,6 +357,7 @@ updateMessagePollVote#42f88f2c poll_id:long user_id:int options:Vector<bytes> =
|
||||
updateDialogFilter#26ffde7d flags:# id:int filter:flags.0?DialogFilter = Update;
|
||||
updateDialogFilterOrder#a5d72105 order:Vector<int> = Update;
|
||||
updateDialogFilters#3504914f = Update;
|
||||
updatePhoneCallSignalingData#2661bf09 phone_call_id:long data:bytes = Update;
|
||||
|
||||
updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
|
||||
|
||||
@@ -421,7 +422,7 @@ inputDocumentEmpty#72f0eaae = InputDocument;
|
||||
inputDocument#1abfb575 id:long access_hash:long file_reference:bytes = InputDocument;
|
||||
|
||||
documentEmpty#36f8c871 id:long = Document;
|
||||
document#9ba29cc1 flags:# id:long access_hash:long file_reference:bytes date:int mime_type:string size:int thumbs:flags.0?Vector<PhotoSize> dc_id:int attributes:Vector<DocumentAttribute> = Document;
|
||||
document#1e87342b flags:# id:long access_hash:long file_reference:bytes date:int mime_type:string size:int thumbs:flags.0?Vector<PhotoSize> video_thumbs:flags.1?Vector<VideoSize> dc_id:int attributes:Vector<DocumentAttribute> = Document;
|
||||
|
||||
help.support#17c6b5f6 phone_number:string user:User = help.Support;
|
||||
|
||||
@@ -1140,6 +1141,8 @@ stats.broadcastStats#bdf78394 period:StatsDateRangeDays followers:StatsAbsValueA
|
||||
help.promoDataEmpty#98f6ac75 expires:int = help.PromoData;
|
||||
help.promoData#8c39793f flags:# proxy:flags.0?true expires:int peer:Peer chats:Vector<Chat> users:Vector<User> psa_type:flags.1?string psa_message:flags.2?string = help.PromoData;
|
||||
|
||||
videoSize#435bb987 type:string location:FileLocation w:int h:int size:int = VideoSize;
|
||||
|
||||
---functions---
|
||||
|
||||
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
|
||||
@@ -1485,6 +1488,7 @@ phone.receivedCall#17d54f61 peer:InputPhoneCall = Bool;
|
||||
phone.discardCall#b2cbc1c0 flags:# video:flags.0?true peer:InputPhoneCall duration:int reason:PhoneCallDiscardReason connection_id:long = Updates;
|
||||
phone.setCallRating#59ead627 flags:# user_initiative:flags.0?true peer:InputPhoneCall rating:int comment:string = Updates;
|
||||
phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool;
|
||||
phone.sendSignalingData#ff7a9383 peer:InputPhoneCall data:bytes = Bool;
|
||||
|
||||
langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDifference;
|
||||
langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector<string> = Vector<LangPackString>;
|
||||
@@ -1498,4 +1502,4 @@ folders.deleteFolder#1c295881 folder_id:int = Updates;
|
||||
stats.getBroadcastStats#ab42441a flags:# dark:flags.0?true channel:InputChannel = stats.BroadcastStats;
|
||||
stats.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph;
|
||||
|
||||
// LAYER 113
|
||||
// LAYER 114
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="2.1.4.0" />
|
||||
Version="2.1.10.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram FZ-LLC</PublisherDisplayName>
|
||||
|
||||
@@ -6,7 +6,18 @@
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 2 resource.
|
||||
//
|
||||
#include "winres.h"
|
||||
#if defined(__MINGW64__) || defined(__MINGW32__)
|
||||
// MinGW-w64, MinGW
|
||||
#if defined(__has_include) && __has_include(<winres.h>)
|
||||
#include <winres.h>
|
||||
#else
|
||||
#include <afxres.h>
|
||||
#include <winresrc.h>
|
||||
#endif
|
||||
#else
|
||||
// MSVC, Windows SDK
|
||||
#include <winres.h>
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#undef APSTUDIO_READONLY_SYMBOLS
|
||||
@@ -33,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2,1,4,0
|
||||
PRODUCTVERSION 2,1,4,0
|
||||
FILEVERSION 2,1,10,0
|
||||
PRODUCTVERSION 2,1,10,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -51,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "2.1.4.0"
|
||||
VALUE "FileVersion", "2.1.10.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2020"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.1.4.0"
|
||||
VALUE "ProductVersion", "2.1.10.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -6,7 +6,18 @@
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 2 resource.
|
||||
//
|
||||
#include "winres.h"
|
||||
#if defined(__MINGW64__) || defined(__MINGW32__)
|
||||
// MinGW-w64, MinGW
|
||||
#if defined(__has_include) && __has_include(<winres.h>)
|
||||
#include <winres.h>
|
||||
#else
|
||||
#include <afxres.h>
|
||||
#include <winresrc.h>
|
||||
#endif
|
||||
#else
|
||||
// MSVC, Windows SDK
|
||||
#include <winres.h>
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#undef APSTUDIO_READONLY_SYMBOLS
|
||||
@@ -24,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2,1,4,0
|
||||
PRODUCTVERSION 2,1,4,0
|
||||
FILEVERSION 2,1,10,0
|
||||
PRODUCTVERSION 2,1,10,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -42,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "2.1.4.0"
|
||||
VALUE "FileVersion", "2.1.10.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2020"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "2.1.4.0"
|
||||
VALUE "ProductVersion", "2.1.10.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -268,7 +268,7 @@ int main(int argc, char *argv[])
|
||||
cout << "Compression start, size: " << resultSize << "\n";
|
||||
|
||||
QByteArray compressed, resultCheck;
|
||||
#ifdef Q_OS_WIN // use Lzma SDK for win
|
||||
#if defined Q_OS_WIN && !defined DESKTOP_APP_USE_PACKAGED // use Lzma SDK for win
|
||||
const int32 hSigLen = 128, hShaLen = 20, hPropsLen = LZMA_PROPS_SIZE, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hPropsLen + hOriginalSizeLen; // header
|
||||
|
||||
compressed.resize(hSize + resultSize + 1024 * 1024); // rsa signature + sha1 + lzma props + max compressed size
|
||||
|
||||
@@ -27,7 +27,7 @@ extern "C" {
|
||||
#include <openssl/evp.h>
|
||||
} // extern "C"
|
||||
|
||||
#ifdef Q_OS_WIN // use Lzma SDK for win
|
||||
#if defined Q_OS_WIN && !defined DESKTOP_APP_USE_PACKAGED // use Lzma SDK for win
|
||||
#include <LzmaLib.h>
|
||||
#else
|
||||
#include <lzma.h>
|
||||
|
||||
@@ -90,7 +90,7 @@ int main(int argc, const char * argv[]) {
|
||||
|
||||
openLog();
|
||||
pid_t procId = 0;
|
||||
BOOL update = YES, toSettings = NO, autoStart = NO, startInTray = NO, testMode = NO, externalUpdater = NO;
|
||||
BOOL update = YES, toSettings = NO, autoStart = NO, startInTray = NO, testMode = NO, freeType = NO, externalUpdater = NO;
|
||||
BOOL customWorkingDir = NO;
|
||||
NSString *key = nil;
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
@@ -116,6 +116,8 @@ int main(int argc, const char * argv[]) {
|
||||
startInTray = YES;
|
||||
} else if ([@"-testmode" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
|
||||
testMode = YES;
|
||||
} else if ([@"-freetype" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
|
||||
freeType = YES;
|
||||
} else if ([@"-externalupdater" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
|
||||
externalUpdater = YES;
|
||||
} else if ([@"-workdir_custom" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
|
||||
@@ -255,6 +257,7 @@ int main(int argc, const char * argv[]) {
|
||||
if (_debug) [args addObject:@"-debug"];
|
||||
if (startInTray) [args addObject:@"-startintray"];
|
||||
if (testMode) [args addObject:@"-testmode"];
|
||||
if (freeType) [args addObject:@"-freetype"];
|
||||
if (externalUpdater) [args addObject:@"-externalupdater"];
|
||||
if (autoStart) [args addObject:@"-autostart"];
|
||||
if (key) {
|
||||
|
||||
@@ -339,7 +339,7 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdPara
|
||||
LPWSTR *args;
|
||||
int argsCount;
|
||||
|
||||
bool needupdate = false, autostart = false, debug = false, writeprotected = false, startintray = false, testmode = false, externalupdater = false;
|
||||
bool needupdate = false, autostart = false, debug = false, writeprotected = false, startintray = false, testmode = false, freetype = false, externalupdater = false;
|
||||
args = CommandLineToArgvW(GetCommandLine(), &argsCount);
|
||||
if (args) {
|
||||
for (int i = 1; i < argsCount; ++i) {
|
||||
@@ -355,6 +355,8 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdPara
|
||||
startintray = true;
|
||||
} else if (equal(args[i], L"-testmode")) {
|
||||
testmode = true;
|
||||
} else if (equal(args[i], L"-freetype")) {
|
||||
freetype = true;
|
||||
} else if (equal(args[i], L"-externalupdater")) {
|
||||
externalupdater = true;
|
||||
} else if (equal(args[i], L"-writeprotected") && ++i < argsCount) {
|
||||
@@ -427,6 +429,7 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR cmdPara
|
||||
if (debug) targs += L" -debug";
|
||||
if (startintray) targs += L" -startintray";
|
||||
if (testmode) targs += L" -testmode";
|
||||
if (freetype) targs += L" -freetype";
|
||||
if (externalupdater) targs += L" -externalupdater";
|
||||
if (!customWorkingDir.empty()) {
|
||||
targs += L" -workdir \"" + customWorkingDir + L"\"";
|
||||
|
||||
@@ -30,6 +30,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
void InnerFillMessagePostFlags(
|
||||
const Api::SendOptions &options,
|
||||
not_null<PeerData*> peer,
|
||||
MTPDmessage::Flags &flags) {
|
||||
const auto channelPost = peer->isChannel() && !peer->isMegagroup();
|
||||
if (!channelPost) {
|
||||
flags |= MTPDmessage::Flag::f_from_id;
|
||||
return;
|
||||
}
|
||||
flags |= MTPDmessage::Flag::f_post;
|
||||
// Don't display views and author of a new post when it's scheduled.
|
||||
if (options.scheduled) {
|
||||
return;
|
||||
}
|
||||
flags |= MTPDmessage::Flag::f_views;
|
||||
if (peer->asChannel()->addsSignature()) {
|
||||
flags |= MTPDmessage::Flag::f_post_author;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename MediaData>
|
||||
void SendExistingMedia(
|
||||
Api::MessageToSend &&message,
|
||||
@@ -60,15 +80,7 @@ void SendExistingMedia(
|
||||
const auto channelPost = peer->isChannel() && !peer->isMegagroup();
|
||||
const auto silentPost = message.action.options.silent
|
||||
|| (channelPost && session->data().notifySilentPosts(peer));
|
||||
if (channelPost) {
|
||||
flags |= MTPDmessage::Flag::f_views;
|
||||
flags |= MTPDmessage::Flag::f_post;
|
||||
}
|
||||
if (!channelPost) {
|
||||
flags |= MTPDmessage::Flag::f_from_id;
|
||||
} else if (peer->asChannel()->addsSignature()) {
|
||||
flags |= MTPDmessage::Flag::f_post_author;
|
||||
}
|
||||
InnerFillMessagePostFlags(message.action.options, peer, flags);
|
||||
if (silentPost) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
|
||||
}
|
||||
@@ -246,15 +258,7 @@ bool SendDice(Api::MessageToSend &message) {
|
||||
const auto channelPost = peer->isChannel() && !peer->isMegagroup();
|
||||
const auto silentPost = message.action.options.silent
|
||||
|| (channelPost && session->data().notifySilentPosts(peer));
|
||||
if (channelPost) {
|
||||
flags |= MTPDmessage::Flag::f_views;
|
||||
flags |= MTPDmessage::Flag::f_post;
|
||||
}
|
||||
if (!channelPost) {
|
||||
flags |= MTPDmessage::Flag::f_from_id;
|
||||
} else if (peer->asChannel()->addsSignature()) {
|
||||
flags |= MTPDmessage::Flag::f_post_author;
|
||||
}
|
||||
InnerFillMessagePostFlags(message.action.options, peer, flags);
|
||||
if (silentPost) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
|
||||
}
|
||||
@@ -320,4 +324,11 @@ bool SendDice(Api::MessageToSend &message) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void FillMessagePostFlags(
|
||||
const Api::SendAction &action,
|
||||
not_null<PeerData*> peer,
|
||||
MTPDmessage::Flags &flags) {
|
||||
InnerFillMessagePostFlags(action.options, peer, flags);
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -25,4 +25,9 @@ void SendExistingPhoto(
|
||||
|
||||
bool SendDice(Api::MessageToSend &message);
|
||||
|
||||
void FillMessagePostFlags(
|
||||
const SendAction &action,
|
||||
not_null<PeerData*> peer,
|
||||
MTPDmessage::Flags &flags);
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -1865,7 +1865,7 @@ void ApiWrap::saveStickerSets(
|
||||
auto &sets = _session->data().stickerSetsRef();
|
||||
|
||||
_stickersOrder = localOrder;
|
||||
for_const (auto removedSetId, localRemoved) {
|
||||
for (const auto removedSetId : localRemoved) {
|
||||
if (removedSetId == Stickers::CloudRecentSetId) {
|
||||
if (sets.remove(Stickers::CloudRecentSetId) != 0) {
|
||||
writeCloudRecent = true;
|
||||
@@ -1890,16 +1890,17 @@ void ApiWrap::saveStickerSets(
|
||||
|
||||
auto it = sets.find(removedSetId);
|
||||
if (it != sets.cend()) {
|
||||
const auto set = it->second.get();
|
||||
for (auto i = recent.begin(); i != recent.cend();) {
|
||||
if (it->stickers.indexOf(i->first) >= 0) {
|
||||
if (set->stickers.indexOf(i->first) >= 0) {
|
||||
i = recent.erase(i);
|
||||
writeRecent = true;
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
if (!(it->flags & MTPDstickerSet::Flag::f_archived)) {
|
||||
MTPInputStickerSet setId = (it->id && it->access) ? MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access)) : MTP_inputStickerSetShortName(MTP_string(it->shortName));
|
||||
if (!(set->flags & MTPDstickerSet::Flag::f_archived)) {
|
||||
const auto setId = set->mtpInput();
|
||||
|
||||
auto requestId = request(MTPmessages_UninstallStickerSet(setId)).done([this](const MTPBool &result, mtpRequestId requestId) {
|
||||
stickerSetDisenabled(requestId);
|
||||
@@ -1909,60 +1910,69 @@ void ApiWrap::saveStickerSets(
|
||||
|
||||
_stickerSetDisenableRequests.insert(requestId);
|
||||
|
||||
int removeIndex = _session->data().stickerSetsOrder().indexOf(it->id);
|
||||
int removeIndex = _session->data().stickerSetsOrder().indexOf(set->id);
|
||||
if (removeIndex >= 0) _session->data().stickerSetsOrderRef().removeAt(removeIndex);
|
||||
if (!(it->flags & MTPDstickerSet_ClientFlag::f_featured)
|
||||
&& !(it->flags & MTPDstickerSet_ClientFlag::f_special)) {
|
||||
if (!(set->flags & MTPDstickerSet_ClientFlag::f_featured)
|
||||
&& !(set->flags & MTPDstickerSet_ClientFlag::f_special)) {
|
||||
sets.erase(it);
|
||||
} else {
|
||||
if (it->flags & MTPDstickerSet::Flag::f_archived) {
|
||||
if (set->flags & MTPDstickerSet::Flag::f_archived) {
|
||||
writeArchived = true;
|
||||
}
|
||||
it->flags &= ~(MTPDstickerSet::Flag::f_installed_date | MTPDstickerSet::Flag::f_archived);
|
||||
it->installDate = TimeId(0);
|
||||
set->flags &= ~(MTPDstickerSet::Flag::f_installed_date | MTPDstickerSet::Flag::f_archived);
|
||||
set->installDate = TimeId(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear all installed flags, set only for sets from order.
|
||||
for (auto &set : sets) {
|
||||
if (!(set.flags & MTPDstickerSet::Flag::f_archived)) {
|
||||
set.flags &= ~MTPDstickerSet::Flag::f_installed_date;
|
||||
for (auto &[id, set] : sets) {
|
||||
if (!(set->flags & MTPDstickerSet::Flag::f_archived)) {
|
||||
set->flags &= ~MTPDstickerSet::Flag::f_installed_date;
|
||||
}
|
||||
}
|
||||
|
||||
auto &order = _session->data().stickerSetsOrderRef();
|
||||
order.clear();
|
||||
for_const (auto setId, _stickersOrder) {
|
||||
for (const auto setId : std::as_const(_stickersOrder)) {
|
||||
auto it = sets.find(setId);
|
||||
if (it != sets.cend()) {
|
||||
if ((it->flags & MTPDstickerSet::Flag::f_archived) && !localRemoved.contains(it->id)) {
|
||||
MTPInputStickerSet mtpSetId = (it->id && it->access) ? MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access)) : MTP_inputStickerSetShortName(MTP_string(it->shortName));
|
||||
const auto set = it->second.get();
|
||||
if ((set->flags & MTPDstickerSet::Flag::f_archived) && !localRemoved.contains(set->id)) {
|
||||
const auto mtpSetId = set->mtpInput();
|
||||
|
||||
auto requestId = request(MTPmessages_InstallStickerSet(mtpSetId, MTP_boolFalse())).done([this](const MTPmessages_StickerSetInstallResult &result, mtpRequestId requestId) {
|
||||
const auto requestId = request(MTPmessages_InstallStickerSet(
|
||||
mtpSetId,
|
||||
MTP_boolFalse()
|
||||
)).done([=](
|
||||
const MTPmessages_StickerSetInstallResult &result,
|
||||
mtpRequestId requestId) {
|
||||
stickerSetDisenabled(requestId);
|
||||
}).fail([this](const RPCError &error, mtpRequestId requestId) {
|
||||
}).fail([=](
|
||||
const RPCError &error,
|
||||
mtpRequestId requestId) {
|
||||
stickerSetDisenabled(requestId);
|
||||
}).afterDelay(kSmallDelayMs).send();
|
||||
|
||||
_stickerSetDisenableRequests.insert(requestId);
|
||||
|
||||
it->flags &= ~MTPDstickerSet::Flag::f_archived;
|
||||
set->flags &= ~MTPDstickerSet::Flag::f_archived;
|
||||
writeArchived = true;
|
||||
}
|
||||
order.push_back(setId);
|
||||
it->flags |= MTPDstickerSet::Flag::f_installed_date;
|
||||
if (!it->installDate) {
|
||||
it->installDate = base::unixtime::now();
|
||||
set->flags |= MTPDstickerSet::Flag::f_installed_date;
|
||||
if (!set->installDate) {
|
||||
set->installDate = base::unixtime::now();
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto it = sets.begin(); it != sets.cend();) {
|
||||
if ((it->flags & MTPDstickerSet_ClientFlag::f_featured)
|
||||
|| (it->flags & MTPDstickerSet::Flag::f_installed_date)
|
||||
|| (it->flags & MTPDstickerSet::Flag::f_archived)
|
||||
|| (it->flags & MTPDstickerSet_ClientFlag::f_special)) {
|
||||
const auto set = it->second.get();
|
||||
if ((set->flags & MTPDstickerSet_ClientFlag::f_featured)
|
||||
|| (set->flags & MTPDstickerSet::Flag::f_installed_date)
|
||||
|| (set->flags & MTPDstickerSet::Flag::f_archived)
|
||||
|| (set->flags & MTPDstickerSet_ClientFlag::f_special)) {
|
||||
++it;
|
||||
} else {
|
||||
it = sets.erase(it);
|
||||
@@ -2623,7 +2633,9 @@ void ApiWrap::resolveWebPages() {
|
||||
if (!ids.isEmpty()) {
|
||||
requestId = request(MTPmessages_GetMessages(
|
||||
MTP_vector<MTPInputMessage>(ids)
|
||||
)).done([=](const MTPmessages_Messages &result, mtpRequestId requestId) {
|
||||
)).done([=](
|
||||
const MTPmessages_Messages &result,
|
||||
mtpRequestId requestId) {
|
||||
gotWebPages(nullptr, result, requestId);
|
||||
}).afterDelay(kSmallDelayMs).send();
|
||||
}
|
||||
@@ -2632,7 +2644,9 @@ void ApiWrap::resolveWebPages() {
|
||||
reqsByIndex[i.value().first] = request(MTPchannels_GetMessages(
|
||||
i.key()->inputChannel,
|
||||
MTP_vector<MTPInputMessage>(i.value().second)
|
||||
)).done([=, channel = i.key()](const MTPmessages_Messages &result, mtpRequestId requestId) {
|
||||
)).done([=, channel = i.key()](
|
||||
const MTPmessages_Messages &result,
|
||||
mtpRequestId requestId) {
|
||||
gotWebPages(channel, result, requestId);
|
||||
}).afterDelay(kSmallDelayMs).send();
|
||||
}
|
||||
@@ -2947,58 +2961,8 @@ void ApiWrap::refreshFileReference(
|
||||
});
|
||||
}
|
||||
|
||||
void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &msgs, mtpRequestId req) {
|
||||
const QVector<MTPMessage> *v = 0;
|
||||
switch (msgs.type()) {
|
||||
case mtpc_messages_messages: {
|
||||
auto &d = msgs.c_messages_messages();
|
||||
_session->data().processUsers(d.vusers());
|
||||
_session->data().processChats(d.vchats());
|
||||
v = &d.vmessages().v;
|
||||
} break;
|
||||
|
||||
case mtpc_messages_messagesSlice: {
|
||||
auto &d = msgs.c_messages_messagesSlice();
|
||||
_session->data().processUsers(d.vusers());
|
||||
_session->data().processChats(d.vchats());
|
||||
v = &d.vmessages().v;
|
||||
} break;
|
||||
|
||||
case mtpc_messages_channelMessages: {
|
||||
auto &d = msgs.c_messages_channelMessages();
|
||||
if (channel) {
|
||||
channel->ptsReceived(d.vpts().v);
|
||||
} else {
|
||||
LOG(("API Error: received messages.channelMessages when no channel was passed! (ApiWrap::gotWebPages)"));
|
||||
}
|
||||
_session->data().processUsers(d.vusers());
|
||||
_session->data().processChats(d.vchats());
|
||||
v = &d.vmessages().v;
|
||||
} break;
|
||||
|
||||
case mtpc_messages_messagesNotModified: {
|
||||
LOG(("API Error: received messages.messagesNotModified! (ApiWrap::gotWebPages)"));
|
||||
} break;
|
||||
}
|
||||
|
||||
if (!v) return;
|
||||
|
||||
auto indices = base::flat_map<uint64, int>(); // copied from feedMsgs
|
||||
for (auto i = 0, l = v->size(); i != l; ++i) {
|
||||
const auto msgId = IdFromMessage(v->at(i));
|
||||
indices.emplace((uint64(uint32(msgId)) << 32) | uint64(i), i);
|
||||
}
|
||||
|
||||
for (const auto &[position, index] : indices) {
|
||||
const auto item = _session->data().addNewMessage(
|
||||
v->at(index),
|
||||
MTPDmessage_ClientFlags(),
|
||||
NewMessageType::Existing);
|
||||
if (item) {
|
||||
_session->data().requestItemResize(item);
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &result, mtpRequestId req) {
|
||||
WebPageData::ApplyChanges(&session(), channel, result);
|
||||
for (auto i = _webPagesPending.begin(); i != _webPagesPending.cend();) {
|
||||
if (i.value() == req) {
|
||||
if (i.key()->pendingTill > 0) {
|
||||
@@ -3334,14 +3298,14 @@ void ApiWrap::readFeaturedSetDelayed(uint64 setId) {
|
||||
}
|
||||
|
||||
void ApiWrap::readFeaturedSets() {
|
||||
auto &sets = _session->data().stickerSetsRef();
|
||||
const auto &sets = _session->data().stickerSets();
|
||||
auto count = _session->data().featuredStickerSetsUnreadCount();
|
||||
QVector<MTPlong> wrappedIds;
|
||||
wrappedIds.reserve(_featuredSetsRead.size());
|
||||
for (auto setId : _featuredSetsRead) {
|
||||
auto it = sets.find(setId);
|
||||
for (const auto setId : _featuredSetsRead) {
|
||||
const auto it = sets.find(setId);
|
||||
if (it != sets.cend()) {
|
||||
it->flags &= ~MTPDstickerSet_ClientFlag::f_unread;
|
||||
it->second->flags &= ~MTPDstickerSet_ClientFlag::f_unread;
|
||||
wrappedIds.append(MTP_long(setId));
|
||||
if (count) {
|
||||
--count;
|
||||
@@ -4328,15 +4292,7 @@ void ApiWrap::forwardMessages(
|
||||
auto flags = MTPDmessage::Flags(0);
|
||||
auto clientFlags = MTPDmessage_ClientFlags();
|
||||
auto sendFlags = MTPmessages_ForwardMessages::Flags(0);
|
||||
if (channelPost) {
|
||||
flags |= MTPDmessage::Flag::f_views;
|
||||
flags |= MTPDmessage::Flag::f_post;
|
||||
}
|
||||
if (!channelPost) {
|
||||
flags |= MTPDmessage::Flag::f_from_id;
|
||||
} else if (peer->asChannel()->addsSignature()) {
|
||||
flags |= MTPDmessage::Flag::f_post_author;
|
||||
}
|
||||
FillMessagePostFlags(action, peer, flags);
|
||||
if (silentPost) {
|
||||
sendFlags |= MTPmessages_ForwardMessages::Flag::f_silent;
|
||||
}
|
||||
@@ -4489,15 +4445,7 @@ void ApiWrap::sendSharedContact(
|
||||
if (action.replyTo) {
|
||||
flags |= MTPDmessage::Flag::f_reply_to_msg_id;
|
||||
}
|
||||
if (channelPost) {
|
||||
flags |= MTPDmessage::Flag::f_views;
|
||||
flags |= MTPDmessage::Flag::f_post;
|
||||
if (peer->asChannel()->addsSignature()) {
|
||||
flags |= MTPDmessage::Flag::f_post_author;
|
||||
}
|
||||
} else {
|
||||
flags |= MTPDmessage::Flag::f_from_id;
|
||||
}
|
||||
FillMessagePostFlags(action, peer, flags);
|
||||
if (action.options.scheduled) {
|
||||
flags |= MTPDmessage::Flag::f_from_scheduled;
|
||||
} else {
|
||||
@@ -4874,15 +4822,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
const auto channelPost = peer->isChannel() && !peer->isMegagroup();
|
||||
const auto silentPost = action.options.silent
|
||||
|| (channelPost && _session->data().notifySilentPosts(peer));
|
||||
if (channelPost) {
|
||||
flags |= MTPDmessage::Flag::f_views;
|
||||
flags |= MTPDmessage::Flag::f_post;
|
||||
}
|
||||
if (!channelPost) {
|
||||
flags |= MTPDmessage::Flag::f_from_id;
|
||||
} else if (peer->asChannel()->addsSignature()) {
|
||||
flags |= MTPDmessage::Flag::f_post_author;
|
||||
}
|
||||
FillMessagePostFlags(action, peer, flags);
|
||||
if (silentPost) {
|
||||
sendFlags |= MTPmessages_SendMessage::Flag::f_silent;
|
||||
}
|
||||
@@ -5019,15 +4959,7 @@ void ApiWrap::sendInlineResult(
|
||||
bool channelPost = peer->isChannel() && !peer->isMegagroup();
|
||||
bool silentPost = action.options.silent
|
||||
|| (channelPost && _session->data().notifySilentPosts(peer));
|
||||
if (channelPost) {
|
||||
flags |= MTPDmessage::Flag::f_views;
|
||||
flags |= MTPDmessage::Flag::f_post;
|
||||
}
|
||||
if (!channelPost) {
|
||||
flags |= MTPDmessage::Flag::f_from_id;
|
||||
} else if (peer->asChannel()->addsSignature()) {
|
||||
flags |= MTPDmessage::Flag::f_post_author;
|
||||
}
|
||||
FillMessagePostFlags(action, peer, flags);
|
||||
if (silentPost) {
|
||||
sendFlags |= MTPmessages_SendInlineBotResult::Flag::f_silent;
|
||||
}
|
||||
@@ -5857,6 +5789,43 @@ void ApiWrap::closePoll(not_null<HistoryItem*> item) {
|
||||
_pollCloseRequestIds.emplace(itemId, requestId);
|
||||
}
|
||||
|
||||
void ApiWrap::rescheduleMessage(
|
||||
not_null<HistoryItem*> item,
|
||||
Api::SendOptions options) {
|
||||
const auto text = item->originalText().text;
|
||||
const auto sentEntities = Api::EntitiesToMTP(
|
||||
item->originalText().entities,
|
||||
Api::ConvertOption::SkipLocal);
|
||||
const auto media = item->media();
|
||||
|
||||
const auto emptyFlag = MTPmessages_EditMessage::Flag(0);
|
||||
const auto flags = MTPmessages_EditMessage::Flag::f_schedule_date
|
||||
| (!text.isEmpty()
|
||||
? MTPmessages_EditMessage::Flag::f_message
|
||||
: emptyFlag)
|
||||
| ((!media || !media->webpage())
|
||||
? MTPmessages_EditMessage::Flag::f_no_webpage
|
||||
: emptyFlag)
|
||||
| (!sentEntities.v.isEmpty()
|
||||
? MTPmessages_EditMessage::Flag::f_entities
|
||||
: emptyFlag);
|
||||
|
||||
const auto id = _session->data().scheduledMessages().lookupId(item);
|
||||
request(MTPmessages_EditMessage(
|
||||
MTP_flags(flags),
|
||||
item->history()->peer->input,
|
||||
MTP_int(id),
|
||||
MTP_string(text),
|
||||
MTPInputMedia(),
|
||||
MTPReplyMarkup(),
|
||||
sentEntities,
|
||||
MTP_int(options.scheduled)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
applyUpdates(result);
|
||||
}).fail([](const RPCError &error) {
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ApiWrap::reloadPollResults(not_null<HistoryItem*> item) {
|
||||
const auto itemId = item->fullId();
|
||||
if (!IsServerMsgId(item->id)
|
||||
|
||||
@@ -12,7 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/flat_map.h"
|
||||
#include "base/flat_set.h"
|
||||
#include "mtproto/sender.h"
|
||||
#include "chat_helpers/stickers.h"
|
||||
#include "chat_helpers/stickers_set.h"
|
||||
#include "data/data_messages.h"
|
||||
|
||||
class TaskQueue;
|
||||
@@ -476,6 +476,10 @@ public:
|
||||
void closePoll(not_null<HistoryItem*> item);
|
||||
void reloadPollResults(not_null<HistoryItem*> item);
|
||||
|
||||
void rescheduleMessage(
|
||||
not_null<HistoryItem*> item,
|
||||
Api::SendOptions options);
|
||||
|
||||
private:
|
||||
struct MessageDataRequest {
|
||||
using Callbacks = QList<RequestMessageDataCallback>;
|
||||
|
||||
@@ -227,8 +227,6 @@ namespace App {
|
||||
clearCorners();
|
||||
|
||||
Data::clearGlobalStructures();
|
||||
|
||||
Images::ClearAll();
|
||||
}
|
||||
|
||||
void hoveredItem(HistoryView::Element *item) {
|
||||
@@ -322,6 +320,9 @@ namespace App {
|
||||
}
|
||||
|
||||
QImage readImage(QByteArray data, QByteArray *format, bool opaque, bool *animated) {
|
||||
if (data.isEmpty()) {
|
||||
return QImage();
|
||||
}
|
||||
QByteArray tmpFormat;
|
||||
QImage result;
|
||||
QBuffer buffer(&data);
|
||||
|
||||
@@ -11,23 +11,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/rect_part.h"
|
||||
|
||||
enum class ImageRoundRadius;
|
||||
class MainWindow;
|
||||
class MainWidget;
|
||||
class HistoryItem;
|
||||
class History;
|
||||
|
||||
namespace HistoryView {
|
||||
class Element;
|
||||
} // namespace HistoryView
|
||||
|
||||
namespace Media {
|
||||
namespace Clip {
|
||||
class Reader;
|
||||
} // namespace Clip
|
||||
} // namespace Media
|
||||
|
||||
using HistoryItemsMap = base::flat_set<not_null<HistoryItem*>>;
|
||||
using GifItems = QHash<Media::Clip::Reader*, HistoryItem*>;
|
||||
|
||||
enum RoundCorners : int {
|
||||
SmallMaskCorners = 0x00, // for images
|
||||
LargeMaskCorners,
|
||||
|
||||
@@ -34,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_cloud_file.h"
|
||||
#include "mainwidget.h"
|
||||
#include "mainwindow.h"
|
||||
#include "apiwrap.h"
|
||||
@@ -222,6 +223,7 @@ private:
|
||||
}
|
||||
|
||||
not_null<PeerData*> peer;
|
||||
mutable std::shared_ptr<Data::CloudImageView> userpic;
|
||||
Ui::Text::String name, status;
|
||||
};
|
||||
void paintChat(Painter &p, const ChatRow &row, bool selected) const;
|
||||
@@ -1438,7 +1440,7 @@ void RevokePublicLinkBox::resizeEvent(QResizeEvent *e) {
|
||||
|
||||
void RevokePublicLinkBox::Inner::paintChat(Painter &p, const ChatRow &row, bool selected) const {
|
||||
auto peer = row.peer;
|
||||
peer->paintUserpicLeft(p, st::contactsPadding.left(), st::contactsPadding.top(), width(), st::contactsPhotoSize);
|
||||
peer->paintUserpicLeft(p, row.userpic, st::contactsPadding.left(), st::contactsPadding.top(), width(), st::contactsPhotoSize);
|
||||
|
||||
p.setPen(st::contactsNameFg);
|
||||
|
||||
|
||||
@@ -216,11 +216,11 @@ void AutoDownloadBox::setupContent() {
|
||||
_session->data().photoLoadSettingsChanged();
|
||||
}
|
||||
if (ranges::find_if(allowMoreTypes, _1 != Type::Photo)
|
||||
!= allowMoreTypes.end()) {
|
||||
!= allowMoreTypes.end()) {
|
||||
_session->data().documentLoadSettingsChanged();
|
||||
}
|
||||
if (less) {
|
||||
_session->data().checkPlayingVideoFiles();
|
||||
_session->data().checkPlayingAnimations();
|
||||
}
|
||||
closeBox();
|
||||
});
|
||||
|
||||
@@ -16,6 +16,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "mtproto/sender.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "boxes/background_preview_box.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "app.h"
|
||||
@@ -53,28 +55,33 @@ QImage TakeMiddleSample(QImage original, QSize size) {
|
||||
|
||||
} // namespace
|
||||
|
||||
class BackgroundBox::Inner : public Ui::RpWidget, private base::Subscriber {
|
||||
class BackgroundBox::Inner final
|
||||
: public Ui::RpWidget
|
||||
, private base::Subscriber {
|
||||
public:
|
||||
Inner(
|
||||
QWidget *parent,
|
||||
not_null<Main::Session*> session);
|
||||
~Inner();
|
||||
|
||||
rpl::producer<Data::WallPaper> chooseEvents() const;
|
||||
rpl::producer<Data::WallPaper> removeRequests() const;
|
||||
|
||||
void removePaper(const Data::WallPaper &data);
|
||||
|
||||
~Inner();
|
||||
|
||||
protected:
|
||||
private:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||
|
||||
private:
|
||||
void visibleTopBottomUpdated(
|
||||
int visibleTop,
|
||||
int visibleBottom) override;
|
||||
|
||||
struct Paper {
|
||||
Data::WallPaper data;
|
||||
mutable std::shared_ptr<Data::DocumentMedia> dataMedia;
|
||||
mutable QPixmap thumbnail;
|
||||
};
|
||||
struct Selected {
|
||||
@@ -252,11 +259,19 @@ void BackgroundBox::Inner::resizeToContentAndPreload() {
|
||||
const auto rows = (count / kBackgroundsInRow)
|
||||
+ (count % kBackgroundsInRow ? 1 : 0);
|
||||
|
||||
resize(st::boxWideWidth, rows * (st::backgroundSize.height() + st::backgroundPadding) + st::backgroundPadding);
|
||||
resize(
|
||||
st::boxWideWidth,
|
||||
(rows * (st::backgroundSize.height() + st::backgroundPadding)
|
||||
+ st::backgroundPadding));
|
||||
|
||||
const auto preload = kBackgroundsInRow * 3;
|
||||
for (const auto &paper : _papers | ranges::view::take(preload)) {
|
||||
paper.data.loadThumbnail();
|
||||
if (!paper.data.localThumbnail() && !paper.dataMedia) {
|
||||
if (const auto document = paper.data.document()) {
|
||||
paper.dataMedia = document->createMediaView();
|
||||
paper.dataMedia->thumbnailWanted(paper.data.fileOrigin());
|
||||
}
|
||||
}
|
||||
}
|
||||
update();
|
||||
}
|
||||
@@ -292,15 +307,24 @@ void BackgroundBox::Inner::paintEvent(QPaintEvent *e) {
|
||||
|
||||
void BackgroundBox::Inner::validatePaperThumbnail(
|
||||
const Paper &paper) const {
|
||||
Expects(paper.data.thumbnail() != nullptr);
|
||||
|
||||
const auto thumbnail = paper.data.thumbnail();
|
||||
if (!paper.thumbnail.isNull()) {
|
||||
return;
|
||||
} else if (!thumbnail->loaded()) {
|
||||
thumbnail->load(paper.data.fileOrigin());
|
||||
return;
|
||||
}
|
||||
const auto localThumbnail = paper.data.localThumbnail();
|
||||
if (!localThumbnail) {
|
||||
if (const auto document = paper.data.document()) {
|
||||
if (!paper.dataMedia) {
|
||||
paper.dataMedia = document->createMediaView();
|
||||
paper.dataMedia->thumbnailWanted(paper.data.fileOrigin());
|
||||
}
|
||||
}
|
||||
if (!paper.dataMedia || !paper.dataMedia->thumbnail()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const auto thumbnail = localThumbnail
|
||||
? localThumbnail
|
||||
: paper.dataMedia->thumbnail();
|
||||
auto original = thumbnail->original();
|
||||
if (paper.data.isPattern()) {
|
||||
const auto color = *paper.data.backgroundColor();
|
||||
@@ -428,7 +452,14 @@ void BackgroundBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
|
||||
if (base::get_if<DeleteSelected>(&_over)) {
|
||||
_backgroundRemove.fire_copy(_papers[index].data);
|
||||
} else if (base::get_if<Selected>(&_over)) {
|
||||
_backgroundChosen.fire_copy(_papers[index].data);
|
||||
auto &paper = _papers[index];
|
||||
if (!paper.dataMedia) {
|
||||
if (const auto document = paper.data.document()) {
|
||||
// Keep it alive while it is on the screen.
|
||||
paper.dataMedia = document->createMediaView();
|
||||
}
|
||||
}
|
||||
_backgroundChosen.fire_copy(paper.data);
|
||||
}
|
||||
}
|
||||
} else if (!_over.has_value()) {
|
||||
@@ -436,6 +467,22 @@ void BackgroundBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
|
||||
}
|
||||
}
|
||||
|
||||
void BackgroundBox::Inner::visibleTopBottomUpdated(
|
||||
int visibleTop,
|
||||
int visibleBottom) {
|
||||
for (auto i = 0, count = int(_papers.size()); i != count; ++i) {
|
||||
const auto row = (i / kBackgroundsInRow);
|
||||
const auto height = st::backgroundSize.height();
|
||||
const auto skip = st::backgroundPadding;
|
||||
const auto top = skip + row * (height + skip);
|
||||
const auto bottom = top + height;
|
||||
if ((bottom <= visibleTop || top >= visibleBottom)
|
||||
&& !_papers[i].thumbnail.isNull()) {
|
||||
_papers[i].dataMedia = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<Data::WallPaper> BackgroundBox::Inner::chooseEvents() const {
|
||||
return _backgroundChosen.events();
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "boxes/background_preview_box.h"
|
||||
@@ -410,7 +412,11 @@ BackgroundPreviewBox::BackgroundPreviewBox(
|
||||
tr::lng_background_text2(tr::now),
|
||||
true))
|
||||
, _paper(paper)
|
||||
, _media(_paper.document() ? _paper.document()->createMediaView() : nullptr)
|
||||
, _radial([=](crl::time now) { radialAnimationCallback(now); }) {
|
||||
if (_media) {
|
||||
_media->thumbnailWanted(_paper.fileOrigin());
|
||||
}
|
||||
subscribe(_session->downloaderTaskFinished(), [=] { update(); });
|
||||
}
|
||||
|
||||
@@ -428,12 +434,14 @@ void BackgroundPreviewBox::prepare() {
|
||||
}
|
||||
updateServiceBg(_paper.backgroundColor());
|
||||
|
||||
_paper.loadThumbnail();
|
||||
_paper.loadDocument();
|
||||
if (_paper.document() && _paper.document()->loading()) {
|
||||
_radial.start(_paper.document()->progress());
|
||||
const auto document = _paper.document();
|
||||
if (document && document->loading()) {
|
||||
_radial.start(_media->progress());
|
||||
}
|
||||
if (_paper.thumbnail() && !_paper.isPattern()) {
|
||||
if (!_paper.isPattern()
|
||||
&& (_paper.localThumbnail()
|
||||
|| (document && document->hasThumbnail()))) {
|
||||
createBlurCheckbox();
|
||||
}
|
||||
setScaledFromThumb();
|
||||
@@ -634,7 +642,7 @@ void BackgroundPreviewBox::radialAnimationCallback(crl::time now) {
|
||||
const auto document = _paper.document();
|
||||
const auto wasAnimating = _radial.animating();
|
||||
const auto updated = _radial.update(
|
||||
document->progress(),
|
||||
_media->progress(),
|
||||
!document->loading(),
|
||||
now);
|
||||
if ((wasAnimating || _radial.animating())
|
||||
@@ -645,8 +653,13 @@ void BackgroundPreviewBox::radialAnimationCallback(crl::time now) {
|
||||
}
|
||||
|
||||
bool BackgroundPreviewBox::setScaledFromThumb() {
|
||||
const auto thumbnail = _paper.thumbnail();
|
||||
if (!thumbnail || !thumbnail->loaded()) {
|
||||
const auto localThumbnail = _paper.localThumbnail();
|
||||
const auto thumbnail = localThumbnail
|
||||
? localThumbnail
|
||||
: _media
|
||||
? _media->thumbnail()
|
||||
: nullptr;
|
||||
if (!thumbnail) {
|
||||
return false;
|
||||
} else if (_paper.isPattern() && _paper.document() != nullptr) {
|
||||
return false;
|
||||
@@ -710,7 +723,7 @@ void BackgroundPreviewBox::checkLoadedDocument() {
|
||||
const auto document = _paper.document();
|
||||
if (!_full.isNull()
|
||||
|| !document
|
||||
|| !document->loaded(DocumentData::FilePathResolve::Checked)
|
||||
|| !_media->loaded(true)
|
||||
|| _generating) {
|
||||
return;
|
||||
}
|
||||
@@ -744,7 +757,7 @@ void BackgroundPreviewBox::checkLoadedDocument() {
|
||||
});
|
||||
};
|
||||
_generating = Data::ReadImageAsync(
|
||||
document,
|
||||
_media.get(),
|
||||
Window::Theme::ProcessBackgroundImage,
|
||||
generateCallback);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/effects/radial_animation.h"
|
||||
|
||||
namespace Data {
|
||||
class DocumentMedia;
|
||||
} // namespace Data
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
@@ -71,6 +75,7 @@ private:
|
||||
AdminLog::OwnedItem _text1;
|
||||
AdminLog::OwnedItem _text2;
|
||||
Data::WallPaper _paper;
|
||||
std::shared_ptr<Data::DocumentMedia> _media;
|
||||
QImage _full;
|
||||
QPixmap _scaled, _blurred, _fadeOutThumbnail;
|
||||
Ui::Animations::Simple _fadeIn;
|
||||
|
||||
@@ -915,7 +915,7 @@ blockUserConfirmation: FlatLabel(boxLabel) {
|
||||
minWidth: 240px;
|
||||
}
|
||||
|
||||
transferCheckWidth: 300px;
|
||||
transferCheckWidth: 320px;
|
||||
|
||||
slowmodeLabelsMargin: margins(0px, 5px, 0px, 0px);
|
||||
slowmodeLabel: LabelSimple(defaultLabelSimple) {
|
||||
|
||||
@@ -599,4 +599,19 @@ void CalendarBox::keyPressEvent(QKeyEvent *e) {
|
||||
}
|
||||
}
|
||||
|
||||
void CalendarBox::wheelEvent(QWheelEvent *e) {
|
||||
// Only a mouse wheel is accepted.
|
||||
constexpr auto step = static_cast<int>(QWheelEvent::DefaultDeltasPerStep);
|
||||
const auto delta = e->angleDelta().y();
|
||||
if (std::abs(delta) != step) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (delta < 0) {
|
||||
goPreviousMonth();
|
||||
} else {
|
||||
goNextMonth();
|
||||
}
|
||||
}
|
||||
|
||||
CalendarBox::~CalendarBox() = default;
|
||||
|
||||
@@ -46,6 +46,7 @@ protected:
|
||||
|
||||
void keyPressEvent(QKeyEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void wheelEvent(QWheelEvent *e) override;
|
||||
|
||||
private:
|
||||
void monthChanged(QDate month);
|
||||
|
||||
@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "main/main_session.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "mtproto/facade.h"
|
||||
#include "app.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
||||
@@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_photo_media.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "main/main_session.h"
|
||||
#include "observer_peer.h"
|
||||
@@ -894,12 +895,12 @@ ConfirmInviteBox::ConfirmInviteBox(
|
||||
|
||||
const auto photo = _session->data().processPhoto(data.vphoto());
|
||||
if (!photo->isNull()) {
|
||||
_photo = photo->thumbnail();
|
||||
if (!_photo->loaded()) {
|
||||
_photo = photo->createMediaView();
|
||||
_photo->wanted(Data::PhotoSize::Small, Data::FileOrigin());
|
||||
if (!_photo->image(Data::PhotoSize::Small)) {
|
||||
subscribe(_session->downloaderTaskFinished(), [=] {
|
||||
update();
|
||||
});
|
||||
_photo->load(Data::FileOrigin());
|
||||
}
|
||||
} else {
|
||||
_photoEmpty = std::make_unique<Ui::EmptyUserpic>(
|
||||
@@ -908,19 +909,20 @@ ConfirmInviteBox::ConfirmInviteBox(
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<not_null<UserData*>> ConfirmInviteBox::GetParticipants(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPDchatInvite &data) {
|
||||
auto ConfirmInviteBox::GetParticipants(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPDchatInvite &data)
|
||||
-> std::vector<Participant> {
|
||||
const auto participants = data.vparticipants();
|
||||
if (!participants) {
|
||||
return {};
|
||||
}
|
||||
const auto &v = participants->v;
|
||||
auto result = std::vector<not_null<UserData*>>();
|
||||
auto result = std::vector<Participant>();
|
||||
result.reserve(v.size());
|
||||
for (const auto &participant : v) {
|
||||
if (const auto user = session->data().processUser(participant)) {
|
||||
result.push_back(user);
|
||||
result.push_back(Participant{ user });
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -945,12 +947,12 @@ void ConfirmInviteBox::prepare() {
|
||||
_userWidth = (st::confirmInviteUserPhotoSize + 2 * padding);
|
||||
int sumWidth = _participants.size() * _userWidth;
|
||||
int left = (st::boxWideWidth - sumWidth) / 2;
|
||||
for (const auto user : _participants) {
|
||||
for (const auto &participant : _participants) {
|
||||
auto name = new Ui::FlatLabel(this, st::confirmInviteUserName);
|
||||
name->resizeToWidth(st::confirmInviteUserPhotoSize + padding);
|
||||
name->setText(user->firstName.isEmpty()
|
||||
? user->name
|
||||
: user->firstName);
|
||||
name->setText(participant.user->firstName.isEmpty()
|
||||
? participant.user->name
|
||||
: participant.user->firstName);
|
||||
name->moveToLeft(left + (padding / 2), st::confirmInviteUserNameTop);
|
||||
left += _userWidth;
|
||||
}
|
||||
@@ -972,14 +974,15 @@ void ConfirmInviteBox::paintEvent(QPaintEvent *e) {
|
||||
Painter p(this);
|
||||
|
||||
if (_photo) {
|
||||
p.drawPixmap(
|
||||
(width() - st::confirmInvitePhotoSize) / 2,
|
||||
st::confirmInvitePhotoTop,
|
||||
_photo->pixCircled(
|
||||
Data::FileOrigin(),
|
||||
st::confirmInvitePhotoSize,
|
||||
st::confirmInvitePhotoSize));
|
||||
} else {
|
||||
if (const auto image = _photo->image(Data::PhotoSize::Small)) {
|
||||
p.drawPixmap(
|
||||
(width() - st::confirmInvitePhotoSize) / 2,
|
||||
st::confirmInvitePhotoTop,
|
||||
image->pixCircled(
|
||||
st::confirmInvitePhotoSize,
|
||||
st::confirmInvitePhotoSize));
|
||||
}
|
||||
} else if (_photoEmpty) {
|
||||
_photoEmpty->paint(
|
||||
p,
|
||||
(width() - st::confirmInvitePhotoSize) / 2,
|
||||
@@ -990,9 +993,10 @@ void ConfirmInviteBox::paintEvent(QPaintEvent *e) {
|
||||
|
||||
int sumWidth = _participants.size() * _userWidth;
|
||||
int left = (width() - sumWidth) / 2;
|
||||
for_const (auto user, _participants) {
|
||||
user->paintUserpicLeft(
|
||||
for (auto &participant : _participants) {
|
||||
participant.user->paintUserpicLeft(
|
||||
p,
|
||||
participant.userpic,
|
||||
left + (_userWidth - st::confirmInviteUserPhotoSize) / 2,
|
||||
st::confirmInviteUserPhotoTop,
|
||||
width(),
|
||||
|
||||
@@ -10,6 +10,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/abstract_box.h"
|
||||
#include "mtproto/mtproto_rpc_sender.h"
|
||||
|
||||
namespace Data {
|
||||
class PhotoMedia;
|
||||
class CloudImageView;
|
||||
} // namespace Data
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
@@ -221,7 +226,11 @@ protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
private:
|
||||
static std::vector<not_null<UserData*>> GetParticipants(
|
||||
struct Participant {
|
||||
not_null<UserData*> user;
|
||||
std::shared_ptr<Data::CloudImageView> userpic;
|
||||
};
|
||||
static std::vector<Participant> GetParticipants(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPDchatInvite &data);
|
||||
|
||||
@@ -230,9 +239,9 @@ private:
|
||||
Fn<void()> _submit;
|
||||
object_ptr<Ui::FlatLabel> _title;
|
||||
object_ptr<Ui::FlatLabel> _status;
|
||||
Image *_photo = nullptr;
|
||||
std::shared_ptr<Data::PhotoMedia> _photo;
|
||||
std::unique_ptr<Ui::EmptyUserpic> _photoEmpty;
|
||||
std::vector<not_null<UserData*>> _participants;
|
||||
std::vector<Participant> _participants;
|
||||
bool _isChannel = false;
|
||||
|
||||
int _userWidth = 0;
|
||||
|
||||
@@ -83,7 +83,7 @@ private:
|
||||
void show(anim::type animated);
|
||||
void destroy(FnMut<void()> done);
|
||||
|
||||
[[nodisacrd]] bool hasShadow() const;
|
||||
[[nodiscard]] bool hasShadow() const;
|
||||
void createShadow();
|
||||
void destroyShadow();
|
||||
|
||||
|
||||
@@ -22,12 +22,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_streaming.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_photo_media.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "layout.h"
|
||||
#include "media/clip/media_clip_reader.h"
|
||||
#include "media/streaming/media_streaming_instance.h"
|
||||
#include "media/streaming/media_streaming_player.h"
|
||||
#include "media/streaming/media_streaming_document.h"
|
||||
#include "media/streaming/media_streaming_loader_local.h"
|
||||
#include "storage/localimageloader.h"
|
||||
#include "storage/storage_media_prepare.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
@@ -46,6 +53,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include <QtCore/QMimeData>
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace ::Media::Streaming;
|
||||
using Data::PhotoSize;
|
||||
|
||||
} // namespace
|
||||
|
||||
EditCaptionBox::EditCaptionBox(
|
||||
QWidget*,
|
||||
not_null<Window::SessionController*> controller,
|
||||
@@ -58,45 +72,67 @@ EditCaptionBox::EditCaptionBox(
|
||||
_isAllowedEditMedia = item->media()->allowsEditMedia();
|
||||
_isAlbum = !item->groupId().empty();
|
||||
|
||||
QSize dimensions;
|
||||
auto image = (Image*)nullptr;
|
||||
DocumentData *doc = nullptr;
|
||||
|
||||
auto dimensions = QSize();
|
||||
const auto media = item->media();
|
||||
if (const auto photo = media->photo()) {
|
||||
_photoMedia = photo->createMediaView();
|
||||
_photoMedia->wanted(PhotoSize::Large, _msgId);
|
||||
dimensions = _photoMedia->size(PhotoSize::Large);
|
||||
if (dimensions.isEmpty()) {
|
||||
dimensions = QSize(1, 1);
|
||||
}
|
||||
_photo = true;
|
||||
dimensions = QSize(photo->width(), photo->height());
|
||||
image = photo->large();
|
||||
} else if (const auto document = media->document()) {
|
||||
image = document->thumbnail();
|
||||
dimensions = image
|
||||
? image->size()
|
||||
_documentMedia = document->createMediaView();
|
||||
_documentMedia->thumbnailWanted(_msgId);
|
||||
dimensions = _documentMedia->thumbnail()
|
||||
? _documentMedia->thumbnail()->size()
|
||||
: document->dimensions;
|
||||
if (document->isAnimation()) {
|
||||
_gifw = document->dimensions.width();
|
||||
_gifh = document->dimensions.height();
|
||||
_gifw = style::ConvertScale(document->dimensions.width());
|
||||
_gifh = style::ConvertScale(document->dimensions.height());
|
||||
_animated = true;
|
||||
} else if (document->isVideoFile()) {
|
||||
_animated = true;
|
||||
} else {
|
||||
_doc = true;
|
||||
}
|
||||
doc = document;
|
||||
} else {
|
||||
Unexpected("Photo or document should be set.");
|
||||
}
|
||||
const auto editData = PrepareEditText(item);
|
||||
|
||||
if (!_animated && (dimensions.isEmpty() || doc || !image)) {
|
||||
if (!image) {
|
||||
_thumbw = 0;
|
||||
const auto computeImage = [=] {
|
||||
if (_documentMedia) {
|
||||
return _documentMedia->thumbnail();
|
||||
} else if (const auto large = _photoMedia->image(PhotoSize::Large)) {
|
||||
return large;
|
||||
} else if (const auto thumbnail = _photoMedia->image(
|
||||
PhotoSize::Thumbnail)) {
|
||||
return thumbnail;
|
||||
} else if (const auto small = _photoMedia->image(PhotoSize::Small)) {
|
||||
return small;
|
||||
} else {
|
||||
const auto tw = image->width(), th = image->height();
|
||||
return _photoMedia->thumbnailInline();
|
||||
}
|
||||
};
|
||||
|
||||
if (!_animated && _documentMedia) {
|
||||
if (dimensions.isEmpty()) {
|
||||
_thumbw = 0;
|
||||
_thumbnailImageLoaded = true;
|
||||
} else {
|
||||
const auto tw = dimensions.width(), th = dimensions.height();
|
||||
if (tw > th) {
|
||||
_thumbw = (tw * st::msgFileThumbSize) / th;
|
||||
} else {
|
||||
_thumbw = st::msgFileThumbSize;
|
||||
}
|
||||
_thumbnailImage = image;
|
||||
_refreshThumbnail = [=] {
|
||||
const auto image = computeImage();
|
||||
if (!image) {
|
||||
return;
|
||||
}
|
||||
const auto options = Images::Option::Smooth
|
||||
| Images::Option::RoundedSmall
|
||||
| Images::Option::RoundedTopLeft
|
||||
@@ -104,30 +140,28 @@ EditCaptionBox::EditCaptionBox(
|
||||
| Images::Option::RoundedBottomLeft
|
||||
| Images::Option::RoundedBottomRight;
|
||||
_thumb = App::pixmapFromImageInPlace(Images::prepare(
|
||||
image->pix(_msgId).toImage(),
|
||||
image->original(),
|
||||
_thumbw * cIntRetinaFactor(),
|
||||
0,
|
||||
options,
|
||||
st::msgFileThumbSize,
|
||||
st::msgFileThumbSize));
|
||||
_thumbnailImageLoaded = true;
|
||||
};
|
||||
}
|
||||
|
||||
if (doc) {
|
||||
const auto nameString = doc->isVoiceMessage()
|
||||
? tr::lng_media_audio(tr::now)
|
||||
: doc->composeNameString();
|
||||
setName(nameString, doc->size);
|
||||
_isImage = doc->isImage();
|
||||
_isAudio = (doc->isVoiceMessage() || doc->isAudioFile());
|
||||
}
|
||||
if (_refreshThumbnail) {
|
||||
_refreshThumbnail();
|
||||
}
|
||||
} else {
|
||||
if (!image) {
|
||||
image = Image::BlankMedia();
|
||||
|
||||
if (_documentMedia) {
|
||||
const auto document = _documentMedia->owner();
|
||||
const auto nameString = document->isVoiceMessage()
|
||||
? tr::lng_media_audio(tr::now)
|
||||
: document->composeNameString();
|
||||
setName(nameString, document->size);
|
||||
_isImage = document->isImage();
|
||||
_isAudio = document->isVoiceMessage()
|
||||
|| document->isAudioFile();
|
||||
}
|
||||
} else {
|
||||
auto maxW = 0, maxH = 0;
|
||||
const auto limitW = st::sendMediaPreviewSize;
|
||||
auto limitH = std::min(st::confirmMaxHeight, _gifh ? _gifh : INT_MAX);
|
||||
@@ -145,29 +179,41 @@ EditCaptionBox::EditCaptionBox(
|
||||
maxH = limitH;
|
||||
}
|
||||
}
|
||||
_thumbnailImage = image;
|
||||
_refreshThumbnail = [=] {
|
||||
const auto image = computeImage();
|
||||
const auto use = image ? image : Image::BlankMedia().get();
|
||||
const auto options = Images::Option::Smooth
|
||||
| Images::Option::Blurred;
|
||||
_thumb = image->pixNoCache(
|
||||
_msgId,
|
||||
_thumb = use->pixNoCache(
|
||||
maxW * cIntRetinaFactor(),
|
||||
maxH * cIntRetinaFactor(),
|
||||
options,
|
||||
maxW,
|
||||
maxH);
|
||||
_thumbnailImageLoaded = true;
|
||||
};
|
||||
prepareGifPreview(doc);
|
||||
} else {
|
||||
Assert(_photoMedia != nullptr);
|
||||
|
||||
maxW = dimensions.width();
|
||||
maxH = dimensions.height();
|
||||
_thumbnailImage = image;
|
||||
_refreshThumbnail = [=] {
|
||||
_thumb = image->pixNoCache(
|
||||
_msgId,
|
||||
const auto image = computeImage();
|
||||
const auto photo = _photoMedia->image(Data::PhotoSize::Large);
|
||||
const auto use = photo
|
||||
? photo
|
||||
: image
|
||||
? image
|
||||
: Image::BlankMedia().get();
|
||||
const auto options = Images::Option::Smooth
|
||||
| (photo
|
||||
? Images::Option(0)
|
||||
: Images::Option::Blurred);
|
||||
_thumbnailImageLoaded = (photo != nullptr);
|
||||
_thumb = use->pixNoCache(
|
||||
maxW * cIntRetinaFactor(),
|
||||
maxH * cIntRetinaFactor(),
|
||||
Images::Option::Smooth,
|
||||
options,
|
||||
maxW,
|
||||
maxH);
|
||||
};
|
||||
@@ -205,7 +251,7 @@ EditCaptionBox::EditCaptionBox(
|
||||
thumbX = (st::boxWideWidth - thumbWidth) / 2;
|
||||
};
|
||||
|
||||
if (doc && doc->isAnimation()) {
|
||||
if (_documentMedia && _documentMedia->owner()->isAnimation()) {
|
||||
resizeDimensions(_gifw, _gifh, _gifx);
|
||||
}
|
||||
limitH = std::min(st::confirmMaxHeight, _gifh ? _gifh : INT_MAX);
|
||||
@@ -236,23 +282,19 @@ EditCaptionBox::EditCaptionBox(
|
||||
scaleThumbDown();
|
||||
}
|
||||
Assert(_animated || _photo || _doc);
|
||||
Assert(_thumbnailImageLoaded || _refreshThumbnail);
|
||||
|
||||
_thumbnailImageLoaded = _thumbnailImage
|
||||
? _thumbnailImage->loaded()
|
||||
: true;
|
||||
subscribe(_controller->session().downloaderTaskFinished(), [=] {
|
||||
if (!_thumbnailImageLoaded
|
||||
&& _thumbnailImage
|
||||
&& _thumbnailImage->loaded()) {
|
||||
_thumbnailImageLoaded = true;
|
||||
if (!_thumbnailImageLoaded) {
|
||||
subscribe(_controller->session().downloaderTaskFinished(), [=] {
|
||||
if (_thumbnailImageLoaded
|
||||
|| (_photoMedia && !_photoMedia->image(PhotoSize::Large))
|
||||
|| (_documentMedia && !_documentMedia->thumbnail())) {
|
||||
return;
|
||||
}
|
||||
_refreshThumbnail();
|
||||
update();
|
||||
}
|
||||
if (doc && doc->isAnimation() && doc->loaded() && !_gifPreview) {
|
||||
prepareGifPreview(doc);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
_field.create(
|
||||
this,
|
||||
st::confirmCaptionArea,
|
||||
@@ -285,8 +327,14 @@ EditCaptionBox::EditCaptionBox(
|
||||
) | rpl::start_with_next([&](bool checked) {
|
||||
_asFile = checked;
|
||||
}, _wayWrap->lifetime());
|
||||
|
||||
if (_animated) {
|
||||
prepareStreamedPreview();
|
||||
}
|
||||
}
|
||||
|
||||
EditCaptionBox::~EditCaptionBox() = default;
|
||||
|
||||
void EditCaptionBox::emojiFilterForGeometry(not_null<QEvent*> event) {
|
||||
const auto type = event->type();
|
||||
if (type == QEvent::Move || type == QEvent::Resize) {
|
||||
@@ -305,74 +353,86 @@ void EditCaptionBox::updateEmojiPanelGeometry() {
|
||||
local.x() + _emojiToggle->width() * 3);
|
||||
}
|
||||
|
||||
void EditCaptionBox::prepareGifPreview(DocumentData* document) {
|
||||
void EditCaptionBox::prepareStreamedPreview() {
|
||||
const auto isListEmpty = _preparedList.files.empty();
|
||||
if (_gifPreview) {
|
||||
if (_streamed) {
|
||||
return;
|
||||
} else if (!document && isListEmpty) {
|
||||
} else if (!_documentMedia && isListEmpty) {
|
||||
return;
|
||||
}
|
||||
const auto callback = [=](Media::Clip::Notification notification) {
|
||||
clipCallback(notification);
|
||||
};
|
||||
if (document && document->isAnimation() && document->loaded()) {
|
||||
_gifPreview = Media::Clip::MakeReader(
|
||||
document,
|
||||
_msgId,
|
||||
callback);
|
||||
const auto document = _documentMedia
|
||||
? _documentMedia->owner().get()
|
||||
: nullptr;
|
||||
if (document && document->isAnimation()) {
|
||||
setupStreamedPreview(
|
||||
document->owner().streaming().sharedDocument(
|
||||
document,
|
||||
_msgId));
|
||||
} else if (!isListEmpty) {
|
||||
const auto file = &_preparedList.files.front();
|
||||
if (file->path.isEmpty()) {
|
||||
_gifPreview = Media::Clip::MakeReader(
|
||||
file->content,
|
||||
callback);
|
||||
} else {
|
||||
_gifPreview = Media::Clip::MakeReader(
|
||||
file->path,
|
||||
callback);
|
||||
}
|
||||
auto loader = file->path.isEmpty()
|
||||
? MakeBytesLoader(file->content)
|
||||
: MakeFileLoader(file->path);
|
||||
setupStreamedPreview(std::make_shared<Document>(std::move(loader)));
|
||||
}
|
||||
if (_gifPreview) _gifPreview->setAutoplay();
|
||||
}
|
||||
|
||||
void EditCaptionBox::clipCallback(Media::Clip::Notification notification) {
|
||||
using namespace Media::Clip;
|
||||
switch (notification) {
|
||||
case NotificationReinit: {
|
||||
if (_gifPreview && _gifPreview->state() == State::Error) {
|
||||
_gifPreview.setBad();
|
||||
}
|
||||
void EditCaptionBox::setupStreamedPreview(std::shared_ptr<Document> shared) {
|
||||
if (!shared) {
|
||||
return;
|
||||
}
|
||||
_streamed = std::make_unique<Instance>(
|
||||
std::move(shared),
|
||||
[=] { update(); });
|
||||
_streamed->lockPlayer();
|
||||
_streamed->player().updates(
|
||||
) | rpl::start_with_next_error([=](Update &&update) {
|
||||
handleStreamingUpdate(std::move(update));
|
||||
}, [=](Error &&error) {
|
||||
handleStreamingError(std::move(error));
|
||||
}, _streamed->lifetime());
|
||||
|
||||
if (_gifPreview && _gifPreview->ready() && !_gifPreview->started()) {
|
||||
const auto calculateGifDimensions = [&]() {
|
||||
const auto scaled = QSize(
|
||||
_gifPreview->width(),
|
||||
_gifPreview->height()).scaled(
|
||||
st::sendMediaPreviewSize * cIntRetinaFactor(),
|
||||
st::confirmMaxHeight * cIntRetinaFactor(),
|
||||
Qt::KeepAspectRatio);
|
||||
_thumbw = _gifw = scaled.width();
|
||||
_thumbh = _gifh = scaled.height();
|
||||
_thumbx = _gifx = (st::boxWideWidth - _gifw) / 2;
|
||||
updateBoxSize();
|
||||
};
|
||||
// If gif file is not mp4,
|
||||
// Its dimension values will be known only after reading.
|
||||
if (_gifw <= 0 || _gifh <= 0) {
|
||||
calculateGifDimensions();
|
||||
}
|
||||
const auto s = QSize(_gifw, _gifh);
|
||||
_gifPreview->start(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None, RectPart::None);
|
||||
}
|
||||
if (_streamed->ready()) {
|
||||
streamingReady(base::duplicate(_streamed->info()));
|
||||
}
|
||||
checkStreamedIsStarted();
|
||||
}
|
||||
|
||||
update();
|
||||
} break;
|
||||
void EditCaptionBox::handleStreamingUpdate(Update &&update) {
|
||||
update.data.match([&](Information &update) {
|
||||
streamingReady(std::move(update));
|
||||
}, [&](const PreloadedVideo &update) {
|
||||
}, [&](const UpdateVideo &update) {
|
||||
this->update();
|
||||
}, [&](const PreloadedAudio &update) {
|
||||
}, [&](const UpdateAudio &update) {
|
||||
}, [&](const WaitingForData &update) {
|
||||
}, [&](MutedByOther) {
|
||||
}, [&](Finished) {
|
||||
});
|
||||
}
|
||||
|
||||
case NotificationRepaint: {
|
||||
if (_gifPreview && !_gifPreview->currentDisplayed()) {
|
||||
update();
|
||||
}
|
||||
} break;
|
||||
void EditCaptionBox::handleStreamingError(Error &&error) {
|
||||
}
|
||||
|
||||
void EditCaptionBox::streamingReady(Information &&info) {
|
||||
const auto calculateGifDimensions = [&]() {
|
||||
const auto scaled = QSize(
|
||||
info.video.size.width(),
|
||||
info.video.size.height()
|
||||
).scaled(
|
||||
st::sendMediaPreviewSize * cIntRetinaFactor(),
|
||||
st::confirmMaxHeight * cIntRetinaFactor(),
|
||||
Qt::KeepAspectRatio);
|
||||
_thumbw = _gifw = scaled.width();
|
||||
_thumbh = _gifh = scaled.height();
|
||||
_thumbx = _gifx = (st::boxWideWidth - _gifw) / 2;
|
||||
updateBoxSize();
|
||||
};
|
||||
// If gif file is not mp4,
|
||||
// Its dimension values will be known only after reading.
|
||||
if (_gifw <= 0 || _gifh <= 0) {
|
||||
calculateGifDimensions();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,7 +450,7 @@ void EditCaptionBox::updateEditPreview() {
|
||||
_animated = false;
|
||||
_photo = false;
|
||||
_doc = false;
|
||||
_gifPreview = nullptr;
|
||||
_streamed = nullptr;
|
||||
_thumbw = _thumbh = _thumbx = 0;
|
||||
_gifw = _gifh = _gifx = 0;
|
||||
|
||||
@@ -469,7 +529,7 @@ void EditCaptionBox::updateEditPreview() {
|
||||
_gifw = _thumbw;
|
||||
_gifh = _thumbh;
|
||||
_gifx = _thumbx;
|
||||
prepareGifPreview();
|
||||
prepareStreamedPreview();
|
||||
}
|
||||
}
|
||||
updateEditMediaButton();
|
||||
@@ -551,10 +611,7 @@ void EditCaptionBox::prepare() {
|
||||
} else if (data->hasImage()) {
|
||||
return true;
|
||||
} else if (const auto urls = data->urls(); !urls.empty()) {
|
||||
if (ranges::find_if(
|
||||
urls,
|
||||
[](const QUrl &url) { return !url.isLocalFile(); }
|
||||
) == urls.end()) {
|
||||
if (ranges::all_of(urls, &QUrl::isLocalFile)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -687,6 +744,31 @@ int EditCaptionBox::errorTopSkip() const {
|
||||
return (st::defaultBox.buttonPadding.top() / 2);
|
||||
}
|
||||
|
||||
void EditCaptionBox::checkStreamedIsStarted() {
|
||||
if (!_streamed) {
|
||||
return;
|
||||
}
|
||||
if (_streamed->paused()) {
|
||||
_streamed->resume();
|
||||
}
|
||||
if (!_streamed->active() && !_streamed->failed()) {
|
||||
startStreamedPlayer();
|
||||
}
|
||||
}
|
||||
|
||||
void EditCaptionBox::startStreamedPlayer() {
|
||||
auto options = ::Media::Streaming::PlaybackOptions();
|
||||
options.audioId = _documentMedia
|
||||
? AudioMsgId(_documentMedia->owner(), _msgId)
|
||||
: AudioMsgId();
|
||||
options.waitForMarkAsShown = true;
|
||||
//if (!_streamed->withSound) {
|
||||
options.mode = ::Media::Streaming::Mode::Video;
|
||||
options.loop = true;
|
||||
//}
|
||||
_streamed->play(options);
|
||||
}
|
||||
|
||||
void EditCaptionBox::paintEvent(QPaintEvent *e) {
|
||||
BoxContent::paintEvent(e);
|
||||
|
||||
@@ -700,16 +782,27 @@ void EditCaptionBox::paintEvent(QPaintEvent *e) {
|
||||
if (_thumbx + _thumbw < width() - st::boxPhotoPadding.right()) {
|
||||
p.fillRect(_thumbx + _thumbw, st::boxPhotoPadding.top(), width() - st::boxPhotoPadding.right() - _thumbx - _thumbw, th, st::confirmBg);
|
||||
}
|
||||
if (_gifPreview && _gifPreview->started()) {
|
||||
checkStreamedIsStarted();
|
||||
if (_streamed
|
||||
&& _streamed->player().ready()
|
||||
&& !_streamed->player().videoSize().isEmpty()) {
|
||||
const auto s = QSize(_gifw, _gifh);
|
||||
const auto paused = _controller->isGifPausedAtLeastFor(Window::GifPauseReason::Layer);
|
||||
const auto frame = _gifPreview->current(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None, RectPart::None, paused ? 0 : crl::now());
|
||||
p.drawPixmap(_gifx, st::boxPhotoPadding.top(), frame);
|
||||
|
||||
auto request = ::Media::Streaming::FrameRequest();
|
||||
request.outer = s * cIntRetinaFactor();
|
||||
request.resize = s * cIntRetinaFactor();
|
||||
p.drawImage(
|
||||
QRect(_gifx, st::boxPhotoPadding.top(), _gifw, _gifh),
|
||||
_streamed->frame(request));
|
||||
if (!paused) {
|
||||
_streamed->markFrameShown();
|
||||
}
|
||||
} else {
|
||||
const auto offset = _gifh ? ((_gifh - _thumbh) / 2) : 0;
|
||||
p.drawPixmap(_thumbx, st::boxPhotoPadding.top() + offset, _thumb);
|
||||
}
|
||||
if (_animated && !_gifPreview) {
|
||||
if (_animated && !_streamed) {
|
||||
QRect inner(_thumbx + (_thumbw - st::msgFileSize) / 2, st::boxPhotoPadding.top() + (th - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::msgDateImgBg);
|
||||
|
||||
@@ -10,9 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/abstract_box.h"
|
||||
#include "storage/storage_media_prepare.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "media/clip/media_clip_reader.h"
|
||||
#include "mtproto/mtproto_rpc_sender.h"
|
||||
|
||||
class Image;
|
||||
|
||||
namespace ChatHelpers {
|
||||
class TabbedPanel;
|
||||
} // namespace ChatHelpers
|
||||
@@ -23,6 +24,8 @@ class SessionController;
|
||||
|
||||
namespace Data {
|
||||
class Media;
|
||||
class PhotoMedia;
|
||||
class DocumentMedia;
|
||||
} // namespace Data
|
||||
|
||||
namespace Ui {
|
||||
@@ -36,6 +39,16 @@ namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace Media {
|
||||
namespace Streaming {
|
||||
class Instance;
|
||||
class Document;
|
||||
struct Update;
|
||||
enum class Error;
|
||||
struct Information;
|
||||
} // namespace Streaming
|
||||
} // namespace Media
|
||||
|
||||
class EditCaptionBox
|
||||
: public Ui::BoxContent
|
||||
, public RPCSender
|
||||
@@ -45,6 +58,7 @@ public:
|
||||
QWidget*,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<HistoryItem*> item);
|
||||
~EditCaptionBox();
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
@@ -56,8 +70,14 @@ protected:
|
||||
|
||||
private:
|
||||
void updateBoxSize();
|
||||
void prepareGifPreview(DocumentData* document = nullptr);
|
||||
void clipCallback(Media::Clip::Notification notification);
|
||||
void prepareStreamedPreview();
|
||||
void checkStreamedIsStarted();
|
||||
void setupStreamedPreview(
|
||||
std::shared_ptr<::Media::Streaming::Document> shared);
|
||||
void handleStreamingUpdate(::Media::Streaming::Update &&update);
|
||||
void handleStreamingError(::Media::Streaming::Error &&error);
|
||||
void streamingReady(::Media::Streaming::Information &&info);
|
||||
void startStreamedPlayer();
|
||||
|
||||
void setupEmojiPanel();
|
||||
void updateEmojiPanelGeometry();
|
||||
@@ -86,7 +106,8 @@ private:
|
||||
|
||||
not_null<Window::SessionController*> _controller;
|
||||
FullMsgId _msgId;
|
||||
Image *_thumbnailImage = nullptr;
|
||||
std::shared_ptr<Data::PhotoMedia> _photoMedia;
|
||||
std::shared_ptr<Data::DocumentMedia> _documentMedia;
|
||||
bool _thumbnailImageLoaded = false;
|
||||
Fn<void()> _refreshThumbnail;
|
||||
bool _animated = false;
|
||||
@@ -94,7 +115,7 @@ private:
|
||||
bool _doc = false;
|
||||
|
||||
QPixmap _thumb;
|
||||
Media::Clip::ReaderPointer _gifPreview;
|
||||
std::unique_ptr<::Media::Streaming::Instance> _streamed;
|
||||
|
||||
object_ptr<Ui::InputField> _field = { nullptr };
|
||||
object_ptr<Ui::EmojiButton> _emojiToggle = { nullptr };
|
||||
|
||||
@@ -78,6 +78,7 @@ private:
|
||||
};
|
||||
struct PeerButton {
|
||||
not_null<History*> history;
|
||||
std::shared_ptr<Data::CloudImageView> userpic;
|
||||
Button button;
|
||||
};
|
||||
|
||||
@@ -184,9 +185,10 @@ void FilterChatsPreview::updateData(
|
||||
}
|
||||
}
|
||||
for (const auto history : peers) {
|
||||
_removePeer.push_back({
|
||||
history,
|
||||
makeButton([=] { removePeer(history); }) });
|
||||
_removePeer.push_back(PeerButton{
|
||||
.history = history,
|
||||
.button = makeButton([=] { removePeer(history); })
|
||||
});
|
||||
}
|
||||
refresh();
|
||||
}
|
||||
@@ -203,7 +205,7 @@ int FilterChatsPreview::resizeGetHeight(int newWidth) {
|
||||
for (const auto &[flag, button] : _removeFlag) {
|
||||
moveNextButton(button.get());
|
||||
}
|
||||
for (const auto &[history, button] : _removePeer) {
|
||||
for (const auto &[history, userpic, button] : _removePeer) {
|
||||
moveNextButton(button.get());
|
||||
}
|
||||
return top;
|
||||
@@ -235,7 +237,7 @@ void FilterChatsPreview::paintEvent(QPaintEvent *e) {
|
||||
FilterChatsTypeName(flag));
|
||||
top += st.height;
|
||||
}
|
||||
for (const auto &[history, button] : _removePeer) {
|
||||
for (auto &[history, userpic, button] : _removePeer) {
|
||||
const auto savedMessages = history->peer->isSelf();
|
||||
if (savedMessages) {
|
||||
Ui::EmptyUserpic::PaintSavedMessages(
|
||||
@@ -253,6 +255,7 @@ void FilterChatsPreview::paintEvent(QPaintEvent *e) {
|
||||
} else {
|
||||
history->peer->paintUserpicLeft(
|
||||
p,
|
||||
userpic,
|
||||
iconLeft,
|
||||
top + iconTop,
|
||||
width(),
|
||||
|
||||
@@ -180,11 +180,12 @@ QString ExceptionRow::generateShortName() {
|
||||
PaintRoundImageCallback ExceptionRow::generatePaintUserpicCallback() {
|
||||
const auto peer = this->peer();
|
||||
const auto saved = peer->isSelf();
|
||||
return [=](Painter &p, int x, int y, int outerWidth, int size) {
|
||||
auto userpic = saved ? nullptr : ensureUserpicView();
|
||||
return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
|
||||
if (saved) {
|
||||
Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
|
||||
} else {
|
||||
peer->paintUserpicLeft(p, x, y, outerWidth, size);
|
||||
peer->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -41,8 +41,9 @@ PaintRoundImageCallback PaintUserpicCallback(
|
||||
Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
|
||||
};
|
||||
}
|
||||
return [=](Painter &p, int x, int y, int outerWidth, int size) {
|
||||
peer->paintUserpicLeft(p, x, y, outerWidth, size);
|
||||
auto userpic = std::shared_ptr<Data::CloudImageView>();
|
||||
return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
|
||||
peer->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -483,14 +484,22 @@ QString PeerListRow::generateShortName() {
|
||||
: peer()->shortName();
|
||||
}
|
||||
|
||||
std::shared_ptr<Data::CloudImageView> PeerListRow::ensureUserpicView() {
|
||||
if (!_userpic) {
|
||||
_userpic = peer()->createUserpicView();
|
||||
}
|
||||
return _userpic;
|
||||
}
|
||||
|
||||
PaintRoundImageCallback PeerListRow::generatePaintUserpicCallback() {
|
||||
const auto saved = _isSavedMessagesChat;
|
||||
const auto peer = this->peer();
|
||||
return [=](Painter &p, int x, int y, int outerWidth, int size) {
|
||||
auto userpic = saved ? nullptr : ensureUserpicView();
|
||||
return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
|
||||
if (saved) {
|
||||
Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
|
||||
} else {
|
||||
peer->paintUserpicLeft(p, x, y, outerWidth, size);
|
||||
peer->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -595,7 +604,7 @@ void PeerListRow::paintDisabledCheckUserpic(
|
||||
if (_isSavedMessagesChat) {
|
||||
Ui::EmptyUserpic::PaintSavedMessages(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
|
||||
} else {
|
||||
peer()->paintUserpicLeft(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
|
||||
peer()->paintUserpicLeft(p, _userpic, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -7,11 +7,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <rpl/event_stream.h>
|
||||
#include "ui/rp_widget.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "boxes/abstract_box.h"
|
||||
#include "mtproto/sender.h"
|
||||
#include "data/data_cloud_file.h"
|
||||
#include "base/timer.h"
|
||||
|
||||
namespace style {
|
||||
@@ -85,6 +85,8 @@ public:
|
||||
return _id;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::shared_ptr<Data::CloudImageView> ensureUserpicView();
|
||||
|
||||
[[nodiscard]] virtual QString generateName();
|
||||
[[nodiscard]] virtual QString generateShortName();
|
||||
[[nodiscard]] virtual auto generatePaintUserpicCallback()
|
||||
@@ -223,6 +225,7 @@ private:
|
||||
|
||||
PeerListRowId _id = 0;
|
||||
PeerData *_peer = nullptr;
|
||||
mutable std::shared_ptr<Data::CloudImageView> _userpic;
|
||||
std::unique_ptr<Ui::RippleAnimation> _ripple;
|
||||
std::unique_ptr<Ui::RoundImageCheckbox> _checkbox;
|
||||
Ui::Text::String _name;
|
||||
|
||||
@@ -354,6 +354,16 @@ bool ParticipantsAdditionalData::canRestrictUser(
|
||||
Unexpected("Peer in ParticipantsAdditionalData::canRestrictUser.");
|
||||
}
|
||||
|
||||
bool ParticipantsAdditionalData::canRemoveUser(
|
||||
not_null<UserData*> user) const {
|
||||
if (canRestrictUser(user)) {
|
||||
return true;
|
||||
} else if (const auto chat = _peer->asChat()) {
|
||||
return chat->invitedByMe.contains(user);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto ParticipantsAdditionalData::adminRights(
|
||||
not_null<UserData*> user) const
|
||||
-> std::optional<MTPChatAdminRights> {
|
||||
@@ -1436,6 +1446,8 @@ base::unique_qptr<Ui::PopupMenu> ParticipantsBoxController::rowContextMenu(
|
||||
tr::lng_context_restrict_user(tr::now),
|
||||
crl::guard(this, [=] { showRestricted(user); }));
|
||||
}
|
||||
}
|
||||
if (_additional.canRemoveUser(user)) {
|
||||
if (!_additional.isKicked(user)) {
|
||||
const auto isGroup = _peer->isChat() || _peer->isMegagroup();
|
||||
result->addAction(
|
||||
@@ -1806,7 +1818,7 @@ auto ParticipantsBoxController::computeType(
|
||||
: _additional.adminRights(user).has_value()
|
||||
? Rights::Admin
|
||||
: Rights::Normal;
|
||||
result.canRemove = _additional.canRestrictUser(user);
|
||||
result.canRemove = _additional.canRemoveUser(user);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -88,6 +88,7 @@ public:
|
||||
[[nodiscard]] bool canEditAdmin(not_null<UserData*> user) const;
|
||||
[[nodiscard]] bool canAddOrEditAdmin(not_null<UserData*> user) const;
|
||||
[[nodiscard]] bool canRestrictUser(not_null<UserData*> user) const;
|
||||
[[nodiscard]] bool canRemoveUser(not_null<UserData*> user) const;
|
||||
[[nodiscard]] std::optional<MTPChatAdminRights> adminRights(
|
||||
not_null<UserData*> user) const;
|
||||
QString adminRank(not_null<UserData*> user) const;
|
||||
|
||||
@@ -888,7 +888,6 @@ void SingleMediaPreview::prepareAnimatedPreview(
|
||||
_gifPreview = Media::Clip::MakeReader(
|
||||
animatedPreviewPath,
|
||||
std::move(callback));
|
||||
if (_gifPreview) _gifPreview->setAutoplay();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1832,11 +1831,7 @@ void SendFilesBox::setupShadows(
|
||||
}
|
||||
|
||||
void SendFilesBox::prepare() {
|
||||
_send = addButton(
|
||||
(_sendType == Api::SendType::Normal
|
||||
? tr::lng_send_button()
|
||||
: tr::lng_schedule_button()),
|
||||
[=] { send({}); });
|
||||
_send = addButton(tr::lng_send_button(), [=] { send({}); });
|
||||
if (_sendType == Api::SendType::Normal) {
|
||||
SetupSendMenuAndShortcuts(
|
||||
_send,
|
||||
@@ -1854,9 +1849,8 @@ void SendFilesBox::prepare() {
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
const auto title = tr::lng_stickers_featured_add(tr::now) + qsl("...");
|
||||
_addFileToAlbum = addLeftButton(
|
||||
rpl::single(title),
|
||||
tr::lng_stickers_featured_add(),
|
||||
App::LambdaDelayed(st::historyAttach.ripple.hideDuration, this, [=] {
|
||||
openDialogToAddFileToAlbum();
|
||||
}));
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "chat_helpers/stickers.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
@@ -20,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/image/image_location_factory.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/emoji_config.h"
|
||||
#include "lottie/lottie_multi_player.h"
|
||||
@@ -72,6 +74,7 @@ protected:
|
||||
private:
|
||||
struct Element {
|
||||
not_null<DocumentData*> document;
|
||||
std::shared_ptr<Data::DocumentMedia> documentMedia;
|
||||
Lottie::Animation *animated = nullptr;
|
||||
Ui::Animations::Simple overAnimation;
|
||||
};
|
||||
@@ -113,7 +116,7 @@ private:
|
||||
int32 _setHash = 0;
|
||||
MTPDstickerSet::Flags _setFlags = 0;
|
||||
TimeId _setInstallDate = TimeId(0);
|
||||
ImagePtr _setThumbnail;
|
||||
ImageWithLocation _setThumbnail;
|
||||
|
||||
MTPInputStickerSet _input;
|
||||
|
||||
@@ -267,7 +270,7 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) {
|
||||
continue;
|
||||
}
|
||||
_pack.push_back(document);
|
||||
_elements.push_back({ document });
|
||||
_elements.push_back({ document, document->createMediaView() });
|
||||
}
|
||||
for (const auto &pack : data.vpacks().v) {
|
||||
pack.match([&](const MTPDstickerPack &pack) {
|
||||
@@ -297,25 +300,29 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) {
|
||||
_setFlags = set.vflags().v;
|
||||
_setInstallDate = set.vinstalled_date().value_or(0);
|
||||
if (const auto thumb = set.vthumb()) {
|
||||
_setThumbnail = Images::Create(set, *thumb);
|
||||
_setThumbnail = Images::FromPhotoSize(
|
||||
&_controller->session(),
|
||||
set,
|
||||
*thumb);
|
||||
} else {
|
||||
_setThumbnail = ImagePtr();
|
||||
_setThumbnail = ImageWithLocation();
|
||||
}
|
||||
auto &sets = _controller->session().data().stickerSetsRef();
|
||||
const auto &sets = _controller->session().data().stickerSets();
|
||||
const auto it = sets.find(_setId);
|
||||
if (it != sets.cend()) {
|
||||
const auto set = it->second.get();
|
||||
using ClientFlag = MTPDstickerSet_ClientFlag;
|
||||
const auto clientFlags = it->flags
|
||||
const auto clientFlags = set->flags
|
||||
& (ClientFlag::f_featured
|
||||
| ClientFlag::f_not_loaded
|
||||
| ClientFlag::f_unread
|
||||
| ClientFlag::f_special);
|
||||
_setFlags |= clientFlags;
|
||||
it->flags = _setFlags;
|
||||
it->installDate = _setInstallDate;
|
||||
it->stickers = _pack;
|
||||
it->emoji = _emoji;
|
||||
it->thumbnail = _setThumbnail;
|
||||
set->flags = _setFlags;
|
||||
set->installDate = _setInstallDate;
|
||||
set->stickers = _pack;
|
||||
set->emoji = _emoji;
|
||||
set->setThumbnail(_setThumbnail);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -357,9 +364,10 @@ void StickerSetBox::Inner::installDone(
|
||||
_setFlags |= MTPDstickerSet::Flag::f_installed_date;
|
||||
auto it = sets.find(_setId);
|
||||
if (it == sets.cend()) {
|
||||
it = sets.insert(
|
||||
it = sets.emplace(
|
||||
_setId,
|
||||
Stickers::Set(
|
||||
std::make_unique<Stickers::Set>(
|
||||
&_controller->session().data(),
|
||||
_setId,
|
||||
_setAccess,
|
||||
_setTitle,
|
||||
@@ -367,14 +375,15 @@ void StickerSetBox::Inner::installDone(
|
||||
_setCount,
|
||||
_setHash,
|
||||
_setFlags,
|
||||
_setInstallDate,
|
||||
_setThumbnail));
|
||||
_setInstallDate)).first;
|
||||
} else {
|
||||
it->flags = _setFlags;
|
||||
it->installDate = _setInstallDate;
|
||||
it->second->flags = _setFlags;
|
||||
it->second->installDate = _setInstallDate;
|
||||
}
|
||||
it->stickers = _pack;
|
||||
it->emoji = _emoji;
|
||||
const auto set = it->second.get();
|
||||
set->setThumbnail(_setThumbnail);
|
||||
set->stickers = _pack;
|
||||
set->emoji = _emoji;
|
||||
|
||||
auto &order = _controller->session().data().stickerSetsOrderRef();
|
||||
int insertAtIndex = 0, currentIndex = order.indexOf(_setId);
|
||||
@@ -385,14 +394,15 @@ void StickerSetBox::Inner::installDone(
|
||||
order.insert(insertAtIndex, _setId);
|
||||
}
|
||||
|
||||
auto custom = sets.find(Stickers::CustomSetId);
|
||||
if (custom != sets.cend()) {
|
||||
for_const (auto sticker, _pack) {
|
||||
const auto customIt = sets.find(Stickers::CustomSetId);
|
||||
if (customIt != sets.cend()) {
|
||||
const auto custom = customIt->second.get();
|
||||
for (const auto sticker : std::as_const(_pack)) {
|
||||
int removeIndex = custom->stickers.indexOf(sticker);
|
||||
if (removeIndex >= 0) custom->stickers.removeAt(removeIndex);
|
||||
}
|
||||
if (custom->stickers.isEmpty()) {
|
||||
sets.erase(custom);
|
||||
sets.erase(customIt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -602,7 +612,7 @@ void StickerSetBox::Inner::setupLottie(int index) {
|
||||
|
||||
element.animated = Stickers::LottieAnimationFromDocument(
|
||||
getLottiePlayer(),
|
||||
document,
|
||||
element.documentMedia.get(),
|
||||
Stickers::LottieSize::StickerSet,
|
||||
boundingBoxSize() * cIntRetinaFactor());
|
||||
}
|
||||
@@ -621,11 +631,12 @@ void StickerSetBox::Inner::paintSticker(
|
||||
|
||||
const auto &element = _elements[index];
|
||||
const auto document = element.document;
|
||||
document->checkStickerSmall();
|
||||
const auto &media = element.documentMedia;
|
||||
media->checkStickerSmall();
|
||||
|
||||
if (document->sticker()->animated
|
||||
&& !element.animated
|
||||
&& document->loaded()) {
|
||||
&& media->loaded()) {
|
||||
const_cast<Inner*>(this)->setupLottie(index);
|
||||
}
|
||||
|
||||
@@ -650,11 +661,11 @@ void StickerSetBox::Inner::paintSticker(
|
||||
frame);
|
||||
|
||||
_lottiePlayer->unpause(element.animated);
|
||||
} else if (const auto image = document->getStickerSmall()) {
|
||||
} else if (const auto image = media->getStickerSmall()) {
|
||||
p.drawPixmapLeft(
|
||||
ppos,
|
||||
width(),
|
||||
image->pix(document->stickerSetOrigin(), w, h));
|
||||
image->pix(w, h));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -666,10 +677,11 @@ bool StickerSetBox::Inner::notInstalled() const {
|
||||
if (!_loaded) {
|
||||
return false;
|
||||
}
|
||||
const auto it = _controller->session().data().stickerSets().constFind(_setId);
|
||||
if ((it == _controller->session().data().stickerSets().cend())
|
||||
|| !(it->flags & MTPDstickerSet::Flag::f_installed_date)
|
||||
|| (it->flags & MTPDstickerSet::Flag::f_archived)) {
|
||||
const auto &sets = _controller->session().data().stickerSets();
|
||||
const auto it = sets.find(_setId);
|
||||
if ((it == sets.cend())
|
||||
|| !(it->second->flags & MTPDstickerSet::Flag::f_installed_date)
|
||||
|| (it->second->flags & MTPDstickerSet::Flag::f_archived)) {
|
||||
return !_pack.empty();
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "core/application.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "mainwidget.h"
|
||||
@@ -165,7 +166,7 @@ void StickersBox::showAttachedStickers() {
|
||||
});
|
||||
|
||||
if (const auto set = Stickers::FeedSet(*setData)) {
|
||||
if (_attached.widget()->appendSet(*set)) {
|
||||
if (_attached.widget()->appendSet(set)) {
|
||||
addedSet = true;
|
||||
if (set->stickers.isEmpty() || (set->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
|
||||
_session->api().scheduleStickerSetRequest(set->id, set->access);
|
||||
@@ -220,8 +221,8 @@ void StickersBox::getArchivedDone(uint64 offsetId, const MTPmessages_ArchivedSti
|
||||
}
|
||||
if (!setData) continue;
|
||||
|
||||
if (auto set = Stickers::FeedSet(*setData)) {
|
||||
auto index = archived.indexOf(set->id);
|
||||
if (const auto set = Stickers::FeedSet(*setData)) {
|
||||
const auto index = archived.indexOf(set->id);
|
||||
if (archived.isEmpty() || index != archived.size() - 1) {
|
||||
changedSets = true;
|
||||
if (index < archived.size() - 1) {
|
||||
@@ -229,7 +230,7 @@ void StickersBox::getArchivedDone(uint64 offsetId, const MTPmessages_ArchivedSti
|
||||
}
|
||||
archived.push_back(set->id);
|
||||
}
|
||||
if (_archived.widget()->appendSet(*set)) {
|
||||
if (_archived.widget()->appendSet(set)) {
|
||||
addedSet = true;
|
||||
if (set->stickers.isEmpty() || (set->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
|
||||
_session->api().scheduleStickerSetRequest(set->id, set->access);
|
||||
@@ -373,17 +374,24 @@ void StickersBox::loadMoreArchived() {
|
||||
}
|
||||
|
||||
uint64 lastId = 0;
|
||||
for (auto setIt = _session->data().archivedStickerSetsOrder().cend(), e = _session->data().archivedStickerSetsOrder().cbegin(); setIt != e;) {
|
||||
const auto &order = _session->data().archivedStickerSetsOrder();
|
||||
const auto &sets = _session->data().stickerSets();
|
||||
for (auto setIt = order.cend(), e = order.cbegin(); setIt != e;) {
|
||||
--setIt;
|
||||
auto it = _session->data().stickerSets().constFind(*setIt);
|
||||
if (it != _session->data().stickerSets().cend()) {
|
||||
if (it->flags & MTPDstickerSet::Flag::f_archived) {
|
||||
lastId = it->id;
|
||||
auto it = sets.find(*setIt);
|
||||
if (it != sets.cend()) {
|
||||
if (it->second->flags & MTPDstickerSet::Flag::f_archived) {
|
||||
lastId = it->second->id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_archivedRequestId = MTP::send(MTPmessages_GetArchivedStickers(MTP_flags(0), MTP_long(lastId), MTP_int(kArchivedLimitPerPage)), rpcDone(&StickersBox::getArchivedDone, lastId));
|
||||
_archivedRequestId = MTP::send(
|
||||
MTPmessages_GetArchivedStickers(
|
||||
MTP_flags(0),
|
||||
MTP_long(lastId),
|
||||
MTP_int(kArchivedLimitPerPage)),
|
||||
rpcDone(&StickersBox::getArchivedDone, lastId));
|
||||
}
|
||||
|
||||
void StickersBox::paintEvent(QPaintEvent *e) {
|
||||
@@ -489,13 +497,14 @@ QPixmap StickersBox::grabContentCache() {
|
||||
}
|
||||
|
||||
void StickersBox::installSet(uint64 setId) {
|
||||
auto &sets = _session->data().stickerSetsRef();
|
||||
auto it = sets.find(setId);
|
||||
const auto &sets = _session->data().stickerSets();
|
||||
const auto it = sets.find(setId);
|
||||
if (it == sets.cend()) {
|
||||
rebuildList();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto set = it->second.get();
|
||||
if (_localRemoved.contains(setId)) {
|
||||
_localRemoved.removeOne(setId);
|
||||
if (_installed.widget()) _installed.widget()->setRemovedSets(_localRemoved);
|
||||
@@ -503,11 +512,11 @@ void StickersBox::installSet(uint64 setId) {
|
||||
if (_archived.widget()) _archived.widget()->setRemovedSets(_localRemoved);
|
||||
if (_attached.widget()) _attached.widget()->setRemovedSets(_localRemoved);
|
||||
}
|
||||
if (!(it->flags & MTPDstickerSet::Flag::f_installed_date)
|
||||
|| (it->flags & MTPDstickerSet::Flag::f_archived)) {
|
||||
if (!(set->flags & MTPDstickerSet::Flag::f_installed_date)
|
||||
|| (set->flags & MTPDstickerSet::Flag::f_archived)) {
|
||||
MTP::send(
|
||||
MTPmessages_InstallStickerSet(
|
||||
Stickers::inputSetId(*it),
|
||||
set->mtpInput(),
|
||||
MTP_boolFalse()),
|
||||
rpcDone(&StickersBox::installDone),
|
||||
rpcFail(&StickersBox::installFail, setId));
|
||||
@@ -526,8 +535,8 @@ void StickersBox::installDone(const MTPmessages_StickerSetInstallResult &result)
|
||||
bool StickersBox::installFail(uint64 setId, const RPCError &error) {
|
||||
if (MTP::isDefaultHandledError(error)) return false;
|
||||
|
||||
auto &sets = _session->data().stickerSetsRef();
|
||||
auto it = sets.find(setId);
|
||||
const auto &sets = _session->data().stickerSets();
|
||||
const auto it = sets.find(setId);
|
||||
if (it == sets.cend()) {
|
||||
rebuildList();
|
||||
return true;
|
||||
@@ -550,12 +559,14 @@ void StickersBox::requestArchivedSets() {
|
||||
preloadArchivedSets();
|
||||
}
|
||||
|
||||
auto &sets = _session->data().stickerSets();
|
||||
for_const (auto setId, _session->data().archivedStickerSetsOrder()) {
|
||||
auto it = sets.constFind(setId);
|
||||
const auto &sets = _session->data().stickerSets();
|
||||
const auto &order = _session->data().archivedStickerSetsOrder();
|
||||
for (const auto setId : order) {
|
||||
auto it = sets.find(setId);
|
||||
if (it != sets.cend()) {
|
||||
if (it->stickers.isEmpty() && (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
|
||||
_session->api().scheduleStickerSetRequest(setId, it->access);
|
||||
const auto set = it->second.get();
|
||||
if (set->stickers.isEmpty() && (set->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
|
||||
_session->api().scheduleStickerSetRequest(setId, set->access);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -624,9 +635,7 @@ void StickersBox::setInnerFocus() {
|
||||
StickersBox::~StickersBox() = default;
|
||||
|
||||
StickersBox::Inner::Row::Row(
|
||||
uint64 id,
|
||||
uint64 accessHash,
|
||||
ImagePtr thumbnail,
|
||||
not_null<Stickers::Set*> set,
|
||||
DocumentData *sticker,
|
||||
int32 count,
|
||||
const QString &title,
|
||||
@@ -638,9 +647,7 @@ StickersBox::Inner::Row::Row(
|
||||
bool removed,
|
||||
int32 pixw,
|
||||
int32 pixh)
|
||||
: id(id)
|
||||
, accessHash(accessHash)
|
||||
, thumbnail(thumbnail)
|
||||
: set(set)
|
||||
, sticker(sticker)
|
||||
, count(count)
|
||||
, title(title)
|
||||
@@ -656,6 +663,10 @@ StickersBox::Inner::Row::Row(
|
||||
|
||||
StickersBox::Inner::Row::~Row() = default;
|
||||
|
||||
bool StickersBox::Inner::Row::isRecentSet() const {
|
||||
return (set->id == Stickers::CloudRecentSetId);
|
||||
}
|
||||
|
||||
StickersBox::Inner::Inner(
|
||||
QWidget *parent,
|
||||
not_null<Main::Session*> session,
|
||||
@@ -804,8 +815,8 @@ QRect StickersBox::Inner::relativeButtonRect(bool removeButton) const {
|
||||
return QRect(buttonx, buttony, buttonw, buttonh);
|
||||
}
|
||||
|
||||
void StickersBox::Inner::paintRow(Painter &p, not_null<Row*> set, int index) {
|
||||
auto xadd = 0, yadd = qRound(set->yadd.current());
|
||||
void StickersBox::Inner::paintRow(Painter &p, not_null<Row*> row, int index) {
|
||||
auto xadd = 0, yadd = qRound(row->yadd.current());
|
||||
if (xadd || yadd) p.translate(xadd, yadd);
|
||||
|
||||
if (_megagroupSet) {
|
||||
@@ -817,8 +828,8 @@ void StickersBox::Inner::paintRow(Painter &p, not_null<Row*> set, int index) {
|
||||
}();
|
||||
if (index >= 0 && index == selectedIndex) {
|
||||
p.fillRect(0, 0, width(), _rowHeight, st::contactsBgOver);
|
||||
if (set->ripple) {
|
||||
set->ripple->paint(p, 0, 0, width());
|
||||
if (row->ripple) {
|
||||
row->ripple->paint(p, 0, 0, width());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -833,24 +844,24 @@ void StickersBox::Inner::paintRow(Painter &p, not_null<Row*> set, int index) {
|
||||
current = reachedOpacity;
|
||||
}
|
||||
}
|
||||
auto row = myrtlrect(st::contactsPadding.left() / 2, st::contactsPadding.top() / 2, width() - (st::contactsPadding.left() / 2) - _scrollbar - st::contactsPadding.left() / 2, _rowHeight - ((st::contactsPadding.top() + st::contactsPadding.bottom()) / 2));
|
||||
auto rect = myrtlrect(st::contactsPadding.left() / 2, st::contactsPadding.top() / 2, width() - (st::contactsPadding.left() / 2) - _scrollbar - st::contactsPadding.left() / 2, _rowHeight - ((st::contactsPadding.top() + st::contactsPadding.bottom()) / 2));
|
||||
p.setOpacity(current);
|
||||
Ui::Shadow::paint(p, row, width(), st::boxRoundShadow);
|
||||
Ui::Shadow::paint(p, rect, width(), st::boxRoundShadow);
|
||||
p.setOpacity(1);
|
||||
|
||||
App::roundRect(p, row, st::boxBg, BoxCorners);
|
||||
App::roundRect(p, rect, st::boxBg, BoxCorners);
|
||||
|
||||
p.setOpacity(1. - current);
|
||||
paintFakeButton(p, set, index);
|
||||
paintFakeButton(p, row, index);
|
||||
p.setOpacity(1.);
|
||||
} else if (!_megagroupSet) {
|
||||
paintFakeButton(p, set, index);
|
||||
paintFakeButton(p, row, index);
|
||||
}
|
||||
} else if (!_megagroupSet) {
|
||||
paintFakeButton(p, set, index);
|
||||
paintFakeButton(p, row, index);
|
||||
}
|
||||
|
||||
if (set->removed && _section == Section::Installed) {
|
||||
if (row->removed && _section == Section::Installed) {
|
||||
p.setOpacity(st::stickersRowDisabledOpacity);
|
||||
}
|
||||
|
||||
@@ -858,13 +869,13 @@ void StickersBox::Inner::paintRow(Painter &p, not_null<Row*> set, int index) {
|
||||
|
||||
if (!_megagroupSet && _section == Section::Installed) {
|
||||
stickerx += st::stickersReorderIcon.width() + st::stickersReorderSkip;
|
||||
if (!set->isRecentSet()) {
|
||||
if (!row->isRecentSet()) {
|
||||
st::stickersReorderIcon.paint(p, st::contactsPadding.left(), (_rowHeight - st::stickersReorderIcon.height()) / 2, width());
|
||||
}
|
||||
}
|
||||
|
||||
if (set->sticker) {
|
||||
paintRowThumbnail(p, set, stickerx);
|
||||
if (row->sticker) {
|
||||
paintRowThumbnail(p, row, stickerx);
|
||||
}
|
||||
|
||||
int namex = stickerx + st::contactsPhotoSize + st::contactsPadding.left();
|
||||
@@ -875,19 +886,19 @@ void StickersBox::Inner::paintRow(Painter &p, not_null<Row*> set, int index) {
|
||||
|
||||
p.setFont(st::contactsNameStyle.font);
|
||||
p.setPen(st::contactsNameFg);
|
||||
p.drawTextLeft(namex, namey, width(), set->title, set->titleWidth);
|
||||
p.drawTextLeft(namex, namey, width(), row->title, row->titleWidth);
|
||||
|
||||
if (set->unread) {
|
||||
if (row->unread) {
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::stickersFeaturedUnreadBg);
|
||||
|
||||
{
|
||||
PainterHighQualityEnabler hq(p);
|
||||
p.drawEllipse(style::rtlrect(namex + set->titleWidth + st::stickersFeaturedUnreadSkip, namey + st::stickersFeaturedUnreadTop, st::stickersFeaturedUnreadSize, st::stickersFeaturedUnreadSize, width()));
|
||||
p.drawEllipse(style::rtlrect(namex + row->titleWidth + st::stickersFeaturedUnreadSkip, namey + st::stickersFeaturedUnreadTop, st::stickersFeaturedUnreadSize, st::stickersFeaturedUnreadSize, width()));
|
||||
}
|
||||
}
|
||||
|
||||
auto statusText = (set->count > 0) ? tr::lng_stickers_count(tr::now, lt_count, set->count) : tr::lng_contacts_loading(tr::now);
|
||||
auto statusText = (row->count > 0) ? tr::lng_stickers_count(tr::now, lt_count, row->count) : tr::lng_contacts_loading(tr::now);
|
||||
|
||||
p.setFont(st::contactsStatusFont);
|
||||
p.setPen(st::contactsStatusFg);
|
||||
@@ -899,30 +910,39 @@ void StickersBox::Inner::paintRow(Painter &p, not_null<Row*> set, int index) {
|
||||
|
||||
void StickersBox::Inner::paintRowThumbnail(
|
||||
Painter &p,
|
||||
not_null<Row*> set,
|
||||
not_null<Row*> row,
|
||||
int left) {
|
||||
const auto origin = Data::FileOriginStickerSet(
|
||||
set->id,
|
||||
set->accessHash);
|
||||
const auto thumb = set->thumbnail
|
||||
? set->thumbnail.get()
|
||||
: set->sticker->thumbnail();
|
||||
if (!thumb) {
|
||||
return;
|
||||
row->set->id,
|
||||
row->set->access);
|
||||
if (row->set->hasThumbnail()) {
|
||||
if (!row->thumbnailMedia) {
|
||||
row->thumbnailMedia = row->set->createThumbnailView();
|
||||
row->set->loadThumbnail();
|
||||
}
|
||||
} else if (row->sticker) {
|
||||
if (!row->stickerMedia) {
|
||||
row->stickerMedia = row->sticker->createMediaView();
|
||||
row->stickerMedia->thumbnailWanted(origin);
|
||||
}
|
||||
}
|
||||
thumb->load(origin);
|
||||
validateLottieAnimation(set);
|
||||
if (!set->lottie) {
|
||||
if (!thumb->loaded()) {
|
||||
validateLottieAnimation(row);
|
||||
if (!row->lottie) {
|
||||
const auto thumb = row->thumbnailMedia
|
||||
? row->thumbnailMedia->image()
|
||||
: row->stickerMedia
|
||||
? row->stickerMedia->thumbnail()
|
||||
: nullptr;
|
||||
if (!thumb) {
|
||||
return;
|
||||
}
|
||||
p.drawPixmapLeft(
|
||||
left + (st::contactsPhotoSize - set->pixw) / 2,
|
||||
st::contactsPadding.top() + (st::contactsPhotoSize - set->pixh) / 2,
|
||||
left + (st::contactsPhotoSize - row->pixw) / 2,
|
||||
st::contactsPadding.top() + (st::contactsPhotoSize - row->pixh) / 2,
|
||||
width(),
|
||||
thumb->pix(origin, set->pixw, set->pixh));
|
||||
} else if (set->lottie->ready()) {
|
||||
const auto frame = set->lottie->frame();
|
||||
thumb->pix(row->pixw, row->pixh));
|
||||
} else if (row->lottie->ready()) {
|
||||
const auto frame = row->lottie->frame();
|
||||
const auto size = frame.size() / cIntRetinaFactor();
|
||||
p.drawImage(
|
||||
QRect(
|
||||
@@ -935,19 +955,21 @@ void StickersBox::Inner::paintRowThumbnail(
|
||||
const auto paused = controller->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::Layer);
|
||||
if (!paused) {
|
||||
set->lottie->markFrameShown();
|
||||
row->lottie->markFrameShown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StickersBox::Inner::validateLottieAnimation(not_null<Row*> set) {
|
||||
if (set->lottie
|
||||
|| !Stickers::HasLottieThumbnail(set->thumbnail, set->sticker)) {
|
||||
void StickersBox::Inner::validateLottieAnimation(not_null<Row*> row) {
|
||||
if (row->lottie
|
||||
|| !Stickers::HasLottieThumbnail(
|
||||
row->thumbnailMedia.get(),
|
||||
row->stickerMedia.get())) {
|
||||
return;
|
||||
}
|
||||
auto player = Stickers::LottieThumbnail(
|
||||
set->thumbnail,
|
||||
set->sticker,
|
||||
row->thumbnailMedia.get(),
|
||||
row->stickerMedia.get(),
|
||||
Stickers::LottieSize::SetsListThumbnail,
|
||||
QSize(
|
||||
st::contactsPhotoSize,
|
||||
@@ -955,21 +977,21 @@ void StickersBox::Inner::validateLottieAnimation(not_null<Row*> set) {
|
||||
if (!player) {
|
||||
return;
|
||||
}
|
||||
set->lottie = std::move(player);
|
||||
set->lottie->updates(
|
||||
row->lottie = std::move(player);
|
||||
row->lottie->updates(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateRowThumbnail(set);
|
||||
updateRowThumbnail(row);
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void StickersBox::Inner::updateRowThumbnail(not_null<Row*> set) {
|
||||
void StickersBox::Inner::updateRowThumbnail(not_null<Row*> row) {
|
||||
const auto rowTop = [&] {
|
||||
if (set == _megagroupSelectedSet.get()) {
|
||||
if (row == _megagroupSelectedSet.get()) {
|
||||
return _megagroupDivider->y() - _rowHeight;
|
||||
}
|
||||
auto top = _itemsTop;
|
||||
for (const auto &row : _rows) {
|
||||
if (row.get() == set) {
|
||||
for (const auto &entry : _rows) {
|
||||
if (entry.get() == row) {
|
||||
return top + qRound(row->yadd.current());
|
||||
}
|
||||
top += _rowHeight;
|
||||
@@ -987,10 +1009,10 @@ void StickersBox::Inner::updateRowThumbnail(not_null<Row*> set) {
|
||||
st::contactsPhotoSize);
|
||||
}
|
||||
|
||||
void StickersBox::Inner::paintFakeButton(Painter &p, not_null<Row*> set, int index) {
|
||||
auto removeButton = (_section == Section::Installed && !set->removed);
|
||||
void StickersBox::Inner::paintFakeButton(Painter &p, not_null<Row*> row, int index) {
|
||||
auto removeButton = (_section == Section::Installed && !row->removed);
|
||||
auto rect = relativeButtonRect(removeButton);
|
||||
if (_section != Section::Installed && set->installed && !set->archived && !set->removed) {
|
||||
if (_section != Section::Installed && row->installed && !row->archived && !row->removed) {
|
||||
// Checkbox after installed from Trending or Archived.
|
||||
int checkx = width() - (st::contactsPadding.right() + st::contactsCheckPosition.x() + (rect.width() + st::stickersFeaturedInstalled.width()) / 2);
|
||||
int checky = st::contactsPadding.top() + (st::contactsPhotoSize - st::stickersFeaturedInstalled.height()) / 2;
|
||||
@@ -999,10 +1021,10 @@ void StickersBox::Inner::paintFakeButton(Painter &p, not_null<Row*> set, int ind
|
||||
auto selected = (index == _actionSel && _actionDown < 0) || (index == _actionDown);
|
||||
if (removeButton) {
|
||||
// Trash icon button when not disabled in Installed.
|
||||
if (set->ripple) {
|
||||
set->ripple->paint(p, rect.x(), rect.y(), width());
|
||||
if (set->ripple->empty()) {
|
||||
set->ripple.reset();
|
||||
if (row->ripple) {
|
||||
row->ripple->paint(p, rect.x(), rect.y(), width());
|
||||
if (row->ripple->empty()) {
|
||||
row->ripple.reset();
|
||||
}
|
||||
}
|
||||
auto &icon = selected ? st::stickersRemove.iconOver : st::stickersRemove.icon;
|
||||
@@ -1018,10 +1040,10 @@ void StickersBox::Inner::paintFakeButton(Painter &p, not_null<Row*> set, int ind
|
||||
auto &text = (_section == Section::Installed) ? _undoText : _addText;
|
||||
auto &textBg = selected ? st.textBgOver : st.textBg;
|
||||
App::roundRect(p, myrtlrect(rect), textBg, ImageRoundRadius::Small);
|
||||
if (set->ripple) {
|
||||
set->ripple->paint(p, rect.x(), rect.y(), width());
|
||||
if (set->ripple->empty()) {
|
||||
set->ripple.reset();
|
||||
if (row->ripple) {
|
||||
row->ripple->paint(p, rect.x(), rect.y(), width());
|
||||
if (row->ripple->empty()) {
|
||||
row->ripple.reset();
|
||||
}
|
||||
}
|
||||
p.setFont(st.font);
|
||||
@@ -1054,19 +1076,19 @@ void StickersBox::Inner::setActionDown(int newActionDown) {
|
||||
}
|
||||
if (_actionDown >= 0 && _actionDown < _rows.size()) {
|
||||
update(0, _itemsTop + _actionDown * _rowHeight, width(), _rowHeight);
|
||||
auto &set = _rows[_actionDown];
|
||||
if (set->ripple) {
|
||||
set->ripple->lastStop();
|
||||
const auto row = _rows[_actionDown].get();
|
||||
if (row->ripple) {
|
||||
row->ripple->lastStop();
|
||||
}
|
||||
}
|
||||
_actionDown = newActionDown;
|
||||
if (_actionDown >= 0 && _actionDown < _rows.size()) {
|
||||
update(0, _itemsTop + _actionDown * _rowHeight, width(), _rowHeight);
|
||||
auto &set = _rows[_actionDown];
|
||||
auto removeButton = (_section == Section::Installed && !set->removed);
|
||||
if (!set->ripple) {
|
||||
const auto row = _rows[_actionDown].get();
|
||||
auto removeButton = (_section == Section::Installed && !row->removed);
|
||||
if (!row->ripple) {
|
||||
if (_section == Section::Installed) {
|
||||
if (set->removed) {
|
||||
if (row->removed) {
|
||||
auto rippleSize = QSize(_undoWidth - st::stickersUndoRemove.width, st::stickersUndoRemove.height);
|
||||
auto rippleMask = Ui::RippleAnimation::roundRectMask(rippleSize, st::buttonRadius);
|
||||
ensureRipple(st::stickersUndoRemove.ripple, std::move(rippleMask), removeButton);
|
||||
@@ -1075,15 +1097,15 @@ void StickersBox::Inner::setActionDown(int newActionDown) {
|
||||
auto rippleMask = Ui::RippleAnimation::ellipseMask(QSize(rippleSize, rippleSize));
|
||||
ensureRipple(st::stickersRemove.ripple, std::move(rippleMask), removeButton);
|
||||
}
|
||||
} else if (!set->installed || set->archived || set->removed) {
|
||||
} else if (!row->installed || row->archived || row->removed) {
|
||||
auto rippleSize = QSize(_addWidth - st::stickersTrendingAdd.width, st::stickersTrendingAdd.height);
|
||||
auto rippleMask = Ui::RippleAnimation::roundRectMask(rippleSize, st::buttonRadius);
|
||||
ensureRipple(st::stickersTrendingAdd.ripple, std::move(rippleMask), removeButton);
|
||||
}
|
||||
}
|
||||
if (set->ripple) {
|
||||
if (row->ripple) {
|
||||
auto rect = relativeButtonRect(removeButton);
|
||||
set->ripple->add(mapFromGlobal(QCursor::pos()) - QPoint(myrtlrect(rect).x(), _itemsTop + _actionDown * _rowHeight + rect.y()));
|
||||
row->ripple->add(mapFromGlobal(QCursor::pos()) - QPoint(myrtlrect(rect).x(), _itemsTop + _actionDown * _rowHeight + rect.y()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1092,14 +1114,6 @@ void StickersBox::Inner::setSelected(SelectedRow selected) {
|
||||
if (_selected == selected) {
|
||||
return;
|
||||
}
|
||||
if ((_megagroupSet || _section != Section::Installed)
|
||||
&& ((_selected.has_value() || _pressed.has_value()) != (selected.has_value() || _pressed.has_value()))) {
|
||||
if (!_inDragArea) {
|
||||
setCursor((selected.has_value() || _pressed.has_value())
|
||||
? style::cur_pointer
|
||||
: style::cur_default);
|
||||
}
|
||||
}
|
||||
auto countSelectedIndex = [&] {
|
||||
if (auto index = base::get_if<int>(&_selected)) {
|
||||
return *index;
|
||||
@@ -1111,6 +1125,7 @@ void StickersBox::Inner::setSelected(SelectedRow selected) {
|
||||
update(0, _itemsTop + selectedIndex * _rowHeight, width(), _rowHeight);
|
||||
}
|
||||
_selected = selected;
|
||||
updateCursor();
|
||||
selectedIndex = countSelectedIndex();
|
||||
if (_megagroupSet && selectedIndex >= 0 && selectedIndex < _rows.size()) {
|
||||
update(0, _itemsTop + selectedIndex * _rowHeight, width(), _rowHeight);
|
||||
@@ -1130,9 +1145,9 @@ void StickersBox::Inner::setPressed(SelectedRow pressed) {
|
||||
auto pressedIndex = countPressedIndex();
|
||||
if (_megagroupSet && pressedIndex >= 0 && pressedIndex < _rows.size()) {
|
||||
update(0, _itemsTop + pressedIndex * _rowHeight, width(), _rowHeight);
|
||||
auto &set = _rows[pressedIndex];
|
||||
if (set->ripple) {
|
||||
set->ripple->lastStop();
|
||||
const auto row = _rows[pressedIndex].get();
|
||||
if (row->ripple) {
|
||||
row->ripple->lastStop();
|
||||
}
|
||||
}
|
||||
_pressed = pressed;
|
||||
@@ -1216,15 +1231,15 @@ void StickersBox::Inner::onUpdateSelected() {
|
||||
auto selectedIndex = floorclamp(local.y() - _itemsTop, _rowHeight, 0, _rows.size() - 1);
|
||||
selected = selectedIndex;
|
||||
local.setY(local.y() - _itemsTop - selectedIndex * _rowHeight);
|
||||
auto &set = _rows[selectedIndex];
|
||||
if (!_megagroupSet && (_section == Section::Installed || !set->installed || set->archived || set->removed)) {
|
||||
auto removeButton = (_section == Section::Installed && !set->removed);
|
||||
const auto row = _rows[selectedIndex].get();
|
||||
if (!_megagroupSet && (_section == Section::Installed || !row->installed || row->archived || row->removed)) {
|
||||
auto removeButton = (_section == Section::Installed && !row->removed);
|
||||
auto rect = myrtlrect(relativeButtonRect(removeButton));
|
||||
actionSel = rect.contains(local) ? selectedIndex : -1;
|
||||
} else {
|
||||
actionSel = -1;
|
||||
}
|
||||
if (!_megagroupSet && _section == Section::Installed && !set->isRecentSet()) {
|
||||
if (!_megagroupSet && _section == Section::Installed && !row->isRecentSet()) {
|
||||
auto dragAreaWidth = st::contactsPadding.left() + st::stickersReorderIcon.width() + st::stickersReorderSkip;
|
||||
auto dragArea = myrtlrect(0, 0, dragAreaWidth, _rowHeight);
|
||||
inDragArea = dragArea.contains(local);
|
||||
@@ -1238,17 +1253,25 @@ void StickersBox::Inner::onUpdateSelected() {
|
||||
setSelected(selected);
|
||||
if (_inDragArea != inDragArea) {
|
||||
_inDragArea = inDragArea;
|
||||
setCursor(_inDragArea
|
||||
? style::cur_sizeall
|
||||
: ((_selected.has_value() || _pressed.has_value())
|
||||
? style::cur_pointer
|
||||
: style::cur_default));
|
||||
updateCursor();
|
||||
}
|
||||
setActionSel(actionSel);
|
||||
emit draggingScrollDelta(0);
|
||||
}
|
||||
}
|
||||
|
||||
void StickersBox::Inner::updateCursor() {
|
||||
setCursor(_inDragArea
|
||||
? style::cur_sizeall
|
||||
: (!_megagroupSet && _section == Section::Installed)
|
||||
? ((_actionSel >= 0 && (_actionDown < 0 || _actionDown == _actionSel))
|
||||
? style::cur_pointer
|
||||
: style::cur_default)
|
||||
: (_selected.has_value() || _pressed.has_value())
|
||||
? style::cur_pointer
|
||||
: style::cur_default);
|
||||
}
|
||||
|
||||
float64 StickersBox::Inner::aboveShadowOpacity() const {
|
||||
if (_above < 0) return 0;
|
||||
|
||||
@@ -1260,9 +1283,7 @@ float64 StickersBox::Inner::aboveShadowOpacity() const {
|
||||
void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
|
||||
auto pressed = std::exchange(_pressed, SelectedRow());
|
||||
|
||||
if (_section != Section::Installed && !_selected.has_value() && pressed.has_value()) {
|
||||
setCursor(style::cur_default);
|
||||
}
|
||||
updateCursor();
|
||||
|
||||
_mouse = e->globalPos();
|
||||
onUpdateSelected();
|
||||
@@ -1270,7 +1291,7 @@ void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
|
||||
if (_section == Section::Installed) {
|
||||
setRowRemoved(_actionDown, !_rows[_actionDown]->removed);
|
||||
} else if (_installSetCallback) {
|
||||
_installSetCallback(_rows[_actionDown]->id);
|
||||
_installSetCallback(_rows[_actionDown]->set->id);
|
||||
}
|
||||
} else if (_dragging >= 0) {
|
||||
QPoint local(mapFromGlobal(_mouse));
|
||||
@@ -1283,42 +1304,28 @@ void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
|
||||
|
||||
_dragging = _started = -1;
|
||||
} else if (pressed == _selected && _actionSel < 0 && _actionDown < 0) {
|
||||
auto selectedIndex = [&] {
|
||||
const auto selectedIndex = [&] {
|
||||
if (auto index = base::get_if<int>(&_selected)) {
|
||||
return *index;
|
||||
}
|
||||
return -1;
|
||||
}();
|
||||
auto getSetByRow = [&](const Row &row) -> const Stickers::Set* {
|
||||
auto &sets = _session->data().stickerSetsRef();
|
||||
if (!row.isRecentSet()) {
|
||||
auto it = sets.find(row.id);
|
||||
if (it != sets.cend()) {
|
||||
return &*it;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
auto showSetByRow = [&](const Row &row) {
|
||||
if (auto set = getSetByRow(row)) {
|
||||
setSelected(SelectedRow());
|
||||
Ui::show(
|
||||
Box<StickerSetBox>(
|
||||
App::wnd()->sessionController(),
|
||||
Stickers::inputSetId(*set)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
}
|
||||
const auto showSetByRow = [&](const Row &row) {
|
||||
setSelected(SelectedRow());
|
||||
Ui::show(
|
||||
Box<StickerSetBox>(
|
||||
App::wnd()->sessionController(),
|
||||
row.set->mtpInput()),
|
||||
Ui::LayerOption::KeepOther);
|
||||
};
|
||||
if (selectedIndex >= 0 && !_inDragArea) {
|
||||
auto &row = *_rows[selectedIndex];
|
||||
if (_megagroupSet) {
|
||||
if (auto set = getSetByRow(row)) {
|
||||
setMegagroupSelectedSet(MTP_inputStickerSetID(
|
||||
MTP_long(set->id),
|
||||
MTP_long(set->access)));
|
||||
const auto row = _rows[selectedIndex].get();
|
||||
if (!row->isRecentSet()) {
|
||||
if (_megagroupSet) {
|
||||
setMegagroupSelectedSet(row->set->mtpInput());
|
||||
} else {
|
||||
showSetByRow(*row);
|
||||
}
|
||||
} else {
|
||||
showSetByRow(row);
|
||||
}
|
||||
} else if (_megagroupSelectedSet && _selected.is<MegagroupSet>()) {
|
||||
showSetByRow(*_megagroupSelectedSet);
|
||||
@@ -1429,9 +1436,7 @@ void StickersBox::Inner::setActionSel(int32 actionSel) {
|
||||
if (_actionSel >= 0) update(0, _itemsTop + _actionSel * _rowHeight, width(), _rowHeight);
|
||||
_actionSel = actionSel;
|
||||
if (_actionSel >= 0) update(0, _itemsTop + _actionSel * _rowHeight, width(), _rowHeight);
|
||||
if (_section == Section::Installed) {
|
||||
setCursor((_actionSel >= 0 && (_actionDown < 0 || _actionDown == _actionSel)) ? style::cur_pointer : style::cur_default);
|
||||
}
|
||||
updateCursor();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1466,8 +1471,9 @@ void StickersBox::Inner::handleMegagroupSetAddressChange() {
|
||||
auto text = _megagroupSetField->getLastText().trimmed();
|
||||
if (text.isEmpty()) {
|
||||
if (_megagroupSelectedSet) {
|
||||
auto it = _session->data().stickerSets().constFind(_megagroupSelectedSet->id);
|
||||
if (it != _session->data().stickerSets().cend() && !it->shortName.isEmpty()) {
|
||||
const auto &sets = _session->data().stickerSets();
|
||||
const auto it = sets.find(_megagroupSelectedSet->set->id);
|
||||
if (it != sets.cend() && !it->second->shortName.isEmpty()) {
|
||||
setMegagroupSelectedSet(MTP_inputStickerSetEmpty());
|
||||
}
|
||||
}
|
||||
@@ -1477,7 +1483,9 @@ void StickersBox::Inner::handleMegagroupSetAddressChange() {
|
||||
)).done([=](const MTPmessages_StickerSet &result) {
|
||||
_megagroupSetRequestId = 0;
|
||||
auto set = Stickers::FeedSetFull(result);
|
||||
setMegagroupSelectedSet(MTP_inputStickerSetID(MTP_long(set->id), MTP_long(set->access)));
|
||||
setMegagroupSelectedSet(MTP_inputStickerSetID(
|
||||
MTP_long(set->id),
|
||||
MTP_long(set->access)));
|
||||
}).fail([=](const RPCError &error) {
|
||||
_megagroupSetRequestId = 0;
|
||||
setMegagroupSelectedSet(MTP_inputStickerSetEmpty());
|
||||
@@ -1499,32 +1507,33 @@ void StickersBox::Inner::rebuildMegagroupSet() {
|
||||
_megagroupSelectedShadow.destroy();
|
||||
return;
|
||||
}
|
||||
auto &set = _megagroupSetInput.c_inputStickerSetID();
|
||||
auto setId = set.vid().v;
|
||||
auto &sets = _session->data().stickerSets();
|
||||
auto &inputId = _megagroupSetInput.c_inputStickerSetID();
|
||||
auto setId = inputId.vid().v;
|
||||
const auto &sets = _session->data().stickerSets();
|
||||
auto it = sets.find(setId);
|
||||
if (it == sets.cend() || (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
|
||||
_session->api().scheduleStickerSetRequest(set.vid().v, set.vaccess_hash().v);
|
||||
if (it == sets.cend()
|
||||
|| (it->second->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
|
||||
_session->api().scheduleStickerSetRequest(
|
||||
inputId.vid().v,
|
||||
inputId.vaccess_hash().v);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto set = it->second.get();
|
||||
auto maxNameWidth = countMaxNameWidth();
|
||||
auto titleWidth = 0;
|
||||
auto title = fillSetTitle(*it, maxNameWidth, &titleWidth);
|
||||
auto count = fillSetCount(*it);
|
||||
auto thumbnail = ImagePtr();
|
||||
auto title = fillSetTitle(set, maxNameWidth, &titleWidth);
|
||||
auto count = fillSetCount(set);
|
||||
auto sticker = (DocumentData*)nullptr;
|
||||
auto pixw = 0, pixh = 0;
|
||||
fillSetCover(*it, &thumbnail, &sticker, &pixw, &pixh);
|
||||
fillSetCover(set, &sticker, &pixw, &pixh);
|
||||
auto installed = true, official = false, unread = false, archived = false, removed = false;
|
||||
if (!_megagroupSelectedSet || _megagroupSelectedSet->id != it->id) {
|
||||
_megagroupSetField->setText(it->shortName);
|
||||
if (!_megagroupSelectedSet || _megagroupSelectedSet->set->id != set->id) {
|
||||
_megagroupSetField->setText(set->shortName);
|
||||
_megagroupSetField->finishAnimating();
|
||||
}
|
||||
_megagroupSelectedSet = std::make_unique<Row>(
|
||||
it->id,
|
||||
it->access,
|
||||
thumbnail,
|
||||
set,
|
||||
sticker,
|
||||
count,
|
||||
title,
|
||||
@@ -1561,7 +1570,7 @@ void StickersBox::Inner::rebuild() {
|
||||
auto maxNameWidth = countMaxNameWidth();
|
||||
|
||||
clear();
|
||||
auto &order = ([&]() -> const Stickers::Order & {
|
||||
const auto &order = ([&]() -> const Stickers::Order & {
|
||||
if (_section == Section::Installed) {
|
||||
auto &result = _session->data().stickerSetsOrder();
|
||||
if (_megagroupSet && result.empty()) {
|
||||
@@ -1576,7 +1585,7 @@ void StickersBox::Inner::rebuild() {
|
||||
_rows.reserve(order.size() + 1);
|
||||
_shiftingStartTimes.reserve(order.size() + 1);
|
||||
|
||||
auto &sets = _session->data().stickerSets();
|
||||
const auto &sets = _session->data().stickerSets();
|
||||
if (_megagroupSet) {
|
||||
auto usingFeatured = _session->data().stickerSetsOrder().empty();
|
||||
_megagroupSubTitle->setText(usingFeatured
|
||||
@@ -1584,21 +1593,23 @@ void StickersBox::Inner::rebuild() {
|
||||
: tr::lng_stickers_group_from_your(tr::now));
|
||||
updateControlsGeometry();
|
||||
} else if (_section == Section::Installed) {
|
||||
auto cloudIt = sets.constFind(Stickers::CloudRecentSetId);
|
||||
if (cloudIt != sets.cend() && !cloudIt->stickers.isEmpty()) {
|
||||
rebuildAppendSet(cloudIt.value(), maxNameWidth);
|
||||
auto cloudIt = sets.find(Stickers::CloudRecentSetId);
|
||||
if (cloudIt != sets.cend() && !cloudIt->second->stickers.isEmpty()) {
|
||||
rebuildAppendSet(cloudIt->second.get(), maxNameWidth);
|
||||
}
|
||||
}
|
||||
for_const (auto setId, order) {
|
||||
auto it = sets.constFind(setId);
|
||||
for (const auto setId : order) {
|
||||
auto it = sets.find(setId);
|
||||
if (it == sets.cend()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
rebuildAppendSet(it.value(), maxNameWidth);
|
||||
const auto set = it->second.get();
|
||||
rebuildAppendSet(set, maxNameWidth);
|
||||
|
||||
if (it->stickers.isEmpty() || (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
|
||||
_session->api().scheduleStickerSetRequest(it->id, it->access);
|
||||
if (set->stickers.isEmpty()
|
||||
|| (set->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
|
||||
_session->api().scheduleStickerSetRequest(set->id, set->access);
|
||||
}
|
||||
}
|
||||
_session->api().requestStickerSets();
|
||||
@@ -1626,24 +1637,22 @@ void StickersBox::Inner::updateSize(int newWidth) {
|
||||
|
||||
void StickersBox::Inner::updateRows() {
|
||||
int maxNameWidth = countMaxNameWidth();
|
||||
auto &sets = _session->data().stickerSets();
|
||||
const auto &sets = _session->data().stickerSets();
|
||||
for (const auto &row : _rows) {
|
||||
const auto it = sets.constFind(row->id);
|
||||
const auto it = sets.find(row->set->id);
|
||||
if (it == sets.cend()) {
|
||||
continue;
|
||||
}
|
||||
const auto &set = it.value();
|
||||
const auto set = it->second.get();
|
||||
if (!row->sticker) {
|
||||
auto thumbnail = ImagePtr();
|
||||
auto sticker = (DocumentData*)nullptr;
|
||||
auto pixw = 0, pixh = 0;
|
||||
fillSetCover(set, &thumbnail, &sticker, &pixw, &pixh);
|
||||
fillSetCover(set, &sticker, &pixw, &pixh);
|
||||
if (sticker) {
|
||||
if ((row->thumbnail.get() != thumbnail.get())
|
||||
|| (!thumbnail && row->sticker != sticker)) {
|
||||
if (row->sticker != sticker && !row->thumbnailMedia) {
|
||||
row->lottie = nullptr;
|
||||
row->stickerMedia = nullptr;
|
||||
}
|
||||
row->thumbnail = thumbnail;
|
||||
row->sticker = sticker;
|
||||
row->pixw = pixw;
|
||||
row->pixh = pixh;
|
||||
@@ -1666,9 +1675,9 @@ void StickersBox::Inner::updateRows() {
|
||||
update();
|
||||
}
|
||||
|
||||
bool StickersBox::Inner::appendSet(const Stickers::Set &set) {
|
||||
for_const (auto &row, _rows) {
|
||||
if (row->id == set.id) {
|
||||
bool StickersBox::Inner::appendSet(not_null<Stickers::Set*> set) {
|
||||
for (const auto &row : _rows) {
|
||||
if (row->set == set) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1695,28 +1704,27 @@ int StickersBox::Inner::countMaxNameWidth() const {
|
||||
return namew;
|
||||
}
|
||||
|
||||
void StickersBox::Inner::rebuildAppendSet(const Stickers::Set &set, int maxNameWidth) {
|
||||
void StickersBox::Inner::rebuildAppendSet(
|
||||
not_null<Stickers::Set*> set,
|
||||
int maxNameWidth) {
|
||||
bool installed = true, official = true, unread = false, archived = false, removed = false;
|
||||
if (set.id != Stickers::CloudRecentSetId) {
|
||||
if (set->id != Stickers::CloudRecentSetId) {
|
||||
fillSetFlags(set, &installed, &official, &unread, &archived);
|
||||
}
|
||||
if (_section == Section::Installed && archived) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImagePtr thumbnail;
|
||||
DocumentData *sticker = nullptr;
|
||||
int pixw = 0, pixh = 0;
|
||||
fillSetCover(set, &thumbnail, &sticker, &pixw, &pixh);
|
||||
fillSetCover(set, &sticker, &pixw, &pixh);
|
||||
|
||||
int titleWidth = 0;
|
||||
QString title = fillSetTitle(set, maxNameWidth, &titleWidth);
|
||||
int count = fillSetCount(set);
|
||||
|
||||
_rows.push_back(std::make_unique<Row>(
|
||||
set.id,
|
||||
set.access,
|
||||
thumbnail,
|
||||
set,
|
||||
sticker,
|
||||
count,
|
||||
title,
|
||||
@@ -1731,19 +1739,26 @@ void StickersBox::Inner::rebuildAppendSet(const Stickers::Set &set, int maxNameW
|
||||
_shiftingStartTimes.push_back(0);
|
||||
}
|
||||
|
||||
void StickersBox::Inner::fillSetCover(const Stickers::Set &set, ImagePtr *thumbnail, DocumentData **outSticker, int *outWidth, int *outHeight) const {
|
||||
*thumbnail = set.thumbnail;
|
||||
if (set.stickers.isEmpty()) {
|
||||
void StickersBox::Inner::fillSetCover(
|
||||
not_null<Stickers::Set*> set,
|
||||
DocumentData **outSticker,
|
||||
int *outWidth,
|
||||
int *outHeight) const {
|
||||
if (set->stickers.isEmpty()) {
|
||||
*outSticker = nullptr;
|
||||
*outWidth = *outHeight = 0;
|
||||
return;
|
||||
}
|
||||
auto sticker = *outSticker = set.stickers.front();
|
||||
auto sticker = *outSticker = set->stickers.front();
|
||||
|
||||
const auto size = set.thumbnail
|
||||
? set.thumbnail->size()
|
||||
: sticker->thumbnail()
|
||||
? sticker->thumbnail()->size()
|
||||
const auto size = set->hasThumbnail()
|
||||
? QSize(
|
||||
set->thumbnailLocation().width(),
|
||||
set->thumbnailLocation().height())
|
||||
: sticker->hasThumbnail()
|
||||
? QSize(
|
||||
sticker->thumbnailLocation().width(),
|
||||
sticker->thumbnailLocation().height())
|
||||
: QSize(1, 1);
|
||||
auto pixw = size.width();
|
||||
auto pixh = size.height();
|
||||
@@ -1763,14 +1778,19 @@ void StickersBox::Inner::fillSetCover(const Stickers::Set &set, ImagePtr *thumbn
|
||||
*outHeight = pixh;
|
||||
}
|
||||
|
||||
int StickersBox::Inner::fillSetCount(const Stickers::Set &set) const {
|
||||
int result = set.stickers.isEmpty() ? set.count : set.stickers.size(), added = 0;
|
||||
if (set.id == Stickers::CloudRecentSetId) {
|
||||
auto customIt = _session->data().stickerSets().constFind(Stickers::CustomSetId);
|
||||
if (customIt != _session->data().stickerSets().cend()) {
|
||||
added = customIt->stickers.size();
|
||||
for_const (auto &sticker, Stickers::GetRecentPack()) {
|
||||
if (customIt->stickers.indexOf(sticker.first) < 0) {
|
||||
int StickersBox::Inner::fillSetCount(not_null<Stickers::Set*> set) const {
|
||||
int result = set->stickers.isEmpty()
|
||||
? set->count
|
||||
: set->stickers.size();
|
||||
auto added = 0;
|
||||
if (set->id == Stickers::CloudRecentSetId) {
|
||||
const auto &sets = _session->data().stickerSets();
|
||||
auto customIt = sets.find(Stickers::CustomSetId);
|
||||
if (customIt != sets.cend()) {
|
||||
added = customIt->second->stickers.size();
|
||||
const auto &recent = Stickers::GetRecentPack();
|
||||
for (const auto &sticker : recent) {
|
||||
if (customIt->second->stickers.indexOf(sticker.first) < 0) {
|
||||
++added;
|
||||
}
|
||||
}
|
||||
@@ -1781,8 +1801,11 @@ int StickersBox::Inner::fillSetCount(const Stickers::Set &set) const {
|
||||
return result + added;
|
||||
}
|
||||
|
||||
QString StickersBox::Inner::fillSetTitle(const Stickers::Set &set, int maxNameWidth, int *outTitleWidth) const {
|
||||
auto result = set.title;
|
||||
QString StickersBox::Inner::fillSetTitle(
|
||||
not_null<Stickers::Set*> set,
|
||||
int maxNameWidth,
|
||||
int *outTitleWidth) const {
|
||||
auto result = set->title;
|
||||
int titleWidth = st::contactsNameStyle.font->width(result);
|
||||
if (titleWidth > maxNameWidth) {
|
||||
result = st::contactsNameStyle.font->elided(result, maxNameWidth);
|
||||
@@ -1795,16 +1818,16 @@ QString StickersBox::Inner::fillSetTitle(const Stickers::Set &set, int maxNameWi
|
||||
}
|
||||
|
||||
void StickersBox::Inner::fillSetFlags(
|
||||
const Stickers::Set &set,
|
||||
not_null<Stickers::Set*> set,
|
||||
bool *outInstalled,
|
||||
bool *outOfficial,
|
||||
bool *outUnread,
|
||||
bool *outArchived) {
|
||||
*outInstalled = (set.flags & MTPDstickerSet::Flag::f_installed_date);
|
||||
*outOfficial = (set.flags & MTPDstickerSet::Flag::f_official);
|
||||
*outArchived = (set.flags & MTPDstickerSet::Flag::f_archived);
|
||||
*outInstalled = (set->flags & MTPDstickerSet::Flag::f_installed_date);
|
||||
*outOfficial = (set->flags & MTPDstickerSet::Flag::f_official);
|
||||
*outArchived = (set->flags & MTPDstickerSet::Flag::f_archived);
|
||||
if (_section == Section::Featured) {
|
||||
*outUnread = (set.flags & MTPDstickerSet_ClientFlag::f_unread);
|
||||
*outUnread = (set->flags & MTPDstickerSet_ClientFlag::f_unread);
|
||||
} else {
|
||||
*outUnread = false;
|
||||
}
|
||||
@@ -1816,7 +1839,7 @@ Stickers::Order StickersBox::Inner::collectSets(Check check) const {
|
||||
result.reserve(_rows.size());
|
||||
for_const (auto &row, _rows) {
|
||||
if (check(row.get())) {
|
||||
result.push_back(row->id);
|
||||
result.push_back(row->set->id);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -1843,7 +1866,7 @@ Stickers::Order StickersBox::Inner::getRemovedSets() const {
|
||||
int StickersBox::Inner::getRowIndex(uint64 setId) const {
|
||||
for (auto i = 0, count = int(_rows.size()); i != count; ++i) {
|
||||
auto &row = _rows[i];
|
||||
if (row->id == setId) {
|
||||
if (row->set->id == setId) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
@@ -1866,7 +1889,7 @@ void StickersBox::Inner::setFullOrder(const Stickers::Order &order) {
|
||||
|
||||
void StickersBox::Inner::setRemovedSets(const Stickers::Order &removed) {
|
||||
for (auto i = 0, count = int(_rows.size()); i != count; ++i) {
|
||||
setRowRemoved(i, removed.contains(_rows[i]->id));
|
||||
setRowRemoved(i, removed.contains(_rows[i]->set->id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1898,21 +1921,28 @@ void StickersBox::Inner::readVisibleSets() {
|
||||
int rowFrom = floorclamp(itemsVisibleTop, _rowHeight, 0, _rows.size());
|
||||
int rowTo = ceilclamp(itemsVisibleBottom, _rowHeight, 0, _rows.size());
|
||||
for (int i = rowFrom; i < rowTo; ++i) {
|
||||
if (!_rows[i]->unread) {
|
||||
const auto row = _rows[i].get();
|
||||
if (!row->unread) {
|
||||
continue;
|
||||
}
|
||||
if (i * _rowHeight < itemsVisibleTop || (i + 1) * _rowHeight > itemsVisibleBottom) {
|
||||
if ((i * _rowHeight < itemsVisibleTop)
|
||||
|| ((i + 1) * _rowHeight > itemsVisibleBottom)) {
|
||||
continue;
|
||||
}
|
||||
const auto thumbnail = !_rows[i]->sticker
|
||||
? nullptr
|
||||
: _rows[i]->thumbnail
|
||||
? _rows[i]->thumbnail.get()
|
||||
: _rows[i]->sticker->thumbnail();
|
||||
if (!thumbnail
|
||||
|| thumbnail->loaded()
|
||||
|| _rows[i]->sticker->loaded()) {
|
||||
_session->api().readFeaturedSetDelayed(_rows[i]->id);
|
||||
const auto thumbnailLoading = row->set->hasThumbnail()
|
||||
? row->set->thumbnailLoading()
|
||||
: row->sticker
|
||||
? row->sticker->thumbnailLoading()
|
||||
: false;
|
||||
const auto thumbnailLoaded = row->set->hasThumbnail()
|
||||
? (row->thumbnailMedia
|
||||
&& (row->thumbnailMedia->image()
|
||||
|| !row->thumbnailMedia->content().isEmpty()))
|
||||
: row->sticker
|
||||
? (row->stickerMedia && row->stickerMedia->loaded())
|
||||
: true;
|
||||
if (!thumbnailLoading || thumbnailLoaded) {
|
||||
_session->api().readFeaturedSetDelayed(row->set->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/abstract_box.h"
|
||||
#include "base/timer.h"
|
||||
#include "mtproto/sender.h"
|
||||
#include "chat_helpers/stickers.h"
|
||||
#include "chat_helpers/stickers_set.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/special_fields.h"
|
||||
|
||||
@@ -33,6 +33,18 @@ namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Data {
|
||||
class DocumentMedia;
|
||||
} // namespace Data
|
||||
|
||||
namespace Lottie {
|
||||
class SinglePlayer;
|
||||
} // namespace Lottie
|
||||
|
||||
namespace Stickers {
|
||||
class Set;
|
||||
} // namespace Stickers
|
||||
|
||||
class StickersBox final
|
||||
: public Ui::BoxContent
|
||||
, public RPCSender
|
||||
@@ -173,7 +185,7 @@ public:
|
||||
void rebuild();
|
||||
void updateSize(int newWidth = 0);
|
||||
void updateRows(); // refresh only pack cover stickers
|
||||
bool appendSet(const Stickers::Set &set);
|
||||
bool appendSet(not_null<Stickers::Set*> set);
|
||||
|
||||
Stickers::Order getOrder() const;
|
||||
Stickers::Order getFullOrder() const;
|
||||
@@ -219,9 +231,7 @@ public slots:
|
||||
private:
|
||||
struct Row {
|
||||
Row(
|
||||
uint64 id,
|
||||
uint64 accessHash,
|
||||
ImagePtr thumbnail,
|
||||
not_null<Stickers::Set*> set,
|
||||
DocumentData *sticker,
|
||||
int32 count,
|
||||
const QString &title,
|
||||
@@ -233,15 +243,14 @@ private:
|
||||
bool removed,
|
||||
int32 pixw,
|
||||
int32 pixh);
|
||||
bool isRecentSet() const {
|
||||
return (id == Stickers::CloudRecentSetId);
|
||||
}
|
||||
~Row();
|
||||
|
||||
uint64 id = 0;
|
||||
uint64 accessHash = 0;
|
||||
ImagePtr thumbnail;
|
||||
bool isRecentSet() const;
|
||||
|
||||
const not_null<Stickers::Set*> set;
|
||||
DocumentData *sticker = nullptr;
|
||||
std::shared_ptr<Data::DocumentMedia> stickerMedia;
|
||||
std::shared_ptr<Stickers::SetThumbnailView> thumbnailMedia;
|
||||
int32 count = 0;
|
||||
QString title;
|
||||
int titleWidth = 0;
|
||||
@@ -294,23 +303,24 @@ private:
|
||||
void ensureRipple(const style::RippleAnimation &st, QImage mask, bool removeButton);
|
||||
|
||||
bool shiftingAnimationCallback(crl::time now);
|
||||
void paintRow(Painter &p, not_null<Row*> set, int index);
|
||||
void paintRowThumbnail(Painter &p, not_null<Row*> set, int left);
|
||||
void paintFakeButton(Painter &p, not_null<Row*> set, int index);
|
||||
void paintRow(Painter &p, not_null<Row*> row, int index);
|
||||
void paintRowThumbnail(Painter &p, not_null<Row*> row, int left);
|
||||
void paintFakeButton(Painter &p, not_null<Row*> row, int index);
|
||||
void clear();
|
||||
void updateCursor();
|
||||
void setActionSel(int32 actionSel);
|
||||
float64 aboveShadowOpacity() const;
|
||||
void validateLottieAnimation(not_null<Row*> set);
|
||||
void updateRowThumbnail(not_null<Row*> set);
|
||||
void validateLottieAnimation(not_null<Row*> row);
|
||||
void updateRowThumbnail(not_null<Row*> row);
|
||||
|
||||
void readVisibleSets();
|
||||
|
||||
void updateControlsGeometry();
|
||||
void rebuildAppendSet(const Stickers::Set &set, int maxNameWidth);
|
||||
void fillSetCover(const Stickers::Set &set, ImagePtr *thumbnail, DocumentData **outSticker, int *outWidth, int *outHeight) const;
|
||||
int fillSetCount(const Stickers::Set &set) const;
|
||||
QString fillSetTitle(const Stickers::Set &set, int maxNameWidth, int *outTitleWidth) const;
|
||||
void fillSetFlags(const Stickers::Set &set, bool *outInstalled, bool *outOfficial, bool *outUnread, bool *outArchived);
|
||||
void rebuildAppendSet(not_null<Stickers::Set*> set, int maxNameWidth);
|
||||
void fillSetCover(not_null<Stickers::Set*> set, DocumentData **outSticker, int *outWidth, int *outHeight) const;
|
||||
int fillSetCount(not_null<Stickers::Set*> set) const;
|
||||
QString fillSetTitle(not_null<Stickers::Set*> set, int maxNameWidth, int *outTitleWidth) const;
|
||||
void fillSetFlags(not_null<Stickers::Set*> set, bool *outInstalled, bool *outOfficial, bool *outUnread, bool *outArchived);
|
||||
void rebuildMegagroupSet();
|
||||
void fixupMegagroupSetAddress();
|
||||
void handleMegagroupSetAddressChange();
|
||||
|
||||
@@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "main/main_session.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "mtproto/facade.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
||||
|
||||
@@ -18,23 +18,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "media/audio/media_audio_track.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "calls/calls_panel.h"
|
||||
#include "calls/calls_controller.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_session.h"
|
||||
#include "facades.h"
|
||||
|
||||
#ifdef slots
|
||||
#undef slots
|
||||
#define NEED_TO_RESTORE_SLOTS
|
||||
#endif // slots
|
||||
|
||||
#include <VoIPController.h>
|
||||
#include <VoIPServerConfig.h>
|
||||
|
||||
#ifdef NEED_TO_RESTORE_SLOTS
|
||||
#define slots Q_SLOTS
|
||||
#undef NEED_TO_RESTORE_SLOTS
|
||||
#endif // NEED_TO_RESTORE_SLOTS
|
||||
|
||||
namespace Calls {
|
||||
namespace {
|
||||
|
||||
@@ -43,25 +31,25 @@ constexpr auto kHangupTimeoutMs = 5000;
|
||||
constexpr auto kSha256Size = 32;
|
||||
|
||||
void AppendEndpoint(
|
||||
std::vector<tgvoip::Endpoint> &list,
|
||||
std::vector<TgVoipEndpoint> &list,
|
||||
const MTPPhoneConnection &connection) {
|
||||
connection.match([&](const MTPDphoneConnection &data) {
|
||||
if (data.vpeer_tag().v.length() != 16) {
|
||||
return;
|
||||
}
|
||||
const auto ipv4 = tgvoip::IPv4Address(std::string(
|
||||
data.vip().v.constData(),
|
||||
data.vip().v.size()));
|
||||
const auto ipv6 = tgvoip::IPv6Address(std::string(
|
||||
data.vipv6().v.constData(),
|
||||
data.vipv6().v.size()));
|
||||
list.emplace_back(
|
||||
(int64_t)data.vid().v,
|
||||
(uint16_t)data.vport().v,
|
||||
ipv4,
|
||||
ipv6,
|
||||
tgvoip::Endpoint::Type::UDP_RELAY,
|
||||
(unsigned char*)data.vpeer_tag().v.data());
|
||||
auto endpoint = TgVoipEndpoint{
|
||||
.endpointId = (int64_t)data.vid().v,
|
||||
.host = TgVoipEdpointHost{
|
||||
.ipv4 = data.vip().v.toStdString(),
|
||||
.ipv6 = data.vipv6().v.toStdString() },
|
||||
.port = (uint16_t)data.vport().v,
|
||||
.type = TgVoipEndpointType::UdpRelay
|
||||
};
|
||||
const auto tag = data.vpeer_tag().v;
|
||||
if (tag.size() >= 16) {
|
||||
memcpy(endpoint.peerTag, tag.data(), 16);
|
||||
}
|
||||
list.push_back(std::move(endpoint));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -80,47 +68,25 @@ uint64 ComputeFingerprint(bytes::const_span authKey) {
|
||||
| (gsl::to_integer<uint64>(hash[12]));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Call::ControllerPointer::create() {
|
||||
Expects(_data == nullptr);
|
||||
|
||||
_data = std::make_unique<tgvoip::VoIPController>();
|
||||
[[nodiscard]] std::vector<std::string> CollectVersions() {
|
||||
return { TgVoip::getVersion() };
|
||||
}
|
||||
|
||||
void Call::ControllerPointer::reset() {
|
||||
if (const auto controller = base::take(_data)) {
|
||||
controller->Stop();
|
||||
[[nodiscard]] QVector<MTPstring> WrapVersions(
|
||||
const std::vector<std::string> &data) {
|
||||
auto result = QVector<MTPstring>();
|
||||
result.reserve(data.size());
|
||||
for (const auto &version : data) {
|
||||
result.push_back(MTP_string(version));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Call::ControllerPointer::empty() const {
|
||||
return (_data == nullptr);
|
||||
[[nodiscard]] QVector<MTPstring> CollectVersionsForApi() {
|
||||
return WrapVersions(CollectVersions());
|
||||
}
|
||||
|
||||
bool Call::ControllerPointer::operator==(std::nullptr_t) const {
|
||||
return empty();
|
||||
}
|
||||
|
||||
Call::ControllerPointer::operator bool() const {
|
||||
return !empty();
|
||||
}
|
||||
|
||||
tgvoip::VoIPController *Call::ControllerPointer::operator->() const {
|
||||
Expects(!empty());
|
||||
|
||||
return _data.get();
|
||||
}
|
||||
|
||||
tgvoip::VoIPController &Call::ControllerPointer::operator*() const {
|
||||
Expects(!empty());
|
||||
|
||||
return *_data;
|
||||
}
|
||||
|
||||
Call::ControllerPointer::~ControllerPointer() {
|
||||
reset();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Call::Delegate::~Delegate() = default;
|
||||
|
||||
@@ -199,8 +165,8 @@ void Call::startOutgoing() {
|
||||
MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p
|
||||
| MTPDphoneCallProtocol::Flag::f_udp_reflector),
|
||||
MTP_int(kMinLayer),
|
||||
MTP_int(tgvoip::VoIPController::GetConnectionMaxLayer()),
|
||||
MTP_vector(1, MTP_string("2.4.4")))
|
||||
MTP_int(TgVoip::getConnectionMaxLayer()),
|
||||
MTP_vector(CollectVersionsForApi()))
|
||||
)).done([=](const MTPphone_PhoneCall &result) {
|
||||
Expects(result.type() == mtpc_phone_phoneCall);
|
||||
|
||||
@@ -278,8 +244,8 @@ void Call::actuallyAnswer() {
|
||||
MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p
|
||||
| MTPDphoneCallProtocol::Flag::f_udp_reflector),
|
||||
MTP_int(kMinLayer),
|
||||
MTP_int(tgvoip::VoIPController::GetConnectionMaxLayer()),
|
||||
MTP_vector(1, MTP_string("2.4.4")))
|
||||
MTP_int(TgVoip::getConnectionMaxLayer()),
|
||||
MTP_vector(CollectVersionsForApi()))
|
||||
)).done([=](const MTPphone_PhoneCall &result) {
|
||||
Expects(result.type() == mtpc_phone_phoneCall);
|
||||
auto &call = result.c_phone_phoneCall();
|
||||
@@ -300,7 +266,7 @@ void Call::actuallyAnswer() {
|
||||
void Call::setMute(bool mute) {
|
||||
_mute = mute;
|
||||
if (_controller) {
|
||||
_controller->SetMicMute(_mute);
|
||||
_controller->setMuteMicrophone(_mute);
|
||||
}
|
||||
_muteChanged.notify(_mute);
|
||||
}
|
||||
@@ -334,8 +300,7 @@ void Call::redial() {
|
||||
}
|
||||
|
||||
QString Call::getDebugLog() const {
|
||||
const auto debug = _controller->GetDebugString();
|
||||
return QString::fromUtf8(debug.data(), debug.size());
|
||||
return QString::fromStdString(_controller->getDebugInfo());
|
||||
}
|
||||
|
||||
void Call::startWaitingTrack() {
|
||||
@@ -441,7 +406,9 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
|
||||
return false;
|
||||
}
|
||||
if (data.is_need_debug()) {
|
||||
auto debugLog = _controller ? _controller->GetDebugLog() : std::string();
|
||||
auto debugLog = _controller
|
||||
? _controller->getDebugInfo()
|
||||
: std::string();
|
||||
if (!debugLog.empty()) {
|
||||
MTP::send(
|
||||
MTPphone_SaveCallDebug(
|
||||
@@ -517,8 +484,8 @@ void Call::confirmAcceptedCall(const MTPDphoneCallAccepted &call) {
|
||||
MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p
|
||||
| MTPDphoneCallProtocol::Flag::f_udp_reflector),
|
||||
MTP_int(kMinLayer),
|
||||
MTP_int(tgvoip::VoIPController::GetConnectionMaxLayer()),
|
||||
MTP_vector(1, MTP_string("2.4.4")))
|
||||
MTP_int(TgVoip::getConnectionMaxLayer()),
|
||||
MTP_vector(CollectVersionsForApi()))
|
||||
)).done([this](const MTPphone_PhoneCall &result) {
|
||||
Expects(result.type() == mtpc_phone_phoneCall);
|
||||
|
||||
@@ -566,126 +533,123 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
|
||||
return;
|
||||
}
|
||||
|
||||
tgvoip::VoIPController::Config config;
|
||||
config.dataSaving = tgvoip::DATA_SAVING_NEVER;
|
||||
const auto &protocol = call.vprotocol().c_phoneCallProtocol();
|
||||
|
||||
TgVoipConfig config;
|
||||
config.dataSaving = TgVoipDataSaving::Never;
|
||||
config.enableAEC = !Platform::IsMac10_7OrGreater();
|
||||
config.enableNS = true;
|
||||
config.enableAGC = true;
|
||||
config.enableVolumeControl = true;
|
||||
config.initTimeout = Global::CallConnectTimeoutMs() / 1000;
|
||||
config.recvTimeout = Global::CallPacketTimeoutMs() / 1000;
|
||||
config.initializationTimeout = Global::CallConnectTimeoutMs() / 1000.;
|
||||
config.receiveTimeout = Global::CallPacketTimeoutMs() / 1000.;
|
||||
config.enableP2P = call.is_p2p_allowed();
|
||||
config.maxApiLayer = protocol.vmax_layer().v;
|
||||
if (Logs::DebugEnabled()) {
|
||||
auto callLogFolder = cWorkingDir() + qsl("DebugLogs");
|
||||
auto callLogPath = callLogFolder + qsl("/last_call_log.txt");
|
||||
auto callLogNative = QDir::toNativeSeparators(callLogPath);
|
||||
#ifdef Q_OS_WIN
|
||||
config.logFilePath = callLogNative.toStdWString();
|
||||
config.logPath = callLogNative.toStdWString();
|
||||
#else // Q_OS_WIN
|
||||
const auto callLogUtf = QFile::encodeName(callLogNative);
|
||||
config.logFilePath.resize(callLogUtf.size());
|
||||
ranges::copy(callLogUtf, config.logFilePath.begin());
|
||||
config.logPath.resize(callLogUtf.size());
|
||||
ranges::copy(callLogUtf, config.logPath.begin());
|
||||
#endif // Q_OS_WIN
|
||||
QFile(callLogPath).remove();
|
||||
QDir().mkpath(callLogFolder);
|
||||
}
|
||||
|
||||
const auto &protocol = call.vprotocol().c_phoneCallProtocol();
|
||||
auto endpoints = std::vector<tgvoip::Endpoint>();
|
||||
auto endpoints = std::vector<TgVoipEndpoint>();
|
||||
for (const auto &connection : call.vconnections().v) {
|
||||
AppendEndpoint(endpoints, connection);
|
||||
}
|
||||
|
||||
auto callbacks = tgvoip::VoIPController::Callbacks();
|
||||
callbacks.connectionStateChanged = [](
|
||||
tgvoip::VoIPController *controller,
|
||||
int state) {
|
||||
const auto call = static_cast<Call*>(controller->implData);
|
||||
call->handleControllerStateChange(controller, state);
|
||||
};
|
||||
callbacks.signalBarCountChanged = [](
|
||||
tgvoip::VoIPController *controller,
|
||||
int count) {
|
||||
const auto call = static_cast<Call*>(controller->implData);
|
||||
call->handleControllerBarCountChange(controller, count);
|
||||
};
|
||||
|
||||
_controller.create();
|
||||
if (_mute) {
|
||||
_controller->SetMicMute(_mute);
|
||||
}
|
||||
_controller->implData = static_cast<void*>(this);
|
||||
_controller->SetRemoteEndpoints(
|
||||
endpoints,
|
||||
call.is_p2p_allowed(),
|
||||
protocol.vmax_layer().v);
|
||||
_controller->SetConfig(config);
|
||||
_controller->SetCurrentAudioOutput(Global::CallOutputDeviceID().toStdString());
|
||||
_controller->SetCurrentAudioInput(Global::CallInputDeviceID().toStdString());
|
||||
_controller->SetOutputVolume(Global::CallOutputVolume()/100.0f);
|
||||
_controller->SetInputVolume(Global::CallInputVolume()/100.0f);
|
||||
#ifdef Q_OS_MAC
|
||||
_controller->SetAudioOutputDuckingEnabled(Global::CallAudioDuckingEnabled());
|
||||
#endif
|
||||
_controller->SetEncryptionKey(reinterpret_cast<char*>(_authKey.data()), (_type == Type::Outgoing));
|
||||
_controller->SetCallbacks(callbacks);
|
||||
auto proxy = TgVoipProxy();
|
||||
if (Global::UseProxyForCalls()
|
||||
&& (Global::ProxySettings() == MTP::ProxyData::Settings::Enabled)) {
|
||||
const auto &proxy = Global::SelectedProxy();
|
||||
if (proxy.supportsCalls()) {
|
||||
Assert(proxy.type == MTP::ProxyData::Type::Socks5);
|
||||
_controller->SetProxy(
|
||||
tgvoip::PROXY_SOCKS5,
|
||||
proxy.host.toStdString(),
|
||||
proxy.port,
|
||||
proxy.user.toStdString(),
|
||||
proxy.password.toStdString());
|
||||
const auto &selected = Global::SelectedProxy();
|
||||
if (selected.supportsCalls()) {
|
||||
Assert(selected.type == MTP::ProxyData::Type::Socks5);
|
||||
proxy.host = selected.host.toStdString();
|
||||
proxy.port = selected.port;
|
||||
proxy.login = selected.user.toStdString();
|
||||
proxy.password = selected.password.toStdString();
|
||||
}
|
||||
}
|
||||
_controller->Start();
|
||||
_controller->Connect();
|
||||
|
||||
auto encryptionKey = TgVoipEncryptionKey();
|
||||
encryptionKey.isOutgoing = (_type == Type::Outgoing);
|
||||
encryptionKey.value = ranges::view::all(
|
||||
_authKey
|
||||
) | ranges::view::transform([](bytes::type byte) {
|
||||
return static_cast<uint8_t>(byte);
|
||||
}) | ranges::to_vector;
|
||||
|
||||
_controller = MakeController(
|
||||
"2.4.4",
|
||||
config,
|
||||
TgVoipPersistentState(),
|
||||
endpoints,
|
||||
proxy.host.empty() ? nullptr : &proxy,
|
||||
TgVoipNetworkType::Unknown,
|
||||
encryptionKey);
|
||||
|
||||
const auto raw = _controller.get();
|
||||
raw->setOnStateUpdated([=](TgVoipState state) {
|
||||
handleControllerStateChange(raw, state);
|
||||
});
|
||||
raw->setOnSignalBarsUpdated([=](int count) {
|
||||
handleControllerBarCountChange(count);
|
||||
});
|
||||
if (_mute) {
|
||||
raw->setMuteMicrophone(_mute);
|
||||
}
|
||||
raw->setAudioOutputDevice(
|
||||
Global::CallOutputDeviceID().toStdString());
|
||||
raw->setAudioInputDevice(
|
||||
Global::CallInputDeviceID().toStdString());
|
||||
raw->setOutputVolume(Global::CallOutputVolume() / 100.0f);
|
||||
raw->setInputVolume(Global::CallInputVolume() / 100.0f);
|
||||
raw->setAudioOutputDuckingEnabled(Global::CallAudioDuckingEnabled());
|
||||
}
|
||||
|
||||
void Call::handleControllerStateChange(
|
||||
tgvoip::VoIPController *controller,
|
||||
int state) {
|
||||
not_null<Controller*> controller,
|
||||
TgVoipState state) {
|
||||
// NB! Can be called from an arbitrary thread!
|
||||
// This can be called from ~VoIPController()!
|
||||
// Expects(controller == _controller.get());
|
||||
Expects(controller->implData == static_cast<void*>(this));
|
||||
|
||||
switch (state) {
|
||||
case tgvoip::STATE_WAIT_INIT: {
|
||||
case TgVoipState::WaitInit: {
|
||||
DEBUG_LOG(("Call Info: State changed to WaitingInit."));
|
||||
setStateQueued(State::WaitingInit);
|
||||
} break;
|
||||
|
||||
case tgvoip::STATE_WAIT_INIT_ACK: {
|
||||
case TgVoipState::WaitInitAck: {
|
||||
DEBUG_LOG(("Call Info: State changed to WaitingInitAck."));
|
||||
setStateQueued(State::WaitingInitAck);
|
||||
} break;
|
||||
|
||||
case tgvoip::STATE_ESTABLISHED: {
|
||||
case TgVoipState::Established: {
|
||||
DEBUG_LOG(("Call Info: State changed to Established."));
|
||||
setStateQueued(State::Established);
|
||||
} break;
|
||||
|
||||
case tgvoip::STATE_FAILED: {
|
||||
auto error = controller->GetLastError();
|
||||
case TgVoipState::Failed: {
|
||||
auto error = QString::fromStdString(controller->getLastError());
|
||||
LOG(("Call Info: State changed to Failed, error: %1.").arg(error));
|
||||
setFailedQueued(error);
|
||||
} break;
|
||||
|
||||
default: LOG(("Call Error: Unexpected state in handleStateChange: %1").arg(state));
|
||||
default: LOG(("Call Error: Unexpected state in handleStateChange: %1"
|
||||
).arg(int(state)));
|
||||
}
|
||||
}
|
||||
|
||||
void Call::handleControllerBarCountChange(
|
||||
tgvoip::VoIPController *controller,
|
||||
int count) {
|
||||
void Call::handleControllerBarCountChange(int count) {
|
||||
// NB! Can be called from an arbitrary thread!
|
||||
// This can be called from ~VoIPController()!
|
||||
// Expects(controller == _controller.get());
|
||||
Expects(controller->implData == static_cast<void*>(this));
|
||||
|
||||
crl::on_main(this, [=] {
|
||||
setSignalBarCount(count);
|
||||
@@ -790,32 +754,30 @@ void Call::setState(State state) {
|
||||
}
|
||||
}
|
||||
|
||||
void Call::setCurrentAudioDevice(bool input, std::string deviceID){
|
||||
void Call::setCurrentAudioDevice(bool input, std::string deviceID) {
|
||||
if (_controller) {
|
||||
if (input) {
|
||||
_controller->SetCurrentAudioInput(deviceID);
|
||||
_controller->setAudioInputDevice(deviceID);
|
||||
} else {
|
||||
_controller->SetCurrentAudioOutput(deviceID);
|
||||
_controller->setAudioOutputDevice(deviceID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Call::setAudioVolume(bool input, float level){
|
||||
void Call::setAudioVolume(bool input, float level) {
|
||||
if (_controller) {
|
||||
if(input) {
|
||||
_controller->SetInputVolume(level);
|
||||
if (input) {
|
||||
_controller->setInputVolume(level);
|
||||
} else {
|
||||
_controller->SetOutputVolume(level);
|
||||
_controller->setOutputVolume(level);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Call::setAudioDuckingEnabled(bool enabled){
|
||||
#ifdef Q_OS_MAC
|
||||
void Call::setAudioDuckingEnabled(bool enabled) {
|
||||
if (_controller) {
|
||||
_controller->SetAudioOutputDuckingEnabled(enabled);
|
||||
_controller->setAudioOutputDuckingEnabled(enabled);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Call::finish(FinishType type, const MTPPhoneCallDiscardReason &reason) {
|
||||
@@ -844,7 +806,7 @@ void Call::finish(FinishType type, const MTPPhoneCallDiscardReason &reason) {
|
||||
|
||||
setState(hangupState);
|
||||
auto duration = getDurationMs() / 1000;
|
||||
auto connectionId = _controller ? _controller->GetPreferredRelayID() : 0;
|
||||
auto connectionId = _controller ? _controller->getPreferredRelayId() : 0;
|
||||
_finishByTimeoutTimer.call(kHangupTimeoutMs, [this, finalState] { setState(finalState); });
|
||||
_api.request(MTPphone_DiscardCall(
|
||||
MTP_flags(0),
|
||||
@@ -870,7 +832,7 @@ void Call::setStateQueued(State state) {
|
||||
});
|
||||
}
|
||||
|
||||
void Call::setFailedQueued(int error) {
|
||||
void Call::setFailedQueued(const QString &error) {
|
||||
crl::on_main(this, [=] {
|
||||
handleControllerError(error);
|
||||
});
|
||||
@@ -887,13 +849,13 @@ void Call::handleRequestError(const RPCError &error) {
|
||||
finish(FinishType::Failed);
|
||||
}
|
||||
|
||||
void Call::handleControllerError(int error) {
|
||||
if (error == tgvoip::ERROR_INCOMPATIBLE) {
|
||||
void Call::handleControllerError(const QString &error) {
|
||||
if (error == u"ERROR_INCOMPATIBLE"_q) {
|
||||
Ui::show(Box<InformBox>(
|
||||
Lang::Hard::CallErrorIncompatible().replace(
|
||||
"{user}",
|
||||
_user->name)));
|
||||
} else if (error == tgvoip::ERROR_AUDIO_IO) {
|
||||
} else if (error == u"ERROR_AUDIO_IO"_q) {
|
||||
Ui::show(Box<InformBox>(tr::lng_call_error_audio_io(tr::now)));
|
||||
}
|
||||
finish(FinishType::Failed);
|
||||
@@ -912,8 +874,8 @@ Call::~Call() {
|
||||
destroyController();
|
||||
}
|
||||
|
||||
void UpdateConfig(const std::string& data) {
|
||||
tgvoip::ServerConfig::GetSharedInstance()->Update(data);
|
||||
void UpdateConfig(const std::string &data) {
|
||||
TgVoip::setGlobalServerConfig(data);
|
||||
}
|
||||
|
||||
} // namespace Calls
|
||||
|
||||
@@ -19,12 +19,12 @@ class Track;
|
||||
} // namespace Audio
|
||||
} // namespace Media
|
||||
|
||||
namespace tgvoip {
|
||||
class VoIPController;
|
||||
} // namespace tgvoip
|
||||
enum class TgVoipState;
|
||||
|
||||
namespace Calls {
|
||||
|
||||
class Controller;
|
||||
|
||||
struct DhConfig {
|
||||
int32 version = 0;
|
||||
int32 g = 0;
|
||||
@@ -129,30 +129,13 @@ public:
|
||||
~Call();
|
||||
|
||||
private:
|
||||
class ControllerPointer {
|
||||
public:
|
||||
void create();
|
||||
void reset();
|
||||
bool empty() const;
|
||||
|
||||
bool operator==(std::nullptr_t) const;
|
||||
explicit operator bool() const;
|
||||
tgvoip::VoIPController *operator->() const;
|
||||
tgvoip::VoIPController &operator*() const;
|
||||
|
||||
~ControllerPointer();
|
||||
|
||||
private:
|
||||
std::unique_ptr<tgvoip::VoIPController> _data;
|
||||
|
||||
};
|
||||
enum class FinishType {
|
||||
None,
|
||||
Ended,
|
||||
Failed,
|
||||
};
|
||||
void handleRequestError(const RPCError &error);
|
||||
void handleControllerError(int error);
|
||||
void handleControllerError(const QString &error);
|
||||
void finish(FinishType type, const MTPPhoneCallDiscardReason &reason = MTP_phoneCallDiscardReasonDisconnect());
|
||||
void startOutgoing();
|
||||
void startIncoming();
|
||||
@@ -160,11 +143,9 @@ private:
|
||||
|
||||
void generateModExpFirst(bytes::const_span randomSeed);
|
||||
void handleControllerStateChange(
|
||||
tgvoip::VoIPController *controller,
|
||||
int state);
|
||||
void handleControllerBarCountChange(
|
||||
tgvoip::VoIPController *controller,
|
||||
int count);
|
||||
not_null<Controller*> controller,
|
||||
TgVoipState state);
|
||||
void handleControllerBarCountChange(int count);
|
||||
void createAndStartController(const MTPDphoneCall &call);
|
||||
|
||||
template <typename T>
|
||||
@@ -177,7 +158,7 @@ private:
|
||||
void startConfirmedCall(const MTPDphoneCall &call);
|
||||
void setState(State state);
|
||||
void setStateQueued(State state);
|
||||
void setFailedQueued(int error);
|
||||
void setFailedQueued(const QString &error);
|
||||
void setSignalBarCount(int count);
|
||||
void destroyController();
|
||||
|
||||
@@ -210,12 +191,12 @@ private:
|
||||
uint64 _accessHash = 0;
|
||||
uint64 _keyFingerprint = 0;
|
||||
|
||||
ControllerPointer _controller;
|
||||
std::unique_ptr<Controller> _controller;
|
||||
|
||||
std::unique_ptr<Media::Audio::Track> _waitingTrack;
|
||||
|
||||
};
|
||||
|
||||
void UpdateConfig(const std::string& data);
|
||||
void UpdateConfig(const std::string &data);
|
||||
|
||||
} // namespace Calls
|
||||
|
||||
31
Telegram/SourceFiles/calls/calls_controller.cpp
Normal file
31
Telegram/SourceFiles/calls/calls_controller.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "calls/calls_controller.h"
|
||||
|
||||
#include "calls/calls_controller_tgvoip.h"
|
||||
|
||||
namespace Calls {
|
||||
|
||||
[[nodiscard]] std::unique_ptr<Controller> MakeController(
|
||||
const std::string &version,
|
||||
const TgVoipConfig &config,
|
||||
const TgVoipPersistentState &persistentState,
|
||||
const std::vector<TgVoipEndpoint> &endpoints,
|
||||
const TgVoipProxy *proxy,
|
||||
TgVoipNetworkType initialNetworkType,
|
||||
const TgVoipEncryptionKey &encryptionKey) {
|
||||
return std::make_unique<TgVoipController>(
|
||||
config,
|
||||
persistentState,
|
||||
endpoints,
|
||||
proxy,
|
||||
initialNetworkType,
|
||||
encryptionKey);
|
||||
}
|
||||
|
||||
} // namespace Calls
|
||||
53
Telegram/SourceFiles/calls/calls_controller.h
Normal file
53
Telegram/SourceFiles/calls/calls_controller.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "TgVoip.h"
|
||||
|
||||
namespace Calls {
|
||||
|
||||
class Controller {
|
||||
public:
|
||||
virtual ~Controller() = default;
|
||||
|
||||
[[nodiscard]] virtual std::string version() = 0;
|
||||
|
||||
virtual void setNetworkType(TgVoipNetworkType networkType) = 0;
|
||||
virtual void setMuteMicrophone(bool muteMicrophone) = 0;
|
||||
virtual void setAudioOutputGainControlEnabled(bool enabled) = 0;
|
||||
virtual void setEchoCancellationStrength(int strength) = 0;
|
||||
virtual void setAudioInputDevice(std::string id) = 0;
|
||||
virtual void setAudioOutputDevice(std::string id) = 0;
|
||||
virtual void setInputVolume(float level) = 0;
|
||||
virtual void setOutputVolume(float level) = 0;
|
||||
virtual void setAudioOutputDuckingEnabled(bool enabled) = 0;
|
||||
|
||||
virtual std::string getLastError() = 0;
|
||||
virtual std::string getDebugInfo() = 0;
|
||||
virtual int64_t getPreferredRelayId() = 0;
|
||||
virtual TgVoipTrafficStats getTrafficStats() = 0;
|
||||
virtual TgVoipPersistentState getPersistentState() = 0;
|
||||
|
||||
virtual void setOnStateUpdated(Fn<void(TgVoipState)> onStateUpdated) = 0;
|
||||
virtual void setOnSignalBarsUpdated(
|
||||
Fn<void(int)> onSignalBarsUpdated) = 0;
|
||||
|
||||
virtual TgVoipFinalState stop() = 0;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] std::unique_ptr<Controller> MakeController(
|
||||
const std::string &version,
|
||||
const TgVoipConfig &config,
|
||||
const TgVoipPersistentState &persistentState,
|
||||
const std::vector<TgVoipEndpoint> &endpoints,
|
||||
const TgVoipProxy *proxy,
|
||||
TgVoipNetworkType initialNetworkType,
|
||||
const TgVoipEncryptionKey &encryptionKey);
|
||||
|
||||
} // namespace Calls
|
||||
97
Telegram/SourceFiles/calls/calls_controller_tgvoip.h
Normal file
97
Telegram/SourceFiles/calls/calls_controller_tgvoip.h
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
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 "calls/calls_controller.h"
|
||||
|
||||
namespace Calls {
|
||||
|
||||
class TgVoipController final : public Controller {
|
||||
public:
|
||||
TgVoipController(
|
||||
const TgVoipConfig &config,
|
||||
const TgVoipPersistentState &persistentState,
|
||||
const std::vector<TgVoipEndpoint> &endpoints,
|
||||
const TgVoipProxy *proxy,
|
||||
TgVoipNetworkType initialNetworkType,
|
||||
const TgVoipEncryptionKey &encryptionKey)
|
||||
: _impl(TgVoip::makeInstance(
|
||||
config,
|
||||
persistentState,
|
||||
endpoints,
|
||||
proxy,
|
||||
initialNetworkType,
|
||||
encryptionKey)) {
|
||||
}
|
||||
|
||||
[[nodiscard]] static std::string Version() {
|
||||
return TgVoip::getVersion();
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string version() override {
|
||||
return Version();
|
||||
}
|
||||
void setNetworkType(TgVoipNetworkType networkType) override {
|
||||
_impl->setNetworkType(networkType);
|
||||
}
|
||||
void setMuteMicrophone(bool muteMicrophone) override {
|
||||
_impl->setMuteMicrophone(muteMicrophone);
|
||||
}
|
||||
void setAudioOutputGainControlEnabled(bool enabled) override {
|
||||
_impl->setAudioOutputGainControlEnabled(enabled);
|
||||
}
|
||||
void setEchoCancellationStrength(int strength) override {
|
||||
_impl->setEchoCancellationStrength(strength);
|
||||
}
|
||||
void setAudioInputDevice(std::string id) override {
|
||||
_impl->setAudioInputDevice(id);
|
||||
}
|
||||
void setAudioOutputDevice(std::string id) override {
|
||||
_impl->setAudioOutputDevice(id);
|
||||
}
|
||||
void setInputVolume(float level) override {
|
||||
_impl->setInputVolume(level);
|
||||
}
|
||||
void setOutputVolume(float level) override {
|
||||
_impl->setOutputVolume(level);
|
||||
}
|
||||
void setAudioOutputDuckingEnabled(bool enabled) override {
|
||||
_impl->setAudioOutputDuckingEnabled(enabled);
|
||||
}
|
||||
std::string getLastError() override {
|
||||
return _impl->getLastError();
|
||||
}
|
||||
std::string getDebugInfo() override {
|
||||
return _impl->getDebugInfo();
|
||||
}
|
||||
int64_t getPreferredRelayId() override {
|
||||
return _impl->getPreferredRelayId();
|
||||
}
|
||||
TgVoipTrafficStats getTrafficStats() override {
|
||||
return _impl->getTrafficStats();
|
||||
}
|
||||
TgVoipPersistentState getPersistentState() override {
|
||||
return _impl->getPersistentState();
|
||||
}
|
||||
void setOnStateUpdated(Fn<void(TgVoipState)> onStateUpdated) override {
|
||||
_impl->setOnStateUpdated(std::move(onStateUpdated));
|
||||
}
|
||||
void setOnSignalBarsUpdated(
|
||||
Fn<void(int)> onSignalBarsUpdated) override {
|
||||
_impl->setOnSignalBarsUpdated(std::move(onSignalBarsUpdated));
|
||||
}
|
||||
TgVoipFinalState stop() override {
|
||||
return _impl->stop();
|
||||
}
|
||||
|
||||
private:
|
||||
const std::unique_ptr<TgVoip> _impl;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Calls
|
||||
@@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_photo_media.h"
|
||||
#include "data/data_cloud_file.h"
|
||||
#include "calls/calls_emoji_fingerprint.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
@@ -299,7 +301,8 @@ QImage Panel::Button::prepareRippleMask() const {
|
||||
}
|
||||
|
||||
Panel::Panel(not_null<Call*> call)
|
||||
: _call(call)
|
||||
: RpWidget(Core::App().getModalParent())
|
||||
, _call(call)
|
||||
, _user(call->user())
|
||||
, _answerHangupRedial(this, st::callAnswer, &st::callHangup)
|
||||
, _decline(this, object_ptr<Button>(this, st::callHangup))
|
||||
@@ -318,6 +321,8 @@ Panel::Panel(not_null<Call*> call)
|
||||
showAndActivate();
|
||||
}
|
||||
|
||||
Panel::~Panel() = default;
|
||||
|
||||
void Panel::showAndActivate() {
|
||||
toggleOpacityAnimation(true);
|
||||
raise();
|
||||
@@ -463,6 +468,8 @@ void Panel::toggleOpacityAnimation(bool visible) {
|
||||
_visible ? 1. : 0.,
|
||||
st::callPanelDuration,
|
||||
_visible ? anim::easeOutCirc : anim::easeInCirc);
|
||||
} else if (!isHidden() && !_visible) {
|
||||
hide();
|
||||
}
|
||||
if (isHidden() && _visible) {
|
||||
show();
|
||||
@@ -484,6 +491,7 @@ void Panel::finishAnimating() {
|
||||
|
||||
void Panel::showControls() {
|
||||
Expects(_call != nullptr);
|
||||
|
||||
showChildren();
|
||||
_decline->setVisible(_decline->toggled());
|
||||
_cancel->setVisible(_cancel->toggled());
|
||||
@@ -505,43 +513,39 @@ void Panel::hideAndDestroy() {
|
||||
}
|
||||
|
||||
void Panel::processUserPhoto() {
|
||||
if (!_user->userpicLoaded()) {
|
||||
_user->loadUserpic();
|
||||
}
|
||||
_userpic = _user->createUserpicView();
|
||||
_user->loadUserpic();
|
||||
const auto photo = _user->userpicPhotoId()
|
||||
? _user->owner().photo(_user->userpicPhotoId()).get()
|
||||
: nullptr;
|
||||
if (isGoodUserPhoto(photo)) {
|
||||
photo->large()->load(_user->userpicPhotoOrigin());
|
||||
} else if (_user->userpicPhotoUnknown() || (photo && !photo->date)) {
|
||||
_user->session().api().requestFullPeer(_user);
|
||||
_photo = photo->createMediaView();
|
||||
_photo->wanted(Data::PhotoSize::Large, _user->userpicPhotoOrigin());
|
||||
} else {
|
||||
_photo = nullptr;
|
||||
if (_user->userpicPhotoUnknown() || (photo && !photo->date)) {
|
||||
_user->session().api().requestFullPeer(_user);
|
||||
}
|
||||
}
|
||||
refreshUserPhoto();
|
||||
}
|
||||
|
||||
void Panel::refreshUserPhoto() {
|
||||
const auto photo = _user->userpicPhotoId()
|
||||
? _user->owner().photo(_user->userpicPhotoId()).get()
|
||||
: nullptr;
|
||||
const auto isNewPhoto = [&](not_null<PhotoData*> photo) {
|
||||
return photo->large()->loaded()
|
||||
&& (photo->id != _userPhotoId || !_userPhotoFull);
|
||||
};
|
||||
if (isGoodUserPhoto(photo) && isNewPhoto(photo)) {
|
||||
_userPhotoId = photo->id;
|
||||
const auto isNewBigPhoto = [&] {
|
||||
return _photo
|
||||
&& _photo->loaded()
|
||||
&& (_photo->owner()->id != _userPhotoId || !_userPhotoFull);
|
||||
}();
|
||||
if (isNewBigPhoto) {
|
||||
_userPhotoId = _photo->owner()->id;
|
||||
_userPhotoFull = true;
|
||||
createUserpicCache(
|
||||
photo->isNull() ? nullptr : photo->large().get(),
|
||||
_user->userpicPhotoOrigin());
|
||||
createUserpicCache(_photo->image(Data::PhotoSize::Large));
|
||||
} else if (_userPhoto.isNull()) {
|
||||
const auto userpic = _user->currentUserpic();
|
||||
createUserpicCache(
|
||||
userpic ? userpic.get() : nullptr,
|
||||
_user->userpicOrigin());
|
||||
createUserpicCache(_userpic ? _userpic->image() : nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void Panel::createUserpicCache(Image *image, Data::FileOrigin origin) {
|
||||
void Panel::createUserpicCache(Image *image) {
|
||||
auto size = st::callWidth * cIntRetinaFactor();
|
||||
auto options = _useTransparency ? (Images::Option::RoundedLarge | Images::Option::RoundedTopLeft | Images::Option::RoundedTopRight | Images::Option::Smooth) : Images::Option::None;
|
||||
if (image) {
|
||||
@@ -555,7 +559,6 @@ void Panel::createUserpicCache(Image *image, Data::FileOrigin origin) {
|
||||
width = size;
|
||||
}
|
||||
_userPhoto = image->pixNoCache(
|
||||
origin,
|
||||
width,
|
||||
height,
|
||||
options,
|
||||
@@ -593,14 +596,18 @@ bool Panel::isGoodUserPhoto(PhotoData *photo) {
|
||||
}
|
||||
|
||||
void Panel::initGeometry() {
|
||||
auto center = Core::App().getPointForCallPanelCenter();
|
||||
const auto center = Core::App().getPointForCallPanelCenter();
|
||||
_useTransparency = Ui::Platform::TranslucentWindowsSupported(center);
|
||||
setAttribute(Qt::WA_OpaquePaintEvent, !_useTransparency);
|
||||
_padding = _useTransparency ? st::callShadow.extend : style::margins(st::lineWidth, st::lineWidth, st::lineWidth, st::lineWidth);
|
||||
_contentTop = _padding.top() + st::callWidth;
|
||||
auto screen = QApplication::desktop()->screenGeometry(center);
|
||||
auto rect = QRect(0, 0, st::callWidth, st::callHeight);
|
||||
setGeometry(rect.translated(center - rect.center()).marginsAdded(_padding));
|
||||
const auto rect = [&] {
|
||||
const QRect initRect(0, 0, st::callWidth, st::callHeight);
|
||||
return initRect.translated(center - initRect.center()).marginsAdded(_padding);
|
||||
}();
|
||||
setGeometry(rect);
|
||||
setMinimumSize(rect.size());
|
||||
setMaximumSize(rect.size());
|
||||
createBottomImage();
|
||||
updateControlsGeometry();
|
||||
}
|
||||
|
||||
@@ -14,6 +14,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/rp_widget.h"
|
||||
|
||||
class Image;
|
||||
|
||||
namespace Data {
|
||||
class PhotoMedia;
|
||||
class CloudImageView;
|
||||
} // namespace Data
|
||||
|
||||
namespace Ui {
|
||||
class IconButton;
|
||||
class FlatLabel;
|
||||
@@ -56,6 +63,7 @@ class Panel
|
||||
|
||||
public:
|
||||
Panel(not_null<Call*> call);
|
||||
~Panel();
|
||||
|
||||
void showAndActivate();
|
||||
void replaceCall(not_null<Call*> call);
|
||||
@@ -93,7 +101,7 @@ private:
|
||||
void processUserPhoto();
|
||||
void refreshUserPhoto();
|
||||
bool isGoodUserPhoto(PhotoData *photo);
|
||||
void createUserpicCache(Image *image, Data::FileOrigin origin);
|
||||
void createUserpicCache(Image *image);
|
||||
QRect signalBarsRect() const;
|
||||
void paintSignalBarsBg(Painter &p);
|
||||
|
||||
@@ -111,6 +119,8 @@ private:
|
||||
|
||||
Call *_call = nullptr;
|
||||
not_null<UserData*> _user;
|
||||
std::shared_ptr<Data::CloudImageView> _userpic;
|
||||
std::shared_ptr<Data::PhotoMedia> _photo;
|
||||
|
||||
bool _useTransparency = true;
|
||||
style::margins _padding;
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "chat_helpers/field_autocomplete.h"
|
||||
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_user.h"
|
||||
@@ -20,9 +21,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "main/main_session.h"
|
||||
#include "chat_helpers/stickers.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "facades.h"
|
||||
#include "app.h"
|
||||
#include "styles/style_history.h"
|
||||
@@ -33,14 +34,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
FieldAutocomplete::FieldAutocomplete(
|
||||
QWidget *parent,
|
||||
not_null<Main::Session*> session)
|
||||
not_null<Window::SessionController*> controller)
|
||||
: RpWidget(parent)
|
||||
, _session(session)
|
||||
, _controller(controller)
|
||||
, _scroll(this, st::mentionScroll) {
|
||||
_scroll->setGeometry(rect());
|
||||
|
||||
_inner = _scroll->setOwnedWidget(
|
||||
object_ptr<internal::FieldAutocompleteInner>(
|
||||
_controller,
|
||||
this,
|
||||
&_mrows,
|
||||
&_hrows,
|
||||
@@ -48,9 +50,9 @@ FieldAutocomplete::FieldAutocomplete(
|
||||
&_srows));
|
||||
_inner->setGeometry(rect());
|
||||
|
||||
connect(_inner, SIGNAL(mentionChosen(UserData*, FieldAutocomplete::ChooseMethod)), this, SIGNAL(mentionChosen(UserData*, FieldAutocomplete::ChooseMethod)));
|
||||
connect(_inner, SIGNAL(hashtagChosen(QString, FieldAutocomplete::ChooseMethod)), this, SIGNAL(hashtagChosen(QString, FieldAutocomplete::ChooseMethod)));
|
||||
connect(_inner, SIGNAL(botCommandChosen(QString, FieldAutocomplete::ChooseMethod)), this, SIGNAL(botCommandChosen(QString, FieldAutocomplete::ChooseMethod)));
|
||||
connect(_inner, SIGNAL(mentionChosen(not_null<UserData*>,FieldAutocomplete::ChooseMethod)), this, SIGNAL(mentionChosen(not_null<UserData*>,FieldAutocomplete::ChooseMethod)));
|
||||
connect(_inner, SIGNAL(hashtagChosen(QString,FieldAutocomplete::ChooseMethod)), this, SIGNAL(hashtagChosen(QString,FieldAutocomplete::ChooseMethod)));
|
||||
connect(_inner, SIGNAL(botCommandChosen(QString,FieldAutocomplete::ChooseMethod)), this, SIGNAL(botCommandChosen(QString,FieldAutocomplete::ChooseMethod)));
|
||||
connect(_inner, SIGNAL(stickerChosen(not_null<DocumentData*>,FieldAutocomplete::ChooseMethod)), this, SIGNAL(stickerChosen(not_null<DocumentData*>,FieldAutocomplete::ChooseMethod)));
|
||||
connect(_inner, SIGNAL(mustScrollTo(int, int)), _scroll, SLOT(scrollToY(int, int)));
|
||||
|
||||
@@ -150,7 +152,9 @@ void FieldAutocomplete::showStickers(EmojiPtr emoji) {
|
||||
}
|
||||
|
||||
bool FieldAutocomplete::clearFilteredBotCommands() {
|
||||
if (_brows.isEmpty()) return false;
|
||||
if (_brows.empty()) {
|
||||
return false;
|
||||
}
|
||||
_brows.clear();
|
||||
return true;
|
||||
}
|
||||
@@ -158,8 +162,8 @@ bool FieldAutocomplete::clearFilteredBotCommands() {
|
||||
namespace {
|
||||
template <typename T, typename U>
|
||||
inline int indexOfInFirstN(const T &v, const U &elem, int last) {
|
||||
for (auto b = v.cbegin(), i = b, e = b + qMax(v.size(), last); i != e; ++i) {
|
||||
if (*i == elem) {
|
||||
for (auto b = v.cbegin(), i = b, e = b + std::max(int(v.size()), last); i != e; ++i) {
|
||||
if (i->user == elem) {
|
||||
return (i - b);
|
||||
}
|
||||
}
|
||||
@@ -169,14 +173,17 @@ inline int indexOfInFirstN(const T &v, const U &elem, int last) {
|
||||
|
||||
internal::StickerRows FieldAutocomplete::getStickerSuggestions() {
|
||||
const auto list = Stickers::GetListByEmoji(
|
||||
_session,
|
||||
&_controller->session(),
|
||||
_emoji,
|
||||
_stickersSeed
|
||||
);
|
||||
auto result = ranges::view::all(
|
||||
list
|
||||
) | ranges::view::transform([](not_null<DocumentData*> sticker) {
|
||||
return internal::StickerSuggestion{ sticker };
|
||||
return internal::StickerSuggestion{
|
||||
sticker,
|
||||
sticker->createMediaView()
|
||||
};
|
||||
}) | ranges::to_vector;
|
||||
for (auto &suggestion : _srows) {
|
||||
if (!suggestion.animated) {
|
||||
@@ -236,12 +243,12 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
|
||||
for_const (auto user, cRecentInlineBots()) {
|
||||
if (user->isInaccessible()) continue;
|
||||
if (!listAllSuggestions && filterNotPassedByUsername(user)) continue;
|
||||
mrows.push_back(user);
|
||||
mrows.push_back({ user });
|
||||
++recentInlineBots;
|
||||
}
|
||||
}
|
||||
if (_chat) {
|
||||
auto ordered = QMultiMap<TimeId, not_null<UserData*>>();
|
||||
auto sorted = base::flat_multi_map<TimeId, not_null<UserData*>>();
|
||||
const auto byOnline = [&](not_null<UserData*> user) {
|
||||
return Data::SortByOnlineValue(user, now);
|
||||
};
|
||||
@@ -253,23 +260,19 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
|
||||
if (user->isInaccessible()) continue;
|
||||
if (!listAllSuggestions && filterNotPassedByName(user)) continue;
|
||||
if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue;
|
||||
ordered.insertMulti(byOnline(user), user);
|
||||
sorted.emplace(byOnline(user), user);
|
||||
}
|
||||
}
|
||||
for (const auto user : _chat->lastAuthors) {
|
||||
if (user->isInaccessible()) continue;
|
||||
if (!listAllSuggestions && filterNotPassedByName(user)) continue;
|
||||
if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue;
|
||||
mrows.push_back(user);
|
||||
if (!ordered.isEmpty()) {
|
||||
ordered.remove(byOnline(user), user);
|
||||
}
|
||||
mrows.push_back({ user });
|
||||
sorted.remove(byOnline(user), user);
|
||||
}
|
||||
if (!ordered.isEmpty()) {
|
||||
for (auto i = ordered.cend(), b = ordered.cbegin(); i != b;) {
|
||||
--i;
|
||||
mrows.push_back(i.value());
|
||||
}
|
||||
for (auto i = sorted.cend(), b = sorted.cbegin(); i != b;) {
|
||||
--i;
|
||||
mrows.push_back({ i->second });
|
||||
}
|
||||
} else if (_channel && _channel->isMegagroup()) {
|
||||
QMultiMap<int32, UserData*> ordered;
|
||||
@@ -281,7 +284,7 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
|
||||
if (user->isInaccessible()) continue;
|
||||
if (!listAllSuggestions && filterNotPassedByName(user)) continue;
|
||||
if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue;
|
||||
mrows.push_back(user);
|
||||
mrows.push_back({ user });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -373,7 +376,7 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
brows.push_back(qMakePair(user, &user->botInfo->commands.at(j)));
|
||||
brows.push_back({ user, &user->botInfo->commands.at(j) });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -385,7 +388,7 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
|
||||
QString toFilter = (hasUsername || botStatus == 0 || botStatus == 2) ? user->botInfo->commands.at(j).command + '@' + user->username : user->botInfo->commands.at(j).command;
|
||||
if (!toFilter.startsWith(_filter, Qt::CaseInsensitive)/* || toFilter.size() == _filter.size()*/) continue;
|
||||
}
|
||||
brows.push_back(qMakePair(user, &user->botInfo->commands.at(j)));
|
||||
brows.push_back({ user, &user->botInfo->commands.at(j) });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -406,7 +409,7 @@ void FieldAutocomplete::rowsUpdated(
|
||||
internal::BotCommandRows &&brows,
|
||||
internal::StickerRows &&srows,
|
||||
bool resetScroll) {
|
||||
if (mrows.isEmpty() && hrows.isEmpty() && brows.isEmpty() && srows.empty()) {
|
||||
if (mrows.empty() && hrows.empty() && brows.empty() && srows.empty()) {
|
||||
if (!isHidden()) {
|
||||
hideAnimated();
|
||||
}
|
||||
@@ -447,11 +450,11 @@ void FieldAutocomplete::recount(bool resetScroll) {
|
||||
int32 stickersPerRow = qMax(1, int32(_boundings.width() - 2 * st::stickerPanPadding) / int32(st::stickerPanSize.width()));
|
||||
int32 rows = rowscount(_srows.size(), stickersPerRow);
|
||||
h = st::stickerPanPadding + rows * st::stickerPanSize.height();
|
||||
} else if (!_mrows.isEmpty()) {
|
||||
} else if (!_mrows.empty()) {
|
||||
h = _mrows.size() * st::mentionHeight;
|
||||
} else if (!_hrows.isEmpty()) {
|
||||
} else if (!_hrows.empty()) {
|
||||
h = _hrows.size() * st::mentionHeight;
|
||||
} else if (!_brows.isEmpty()) {
|
||||
} else if (!_brows.empty()) {
|
||||
h = _brows.size() * st::mentionHeight;
|
||||
}
|
||||
|
||||
@@ -584,12 +587,14 @@ bool FieldAutocomplete::eventFilter(QObject *obj, QEvent *e) {
|
||||
namespace internal {
|
||||
|
||||
FieldAutocompleteInner::FieldAutocompleteInner(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<FieldAutocomplete*> parent,
|
||||
not_null<MentionRows*> mrows,
|
||||
not_null<HashtagRows*> hrows,
|
||||
not_null<BotCommandRows*> brows,
|
||||
not_null<StickerRows*> srows)
|
||||
: _parent(parent)
|
||||
: _controller(controller)
|
||||
, _parent(parent)
|
||||
, _mrows(mrows)
|
||||
, _hrows(hrows)
|
||||
, _brows(brows)
|
||||
@@ -631,11 +636,12 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
|
||||
|
||||
auto &sticker = (*_srows)[index];
|
||||
const auto document = sticker.document;
|
||||
const auto &media = sticker.documentMedia;
|
||||
if (!document->sticker()) continue;
|
||||
|
||||
if (document->sticker()->animated
|
||||
&& !sticker.animated
|
||||
&& document->loaded()) {
|
||||
&& media->loaded()) {
|
||||
setupLottie(sticker);
|
||||
}
|
||||
|
||||
@@ -646,7 +652,7 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
|
||||
App::roundRect(p, QRect(tl, st::stickerPanSize), st::emojiPanHover, StickerHoverCorners);
|
||||
}
|
||||
|
||||
document->checkStickerSmall();
|
||||
media->checkStickerSmall();
|
||||
auto w = 1;
|
||||
auto h = 1;
|
||||
if (sticker.animated && !document->dimensions.isEmpty()) {
|
||||
@@ -665,7 +671,6 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
|
||||
}
|
||||
if (sticker.animated && sticker.animated->ready()) {
|
||||
const auto frame = sticker.animated->frame();
|
||||
sticker.animated->markFrameShown();
|
||||
const auto size = frame.size() / cIntRetinaFactor();
|
||||
const auto ppos = pos + QPoint(
|
||||
(st::stickerPanSize.width() - size.width()) / 2,
|
||||
@@ -673,15 +678,24 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
|
||||
p.drawImage(
|
||||
QRect(ppos, size),
|
||||
frame);
|
||||
} else if (const auto image = document->getStickerSmall()) {
|
||||
const auto paused = _controller->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::SavedGifs);
|
||||
if (!paused) {
|
||||
sticker.animated->markFrameShown();
|
||||
}
|
||||
} else if (const auto image = media->getStickerSmall()) {
|
||||
QPoint ppos = pos + QPoint((st::stickerPanSize.width() - w) / 2, (st::stickerPanSize.height() - h) / 2);
|
||||
p.drawPixmapLeft(ppos, width(), image->pix(document->stickerSetOrigin(), w, h));
|
||||
p.drawPixmapLeft(ppos, width(), image->pix(w, h));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int32 from = qFloor(e->rect().top() / st::mentionHeight), to = qFloor(e->rect().bottom() / st::mentionHeight) + 1;
|
||||
int32 last = _mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size();
|
||||
int32 last = !_mrows->empty()
|
||||
? _mrows->size()
|
||||
: !_hrows->empty()
|
||||
? _hrows->size()
|
||||
: _brows->size();
|
||||
auto filter = _parent->filter();
|
||||
bool hasUsername = filter.indexOf('@') > 0;
|
||||
int filterSize = filter.size();
|
||||
@@ -693,12 +707,13 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
|
||||
if (selected) {
|
||||
p.fillRect(0, i * st::mentionHeight, width(), st::mentionHeight, st::mentionBgOver);
|
||||
int skip = (st::mentionHeight - st::smallCloseIconOver.height()) / 2;
|
||||
if (!_hrows->isEmpty() || (!_mrows->isEmpty() && i < _recentInlineBotsInRows)) {
|
||||
if (!_hrows->empty() || (!_mrows->empty() && i < _recentInlineBotsInRows)) {
|
||||
st::smallCloseIconOver.paint(p, QPoint(width() - st::smallCloseIconOver.width() - skip, i * st::mentionHeight + skip), width());
|
||||
}
|
||||
}
|
||||
if (!_mrows->isEmpty()) {
|
||||
const auto user = _mrows->at(i);
|
||||
if (!_mrows->empty()) {
|
||||
auto &row = _mrows->at(i);
|
||||
const auto user = row.user;
|
||||
auto first = (!filterIsEmpty && user->username.startsWith(filter, Qt::CaseInsensitive)) ? ('@' + user->username.mid(0, filterSize)) : QString();
|
||||
auto second = first.isEmpty() ? (user->username.isEmpty() ? QString() : ('@' + user->username)) : user->username.mid(filterSize);
|
||||
auto firstwidth = st::mentionFont->width(first);
|
||||
@@ -720,7 +735,7 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
|
||||
}
|
||||
}
|
||||
user->loadUserpic();
|
||||
user->paintUserpicLeft(p, st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), width(), st::mentionPhotoSize);
|
||||
user->paintUserpicLeft(p, row.userpic, st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), width(), st::mentionPhotoSize);
|
||||
|
||||
p.setPen(selected ? st::mentionNameFgOver : st::mentionNameFg);
|
||||
user->nameText().drawElided(p, 2 * st::mentionPadding.left() + st::mentionPhotoSize, i * st::mentionHeight + st::mentionTop, namewidth);
|
||||
@@ -732,7 +747,7 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
|
||||
p.setPen(selected ? st::mentionFgOver : st::mentionFg);
|
||||
p.drawText(mentionleft + namewidth + st::mentionPadding.right() + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second);
|
||||
}
|
||||
} else if (!_hrows->isEmpty()) {
|
||||
} else if (!_hrows->empty()) {
|
||||
QString hrow = _hrows->at(i);
|
||||
QString first = filterIsEmpty ? QString() : ('#' + hrow.mid(0, filterSize));
|
||||
QString second = filterIsEmpty ? ('#' + hrow) : hrow.mid(filterSize);
|
||||
@@ -756,16 +771,17 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
|
||||
p.drawText(htagleft + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second);
|
||||
}
|
||||
} else {
|
||||
UserData *user = _brows->at(i).first;
|
||||
auto &row = _brows->at(i);
|
||||
const auto user = row.user;
|
||||
|
||||
const BotCommand *command = _brows->at(i).second;
|
||||
QString toHighlight = command->command;
|
||||
const auto command = row.command;
|
||||
auto toHighlight = command->command;
|
||||
int32 botStatus = _parent->chat() ? _parent->chat()->botStatus : ((_parent->channel() && _parent->channel()->isMegagroup()) ? _parent->channel()->mgInfo->botStatus : -1);
|
||||
if (hasUsername || botStatus == 0 || botStatus == 2) {
|
||||
toHighlight += '@' + user->username;
|
||||
}
|
||||
user->loadUserpic();
|
||||
user->paintUserpicLeft(p, st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), width(), st::mentionPhotoSize);
|
||||
user->paintUserpicLeft(p, row.userpic, st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), width(), st::mentionPhotoSize);
|
||||
|
||||
auto commandText = '/' + toHighlight;
|
||||
|
||||
@@ -808,7 +824,7 @@ void FieldAutocompleteInner::clearSel(bool hidden) {
|
||||
_overDelete = false;
|
||||
_mouseSelection = false;
|
||||
_lastMousePosition = std::nullopt;
|
||||
setSel((_mrows->isEmpty() && _brows->isEmpty() && _hrows->isEmpty()) ? -1 : 0);
|
||||
setSel((_mrows->empty() && _brows->empty() && _hrows->empty()) ? -1 : 0);
|
||||
if (hidden) {
|
||||
_down = -1;
|
||||
_previewShown = false;
|
||||
@@ -819,7 +835,13 @@ bool FieldAutocompleteInner::moveSel(int key) {
|
||||
_mouseSelection = false;
|
||||
_lastMousePosition = std::nullopt;
|
||||
|
||||
int32 maxSel = (_mrows->isEmpty() ? (_hrows->isEmpty() ? (_brows->isEmpty() ? _srows->size() : _brows->size()) : _hrows->size()) : _mrows->size());
|
||||
int32 maxSel = !_mrows->empty()
|
||||
? _mrows->size()
|
||||
: !_hrows->empty()
|
||||
? _hrows->size()
|
||||
: !_brows->empty()
|
||||
? _brows->size()
|
||||
: _srows->size();
|
||||
int32 direction = (key == Qt::Key_Up) ? -1 : (key == Qt::Key_Down ? 1 : 0);
|
||||
if (!_srows->empty()) {
|
||||
if (key == Qt::Key_Left) {
|
||||
@@ -850,20 +872,20 @@ bool FieldAutocompleteInner::chooseSelected(FieldAutocomplete::ChooseMethod meth
|
||||
emit stickerChosen((*_srows)[_sel].document, method);
|
||||
return true;
|
||||
}
|
||||
} else if (!_mrows->isEmpty()) {
|
||||
} else if (!_mrows->empty()) {
|
||||
if (_sel >= 0 && _sel < _mrows->size()) {
|
||||
emit mentionChosen(_mrows->at(_sel), method);
|
||||
emit mentionChosen(_mrows->at(_sel).user, method);
|
||||
return true;
|
||||
}
|
||||
} else if (!_hrows->isEmpty()) {
|
||||
} else if (!_hrows->empty()) {
|
||||
if (_sel >= 0 && _sel < _hrows->size()) {
|
||||
emit hashtagChosen('#' + _hrows->at(_sel), method);
|
||||
return true;
|
||||
}
|
||||
} else if (!_brows->isEmpty()) {
|
||||
} else if (!_brows->empty()) {
|
||||
if (_sel >= 0 && _sel < _brows->size()) {
|
||||
UserData *user = _brows->at(_sel).first;
|
||||
const BotCommand *command(_brows->at(_sel).second);
|
||||
const auto user = _brows->at(_sel).user;
|
||||
const auto command = _brows->at(_sel).command;
|
||||
int32 botStatus = _parent->chat() ? _parent->chat()->botStatus : ((_parent->channel() && _parent->channel()->isMegagroup()) ? _parent->channel()->mgInfo->botStatus : -1);
|
||||
if (botStatus == 0 || botStatus == 2 || _parent->filter().indexOf('@') > 0) {
|
||||
emit botCommandChosen('/' + command->command + '@' + user->username, method);
|
||||
@@ -883,9 +905,9 @@ void FieldAutocompleteInner::setRecentInlineBotsInRows(int32 bots) {
|
||||
void FieldAutocompleteInner::mousePressEvent(QMouseEvent *e) {
|
||||
selectByMouse(e->globalPos());
|
||||
if (e->button() == Qt::LeftButton) {
|
||||
if (_overDelete && _sel >= 0 && _sel < (_mrows->isEmpty() ? _hrows->size() : _recentInlineBotsInRows)) {
|
||||
if (_overDelete && _sel >= 0 && _sel < (_mrows->empty() ? _hrows->size() : _recentInlineBotsInRows)) {
|
||||
bool removed = false;
|
||||
if (_mrows->isEmpty()) {
|
||||
if (_mrows->empty()) {
|
||||
QString toRemove = _hrows->at(_sel);
|
||||
RecentHashtagPack &recent(cRefRecentWriteHashtags());
|
||||
for (RecentHashtagPack::iterator i = recent.begin(); i != recent.cend();) {
|
||||
@@ -897,7 +919,7 @@ void FieldAutocompleteInner::mousePressEvent(QMouseEvent *e) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
UserData *toRemove = _mrows->at(_sel);
|
||||
UserData *toRemove = _mrows->at(_sel).user;
|
||||
RecentInlineBots &recent(cRefRecentInlineBots());
|
||||
int32 index = recent.indexOf(toRemove);
|
||||
if (index >= 0) {
|
||||
@@ -996,7 +1018,7 @@ auto FieldAutocompleteInner::getLottieRenderer()
|
||||
void FieldAutocompleteInner::setupLottie(StickerSuggestion &suggestion) {
|
||||
const auto document = suggestion.document;
|
||||
suggestion.animated = Stickers::LottiePlayerFromDocument(
|
||||
document,
|
||||
suggestion.documentMedia.get(),
|
||||
Stickers::LottieSize::InlineResults,
|
||||
stickerBoundingBox() * cIntRetinaFactor(),
|
||||
Lottie::Quality::Default,
|
||||
@@ -1054,8 +1076,12 @@ void FieldAutocompleteInner::selectByMouse(QPoint globalPosition) {
|
||||
_overDelete = false;
|
||||
} else {
|
||||
sel = mouse.y() / int32(st::mentionHeight);
|
||||
maxSel = _mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size();
|
||||
_overDelete = (!_hrows->isEmpty() || (!_mrows->isEmpty() && sel < _recentInlineBotsInRows)) ? (mouse.x() >= width() - st::mentionHeight) : false;
|
||||
maxSel = !_mrows->empty()
|
||||
? _mrows->size()
|
||||
: !_hrows->empty()
|
||||
? _hrows->size()
|
||||
: _brows->size();
|
||||
_overDelete = (!_hrows->empty() || (!_mrows->empty() && sel < _recentInlineBotsInRows)) ? (mouse.x() >= width() - st::mentionHeight) : false;
|
||||
}
|
||||
if (sel < 0 || sel >= maxSel) {
|
||||
sel = -1;
|
||||
|
||||
@@ -22,21 +22,38 @@ class SinglePlayer;
|
||||
class FrameRenderer;
|
||||
} // namespace Lottie;
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace Data {
|
||||
class DocumentMedia;
|
||||
class CloudImageView;
|
||||
} // namespace Data
|
||||
|
||||
namespace internal {
|
||||
|
||||
struct StickerSuggestion {
|
||||
not_null<DocumentData*> document;
|
||||
std::shared_ptr<Data::DocumentMedia> documentMedia;
|
||||
std::unique_ptr<Lottie::SinglePlayer> animated;
|
||||
};
|
||||
|
||||
using MentionRows = QList<UserData*>;
|
||||
using HashtagRows = QList<QString>;
|
||||
using BotCommandRows = QList<QPair<UserData*, const BotCommand*>>;
|
||||
struct MentionRow {
|
||||
not_null<UserData*> user;
|
||||
std::shared_ptr<Data::CloudImageView> userpic;
|
||||
};
|
||||
|
||||
struct BotCommandRow {
|
||||
not_null<UserData*> user;
|
||||
not_null<const BotCommand*> command;
|
||||
std::shared_ptr<Data::CloudImageView> userpic;
|
||||
};
|
||||
|
||||
using HashtagRows = std::vector<QString>;
|
||||
using BotCommandRows = std::vector<BotCommandRow>;
|
||||
using StickerRows = std::vector<StickerSuggestion>;
|
||||
using MentionRows = std::vector<MentionRow>;
|
||||
|
||||
class FieldAutocompleteInner;
|
||||
|
||||
@@ -46,7 +63,9 @@ class FieldAutocomplete final : public Ui::RpWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
FieldAutocomplete(QWidget *parent, not_null<Main::Session*> session);
|
||||
FieldAutocomplete(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller);
|
||||
~FieldAutocomplete();
|
||||
|
||||
bool clearFilteredBotCommands();
|
||||
@@ -87,7 +106,7 @@ public:
|
||||
void hideFast();
|
||||
|
||||
signals:
|
||||
void mentionChosen(UserData *user, FieldAutocomplete::ChooseMethod method) const;
|
||||
void mentionChosen(not_null<UserData*> user, FieldAutocomplete::ChooseMethod method) const;
|
||||
void hashtagChosen(QString hashtag, FieldAutocomplete::ChooseMethod method) const;
|
||||
void botCommandChosen(QString command, FieldAutocomplete::ChooseMethod method) const;
|
||||
void stickerChosen(not_null<DocumentData*> sticker, FieldAutocomplete::ChooseMethod method) const;
|
||||
@@ -109,7 +128,7 @@ private:
|
||||
void recount(bool resetScroll = false);
|
||||
internal::StickerRows getStickerSuggestions();
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
QPixmap _cache;
|
||||
internal::MentionRows _mrows;
|
||||
internal::HashtagRows _hrows;
|
||||
@@ -160,6 +179,7 @@ class FieldAutocompleteInner final
|
||||
|
||||
public:
|
||||
FieldAutocompleteInner(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<FieldAutocomplete*> parent,
|
||||
not_null<MentionRows*> mrows,
|
||||
not_null<HashtagRows*> hrows,
|
||||
@@ -174,7 +194,7 @@ public:
|
||||
void rowsUpdated();
|
||||
|
||||
signals:
|
||||
void mentionChosen(UserData *user, FieldAutocomplete::ChooseMethod method) const;
|
||||
void mentionChosen(not_null<UserData*> user, FieldAutocomplete::ChooseMethod method) const;
|
||||
void hashtagChosen(QString hashtag, FieldAutocomplete::ChooseMethod method) const;
|
||||
void botCommandChosen(QString command, FieldAutocomplete::ChooseMethod method) const;
|
||||
void stickerChosen(not_null<DocumentData*> sticker, FieldAutocomplete::ChooseMethod method) const;
|
||||
@@ -204,11 +224,12 @@ private:
|
||||
void repaintSticker(not_null<DocumentData*> document);
|
||||
std::shared_ptr<Lottie::FrameRenderer> getLottieRenderer();
|
||||
|
||||
not_null<FieldAutocomplete*> _parent;
|
||||
not_null<MentionRows*> _mrows;
|
||||
not_null<HashtagRows*> _hrows;
|
||||
not_null<BotCommandRows*> _brows;
|
||||
not_null<StickerRows*> _srows;
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
const not_null<FieldAutocomplete*> _parent;
|
||||
const not_null<MentionRows*> _mrows;
|
||||
const not_null<HashtagRows*> _hrows;
|
||||
const not_null<BotCommandRows*> _brows;
|
||||
const not_null<StickerRows*> _srows;
|
||||
rpl::lifetime _stickersLifetime;
|
||||
std::weak_ptr<Lottie::FrameRenderer> _lottieRenderer;
|
||||
int _stickersPerRow = 1;
|
||||
|
||||
@@ -13,6 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_photo_media.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
@@ -248,14 +250,18 @@ void GifsListWidget::inlineResultsDone(const MTPmessages_BotResults &result) {
|
||||
_inlineQuery,
|
||||
std::make_unique<InlineCacheEntry>()).first;
|
||||
}
|
||||
auto entry = it->second.get();
|
||||
const auto entry = it->second.get();
|
||||
entry->nextOffset = qs(d.vnext_offset().value_or_empty());
|
||||
if (auto count = v.size()) {
|
||||
if (const auto count = v.size()) {
|
||||
entry->results.reserve(entry->results.size() + count);
|
||||
}
|
||||
auto added = 0;
|
||||
for_const (const auto &res, v) {
|
||||
if (auto result = InlineBots::Result::create(queryId, res)) {
|
||||
for (const auto &res : v) {
|
||||
auto result = InlineBots::Result::Create(
|
||||
&controller()->session(),
|
||||
queryId,
|
||||
res);
|
||||
if (result) {
|
||||
++added;
|
||||
entry->results.push_back(std::move(result));
|
||||
}
|
||||
@@ -369,23 +375,32 @@ void GifsListWidget::selectInlineResult(int row, int column) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto ctrl = (QGuiApplication::keyboardModifiers()
|
||||
== Qt::ControlModifier);
|
||||
auto item = _rows[row].items[column];
|
||||
if (const auto photo = item->getPhoto()) {
|
||||
if (photo->thumbnail()->loaded()) {
|
||||
using Data::PhotoSize;
|
||||
const auto media = photo->activeMediaView();
|
||||
if (ctrl
|
||||
|| (media && media->image(PhotoSize::Thumbnail))
|
||||
|| (media && media->image(PhotoSize::Large))) {
|
||||
_photoChosen.fire_copy(photo);
|
||||
} else if (!photo->thumbnail()->loading()) {
|
||||
photo->thumbnail()->loadEvenCancelled(Data::FileOrigin());
|
||||
} else if (!photo->loading(PhotoSize::Thumbnail)) {
|
||||
photo->load(PhotoSize::Thumbnail, Data::FileOrigin());
|
||||
}
|
||||
} else if (const auto document = item->getDocument()) {
|
||||
if (document->loaded()
|
||||
|| QGuiApplication::keyboardModifiers() == Qt::ControlModifier) {
|
||||
const auto media = document->activeMediaView();
|
||||
const auto preview = Data::VideoPreviewState(media.get());
|
||||
if (ctrl || (media && preview.loaded())) {
|
||||
_fileChosen.fire_copy(document);
|
||||
} else if (document->loading()) {
|
||||
document->cancel();
|
||||
} else {
|
||||
document->save(
|
||||
document->stickerOrGifOrigin(),
|
||||
QString());
|
||||
} else if (!preview.usingThumbnail()) {
|
||||
if (preview.loading()) {
|
||||
document->cancel();
|
||||
} else {
|
||||
document->save(
|
||||
document->stickerOrGifOrigin(),
|
||||
QString());
|
||||
}
|
||||
}
|
||||
} else if (const auto inlineResult = item->getResult()) {
|
||||
if (inlineResult->onChoose(item)) {
|
||||
@@ -429,28 +444,21 @@ TabbedSelector::InnerFooter *GifsListWidget::getFooter() const {
|
||||
|
||||
void GifsListWidget::processHideFinished() {
|
||||
clearSelection();
|
||||
clearHeavyData();
|
||||
}
|
||||
|
||||
void GifsListWidget::processPanelHideFinished() {
|
||||
const auto itemForget = [](const auto &item) {
|
||||
if (const auto document = item->getDocument()) {
|
||||
document->unload();
|
||||
}
|
||||
if (const auto photo = item->getPhoto()) {
|
||||
photo->unload();
|
||||
}
|
||||
if (const auto result = item->getResult()) {
|
||||
result->unload();
|
||||
}
|
||||
item->unloadAnimation();
|
||||
};
|
||||
clearHeavyData();
|
||||
}
|
||||
|
||||
void GifsListWidget::clearHeavyData() {
|
||||
// Preserve panel state through visibility toggles.
|
||||
//clearInlineRows(false);
|
||||
for (const auto &[document, layout] : _gifLayouts) {
|
||||
itemForget(layout);
|
||||
layout->unloadHeavyPart();
|
||||
}
|
||||
for (const auto &[document, layout] : _inlineLayouts) {
|
||||
itemForget(layout);
|
||||
layout->unloadHeavyPart();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -541,11 +549,13 @@ void GifsListWidget::clearInlineRows(bool resultsDeleted) {
|
||||
_rows.clear();
|
||||
}
|
||||
|
||||
GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareSavedGif(DocumentData *doc, int32 position) {
|
||||
auto it = _gifLayouts.find(doc);
|
||||
GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareSavedGif(
|
||||
not_null<DocumentData*> document,
|
||||
int32 position) {
|
||||
auto it = _gifLayouts.find(document);
|
||||
if (it == _gifLayouts.cend()) {
|
||||
if (auto layout = LayoutItem::createLayoutGif(this, doc)) {
|
||||
it = _gifLayouts.emplace(doc, std::move(layout)).first;
|
||||
if (auto layout = LayoutItem::createLayoutGif(this, document)) {
|
||||
it = _gifLayouts.emplace(document, std::move(layout)).first;
|
||||
it->second->initDimensions();
|
||||
} else {
|
||||
return nullptr;
|
||||
@@ -557,10 +567,15 @@ GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareSavedGif(DocumentData *
|
||||
return it->second.get();
|
||||
}
|
||||
|
||||
GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareInlineResult(InlineResult *result, int32 position) {
|
||||
GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareInlineResult(
|
||||
not_null<InlineResult*> result,
|
||||
int32 position) {
|
||||
auto it = _inlineLayouts.find(result);
|
||||
if (it == _inlineLayouts.cend()) {
|
||||
if (auto layout = LayoutItem::createLayout(this, result, _inlineWithThumb)) {
|
||||
if (auto layout = LayoutItem::createLayout(
|
||||
this,
|
||||
result,
|
||||
_inlineWithThumb)) {
|
||||
it = _inlineLayouts.emplace(result, std::move(layout)).first;
|
||||
it->second->initDimensions();
|
||||
} else {
|
||||
@@ -1026,15 +1041,13 @@ void GifsListWidget::showPreview() {
|
||||
auto layout = _rows[row].items[col];
|
||||
if (const auto w = App::wnd()) {
|
||||
if (const auto previewDocument = layout->getPreviewDocument()) {
|
||||
w->showMediaPreview(
|
||||
_previewShown = w->showMediaPreview(
|
||||
Data::FileOriginSavedGifs(),
|
||||
previewDocument);
|
||||
_previewShown = true;
|
||||
} else if (const auto previewPhoto = layout->getPreviewPhoto()) {
|
||||
w->showMediaPreview(
|
||||
_previewShown = w->showMediaPreview(
|
||||
Data::FileOrigin(),
|
||||
previewPhoto);
|
||||
_previewShown = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@ private:
|
||||
InlineResults results;
|
||||
};
|
||||
|
||||
void clearHeavyData();
|
||||
void cancelGifsSearch();
|
||||
void switchToSavedGifs();
|
||||
void refreshSavedGifs();
|
||||
@@ -133,11 +134,19 @@ private:
|
||||
QVector<Row> _rows;
|
||||
void clearInlineRows(bool resultsDeleted);
|
||||
|
||||
std::map<DocumentData*, std::unique_ptr<LayoutItem>> _gifLayouts;
|
||||
LayoutItem *layoutPrepareSavedGif(DocumentData *doc, int32 position);
|
||||
std::map<
|
||||
not_null<DocumentData*>,
|
||||
std::unique_ptr<LayoutItem>> _gifLayouts;
|
||||
LayoutItem *layoutPrepareSavedGif(
|
||||
not_null<DocumentData*> document,
|
||||
int32 position);
|
||||
|
||||
std::map<InlineResult*, std::unique_ptr<LayoutItem>> _inlineLayouts;
|
||||
LayoutItem *layoutPrepareInlineResult(InlineResult *result, int32 position);
|
||||
std::map<
|
||||
not_null<InlineResult*>,
|
||||
std::unique_ptr<LayoutItem>> _inlineLayouts;
|
||||
LayoutItem *layoutPrepareInlineResult(
|
||||
not_null<InlineResult*> result,
|
||||
int32 position);
|
||||
|
||||
bool inlineRowsAddItem(DocumentData *savedGif, InlineResult *result, Row &row, int32 &sumWidth);
|
||||
bool inlineRowFinalize(Row &row, int32 &sumWidth, bool force = false);
|
||||
|
||||
@@ -38,6 +38,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace Ui::Text;
|
||||
|
||||
using EditLinkAction = Ui::InputField::EditLinkAction;
|
||||
using EditLinkSelection = Ui::InputField::EditLinkSelection;
|
||||
|
||||
@@ -298,7 +300,7 @@ bool HasSendText(not_null<const Ui::InputField*> field) {
|
||||
if (code != ' '
|
||||
&& code != '\n'
|
||||
&& code != '\r'
|
||||
&& !chReplacedBySpace(code)) {
|
||||
&& !IsReplacedBySpace(code)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -585,13 +587,14 @@ void MessageLinksParser::parse() {
|
||||
const QChar *domainEnd = start + m.capturedEnd(), *p = domainEnd;
|
||||
for (; p < end; ++p) {
|
||||
QChar ch(*p);
|
||||
if (chIsLinkEnd(ch)) break; // link finished
|
||||
if (chIsAlmostLinkEnd(ch)) {
|
||||
if (IsLinkEnd(ch)) {
|
||||
break; // link finished
|
||||
} else if (IsAlmostLinkEnd(ch)) {
|
||||
const QChar *endTest = p + 1;
|
||||
while (endTest < end && chIsAlmostLinkEnd(*endTest)) {
|
||||
while (endTest < end && IsAlmostLinkEnd(*endTest)) {
|
||||
++endTest;
|
||||
}
|
||||
if (endTest >= end || chIsLinkEnd(*endTest)) {
|
||||
if (endTest >= end || IsLinkEnd(*endTest)) {
|
||||
break; // link finished at p
|
||||
}
|
||||
p = endTest;
|
||||
|
||||
@@ -5,11 +5,13 @@ 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 "stickers.h"
|
||||
#include "chat_helpers/stickers.h"
|
||||
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "chat_helpers/stickers_set.h"
|
||||
#include "boxes/stickers_box.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "lang/lang_keys.h"
|
||||
@@ -20,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "mainwindow.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/emoji_config.h"
|
||||
#include "ui/image/image_location_factory.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "lottie/lottie_single_player.h"
|
||||
#include "lottie/lottie_multi_player.h"
|
||||
@@ -40,7 +43,7 @@ void ApplyArchivedResult(const MTPDmessages_stickerSetInstallResultArchive &d) {
|
||||
Order archived;
|
||||
archived.reserve(v.size());
|
||||
QMap<uint64, uint64> setsToRequest;
|
||||
for_const (auto &stickerSet, v) {
|
||||
for (const auto &stickerSet : v) {
|
||||
const MTPDstickerSet *setData = nullptr;
|
||||
switch (stickerSet.type()) {
|
||||
case mtpc_stickerSetCovered: {
|
||||
@@ -90,21 +93,22 @@ void ApplyArchivedResult(const MTPDmessages_stickerSetInstallResultArchive &d) {
|
||||
// For testing: Just apply random subset or your sticker sets as archived.
|
||||
bool ApplyArchivedResultFake() {
|
||||
auto sets = QVector<MTPStickerSetCovered>();
|
||||
for (auto &set : Auth().data().stickerSetsRef()) {
|
||||
if ((set.flags & MTPDstickerSet::Flag::f_installed_date)
|
||||
&& !(set.flags & MTPDstickerSet_ClientFlag::f_special)) {
|
||||
for (const auto &[id, set] : Auth().data().stickerSets()) {
|
||||
const auto raw = set.get();
|
||||
if ((raw->flags & MTPDstickerSet::Flag::f_installed_date)
|
||||
&& !(raw->flags & MTPDstickerSet_ClientFlag::f_special)) {
|
||||
if (rand_value<uint32>() % 128 < 64) {
|
||||
const auto data = MTP_stickerSet(
|
||||
MTP_flags(set.flags | MTPDstickerSet::Flag::f_archived),
|
||||
MTP_int(set.installDate),
|
||||
MTP_long(set.id),
|
||||
MTP_long(set.access),
|
||||
MTP_string(set.title),
|
||||
MTP_string(set.shortName),
|
||||
MTP_flags(raw->flags | MTPDstickerSet::Flag::f_archived),
|
||||
MTP_int(raw->installDate),
|
||||
MTP_long(raw->id),
|
||||
MTP_long(raw->access),
|
||||
MTP_string(raw->title),
|
||||
MTP_string(raw->shortName),
|
||||
MTP_photoSizeEmpty(MTP_string()),
|
||||
MTP_int(0),
|
||||
MTP_int(set.count),
|
||||
MTP_int(set.hash));
|
||||
MTP_int(raw->count),
|
||||
MTP_int(raw->hash));
|
||||
sets.push_back(MTP_stickerSetCovered(
|
||||
data,
|
||||
MTP_documentEmpty(MTP_long(0))));
|
||||
@@ -127,11 +131,12 @@ void InstallLocally(uint64 setId) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto flags = it->flags;
|
||||
it->flags &= ~(MTPDstickerSet::Flag::f_archived | MTPDstickerSet_ClientFlag::f_unread);
|
||||
it->flags |= MTPDstickerSet::Flag::f_installed_date;
|
||||
it->installDate = base::unixtime::now();
|
||||
auto changedFlags = flags ^ it->flags;
|
||||
const auto set = it->second.get();
|
||||
auto flags = set->flags;
|
||||
set->flags &= ~(MTPDstickerSet::Flag::f_archived | MTPDstickerSet_ClientFlag::f_unread);
|
||||
set->flags |= MTPDstickerSet::Flag::f_installed_date;
|
||||
set->installDate = base::unixtime::now();
|
||||
auto changedFlags = flags ^ set->flags;
|
||||
|
||||
auto &order = Auth().data().stickerSetsOrderRef();
|
||||
int insertAtIndex = 0, currentIndex = order.indexOf(setId);
|
||||
@@ -142,14 +147,15 @@ void InstallLocally(uint64 setId) {
|
||||
order.insert(insertAtIndex, setId);
|
||||
}
|
||||
|
||||
auto custom = sets.find(CustomSetId);
|
||||
if (custom != sets.cend()) {
|
||||
for_const (auto sticker, it->stickers) {
|
||||
auto customIt = sets.find(CustomSetId);
|
||||
if (customIt != sets.cend()) {
|
||||
const auto custom = customIt->second.get();
|
||||
for (const auto sticker : std::as_const(set->stickers)) {
|
||||
int removeIndex = custom->stickers.indexOf(sticker);
|
||||
if (removeIndex >= 0) custom->stickers.removeAt(removeIndex);
|
||||
}
|
||||
if (custom->stickers.isEmpty()) {
|
||||
sets.erase(custom);
|
||||
sets.erase(customIt);
|
||||
}
|
||||
}
|
||||
Local::writeInstalledStickers();
|
||||
@@ -167,14 +173,15 @@ void InstallLocally(uint64 setId) {
|
||||
}
|
||||
|
||||
void UndoInstallLocally(uint64 setId) {
|
||||
auto &sets = Auth().data().stickerSetsRef();
|
||||
auto it = sets.find(setId);
|
||||
const auto &sets = Auth().data().stickerSets();
|
||||
const auto it = sets.find(setId);
|
||||
if (it == sets.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
it->flags &= ~MTPDstickerSet::Flag::f_installed_date;
|
||||
it->installDate = TimeId(0);
|
||||
const auto set = it->second.get();
|
||||
set->flags &= ~MTPDstickerSet::Flag::f_installed_date;
|
||||
set->installDate = TimeId(0);
|
||||
|
||||
auto &order = Auth().data().stickerSetsOrderRef();
|
||||
int currentIndex = order.indexOf(setId);
|
||||
@@ -191,11 +198,12 @@ void UndoInstallLocally(uint64 setId) {
|
||||
}
|
||||
|
||||
bool IsFaved(not_null<const DocumentData*> document) {
|
||||
const auto it = Auth().data().stickerSets().constFind(FavedSetId);
|
||||
if (it == Auth().data().stickerSets().cend()) {
|
||||
const auto &sets = Auth().data().stickerSets();
|
||||
const auto it = sets.find(FavedSetId);
|
||||
if (it == sets.cend()) {
|
||||
return false;
|
||||
}
|
||||
for (const auto sticker : it->stickers) {
|
||||
for (const auto sticker : it->second->stickers) {
|
||||
if (sticker == document) {
|
||||
return true;
|
||||
}
|
||||
@@ -253,11 +261,14 @@ void MoveFavedToFront(Set &set, int index) {
|
||||
|
||||
void RequestSetToPushFaved(not_null<DocumentData*> document);
|
||||
|
||||
void SetIsFaved(not_null<DocumentData*> document, std::optional<std::vector<not_null<EmojiPtr>>> emojiList = std::nullopt) {
|
||||
auto &sets = Auth().data().stickerSetsRef();
|
||||
void SetIsFaved(
|
||||
not_null<DocumentData*> document,
|
||||
std::optional<std::vector<not_null<EmojiPtr>>> emojiList = std::nullopt) {
|
||||
auto &sets = document->owner().stickerSetsRef();
|
||||
auto it = sets.find(FavedSetId);
|
||||
if (it == sets.end()) {
|
||||
it = sets.insert(FavedSetId, Set(
|
||||
it = sets.emplace(FavedSetId, std::make_unique<Set>(
|
||||
&document->owner(),
|
||||
FavedSetId,
|
||||
uint64(0),
|
||||
Lang::Hard::FavedSetTitle(),
|
||||
@@ -265,19 +276,19 @@ void SetIsFaved(not_null<DocumentData*> document, std::optional<std::vector<not_
|
||||
0, // count
|
||||
0, // hash
|
||||
MTPDstickerSet_ClientFlag::f_special | 0,
|
||||
TimeId(0),
|
||||
ImagePtr()));
|
||||
TimeId(0))).first;
|
||||
}
|
||||
auto index = it->stickers.indexOf(document);
|
||||
const auto set = it->second.get();
|
||||
auto index = set->stickers.indexOf(document);
|
||||
if (index == 0) {
|
||||
return;
|
||||
}
|
||||
if (index > 0) {
|
||||
MoveFavedToFront(*it, index);
|
||||
MoveFavedToFront(*set, index);
|
||||
} else if (emojiList) {
|
||||
PushFavedToFront(*it, document, *emojiList);
|
||||
PushFavedToFront(*set, document, *emojiList);
|
||||
} else if (auto list = GetEmojiListFromSet(document)) {
|
||||
PushFavedToFront(*it, document, *list);
|
||||
PushFavedToFront(*set, document, *list);
|
||||
} else {
|
||||
RequestSetToPushFaved(document);
|
||||
return;
|
||||
@@ -303,9 +314,9 @@ void RequestSetToPushFaved(not_null<DocumentData*> document) {
|
||||
auto list = std::vector<not_null<EmojiPtr>>();
|
||||
auto &d = result.c_messages_stickerSet();
|
||||
list.reserve(d.vpacks().v.size());
|
||||
for_const (auto &mtpPack, d.vpacks().v) {
|
||||
for (const auto &mtpPack : d.vpacks().v) {
|
||||
auto &pack = mtpPack.c_stickerPack();
|
||||
for_const (auto &documentId, pack.vdocuments().v) {
|
||||
for (const auto &documentId : pack.vdocuments().v) {
|
||||
if (documentId.v == document->id) {
|
||||
if (const auto emoji = Ui::Emoji::Find(qs(mtpPack.c_stickerPack().vemoticon()))) {
|
||||
list.emplace_back(emoji);
|
||||
@@ -331,23 +342,24 @@ void SetIsNotFaved(not_null<DocumentData*> document) {
|
||||
if (it == sets.end()) {
|
||||
return;
|
||||
}
|
||||
auto index = it->stickers.indexOf(document);
|
||||
const auto set = it->second.get();
|
||||
auto index = set->stickers.indexOf(document);
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
it->stickers.removeAt(index);
|
||||
for (auto i = it->emoji.begin(); i != it->emoji.end();) {
|
||||
set->stickers.removeAt(index);
|
||||
for (auto i = set->emoji.begin(); i != set->emoji.end();) {
|
||||
auto index = i->indexOf(document);
|
||||
if (index >= 0) {
|
||||
i->removeAt(index);
|
||||
if (i->empty()) {
|
||||
i = it->emoji.erase(i);
|
||||
i = set->emoji.erase(i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
++i;
|
||||
}
|
||||
if (it->stickers.empty()) {
|
||||
if (set->stickers.empty()) {
|
||||
sets.erase(it);
|
||||
}
|
||||
Local::writeFavedStickers();
|
||||
@@ -368,14 +380,14 @@ void SetsReceived(const QVector<MTPStickerSet> &data, int32 hash) {
|
||||
|
||||
auto &sets = Auth().data().stickerSetsRef();
|
||||
QMap<uint64, uint64> setsToRequest;
|
||||
for (auto &set : sets) {
|
||||
if (!(set.flags & MTPDstickerSet::Flag::f_archived)) {
|
||||
for (auto &[id, set] : sets) {
|
||||
if (!(set->flags & MTPDstickerSet::Flag::f_archived)) {
|
||||
// Mark for removing.
|
||||
set.flags &= ~MTPDstickerSet::Flag::f_installed_date;
|
||||
set.installDate = 0;
|
||||
set->flags &= ~MTPDstickerSet::Flag::f_installed_date;
|
||||
set->installDate = 0;
|
||||
}
|
||||
}
|
||||
for_const (auto &setData, data) {
|
||||
for (const auto &setData : data) {
|
||||
if (setData.type() == mtpc_stickerSet) {
|
||||
auto set = FeedSet(setData.c_stickerSet());
|
||||
if (!(set->flags & MTPDstickerSet::Flag::f_archived) || (set->flags & MTPDstickerSet::Flag::f_official)) {
|
||||
@@ -388,14 +400,15 @@ void SetsReceived(const QVector<MTPStickerSet> &data, int32 hash) {
|
||||
}
|
||||
auto writeRecent = false;
|
||||
auto &recent = GetRecentPack();
|
||||
for (auto it = sets.begin(), e = sets.end(); it != e;) {
|
||||
bool installed = (it->flags & MTPDstickerSet::Flag::f_installed_date);
|
||||
bool featured = (it->flags & MTPDstickerSet_ClientFlag::f_featured);
|
||||
bool special = (it->flags & MTPDstickerSet_ClientFlag::f_special);
|
||||
bool archived = (it->flags & MTPDstickerSet::Flag::f_archived);
|
||||
for (auto it = sets.begin(); it != sets.end();) {
|
||||
const auto set = it->second.get();
|
||||
bool installed = (set->flags & MTPDstickerSet::Flag::f_installed_date);
|
||||
bool featured = (set->flags & MTPDstickerSet_ClientFlag::f_featured);
|
||||
bool special = (set->flags & MTPDstickerSet_ClientFlag::f_special);
|
||||
bool archived = (set->flags & MTPDstickerSet::Flag::f_archived);
|
||||
if (!installed) { // remove not mine sets from recent stickers
|
||||
for (auto i = recent.begin(); i != recent.cend();) {
|
||||
if (it->stickers.indexOf(i->first) >= 0) {
|
||||
if (set->stickers.indexOf(i->first) >= 0) {
|
||||
i = recent.erase(i);
|
||||
writeRecent = true;
|
||||
} else {
|
||||
@@ -436,7 +449,7 @@ void SetPackAndEmoji(
|
||||
set.stickers = std::move(pack);
|
||||
set.dates = std::move(dates);
|
||||
set.emoji.clear();
|
||||
for_const (auto &mtpPack, packs) {
|
||||
for (const auto &mtpPack : packs) {
|
||||
Assert(mtpPack.type() == mtpc_stickerPack);
|
||||
auto &pack = mtpPack.c_stickerPack();
|
||||
if (auto emoji = Ui::Emoji::Find(qs(pack.vemoticon()))) {
|
||||
@@ -472,7 +485,8 @@ void SpecialSetReceived(
|
||||
}
|
||||
} else {
|
||||
if (it == sets.cend()) {
|
||||
it = sets.insert(setId, Set(
|
||||
it = sets.emplace(setId, std::make_unique<Set>(
|
||||
&Auth().data(),
|
||||
setId,
|
||||
uint64(0),
|
||||
setTitle,
|
||||
@@ -480,19 +494,19 @@ void SpecialSetReceived(
|
||||
0, // count
|
||||
0, // hash
|
||||
MTPDstickerSet_ClientFlag::f_special | 0,
|
||||
TimeId(0),
|
||||
ImagePtr()));
|
||||
TimeId(0))).first;
|
||||
} else {
|
||||
it->title = setTitle;
|
||||
it->second->title = setTitle;
|
||||
}
|
||||
it->hash = hash;
|
||||
const auto set = it->second.get();
|
||||
set->hash = hash;
|
||||
|
||||
auto dates = std::vector<TimeId>();
|
||||
auto dateIndex = 0;
|
||||
auto datesAvailable = (items.size() == usageDates.size())
|
||||
&& (setId == CloudRecentSetId);
|
||||
|
||||
auto custom = sets.find(CustomSetId);
|
||||
auto customIt = sets.find(CustomSetId);
|
||||
auto pack = Pack();
|
||||
pack.reserve(items.size());
|
||||
for (const auto &item : items) {
|
||||
@@ -506,22 +520,24 @@ void SpecialSetReceived(
|
||||
if (datesAvailable) {
|
||||
dates.push_back(TimeId(usageDates[dateIndex - 1].v));
|
||||
}
|
||||
if (custom != sets.cend()) {
|
||||
if (customIt != sets.cend()) {
|
||||
const auto custom = customIt->second.get();
|
||||
auto index = custom->stickers.indexOf(document);
|
||||
if (index >= 0) {
|
||||
custom->stickers.removeAt(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (custom != sets.cend() && custom->stickers.isEmpty()) {
|
||||
sets.erase(custom);
|
||||
custom = sets.end();
|
||||
if (customIt != sets.cend()
|
||||
&& customIt->second->stickers.isEmpty()) {
|
||||
sets.erase(customIt);
|
||||
customIt = sets.end();
|
||||
}
|
||||
|
||||
auto writeRecent = false;
|
||||
auto &recent = GetRecentPack();
|
||||
for (auto i = recent.begin(); i != recent.cend();) {
|
||||
if (it->stickers.indexOf(i->first) >= 0 && pack.indexOf(i->first) < 0) {
|
||||
if (set->stickers.indexOf(i->first) >= 0 && pack.indexOf(i->first) < 0) {
|
||||
i = recent.erase(i);
|
||||
writeRecent = true;
|
||||
} else {
|
||||
@@ -532,7 +548,7 @@ void SpecialSetReceived(
|
||||
if (pack.isEmpty()) {
|
||||
sets.erase(it);
|
||||
} else {
|
||||
SetPackAndEmoji(*it, std::move(pack), std::move(dates), packs);
|
||||
SetPackAndEmoji(*set, std::move(pack), std::move(dates), packs);
|
||||
}
|
||||
|
||||
if (writeRecent) {
|
||||
@@ -560,7 +576,7 @@ void SpecialSetReceived(
|
||||
}
|
||||
|
||||
void FeaturedSetsReceived(
|
||||
const QVector<MTPStickerSetCovered> &data,
|
||||
const QVector<MTPStickerSetCovered> &list,
|
||||
const QVector<MTPlong> &unread,
|
||||
int32 hash) {
|
||||
auto &&unreadIds = ranges::view::all(
|
||||
@@ -578,87 +594,77 @@ void FeaturedSetsReceived(
|
||||
|
||||
auto &sets = Auth().data().stickerSetsRef();
|
||||
auto setsToRequest = base::flat_map<uint64, uint64>();
|
||||
for (auto &set : sets) {
|
||||
for (auto &[id, set] : sets) {
|
||||
// Mark for removing.
|
||||
set.flags &= ~MTPDstickerSet_ClientFlag::f_featured;
|
||||
set->flags &= ~MTPDstickerSet_ClientFlag::f_featured;
|
||||
}
|
||||
for (int i = 0, l = data.size(); i != l; ++i) {
|
||||
auto &setData = data[i];
|
||||
const MTPDstickerSet *set = nullptr;
|
||||
switch (setData.type()) {
|
||||
case mtpc_stickerSetCovered: {
|
||||
auto &d = setData.c_stickerSetCovered();
|
||||
if (d.vset().type() == mtpc_stickerSet) {
|
||||
set = &d.vset().c_stickerSet();
|
||||
for (const auto &entry : list) {
|
||||
const auto data = entry.match([&](const auto &data) {
|
||||
return data.vset().match([&](const MTPDstickerSet &data) {
|
||||
return &data;
|
||||
});
|
||||
});
|
||||
auto it = sets.find(data->vid().v);
|
||||
const auto title = GetSetTitle(*data);
|
||||
const auto installDate = data->vinstalled_date().value_or_empty();
|
||||
const auto thumb = data->vthumb();
|
||||
const auto thumbnail = thumb
|
||||
? Images::FromPhotoSize(&Auth(), *data, *thumb)
|
||||
: ImageWithLocation();
|
||||
if (it == sets.cend()) {
|
||||
auto setClientFlags = MTPDstickerSet_ClientFlag::f_featured
|
||||
| MTPDstickerSet_ClientFlag::f_not_loaded;
|
||||
if (unreadMap.contains(data->vid().v)) {
|
||||
setClientFlags |= MTPDstickerSet_ClientFlag::f_unread;
|
||||
}
|
||||
} break;
|
||||
case mtpc_stickerSetMultiCovered: {
|
||||
auto &d = setData.c_stickerSetMultiCovered();
|
||||
if (d.vset().type() == mtpc_stickerSet) {
|
||||
set = &d.vset().c_stickerSet();
|
||||
}
|
||||
} break;
|
||||
}
|
||||
|
||||
if (set) {
|
||||
auto it = sets.find(set->vid().v);
|
||||
const auto title = GetSetTitle(*set);
|
||||
const auto installDate = set->vinstalled_date().value_or_empty();
|
||||
const auto thumb = set->vthumb();
|
||||
const auto thumbnail = thumb
|
||||
? Images::Create(*set, *thumb)
|
||||
: ImagePtr();
|
||||
if (it == sets.cend()) {
|
||||
auto setClientFlags = MTPDstickerSet_ClientFlag::f_featured
|
||||
| MTPDstickerSet_ClientFlag::f_not_loaded;
|
||||
if (unreadMap.contains(set->vid().v)) {
|
||||
setClientFlags |= MTPDstickerSet_ClientFlag::f_unread;
|
||||
}
|
||||
it = sets.insert(set->vid().v, Set(
|
||||
set->vid().v,
|
||||
set->vaccess_hash().v,
|
||||
title,
|
||||
qs(set->vshort_name()),
|
||||
set->vcount().v,
|
||||
set->vhash().v,
|
||||
set->vflags().v | setClientFlags,
|
||||
installDate,
|
||||
thumbnail));
|
||||
it = sets.emplace(data->vid().v, std::make_unique<Set>(
|
||||
&Auth().data(),
|
||||
data->vid().v,
|
||||
data->vaccess_hash().v,
|
||||
title,
|
||||
qs(data->vshort_name()),
|
||||
data->vcount().v,
|
||||
data->vhash().v,
|
||||
data->vflags().v | setClientFlags,
|
||||
installDate)).first;
|
||||
it->second->setThumbnail(thumbnail);
|
||||
} else {
|
||||
const auto set = it->second.get();
|
||||
set->access = data->vaccess_hash().v;
|
||||
set->title = title;
|
||||
set->shortName = qs(data->vshort_name());
|
||||
auto clientFlags = set->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_unread | MTPDstickerSet_ClientFlag::f_not_loaded | MTPDstickerSet_ClientFlag::f_special);
|
||||
set->flags = data->vflags().v | clientFlags;
|
||||
set->flags |= MTPDstickerSet_ClientFlag::f_featured;
|
||||
set->installDate = installDate;
|
||||
set->setThumbnail(thumbnail);
|
||||
if (unreadMap.contains(set->id)) {
|
||||
set->flags |= MTPDstickerSet_ClientFlag::f_unread;
|
||||
} else {
|
||||
it->access = set->vaccess_hash().v;
|
||||
it->title = title;
|
||||
it->shortName = qs(set->vshort_name());
|
||||
auto clientFlags = it->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_unread | MTPDstickerSet_ClientFlag::f_not_loaded | MTPDstickerSet_ClientFlag::f_special);
|
||||
it->flags = set->vflags().v | clientFlags;
|
||||
it->flags |= MTPDstickerSet_ClientFlag::f_featured;
|
||||
it->installDate = installDate;
|
||||
it->thumbnail = thumbnail;
|
||||
if (unreadMap.contains(it->id)) {
|
||||
it->flags |= MTPDstickerSet_ClientFlag::f_unread;
|
||||
} else {
|
||||
it->flags &= ~MTPDstickerSet_ClientFlag::f_unread;
|
||||
}
|
||||
if (it->count != set->vcount().v || it->hash != set->vhash().v || it->emoji.isEmpty()) {
|
||||
it->count = set->vcount().v;
|
||||
it->hash = set->vhash().v;
|
||||
it->flags |= MTPDstickerSet_ClientFlag::f_not_loaded; // need to request this set
|
||||
}
|
||||
set->flags &= ~MTPDstickerSet_ClientFlag::f_unread;
|
||||
}
|
||||
setsOrder.push_back(set->vid().v);
|
||||
if (it->stickers.isEmpty() || (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
|
||||
setsToRequest.emplace(set->vid().v, set->vaccess_hash().v);
|
||||
if (set->count != data->vcount().v || set->hash != data->vhash().v || set->emoji.isEmpty()) {
|
||||
set->count = data->vcount().v;
|
||||
set->hash = data->vhash().v;
|
||||
set->flags |= MTPDstickerSet_ClientFlag::f_not_loaded; // need to request this set
|
||||
}
|
||||
}
|
||||
setsOrder.push_back(data->vid().v);
|
||||
if (it->second->stickers.isEmpty()
|
||||
|| (it->second->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
|
||||
setsToRequest.emplace(data->vid().v, data->vaccess_hash().v);
|
||||
}
|
||||
}
|
||||
|
||||
auto unreadCount = 0;
|
||||
for (auto it = sets.begin(), e = sets.end(); it != e;) {
|
||||
bool installed = (it->flags & MTPDstickerSet::Flag::f_installed_date);
|
||||
bool featured = (it->flags & MTPDstickerSet_ClientFlag::f_featured);
|
||||
bool special = (it->flags & MTPDstickerSet_ClientFlag::f_special);
|
||||
bool archived = (it->flags & MTPDstickerSet::Flag::f_archived);
|
||||
for (auto it = sets.begin(); it != sets.end();) {
|
||||
const auto set = it->second.get();
|
||||
bool installed = (set->flags & MTPDstickerSet::Flag::f_installed_date);
|
||||
bool featured = (set->flags & MTPDstickerSet_ClientFlag::f_featured);
|
||||
bool special = (set->flags & MTPDstickerSet_ClientFlag::f_special);
|
||||
bool archived = (set->flags & MTPDstickerSet::Flag::f_archived);
|
||||
if (installed || featured || special || archived) {
|
||||
if (featured && (it->flags & MTPDstickerSet_ClientFlag::f_unread)) {
|
||||
if (featured && (set->flags & MTPDstickerSet_ClientFlag::f_unread)) {
|
||||
++unreadCount;
|
||||
}
|
||||
++it;
|
||||
@@ -772,7 +778,7 @@ std::vector<not_null<DocumentData*>> GetListByEmoji(
|
||||
const auto setId = sticker->set.c_inputStickerSetID().vid().v;
|
||||
const auto setIt = sets.find(setId);
|
||||
if (setIt != sets.end()) {
|
||||
return InstallDateAdjusted(setIt->installDate, document);
|
||||
return InstallDateAdjusted(setIt->second->installDate, document);
|
||||
}
|
||||
}
|
||||
return TimeId(0);
|
||||
@@ -780,20 +786,21 @@ std::vector<not_null<DocumentData*>> GetListByEmoji(
|
||||
|
||||
auto recentIt = sets.find(Stickers::CloudRecentSetId);
|
||||
if (recentIt != sets.cend()) {
|
||||
auto i = recentIt->emoji.constFind(original);
|
||||
if (i != recentIt->emoji.cend()) {
|
||||
const auto recent = recentIt->second.get();
|
||||
auto i = recent->emoji.constFind(original);
|
||||
if (i != recent->emoji.cend()) {
|
||||
result.reserve(i->size());
|
||||
for (const auto document : *i) {
|
||||
const auto usageDate = [&] {
|
||||
if (recentIt->dates.empty()) {
|
||||
if (recent->dates.empty()) {
|
||||
return TimeId(0);
|
||||
}
|
||||
const auto index = recentIt->stickers.indexOf(document);
|
||||
const auto index = recent->stickers.indexOf(document);
|
||||
if (index < 0) {
|
||||
return TimeId(0);
|
||||
}
|
||||
Assert(index < recentIt->dates.size());
|
||||
return recentIt->dates[index];
|
||||
Assert(index < recent->dates.size());
|
||||
return recent->dates[index];
|
||||
}();
|
||||
const auto date = usageDate
|
||||
? usageDate
|
||||
@@ -807,22 +814,23 @@ std::vector<not_null<DocumentData*>> GetListByEmoji(
|
||||
const auto addList = [&](const Order &order, MTPDstickerSet::Flag skip) {
|
||||
for (const auto setId : order) {
|
||||
auto it = sets.find(setId);
|
||||
if (it == sets.cend() || (it->flags & skip)) {
|
||||
if (it == sets.cend() || (it->second->flags & skip)) {
|
||||
continue;
|
||||
}
|
||||
if (it->emoji.isEmpty()) {
|
||||
setsToRequest.emplace(it->id, it->access);
|
||||
it->flags |= MTPDstickerSet_ClientFlag::f_not_loaded;
|
||||
const auto set = it->second.get();
|
||||
if (set->emoji.isEmpty()) {
|
||||
setsToRequest.emplace(set->id, set->access);
|
||||
set->flags |= MTPDstickerSet_ClientFlag::f_not_loaded;
|
||||
continue;
|
||||
}
|
||||
auto i = it->emoji.constFind(original);
|
||||
if (i == it->emoji.cend()) {
|
||||
auto i = set->emoji.constFind(original);
|
||||
if (i == set->emoji.cend()) {
|
||||
continue;
|
||||
}
|
||||
const auto my = (it->flags & MTPDstickerSet::Flag::f_installed_date);
|
||||
const auto my = (set->flags & MTPDstickerSet::Flag::f_installed_date);
|
||||
result.reserve(result.size() + i->size());
|
||||
for (const auto document : *i) {
|
||||
const auto installDate = my ? it->installDate : TimeId(0);
|
||||
const auto installDate = my ? set->installDate : TimeId(0);
|
||||
const auto date = (installDate > 1)
|
||||
? InstallDateAdjusted(installDate, document)
|
||||
: my
|
||||
@@ -877,13 +885,14 @@ std::optional<std::vector<not_null<EmojiPtr>>> GetEmojiListFromSet(
|
||||
if (inputSet.type() != mtpc_inputStickerSetID) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto &sets = Auth().data().stickerSets();
|
||||
auto it = sets.constFind(inputSet.c_inputStickerSetID().vid().v);
|
||||
const auto &sets = Auth().data().stickerSets();
|
||||
auto it = sets.find(inputSet.c_inputStickerSetID().vid().v);
|
||||
if (it == sets.cend()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto set = it->second.get();
|
||||
auto result = std::vector<not_null<EmojiPtr>>();
|
||||
for (auto i = it->emoji.cbegin(), e = it->emoji.cend(); i != e; ++i) {
|
||||
for (auto i = set->emoji.cbegin(), e = set->emoji.cend(); i != e; ++i) {
|
||||
if (i->contains(document)) {
|
||||
result.emplace_back(i.key());
|
||||
}
|
||||
@@ -896,61 +905,66 @@ std::optional<std::vector<not_null<EmojiPtr>>> GetEmojiListFromSet(
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Set *FeedSet(const MTPDstickerSet &set) {
|
||||
Set *FeedSet(const MTPDstickerSet &data) {
|
||||
auto &sets = Auth().data().stickerSetsRef();
|
||||
auto it = sets.find(set.vid().v);
|
||||
auto title = GetSetTitle(set);
|
||||
auto it = sets.find(data.vid().v);
|
||||
auto title = GetSetTitle(data);
|
||||
auto flags = MTPDstickerSet::Flags(0);
|
||||
const auto thumb = set.vthumb();
|
||||
const auto thumbnail = thumb ? Images::Create(set, *thumb) : ImagePtr();
|
||||
const auto thumb = data.vthumb();
|
||||
const auto thumbnail = thumb
|
||||
? Images::FromPhotoSize(&Auth(), data, *thumb)
|
||||
: ImageWithLocation();
|
||||
if (it == sets.cend()) {
|
||||
it = sets.insert(set.vid().v, Stickers::Set(
|
||||
set.vid().v,
|
||||
set.vaccess_hash().v,
|
||||
it = sets.emplace(data.vid().v, std::make_unique<Set>(
|
||||
&Auth().data(),
|
||||
data.vid().v,
|
||||
data.vaccess_hash().v,
|
||||
title,
|
||||
qs(set.vshort_name()),
|
||||
set.vcount().v,
|
||||
set.vhash().v,
|
||||
set.vflags().v | MTPDstickerSet_ClientFlag::f_not_loaded,
|
||||
set.vinstalled_date().value_or_empty(),
|
||||
thumbnail));
|
||||
qs(data.vshort_name()),
|
||||
data.vcount().v,
|
||||
data.vhash().v,
|
||||
data.vflags().v | MTPDstickerSet_ClientFlag::f_not_loaded,
|
||||
data.vinstalled_date().value_or_empty())).first;
|
||||
it->second->setThumbnail(thumbnail);
|
||||
} else {
|
||||
it->access = set.vaccess_hash().v;
|
||||
it->title = title;
|
||||
it->shortName = qs(set.vshort_name());
|
||||
flags = it->flags;
|
||||
auto clientFlags = it->flags
|
||||
const auto set = it->second.get();
|
||||
set->access = data.vaccess_hash().v;
|
||||
set->title = title;
|
||||
set->shortName = qs(data.vshort_name());
|
||||
flags = set->flags;
|
||||
auto clientFlags = set->flags
|
||||
& (MTPDstickerSet_ClientFlag::f_featured
|
||||
| MTPDstickerSet_ClientFlag::f_unread
|
||||
| MTPDstickerSet_ClientFlag::f_not_loaded
|
||||
| MTPDstickerSet_ClientFlag::f_special);
|
||||
it->flags = set.vflags().v | clientFlags;
|
||||
const auto installDate = set.vinstalled_date();
|
||||
it->installDate = installDate
|
||||
set->flags = data.vflags().v | clientFlags;
|
||||
const auto installDate = data.vinstalled_date();
|
||||
set->installDate = installDate
|
||||
? (installDate->v ? installDate->v : base::unixtime::now())
|
||||
: TimeId(0);
|
||||
it->thumbnail = thumbnail;
|
||||
if (it->count != set.vcount().v
|
||||
|| it->hash != set.vhash().v
|
||||
|| it->emoji.isEmpty()) {
|
||||
// Need to request this set.
|
||||
it->count = set.vcount().v;
|
||||
it->hash = set.vhash().v;
|
||||
it->flags |= MTPDstickerSet_ClientFlag::f_not_loaded;
|
||||
it->second->setThumbnail(thumbnail);
|
||||
if (set->count != data.vcount().v
|
||||
|| set->hash != data.vhash().v
|
||||
|| set->emoji.isEmpty()) {
|
||||
// Need to request this data.
|
||||
set->count = data.vcount().v;
|
||||
set->hash = data.vhash().v;
|
||||
set->flags |= MTPDstickerSet_ClientFlag::f_not_loaded;
|
||||
}
|
||||
}
|
||||
auto changedFlags = (flags ^ it->flags);
|
||||
const auto set = it->second.get();
|
||||
auto changedFlags = (flags ^ set->flags);
|
||||
if (changedFlags & MTPDstickerSet::Flag::f_archived) {
|
||||
auto index = Auth().data().archivedStickerSetsOrder().indexOf(it->id);
|
||||
if (it->flags & MTPDstickerSet::Flag::f_archived) {
|
||||
auto index = Auth().data().archivedStickerSetsOrder().indexOf(set->id);
|
||||
if (set->flags & MTPDstickerSet::Flag::f_archived) {
|
||||
if (index < 0) {
|
||||
Auth().data().archivedStickerSetsOrderRef().push_front(it->id);
|
||||
Auth().data().archivedStickerSetsOrderRef().push_front(set->id);
|
||||
}
|
||||
} else if (index >= 0) {
|
||||
Auth().data().archivedStickerSetsOrderRef().removeAt(index);
|
||||
}
|
||||
}
|
||||
return &it.value();
|
||||
return it->second.get();
|
||||
}
|
||||
|
||||
Set *FeedSetFull(const MTPmessages_StickerSet &data) {
|
||||
@@ -961,16 +975,19 @@ Set *FeedSetFull(const MTPmessages_StickerSet &data) {
|
||||
const auto &s = d.vset().c_stickerSet();
|
||||
|
||||
auto &sets = Auth().data().stickerSetsRef();
|
||||
auto it = sets.find(s.vid().v);
|
||||
const auto wasArchived = (it->flags & MTPDstickerSet::Flag::f_archived);
|
||||
const auto wasArchived = [&] {
|
||||
auto it = sets.find(s.vid().v);
|
||||
return (it != sets.end())
|
||||
&& (it->second->flags & MTPDstickerSet::Flag::f_archived);
|
||||
}();
|
||||
|
||||
auto set = FeedSet(s);
|
||||
|
||||
set->flags &= ~MTPDstickerSet_ClientFlag::f_not_loaded;
|
||||
|
||||
auto &d_docs = d.vdocuments().v;
|
||||
auto custom = sets.find(Stickers::CustomSetId);
|
||||
auto inputSet = MTP_inputStickerSetID(
|
||||
const auto &d_docs = d.vdocuments().v;
|
||||
auto customIt = sets.find(Stickers::CustomSetId);
|
||||
const auto inputSet = MTP_inputStickerSetID(
|
||||
MTP_long(set->id),
|
||||
MTP_long(set->access));
|
||||
|
||||
@@ -984,16 +1001,17 @@ Set *FeedSetFull(const MTPmessages_StickerSet &data) {
|
||||
if (document->sticker()->set.type() != mtpc_inputStickerSetID) {
|
||||
document->sticker()->set = inputSet;
|
||||
}
|
||||
if (custom != sets.cend()) {
|
||||
if (customIt != sets.cend()) {
|
||||
const auto custom = customIt->second.get();
|
||||
const auto index = custom->stickers.indexOf(document);
|
||||
if (index >= 0) {
|
||||
custom->stickers.removeAt(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (custom != sets.cend() && custom->stickers.isEmpty()) {
|
||||
sets.erase(custom);
|
||||
custom = sets.end();
|
||||
if (customIt != sets.cend() && customIt->second->stickers.isEmpty()) {
|
||||
sets.erase(customIt);
|
||||
customIt = sets.end();
|
||||
}
|
||||
|
||||
auto writeRecent = false;
|
||||
@@ -1042,7 +1060,7 @@ Set *FeedSetFull(const MTPmessages_StickerSet &data) {
|
||||
}
|
||||
|
||||
if (set) {
|
||||
const auto isArchived = (set->flags & MTPDstickerSet::Flag::f_archived);
|
||||
const auto isArchived = !!(set->flags & MTPDstickerSet::Flag::f_archived);
|
||||
if (set->flags & MTPDstickerSet::Flag::f_installed_date) {
|
||||
if (!isArchived) {
|
||||
Local::writeInstalledStickers();
|
||||
@@ -1146,10 +1164,11 @@ auto LottieCachedFromContent(
|
||||
template <typename Method>
|
||||
auto LottieFromDocument(
|
||||
Method &&method,
|
||||
not_null<DocumentData*> document,
|
||||
not_null<Data::DocumentMedia*> media,
|
||||
uint8 keyShift,
|
||||
QSize box) {
|
||||
const auto data = document->data();
|
||||
const auto document = media->owner();
|
||||
const auto data = media->bytes();
|
||||
const auto filepath = document->filepath();
|
||||
if (box.width() * box.height() > kDontCacheLottieAfterArea) {
|
||||
// Don't use frame caching for large stickers.
|
||||
@@ -1160,7 +1179,7 @@ auto LottieFromDocument(
|
||||
if (const auto baseKey = document->bigFileBaseCacheKey()) {
|
||||
return LottieCachedFromContent(
|
||||
std::forward<Method>(method),
|
||||
*baseKey,
|
||||
baseKey,
|
||||
keyShift,
|
||||
&document->session(),
|
||||
Lottie::ReadContent(data, filepath),
|
||||
@@ -1172,13 +1191,13 @@ auto LottieFromDocument(
|
||||
}
|
||||
|
||||
std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(
|
||||
not_null<DocumentData*> document,
|
||||
not_null<Data::DocumentMedia*> media,
|
||||
LottieSize sizeTag,
|
||||
QSize box,
|
||||
Lottie::Quality quality,
|
||||
std::shared_ptr<Lottie::FrameRenderer> renderer) {
|
||||
return LottiePlayerFromDocument(
|
||||
document,
|
||||
media,
|
||||
nullptr,
|
||||
sizeTag,
|
||||
box,
|
||||
@@ -1187,7 +1206,7 @@ std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(
|
||||
}
|
||||
|
||||
std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(
|
||||
not_null<DocumentData*> document,
|
||||
not_null<Data::DocumentMedia*> media,
|
||||
const Lottie::ColorReplacements *replacements,
|
||||
LottieSize sizeTag,
|
||||
QSize box,
|
||||
@@ -1202,60 +1221,59 @@ std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(
|
||||
};
|
||||
const auto tag = replacements ? replacements->tag : uint8(0);
|
||||
const auto keyShift = ((tag << 4) & 0xF0) | (uint8(sizeTag) & 0x0F);
|
||||
return LottieFromDocument(method, document, uint8(keyShift), box);
|
||||
return LottieFromDocument(method, media, uint8(keyShift), box);
|
||||
}
|
||||
|
||||
not_null<Lottie::Animation*> LottieAnimationFromDocument(
|
||||
not_null<Lottie::MultiPlayer*> player,
|
||||
not_null<DocumentData*> document,
|
||||
not_null<Data::DocumentMedia*> media,
|
||||
LottieSize sizeTag,
|
||||
QSize box) {
|
||||
const auto method = [&](auto &&...args) {
|
||||
return player->append(std::forward<decltype(args)>(args)...);
|
||||
};
|
||||
return LottieFromDocument(method, document, uint8(sizeTag), box);
|
||||
return LottieFromDocument(method, media, uint8(sizeTag), box);
|
||||
}
|
||||
|
||||
bool HasLottieThumbnail(
|
||||
ImagePtr thumbnail,
|
||||
not_null<DocumentData*> sticker) {
|
||||
if (thumbnail) {
|
||||
if (!thumbnail->loaded()) {
|
||||
return false;
|
||||
}
|
||||
const auto &location = thumbnail->location();
|
||||
const auto &bytes = thumbnail->bytesForCache();
|
||||
return location.valid()
|
||||
&& location.type() == StorageFileLocation::Type::StickerSetThumb
|
||||
&& !bytes.isEmpty();
|
||||
} else if (const auto info = sticker->sticker()) {
|
||||
SetThumbnailView *thumb,
|
||||
Data::DocumentMedia *media) {
|
||||
if (thumb) {
|
||||
return !thumb->content().isEmpty();
|
||||
} else if (!media) {
|
||||
return false;
|
||||
}
|
||||
const auto document = media->owner();
|
||||
if (const auto info = document->sticker()) {
|
||||
if (!info->animated) {
|
||||
return false;
|
||||
}
|
||||
sticker->automaticLoad(sticker->stickerSetOrigin(), nullptr);
|
||||
if (!sticker->loaded()) {
|
||||
media->automaticLoad(document->stickerSetOrigin(), nullptr);
|
||||
if (!media->loaded()) {
|
||||
return false;
|
||||
}
|
||||
return sticker->bigFileBaseCacheKey().has_value();
|
||||
return document->bigFileBaseCacheKey().valid();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<Lottie::SinglePlayer> LottieThumbnail(
|
||||
ImagePtr thumbnail,
|
||||
not_null<DocumentData*> sticker,
|
||||
SetThumbnailView *thumb,
|
||||
Data::DocumentMedia *media,
|
||||
LottieSize sizeTag,
|
||||
QSize box,
|
||||
std::shared_ptr<Lottie::FrameRenderer> renderer) {
|
||||
const auto baseKey = thumbnail
|
||||
? thumbnail->location().file().bigFileBaseCacheKey()
|
||||
: sticker->bigFileBaseCacheKey();
|
||||
const auto baseKey = thumb
|
||||
? thumb->owner()->thumbnailLocation().file().bigFileBaseCacheKey()
|
||||
: media
|
||||
? media->owner()->bigFileBaseCacheKey()
|
||||
: Storage::Cache::Key();
|
||||
if (!baseKey) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto content = (thumbnail
|
||||
? thumbnail->bytesForCache()
|
||||
: Lottie::ReadContent(sticker->data(), sticker->filepath()));
|
||||
const auto content = thumb
|
||||
? thumb->content()
|
||||
: Lottie::ReadContent(media->bytes(), media->owner()->filepath());
|
||||
if (content.isEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1263,51 +1281,18 @@ std::unique_ptr<Lottie::SinglePlayer> LottieThumbnail(
|
||||
return std::make_unique<Lottie::SinglePlayer>(
|
||||
std::forward<decltype(args)>(args)...);
|
||||
};
|
||||
const auto session = thumb
|
||||
? &thumb->owner()->session()
|
||||
: media
|
||||
? &media->owner()->session()
|
||||
: nullptr;
|
||||
return LottieCachedFromContent(
|
||||
method,
|
||||
*baseKey,
|
||||
baseKey,
|
||||
uint8(sizeTag),
|
||||
&sticker->session(),
|
||||
session,
|
||||
content,
|
||||
box);
|
||||
}
|
||||
|
||||
ThumbnailSource::ThumbnailSource(
|
||||
const StorageImageLocation &location,
|
||||
int size)
|
||||
: StorageSource(location, size) {
|
||||
}
|
||||
|
||||
QImage ThumbnailSource::takeLoaded() {
|
||||
if (_bytesForAnimated.isEmpty()
|
||||
&& _loader
|
||||
&& _loader->finished()
|
||||
&& !_loader->cancelled()) {
|
||||
_bytesForAnimated = _loader->bytes();
|
||||
}
|
||||
auto result = StorageSource::takeLoaded();
|
||||
if (!_bytesForAnimated.isEmpty()
|
||||
&& !result.isNull()
|
||||
&& result.size() != Image::Empty()->original().size()) {
|
||||
_bytesForAnimated = QByteArray();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QByteArray ThumbnailSource::bytesForCache() {
|
||||
return _bytesForAnimated;
|
||||
}
|
||||
|
||||
std::unique_ptr<FileLoader> ThumbnailSource::createLoader(
|
||||
Data::FileOrigin origin,
|
||||
LoadFromCloudSetting fromCloud,
|
||||
bool autoLoading) {
|
||||
auto result = StorageSource::createLoader(
|
||||
origin,
|
||||
fromCloud,
|
||||
autoLoading);
|
||||
_loader = result.get();
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Stickers
|
||||
|
||||
@@ -8,10 +8,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/sender.h"
|
||||
#include "ui/image/image_source.h"
|
||||
|
||||
class DocumentData;
|
||||
|
||||
namespace Data {
|
||||
class DocumentMedia;
|
||||
} // namespace Data
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
@@ -42,53 +45,8 @@ constexpr auto FeaturedSetId = 0xFFFFFFFFFFFFFFFBULL; // for emoji/stickers pane
|
||||
constexpr auto FavedSetId = 0xFFFFFFFFFFFFFFFAULL; // for cloud-stored faved stickers
|
||||
constexpr auto MegagroupSetId = 0xFFFFFFFFFFFFFFEFULL; // for setting up megagroup sticker set
|
||||
|
||||
using Order = QList<uint64>;
|
||||
using SavedGifs = QVector<DocumentData*>;
|
||||
using Pack = QVector<DocumentData*>;
|
||||
using ByEmojiMap = QMap<EmojiPtr, Pack>;
|
||||
|
||||
struct Set {
|
||||
Set(
|
||||
uint64 id,
|
||||
uint64 access,
|
||||
const QString &title,
|
||||
const QString &shortName,
|
||||
int count,
|
||||
int32 hash,
|
||||
MTPDstickerSet::Flags flags,
|
||||
TimeId installDate,
|
||||
ImagePtr thumbnail)
|
||||
: id(id)
|
||||
, access(access)
|
||||
, title(title)
|
||||
, shortName(shortName)
|
||||
, count(count)
|
||||
, hash(hash)
|
||||
, flags(flags)
|
||||
, installDate(installDate)
|
||||
, thumbnail(thumbnail) {
|
||||
}
|
||||
uint64 id = 0;
|
||||
uint64 access = 0;
|
||||
QString title, shortName;
|
||||
int count = 0;
|
||||
int32 hash = 0;
|
||||
MTPDstickerSet::Flags flags;
|
||||
TimeId installDate = 0;
|
||||
ImagePtr thumbnail;
|
||||
Pack stickers;
|
||||
std::vector<TimeId> dates;
|
||||
Pack covers;
|
||||
ByEmojiMap emoji;
|
||||
};
|
||||
using Sets = QMap<uint64, Set>;
|
||||
|
||||
inline MTPInputStickerSet inputSetId(const Set &set) {
|
||||
if (set.id && set.access) {
|
||||
return MTP_inputStickerSetID(MTP_long(set.id), MTP_long(set.access));
|
||||
}
|
||||
return MTP_inputStickerSetShortName(MTP_string(set.shortName));
|
||||
}
|
||||
class Set;
|
||||
class SetThumbnailView;
|
||||
|
||||
void ApplyArchivedResult(const MTPDmessages_stickerSetInstallResultArchive &d);
|
||||
bool ApplyArchivedResultFake(); // For testing.
|
||||
@@ -106,7 +64,7 @@ void SpecialSetReceived(
|
||||
const QVector<MTPStickerPack> &packs = QVector<MTPStickerPack>(),
|
||||
const QVector<MTPint> &usageDates = QVector<MTPint>());
|
||||
void FeaturedSetsReceived(
|
||||
const QVector<MTPStickerSetCovered> &data,
|
||||
const QVector<MTPStickerSetCovered> &list,
|
||||
const QVector<MTPlong> &unread,
|
||||
int32 hash);
|
||||
void GifsReceived(const QVector<MTPDocument> &items, int32 hash);
|
||||
@@ -136,13 +94,13 @@ enum class LottieSize : uchar {
|
||||
};
|
||||
|
||||
[[nodiscard]] std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(
|
||||
not_null<DocumentData*> document,
|
||||
not_null<Data::DocumentMedia*> media,
|
||||
LottieSize sizeTag,
|
||||
QSize box,
|
||||
Lottie::Quality quality = Lottie::Quality(),
|
||||
std::shared_ptr<Lottie::FrameRenderer> renderer = nullptr);
|
||||
[[nodiscard]] std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(
|
||||
not_null<DocumentData*> document,
|
||||
not_null<Data::DocumentMedia*> media,
|
||||
const Lottie::ColorReplacements *replacements,
|
||||
LottieSize sizeTag,
|
||||
QSize box,
|
||||
@@ -150,40 +108,18 @@ enum class LottieSize : uchar {
|
||||
std::shared_ptr<Lottie::FrameRenderer> renderer = nullptr);
|
||||
[[nodiscard]] not_null<Lottie::Animation*> LottieAnimationFromDocument(
|
||||
not_null<Lottie::MultiPlayer*> player,
|
||||
not_null<DocumentData*> document,
|
||||
not_null<Data::DocumentMedia*> media,
|
||||
LottieSize sizeTag,
|
||||
QSize box);
|
||||
|
||||
[[nodiscard]] bool HasLottieThumbnail(
|
||||
ImagePtr thumbnail,
|
||||
not_null<DocumentData*> sticker);
|
||||
SetThumbnailView *thumb,
|
||||
Data::DocumentMedia *media);
|
||||
[[nodiscard]] std::unique_ptr<Lottie::SinglePlayer> LottieThumbnail(
|
||||
ImagePtr thumbnail,
|
||||
not_null<DocumentData*> sticker,
|
||||
SetThumbnailView *thumb,
|
||||
Data::DocumentMedia *media,
|
||||
LottieSize sizeTag,
|
||||
QSize box,
|
||||
std::shared_ptr<Lottie::FrameRenderer> renderer = nullptr);
|
||||
|
||||
class ThumbnailSource : public Images::StorageSource {
|
||||
public:
|
||||
ThumbnailSource(
|
||||
const StorageImageLocation &location,
|
||||
int size);
|
||||
|
||||
QImage takeLoaded() override;
|
||||
|
||||
QByteArray bytesForCache() override;
|
||||
|
||||
protected:
|
||||
std::unique_ptr<FileLoader> createLoader(
|
||||
Data::FileOrigin origin,
|
||||
LoadFromCloudSetting fromCloud,
|
||||
bool autoLoading) override;
|
||||
|
||||
private:
|
||||
QPointer<FileLoader> _loader;
|
||||
QByteArray _bytesForAnimated;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Stickers
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "main/main_session.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_document.h"
|
||||
#include "ui/image/image_location_factory.h"
|
||||
#include "storage/localimageloader.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "apiwrap.h"
|
||||
@@ -112,7 +113,7 @@ void DicePack::tryGenerateLocalZero() {
|
||||
Assert(result != nullptr);
|
||||
const auto document = _session->data().processDocument(
|
||||
result->document,
|
||||
std::move(result->thumb));
|
||||
Images::FromImageInMemory(result->thumb, "PNG"));
|
||||
document->setLocation(FileLocation(path));
|
||||
|
||||
_map.emplace(0, document);
|
||||
|
||||
@@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lottie/lottie_common.h"
|
||||
#include "ui/emoji_config.h"
|
||||
#include "ui/text/text_isolated_emoji.h"
|
||||
#include "ui/image/image_source.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "main/main_session.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_session.h"
|
||||
@@ -117,194 +117,6 @@ constexpr auto kClearSourceTimeout = 10 * crl::time(1000);
|
||||
return list[index - 1];
|
||||
}
|
||||
|
||||
class ImageSource : public Images::Source {
|
||||
public:
|
||||
explicit ImageSource(
|
||||
EmojiPtr emoji,
|
||||
not_null<crl::object_on_queue<EmojiImageLoader>*> loader);
|
||||
|
||||
void load(Data::FileOrigin origin) override;
|
||||
void loadEvenCancelled(Data::FileOrigin origin) override;
|
||||
QImage takeLoaded() override;
|
||||
void unload() override;
|
||||
|
||||
void automaticLoad(
|
||||
Data::FileOrigin origin,
|
||||
const HistoryItem *item) override;
|
||||
void automaticLoadSettingsChanged() override;
|
||||
|
||||
bool loading() override;
|
||||
bool displayLoading() override;
|
||||
void cancel() override;
|
||||
float64 progress() override;
|
||||
int loadOffset() override;
|
||||
|
||||
const StorageImageLocation &location() override;
|
||||
void refreshFileReference(const QByteArray &data) override;
|
||||
std::optional<Storage::Cache::Key> cacheKey() override;
|
||||
void setDelayedStorageLocation(
|
||||
const StorageImageLocation &location) override;
|
||||
void performDelayedLoad(Data::FileOrigin origin) override;
|
||||
bool isDelayedStorageImage() const override;
|
||||
void setImageBytes(const QByteArray &bytes) override;
|
||||
|
||||
int width() override;
|
||||
int height() override;
|
||||
int bytesSize() override;
|
||||
void setInformation(int size, int width, int height) override;
|
||||
|
||||
QByteArray bytesForCache() override;
|
||||
|
||||
private:
|
||||
// While HistoryView::Element-s are almost never destroyed
|
||||
// we make loading of the image lazy.
|
||||
not_null<crl::object_on_queue<EmojiImageLoader>*> _loader;
|
||||
EmojiPtr _emoji = nullptr;
|
||||
QImage _data;
|
||||
QByteArray _format;
|
||||
QByteArray _bytes;
|
||||
QSize _size;
|
||||
base::binary_guard _loading;
|
||||
|
||||
};
|
||||
|
||||
ImageSource::ImageSource(
|
||||
EmojiPtr emoji,
|
||||
not_null<crl::object_on_queue<EmojiImageLoader>*> loader)
|
||||
: _loader(loader)
|
||||
, _emoji(emoji)
|
||||
, _size(SingleSize()) {
|
||||
}
|
||||
|
||||
void ImageSource::load(Data::FileOrigin origin) {
|
||||
if (!_data.isNull()) {
|
||||
return;
|
||||
}
|
||||
if (_bytes.isEmpty()) {
|
||||
_loader->with([
|
||||
this,
|
||||
emoji = _emoji,
|
||||
guard = _loading.make_guard()
|
||||
](EmojiImageLoader &loader) mutable {
|
||||
if (!guard) {
|
||||
return;
|
||||
}
|
||||
crl::on_main(std::move(guard), [this, image = loader.prepare(emoji)]{
|
||||
_data = image;
|
||||
Auth().downloaderTaskFinished().notify();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
_data = App::readImage(_bytes, &_format, false);
|
||||
}
|
||||
}
|
||||
|
||||
void ImageSource::loadEvenCancelled(Data::FileOrigin origin) {
|
||||
load(origin);
|
||||
}
|
||||
|
||||
QImage ImageSource::takeLoaded() {
|
||||
load({});
|
||||
return _data;
|
||||
}
|
||||
|
||||
void ImageSource::unload() {
|
||||
if (_bytes.isEmpty() && !_data.isNull()) {
|
||||
if (_format != "JPG") {
|
||||
_format = "PNG";
|
||||
}
|
||||
{
|
||||
QBuffer buffer(&_bytes);
|
||||
_data.save(&buffer, _format);
|
||||
}
|
||||
Assert(!_bytes.isEmpty());
|
||||
}
|
||||
_data = QImage();
|
||||
}
|
||||
|
||||
void ImageSource::automaticLoad(
|
||||
Data::FileOrigin origin,
|
||||
const HistoryItem *item) {
|
||||
}
|
||||
|
||||
void ImageSource::automaticLoadSettingsChanged() {
|
||||
}
|
||||
|
||||
bool ImageSource::loading() {
|
||||
return _data.isNull() && _bytes.isEmpty();
|
||||
}
|
||||
|
||||
bool ImageSource::displayLoading() {
|
||||
return false;
|
||||
}
|
||||
|
||||
void ImageSource::cancel() {
|
||||
}
|
||||
|
||||
float64 ImageSource::progress() {
|
||||
return 1.;
|
||||
}
|
||||
|
||||
int ImageSource::loadOffset() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const StorageImageLocation &ImageSource::location() {
|
||||
return StorageImageLocation::Invalid();
|
||||
}
|
||||
|
||||
void ImageSource::refreshFileReference(const QByteArray &data) {
|
||||
}
|
||||
|
||||
std::optional<Storage::Cache::Key> ImageSource::cacheKey() {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void ImageSource::setDelayedStorageLocation(
|
||||
const StorageImageLocation &location) {
|
||||
}
|
||||
|
||||
void ImageSource::performDelayedLoad(Data::FileOrigin origin) {
|
||||
}
|
||||
|
||||
bool ImageSource::isDelayedStorageImage() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void ImageSource::setImageBytes(const QByteArray &bytes) {
|
||||
}
|
||||
|
||||
int ImageSource::width() {
|
||||
return _size.width();
|
||||
}
|
||||
|
||||
int ImageSource::height() {
|
||||
return _size.height();
|
||||
}
|
||||
|
||||
int ImageSource::bytesSize() {
|
||||
return _bytes.size();
|
||||
}
|
||||
|
||||
void ImageSource::setInformation(int size, int width, int height) {
|
||||
if (width && height) {
|
||||
_size = QSize(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray ImageSource::bytesForCache() {
|
||||
auto result = QByteArray();
|
||||
{
|
||||
QBuffer buffer(&result);
|
||||
if (!_data.save(&buffer, _format)) {
|
||||
if (_data.save(&buffer, "PNG")) {
|
||||
_format = "PNG";
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EmojiImageLoader::EmojiImageLoader(
|
||||
@@ -390,6 +202,10 @@ std::shared_ptr<UniversalImages> EmojiImageLoader::releaseImages() {
|
||||
|
||||
} // namespace details
|
||||
|
||||
QSize LargeEmojiImage::Size() {
|
||||
return details::SingleSize();
|
||||
}
|
||||
|
||||
EmojiPack::EmojiPack(not_null<Main::Session*> session)
|
||||
: _session(session)
|
||||
, _imageLoader(prepareSourceImages(), session->settings().largeEmoji())
|
||||
@@ -473,13 +289,36 @@ auto EmojiPack::stickerForEmoji(const IsolatedEmoji &emoji) -> Sticker {
|
||||
return Sticker();
|
||||
}
|
||||
|
||||
std::shared_ptr<Image> EmojiPack::image(EmojiPtr emoji) {
|
||||
const auto i = _images.emplace(emoji, std::weak_ptr<Image>()).first;
|
||||
std::shared_ptr<LargeEmojiImage> EmojiPack::image(EmojiPtr emoji) {
|
||||
const auto i = _images.emplace(
|
||||
emoji,
|
||||
std::weak_ptr<LargeEmojiImage>()).first;
|
||||
if (const auto result = i->second.lock()) {
|
||||
return result;
|
||||
}
|
||||
auto result = std::make_shared<Image>(
|
||||
std::make_unique<details::ImageSource>(emoji, &_imageLoader));
|
||||
auto result = std::make_shared<LargeEmojiImage>();
|
||||
const auto raw = result.get();
|
||||
const auto weak = base::make_weak(_session.get());
|
||||
raw->load = [=] {
|
||||
_imageLoader.with([=](details::EmojiImageLoader &loader) mutable {
|
||||
crl::on_main(weak, [
|
||||
=,
|
||||
image = loader.prepare(emoji)
|
||||
]() mutable {
|
||||
const auto i = _images.find(emoji);
|
||||
if (i != end(_images)) {
|
||||
if (const auto strong = i->second.lock()) {
|
||||
if (!strong->image) {
|
||||
strong->load = nullptr;
|
||||
strong->image.emplace(std::move(image));
|
||||
_session->downloaderTaskFinished().notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
raw->load = nullptr;
|
||||
};
|
||||
i->second = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#pragma once
|
||||
|
||||
#include "ui/text/text_isolated_emoji.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "base/timer.h"
|
||||
|
||||
#include <crl/crl_object_on_queue.h>
|
||||
@@ -39,6 +40,13 @@ class EmojiImageLoader;
|
||||
|
||||
using IsolatedEmoji = Ui::Text::IsolatedEmoji;
|
||||
|
||||
struct LargeEmojiImage {
|
||||
std::optional<Image> image;
|
||||
FnMut<void()> load;
|
||||
|
||||
[[nodiscard]] static QSize Size();
|
||||
};
|
||||
|
||||
class EmojiPack final {
|
||||
public:
|
||||
struct Sticker {
|
||||
@@ -60,7 +68,7 @@ public:
|
||||
void remove(not_null<const HistoryItem*> item);
|
||||
|
||||
[[nodiscard]] Sticker stickerForEmoji(const IsolatedEmoji &emoji);
|
||||
[[nodiscard]] std::shared_ptr<Image> image(EmojiPtr emoji);
|
||||
[[nodiscard]] std::shared_ptr<LargeEmojiImage> image(EmojiPtr emoji);
|
||||
|
||||
private:
|
||||
class ImageLoader;
|
||||
@@ -84,7 +92,7 @@ private:
|
||||
base::flat_map<
|
||||
IsolatedEmoji,
|
||||
base::flat_set<not_null<HistoryItem*>>> _items;
|
||||
base::flat_map<EmojiPtr, std::weak_ptr<Image>> _images;
|
||||
base::flat_map<EmojiPtr, std::weak_ptr<LargeEmojiImage>> _images;
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
crl::object_on_queue<details::EmojiImageLoader> _imageLoader;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -32,6 +32,14 @@ class MultiPlayer;
|
||||
class FrameRenderer;
|
||||
} // namespace Lottie
|
||||
|
||||
namespace Data {
|
||||
class DocumentMedia;
|
||||
} // namespace Data
|
||||
|
||||
namespace Stickers {
|
||||
class Set;
|
||||
} // namespace Stickers
|
||||
|
||||
namespace ChatHelpers {
|
||||
|
||||
struct StickerIcon;
|
||||
@@ -63,7 +71,7 @@ public:
|
||||
|
||||
void refreshStickers();
|
||||
|
||||
void fillIcons(QList<StickerIcon> &icons);
|
||||
std::vector<StickerIcon> fillIcons();
|
||||
bool preventAutoHide();
|
||||
|
||||
uint64 currentSet(int yOffset) const;
|
||||
@@ -151,51 +159,49 @@ private:
|
||||
|
||||
struct Sticker {
|
||||
not_null<DocumentData*> document;
|
||||
std::shared_ptr<Data::DocumentMedia> documentMedia;
|
||||
Lottie::Animation *animated = nullptr;
|
||||
QPixmap savedFrame;
|
||||
|
||||
void ensureMediaCreated();
|
||||
};
|
||||
|
||||
struct Set {
|
||||
Set(
|
||||
uint64 id,
|
||||
Stickers::Set *set,
|
||||
MTPDstickerSet::Flags flags,
|
||||
const QString &title,
|
||||
const QString &shortName,
|
||||
ImagePtr thumbnail,
|
||||
bool externalLayout,
|
||||
int count,
|
||||
bool externalLayout,
|
||||
std::vector<Sticker> &&stickers = {});
|
||||
Set(Set &&other);
|
||||
Set &operator=(Set &&other);
|
||||
~Set();
|
||||
|
||||
uint64 id = 0;
|
||||
Stickers::Set *set = nullptr;
|
||||
MTPDstickerSet::Flags flags = MTPDstickerSet::Flags();
|
||||
QString title;
|
||||
QString shortName;
|
||||
ImagePtr thumbnail;
|
||||
std::vector<Sticker> stickers;
|
||||
std::unique_ptr<Ui::RippleAnimation> ripple;
|
||||
Lottie::MultiPlayer *lottiePlayer = nullptr;
|
||||
bool externalLayout = false;
|
||||
|
||||
std::unique_ptr<Lottie::MultiPlayer> lottiePlayer;
|
||||
rpl::lifetime lottieLifetime;
|
||||
|
||||
int count = 0;
|
||||
bool externalLayout = false;
|
||||
};
|
||||
struct FeaturedSet {
|
||||
uint64 id = 0;
|
||||
MTPDstickerSet::Flags flags = MTPDstickerSet::Flags();
|
||||
std::vector<Sticker> stickers;
|
||||
};
|
||||
struct LottieSet {
|
||||
struct Item {
|
||||
not_null<Lottie::Animation*> animation;
|
||||
bool stale = false;
|
||||
};
|
||||
std::unique_ptr<Lottie::MultiPlayer> player;
|
||||
base::flat_map<DocumentId, Item> items;
|
||||
bool stale = false;
|
||||
rpl::lifetime lifetime;
|
||||
};
|
||||
|
||||
static std::vector<Sticker> PrepareStickers(const Stickers::Pack &pack);
|
||||
static std::vector<Sticker> PrepareStickers(
|
||||
const QVector<DocumentData*> &pack);
|
||||
|
||||
void preloadMoreOfficial();
|
||||
QSize boundingBoxSize() const;
|
||||
@@ -260,10 +266,11 @@ private:
|
||||
void markLottieFrameShown(Set &set);
|
||||
void checkVisibleLottie();
|
||||
void pauseInvisibleLottieIn(const SectionInfo &info);
|
||||
void destroyLottieIn(Set &set);
|
||||
void refillLottieData();
|
||||
void refillLottieData(Set &set);
|
||||
void clearLottieData();
|
||||
void takeHeavyData(std::vector<Set> &to, std::vector<Set> &from);
|
||||
void takeHeavyData(Set &to, Set &from);
|
||||
void takeHeavyData(Sticker &to, Sticker &from);
|
||||
void clearHeavyIn(Set &set, bool clearSavedFrames = true);
|
||||
void clearHeavyData();
|
||||
|
||||
int stickersRight() const;
|
||||
bool featuredHasAddButton(int index) const;
|
||||
@@ -301,7 +308,7 @@ private:
|
||||
void refreshSearchRows(const std::vector<uint64> *cloudSets);
|
||||
void fillLocalSearchRows(const QString &query);
|
||||
void fillCloudSearchRows(const std::vector<uint64> &cloudSets);
|
||||
void addSearchRow(not_null<const Stickers::Set*> set);
|
||||
void addSearchRow(not_null<Stickers::Set*> set);
|
||||
|
||||
void showPreview();
|
||||
|
||||
@@ -354,8 +361,6 @@ private:
|
||||
QString _searchQuery, _searchNextQuery;
|
||||
mtpRequestId _searchRequestId = 0;
|
||||
|
||||
base::flat_map<uint64, LottieSet> _lottieData;
|
||||
|
||||
rpl::event_stream<not_null<DocumentData*>> _chosen;
|
||||
rpl::event_stream<> _scrollUpdated;
|
||||
rpl::event_stream<> _checkForHide;
|
||||
|
||||
149
Telegram/SourceFiles/chat_helpers/stickers_set.cpp
Normal file
149
Telegram/SourceFiles/chat_helpers/stickers_set.cpp
Normal file
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
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/stickers_set.h"
|
||||
|
||||
#include "main/main_session.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "storage/file_download.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "app.h"
|
||||
|
||||
namespace Stickers {
|
||||
|
||||
SetThumbnailView::SetThumbnailView(not_null<Set*> owner) : _owner(owner) {
|
||||
}
|
||||
|
||||
not_null<Set*> SetThumbnailView::owner() const {
|
||||
return _owner;
|
||||
}
|
||||
|
||||
void SetThumbnailView::set(
|
||||
not_null<Main::Session*> session,
|
||||
QByteArray content) {
|
||||
auto image = App::readImage(content, nullptr, false);
|
||||
if (image.isNull()) {
|
||||
_content = std::move(content);
|
||||
} else {
|
||||
_image = std::make_unique<Image>(std::move(image));
|
||||
}
|
||||
session->downloaderTaskFinished().notify();
|
||||
}
|
||||
|
||||
Image *SetThumbnailView::image() const {
|
||||
return _image.get();
|
||||
}
|
||||
|
||||
QByteArray SetThumbnailView::content() const {
|
||||
return _content;
|
||||
}
|
||||
|
||||
Set::Set(
|
||||
not_null<Data::Session*> owner,
|
||||
uint64 id,
|
||||
uint64 access,
|
||||
const QString &title,
|
||||
const QString &shortName,
|
||||
int count,
|
||||
int32 hash,
|
||||
MTPDstickerSet::Flags flags,
|
||||
TimeId installDate)
|
||||
: id(id)
|
||||
, access(access)
|
||||
, title(title)
|
||||
, shortName(shortName)
|
||||
, count(count)
|
||||
, hash(hash)
|
||||
, flags(flags)
|
||||
, installDate(installDate)
|
||||
, _owner(owner) {
|
||||
}
|
||||
|
||||
Data::Session &Set::owner() const {
|
||||
return *_owner;
|
||||
}
|
||||
|
||||
Main::Session &Set::session() const {
|
||||
return _owner->session();
|
||||
}
|
||||
|
||||
MTPInputStickerSet Set::mtpInput() const {
|
||||
return (id && access)
|
||||
? MTP_inputStickerSetID(MTP_long(id), MTP_long(access))
|
||||
: MTP_inputStickerSetShortName(MTP_string(shortName));
|
||||
}
|
||||
|
||||
void Set::setThumbnail(const ImageWithLocation &data) {
|
||||
Data::UpdateCloudFile(
|
||||
_thumbnail,
|
||||
data,
|
||||
_owner->cache(),
|
||||
Data::kImageCacheTag,
|
||||
[=](Data::FileOrigin origin) { loadThumbnail(); });
|
||||
if (!data.bytes.isEmpty()) {
|
||||
if (_thumbnail.loader) {
|
||||
_thumbnail.loader->cancel();
|
||||
}
|
||||
if (const auto view = activeThumbnailView()) {
|
||||
view->set(&_owner->session(), data.bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Set::hasThumbnail() const {
|
||||
return _thumbnail.location.valid();
|
||||
}
|
||||
|
||||
bool Set::thumbnailLoading() const {
|
||||
return (_thumbnail.loader != nullptr);
|
||||
}
|
||||
|
||||
bool Set::thumbnailFailed() const {
|
||||
return (_thumbnail.flags & Data::CloudFile::Flag::Failed);
|
||||
}
|
||||
|
||||
void Set::loadThumbnail() {
|
||||
auto &file = _thumbnail;
|
||||
const auto origin = Data::FileOriginStickerSet(id, access);
|
||||
const auto fromCloud = LoadFromCloudOrLocal;
|
||||
const auto cacheTag = Data::kImageCacheTag;
|
||||
const auto autoLoading = false;
|
||||
Data::LoadCloudFile(file, origin, fromCloud, autoLoading, cacheTag, [=] {
|
||||
if (const auto active = activeThumbnailView()) {
|
||||
return !active->image() && active->content().isEmpty();
|
||||
}
|
||||
return true;
|
||||
}, [=](QByteArray result) {
|
||||
if (const auto active = activeThumbnailView()) {
|
||||
active->set(&_owner->session(), std::move(result));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const ImageLocation &Set::thumbnailLocation() const {
|
||||
return _thumbnail.location;
|
||||
}
|
||||
|
||||
int Set::thumbnailByteSize() const {
|
||||
return _thumbnail.byteSize;
|
||||
}
|
||||
|
||||
std::shared_ptr<SetThumbnailView> Set::createThumbnailView() {
|
||||
if (auto active = activeThumbnailView()) {
|
||||
return active;
|
||||
}
|
||||
auto view = std::make_shared<SetThumbnailView>(this);
|
||||
_thumbnailView = view;
|
||||
return view;
|
||||
}
|
||||
|
||||
std::shared_ptr<SetThumbnailView> Set::activeThumbnailView() {
|
||||
return _thumbnailView.lock();
|
||||
}
|
||||
|
||||
} // namespace Stickers
|
||||
100
Telegram/SourceFiles/chat_helpers/stickers_set.h
Normal file
100
Telegram/SourceFiles/chat_helpers/stickers_set.h
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "data/data_cloud_file.h"
|
||||
|
||||
class DocumentData;
|
||||
|
||||
namespace Data {
|
||||
class Session;
|
||||
} // namespace Data
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Stickers {
|
||||
|
||||
using Order = QList<uint64>;
|
||||
using SavedGifs = QVector<DocumentData*>;
|
||||
using Pack = QVector<DocumentData*>;
|
||||
using ByEmojiMap = QMap<EmojiPtr, Pack>;
|
||||
|
||||
class Set;
|
||||
using Sets = base::flat_map<uint64, std::unique_ptr<Set>>;
|
||||
|
||||
class SetThumbnailView final {
|
||||
public:
|
||||
explicit SetThumbnailView(not_null<Set*> owner);
|
||||
|
||||
[[nodiscard]] not_null<Set*> owner() const;
|
||||
|
||||
void set(not_null<Main::Session*> session, QByteArray content);
|
||||
|
||||
[[nodiscard]] Image *image() const;
|
||||
[[nodiscard]] QByteArray content() const;
|
||||
|
||||
private:
|
||||
const not_null<Set*> _owner;
|
||||
std::unique_ptr<Image> _image;
|
||||
QByteArray _content;
|
||||
|
||||
};
|
||||
|
||||
class Set final {
|
||||
public:
|
||||
Set(
|
||||
not_null<Data::Session*> owner,
|
||||
uint64 id,
|
||||
uint64 access,
|
||||
const QString &title,
|
||||
const QString &shortName,
|
||||
int count,
|
||||
int32 hash,
|
||||
MTPDstickerSet::Flags flags,
|
||||
TimeId installDate);
|
||||
|
||||
[[nodiscard]] Data::Session &owner() const;
|
||||
[[nodiscard]] Main::Session &session() const;
|
||||
|
||||
[[nodiscard]] MTPInputStickerSet mtpInput() const;
|
||||
|
||||
void setThumbnail(const ImageWithLocation &data);
|
||||
|
||||
[[nodiscard]] bool hasThumbnail() const;
|
||||
[[nodiscard]] bool thumbnailLoading() const;
|
||||
[[nodiscard]] bool thumbnailFailed() const;
|
||||
void loadThumbnail();
|
||||
[[nodiscard]] const ImageLocation &thumbnailLocation() const;
|
||||
[[nodiscard]] int thumbnailByteSize() const;
|
||||
|
||||
[[nodiscard]] std::shared_ptr<SetThumbnailView> createThumbnailView();
|
||||
[[nodiscard]] std::shared_ptr<SetThumbnailView> activeThumbnailView();
|
||||
|
||||
uint64 id = 0;
|
||||
uint64 access = 0;
|
||||
QString title, shortName;
|
||||
int count = 0;
|
||||
int32 hash = 0;
|
||||
MTPDstickerSet::Flags flags;
|
||||
TimeId installDate = 0;
|
||||
Pack stickers;
|
||||
std::vector<TimeId> dates;
|
||||
Pack covers;
|
||||
ByEmojiMap emoji;
|
||||
|
||||
private:
|
||||
const not_null<Data::Session*> _owner;
|
||||
|
||||
Data::CloudFile _thumbnail;
|
||||
std::weak_ptr<SetThumbnailView> _thumbnailView;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Stickers
|
||||
@@ -66,56 +66,6 @@ inline const char *cGUIDStr() {
|
||||
return gGuidStr;
|
||||
}
|
||||
|
||||
struct BuiltInDc {
|
||||
int id;
|
||||
const char *ip;
|
||||
int port;
|
||||
};
|
||||
|
||||
static const BuiltInDc _builtInDcs[] = {
|
||||
{ 1, "149.154.175.50", 443 },
|
||||
{ 2, "149.154.167.51", 443 },
|
||||
{ 3, "149.154.175.100", 443 },
|
||||
{ 4, "149.154.167.91", 443 },
|
||||
{ 5, "149.154.171.5", 443 }
|
||||
};
|
||||
|
||||
static const BuiltInDc _builtInDcsIPv6[] = {
|
||||
{ 1, "2001:0b28:f23d:f001:0000:0000:0000:000a", 443 },
|
||||
{ 2, "2001:067c:04e8:f002:0000:0000:0000:000a", 443 },
|
||||
{ 3, "2001:0b28:f23d:f003:0000:0000:0000:000a", 443 },
|
||||
{ 4, "2001:067c:04e8:f004:0000:0000:0000:000a", 443 },
|
||||
{ 5, "2001:0b28:f23f:f005:0000:0000:0000:000a", 443 }
|
||||
};
|
||||
|
||||
static const BuiltInDc _builtInTestDcs[] = {
|
||||
{ 1, "149.154.175.10", 443 },
|
||||
{ 2, "149.154.167.40", 443 },
|
||||
{ 3, "149.154.175.117", 443 }
|
||||
};
|
||||
|
||||
static const BuiltInDc _builtInTestDcsIPv6[] = {
|
||||
{ 1, "2001:0b28:f23d:f001:0000:0000:0000:000e", 443 },
|
||||
{ 2, "2001:067c:04e8:f002:0000:0000:0000:000e", 443 },
|
||||
{ 3, "2001:0b28:f23d:f003:0000:0000:0000:000e", 443 }
|
||||
};
|
||||
|
||||
inline const BuiltInDc *builtInDcs() {
|
||||
return cTestMode() ? _builtInTestDcs : _builtInDcs;
|
||||
}
|
||||
|
||||
inline int builtInDcsCount() {
|
||||
return (cTestMode() ? sizeof(_builtInTestDcs) : sizeof(_builtInDcs)) / sizeof(BuiltInDc);
|
||||
}
|
||||
|
||||
inline const BuiltInDc *builtInDcsIPv6() {
|
||||
return cTestMode() ? _builtInTestDcsIPv6 : _builtInDcsIPv6;
|
||||
}
|
||||
|
||||
inline int builtInDcsCountIPv6() {
|
||||
return (cTestMode() ? sizeof(_builtInTestDcsIPv6) : sizeof(_builtInDcsIPv6)) / sizeof(BuiltInDc);
|
||||
}
|
||||
|
||||
static const char *UpdatesPublicKey = "\
|
||||
-----BEGIN RSA PUBLIC KEY-----\n\
|
||||
MIGJAoGBAMA4ViQrjkPZ9xj0lrer3r23JvxOnrtE8nI69XLGSr+sRERz9YnUptnU\n\
|
||||
|
||||
@@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/ui_integration.h"
|
||||
#include "chat_helpers/emoji_keywords.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "mainwindow.h"
|
||||
#include "dialogs/dialogs_entry.h"
|
||||
@@ -174,6 +175,10 @@ void Application::run() {
|
||||
psNewVersion();
|
||||
}
|
||||
|
||||
if (cAutoStart() && !Platform::AutostartSupported()) {
|
||||
cSetAutoStart(false);
|
||||
}
|
||||
|
||||
if (cLaunchMode() == LaunchModeAutoStart && !cAutoStart()) {
|
||||
psAutoStart(false, true);
|
||||
App::quit();
|
||||
@@ -300,7 +305,7 @@ void Application::showDocument(not_null<DocumentData*> document, HistoryItem *it
|
||||
|
||||
if (cUseExternalVideoPlayer()
|
||||
&& document->isVideoFile()
|
||||
&& document->loaded()) {
|
||||
&& !document->filepath().isEmpty()) {
|
||||
File::Launch(document->location(false).fname);
|
||||
} else {
|
||||
_mediaView->showDocument(document, item);
|
||||
@@ -518,6 +523,21 @@ void Application::switchTestMode() {
|
||||
App::restart();
|
||||
}
|
||||
|
||||
void Application::switchFreeType() {
|
||||
if (cUseFreeType()) {
|
||||
QFile(cWorkingDir() + qsl("tdata/withfreetype")).remove();
|
||||
cSetUseFreeType(false);
|
||||
} else {
|
||||
QFile f(cWorkingDir() + qsl("tdata/withfreetype"));
|
||||
if (f.open(QIODevice::WriteOnly)) {
|
||||
f.write("1");
|
||||
f.close();
|
||||
}
|
||||
cSetUseFreeType(true);
|
||||
}
|
||||
App::restart();
|
||||
}
|
||||
|
||||
void Application::writeInstallBetaVersionsSetting() {
|
||||
_launcher->writeInstallBetaVersionsSetting();
|
||||
}
|
||||
@@ -752,6 +772,17 @@ void Application::notifyFileDialogShown(bool shown) {
|
||||
}
|
||||
}
|
||||
|
||||
QWidget *Application::getModalParent() {
|
||||
#ifdef Q_OS_LINUX
|
||||
return Platform::IsWayland()
|
||||
? App::wnd()
|
||||
: nullptr;
|
||||
#endif // Q_OS_LINUX
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
void Application::checkMediaViewActivation() {
|
||||
if (_mediaView && !_mediaView->isHidden()) {
|
||||
_mediaView->activateWindow();
|
||||
|
||||
@@ -101,6 +101,7 @@ public:
|
||||
bool minimizeActiveWindow();
|
||||
QWidget *getFileDialogParent();
|
||||
void notifyFileDialogShown(bool shown);
|
||||
QWidget *getModalParent();
|
||||
|
||||
// Media view interface.
|
||||
void checkMediaViewActivation();
|
||||
@@ -215,6 +216,7 @@ public:
|
||||
|
||||
void switchDebugMode();
|
||||
void switchTestMode();
|
||||
void switchFreeType();
|
||||
void writeInstallBetaVersionsSetting();
|
||||
|
||||
void call_handleUnreadCounterUpdate();
|
||||
|
||||
@@ -18,30 +18,6 @@ namespace {
|
||||
|
||||
std::map<int, const char*> BetaLogs() {
|
||||
return {
|
||||
{
|
||||
1009010,
|
||||
"\xE2\x80\xA2 Switch to the Picture-in-Picture mode "
|
||||
"to watch your video in a small window.\n"
|
||||
|
||||
"\xE2\x80\xA2 Change video playback speed "
|
||||
"in the playback controls '...' menu.\n"
|
||||
|
||||
"\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."
|
||||
},
|
||||
{
|
||||
1009020,
|
||||
"\xE2\x80\xA2 Fix crash in shared links search.\n"
|
||||
@@ -63,6 +39,13 @@ std::map<int, const char*> BetaLogs() {
|
||||
"was added to archive.\n"
|
||||
|
||||
"\xE2\x80\xA2 Fix font issues in Linux version."
|
||||
},
|
||||
{
|
||||
2001008,
|
||||
"\xE2\x80\xA2 Add support for full group message history export.\n"
|
||||
|
||||
"\xE2\x80\xA2 Allow export of a single chat message history "
|
||||
"in JSON format."
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -34,26 +34,47 @@ private:
|
||||
static constexpr auto kForwardArgumentCount = 1;
|
||||
|
||||
int _count = 0;
|
||||
char *_arguments[kForwardArgumentCount + 1] = { nullptr };
|
||||
std::vector<QByteArray> _owned;
|
||||
std::vector<char*> _arguments;
|
||||
|
||||
void pushArgument(const char *text);
|
||||
|
||||
};
|
||||
|
||||
FilteredCommandLineArguments::FilteredCommandLineArguments(
|
||||
int argc,
|
||||
char **argv)
|
||||
: _count(std::clamp(argc, 0, kForwardArgumentCount)) {
|
||||
char **argv) {
|
||||
// For now just pass only the first argument, the executable path.
|
||||
for (auto i = 0; i != _count; ++i) {
|
||||
_arguments[i] = argv[i];
|
||||
for (auto i = 0; i != kForwardArgumentCount; ++i) {
|
||||
pushArgument(argv[i]);
|
||||
}
|
||||
|
||||
#if defined Q_OS_WIN || defined Q_OS_MAC
|
||||
if (cUseFreeType()) {
|
||||
pushArgument("-platform");
|
||||
#ifdef Q_OS_WIN
|
||||
pushArgument("windows:fontengine=freetype");
|
||||
#else // Q_OS_WIN
|
||||
pushArgument("cocoa:fontengine=freetype");
|
||||
#endif // !Q_OS_WIN
|
||||
}
|
||||
#endif // Q_OS_WIN || Q_OS_MAC
|
||||
|
||||
pushArgument(nullptr);
|
||||
}
|
||||
|
||||
int &FilteredCommandLineArguments::count() {
|
||||
_count = _arguments.size() - 1;
|
||||
return _count;
|
||||
}
|
||||
|
||||
char **FilteredCommandLineArguments::values() {
|
||||
return _arguments;
|
||||
return _arguments.data();
|
||||
}
|
||||
|
||||
void FilteredCommandLineArguments::pushArgument(const char *text) {
|
||||
_owned.emplace_back(text);
|
||||
_arguments.push_back(_owned.back().data());
|
||||
}
|
||||
|
||||
QString DebugModeSettingPath() {
|
||||
@@ -77,11 +98,33 @@ void ComputeDebugMode() {
|
||||
}
|
||||
|
||||
void ComputeTestMode() {
|
||||
if (QFile(cWorkingDir() + qsl("tdata/withtestmode")).exists()) {
|
||||
if (QFile::exists(cWorkingDir() + qsl("tdata/withtestmode"))) {
|
||||
cSetTestMode(true);
|
||||
}
|
||||
}
|
||||
|
||||
void ComputeExternalUpdater() {
|
||||
QFile file(qsl("/etc/tdesktop/externalupdater"));
|
||||
|
||||
if (file.exists() && file.open(QIODevice::ReadOnly)) {
|
||||
QTextStream fileStream(&file);
|
||||
while (!fileStream.atEnd()) {
|
||||
const auto path = fileStream.readLine();
|
||||
|
||||
if (path == (cWorkingDir() + cExeName())) {
|
||||
SetUpdaterDisabledAtStartup();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ComputeFreeType() {
|
||||
if (QFile::exists(cWorkingDir() + qsl("tdata/withfreetype"))) {
|
||||
cSetUseFreeType(true);
|
||||
}
|
||||
}
|
||||
|
||||
QString InstallBetaVersionsSettingPath() {
|
||||
return cWorkingDir() + qsl("tdata/devversion");
|
||||
}
|
||||
@@ -97,7 +140,7 @@ void ComputeInstallBetaVersions() {
|
||||
const auto installBetaSettingPath = InstallBetaVersionsSettingPath();
|
||||
if (cAlphaVersion()) {
|
||||
cSetInstallBetaVersion(false);
|
||||
} else if (QFile(installBetaSettingPath).exists()) {
|
||||
} else if (QFile::exists(installBetaSettingPath)) {
|
||||
QFile f(installBetaSettingPath);
|
||||
if (f.open(QIODevice::ReadOnly)) {
|
||||
cSetInstallBetaVersion(f.read(1) != "0");
|
||||
@@ -141,7 +184,7 @@ bool MoveLegacyAlphaFolder(const QString &folder, const QString &file) {
|
||||
if (QDir(was).exists() && !QDir(now).exists()) {
|
||||
const auto oldFile = was + "/tdata/" + file;
|
||||
const auto newFile = was + "/tdata/alpha";
|
||||
if (QFile(oldFile).exists() && !QFile(newFile).exists()) {
|
||||
if (QFile::exists(oldFile) && !QFile::exists(newFile)) {
|
||||
if (!QFile(oldFile).copy(newFile)) {
|
||||
LOG(("FATAL: Could not copy '%1' to '%2'"
|
||||
).arg(oldFile
|
||||
@@ -256,6 +299,14 @@ void Launcher::init() {
|
||||
QApplication::setAttribute(Qt::AA_DisableHighDpiScaling, true);
|
||||
#endif // OS_MAC_OLD
|
||||
|
||||
// fallback session management is useless for tdesktop since it doesn't have
|
||||
// any "are you sure you want to close this window?" dialogs
|
||||
// but it produces bugs like https://github.com/telegramdesktop/tdesktop/issues/5022
|
||||
// and https://github.com/telegramdesktop/tdesktop/issues/7549
|
||||
// and https://github.com/telegramdesktop/tdesktop/issues/948
|
||||
// more info: https://doc.qt.io/qt-5/qguiapplication.html#isFallbackSessionManagementEnabled
|
||||
QApplication::setFallbackSessionManagementEnabled(false);
|
||||
|
||||
initHook();
|
||||
}
|
||||
|
||||
@@ -301,6 +352,8 @@ void Launcher::workingFolderReady() {
|
||||
|
||||
ComputeTestMode();
|
||||
ComputeDebugMode();
|
||||
ComputeExternalUpdater();
|
||||
ComputeFreeType();
|
||||
ComputeInstallBetaVersions();
|
||||
ComputeInstallationTag();
|
||||
}
|
||||
@@ -382,6 +435,7 @@ void Launcher::processArguments() {
|
||||
auto parseMap = std::map<QByteArray, KeyFormat> {
|
||||
{ "-testmode" , KeyFormat::NoValues },
|
||||
{ "-debug" , KeyFormat::NoValues },
|
||||
{ "-freetype" , KeyFormat::NoValues },
|
||||
{ "-many" , KeyFormat::NoValues },
|
||||
{ "-key" , KeyFormat::OneValue },
|
||||
{ "-autostart" , KeyFormat::NoValues },
|
||||
@@ -423,6 +477,7 @@ void Launcher::processArguments() {
|
||||
SetUpdaterDisabledAtStartup();
|
||||
}
|
||||
gTestMode = parseResult.contains("-testmode");
|
||||
gUseFreeType = parseResult.contains("-freetype");
|
||||
Logs::SetDebugEnabled(parseResult.contains("-debug"));
|
||||
gManyInstance = parseResult.contains("-many");
|
||||
gKeyFile = parseResult.value("-key", {}).join(QString()).toLower();
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
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/last_used_cache.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
template <typename Type>
|
||||
class MediaActiveCache {
|
||||
public:
|
||||
template <typename Unload>
|
||||
MediaActiveCache(int64 limit, Unload &&unload);
|
||||
|
||||
void up(Type *entry);
|
||||
void remove(Type *entry);
|
||||
void clear();
|
||||
|
||||
void increment(int64 amount);
|
||||
void decrement(int64 amount);
|
||||
|
||||
private:
|
||||
template <typename Unload>
|
||||
void check(Unload &&unload);
|
||||
|
||||
base::last_used_cache<Type*> _cache;
|
||||
SingleQueuedInvokation _delayed;
|
||||
int64 _usage = 0;
|
||||
int64 _limit = 0;
|
||||
|
||||
};
|
||||
|
||||
template <typename Type>
|
||||
template <typename Unload>
|
||||
MediaActiveCache<Type>::MediaActiveCache(int64 limit, Unload &&unload)
|
||||
: _delayed([=] { check(unload); })
|
||||
, _limit(limit) {
|
||||
}
|
||||
|
||||
template <typename Type>
|
||||
void MediaActiveCache<Type>::up(Type *entry) {
|
||||
_cache.up(entry);
|
||||
_delayed.call();
|
||||
}
|
||||
|
||||
template <typename Type>
|
||||
void MediaActiveCache<Type>::remove(Type *entry) {
|
||||
_cache.remove(entry);
|
||||
}
|
||||
|
||||
template <typename Type>
|
||||
void MediaActiveCache<Type>::clear() {
|
||||
_cache.clear();
|
||||
}
|
||||
|
||||
template <typename Type>
|
||||
void MediaActiveCache<Type>::increment(int64 amount) {
|
||||
_usage += amount;
|
||||
}
|
||||
|
||||
template <typename Type>
|
||||
void MediaActiveCache<Type>::decrement(int64 amount) {
|
||||
_usage -= amount;
|
||||
}
|
||||
|
||||
template <typename Type>
|
||||
template <typename Unload>
|
||||
void MediaActiveCache<Type>::check(Unload &&unload) {
|
||||
while (_usage > _limit) {
|
||||
if (const auto entry = _cache.take_lowest()) {
|
||||
unload(entry);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
@@ -373,11 +373,8 @@ void Sandbox::readClients() {
|
||||
toSend.append(_escapeFrom7bit(cmds.mid(from + 5, to - from - 5)));
|
||||
}
|
||||
} else if (cmd.startsWith(qsl("OPEN:"))) {
|
||||
auto activateRequired = true;
|
||||
if (cStartUrl().isEmpty()) {
|
||||
startUrl = _escapeFrom7bit(cmds.mid(from + 5, to - from - 5)).mid(0, 8192);
|
||||
activateRequired = StartUrlRequiresActivate(startUrl);
|
||||
}
|
||||
startUrl = _escapeFrom7bit(cmds.mid(from + 5, to - from - 5)).mid(0, 8192);
|
||||
auto activateRequired = StartUrlRequiresActivate(startUrl);
|
||||
if (activateRequired) {
|
||||
execExternal("show");
|
||||
}
|
||||
|
||||
@@ -33,11 +33,11 @@ extern "C" {
|
||||
#include <openssl/err.h>
|
||||
} // extern "C"
|
||||
|
||||
#ifdef Q_OS_WIN // use Lzma SDK for win
|
||||
#if defined Q_OS_WIN && !defined DESKTOP_APP_USE_PACKAGED // use Lzma SDK for win
|
||||
#include <LzmaLib.h>
|
||||
#else // Q_OS_WIN
|
||||
#else // Q_OS_WIN && !DESKTOP_APP_USE_PACKAGED
|
||||
#include <lzma.h>
|
||||
#endif // else of Q_OS_WIN
|
||||
#endif // else of Q_OS_WIN && !DESKTOP_APP_USE_PACKAGED
|
||||
|
||||
namespace Core {
|
||||
namespace {
|
||||
@@ -252,11 +252,11 @@ bool UnpackUpdate(const QString &filepath) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN // use Lzma SDK for win
|
||||
#if defined Q_OS_WIN && !defined DESKTOP_APP_USE_PACKAGED // use Lzma SDK for win
|
||||
const int32 hSigLen = 128, hShaLen = 20, hPropsLen = LZMA_PROPS_SIZE, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hPropsLen + hOriginalSizeLen; // header
|
||||
#else // Q_OS_WIN
|
||||
#else // Q_OS_WIN && !DESKTOP_APP_USE_PACKAGED
|
||||
const int32 hSigLen = 128, hShaLen = 20, hPropsLen = 0, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hOriginalSizeLen; // header
|
||||
#endif // Q_OS_WIN
|
||||
#endif // Q_OS_WIN && !DESKTOP_APP_USE_PACKAGED
|
||||
|
||||
QByteArray compressed = input.readAll();
|
||||
int32 compressedLen = compressed.size() - hSize;
|
||||
@@ -311,14 +311,14 @@ bool UnpackUpdate(const QString &filepath) {
|
||||
uncompressed.resize(uncompressedLen);
|
||||
|
||||
size_t resultLen = uncompressed.size();
|
||||
#ifdef Q_OS_WIN // use Lzma SDK for win
|
||||
#if defined Q_OS_WIN && !defined DESKTOP_APP_USE_PACKAGED // use Lzma SDK for win
|
||||
SizeT srcLen = compressedLen;
|
||||
int uncompressRes = LzmaUncompress((uchar*)uncompressed.data(), &resultLen, (const uchar*)(compressed.constData() + hSize), &srcLen, (const uchar*)(compressed.constData() + hSigLen + hShaLen), LZMA_PROPS_SIZE);
|
||||
if (uncompressRes != SZ_OK) {
|
||||
LOG(("Update Error: could not uncompress lzma, code: %1").arg(uncompressRes));
|
||||
return false;
|
||||
}
|
||||
#else // Q_OS_WIN
|
||||
#else // Q_OS_WIN && !DESKTOP_APP_USE_PACKAGED
|
||||
lzma_stream stream = LZMA_STREAM_INIT;
|
||||
|
||||
lzma_ret ret = lzma_stream_decoder(&stream, UINT64_MAX, LZMA_CONCATENATED);
|
||||
@@ -361,7 +361,7 @@ bool UnpackUpdate(const QString &filepath) {
|
||||
LOG(("Error in decompression: %1 (error code %2)").arg(msg).arg(res));
|
||||
return false;
|
||||
}
|
||||
#endif // Q_OS_WIN
|
||||
#endif // Q_OS_WIN && !DESKTOP_APP_USE_PACKAGED
|
||||
|
||||
tempDir.mkdir(tempDir.absolutePath());
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
|
||||
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
|
||||
constexpr auto AppName = "Telegram Desktop"_cs;
|
||||
constexpr auto AppFile = "Telegram"_cs;
|
||||
constexpr auto AppVersion = 2001004;
|
||||
constexpr auto AppVersionStr = "2.1.4";
|
||||
constexpr auto AppVersion = 2001010;
|
||||
constexpr auto AppVersionStr = "2.1.10";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -10,8 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_document.h"
|
||||
#include "ui/image/image_source.h"
|
||||
#include "ui/image/image.h"
|
||||
|
||||
#include <QtCore/QBuffer>
|
||||
|
||||
@@ -261,7 +259,7 @@ bool Should(
|
||||
const Full &data,
|
||||
Source source,
|
||||
not_null<DocumentData*> document) {
|
||||
if (document->sticker()) {
|
||||
if (document->sticker() || document->isGifv()) {
|
||||
return true;
|
||||
} else if (document->isVoiceMessage()
|
||||
|| document->isVideoMessage()
|
||||
@@ -294,11 +292,11 @@ bool Should(
|
||||
bool Should(
|
||||
const Full &data,
|
||||
not_null<PeerData*> peer,
|
||||
not_null<Images::Source*> image) {
|
||||
not_null<PhotoData*> photo) {
|
||||
return data.shouldDownload(
|
||||
SourceFromPeer(peer),
|
||||
Type::Photo,
|
||||
image->bytesSize());
|
||||
photo->imageByteSize(PhotoSize::Large));
|
||||
}
|
||||
|
||||
bool ShouldAutoPlay(
|
||||
|
||||
@@ -9,10 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace Images {
|
||||
class Source;
|
||||
} // namespace Images
|
||||
|
||||
namespace Data {
|
||||
namespace AutoDownload {
|
||||
|
||||
@@ -118,7 +114,7 @@ private:
|
||||
[[nodiscard]] bool Should(
|
||||
const Full &data,
|
||||
not_null<PeerData*> peer,
|
||||
not_null<Images::Source*> image);
|
||||
not_null<PhotoData*> photo);
|
||||
|
||||
[[nodiscard]] bool ShouldAutoPlay(
|
||||
const Full &data,
|
||||
|
||||
@@ -175,7 +175,6 @@ public:
|
||||
std::deque<not_null<UserData*>> lastAuthors;
|
||||
base::flat_set<not_null<PeerData*>> markupSenders;
|
||||
int botStatus = 0; // -1 - no bots, 0 - unknown, 1 - one bot, that sees all history, 2 - other
|
||||
// ImagePtr photoFull;
|
||||
|
||||
private:
|
||||
Flags _flags;
|
||||
|
||||
322
Telegram/SourceFiles/data/data_cloud_file.cpp
Normal file
322
Telegram/SourceFiles/data/data_cloud_file.cpp
Normal file
@@ -0,0 +1,322 @@
|
||||
/*
|
||||
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_cloud_file.h"
|
||||
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_session.h"
|
||||
#include "storage/cache/storage_cache_database.h"
|
||||
#include "storage/file_download.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
namespace Data {
|
||||
|
||||
CloudFile::~CloudFile() {
|
||||
// Destroy loader with still alive CloudFile with already zero '.loader'.
|
||||
// Otherwise in ~FileLoader it tries to clear file.loader and crashes.
|
||||
base::take(loader);
|
||||
}
|
||||
|
||||
void CloudImageView::set(
|
||||
not_null<Main::Session*> session,
|
||||
QImage image) {
|
||||
_image.emplace(std::move(image));
|
||||
session->downloaderTaskFinished().notify();
|
||||
}
|
||||
|
||||
CloudImage::CloudImage() = default;
|
||||
|
||||
CloudImage::CloudImage(
|
||||
not_null<Main::Session*> session,
|
||||
const ImageWithLocation &data) {
|
||||
update(session, data);
|
||||
}
|
||||
|
||||
Image *CloudImageView::image() {
|
||||
return _image ? &*_image : nullptr;
|
||||
}
|
||||
|
||||
void CloudImage::set(
|
||||
not_null<Main::Session*> session,
|
||||
const ImageWithLocation &data) {
|
||||
const auto &was = _file.location.file().data;
|
||||
const auto &now = data.location.file().data;
|
||||
if (!data.location.valid()) {
|
||||
_file.flags |= CloudFile::Flag::Cancelled;
|
||||
_file.loader = nullptr;
|
||||
_file.location = ImageLocation();
|
||||
_file.byteSize = 0;
|
||||
_file.flags = CloudFile::Flag();
|
||||
_view = std::weak_ptr<CloudImageView>();
|
||||
} else if (was != now
|
||||
&& (!was.is<InMemoryLocation>() || now.is<InMemoryLocation>())) {
|
||||
_file.location = ImageLocation();
|
||||
_view = std::weak_ptr<CloudImageView>();
|
||||
}
|
||||
UpdateCloudFile(
|
||||
_file,
|
||||
data,
|
||||
session->data().cache(),
|
||||
kImageCacheTag,
|
||||
[=](FileOrigin origin) { load(session, origin); },
|
||||
[=](QImage preloaded) {
|
||||
if (const auto view = activeView()) {
|
||||
view->set(session, data.preloaded);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void CloudImage::update(
|
||||
not_null<Main::Session*> session,
|
||||
const ImageWithLocation &data) {
|
||||
UpdateCloudFile(
|
||||
_file,
|
||||
data,
|
||||
session->data().cache(),
|
||||
kImageCacheTag,
|
||||
[=](FileOrigin origin) { load(session, origin); },
|
||||
[=](QImage preloaded) {
|
||||
if (const auto view = activeView()) {
|
||||
view->set(session, data.preloaded);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool CloudImage::empty() const {
|
||||
return !_file.location.valid();
|
||||
}
|
||||
|
||||
bool CloudImage::loading() const {
|
||||
return (_file.loader != nullptr);
|
||||
}
|
||||
|
||||
bool CloudImage::failed() const {
|
||||
return (_file.flags & CloudFile::Flag::Failed);
|
||||
}
|
||||
|
||||
void CloudImage::load(not_null<Main::Session*> session, FileOrigin origin) {
|
||||
const auto fromCloud = LoadFromCloudOrLocal;
|
||||
const auto cacheTag = kImageCacheTag;
|
||||
const auto autoLoading = false;
|
||||
LoadCloudFile(_file, origin, fromCloud, autoLoading, cacheTag, [=] {
|
||||
if (const auto active = activeView()) {
|
||||
return !active->image();
|
||||
}
|
||||
return true;
|
||||
}, [=](QImage result) {
|
||||
if (const auto active = activeView()) {
|
||||
active->set(session, std::move(result));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const ImageLocation &CloudImage::location() const {
|
||||
return _file.location;
|
||||
}
|
||||
|
||||
int CloudImage::byteSize() const {
|
||||
return _file.byteSize;
|
||||
}
|
||||
|
||||
std::shared_ptr<CloudImageView> CloudImage::createView() {
|
||||
if (auto active = activeView()) {
|
||||
return active;
|
||||
}
|
||||
auto view = std::make_shared<CloudImageView>();
|
||||
_view = view;
|
||||
return view;
|
||||
}
|
||||
|
||||
std::shared_ptr<CloudImageView> CloudImage::activeView() {
|
||||
return _view.lock();
|
||||
}
|
||||
|
||||
bool CloudImage::isCurrentView(
|
||||
const std::shared_ptr<CloudImageView> &view) const {
|
||||
if (!view) {
|
||||
return empty();
|
||||
}
|
||||
return !view.owner_before(_view) && !_view.owner_before(view);
|
||||
}
|
||||
|
||||
void UpdateCloudFile(
|
||||
CloudFile &file,
|
||||
const ImageWithLocation &data,
|
||||
Storage::Cache::Database &cache,
|
||||
uint8 cacheTag,
|
||||
Fn<void(FileOrigin)> restartLoader,
|
||||
Fn<void(QImage)> usePreloaded) {
|
||||
if (!data.location.valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto update = !file.location.valid()
|
||||
|| (data.location.file().cacheKey()
|
||||
&& (!file.location.file().cacheKey()
|
||||
|| (file.location.width() < data.location.width())
|
||||
|| (file.location.height() < data.location.height())));
|
||||
if (!update) {
|
||||
return;
|
||||
}
|
||||
auto cacheBytes = !data.bytes.isEmpty()
|
||||
? data.bytes
|
||||
: file.location.file().data.is<InMemoryLocation>()
|
||||
? file.location.file().data.get_unchecked<InMemoryLocation>().bytes
|
||||
: QByteArray();
|
||||
if (!cacheBytes.isEmpty()) {
|
||||
if (const auto cacheKey = data.location.file().cacheKey()) {
|
||||
cache.putIfEmpty(
|
||||
cacheKey,
|
||||
Storage::Cache::Database::TaggedValue(
|
||||
std::move(cacheBytes),
|
||||
cacheTag));
|
||||
}
|
||||
}
|
||||
file.location = data.location;
|
||||
file.byteSize = data.bytesCount;
|
||||
if (!data.preloaded.isNull()) {
|
||||
file.loader = nullptr;
|
||||
if (usePreloaded) {
|
||||
usePreloaded(data.preloaded);
|
||||
}
|
||||
} else if (file.loader) {
|
||||
const auto origin = base::take(file.loader)->fileOrigin();
|
||||
restartLoader(origin);
|
||||
}
|
||||
}
|
||||
|
||||
void LoadCloudFile(
|
||||
CloudFile &file,
|
||||
FileOrigin origin,
|
||||
LoadFromCloudSetting fromCloud,
|
||||
bool autoLoading,
|
||||
uint8 cacheTag,
|
||||
Fn<bool()> finalCheck,
|
||||
Fn<void(CloudFile&)> done,
|
||||
Fn<void(bool)> fail,
|
||||
Fn<void()> progress) {
|
||||
if (file.loader) {
|
||||
if (fromCloud == LoadFromCloudOrLocal) {
|
||||
file.loader->permitLoadFromCloud();
|
||||
}
|
||||
return;
|
||||
} else if ((file.flags & CloudFile::Flag::Failed)
|
||||
|| !file.location.valid()
|
||||
|| (finalCheck && !finalCheck())) {
|
||||
return;
|
||||
}
|
||||
file.flags &= ~CloudFile::Flag::Cancelled;
|
||||
file.loader = CreateFileLoader(
|
||||
file.location.file(),
|
||||
origin,
|
||||
QString(),
|
||||
file.byteSize,
|
||||
UnknownFileLocation,
|
||||
LoadToCacheAsWell,
|
||||
fromCloud,
|
||||
autoLoading,
|
||||
cacheTag);
|
||||
|
||||
const auto finish = [done](CloudFile &file) {
|
||||
if (!file.loader || file.loader->cancelled()) {
|
||||
file.flags |= CloudFile::Flag::Cancelled;
|
||||
} else {
|
||||
done(file);
|
||||
}
|
||||
// NB! file.loader may be in ~FileLoader() already.
|
||||
if (const auto loader = base::take(file.loader)) {
|
||||
if ((file.flags & CloudFile::Flag::Cancelled)
|
||||
&& !loader->cancelled()) {
|
||||
loader->cancel();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
file.loader->updates(
|
||||
) | rpl::start_with_next_error_done([=] {
|
||||
if (const auto onstack = progress) {
|
||||
onstack();
|
||||
}
|
||||
}, [=, &file](bool started) {
|
||||
finish(file);
|
||||
file.flags |= CloudFile::Flag::Failed;
|
||||
if (const auto onstack = fail) {
|
||||
onstack(started);
|
||||
}
|
||||
}, [=, &file] {
|
||||
finish(file);
|
||||
}, file.loader->lifetime());
|
||||
|
||||
file.loader->start();
|
||||
}
|
||||
|
||||
void LoadCloudFile(
|
||||
CloudFile &file,
|
||||
FileOrigin origin,
|
||||
LoadFromCloudSetting fromCloud,
|
||||
bool autoLoading,
|
||||
uint8 cacheTag,
|
||||
Fn<bool()> finalCheck,
|
||||
Fn<void(QImage)> done,
|
||||
Fn<void(bool)> fail,
|
||||
Fn<void()> progress) {
|
||||
const auto callback = [=](CloudFile &file) {
|
||||
if (auto read = file.loader->imageData(); read.isNull()) {
|
||||
file.flags |= CloudFile::Flag::Failed;
|
||||
if (const auto onstack = fail) {
|
||||
onstack(true);
|
||||
}
|
||||
} else if (const auto onstack = done) {
|
||||
onstack(std::move(read));
|
||||
}
|
||||
};
|
||||
LoadCloudFile(
|
||||
file,
|
||||
origin,
|
||||
fromCloud,
|
||||
autoLoading,
|
||||
cacheTag,
|
||||
finalCheck,
|
||||
callback,
|
||||
std::move(fail),
|
||||
std::move(progress));
|
||||
}
|
||||
|
||||
void LoadCloudFile(
|
||||
CloudFile &file,
|
||||
FileOrigin origin,
|
||||
LoadFromCloudSetting fromCloud,
|
||||
bool autoLoading,
|
||||
uint8 cacheTag,
|
||||
Fn<bool()> finalCheck,
|
||||
Fn<void(QByteArray)> done,
|
||||
Fn<void(bool)> fail,
|
||||
Fn<void()> progress) {
|
||||
const auto callback = [=](CloudFile &file) {
|
||||
if (auto bytes = file.loader->bytes(); bytes.isEmpty()) {
|
||||
file.flags |= CloudFile::Flag::Failed;
|
||||
if (const auto onstack = fail) {
|
||||
onstack(true);
|
||||
}
|
||||
} else if (const auto onstack = done) {
|
||||
onstack(std::move(bytes));
|
||||
}
|
||||
};
|
||||
LoadCloudFile(
|
||||
file,
|
||||
origin,
|
||||
fromCloud,
|
||||
autoLoading,
|
||||
cacheTag,
|
||||
finalCheck,
|
||||
callback,
|
||||
std::move(fail),
|
||||
std::move(progress));
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
120
Telegram/SourceFiles/data/data_cloud_file.h
Normal file
120
Telegram/SourceFiles/data/data_cloud_file.h
Normal file
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
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/flags.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/image/image_location.h"
|
||||
|
||||
class FileLoader;
|
||||
|
||||
namespace Storage {
|
||||
namespace Cache {
|
||||
class Database;
|
||||
} // namespace Cache
|
||||
} // namespace Storage
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Data {
|
||||
|
||||
struct FileOrigin;
|
||||
|
||||
struct CloudFile final {
|
||||
enum class Flag : uchar {
|
||||
Cancelled = 0x01,
|
||||
Failed = 0x02,
|
||||
};
|
||||
friend inline constexpr bool is_flag_type(Flag) { return true; };
|
||||
|
||||
~CloudFile();
|
||||
|
||||
ImageLocation location;
|
||||
std::unique_ptr<FileLoader> loader;
|
||||
int byteSize = 0;
|
||||
base::flags<Flag> flags;
|
||||
};
|
||||
|
||||
class CloudImageView final {
|
||||
public:
|
||||
void set(not_null<Main::Session*> session, QImage image);
|
||||
|
||||
[[nodiscard]] Image *image();
|
||||
|
||||
private:
|
||||
std::optional<Image> _image;
|
||||
|
||||
};
|
||||
|
||||
class CloudImage final {
|
||||
public:
|
||||
CloudImage();
|
||||
CloudImage(
|
||||
not_null<Main::Session*> session,
|
||||
const ImageWithLocation &data);
|
||||
|
||||
// This method will replace the location and zero the _view pointer.
|
||||
void set(
|
||||
not_null<Main::Session*> session,
|
||||
const ImageWithLocation &data);
|
||||
|
||||
void update(
|
||||
not_null<Main::Session*> session,
|
||||
const ImageWithLocation &data);
|
||||
|
||||
[[nodiscard]] bool empty() const;
|
||||
[[nodiscard]] bool loading() const;
|
||||
[[nodiscard]] bool failed() const;
|
||||
void load(not_null<Main::Session*> session, FileOrigin origin);
|
||||
[[nodiscard]] const ImageLocation &location() const;
|
||||
[[nodiscard]] int byteSize() const;
|
||||
|
||||
[[nodiscard]] std::shared_ptr<CloudImageView> createView();
|
||||
[[nodiscard]] std::shared_ptr<CloudImageView> activeView();
|
||||
[[nodiscard]] bool isCurrentView(
|
||||
const std::shared_ptr<CloudImageView> &view) const;
|
||||
|
||||
private:
|
||||
CloudFile _file;
|
||||
std::weak_ptr<CloudImageView> _view;
|
||||
|
||||
};
|
||||
|
||||
void UpdateCloudFile(
|
||||
CloudFile &file,
|
||||
const ImageWithLocation &data,
|
||||
Storage::Cache::Database &cache,
|
||||
uint8 cacheTag,
|
||||
Fn<void(FileOrigin)> restartLoader,
|
||||
Fn<void(QImage)> usePreloaded = nullptr);
|
||||
|
||||
void LoadCloudFile(
|
||||
CloudFile &file,
|
||||
FileOrigin origin,
|
||||
LoadFromCloudSetting fromCloud,
|
||||
bool autoLoading,
|
||||
uint8 cacheTag,
|
||||
Fn<bool()> finalCheck,
|
||||
Fn<void(QImage)> done,
|
||||
Fn<void(bool)> fail = nullptr,
|
||||
Fn<void()> progress = nullptr);
|
||||
|
||||
void LoadCloudFile(
|
||||
CloudFile &file,
|
||||
FileOrigin origin,
|
||||
LoadFromCloudSetting fromCloud,
|
||||
bool autoLoading,
|
||||
uint8 cacheTag,
|
||||
Fn<bool()> finalCheck,
|
||||
Fn<void(QByteArray)> done,
|
||||
Fn<void(bool)> fail = nullptr,
|
||||
Fn<void()> progress = nullptr);
|
||||
|
||||
} // namespace Data
|
||||
@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "main/main_session.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "core/application.h" // Core::App().showTheme.
|
||||
@@ -178,9 +179,11 @@ void CloudThemes::showPreview(const CloudTheme &cloud) {
|
||||
|
||||
void CloudThemes::applyFromDocument(const CloudTheme &cloud) {
|
||||
const auto document = _session->data().document(cloud.documentId);
|
||||
loadDocumentAndInvoke(_updatingFrom, cloud, document, [=] {
|
||||
loadDocumentAndInvoke(_updatingFrom, cloud, document, [=](
|
||||
std::shared_ptr<Data::DocumentMedia> media) {
|
||||
const auto document = media->owner();
|
||||
auto preview = Window::Theme::PreviewFromFile(
|
||||
document->data(),
|
||||
media->bytes(),
|
||||
document->location().name(),
|
||||
cloud);
|
||||
if (preview) {
|
||||
@@ -192,7 +195,9 @@ void CloudThemes::applyFromDocument(const CloudTheme &cloud) {
|
||||
|
||||
void CloudThemes::previewFromDocument(const CloudTheme &cloud) {
|
||||
const auto document = _session->data().document(cloud.documentId);
|
||||
loadDocumentAndInvoke(_previewFrom, cloud, document, [=] {
|
||||
loadDocumentAndInvoke(_previewFrom, cloud, document, [=](
|
||||
std::shared_ptr<Data::DocumentMedia> media) {
|
||||
const auto document = media->owner();
|
||||
Core::App().showTheme(document, cloud);
|
||||
});
|
||||
}
|
||||
@@ -201,25 +206,26 @@ void CloudThemes::loadDocumentAndInvoke(
|
||||
LoadingDocument &value,
|
||||
const CloudTheme &cloud,
|
||||
not_null<DocumentData*> document,
|
||||
Fn<void()> callback) {
|
||||
Fn<void(std::shared_ptr<Data::DocumentMedia>)> callback) {
|
||||
const auto alreadyWaiting = (value.document != nullptr);
|
||||
if (alreadyWaiting) {
|
||||
value.document->cancel();
|
||||
}
|
||||
value.document = document;
|
||||
value.documentMedia = document->createMediaView();
|
||||
value.document->save(
|
||||
Data::FileOriginTheme(cloud.id, cloud.accessHash),
|
||||
QString());
|
||||
value.callback = std::move(callback);
|
||||
if (document->loaded()) {
|
||||
if (value.documentMedia->loaded()) {
|
||||
invokeForLoaded(value);
|
||||
return;
|
||||
}
|
||||
if (!alreadyWaiting) {
|
||||
base::ObservableViewer(
|
||||
_session->downloaderTaskFinished()
|
||||
) | rpl::filter([=] {
|
||||
return document->loaded();
|
||||
) | rpl::filter([=, &value] {
|
||||
return value.documentMedia->loaded();
|
||||
}) | rpl::start_with_next([=, &value] {
|
||||
invokeForLoaded(value);
|
||||
}, value.subscription);
|
||||
@@ -228,8 +234,9 @@ void CloudThemes::loadDocumentAndInvoke(
|
||||
|
||||
void CloudThemes::invokeForLoaded(LoadingDocument &value) {
|
||||
const auto onstack = std::move(value.callback);
|
||||
auto media = std::move(value.documentMedia);
|
||||
value = LoadingDocument();
|
||||
onstack();
|
||||
onstack(std::move(media));
|
||||
}
|
||||
|
||||
void CloudThemes::scheduleReload() {
|
||||
|
||||
@@ -17,6 +17,8 @@ class Session;
|
||||
|
||||
namespace Data {
|
||||
|
||||
class DocumentMedia;
|
||||
|
||||
struct CloudTheme {
|
||||
uint64 id = 0;
|
||||
uint64 accessHash = 0;
|
||||
@@ -54,8 +56,9 @@ private:
|
||||
struct LoadingDocument {
|
||||
CloudTheme theme;
|
||||
DocumentData *document = nullptr;
|
||||
std::shared_ptr<Data::DocumentMedia> documentMedia;
|
||||
rpl::lifetime subscription;
|
||||
Fn<void()> callback;
|
||||
Fn<void(std::shared_ptr<Data::DocumentMedia>)> callback;
|
||||
};
|
||||
|
||||
void parseThemes(const QVector<MTPTheme> &list);
|
||||
@@ -71,7 +74,7 @@ private:
|
||||
LoadingDocument &value,
|
||||
const CloudTheme &cloud,
|
||||
not_null<DocumentData*> document,
|
||||
Fn<void()> callback);
|
||||
Fn<void(std::shared_ptr<Data::DocumentMedia>)> callback);
|
||||
void invokeForLoaded(LoadingDocument &value);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/flags.h"
|
||||
#include "base/binary_guard.h"
|
||||
#include "data/data_types.h"
|
||||
#include "data/data_cloud_file.h"
|
||||
#include "ui/image/image.h"
|
||||
|
||||
class mtpFileLoader;
|
||||
@@ -32,6 +33,8 @@ class Loader;
|
||||
|
||||
namespace Data {
|
||||
class Session;
|
||||
class DocumentMedia;
|
||||
class ReplyPreview;
|
||||
} // namespace Data
|
||||
|
||||
namespace Main {
|
||||
@@ -56,11 +59,9 @@ struct DocumentAdditionalData {
|
||||
struct StickerData : public DocumentAdditionalData {
|
||||
Data::FileOrigin setOrigin() const;
|
||||
|
||||
std::unique_ptr<Image> image;
|
||||
bool animated = false;
|
||||
QString alt;
|
||||
MTPInputStickerSet set = MTP_inputStickerSetEmpty();
|
||||
StorageImageLocation loc; // doc thumb location
|
||||
};
|
||||
|
||||
struct SongData : public DocumentAdditionalData {
|
||||
@@ -83,9 +84,10 @@ namespace Serialize {
|
||||
class Document;
|
||||
} // namespace Serialize;
|
||||
|
||||
class DocumentData {
|
||||
class DocumentData final {
|
||||
public:
|
||||
DocumentData(not_null<Data::Session*> owner, DocumentId id);
|
||||
~DocumentData();
|
||||
|
||||
[[nodiscard]] Data::Session &owner() const;
|
||||
[[nodiscard]] Main::Session &session() const;
|
||||
@@ -93,19 +95,8 @@ public:
|
||||
void setattributes(
|
||||
const QVector<MTPDocumentAttribute> &attributes);
|
||||
|
||||
void automaticLoad(
|
||||
Data::FileOrigin origin,
|
||||
const HistoryItem *item);
|
||||
void automaticLoadSettingsChanged();
|
||||
|
||||
enum class FilePathResolve {
|
||||
Cached,
|
||||
Checked,
|
||||
SaveFromData,
|
||||
SaveFromDataSilent,
|
||||
};
|
||||
[[nodiscard]] bool loaded(
|
||||
FilePathResolve resolve = FilePathResolve::Cached) const;
|
||||
[[nodiscard]] bool loading() const;
|
||||
[[nodiscard]] QString loadingFilePath() const;
|
||||
[[nodiscard]] bool displayLoading() const;
|
||||
@@ -125,23 +116,18 @@ public:
|
||||
void setWaitingForAlbum();
|
||||
[[nodiscard]] bool waitingForAlbum() const;
|
||||
|
||||
[[nodiscard]] QByteArray data() const;
|
||||
[[nodiscard]] const FileLocation &location(bool check = false) const;
|
||||
void setLocation(const FileLocation &loc);
|
||||
|
||||
[[nodiscard]] QString filepath(
|
||||
FilePathResolve resolve = FilePathResolve::Cached) const;
|
||||
bool saveFromData();
|
||||
bool saveFromDataSilent();
|
||||
[[nodiscard]] QString filepath(bool check = false) const;
|
||||
|
||||
[[nodiscard]] bool saveToCache() const;
|
||||
|
||||
void unload();
|
||||
[[nodiscard]] Image *getReplyPreview(Data::FileOrigin origin);
|
||||
|
||||
[[nodiscard]] StickerData *sticker() const;
|
||||
void checkStickerLarge();
|
||||
void checkStickerSmall();
|
||||
[[nodiscard]] Image *getStickerSmall();
|
||||
[[nodiscard]] Image *getStickerLarge();
|
||||
[[nodiscard]] Data::FileOrigin stickerSetOrigin() const;
|
||||
[[nodiscard]] Data::FileOrigin stickerOrGifOrigin() const;
|
||||
[[nodiscard]] bool isStickerSetInstalled() const;
|
||||
@@ -164,30 +150,52 @@ public:
|
||||
void recountIsImage();
|
||||
[[nodiscard]] bool supportsStreaming() const;
|
||||
void setNotSupportsStreaming();
|
||||
void setData(const QByteArray &data) {
|
||||
_data = data;
|
||||
}
|
||||
void setDataAndCache(const QByteArray &data);
|
||||
bool checkWallPaperProperties();
|
||||
[[nodiscard]] bool isWallPaper() const;
|
||||
[[nodiscard]] bool isPatternWallPaper() const;
|
||||
|
||||
[[nodiscard]] bool hasThumbnail() const;
|
||||
[[nodiscard]] bool thumbnailLoading() const;
|
||||
[[nodiscard]] bool thumbnailFailed() const;
|
||||
void loadThumbnail(Data::FileOrigin origin);
|
||||
[[nodiscard]] Image *thumbnailInline() const;
|
||||
[[nodiscard]] Image *thumbnail() const;
|
||||
[[nodiscard]] const ImageLocation &thumbnailLocation() const;
|
||||
[[nodiscard]] int thumbnailByteSize() const;
|
||||
|
||||
[[nodiscard]] bool hasVideoThumbnail() const;
|
||||
[[nodiscard]] bool videoThumbnailLoading() const;
|
||||
[[nodiscard]] bool videoThumbnailFailed() const;
|
||||
void loadVideoThumbnail(Data::FileOrigin origin);
|
||||
const ImageLocation &videoThumbnailLocation() const;
|
||||
int videoThumbnailByteSize() const;
|
||||
|
||||
void updateThumbnails(
|
||||
ImagePtr thumbnailInline,
|
||||
ImagePtr thumbnail);
|
||||
const QByteArray &inlineThumbnailBytes,
|
||||
const ImageWithLocation &thumbnail,
|
||||
const ImageWithLocation &videoThumbnail);
|
||||
|
||||
[[nodiscard]] QByteArray inlineThumbnailBytes() const {
|
||||
return _inlineThumbnailBytes;
|
||||
}
|
||||
void clearInlineThumbnailBytes() {
|
||||
_inlineThumbnailBytes = QByteArray();
|
||||
}
|
||||
|
||||
[[nodiscard]] Image *goodThumbnail() const;
|
||||
[[nodiscard]] Storage::Cache::Key goodThumbnailCacheKey() const;
|
||||
void setGoodThumbnailOnUpload(QImage &&image, QByteArray &&bytes);
|
||||
void refreshGoodThumbnail();
|
||||
void replaceGoodThumbnail(std::unique_ptr<Images::Source> &&source);
|
||||
[[nodiscard]] bool goodThumbnailChecked() const;
|
||||
[[nodiscard]] bool goodThumbnailGenerating() const;
|
||||
[[nodiscard]] bool goodThumbnailNoData() const;
|
||||
void setGoodThumbnailGenerating();
|
||||
void setGoodThumbnailDataReady();
|
||||
void setGoodThumbnailChecked(bool hasData);
|
||||
|
||||
[[nodiscard]] auto bigFileBaseCacheKey() const
|
||||
-> std::optional<Storage::Cache::Key>;
|
||||
[[nodiscard]] std::shared_ptr<Data::DocumentMedia> createMediaView();
|
||||
[[nodiscard]] auto activeMediaView() const
|
||||
-> std::shared_ptr<Data::DocumentMedia>;
|
||||
void setGoodThumbnailPhoto(not_null<PhotoData*> photo);
|
||||
[[nodiscard]] PhotoData *goodThumbnailPhoto() const;
|
||||
|
||||
[[nodiscard]] Storage::Cache::Key bigFileBaseCacheKey() const;
|
||||
|
||||
void setRemoteLocation(
|
||||
int32 dc,
|
||||
@@ -201,7 +209,6 @@ public:
|
||||
[[nodiscard]] MTPInputDocument mtpInput() const;
|
||||
[[nodiscard]] QByteArray fileReference() const;
|
||||
void refreshFileReference(const QByteArray &value);
|
||||
void refreshStickerThumbFileReference();
|
||||
|
||||
// When we have some client-side generated document
|
||||
// (for example for displaying an external inline bot result)
|
||||
@@ -224,18 +231,16 @@ public:
|
||||
const QString &songPerformer);
|
||||
[[nodiscard]] QString composeNameString() const;
|
||||
|
||||
[[nodiscard]] bool canBePlayed() const;
|
||||
[[nodiscard]] bool canBeStreamed() const;
|
||||
[[nodiscard]] auto createStreamingLoader(
|
||||
Data::FileOrigin origin,
|
||||
bool forceRemoteLoader) const
|
||||
-> std::unique_ptr<Media::Streaming::Loader>;
|
||||
[[nodiscard]] bool useStreamingLoader() const;
|
||||
|
||||
void setInappPlaybackFailed();
|
||||
[[nodiscard]] bool inappPlaybackFailed() const;
|
||||
|
||||
~DocumentData();
|
||||
|
||||
DocumentId id = 0;
|
||||
DocumentType type = FileDocument;
|
||||
QSize dimensions;
|
||||
@@ -258,6 +263,17 @@ private:
|
||||
using Flags = base::flags<Flag>;
|
||||
friend constexpr bool is_flag_type(Flag) { return true; };
|
||||
|
||||
enum class GoodThumbnailFlag : uchar {
|
||||
Checked = 0x01,
|
||||
Generating = 0x02,
|
||||
NoData = 0x03,
|
||||
Mask = 0x03,
|
||||
|
||||
DataReady = 0x04,
|
||||
};
|
||||
using GoodThumbnailState = base::flags<GoodThumbnailFlag>;
|
||||
friend constexpr bool is_flag_type(GoodThumbnailFlag) { return true; };
|
||||
|
||||
static constexpr Flags kStreamingSupportedMask = Flags()
|
||||
| Flag::StreamingMaybeYes
|
||||
| Flag::StreamingMaybeNo;
|
||||
@@ -272,16 +288,18 @@ private:
|
||||
|
||||
friend class Serialize::Document;
|
||||
|
||||
LocationType locationType() const;
|
||||
[[nodiscard]] LocationType locationType() const;
|
||||
void validateLottieSticker();
|
||||
void validateGoodThumbnail();
|
||||
void setMaybeSupportsStreaming(bool supports);
|
||||
void setLoadedInMediaCacheLocation();
|
||||
|
||||
void destroyLoader() const;
|
||||
void finishLoad();
|
||||
void handleLoaderUpdates();
|
||||
void destroyLoader();
|
||||
|
||||
[[nodiscard]] bool useStreamingLoader() const;
|
||||
[[nodiscard]] bool thumbnailEnoughForSticker() const;
|
||||
bool saveFromDataChecked();
|
||||
|
||||
const not_null<Data::Session*> _owner;
|
||||
|
||||
// Two types of location: from MTProto by dc+access or from web by url
|
||||
int32 _dc = 0;
|
||||
@@ -292,19 +310,19 @@ private:
|
||||
QString _mimeString;
|
||||
WebFileLocation _urlLocation;
|
||||
|
||||
ImagePtr _thumbnailInline;
|
||||
ImagePtr _thumbnail;
|
||||
std::unique_ptr<Image> _goodThumbnail;
|
||||
Data::ReplyPreview _replyPreview;
|
||||
|
||||
not_null<Data::Session*> _owner;
|
||||
QByteArray _inlineThumbnailBytes;
|
||||
Data::CloudFile _thumbnail;
|
||||
Data::CloudFile _videoThumbnail;
|
||||
std::unique_ptr<Data::ReplyPreview> _replyPreview;
|
||||
std::weak_ptr<Data::DocumentMedia> _media;
|
||||
PhotoData *_goodThumbnailPhoto = nullptr;
|
||||
|
||||
FileLocation _location;
|
||||
QByteArray _data;
|
||||
std::unique_ptr<DocumentAdditionalData> _additional;
|
||||
int32 _duration = -1;
|
||||
mutable Flags _flags = kStreamingSupportedUnknown;
|
||||
mutable std::unique_ptr<FileLoader> _loader;
|
||||
GoodThumbnailState _goodThumbnailState = GoodThumbnailState();
|
||||
std::unique_ptr<FileLoader> _loader;
|
||||
|
||||
};
|
||||
|
||||
@@ -421,13 +439,19 @@ QString FileNameForSave(
|
||||
bool savingAs,
|
||||
const QDir &dir = QDir());
|
||||
|
||||
QString DocumentFileNameForSave(
|
||||
not_null<const DocumentData*> data,
|
||||
bool forceSavingAs = false,
|
||||
const QString &already = QString(),
|
||||
const QDir &dir = QDir());
|
||||
|
||||
namespace Data {
|
||||
|
||||
QString FileExtension(const QString &filepath);
|
||||
bool IsValidMediaFile(const QString &filepath);
|
||||
bool IsExecutableName(const QString &filepath);
|
||||
base::binary_guard ReadImageAsync(
|
||||
not_null<DocumentData*> document,
|
||||
not_null<Data::DocumentMedia*> media,
|
||||
FnMut<QImage(QImage)> postprocess,
|
||||
FnMut<void(QImage&&)> done);
|
||||
|
||||
|
||||
@@ -1,285 +0,0 @@
|
||||
/*
|
||||
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_document_good_thumbnail.h"
|
||||
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_cloud_themes.h"
|
||||
#include "media/clip/media_clip_reader.h"
|
||||
#include "lottie/lottie_animation.h"
|
||||
#include "window/themes/window_theme_preview.h"
|
||||
#include "main/main_session.h"
|
||||
#include "app.h"
|
||||
|
||||
#include <QtCore/QBuffer>
|
||||
#include <QtGui/QImageReader>
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
constexpr auto kGoodThumbQuality = 87;
|
||||
constexpr auto kWallPaperSize = 960;
|
||||
|
||||
enum class FileType {
|
||||
Video,
|
||||
AnimatedSticker,
|
||||
WallPaper,
|
||||
Theme,
|
||||
};
|
||||
|
||||
QImage Prepare(
|
||||
const QString &path,
|
||||
QByteArray data,
|
||||
FileType type) {
|
||||
if (type == FileType::Video) {
|
||||
return ::Media::Clip::PrepareForSending(path, data).thumbnail;
|
||||
} else if (type == FileType::AnimatedSticker) {
|
||||
return Lottie::ReadThumbnail(Lottie::ReadContent(data, path));
|
||||
} else if (type == FileType::Theme) {
|
||||
return Window::Theme::GeneratePreview(data, path);
|
||||
}
|
||||
const auto validateSize = [](QSize size) {
|
||||
return (size.width() + size.height()) < 10'000;
|
||||
};
|
||||
auto buffer = QBuffer(&data);
|
||||
auto file = QFile(path);
|
||||
auto device = data.isEmpty() ? static_cast<QIODevice*>(&file) : &buffer;
|
||||
auto reader = QImageReader(device);
|
||||
#ifndef OS_MAC_OLD
|
||||
reader.setAutoTransform(true);
|
||||
#endif // OS_MAC_OLD
|
||||
if (!reader.canRead() || !validateSize(reader.size())) {
|
||||
return QImage();
|
||||
}
|
||||
auto result = reader.read();
|
||||
if (!result.width() || !result.height()) {
|
||||
return QImage();
|
||||
}
|
||||
return (result.width() > kWallPaperSize
|
||||
|| result.height() > kWallPaperSize)
|
||||
? result.scaled(
|
||||
kWallPaperSize,
|
||||
kWallPaperSize,
|
||||
Qt::KeepAspectRatio,
|
||||
Qt::SmoothTransformation)
|
||||
: result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
GoodThumbSource::GoodThumbSource(not_null<DocumentData*> document)
|
||||
: _document(document) {
|
||||
}
|
||||
|
||||
void GoodThumbSource::generate(base::binary_guard &&guard) {
|
||||
if (!guard) {
|
||||
return;
|
||||
}
|
||||
const auto data = _document->data();
|
||||
const auto type = _document->isWallPaper()
|
||||
? FileType::WallPaper
|
||||
: _document->isTheme()
|
||||
? FileType::Theme
|
||||
: _document->sticker()
|
||||
? FileType::AnimatedSticker
|
||||
: FileType::Video;
|
||||
auto location = _document->location().isEmpty()
|
||||
? nullptr
|
||||
: std::make_unique<FileLocation>(_document->location());
|
||||
if (data.isEmpty() && !location) {
|
||||
_empty = true;
|
||||
return;
|
||||
}
|
||||
crl::async([
|
||||
=,
|
||||
guard = std::move(guard),
|
||||
location = std::move(location)
|
||||
]() mutable {
|
||||
const auto filepath = (location && location->accessEnable())
|
||||
? location->name()
|
||||
: QString();
|
||||
auto result = Prepare(filepath, data, type);
|
||||
auto bytes = QByteArray();
|
||||
if (!result.isNull()) {
|
||||
auto buffer = QBuffer(&bytes);
|
||||
const auto format = (type == FileType::AnimatedSticker)
|
||||
? "WEBP"
|
||||
: (type == FileType::WallPaper && result.hasAlphaChannel())
|
||||
? "PNG"
|
||||
: "JPG";
|
||||
result.save(&buffer, format, kGoodThumbQuality);
|
||||
}
|
||||
if (!filepath.isEmpty()) {
|
||||
location->accessDisable();
|
||||
}
|
||||
const auto bytesSize = bytes.size();
|
||||
ready(
|
||||
std::move(guard),
|
||||
std::move(result),
|
||||
bytesSize,
|
||||
std::move(bytes));
|
||||
});
|
||||
}
|
||||
|
||||
// NB: This method is called from crl::async(), 'this' is unreliable.
|
||||
void GoodThumbSource::ready(
|
||||
base::binary_guard &&guard,
|
||||
QImage &&image,
|
||||
int bytesSize,
|
||||
QByteArray &&bytesForCache) {
|
||||
crl::on_main(std::move(guard), [
|
||||
=,
|
||||
image = std::move(image),
|
||||
bytes = std::move(bytesForCache)
|
||||
]() mutable {
|
||||
if (image.isNull()) {
|
||||
_empty = true;
|
||||
return;
|
||||
}
|
||||
_loaded = std::move(image);
|
||||
_width = _loaded.width();
|
||||
_height = _loaded.height();
|
||||
_bytesSize = bytesSize;
|
||||
if (!bytes.isEmpty()) {
|
||||
Auth().data().cache().put(
|
||||
_document->goodThumbnailCacheKey(),
|
||||
Storage::Cache::Database::TaggedValue{
|
||||
std::move(bytes),
|
||||
kImageCacheTag });
|
||||
}
|
||||
Auth().downloaderTaskFinished().notify();
|
||||
});
|
||||
}
|
||||
|
||||
void GoodThumbSource::load(FileOrigin origin) {
|
||||
if (loading() || _empty) {
|
||||
return;
|
||||
}
|
||||
auto callback = [=, guard = _loading.make_guard()](
|
||||
QByteArray &&value) mutable {
|
||||
if (value.isEmpty()) {
|
||||
crl::on_main([=, guard = std::move(guard)]() mutable {
|
||||
generate(std::move(guard));
|
||||
});
|
||||
return;
|
||||
}
|
||||
crl::async([
|
||||
=,
|
||||
guard = std::move(guard),
|
||||
value = std::move(value)
|
||||
]() mutable {
|
||||
ready(
|
||||
std::move(guard),
|
||||
App::readImage(value, nullptr, false),
|
||||
value.size());
|
||||
});
|
||||
};
|
||||
|
||||
Auth().data().cache().get(
|
||||
_document->goodThumbnailCacheKey(),
|
||||
std::move(callback));
|
||||
}
|
||||
|
||||
void GoodThumbSource::loadEvenCancelled(FileOrigin origin) {
|
||||
_empty = false;
|
||||
load(origin);
|
||||
}
|
||||
|
||||
QImage GoodThumbSource::takeLoaded() {
|
||||
return std::move(_loaded);
|
||||
}
|
||||
|
||||
void GoodThumbSource::unload() {
|
||||
_loaded = QImage();
|
||||
cancel();
|
||||
}
|
||||
|
||||
void GoodThumbSource::automaticLoad(
|
||||
FileOrigin origin,
|
||||
const HistoryItem *item) {
|
||||
}
|
||||
|
||||
void GoodThumbSource::automaticLoadSettingsChanged() {
|
||||
}
|
||||
|
||||
bool GoodThumbSource::loading() {
|
||||
return _loading.alive();
|
||||
}
|
||||
|
||||
bool GoodThumbSource::displayLoading() {
|
||||
return false;
|
||||
}
|
||||
|
||||
void GoodThumbSource::cancel() {
|
||||
_loading = nullptr;
|
||||
}
|
||||
|
||||
float64 GoodThumbSource::progress() {
|
||||
return 1.;
|
||||
}
|
||||
|
||||
int GoodThumbSource::loadOffset() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const StorageImageLocation &GoodThumbSource::location() {
|
||||
return StorageImageLocation::Invalid();
|
||||
}
|
||||
|
||||
void GoodThumbSource::refreshFileReference(const QByteArray &data) {
|
||||
}
|
||||
|
||||
std::optional<Storage::Cache::Key> GoodThumbSource::cacheKey() {
|
||||
return _document->goodThumbnailCacheKey();
|
||||
}
|
||||
|
||||
void GoodThumbSource::setDelayedStorageLocation(
|
||||
const StorageImageLocation &location) {
|
||||
}
|
||||
|
||||
void GoodThumbSource::performDelayedLoad(FileOrigin origin) {
|
||||
}
|
||||
|
||||
bool GoodThumbSource::isDelayedStorageImage() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void GoodThumbSource::setImageBytes(const QByteArray &bytes) {
|
||||
if (!bytes.isEmpty()) {
|
||||
cancel();
|
||||
_loaded = App::readImage(bytes);
|
||||
_width = _loaded.width();
|
||||
_height = _loaded.height();
|
||||
_bytesSize = bytes.size();
|
||||
}
|
||||
}
|
||||
|
||||
int GoodThumbSource::width() {
|
||||
return _width;
|
||||
}
|
||||
|
||||
int GoodThumbSource::height() {
|
||||
return _height;
|
||||
}
|
||||
|
||||
int GoodThumbSource::bytesSize() {
|
||||
return _bytesSize;
|
||||
}
|
||||
|
||||
void GoodThumbSource::setInformation(int size, int width, int height) {
|
||||
_width = width;
|
||||
_height = height;
|
||||
_bytesSize = size;
|
||||
}
|
||||
|
||||
QByteArray GoodThumbSource::bytesForCache() {
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ui/image/image.h"
|
||||
#include "base/binary_guard.h"
|
||||
|
||||
class DocumentData;
|
||||
|
||||
namespace Data {
|
||||
|
||||
class GoodThumbSource : public Images::Source {
|
||||
public:
|
||||
explicit GoodThumbSource(not_null<DocumentData*> document);
|
||||
|
||||
void load(FileOrigin origin) override;
|
||||
void loadEvenCancelled(FileOrigin origin) override;
|
||||
QImage takeLoaded() override;
|
||||
void unload() override;
|
||||
|
||||
void automaticLoad(
|
||||
FileOrigin origin,
|
||||
const HistoryItem *item) override;
|
||||
void automaticLoadSettingsChanged() override;
|
||||
|
||||
bool loading() override;
|
||||
bool displayLoading() override;
|
||||
void cancel() override;
|
||||
float64 progress() override;
|
||||
int loadOffset() override;
|
||||
|
||||
const StorageImageLocation &location() override;
|
||||
void refreshFileReference(const QByteArray &data) override;
|
||||
std::optional<Storage::Cache::Key> cacheKey() override;
|
||||
void setDelayedStorageLocation(
|
||||
const StorageImageLocation &location) override;
|
||||
void performDelayedLoad(FileOrigin origin) override;
|
||||
bool isDelayedStorageImage() const override;
|
||||
void setImageBytes(const QByteArray &bytes) override;
|
||||
|
||||
int width() override;
|
||||
int height() override;
|
||||
int bytesSize() override;
|
||||
void setInformation(int size, int width, int height) override;
|
||||
|
||||
QByteArray bytesForCache() override;
|
||||
|
||||
private:
|
||||
void generate(base::binary_guard &&guard);
|
||||
|
||||
// NB: This method is called from crl::async(), 'this' is unreliable.
|
||||
void ready(
|
||||
base::binary_guard &&guard,
|
||||
QImage &&image,
|
||||
int bytesSize,
|
||||
QByteArray &&bytesForCache = {});
|
||||
|
||||
not_null<DocumentData*> _document;
|
||||
QImage _loaded;
|
||||
base::binary_guard _loading;
|
||||
int _width = 0;
|
||||
int _height = 0;
|
||||
int _bytesSize = 0;
|
||||
bool _empty = false;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Data
|
||||
469
Telegram/SourceFiles/data/data_document_media.cpp
Normal file
469
Telegram/SourceFiles/data/data_document_media.cpp
Normal file
@@ -0,0 +1,469 @@
|
||||
/*
|
||||
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_document_media.h"
|
||||
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_cloud_themes.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_auto_download.h"
|
||||
#include "media/clip/media_clip_reader.h"
|
||||
#include "main/main_session.h"
|
||||
#include "lottie/lottie_animation.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history.h"
|
||||
#include "window/themes/window_theme_preview.h"
|
||||
#include "storage/file_download.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "facades.h"
|
||||
#include "app.h"
|
||||
|
||||
#include <QtCore/QBuffer>
|
||||
#include <QtGui/QImageReader>
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
constexpr auto kReadAreaLimit = 12'032 * 9'024;
|
||||
constexpr auto kWallPaperThumbnailLimit = 960;
|
||||
constexpr auto kMaxVideoFrameArea = 7'680 * 4'320;
|
||||
constexpr auto kGoodThumbQuality = 87;
|
||||
|
||||
enum class FileType {
|
||||
Video,
|
||||
AnimatedSticker,
|
||||
WallPaper,
|
||||
Theme,
|
||||
};
|
||||
|
||||
[[nodiscard]] bool MayHaveGoodThumbnail(not_null<DocumentData*> owner) {
|
||||
return owner->isVideoFile()
|
||||
|| owner->isAnimation()
|
||||
|| owner->isWallPaper()
|
||||
|| owner->isTheme()
|
||||
|| (owner->sticker() && owner->sticker()->animated);
|
||||
}
|
||||
|
||||
[[nodiscard]] QImage PrepareGoodThumbnail(
|
||||
const QString &path,
|
||||
QByteArray data,
|
||||
FileType type) {
|
||||
if (type == FileType::Video) {
|
||||
return ::Media::Clip::PrepareForSending(path, data).thumbnail;
|
||||
} else if (type == FileType::AnimatedSticker) {
|
||||
return Lottie::ReadThumbnail(Lottie::ReadContent(data, path));
|
||||
} else if (type == FileType::Theme) {
|
||||
return Window::Theme::GeneratePreview(data, path);
|
||||
}
|
||||
auto buffer = QBuffer(&data);
|
||||
auto file = QFile(path);
|
||||
auto device = data.isEmpty() ? static_cast<QIODevice*>(&file) : &buffer;
|
||||
auto reader = QImageReader(device);
|
||||
const auto size = reader.size();
|
||||
if (!reader.canRead()
|
||||
|| (size.width() * size.height() > kReadAreaLimit)) {
|
||||
return QImage();
|
||||
}
|
||||
auto result = reader.read();
|
||||
if (!result.width() || !result.height()) {
|
||||
return QImage();
|
||||
}
|
||||
return (result.width() > kWallPaperThumbnailLimit
|
||||
|| result.height() > kWallPaperThumbnailLimit)
|
||||
? result.scaled(
|
||||
kWallPaperThumbnailLimit,
|
||||
kWallPaperThumbnailLimit,
|
||||
Qt::KeepAspectRatio,
|
||||
Qt::SmoothTransformation)
|
||||
: result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
VideoPreviewState::VideoPreviewState(DocumentMedia *media)
|
||||
: _media(media)
|
||||
, _usingThumbnail(media ? media->owner()->hasVideoThumbnail() : false) {
|
||||
}
|
||||
|
||||
void VideoPreviewState::automaticLoad(Data::FileOrigin origin) const {
|
||||
Expects(_media != nullptr);
|
||||
|
||||
if (_usingThumbnail) {
|
||||
_media->videoThumbnailWanted(origin);
|
||||
} else {
|
||||
_media->automaticLoad(origin, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
::Media::Clip::ReaderPointer VideoPreviewState::makeAnimation(
|
||||
Fn<void(::Media::Clip::Notification)> callback) const {
|
||||
Expects(_media != nullptr);
|
||||
Expects(loaded());
|
||||
|
||||
return _usingThumbnail
|
||||
? ::Media::Clip::MakeReader(
|
||||
_media->videoThumbnailContent(),
|
||||
std::move(callback))
|
||||
: ::Media::Clip::MakeReader(
|
||||
_media,
|
||||
FullMsgId(),
|
||||
std::move(callback));
|
||||
}
|
||||
|
||||
bool VideoPreviewState::usingThumbnail() const {
|
||||
return _usingThumbnail;
|
||||
}
|
||||
|
||||
bool VideoPreviewState::loading() const {
|
||||
return _usingThumbnail
|
||||
? _media->owner()->videoThumbnailLoading()
|
||||
: _media
|
||||
? _media->owner()->loading()
|
||||
: false;
|
||||
}
|
||||
|
||||
bool VideoPreviewState::loaded() const {
|
||||
return _usingThumbnail
|
||||
? !_media->videoThumbnailContent().isEmpty()
|
||||
: _media
|
||||
? _media->loaded()
|
||||
: false;
|
||||
}
|
||||
|
||||
DocumentMedia::DocumentMedia(not_null<DocumentData*> owner)
|
||||
: _owner(owner) {
|
||||
}
|
||||
|
||||
// NB! Right now DocumentMedia can outlive Main::Session!
|
||||
// In DocumentData::collectLocalData a shared_ptr is sent on_main.
|
||||
// In case this is a problem the ~Gif code should be rewritten.
|
||||
DocumentMedia::~DocumentMedia() = default;
|
||||
|
||||
not_null<DocumentData*> DocumentMedia::owner() const {
|
||||
return _owner;
|
||||
}
|
||||
|
||||
void DocumentMedia::goodThumbnailWanted() {
|
||||
_flags |= Flag::GoodThumbnailWanted;
|
||||
}
|
||||
|
||||
Image *DocumentMedia::goodThumbnail() const {
|
||||
Expects((_flags & Flag::GoodThumbnailWanted) != 0);
|
||||
|
||||
if (!_goodThumbnail) {
|
||||
ReadOrGenerateThumbnail(_owner);
|
||||
}
|
||||
return _goodThumbnail.get();
|
||||
}
|
||||
|
||||
void DocumentMedia::setGoodThumbnail(QImage thumbnail) {
|
||||
if (!(_flags & Flag::GoodThumbnailWanted)) {
|
||||
return;
|
||||
}
|
||||
_goodThumbnail = std::make_unique<Image>(std::move(thumbnail));
|
||||
_owner->session().downloaderTaskFinished().notify();
|
||||
}
|
||||
|
||||
Image *DocumentMedia::thumbnailInline() const {
|
||||
if (!_inlineThumbnail) {
|
||||
const auto bytes = _owner->inlineThumbnailBytes();
|
||||
if (!bytes.isEmpty()) {
|
||||
auto image = Images::FromInlineBytes(bytes);
|
||||
if (image.isNull()) {
|
||||
_owner->clearInlineThumbnailBytes();
|
||||
} else {
|
||||
_inlineThumbnail = std::make_unique<Image>(std::move(image));
|
||||
}
|
||||
}
|
||||
}
|
||||
return _inlineThumbnail.get();
|
||||
}
|
||||
|
||||
Image *DocumentMedia::thumbnail() const {
|
||||
return _thumbnail.get();
|
||||
}
|
||||
|
||||
void DocumentMedia::thumbnailWanted(Data::FileOrigin origin) {
|
||||
if (!_thumbnail) {
|
||||
_owner->loadThumbnail(origin);
|
||||
}
|
||||
}
|
||||
|
||||
QSize DocumentMedia::thumbnailSize() const {
|
||||
if (const auto image = _thumbnail.get()) {
|
||||
return image->size();
|
||||
}
|
||||
const auto &location = _owner->thumbnailLocation();
|
||||
return { location.width(), location.height() };
|
||||
}
|
||||
|
||||
void DocumentMedia::setThumbnail(QImage thumbnail) {
|
||||
_thumbnail = std::make_unique<Image>(std::move(thumbnail));
|
||||
_owner->session().downloaderTaskFinished().notify();
|
||||
}
|
||||
|
||||
QByteArray DocumentMedia::videoThumbnailContent() const {
|
||||
return _videoThumbnailBytes;
|
||||
}
|
||||
|
||||
QSize DocumentMedia::videoThumbnailSize() const {
|
||||
const auto &location = _owner->videoThumbnailLocation();
|
||||
return { location.width(), location.height() };
|
||||
}
|
||||
|
||||
void DocumentMedia::videoThumbnailWanted(Data::FileOrigin origin) {
|
||||
if (_videoThumbnailBytes.isEmpty()) {
|
||||
_owner->loadVideoThumbnail(origin);
|
||||
}
|
||||
}
|
||||
|
||||
void DocumentMedia::setVideoThumbnail(QByteArray content) {
|
||||
_videoThumbnailBytes = std::move(content);
|
||||
}
|
||||
|
||||
void DocumentMedia::checkStickerLarge() {
|
||||
if (_sticker) {
|
||||
return;
|
||||
}
|
||||
const auto data = _owner->sticker();
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
automaticLoad(_owner->stickerSetOrigin(), nullptr);
|
||||
if (data->animated || !loaded()) {
|
||||
return;
|
||||
}
|
||||
if (_bytes.isEmpty()) {
|
||||
const auto &loc = _owner->location(true);
|
||||
if (loc.accessEnable()) {
|
||||
_sticker = std::make_unique<Image>(loc.name());
|
||||
loc.accessDisable();
|
||||
}
|
||||
} else {
|
||||
_sticker = std::make_unique<Image>(_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
void DocumentMedia::automaticLoad(
|
||||
Data::FileOrigin origin,
|
||||
const HistoryItem *item) {
|
||||
if (_owner->status != FileReady || loaded() || _owner->cancelled()) {
|
||||
return;
|
||||
} else if (!item && !_owner->sticker() && !_owner->isAnimation()) {
|
||||
return;
|
||||
}
|
||||
const auto toCache = _owner->saveToCache();
|
||||
if (!toCache && Global::AskDownloadPath()) {
|
||||
// We need a filename, but we're supposed to ask user for it.
|
||||
// No automatic download in this case.
|
||||
return;
|
||||
}
|
||||
const auto filename = toCache
|
||||
? QString()
|
||||
: DocumentFileNameForSave(_owner);
|
||||
const auto shouldLoadFromCloud = !Data::IsExecutableName(filename)
|
||||
&& (item
|
||||
? Data::AutoDownload::Should(
|
||||
_owner->session().settings().autoDownload(),
|
||||
item->history()->peer,
|
||||
_owner)
|
||||
: Data::AutoDownload::Should(
|
||||
_owner->session().settings().autoDownload(),
|
||||
_owner));
|
||||
const auto loadFromCloud = shouldLoadFromCloud
|
||||
? LoadFromCloudOrLocal
|
||||
: LoadFromLocalOnly;
|
||||
_owner->save(
|
||||
origin,
|
||||
filename,
|
||||
loadFromCloud,
|
||||
true);
|
||||
}
|
||||
|
||||
void DocumentMedia::collectLocalData(not_null<DocumentMedia*> local) {
|
||||
if (const auto image = local->_goodThumbnail.get()) {
|
||||
_goodThumbnail = std::make_unique<Image>(image->original());
|
||||
}
|
||||
if (const auto image = local->_inlineThumbnail.get()) {
|
||||
_inlineThumbnail = std::make_unique<Image>(image->original());
|
||||
}
|
||||
if (const auto image = local->_thumbnail.get()) {
|
||||
_thumbnail = std::make_unique<Image>(image->original());
|
||||
}
|
||||
if (const auto image = local->_sticker.get()) {
|
||||
_sticker = std::make_unique<Image>(image->original());
|
||||
}
|
||||
_bytes = local->_bytes;
|
||||
_videoThumbnailBytes = local->_videoThumbnailBytes;
|
||||
_flags = local->_flags;
|
||||
}
|
||||
|
||||
void DocumentMedia::setBytes(const QByteArray &bytes) {
|
||||
if (!bytes.isEmpty()) {
|
||||
_bytes = bytes;
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray DocumentMedia::bytes() const {
|
||||
return _bytes;
|
||||
}
|
||||
|
||||
bool DocumentMedia::loaded(bool check) const {
|
||||
return !_bytes.isEmpty() || !_owner->filepath(check).isEmpty();
|
||||
}
|
||||
|
||||
float64 DocumentMedia::progress() const {
|
||||
return (_owner->uploading() || _owner->loading())
|
||||
? _owner->progress()
|
||||
: (loaded() ? 1. : 0.);
|
||||
}
|
||||
|
||||
bool DocumentMedia::canBePlayed() const {
|
||||
return !_owner->inappPlaybackFailed()
|
||||
&& _owner->useStreamingLoader()
|
||||
&& (loaded() || _owner->canBeStreamed());
|
||||
}
|
||||
|
||||
bool DocumentMedia::thumbnailEnoughForSticker() const {
|
||||
const auto &location = owner()->thumbnailLocation();
|
||||
const auto size = _thumbnail
|
||||
? QSize(_thumbnail->width(), _thumbnail->height())
|
||||
: location.valid()
|
||||
? QSize(location.width(), location.height())
|
||||
: QSize();
|
||||
return (size.width() >= 128) || (size.height() >= 128);
|
||||
}
|
||||
|
||||
void DocumentMedia::checkStickerSmall() {
|
||||
const auto data = _owner->sticker();
|
||||
if ((data && data->animated) || thumbnailEnoughForSticker()) {
|
||||
_owner->loadThumbnail(_owner->stickerSetOrigin());
|
||||
if (data && data->animated) {
|
||||
automaticLoad(_owner->stickerSetOrigin(), nullptr);
|
||||
}
|
||||
} else {
|
||||
checkStickerLarge();
|
||||
}
|
||||
}
|
||||
|
||||
Image *DocumentMedia::getStickerLarge() {
|
||||
checkStickerLarge();
|
||||
return _sticker.get();
|
||||
}
|
||||
|
||||
Image *DocumentMedia::getStickerSmall() {
|
||||
const auto data = _owner->sticker();
|
||||
if ((data && data->animated) || thumbnailEnoughForSticker()) {
|
||||
return thumbnail();
|
||||
}
|
||||
return _sticker.get();
|
||||
}
|
||||
|
||||
void DocumentMedia::checkStickerLarge(not_null<FileLoader*> loader) {
|
||||
if (_sticker || !_owner->sticker()) {
|
||||
return;
|
||||
}
|
||||
if (auto image = loader->imageData(); !image.isNull()) {
|
||||
_sticker = std::make_unique<Image>(std::move(image));
|
||||
}
|
||||
}
|
||||
|
||||
void DocumentMedia::GenerateGoodThumbnail(
|
||||
not_null<DocumentData*> document,
|
||||
QByteArray data) {
|
||||
const auto type = document->isWallPaper()
|
||||
? FileType::WallPaper
|
||||
: document->isTheme()
|
||||
? FileType::Theme
|
||||
: document->sticker()
|
||||
? FileType::AnimatedSticker
|
||||
: FileType::Video;
|
||||
auto location = document->location().isEmpty()
|
||||
? nullptr
|
||||
: std::make_unique<FileLocation>(document->location());
|
||||
if (data.isEmpty() && !location) {
|
||||
document->setGoodThumbnailChecked(false);
|
||||
return;
|
||||
}
|
||||
const auto guard = base::make_weak(&document->owner().session());
|
||||
crl::async([=, location = std::move(location)] {
|
||||
const auto filepath = (location && location->accessEnable())
|
||||
? location->name()
|
||||
: QString();
|
||||
auto result = PrepareGoodThumbnail(filepath, data, type);
|
||||
auto bytes = QByteArray();
|
||||
if (!result.isNull()) {
|
||||
auto buffer = QBuffer(&bytes);
|
||||
const auto format = (type == FileType::AnimatedSticker)
|
||||
? "WEBP"
|
||||
: (type == FileType::WallPaper && result.hasAlphaChannel())
|
||||
? "PNG"
|
||||
: "JPG";
|
||||
result.save(&buffer, format, kGoodThumbQuality);
|
||||
}
|
||||
if (!filepath.isEmpty()) {
|
||||
location->accessDisable();
|
||||
}
|
||||
const auto cache = bytes.isEmpty() ? QByteArray("(failed)") : bytes;
|
||||
crl::on_main(guard, [=] {
|
||||
document->setGoodThumbnailChecked(true);
|
||||
if (const auto active = document->activeMediaView()) {
|
||||
active->setGoodThumbnail(result);
|
||||
}
|
||||
document->owner().cache().put(
|
||||
document->goodThumbnailCacheKey(),
|
||||
Storage::Cache::Database::TaggedValue{
|
||||
base::duplicate(cache),
|
||||
kImageCacheTag });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void DocumentMedia::CheckGoodThumbnail(not_null<DocumentData*> document) {
|
||||
if (!document->goodThumbnailChecked()) {
|
||||
ReadOrGenerateThumbnail(document);
|
||||
}
|
||||
}
|
||||
|
||||
void DocumentMedia::ReadOrGenerateThumbnail(
|
||||
not_null<DocumentData*> document) {
|
||||
if (document->goodThumbnailGenerating()
|
||||
|| document->goodThumbnailNoData()
|
||||
|| !MayHaveGoodThumbnail(document)) {
|
||||
return;
|
||||
}
|
||||
document->setGoodThumbnailGenerating();
|
||||
|
||||
const auto guard = base::make_weak(&document->session());
|
||||
const auto active = document->activeMediaView();
|
||||
const auto got = [=](QByteArray value) {
|
||||
if (value.isEmpty()) {
|
||||
const auto bytes = active ? active->bytes() : QByteArray();
|
||||
crl::on_main(guard, [=] {
|
||||
GenerateGoodThumbnail(document, bytes);
|
||||
});
|
||||
} else if (active) {
|
||||
crl::async([=] {
|
||||
const auto image = App::readImage(value, nullptr, false);
|
||||
crl::on_main(guard, [=] {
|
||||
document->setGoodThumbnailChecked(true);
|
||||
if (const auto active = document->activeMediaView()) {
|
||||
active->setGoodThumbnail(image);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
crl::on_main(guard, [=] {
|
||||
document->setGoodThumbnailChecked(true);
|
||||
});
|
||||
}
|
||||
};
|
||||
document->owner().cache().get(document->goodThumbnailCacheKey(), got);
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
113
Telegram/SourceFiles/data/data_document_media.h
Normal file
113
Telegram/SourceFiles/data/data_document_media.h
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
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/flags.h"
|
||||
|
||||
class Image;
|
||||
class FileLoader;
|
||||
|
||||
namespace Media {
|
||||
namespace Clip {
|
||||
enum Notification : int;
|
||||
class ReaderPointer;
|
||||
} // namespace Clip
|
||||
} // namespace Media
|
||||
|
||||
namespace Data {
|
||||
|
||||
class DocumentMedia;
|
||||
|
||||
class VideoPreviewState final {
|
||||
public:
|
||||
explicit VideoPreviewState(DocumentMedia *media);
|
||||
|
||||
void automaticLoad(Data::FileOrigin origin) const;
|
||||
[[nodiscard]] ::Media::Clip::ReaderPointer makeAnimation(
|
||||
Fn<void(::Media::Clip::Notification)> callback) const;
|
||||
[[nodiscard]] bool usingThumbnail() const;
|
||||
[[nodiscard]] bool loading() const;
|
||||
[[nodiscard]] bool loaded() const;
|
||||
|
||||
private:
|
||||
DocumentMedia *_media = nullptr;
|
||||
bool _usingThumbnail = false;
|
||||
|
||||
};
|
||||
|
||||
class DocumentMedia final {
|
||||
public:
|
||||
explicit DocumentMedia(not_null<DocumentData*> owner);
|
||||
~DocumentMedia();
|
||||
|
||||
[[nodiscard]] not_null<DocumentData*> owner() const;
|
||||
|
||||
void goodThumbnailWanted();
|
||||
[[nodiscard]] Image *goodThumbnail() const;
|
||||
void setGoodThumbnail(QImage thumbnail);
|
||||
|
||||
[[nodiscard]] Image *thumbnailInline() const;
|
||||
|
||||
[[nodiscard]] Image *thumbnail() const;
|
||||
[[nodiscard]] QSize thumbnailSize() const;
|
||||
void thumbnailWanted(Data::FileOrigin origin);
|
||||
void setThumbnail(QImage thumbnail);
|
||||
|
||||
[[nodiscard]] QByteArray videoThumbnailContent() const;
|
||||
[[nodiscard]] QSize videoThumbnailSize() const;
|
||||
void videoThumbnailWanted(Data::FileOrigin origin);
|
||||
void setVideoThumbnail(QByteArray content);
|
||||
|
||||
void checkStickerLarge();
|
||||
void checkStickerSmall();
|
||||
[[nodiscard]] Image *getStickerSmall();
|
||||
[[nodiscard]] Image *getStickerLarge();
|
||||
void checkStickerLarge(not_null<FileLoader*> loader);
|
||||
|
||||
void setBytes(const QByteArray &bytes);
|
||||
[[nodiscard]] QByteArray bytes() const;
|
||||
[[nodiscard]] bool loaded(bool check = false) const;
|
||||
[[nodiscard]] float64 progress() const;
|
||||
[[nodiscard]] bool canBePlayed() const;
|
||||
|
||||
void automaticLoad(Data::FileOrigin origin, const HistoryItem *item);
|
||||
|
||||
void collectLocalData(not_null<DocumentMedia*> local);
|
||||
|
||||
// For DocumentData.
|
||||
static void CheckGoodThumbnail(not_null<DocumentData*> document);
|
||||
|
||||
private:
|
||||
enum class Flag : uchar {
|
||||
GoodThumbnailWanted = 0x01,
|
||||
};
|
||||
inline constexpr bool is_flag_type(Flag) { return true; };
|
||||
using Flags = base::flags<Flag>;
|
||||
|
||||
static void ReadOrGenerateThumbnail(not_null<DocumentData*> document);
|
||||
static void GenerateGoodThumbnail(
|
||||
not_null<DocumentData*> document,
|
||||
QByteArray data);
|
||||
|
||||
[[nodiscard]] bool thumbnailEnoughForSticker() const;
|
||||
|
||||
// NB! Right now DocumentMedia can outlive Main::Session!
|
||||
// In DocumentData::collectLocalData a shared_ptr is sent on_main.
|
||||
// In case this is a problem the ~Gif code should be rewritten.
|
||||
const not_null<DocumentData*> _owner;
|
||||
std::unique_ptr<Image> _goodThumbnail;
|
||||
mutable std::unique_ptr<Image> _inlineThumbnail;
|
||||
std::unique_ptr<Image> _thumbnail;
|
||||
std::unique_ptr<Image> _sticker;
|
||||
QByteArray _bytes;
|
||||
QByteArray _videoThumbnailBytes;
|
||||
Flags _flags;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Data
|
||||
@@ -248,6 +248,7 @@ void Folder::loadUserpic() {
|
||||
|
||||
void Folder::paintUserpic(
|
||||
Painter &p,
|
||||
std::shared_ptr<Data::CloudImageView> &view,
|
||||
int x,
|
||||
int y,
|
||||
int size) const {
|
||||
|
||||
@@ -64,6 +64,7 @@ public:
|
||||
void loadUserpic() override;
|
||||
void paintUserpic(
|
||||
Painter &p,
|
||||
std::shared_ptr<Data::CloudImageView> &view,
|
||||
int x,
|
||||
int y,
|
||||
int size) const override;
|
||||
|
||||
@@ -66,23 +66,32 @@ void Histories::clearAll() {
|
||||
}
|
||||
|
||||
void Histories::readInbox(not_null<History*> history) {
|
||||
DEBUG_LOG(("Reading: readInbox called."));
|
||||
if (history->lastServerMessageKnown()) {
|
||||
const auto last = history->lastServerMessage();
|
||||
DEBUG_LOG(("Reading: last known, reading till %1."
|
||||
).arg(last ? last->id : 0));
|
||||
readInboxTill(history, last ? last->id : 0);
|
||||
return;
|
||||
} else if (history->loadedAtBottom()) {
|
||||
if (const auto lastId = history->maxMsgId()) {
|
||||
DEBUG_LOG(("Reading: loaded at bottom, maxMsgId %1."
|
||||
).arg(lastId));
|
||||
readInboxTill(history, lastId);
|
||||
return;
|
||||
} else if (history->loadedAtTop()) {
|
||||
DEBUG_LOG(("Reading: loaded at bottom, loaded at top."));
|
||||
readInboxTill(history, 0);
|
||||
return;
|
||||
}
|
||||
DEBUG_LOG(("Reading: loaded at bottom, but requesting entry."));
|
||||
}
|
||||
requestDialogEntry(history, [=] {
|
||||
Expects(history->lastServerMessageKnown());
|
||||
|
||||
const auto last = history->lastServerMessage();
|
||||
DEBUG_LOG(("Reading: got entry, reading till %1."
|
||||
).arg(last ? last->id : 0));
|
||||
readInboxTill(history, last ? last->id : 0);
|
||||
});
|
||||
}
|
||||
@@ -135,10 +144,20 @@ void Histories::readInboxTill(
|
||||
bool force) {
|
||||
Expects(IsServerMsgId(tillId) || (!tillId && !force));
|
||||
|
||||
DEBUG_LOG(("Reading: readInboxTill %1, force %2."
|
||||
).arg(tillId
|
||||
).arg(Logs::b(force)));
|
||||
|
||||
const auto syncGuard = gsl::finally([&] {
|
||||
DEBUG_LOG(("Reading: in guard, unread %1."
|
||||
).arg(history->unreadCount()));
|
||||
if (history->unreadCount() > 0) {
|
||||
if (const auto last = history->lastServerMessage()) {
|
||||
DEBUG_LOG(("Reading: checking last %1 and %2."
|
||||
).arg(last->id
|
||||
).arg(tillId));
|
||||
if (last->id == tillId) {
|
||||
DEBUG_LOG(("Reading: locally marked as read."));
|
||||
history->setUnreadCount(0);
|
||||
history->updateChatListEntry();
|
||||
}
|
||||
@@ -150,14 +169,21 @@ void Histories::readInboxTill(
|
||||
|
||||
const auto needsRequest = history->readInboxTillNeedsRequest(tillId);
|
||||
if (!needsRequest && !force) {
|
||||
DEBUG_LOG(("Reading: readInboxTill finish 1."));
|
||||
return;
|
||||
} else if (!history->trackUnreadMessages()) {
|
||||
DEBUG_LOG(("Reading: readInboxTill finish 2."));
|
||||
return;
|
||||
}
|
||||
const auto maybeState = lookup(history);
|
||||
if (maybeState && maybeState->sentReadTill >= tillId) {
|
||||
DEBUG_LOG(("Reading: readInboxTill finish 3 with %1."
|
||||
).arg(maybeState->sentReadTill));
|
||||
return;
|
||||
} else if (maybeState && maybeState->willReadTill >= tillId) {
|
||||
DEBUG_LOG(("Reading: readInboxTill finish 4 with %1 and force %2."
|
||||
).arg(maybeState->sentReadTill
|
||||
).arg(Logs::b(force)));
|
||||
if (force) {
|
||||
sendPendingReadInbox(history);
|
||||
}
|
||||
@@ -171,23 +197,35 @@ void Histories::readInboxTill(
|
||||
&& stillUnread
|
||||
&& history->unreadCountKnown()
|
||||
&& *stillUnread == history->unreadCount()) {
|
||||
DEBUG_LOG(("Reading: count didn't change so just update till %1"
|
||||
).arg(tillId));
|
||||
history->setInboxReadTill(tillId);
|
||||
return;
|
||||
}
|
||||
auto &state = maybeState ? *maybeState : _states[history];
|
||||
state.willReadTill = tillId;
|
||||
if (force || !stillUnread || !*stillUnread) {
|
||||
DEBUG_LOG(("Reading: will read till %1 with still unread %2"
|
||||
).arg(tillId
|
||||
).arg(stillUnread.value_or(-666)));
|
||||
state.willReadWhen = 0;
|
||||
sendReadRequests();
|
||||
if (!stillUnread) {
|
||||
return;
|
||||
}
|
||||
} else if (!state.willReadWhen) {
|
||||
DEBUG_LOG(("Reading: will read till %1 with postponed").arg(tillId));
|
||||
state.willReadWhen = crl::now() + kReadRequestTimeout;
|
||||
if (!_readRequestsTimer.isActive()) {
|
||||
_readRequestsTimer.callOnce(kReadRequestTimeout);
|
||||
}
|
||||
} else {
|
||||
DEBUG_LOG(("Reading: will read till %1 postponed already"
|
||||
).arg(tillId));
|
||||
}
|
||||
DEBUG_LOG(("Reading: marking now with till %1 and still %2"
|
||||
).arg(tillId
|
||||
).arg(*stillUnread));
|
||||
history->setInboxReadTill(tillId);
|
||||
history->setUnreadCount(*stillUnread);
|
||||
history->updateChatListEntry();
|
||||
@@ -399,6 +437,9 @@ void Histories::requestFakeChatListMessage(
|
||||
|
||||
void Histories::sendPendingReadInbox(not_null<History*> history) {
|
||||
if (const auto state = lookup(history)) {
|
||||
DEBUG_LOG(("Reading: send pending now with till %1 and when %2"
|
||||
).arg(state->willReadTill
|
||||
).arg(state->willReadWhen));
|
||||
if (state->willReadTill && state->willReadWhen) {
|
||||
state->willReadWhen = 0;
|
||||
sendReadRequests();
|
||||
@@ -407,6 +448,7 @@ void Histories::sendPendingReadInbox(not_null<History*> history) {
|
||||
}
|
||||
|
||||
void Histories::sendReadRequests() {
|
||||
DEBUG_LOG(("Reading: send requests with count %1.").arg(_states.size()));
|
||||
if (_states.empty()) {
|
||||
return;
|
||||
}
|
||||
@@ -414,10 +456,14 @@ void Histories::sendReadRequests() {
|
||||
auto next = std::optional<crl::time>();
|
||||
for (auto &[history, state] : _states) {
|
||||
if (!state.willReadTill) {
|
||||
DEBUG_LOG(("Reading: skipping zero till."));
|
||||
continue;
|
||||
} else if (state.willReadWhen <= now) {
|
||||
DEBUG_LOG(("Reading: sending with till %1."
|
||||
).arg(state.willReadTill));
|
||||
sendReadRequest(history, state);
|
||||
} else if (!next || *next > state.willReadWhen) {
|
||||
DEBUG_LOG(("Reading: scheduling for later send."));
|
||||
next = state.willReadWhen;
|
||||
}
|
||||
}
|
||||
@@ -434,7 +480,11 @@ void Histories::sendReadRequest(not_null<History*> history, State &state) {
|
||||
const auto tillId = state.sentReadTill = base::take(state.willReadTill);
|
||||
state.willReadWhen = 0;
|
||||
state.sentReadDone = false;
|
||||
DEBUG_LOG(("Reading: sending request now with till %1."
|
||||
).arg(tillId));
|
||||
sendRequest(history, RequestType::ReadInbox, [=](Fn<void()> finish) {
|
||||
DEBUG_LOG(("Reading: sending request invoked with till %1."
|
||||
).arg(tillId));
|
||||
const auto finished = [=] {
|
||||
const auto state = lookup(history);
|
||||
Assert(state != nullptr);
|
||||
|
||||
@@ -13,7 +13,53 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
GeoPointLocation ComputeLocation(const Data::LocationPoint &point) {
|
||||
[[nodiscard]] QString AsString(float64 value) {
|
||||
constexpr auto kPrecision = 6;
|
||||
return QString::number(value, 'f', kPrecision);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
LocationPoint::LocationPoint(const MTPDgeoPoint &point)
|
||||
: _lat(point.vlat().v)
|
||||
, _lon(point.vlong().v)
|
||||
, _access(point.vaccess_hash().v) {
|
||||
}
|
||||
|
||||
QString LocationPoint::latAsString() const {
|
||||
return AsString(_lat);
|
||||
}
|
||||
|
||||
QString LocationPoint::lonAsString() const {
|
||||
return AsString(_lon);
|
||||
}
|
||||
|
||||
MTPGeoPoint LocationPoint::toMTP() const {
|
||||
return MTP_geoPoint(
|
||||
MTP_double(_lon),
|
||||
MTP_double(_lat),
|
||||
MTP_long(_access));
|
||||
}
|
||||
|
||||
float64 LocationPoint::lat() const {
|
||||
return _lat;
|
||||
}
|
||||
|
||||
float64 LocationPoint::lon() const {
|
||||
return _lon;
|
||||
}
|
||||
|
||||
uint64 LocationPoint::accessHash() const {
|
||||
return _access;
|
||||
}
|
||||
|
||||
size_t LocationPoint::hash() const {
|
||||
return QtPrivate::QHashCombine().operator()(
|
||||
std::hash<float64>()(_lat),
|
||||
_lon);
|
||||
}
|
||||
|
||||
GeoPointLocation ComputeLocation(const LocationPoint &point) {
|
||||
const auto scale = 1 + (cScale() * cIntRetinaFactor()) / 200;
|
||||
const auto zoom = 13 + (scale - 1);
|
||||
const auto w = st::locationSize.width() / scale;
|
||||
@@ -30,15 +76,4 @@ GeoPointLocation ComputeLocation(const Data::LocationPoint &point) {
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
LocationThumbnail::LocationThumbnail(const LocationPoint &point)
|
||||
: point(point)
|
||||
, thumb(Images::Create(ComputeLocation(point))) {
|
||||
}
|
||||
|
||||
void LocationThumbnail::load(FileOrigin origin) {
|
||||
thumb->load(origin);
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -14,58 +14,28 @@ struct FileOrigin;
|
||||
class LocationPoint {
|
||||
public:
|
||||
LocationPoint() = default;
|
||||
explicit LocationPoint(const MTPDgeoPoint &point)
|
||||
: _lat(point.vlat().v)
|
||||
, _lon(point.vlong().v)
|
||||
, _access(point.vaccess_hash().v) {
|
||||
}
|
||||
explicit LocationPoint(const MTPDgeoPoint &point);
|
||||
|
||||
QString latAsString() const {
|
||||
return AsString(_lat);
|
||||
}
|
||||
QString lonAsString() const {
|
||||
return AsString(_lon);
|
||||
}
|
||||
MTPGeoPoint toMTP() const {
|
||||
return MTP_geoPoint(
|
||||
MTP_double(_lon),
|
||||
MTP_double(_lat),
|
||||
MTP_long(_access));
|
||||
}
|
||||
[[nodiscard]] QString latAsString() const;
|
||||
[[nodiscard]] QString lonAsString() const;
|
||||
[[nodiscard]] MTPGeoPoint toMTP() const;
|
||||
|
||||
float64 lat() const {
|
||||
return _lat;
|
||||
}
|
||||
float64 lon() const {
|
||||
return _lon;
|
||||
}
|
||||
uint64 accessHash() const {
|
||||
return _access;
|
||||
}
|
||||
[[nodiscard]] float64 lat() const;
|
||||
[[nodiscard]] float64 lon() const;
|
||||
[[nodiscard]] uint64 accessHash() const;
|
||||
|
||||
inline size_t hash() const {
|
||||
#ifndef OS_MAC_OLD
|
||||
return QtPrivate::QHashCombine().operator()(
|
||||
std::hash<float64>()(_lat),
|
||||
_lon);
|
||||
#else // OS_MAC_OLD
|
||||
const auto h1 = std::hash<float64>()(_lat);
|
||||
const auto h2 = std::hash<float64>()(_lon);
|
||||
return ((h1 << 16) | (h1 >> 16)) ^ h2;
|
||||
#endif // OS_MAC_OLD
|
||||
}
|
||||
[[nodiscard]] size_t hash() const;
|
||||
|
||||
private:
|
||||
static QString AsString(float64 value) {
|
||||
constexpr auto kPrecision = 6;
|
||||
return QString::number(value, 'f', kPrecision);
|
||||
}
|
||||
|
||||
friend inline bool operator==(const LocationPoint &a, const LocationPoint &b) {
|
||||
friend inline bool operator==(
|
||||
const LocationPoint &a,
|
||||
const LocationPoint &b) {
|
||||
return (a._lat == b._lat) && (a._lon == b._lon);
|
||||
}
|
||||
|
||||
friend inline bool operator<(const LocationPoint &a, const LocationPoint &b) {
|
||||
friend inline bool operator<(
|
||||
const LocationPoint &a,
|
||||
const LocationPoint &b) {
|
||||
return (a._lat < b._lat) || ((a._lat == b._lat) && (a._lon < b._lon));
|
||||
}
|
||||
|
||||
@@ -75,15 +45,7 @@ private:
|
||||
|
||||
};
|
||||
|
||||
struct LocationThumbnail {
|
||||
LocationThumbnail(const LocationPoint &point);
|
||||
|
||||
LocationPoint point;
|
||||
ImagePtr thumb;
|
||||
|
||||
void load(FileOrigin origin);
|
||||
|
||||
};
|
||||
[[nodiscard]] GeoPointLocation ComputeLocation(const LocationPoint &point);
|
||||
|
||||
} // namespace Data
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/view/media/history_view_theme_document.h"
|
||||
#include "history/view/media/history_view_dice.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/image/image_source.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "ui/emoji_config.h"
|
||||
#include "storage/storage_shared_media.h"
|
||||
@@ -81,7 +80,9 @@ constexpr auto kFastRevokeRestriction = 24 * 60 * TimeId(60);
|
||||
result.title = TextUtilities::SingleLine(qs(data.vtitle()));
|
||||
result.receiptMsgId = data.vreceipt_msg_id().value_or_empty();
|
||||
if (const auto photo = data.vphoto()) {
|
||||
result.photo = item->history()->owner().photoFromWeb(*photo);
|
||||
result.photo = item->history()->owner().photoFromWeb(
|
||||
*photo,
|
||||
ImageLocation());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -171,7 +172,7 @@ const Invoice *Media::invoice() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
LocationThumbnail *Media::location() const {
|
||||
Data::CloudImage *Media::location() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -380,99 +381,6 @@ bool MediaPhoto::updateSentMedia(const MTPMessageMedia &media) {
|
||||
return false;
|
||||
}
|
||||
parent()->history()->owner().photoConvert(_photo, *content);
|
||||
|
||||
if (content->type() != mtpc_photo) {
|
||||
return false;
|
||||
}
|
||||
const auto &photo = content->c_photo();
|
||||
|
||||
struct SizeData {
|
||||
MTPstring type = MTP_string();
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
QByteArray bytes;
|
||||
};
|
||||
const auto saveImageToCache = [&](
|
||||
not_null<Image*> image,
|
||||
SizeData size) {
|
||||
Expects(!size.type.v.isEmpty());
|
||||
|
||||
const auto key = StorageImageLocation(
|
||||
StorageFileLocation(
|
||||
photo.vdc_id().v,
|
||||
_photo->session().userId(),
|
||||
MTP_inputPhotoFileLocation(
|
||||
photo.vid(),
|
||||
photo.vaccess_hash(),
|
||||
photo.vfile_reference(),
|
||||
size.type)),
|
||||
size.width,
|
||||
size.height);
|
||||
if (!key.valid() || image->isNull() || !image->loaded()) {
|
||||
return;
|
||||
}
|
||||
if (size.bytes.isEmpty()) {
|
||||
size.bytes = image->bytesForCache();
|
||||
}
|
||||
const auto length = size.bytes.size();
|
||||
if (!length || length > Storage::kMaxFileInMemory) {
|
||||
LOG(("App Error: Bad photo data for saving to cache."));
|
||||
return;
|
||||
}
|
||||
parent()->history()->owner().cache().putIfEmpty(
|
||||
key.file().cacheKey(),
|
||||
Storage::Cache::Database::TaggedValue(
|
||||
std::move(size.bytes),
|
||||
Data::kImageCacheTag));
|
||||
image->replaceSource(
|
||||
std::make_unique<Images::StorageSource>(key, length));
|
||||
};
|
||||
auto &sizes = photo.vsizes().v;
|
||||
auto max = 0;
|
||||
auto maxSize = SizeData();
|
||||
for (const auto &data : sizes) {
|
||||
const auto size = data.match([](const MTPDphotoSize &data) {
|
||||
return SizeData{
|
||||
data.vtype(),
|
||||
data.vw().v,
|
||||
data.vh().v,
|
||||
QByteArray()
|
||||
};
|
||||
}, [](const MTPDphotoCachedSize &data) {
|
||||
return SizeData{
|
||||
data.vtype(),
|
||||
data.vw().v,
|
||||
data.vh().v,
|
||||
qba(data.vbytes())
|
||||
};
|
||||
}, [](const MTPDphotoSizeEmpty &) {
|
||||
return SizeData();
|
||||
}, [](const MTPDphotoStrippedSize &data) {
|
||||
// No need to save stripped images to local cache.
|
||||
return SizeData();
|
||||
});
|
||||
const auto letter = size.type.v.isEmpty() ? char(0) : size.type.v[0];
|
||||
if (!letter) {
|
||||
continue;
|
||||
}
|
||||
if (letter == 's') {
|
||||
saveImageToCache(_photo->thumbnailSmall(), size);
|
||||
} else if (letter == 'm') {
|
||||
saveImageToCache(_photo->thumbnail(), size);
|
||||
} else if (letter == 'x' && max < 1) {
|
||||
max = 1;
|
||||
maxSize = size;
|
||||
} else if (letter == 'y' && max < 2) {
|
||||
max = 2;
|
||||
maxSize = size;
|
||||
//} else if (letter == 'w' && max < 3) {
|
||||
// max = 3;
|
||||
// maxSize = size;
|
||||
}
|
||||
}
|
||||
if (!maxSize.type.v.isEmpty()) {
|
||||
saveImageToCache(_photo->large(), maxSize);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -750,23 +658,6 @@ bool MediaFile::updateSentMedia(const MTPMessageMedia &media) {
|
||||
return false;
|
||||
}
|
||||
parent()->history()->owner().documentConvert(_document, *content);
|
||||
|
||||
if (const auto good = _document->goodThumbnail()) {
|
||||
auto bytes = good->bytesForCache();
|
||||
if (const auto length = bytes.size()) {
|
||||
if (length > Storage::kMaxFileInMemory) {
|
||||
LOG(("App Error: Bad thumbnail data for saving to cache."));
|
||||
} else {
|
||||
parent()->history()->owner().cache().putIfEmpty(
|
||||
_document->goodThumbnailCacheKey(),
|
||||
Storage::Cache::Database::TaggedValue(
|
||||
std::move(bytes),
|
||||
Data::kImageCacheTag));
|
||||
_document->refreshGoodThumbnail();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -890,6 +781,7 @@ MediaLocation::MediaLocation(
|
||||
const QString &title,
|
||||
const QString &description)
|
||||
: Media(parent)
|
||||
, _point(point)
|
||||
, _location(parent->history()->owner().location(point))
|
||||
, _title(title)
|
||||
, _description(description) {
|
||||
@@ -898,12 +790,12 @@ MediaLocation::MediaLocation(
|
||||
std::unique_ptr<Media> MediaLocation::clone(not_null<HistoryItem*> parent) {
|
||||
return std::make_unique<MediaLocation>(
|
||||
parent,
|
||||
_location->point,
|
||||
_point,
|
||||
_title,
|
||||
_description);
|
||||
}
|
||||
|
||||
LocationThumbnail *MediaLocation::location() const {
|
||||
Data::CloudImage *MediaLocation::location() const {
|
||||
return _location;
|
||||
}
|
||||
|
||||
@@ -934,7 +826,7 @@ TextForMimeData MediaLocation::clipboardText() const {
|
||||
if (!descriptionResult.text.isEmpty()) {
|
||||
result.append(std::move(descriptionResult));
|
||||
}
|
||||
result.append(LocationClickHandler(_location->point).dragText());
|
||||
result.append(LocationClickHandler(_point).dragText());
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -952,6 +844,7 @@ std::unique_ptr<HistoryView::Media> MediaLocation::createView(
|
||||
return std::make_unique<HistoryView::Location>(
|
||||
message,
|
||||
_location,
|
||||
_point,
|
||||
_title,
|
||||
_description);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "data/data_location.h"
|
||||
|
||||
class Image;
|
||||
class HistoryItem;
|
||||
|
||||
namespace base {
|
||||
@@ -27,8 +30,7 @@ class Media;
|
||||
|
||||
namespace Data {
|
||||
|
||||
class LocationPoint;
|
||||
struct LocationThumbnail;
|
||||
class CloudImage;
|
||||
|
||||
enum class CallFinishReason : char {
|
||||
Missed,
|
||||
@@ -77,7 +79,7 @@ public:
|
||||
virtual const Call *call() const;
|
||||
virtual GameData *game() const;
|
||||
virtual const Invoice *invoice() const;
|
||||
virtual LocationThumbnail *location() const;
|
||||
virtual Data::CloudImage *location() const;
|
||||
virtual PollData *poll() const;
|
||||
|
||||
virtual bool uploading() const;
|
||||
@@ -236,7 +238,7 @@ public:
|
||||
|
||||
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
|
||||
|
||||
LocationThumbnail *location() const override;
|
||||
Data::CloudImage *location() const override;
|
||||
QString chatListText() const override;
|
||||
QString notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
@@ -249,7 +251,8 @@ public:
|
||||
not_null<HistoryItem*> realParent) override;
|
||||
|
||||
private:
|
||||
not_null<LocationThumbnail*> _location;
|
||||
LocationPoint _point;
|
||||
not_null<Data::CloudImage*> _location;
|
||||
QString _title;
|
||||
QString _description;
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/history.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "history/history_item.h"
|
||||
#include "storage/file_download.h"
|
||||
#include "facades.h"
|
||||
#include "app.h"
|
||||
|
||||
@@ -108,8 +109,7 @@ void PeerClickHandler::onClick(ClickContext context) const {
|
||||
|
||||
PeerData::PeerData(not_null<Data::Session*> owner, PeerId id)
|
||||
: id(id)
|
||||
, _owner(owner)
|
||||
, _userpicEmpty(createEmptyUserpic()) {
|
||||
, _owner(owner) {
|
||||
_nameText.setText(st::msgNameStyle, QString(), Ui::NameTextOptions());
|
||||
}
|
||||
|
||||
@@ -145,7 +145,8 @@ void PeerData::updateNameDelayed(
|
||||
}
|
||||
name = newName;
|
||||
_nameText.setText(st::msgNameStyle, name, Ui::NameTextOptions());
|
||||
refreshEmptyUserpic();
|
||||
_userpicEmpty = nullptr;
|
||||
|
||||
Notify::PeerUpdate update(this);
|
||||
if (nameVersion++ > 1) {
|
||||
update.flags |= UpdateFlag::NameChanged;
|
||||
@@ -175,28 +176,22 @@ void PeerData::updateNameDelayed(
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Ui::EmptyUserpic> PeerData::createEmptyUserpic() const {
|
||||
return std::make_unique<Ui::EmptyUserpic>(
|
||||
Data::PeerUserpicColor(id),
|
||||
name);
|
||||
}
|
||||
|
||||
void PeerData::refreshEmptyUserpic() const {
|
||||
_userpicEmpty = useEmptyUserpic() ? createEmptyUserpic() : nullptr;
|
||||
not_null<Ui::EmptyUserpic*> PeerData::ensureEmptyUserpic() const {
|
||||
if (!_userpicEmpty) {
|
||||
_userpicEmpty = std::make_unique<Ui::EmptyUserpic>(
|
||||
Data::PeerUserpicColor(id),
|
||||
name);
|
||||
}
|
||||
return _userpicEmpty.get();
|
||||
}
|
||||
|
||||
ClickHandlerPtr PeerData::createOpenLink() {
|
||||
return std::make_shared<PeerClickHandler>(this);
|
||||
}
|
||||
|
||||
void PeerData::setUserpic(
|
||||
PhotoId photoId,
|
||||
const StorageImageLocation &location,
|
||||
ImagePtr userpic) {
|
||||
void PeerData::setUserpic(PhotoId photoId, const ImageLocation &location) {
|
||||
_userpicPhotoId = photoId;
|
||||
_userpic = userpic;
|
||||
_userpicLocation = location;
|
||||
refreshEmptyUserpic();
|
||||
_userpic.set(&session(), ImageWithLocation{ .location = location });
|
||||
}
|
||||
|
||||
void PeerData::setUserpicPhoto(const MTPPhoto &data) {
|
||||
@@ -213,102 +208,139 @@ void PeerData::setUserpicPhoto(const MTPPhoto &data) {
|
||||
}
|
||||
}
|
||||
|
||||
ImagePtr PeerData::currentUserpic() const {
|
||||
if (_userpic) {
|
||||
_userpic->load(userpicOrigin());
|
||||
if (_userpic->loaded()) {
|
||||
if (!useEmptyUserpic()) {
|
||||
_userpicEmpty = nullptr;
|
||||
}
|
||||
return _userpic;
|
||||
}
|
||||
Image *PeerData::currentUserpic(
|
||||
std::shared_ptr<Data::CloudImageView> &view) const {
|
||||
if (!_userpic.isCurrentView(view)) {
|
||||
view = _userpic.createView();
|
||||
_userpic.load(&session(), userpicOrigin());
|
||||
}
|
||||
if (!_userpicEmpty) {
|
||||
refreshEmptyUserpic();
|
||||
const auto image = view ? view->image() : nullptr;
|
||||
if (image) {
|
||||
_userpicEmpty = nullptr;
|
||||
} else if (isNotificationsUser()) {
|
||||
static auto result = Image(
|
||||
Core::App().logoNoMargin().scaledToWidth(
|
||||
kUserpicSize,
|
||||
Qt::SmoothTransformation));
|
||||
return &result;
|
||||
}
|
||||
return ImagePtr();
|
||||
return image;
|
||||
}
|
||||
|
||||
void PeerData::paintUserpic(Painter &p, int x, int y, int size) const {
|
||||
if (auto userpic = currentUserpic()) {
|
||||
p.drawPixmap(x, y, userpic->pixCircled(userpicOrigin(), size, size));
|
||||
void PeerData::paintUserpic(
|
||||
Painter &p,
|
||||
std::shared_ptr<Data::CloudImageView> &view,
|
||||
int x,
|
||||
int y,
|
||||
int size) const {
|
||||
if (const auto userpic = currentUserpic(view)) {
|
||||
p.drawPixmap(x, y, userpic->pixCircled(size, size));
|
||||
} else {
|
||||
_userpicEmpty->paint(p, x, y, x + size + x, size);
|
||||
ensureEmptyUserpic()->paint(p, x, y, x + size + x, size);
|
||||
}
|
||||
}
|
||||
|
||||
void PeerData::paintUserpicRounded(Painter &p, int x, int y, int size) const {
|
||||
if (auto userpic = currentUserpic()) {
|
||||
p.drawPixmap(x, y, userpic->pixRounded(userpicOrigin(), size, size, ImageRoundRadius::Small));
|
||||
void PeerData::paintUserpicRounded(
|
||||
Painter &p,
|
||||
std::shared_ptr<Data::CloudImageView> &view,
|
||||
int x,
|
||||
int y,
|
||||
int size) const {
|
||||
if (const auto userpic = currentUserpic(view)) {
|
||||
p.drawPixmap(x, y, userpic->pixRounded(size, size, ImageRoundRadius::Small));
|
||||
} else {
|
||||
_userpicEmpty->paintRounded(p, x, y, x + size + x, size);
|
||||
ensureEmptyUserpic()->paintRounded(p, x, y, x + size + x, size);
|
||||
}
|
||||
}
|
||||
|
||||
void PeerData::paintUserpicSquare(Painter &p, int x, int y, int size) const {
|
||||
if (auto userpic = currentUserpic()) {
|
||||
p.drawPixmap(x, y, userpic->pix(userpicOrigin(), size, size));
|
||||
void PeerData::paintUserpicSquare(
|
||||
Painter &p,
|
||||
std::shared_ptr<Data::CloudImageView> &view,
|
||||
int x,
|
||||
int y,
|
||||
int size) const {
|
||||
if (const auto userpic = currentUserpic(view)) {
|
||||
p.drawPixmap(x, y, userpic->pix(size, size));
|
||||
} else {
|
||||
_userpicEmpty->paintSquare(p, x, y, x + size + x, size);
|
||||
ensureEmptyUserpic()->paintSquare(p, x, y, x + size + x, size);
|
||||
}
|
||||
}
|
||||
|
||||
void PeerData::loadUserpic() {
|
||||
_userpic->load(userpicOrigin());
|
||||
_userpic.load(&session(), userpicOrigin());
|
||||
}
|
||||
|
||||
bool PeerData::userpicLoaded() const {
|
||||
return _userpic->loaded();
|
||||
bool PeerData::hasUserpic() const {
|
||||
return !_userpic.empty();
|
||||
}
|
||||
|
||||
bool PeerData::useEmptyUserpic() const {
|
||||
return !_userpicLocation.valid()
|
||||
|| !_userpic
|
||||
|| !_userpic->loaded();
|
||||
std::shared_ptr<Data::CloudImageView> PeerData::activeUserpicView() {
|
||||
return _userpic.empty() ? nullptr : _userpic.activeView();
|
||||
}
|
||||
|
||||
InMemoryKey PeerData::userpicUniqueKey() const {
|
||||
if (useEmptyUserpic()) {
|
||||
if (!_userpicEmpty) {
|
||||
refreshEmptyUserpic();
|
||||
}
|
||||
return _userpicEmpty->uniqueKey();
|
||||
std::shared_ptr<Data::CloudImageView> PeerData::createUserpicView() {
|
||||
if (_userpic.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
return inMemoryKey(_userpicLocation);
|
||||
auto result = _userpic.createView();
|
||||
_userpic.load(&session(), userpicPhotoOrigin());
|
||||
return result;
|
||||
}
|
||||
|
||||
void PeerData::saveUserpic(const QString &path, int size) const {
|
||||
genUserpic(size).save(path, "PNG");
|
||||
bool PeerData::useEmptyUserpic(
|
||||
std::shared_ptr<Data::CloudImageView> &view) const {
|
||||
return !currentUserpic(view);
|
||||
}
|
||||
|
||||
void PeerData::saveUserpicRounded(const QString &path, int size) const {
|
||||
genUserpicRounded(size).save(path, "PNG");
|
||||
InMemoryKey PeerData::userpicUniqueKey(
|
||||
std::shared_ptr<Data::CloudImageView> &view) const {
|
||||
return useEmptyUserpic(view)
|
||||
? ensureEmptyUserpic()->uniqueKey()
|
||||
: inMemoryKey(_userpic.location());
|
||||
}
|
||||
|
||||
QPixmap PeerData::genUserpic(int size) const {
|
||||
if (auto userpic = currentUserpic()) {
|
||||
return userpic->pixCircled(userpicOrigin(), size, size);
|
||||
void PeerData::saveUserpic(
|
||||
std::shared_ptr<Data::CloudImageView> &view,
|
||||
const QString &path,
|
||||
int size) const {
|
||||
genUserpic(view, size).save(path, "PNG");
|
||||
}
|
||||
|
||||
void PeerData::saveUserpicRounded(
|
||||
std::shared_ptr<Data::CloudImageView> &view,
|
||||
const QString &path,
|
||||
int size) const {
|
||||
genUserpicRounded(view, size).save(path, "PNG");
|
||||
}
|
||||
|
||||
QPixmap PeerData::genUserpic(
|
||||
std::shared_ptr<Data::CloudImageView> &view,
|
||||
int size) const {
|
||||
if (const auto userpic = currentUserpic(view)) {
|
||||
return userpic->pixCircled(size, size);
|
||||
}
|
||||
auto result = QImage(QSize(size, size) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
|
||||
result.setDevicePixelRatio(cRetinaFactor());
|
||||
result.fill(Qt::transparent);
|
||||
{
|
||||
Painter p(&result);
|
||||
paintUserpic(p, 0, 0, size);
|
||||
paintUserpic(p, view, 0, 0, size);
|
||||
}
|
||||
return App::pixmapFromImageInPlace(std::move(result));
|
||||
}
|
||||
|
||||
QPixmap PeerData::genUserpicRounded(int size) const {
|
||||
if (auto userpic = currentUserpic()) {
|
||||
return userpic->pixRounded(userpicOrigin(), size, size, ImageRoundRadius::Small);
|
||||
QPixmap PeerData::genUserpicRounded(
|
||||
std::shared_ptr<Data::CloudImageView> &view,
|
||||
int size) const {
|
||||
if (const auto userpic = currentUserpic(view)) {
|
||||
return userpic->pixRounded(size, size, ImageRoundRadius::Small);
|
||||
}
|
||||
auto result = QImage(QSize(size, size) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
|
||||
result.setDevicePixelRatio(cRetinaFactor());
|
||||
result.fill(Qt::transparent);
|
||||
{
|
||||
Painter p(&result);
|
||||
paintUserpicRounded(p, 0, 0, size);
|
||||
paintUserpicRounded(p, view, 0, 0, size);
|
||||
}
|
||||
return App::pixmapFromImageInPlace(std::move(result));
|
||||
}
|
||||
@@ -327,49 +359,31 @@ void PeerData::updateUserpic(
|
||||
PhotoId photoId,
|
||||
MTP::DcId dcId,
|
||||
const MTPFileLocation &location) {
|
||||
const auto size = kUserpicSize;
|
||||
const auto loc = location.match([&](
|
||||
setUserpicChecked(photoId, location.match([&](
|
||||
const MTPDfileLocationToBeDeprecated &deprecated) {
|
||||
return StorageImageLocation(
|
||||
StorageFileLocation(
|
||||
return ImageLocation(
|
||||
{ StorageFileLocation(
|
||||
dcId,
|
||||
isSelf() ? peerToUser(id) : 0,
|
||||
MTP_inputPeerPhotoFileLocation(
|
||||
MTP_flags(0),
|
||||
input,
|
||||
deprecated.vvolume_id(),
|
||||
deprecated.vlocal_id())),
|
||||
size,
|
||||
size);
|
||||
});
|
||||
setUserpicChecked(photoId, loc, Images::Create(loc));
|
||||
deprecated.vlocal_id())) },
|
||||
kUserpicSize,
|
||||
kUserpicSize);
|
||||
}));
|
||||
}
|
||||
|
||||
void PeerData::clearUserpic() {
|
||||
const auto photoId = PhotoId(0);
|
||||
const auto loc = StorageImageLocation();
|
||||
const auto photo = [&] {
|
||||
if (isNotificationsUser()) {
|
||||
auto image = Core::App().logoNoMargin().scaledToWidth(
|
||||
kUserpicSize,
|
||||
Qt::SmoothTransformation);
|
||||
return _userpic
|
||||
? _userpic
|
||||
: Images::Create(std::move(image), "PNG");
|
||||
}
|
||||
return ImagePtr();
|
||||
}();
|
||||
setUserpicChecked(photoId, loc, photo);
|
||||
setUserpicChecked(PhotoId(), ImageLocation());
|
||||
}
|
||||
|
||||
void PeerData::setUserpicChecked(
|
||||
PhotoId photoId,
|
||||
const StorageImageLocation &location,
|
||||
ImagePtr userpic) {
|
||||
if (_userpicPhotoId != photoId
|
||||
|| _userpic.get() != userpic.get()
|
||||
|| _userpicLocation != location) {
|
||||
setUserpic(photoId, location, userpic);
|
||||
const ImageLocation &location) {
|
||||
if (_userpicPhotoId != photoId || _userpic.location() != location) {
|
||||
setUserpic(photoId, location);
|
||||
Notify::peerUpdatedDelayed(this, UpdateFlag::PhotoChanged);
|
||||
//if (const auto channel = asChannel()) { // #feed
|
||||
// if (const auto feed = channel->feed()) {
|
||||
@@ -454,6 +468,9 @@ bool PeerData::canExportChatHistory() const {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (const auto from = migrateFrom()) {
|
||||
return from->canExportChatHistory();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -770,7 +787,7 @@ int PeerData::slowmodeSecondsLeft() const {
|
||||
|
||||
bool PeerData::canSendPolls() const {
|
||||
if (const auto user = asUser()) {
|
||||
return user->isBot();
|
||||
return user->isBot() && !user->isSupport();
|
||||
} else if (const auto chat = asChat()) {
|
||||
return chat->canSendPolls();
|
||||
} else if (const auto channel = asChannel()) {
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_types.h"
|
||||
#include "data/data_flags.h"
|
||||
#include "data/data_notify_settings.h"
|
||||
#include "data/data_cloud_file.h"
|
||||
|
||||
class PeerData;
|
||||
class UserData;
|
||||
@@ -43,6 +44,8 @@ using ChatRestrictions = MTPDchatBannedRights::Flags;
|
||||
|
||||
namespace Data {
|
||||
|
||||
class CloudImageView;
|
||||
|
||||
class RestrictionCheckResult {
|
||||
public:
|
||||
[[nodiscard]] static RestrictionCheckResult Allowed() {
|
||||
@@ -234,44 +237,59 @@ public:
|
||||
return _nameFirstLetters;
|
||||
}
|
||||
|
||||
void setUserpic(
|
||||
PhotoId photoId,
|
||||
const StorageImageLocation &location,
|
||||
ImagePtr userpic);
|
||||
void setUserpic(PhotoId photoId, const ImageLocation &location);
|
||||
void setUserpicPhoto(const MTPPhoto &data);
|
||||
void paintUserpic(
|
||||
Painter &p,
|
||||
std::shared_ptr<Data::CloudImageView> &view,
|
||||
int x,
|
||||
int y,
|
||||
int size) const;
|
||||
void paintUserpicLeft(
|
||||
Painter &p,
|
||||
std::shared_ptr<Data::CloudImageView> &view,
|
||||
int x,
|
||||
int y,
|
||||
int w,
|
||||
int size) const {
|
||||
paintUserpic(p, rtl() ? (w - x - size) : x, y, size);
|
||||
paintUserpic(p, view, rtl() ? (w - x - size) : x, y, size);
|
||||
}
|
||||
void paintUserpicRounded(
|
||||
Painter &p,
|
||||
std::shared_ptr<Data::CloudImageView> &view,
|
||||
int x,
|
||||
int y,
|
||||
int size) const;
|
||||
void paintUserpicSquare(
|
||||
Painter &p,
|
||||
std::shared_ptr<Data::CloudImageView> &view,
|
||||
int x,
|
||||
int y,
|
||||
int size) const;
|
||||
void loadUserpic();
|
||||
[[nodiscard]] bool userpicLoaded() const;
|
||||
[[nodiscard]] bool useEmptyUserpic() const;
|
||||
[[nodiscard]] InMemoryKey userpicUniqueKey() const;
|
||||
void saveUserpic(const QString &path, int size) const;
|
||||
void saveUserpicRounded(const QString &path, int size) const;
|
||||
[[nodiscard]] QPixmap genUserpic(int size) const;
|
||||
[[nodiscard]] QPixmap genUserpicRounded(int size) const;
|
||||
[[nodiscard]] StorageImageLocation userpicLocation() const {
|
||||
return _userpicLocation;
|
||||
[[nodiscard]] bool hasUserpic() const;
|
||||
[[nodiscard]] std::shared_ptr<Data::CloudImageView> activeUserpicView();
|
||||
[[nodiscard]] std::shared_ptr<Data::CloudImageView> createUserpicView();
|
||||
[[nodiscard]] bool useEmptyUserpic(
|
||||
std::shared_ptr<Data::CloudImageView> &view) const;
|
||||
[[nodiscard]] InMemoryKey userpicUniqueKey(
|
||||
std::shared_ptr<Data::CloudImageView> &view) const;
|
||||
void saveUserpic(
|
||||
std::shared_ptr<Data::CloudImageView> &view,
|
||||
const QString &path,
|
||||
int size) const;
|
||||
void saveUserpicRounded(
|
||||
std::shared_ptr<Data::CloudImageView> &view,
|
||||
const QString &path,
|
||||
int size) const;
|
||||
[[nodiscard]] QPixmap genUserpic(
|
||||
std::shared_ptr<Data::CloudImageView> &view,
|
||||
int size) const;
|
||||
[[nodiscard]] QPixmap genUserpicRounded(
|
||||
std::shared_ptr<Data::CloudImageView> &view,
|
||||
int size) const;
|
||||
[[nodiscard]] ImageLocation userpicLocation() const {
|
||||
return _userpic.location();
|
||||
}
|
||||
[[nodiscard]] bool userpicPhotoUnknown() const {
|
||||
return (_userpicPhotoId == kUnknownPhotoId);
|
||||
@@ -294,7 +312,8 @@ public:
|
||||
return _openLink;
|
||||
}
|
||||
|
||||
[[nodiscard]] ImagePtr currentUserpic() const;
|
||||
[[nodiscard]] Image *currentUserpic(
|
||||
std::shared_ptr<Data::CloudImageView> &view) const;
|
||||
|
||||
[[nodiscard]] bool canPinMessages() const;
|
||||
[[nodiscard]] bool canEditMessagesIndefinitely() const;
|
||||
@@ -356,24 +375,19 @@ protected:
|
||||
|
||||
private:
|
||||
void fillNames();
|
||||
std::unique_ptr<Ui::EmptyUserpic> createEmptyUserpic() const;
|
||||
void refreshEmptyUserpic() const;
|
||||
[[nodiscard]] not_null<Ui::EmptyUserpic*> ensureEmptyUserpic() const;
|
||||
[[nodiscard]] virtual auto unavailableReasons() const
|
||||
-> const std::vector<Data::UnavailableReason> &;
|
||||
|
||||
void setUserpicChecked(
|
||||
PhotoId photoId,
|
||||
const StorageImageLocation &location,
|
||||
ImagePtr userpic);
|
||||
void setUserpicChecked(PhotoId photoId, const ImageLocation &location);
|
||||
|
||||
static constexpr auto kUnknownPhotoId = PhotoId(0xFFFFFFFFFFFFFFFFULL);
|
||||
|
||||
const not_null<Data::Session*> _owner;
|
||||
|
||||
ImagePtr _userpic;
|
||||
mutable Data::CloudImage _userpic;
|
||||
PhotoId _userpicPhotoId = kUnknownPhotoId;
|
||||
mutable std::unique_ptr<Ui::EmptyUserpic> _userpicEmpty;
|
||||
StorageImageLocation _userpicLocation;
|
||||
Ui::Text::String _nameText;
|
||||
|
||||
Data::NotifySettings _notify;
|
||||
|
||||
@@ -9,19 +9,38 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_reply_preview.h"
|
||||
#include "data/data_photo_media.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/image/image_source.h"
|
||||
#include "main/main_session.h"
|
||||
#include "mainwidget.h"
|
||||
#include "storage/file_download.h"
|
||||
#include "core/application.h"
|
||||
#include "facades.h"
|
||||
#include "app.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kPhotoSideLimit = 1280;
|
||||
|
||||
using Data::PhotoMedia;
|
||||
using Data::PhotoSize;
|
||||
using Data::PhotoSizeIndex;
|
||||
using Data::kPhotoSizeCount;
|
||||
|
||||
} // namespace
|
||||
|
||||
PhotoData::PhotoData(not_null<Data::Session*> owner, PhotoId id)
|
||||
: id(id)
|
||||
, _owner(owner) {
|
||||
}
|
||||
|
||||
PhotoData::~PhotoData() {
|
||||
for (auto &image : _images) {
|
||||
base::take(image.loader).reset();
|
||||
}
|
||||
}
|
||||
|
||||
Data::Session &PhotoData::owner() const {
|
||||
return *_owner;
|
||||
}
|
||||
@@ -30,55 +49,104 @@ Main::Session &PhotoData::session() const {
|
||||
return _owner->session();
|
||||
}
|
||||
|
||||
void PhotoData::automaticLoad(
|
||||
Data::FileOrigin origin,
|
||||
const HistoryItem *item) {
|
||||
_large->automaticLoad(origin, item);
|
||||
}
|
||||
|
||||
void PhotoData::automaticLoadSettingsChanged() {
|
||||
_large->automaticLoadSettingsChanged();
|
||||
}
|
||||
|
||||
void PhotoData::download(Data::FileOrigin origin) {
|
||||
_large->loadEvenCancelled(origin);
|
||||
_owner->notifyPhotoLayoutChanged(this);
|
||||
}
|
||||
|
||||
bool PhotoData::loaded() const {
|
||||
bool wasLoading = loading();
|
||||
if (_large->loaded()) {
|
||||
if (wasLoading) {
|
||||
_owner->notifyPhotoLayoutChanged(this);
|
||||
}
|
||||
return true;
|
||||
const auto index = PhotoSizeIndex(PhotoSize::Large);
|
||||
if (!(_images[index].flags & Data::CloudFile::Flag::Cancelled)) {
|
||||
return;
|
||||
}
|
||||
return false;
|
||||
_images[index].loader = nullptr;
|
||||
_images[index].flags &= ~Data::CloudFile::Flag::Cancelled;
|
||||
}
|
||||
|
||||
void PhotoData::load(
|
||||
Data::FileOrigin origin,
|
||||
LoadFromCloudSetting fromCloud,
|
||||
bool autoLoading) {
|
||||
load(PhotoSize::Large, origin, fromCloud, autoLoading);
|
||||
}
|
||||
|
||||
bool PhotoData::loading() const {
|
||||
return _large->loading();
|
||||
return loading(PhotoSize::Large);
|
||||
}
|
||||
|
||||
int PhotoData::validSizeIndex(PhotoSize size) const {
|
||||
const auto index = PhotoSizeIndex(size);
|
||||
for (auto i = index; i != kPhotoSizeCount; ++i) {
|
||||
if (_images[i].location.valid()) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return PhotoSizeIndex(PhotoSize::Large);
|
||||
}
|
||||
|
||||
bool PhotoData::hasExact(PhotoSize size) const {
|
||||
return _images[PhotoSizeIndex(size)].location.valid();
|
||||
}
|
||||
|
||||
bool PhotoData::loading(PhotoSize size) const {
|
||||
return (_images[validSizeIndex(size)].loader != nullptr);
|
||||
}
|
||||
|
||||
bool PhotoData::failed(PhotoSize size) const {
|
||||
const auto flags = _images[validSizeIndex(size)].flags;
|
||||
return (flags & Data::CloudFile::Flag::Failed);
|
||||
}
|
||||
|
||||
const ImageLocation &PhotoData::location(PhotoSize size) const {
|
||||
return _images[validSizeIndex(size)].location;
|
||||
}
|
||||
|
||||
int PhotoData::SideLimit() {
|
||||
return kPhotoSideLimit;
|
||||
}
|
||||
|
||||
std::optional<QSize> PhotoData::size(PhotoSize size) const {
|
||||
const auto &provided = location(size);
|
||||
const auto result = QSize{ provided.width(), provided.height() };
|
||||
const auto limit = SideLimit();
|
||||
if (result.isEmpty()) {
|
||||
return std::nullopt;
|
||||
} else if (result.width() <= limit && result.height() <= limit) {
|
||||
return result;
|
||||
}
|
||||
const auto scaled = result.scaled(limit, limit, Qt::KeepAspectRatio);
|
||||
return QSize(std::max(scaled.width(), 1), std::max(scaled.height(), 1));
|
||||
}
|
||||
|
||||
int PhotoData::imageByteSize(PhotoSize size) const {
|
||||
return _images[validSizeIndex(size)].byteSize;
|
||||
}
|
||||
|
||||
bool PhotoData::displayLoading() const {
|
||||
return _large->loading()
|
||||
? _large->displayLoading()
|
||||
const auto index = PhotoSizeIndex(PhotoSize::Large);
|
||||
return _images[index].loader
|
||||
? (!_images[index].loader->loadingLocal()
|
||||
|| !_images[index].loader->autoLoading())
|
||||
: (uploading() && !waitingForAlbum());
|
||||
}
|
||||
|
||||
void PhotoData::cancel() {
|
||||
_large->cancel();
|
||||
_owner->notifyPhotoLayoutChanged(this);
|
||||
if (loading()) {
|
||||
_images[PhotoSizeIndex(PhotoSize::Large)].loader->cancel();
|
||||
}
|
||||
}
|
||||
|
||||
float64 PhotoData::progress() const {
|
||||
if (uploading()) {
|
||||
if (uploadingData->size > 0) {
|
||||
return float64(uploadingData->offset) / uploadingData->size;
|
||||
const auto result = float64(uploadingData->offset)
|
||||
/ uploadingData->size;
|
||||
return snap(result, 0., 1.);
|
||||
}
|
||||
return 0;
|
||||
return 0.;
|
||||
}
|
||||
return _large->progress();
|
||||
const auto index = PhotoSizeIndex(PhotoSize::Large);
|
||||
return loading() ? _images[index].loader->currentProgress() : 0.;
|
||||
}
|
||||
|
||||
bool PhotoData::cancelled() const {
|
||||
const auto index = PhotoSizeIndex(PhotoSize::Large);
|
||||
return (_images[index].flags & Data::CloudFile::Flag::Cancelled);
|
||||
}
|
||||
|
||||
void PhotoData::setWaitingForAlbum() {
|
||||
@@ -92,50 +160,19 @@ bool PhotoData::waitingForAlbum() const {
|
||||
}
|
||||
|
||||
int32 PhotoData::loadOffset() const {
|
||||
return _large->loadOffset();
|
||||
const auto index = PhotoSizeIndex(PhotoSize::Large);
|
||||
return loading() ? _images[index].loader->currentOffset() : 0;
|
||||
}
|
||||
|
||||
bool PhotoData::uploading() const {
|
||||
return (uploadingData != nullptr);
|
||||
}
|
||||
|
||||
void PhotoData::unload() {
|
||||
// Forget thumbnail only when image cache limit exceeds.
|
||||
//_thumbnailInline->unload();
|
||||
_thumbnailSmall->unload();
|
||||
_thumbnail->unload();
|
||||
_large->unload();
|
||||
_replyPreview.clear();
|
||||
}
|
||||
|
||||
Image *PhotoData::getReplyPreview(Data::FileOrigin origin) {
|
||||
if (_replyPreview
|
||||
&& (_replyPreview.good() || !_thumbnailSmall->loaded())) {
|
||||
return _replyPreview.image();
|
||||
if (!_replyPreview) {
|
||||
_replyPreview = std::make_unique<Data::ReplyPreview>(this);
|
||||
}
|
||||
if (_thumbnailSmall->isDelayedStorageImage()
|
||||
&& !_large->isNull()
|
||||
&& !_large->isDelayedStorageImage()
|
||||
&& _large->loaded()) {
|
||||
_replyPreview.prepare(
|
||||
_large.get(),
|
||||
origin,
|
||||
Images::Option(0));
|
||||
} else if (_thumbnailSmall->loaded()) {
|
||||
_replyPreview.prepare(
|
||||
_thumbnailSmall.get(),
|
||||
origin,
|
||||
Images::Option(0));
|
||||
} else {
|
||||
_thumbnailSmall->load(origin);
|
||||
if (_thumbnailInline) {
|
||||
_replyPreview.prepare(
|
||||
_thumbnailInline.get(),
|
||||
origin,
|
||||
Images::Option::Blurred);
|
||||
}
|
||||
}
|
||||
return _replyPreview.image();
|
||||
return _replyPreview->image(origin);
|
||||
}
|
||||
|
||||
void PhotoData::setRemoteLocation(
|
||||
@@ -162,9 +199,9 @@ QByteArray PhotoData::fileReference() const {
|
||||
|
||||
void PhotoData::refreshFileReference(const QByteArray &value) {
|
||||
_fileReference = value;
|
||||
_thumbnailSmall->refreshFileReference(value);
|
||||
_thumbnail->refreshFileReference(value);
|
||||
_large->refreshFileReference(value);
|
||||
for (auto &image : _images) {
|
||||
image.location.refreshFileReference(value);
|
||||
}
|
||||
}
|
||||
|
||||
void PhotoData::collectLocalData(not_null<PhotoData*> local) {
|
||||
@@ -172,83 +209,108 @@ void PhotoData::collectLocalData(not_null<PhotoData*> local) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto copyImage = [&](const ImagePtr &src, const ImagePtr &dst) {
|
||||
if (const auto from = src->cacheKey()) {
|
||||
if (const auto to = dst->cacheKey()) {
|
||||
_owner->cache().copyIfEmpty(*from, *to);
|
||||
for (auto i = 0; i != kPhotoSizeCount; ++i) {
|
||||
if (const auto from = local->_images[i].location.file().cacheKey()) {
|
||||
if (const auto to = _images[i].location.file().cacheKey()) {
|
||||
_owner->cache().copyIfEmpty(from, to);
|
||||
}
|
||||
}
|
||||
};
|
||||
copyImage(local->_thumbnailSmall, _thumbnailSmall);
|
||||
copyImage(local->_thumbnail, _thumbnail);
|
||||
copyImage(local->_large, _large);
|
||||
}
|
||||
if (const auto localMedia = local->activeMediaView()) {
|
||||
auto media = createMediaView();
|
||||
media->collectLocalData(localMedia.get());
|
||||
_owner->keepAlive(std::move(media));
|
||||
}
|
||||
}
|
||||
|
||||
bool PhotoData::isNull() const {
|
||||
return _large->isNull();
|
||||
return !_images[PhotoSizeIndex(PhotoSize::Large)].location.valid();
|
||||
}
|
||||
|
||||
void PhotoData::loadThumbnail(Data::FileOrigin origin) {
|
||||
_thumbnail->load(origin);
|
||||
void PhotoData::load(
|
||||
PhotoSize size,
|
||||
Data::FileOrigin origin,
|
||||
LoadFromCloudSetting fromCloud,
|
||||
bool autoLoading) {
|
||||
const auto index = validSizeIndex(size);
|
||||
auto &image = _images[index];
|
||||
|
||||
// Could've changed, if the requested size didn't have a location.
|
||||
const auto loadingSize = static_cast<PhotoSize>(index);
|
||||
const auto cacheTag = Data::kImageCacheTag;
|
||||
Data::LoadCloudFile(image, origin, fromCloud, autoLoading, cacheTag, [=] {
|
||||
if (const auto active = activeMediaView()) {
|
||||
return !active->image(size);
|
||||
}
|
||||
return true;
|
||||
}, [=](QImage result) {
|
||||
if (const auto active = activeMediaView()) {
|
||||
active->set(loadingSize, std::move(result));
|
||||
}
|
||||
if (loadingSize == PhotoSize::Large) {
|
||||
_owner->photoLoadDone(this);
|
||||
}
|
||||
}, [=](bool started) {
|
||||
if (loadingSize == PhotoSize::Large) {
|
||||
_owner->photoLoadFail(this, started);
|
||||
}
|
||||
}, [=] {
|
||||
if (loadingSize == PhotoSize::Large) {
|
||||
_owner->photoLoadProgress(this);
|
||||
}
|
||||
});
|
||||
|
||||
if (size == PhotoSize::Large) {
|
||||
_owner->notifyPhotoLayoutChanged(this);
|
||||
}
|
||||
}
|
||||
|
||||
void PhotoData::loadThumbnailSmall(Data::FileOrigin origin) {
|
||||
_thumbnailSmall->load(origin);
|
||||
std::shared_ptr<PhotoMedia> PhotoData::createMediaView() {
|
||||
if (auto result = activeMediaView()) {
|
||||
return result;
|
||||
}
|
||||
auto result = std::make_shared<PhotoMedia>(this);
|
||||
_media = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
Image *PhotoData::thumbnailInline() const {
|
||||
return _thumbnailInline ? _thumbnailInline.get() : nullptr;
|
||||
}
|
||||
|
||||
not_null<Image*> PhotoData::thumbnailSmall() const {
|
||||
return _thumbnailSmall.get();
|
||||
}
|
||||
|
||||
not_null<Image*> PhotoData::thumbnail() const {
|
||||
return _thumbnail.get();
|
||||
}
|
||||
|
||||
void PhotoData::load(Data::FileOrigin origin) {
|
||||
_large->load(origin);
|
||||
}
|
||||
|
||||
not_null<Image*> PhotoData::large() const {
|
||||
return _large.get();
|
||||
std::shared_ptr<PhotoMedia> PhotoData::activeMediaView() const {
|
||||
return _media.lock();
|
||||
}
|
||||
|
||||
void PhotoData::updateImages(
|
||||
ImagePtr thumbnailInline,
|
||||
ImagePtr thumbnailSmall,
|
||||
ImagePtr thumbnail,
|
||||
ImagePtr large) {
|
||||
if (!thumbnailSmall || !thumbnail || !large) {
|
||||
return;
|
||||
const QByteArray &inlineThumbnailBytes,
|
||||
const ImageWithLocation &small,
|
||||
const ImageWithLocation &thumbnail,
|
||||
const ImageWithLocation &large) {
|
||||
if (!inlineThumbnailBytes.isEmpty()
|
||||
&& _inlineThumbnailBytes.isEmpty()) {
|
||||
_inlineThumbnailBytes = inlineThumbnailBytes;
|
||||
}
|
||||
if (thumbnailInline && !_thumbnailInline) {
|
||||
_thumbnailInline = thumbnailInline;
|
||||
}
|
||||
const auto update = [](ImagePtr &was, ImagePtr now) {
|
||||
if (!was) {
|
||||
was = now;
|
||||
} else if (was->isDelayedStorageImage()) {
|
||||
if (const auto location = now->location(); location.valid()) {
|
||||
was->setDelayedStorageLocation(
|
||||
Data::FileOrigin(),
|
||||
location);
|
||||
}
|
||||
}
|
||||
const auto update = [&](PhotoSize size, const ImageWithLocation &data) {
|
||||
Data::UpdateCloudFile(
|
||||
_images[PhotoSizeIndex(size)],
|
||||
data,
|
||||
owner().cache(),
|
||||
Data::kImageCacheTag,
|
||||
[=](Data::FileOrigin origin) { load(size, origin); },
|
||||
[=](QImage preloaded) {
|
||||
if (const auto media = activeMediaView()) {
|
||||
media->set(size, data.preloaded);
|
||||
}
|
||||
});
|
||||
};
|
||||
update(_thumbnailSmall, thumbnailSmall);
|
||||
update(_thumbnail, thumbnail);
|
||||
update(_large, large);
|
||||
update(PhotoSize::Small, small);
|
||||
update(PhotoSize::Thumbnail, thumbnail);
|
||||
update(PhotoSize::Large, large);
|
||||
}
|
||||
|
||||
int PhotoData::width() const {
|
||||
return _large->width();
|
||||
return _images[PhotoSizeIndex(PhotoSize::Large)].location.width();
|
||||
}
|
||||
|
||||
int PhotoData::height() const {
|
||||
return _large->height();
|
||||
return _images[PhotoSizeIndex(PhotoSize::Large)].location.height();
|
||||
}
|
||||
|
||||
PhotoClickHandler::PhotoClickHandler(
|
||||
@@ -275,7 +337,7 @@ void PhotoSaveClickHandler::onClickImpl() const {
|
||||
if (!data->date) {
|
||||
return;
|
||||
} else {
|
||||
data->download(context());
|
||||
data->load(context());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user