Compare commits

...

154 Commits

Author SHA1 Message Date
John Preston
d0994019ca Beta version 2.1.9: Fix 'edited' field export.
Export 'edited' only if the message was edited.
2020-06-04 18:17:50 +04:00
John Preston
23f94c61a4 Beta version 2.1.9.
- Several crash fixes.
2020-06-04 17:32:10 +04:00
John Preston
2b9e4a8ddf Simplify playing video tracking (and fix a crash). 2020-06-04 17:26:11 +04:00
John Preston
e1d36cfd50 Fix crash in the EditCaptionBox. 2020-06-04 17:26:11 +04:00
John Preston
fbb2bae99f Fix crash in OS X 10.10 / 10.11. 2020-06-04 13:24:38 +04:00
John Preston
6bc7fa9ef4 Fix crash in saving of a document. 2020-06-04 12:22:37 +04:00
John Preston
bf06d4d545 Fix crash in stickers box. 2020-06-04 12:16:56 +04:00
John Preston
bfafdd5b38 Fix crash in streaming+loading of a document. 2020-06-04 12:16:44 +04:00
John Preston
f581a15b6e Fix crash in PiP window. 2020-06-04 11:00:59 +04:00
John Preston
c868cd6036 Update lib_ui. 2020-06-04 10:53:59 +04:00
John Preston
9d1a4cdbfe Beta version 2.1.8: Fix build on 64 bit systems. 2020-06-03 16:18:03 +04:00
John Preston
383e6dec43 Beta version 2.1.8.
- Add support for full group message history export.
- Allow export of a single chat message history in JSON format.
2020-06-03 15:51:27 +04:00
John Preston
85904e3022 Update submodules. 2020-06-03 15:51:15 +04:00
John Preston
f88b97553e Fix crash in destructor of Data::CloudFile. 2020-06-03 13:48:11 +04:00
John Preston
63c6a1db82 Allow removing users invited by me. 2020-06-03 12:57:36 +04:00
John Preston
ca97e3c375 Add more warnings for suspicious urls. 2020-06-03 12:44:46 +04:00
John Preston
ef30c776bf Fix visual glitch in filter change from archived chat. 2020-06-03 12:31:15 +04:00
Ilya Fedin
d45e74619d Use Platform::IsWayland from lib_base 2020-06-03 11:43:55 +04:00
Ilya Fedin
d92b5eebcc Restore X error handler just like qgtk3 2020-06-03 11:31:34 +04:00
Ilya Fedin
5c6b4d95b0 Suppress warning about transient parent when opening gtk file dialog 2020-06-03 11:31:34 +04:00
Ilya Fedin
0fbec5eba1 Use QVersionNumber to compare version in native notifications 2020-06-03 11:31:34 +04:00
Ilya Fedin
ab13d9bdaf Skip empty parts in QT_QPA_PLATFORMTHEME 2020-06-03 11:31:34 +04:00
Ilya Fedin
0165e31ca7 Never use custom code for portal detecting in flatpak 2020-06-03 11:31:34 +04:00
Ilya Fedin
f1e75d809a Separate patches 2020-06-03 11:31:34 +04:00
Ilya Fedin
c776f81dc7 Add support for choosing directories via xdg-desktop-portal 2020-06-03 11:31:34 +04:00
John Preston
9fd62d3892 Add more deprecated system versions. 2020-06-02 22:26:59 +04:00
John Preston
793906ca9a Fix build on Windows. 2020-06-02 12:26:58 +04:00
23rd
35e575c2d7 Fixed build for macOS. 2020-06-01 19:28:19 +03:00
23rd
f5e84220eb Fixed crash in context menu for uploading scheduled messages. 2020-06-01 17:55:22 +03:00
Ilya Fedin
1d622fb3c0 Add patches with the fix for https://github.com/telegramdesktop/tdesktop/issues/6645 2020-06-01 18:43:42 +04:00
Nicholas Guriev
d8d3dda2f3 Fix little typo in theme names generator 2020-06-01 18:26:16 +04:00
Ilya Fedin
e098922a4b Add Platform::AutostartSupported 2020-06-01 18:25:21 +04:00
Ilya Fedin
413ddf285e Fix crash in gtk file dialog on Wayland 2020-06-01 18:22:53 +04:00
Ilya Fedin
7ac78be984 Load gtk2 even on Wayland 2020-06-01 18:22:53 +04:00
Ilya Fedin
4c546156da Remove duplicate log line 2020-06-01 18:22:53 +04:00
Ilya Fedin
db528b39e1 Fix macOS cache validating
macOS action has runner version in the workdir path, it should be a part of the cache key
2020-06-01 18:21:52 +04:00
Ilya Fedin
586744c112 Apply sway fixes to the PiP and export windows too 2020-06-01 18:21:30 +04:00
Ilya Fedin
7b106761be Remove cache from snap action since it works not so good 2020-06-01 18:19:34 +04:00
Ilya Fedin
8fb7f0fc73 Use TDESKTOP_USE_GTK_FILE_DIALOG in snap 2020-06-01 18:19:34 +04:00
Ilya Fedin
10b169f9f6 Make not supported errors static 2020-06-01 18:19:34 +04:00
Ilya Fedin
c83b8d4043 Fix naming of static variables 2020-06-01 18:19:34 +04:00
Ilya Fedin
1fc2b19c94 Add Cinnamon sound settings command 2020-06-01 18:19:34 +04:00
Ilya Fedin
fb97940cac Rename SandboxAutostart to PortalAutostart 2020-06-01 18:19:34 +04:00
Ilya Fedin
16c38b54e2 Rename InSandbox to InFlatpak 2020-06-01 18:19:34 +04:00
Ilya Fedin
7f29f57c3d Use custom gtk file dialog only on gtk-based DEs 2020-06-01 18:19:34 +04:00
Ilya Fedin
1fb1d57a27 Get system icon theme on gtk-based DEs 2020-06-01 18:19:34 +04:00
Ilya Fedin
47d7bd95ae Add a method to check if gtk integration is forced 2020-06-01 18:19:34 +04:00
John Preston
368eeaf754 Improve single chat export progress display. 2020-06-01 18:09:34 +04:00
John Preston
1686eb394d Add support for JSON single-chat export. 2020-06-01 18:09:34 +04:00
John Preston
02586ebe4b Allow export of just-converted supergroup. 2020-06-01 18:09:34 +04:00
John Preston
8f80c19ae1 Merge old group with supergroup history in export. 2020-06-01 18:09:34 +04:00
John Preston
1598165e2b Closed alpha version 2.1.7.4. 2020-06-01 18:09:34 +04:00
John Preston
f4cd84c313 Fix crash in stickers. 2020-06-01 18:09:34 +04:00
John Preston
9b574e497d Closed alpha version 2.1.7.3. 2020-06-01 18:09:34 +04:00
John Preston
6660338ccc Fix crash in sticker sets without a thumbnail. 2020-06-01 18:09:34 +04:00
John Preston
423ea5b499 Fix crash on invalid image data. 2020-06-01 18:09:34 +04:00
John Preston
4695ebae6e Fix crash in sticker set parsing. 2020-06-01 18:09:34 +04:00
John Preston
aaa4db7b27 Fix a crash in custom notifications. 2020-06-01 18:09:34 +04:00
John Preston
0965b06fa3 Closed alpha version 2.1.7.2. 2020-06-01 18:09:34 +04:00
Ilya Fedin
be96bf2812 Set parent for dialogs only on Wayland 2020-06-01 18:09:34 +04:00
John Preston
b7aa60bedf Fix build for Linux. 2020-06-01 18:09:34 +04:00
John Preston
d5b3fa017b Fix build for macOS. 2020-06-01 18:09:34 +04:00
John Preston
36fbdfb380 Simplify Image, remove ImageSource. 2020-06-01 18:09:33 +04:00
John Preston
d0c78eaddd Leave only one image source type. 2020-06-01 18:09:33 +04:00
John Preston
6513422e40 Remove legacy image-related code. 2020-06-01 18:09:33 +04:00
John Preston
f066e0f05a Use Data::CloudImage for userpics. 2020-06-01 18:09:33 +04:00
John Preston
249f7813c1 Don't hold session pointer in Data::CloudImage. 2020-06-01 18:09:33 +04:00
John Preston
29a498b959 Use Data::CloudImage for location thumbnails. 2020-06-01 18:09:33 +04:00
John Preston
ae9ed820ee Fix sticker set icons display. 2020-06-01 18:09:33 +04:00
John Preston
803593cd8d Change Stickers::Set from value to object type. 2020-06-01 18:09:33 +04:00
John Preston
897e432f40 Use CloudImageView in the inline bot thumbnails. 2020-06-01 18:09:33 +04:00
John Preston
50e0c3ee4d Fix preloading in media viewer. 2020-06-01 18:09:33 +04:00
John Preston
056945d9f5 Remove legacy image creation methods. 2020-06-01 18:09:32 +04:00
John Preston
a9b70a7d63 Closed alpha 2.1.7.1: Fix build for Xcode. 2020-06-01 18:09:32 +04:00
John Preston
6dabd87df3 Closed alpha version 2.1.7.1. 2020-06-01 18:09:32 +04:00
John Preston
b35b6c4449 Fix saving cache from InMemoryLocation. 2020-06-01 18:09:32 +04:00
John Preston
74ef8104a7 Fix photo edit caption box, remove 's' size. 2020-06-01 18:09:32 +04:00
John Preston
af0eebb6f1 Remove debug inline bot results marks. 2020-06-01 18:09:32 +04:00
John Preston
dbb46ce9b0 Let [Photo|Document]Media outlive message view. 2020-06-01 18:09:32 +04:00
John Preston
700d3db4cc Correctly unload heavy parts on quit. 2020-06-01 18:09:32 +04:00
John Preston
64cf0e1a44 Fix caching of sent photos and document previews. 2020-06-01 18:09:32 +04:00
John Preston
7ad660a0e7 Allow photos not have some of the thumbnails. 2020-06-01 18:09:32 +04:00
John Preston
e27d2bc2d5 Move photo data to Data::PhotoMedia. 2020-06-01 18:09:32 +04:00
John Preston
24fed8105c Fix stickers panel on Retina screens. 2020-06-01 18:09:32 +04:00
John Preston
9ce59730ff Collect local DocumentMedia data. 2020-06-01 18:09:32 +04:00
John Preston
3f26fc9f55 Allow WebDocument video thumbnails. 2020-06-01 18:09:32 +04:00
John Preston
0834920db8 Fix sending of video-thumbed GIFs from panel. 2020-06-01 18:09:32 +04:00
John Preston
f4ed2c26ba Save video thumbnail location to local storage. 2020-06-01 18:09:32 +04:00
John Preston
c63e2c01ac Use video thumbnail in media preview. 2020-06-01 18:09:31 +04:00
John Preston
c61f3a0aba Fix sending of thumbnailed inline result GIFs. 2020-06-01 18:09:31 +04:00
John Preston
3c9ca2eb94 Load and show video thumbnails in the panel. 2020-06-01 18:09:31 +04:00
John Preston
33c1c48ad9 Update API scheme to layer 114. 2020-06-01 18:09:31 +04:00
John Preston
a27aea3887 Allow retrying of updates build preparing. 2020-06-01 18:09:31 +04:00
John Preston
ea4044e38c Use TgVoip interface instead of VoIPController. 2020-06-01 18:09:31 +04:00
John Preston
c967a72dcb Save frame in GIFs panel. 2020-06-01 18:09:31 +04:00
John Preston
7d386b164b Save a frame in stickers panel. 2020-06-01 18:09:31 +04:00
John Preston
ccbbf6f5f3 Always download GIFs in the panel.
Fixes #6981.
2020-06-01 18:09:31 +04:00
John Preston
9725d4272e Clear DocumentMedia in sticker panel. 2020-06-01 18:09:31 +04:00
John Preston
eb75859dc0 Cache last frame of stickers panel footer icons. 2020-06-01 18:09:31 +04:00
John Preston
ad5507f2c8 Clear DocumentMedia in media overview. 2020-06-01 18:09:31 +04:00
John Preston
58f82620e0 Simplify media overview layouts. 2020-06-01 18:09:31 +04:00
John Preston
053eace154 Prepare overview layouts for media clearing. 2020-06-01 18:09:31 +04:00
John Preston
d64014c995 Clear DocumentMedia in ReplyPreview. 2020-06-01 18:09:31 +04:00
John Preston
44ec55b6a8 Clear DocumentMedia in links overview. 2020-06-01 18:09:31 +04:00
John Preston
9dba723643 Better DocumentMedia management in BackgroundBox. 2020-06-01 18:09:31 +04:00
John Preston
97a82762ef Fix build with MSVC 16.6.0. 2020-06-01 18:09:31 +04:00
John Preston
1542311d89 Preload documents in media viewer. 2020-06-01 18:09:31 +04:00
John Preston
fb322b5fc5 Use empty Storage::Cache::Key as nullopt. 2020-06-01 18:09:31 +04:00
John Preston
581a21dbd9 Use Media::Streaming in EditCaptionBox. 2020-06-01 18:09:31 +04:00
John Preston
3d431a27cb Improve inline thumbnail usage in PiP player. 2020-06-01 18:09:31 +04:00
John Preston
cbb9657044 Fix download task finalizing. 2020-06-01 18:09:30 +04:00
John Preston
3797753d16 Support different location types for thumbnails. 2020-06-01 18:09:30 +04:00
John Preston
37aabc0da9 Add generic DownloadLocation and ImageLocation. 2020-06-01 18:09:30 +04:00
John Preston
956c3af0ae Start DocumentData::thumbnail move to DocumentMedia. 2020-06-01 18:09:30 +04:00
John Preston
1329870c8e Fix build on macOS. 2020-06-01 18:09:30 +04:00
John Preston
ff6365ec72 Fix crash in still downloaded ~DocumentData. 2020-06-01 18:09:30 +04:00
John Preston
1e9c79ca85 Move automaticLoad() to DocumentMedia. 2020-06-01 18:09:30 +04:00
John Preston
40f12a2584 Keep document byte data only in DocumentMedia. 2020-06-01 18:09:30 +04:00
John Preston
97bab388ea Use rpl for file download progress notifications. 2020-06-01 18:09:30 +04:00
John Preston
bf616036b3 Check loaded status through DocumentMedia if possible. 2020-06-01 18:09:30 +04:00
John Preston
669b79588e Remove FilePathResolve::SaveFromData. 2020-06-01 18:09:30 +04:00
John Preston
33f4946242 Start using document bytes from DocumentMedia. 2020-06-01 18:09:30 +04:00
John Preston
888e42df34 Remove data_document_good_thumbnail module. 2020-06-01 18:09:30 +04:00
John Preston
70c79eb6bd Move sticker image to DocumentMedia. 2020-06-01 18:09:30 +04:00
John Preston
bdd3c51ab8 Move inline thumbnail image to DocumentMedia. 2020-06-01 18:09:30 +04:00
John Preston
6ca43153bb Improve clearing of DocumentMedia. 2020-06-01 18:09:29 +04:00
John Preston
7db53599e8 Use Data::DocumentMedia to store good thumbnails. 2020-06-01 18:09:29 +04:00
John Preston
61647275e8 Optimize image destruction.
No need to call _source->unload(), it leads to saving to PNG.
2020-06-01 18:09:29 +04:00
Ilya Fedin
a37138aa52 Fix signature key errors in snap action 2020-06-01 15:24:35 +04:00
Ilya Fedin
1504136828 Don't spam logs if there are no dbus 2020-05-26 07:24:18 +04:00
Ilya Fedin
c12356a032 Disable unneeded alsa dependency in ffmpeg 2020-05-25 10:34:12 +04:00
Ilya Fedin
126ed6e6e3 Fix path to compose file 2020-05-25 10:34:12 +04:00
Ilya Fedin
fa4236e9ea Add support for DESKTOP_APP_USE_PACKAGED on macOS 2020-05-25 10:29:40 +04:00
Ilya Fedin
b19dcf0653 Add possibility to control external upater flag with a config in /etc 2020-05-25 10:27:48 +04:00
Ilya Fedin
77d1f64e0e Disable fallback session management 2020-05-25 09:31:52 +04:00
Ilya Fedin
3479a4ec59 Add parent, minimum and maximum size to notifications 2020-05-25 09:29:15 +04:00
Ilya Fedin
bdf28370f9 Fix call window size on Sway 2020-05-25 09:29:15 +04:00
Ilya Fedin
cd81fc6727 Don't lose -freetype argument on restart 2020-05-25 09:26:49 +04:00
John Preston
7351641034 Version 2.1.7.
- Fix the Fcitx input method plugin.
2020-05-24 11:59:19 +04:00
Ilya Fedin
e0669e222d Update fcitx5-qt 2020-05-24 11:09:34 +04:00
Ilya Fedin
4c1f83daca Add a check for bundled Qt plugins 2020-05-24 10:57:37 +04:00
Ilya Fedin
ced2652deb OpenAL returns device names with UTF-8 2020-05-24 10:56:29 +04:00
23rd
8d1db85a28 Fixed album items selection in section of scheduled messages.
This bug relates only to albums with captions.
2020-05-20 12:00:44 +03:00
John Preston
97305c8cb5 Fix build. 2020-05-20 12:49:41 +04:00
23rd
1ef5d81270 Added ability to change months in calendar with mouse wheel. 2020-05-20 12:42:03 +04:00
23rd
9ff427afad Fixed indefinitely bouncing of dock icon. 2020-05-20 12:41:44 +04:00
23rd
1c5eadcd79 Fixed context menu for caption of scheduled album.
Fixed #6523.
2020-05-20 12:41:44 +04:00
23rd
bc6c01de7f Added Esc shortcut to clear selection in section of scheduled messages. 2020-05-20 12:41:44 +04:00
23rd
41255cab44 Removed display views and author for sent scheduled messages.
Moved filling of post flags to a single place.
2020-05-20 12:41:44 +04:00
23rd
ccbc63cd6e Added ability to paste data in section of scheduled messages.
Fixed #6702.
Fixed #6539.
2020-05-20 12:41:44 +04:00
23rd
97446ae783 Added ability to reschedule scheduled messages. 2020-05-20 12:41:44 +04:00
23rd
5a75dd2b6f Added handling of updates for rescheduled messages. 2020-05-20 12:41:43 +04:00
Wei Cheng
6559e83e83 fix: obtain doNotDisturb value correctly 2020-05-15 11:44:06 +04:00
John Preston
d679703bbf Version 2.1.6.
- Fix automatic downloads on Windows by clean rebuild.
2020-05-14 01:16:13 +04:00
311 changed files with 10657 additions and 8032 deletions

View File

@@ -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: |
@@ -418,8 +426,11 @@ jobs:
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 \

View File

@@ -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 \

View File

@@ -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}

View File

@@ -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

2
.gitmodules vendored
View File

@@ -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

View File

@@ -90,6 +90,7 @@ if (LINUX)
PRIVATE
desktop-app::external_statusnotifieritem
desktop-app::external_dbusmenu_qt
desktop-app::external_fcitx_qt5
desktop-app::external_fcitx5_qt5
desktop-app::external_hime_qt
)
@@ -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})

View File

@@ -1454,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";
@@ -2149,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}";
@@ -2248,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.";

View File

@@ -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

View File

@@ -9,7 +9,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="2.1.5.0" />
Version="2.1.9.0" />
<Properties>
<DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram FZ-LLC</PublisherDisplayName>

View File

@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 2,1,5,0
PRODUCTVERSION 2,1,5,0
FILEVERSION 2,1,9,0
PRODUCTVERSION 2,1,9,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop"
VALUE "FileVersion", "2.1.5.0"
VALUE "FileVersion", "2.1.9.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2020"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "2.1.5.0"
VALUE "ProductVersion", "2.1.9.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 2,1,5,0
PRODUCTVERSION 2,1,5,0
FILEVERSION 2,1,9,0
PRODUCTVERSION 2,1,9,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -53,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop Updater"
VALUE "FileVersion", "2.1.5.0"
VALUE "FileVersion", "2.1.9.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2020"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "2.1.5.0"
VALUE "ProductVersion", "2.1.9.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -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) {

View File

@@ -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"\"";

View File

@@ -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

View File

@@ -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

View File

@@ -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);
@@ -3334,14 +3344,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 +4338,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 +4491,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 +4868,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 +5005,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 +5835,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)

View File

@@ -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>;

View File

@@ -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);

View File

@@ -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,

View File

@@ -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);

View File

@@ -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();
});

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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"

View File

@@ -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(),

View File

@@ -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;

View File

@@ -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,42 @@ 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);
prepareStreamedPreview();
} 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 +252,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 +283,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,
@@ -287,6 +330,8 @@ EditCaptionBox::EditCaptionBox(
}, _wayWrap->lifetime());
}
EditCaptionBox::~EditCaptionBox() = default;
void EditCaptionBox::emojiFilterForGeometry(not_null<QEvent*> event) {
const auto type = event->type();
if (type == QEvent::Move || type == QEvent::Resize) {
@@ -305,74 +350,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 +447,7 @@ void EditCaptionBox::updateEditPreview() {
_animated = false;
_photo = false;
_doc = false;
_gifPreview = nullptr;
_streamed = nullptr;
_thumbw = _thumbh = _thumbx = 0;
_gifw = _gifh = _gifx = 0;
@@ -469,7 +526,7 @@ void EditCaptionBox::updateEditPreview() {
_gifw = _thumbw;
_gifh = _thumbh;
_gifx = _thumbx;
prepareGifPreview();
prepareStreamedPreview();
}
}
updateEditMediaButton();
@@ -551,10 +608,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 +741,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 +779,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);

View File

@@ -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 };

View File

@@ -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(),

View File

@@ -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);
}
};
}

View File

@@ -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);
}
{

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -888,7 +888,6 @@ void SingleMediaPreview::prepareAnimatedPreview(
_gifPreview = Media::Clip::MakeReader(
animatedPreviewPath,
std::move(callback));
if (_gifPreview) _gifPreview->setAutoplay();
}
}

View File

@@ -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;

View File

@@ -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);
}
}
}

View File

@@ -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();

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View 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

View 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

View 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

View File

@@ -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();
@@ -486,6 +491,7 @@ void Panel::finishAnimating() {
void Panel::showControls() {
Expects(_call != nullptr);
showChildren();
_decline->setVisible(_decline->toggled());
_cancel->setVisible(_cancel->toggled());
@@ -507,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) {
@@ -557,7 +559,6 @@ void Panel::createUserpicCache(Image *image, Data::FileOrigin origin) {
width = size;
}
_userPhoto = image->pixNoCache(
origin,
width,
height,
options,
@@ -595,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();
}

View File

@@ -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;

View File

@@ -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"
@@ -49,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)));
@@ -151,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;
}
@@ -159,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);
}
}
@@ -177,7 +180,10 @@ internal::StickerRows FieldAutocomplete::getStickerSuggestions() {
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) {
@@ -237,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);
};
@@ -254,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;
@@ -282,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 });
}
}
}
@@ -374,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) });
}
}
}
@@ -386,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) });
}
}
}
@@ -407,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();
}
@@ -448,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;
}
@@ -634,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);
}
@@ -649,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()) {
@@ -680,15 +683,19 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
if (!paused) {
sticker.animated->markFrameShown();
}
} else if (const auto image = document->getStickerSmall()) {
} 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();
@@ -700,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);
@@ -727,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);
@@ -739,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);
@@ -763,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;
@@ -815,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;
@@ -826,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) {
@@ -857,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);
@@ -890,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();) {
@@ -904,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) {
@@ -1003,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,
@@ -1061,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;

View File

@@ -26,17 +26,34 @@ 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;
@@ -89,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;
@@ -177,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;

View File

@@ -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;
}
}
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;

View 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

View 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

View File

@@ -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\

View File

@@ -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);
@@ -767,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();

View File

@@ -101,6 +101,7 @@ public:
bool minimizeActiveWindow();
QWidget *getFileDialogParent();
void notifyFileDialogShown(bool shown);
QWidget *getModalParent();
// Media view interface.
void checkMediaViewActivation();

View File

@@ -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."
}
};
};

View File

@@ -98,13 +98,29 @@ 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(cWorkingDir() + qsl("tdata/withfreetype")).exists()) {
if (QFile::exists(cWorkingDir() + qsl("tdata/withfreetype"))) {
cSetUseFreeType(true);
}
}
@@ -124,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");
@@ -168,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
@@ -283,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();
}
@@ -328,6 +352,7 @@ void Launcher::workingFolderReady() {
ComputeTestMode();
ComputeDebugMode();
ComputeExternalUpdater();
ComputeFreeType();
ComputeInstallBetaVersions();
ComputeInstallationTag();

View File

@@ -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

View File

@@ -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 = 2001005;
constexpr auto AppVersionStr = "2.1.5";
constexpr auto AppBetaVersion = false;
constexpr auto AppVersion = 2001009;
constexpr auto AppVersionStr = "2.1.9";
constexpr auto AppBetaVersion = true;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View File

@@ -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(

View File

@@ -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,

View File

@@ -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;

View 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

View 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

View File

@@ -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() {

View File

@@ -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

View File

@@ -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);

View File

@@ -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

View File

@@ -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

View 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

View 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

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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());
}
}

View File

@@ -8,40 +8,56 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "data/data_types.h"
#include "data/data_cloud_file.h"
namespace Main {
class Session;
} // namespace Main
namespace Data {
class Session;
class ReplyPreview;
class PhotoMedia;
inline constexpr auto kPhotoSizeCount = 3;
enum class PhotoSize : uchar {
Small,
Thumbnail,
Large,
};
[[nodiscard]] inline int PhotoSizeIndex(PhotoSize size) {
Expects(static_cast<int>(size) < kPhotoSizeCount);
return static_cast<int>(size);
}
} // namespace Data
class PhotoData {
class PhotoData final {
public:
PhotoData(not_null<Data::Session*> owner, PhotoId id);
~PhotoData();
[[nodiscard]] Data::Session &owner() const;
[[nodiscard]] Main::Session &session() const;
[[nodiscard]] bool isNull() const;
void automaticLoad(
Data::FileOrigin origin,
const HistoryItem *item);
void automaticLoadSettingsChanged();
void download(Data::FileOrigin origin);
[[nodiscard]] bool loaded() const;
[[nodiscard]] bool loading() const;
[[nodiscard]] bool displayLoading() const;
void cancel();
[[nodiscard]] float64 progress() const;
[[nodiscard]] int32 loadOffset() const;
[[nodiscard]] bool uploading() const;
[[nodiscard]] bool cancelled() const;
void setWaitingForAlbum();
[[nodiscard]] bool waitingForAlbum() const;
void unload();
[[nodiscard]] Image *getReplyPreview(Data::FileOrigin origin);
void setRemoteLocation(
@@ -58,27 +74,47 @@ public:
// to (this) received from the server "same" photo.
void collectLocalData(not_null<PhotoData*> local);
bool isNull() const;
[[nodiscard]] std::shared_ptr<Data::PhotoMedia> createMediaView();
[[nodiscard]] auto activeMediaView() const
-> std::shared_ptr<Data::PhotoMedia>;
void loadThumbnail(Data::FileOrigin origin);
void loadThumbnailSmall(Data::FileOrigin origin);
Image *thumbnailInline() const;
not_null<Image*> thumbnailSmall() const;
not_null<Image*> thumbnail() const;
void updateImages(
const QByteArray &inlineThumbnailBytes,
const ImageWithLocation &small,
const ImageWithLocation &thumbnail,
const ImageWithLocation &large);
[[nodiscard]] int validSizeIndex(Data::PhotoSize size) const;
void load(Data::FileOrigin origin);
not_null<Image*> large() const;
[[nodiscard]] QByteArray inlineThumbnailBytes() const {
return _inlineThumbnailBytes;
}
void clearInlineThumbnailBytes() {
_inlineThumbnailBytes = QByteArray();
}
void load(
Data::FileOrigin origin,
LoadFromCloudSetting fromCloud = LoadFromCloudOrLocal,
bool autoLoading = false);
[[nodiscard]] static int SideLimit();
[[nodiscard]] bool hasExact(Data::PhotoSize size) const;
[[nodiscard]] bool loading(Data::PhotoSize size) const;
[[nodiscard]] bool failed(Data::PhotoSize size) const;
void load(
Data::PhotoSize size,
Data::FileOrigin origin,
LoadFromCloudSetting fromCloud = LoadFromCloudOrLocal,
bool autoLoading = false);
[[nodiscard]] const ImageLocation &location(Data::PhotoSize size) const;
[[nodiscard]] std::optional<QSize> size(Data::PhotoSize size) const;
[[nodiscard]] int imageByteSize(Data::PhotoSize size) const;
// For now they return size of the 'large' image.
int width() const;
int height() const;
void updateImages(
ImagePtr thumbnailInline,
ImagePtr thumbnailSmall,
ImagePtr thumbnail,
ImagePtr large);
PhotoId id = 0;
TimeId date = 0;
bool hasSticker = false;
@@ -89,15 +125,14 @@ public:
std::unique_ptr<Data::UploadState> uploadingData;
private:
ImagePtr _thumbnailInline;
ImagePtr _thumbnailSmall;
ImagePtr _thumbnail;
ImagePtr _large;
QByteArray _inlineThumbnailBytes;
std::array<Data::CloudFile, Data::kPhotoSizeCount> _images;
int32 _dc = 0;
uint64 _access = 0;
QByteArray _fileReference;
Data::ReplyPreview _replyPreview;
std::unique_ptr<Data::ReplyPreview> _replyPreview;
std::weak_ptr<Data::PhotoMedia> _media;
not_null<Data::Session*> _owner;

View File

@@ -0,0 +1,126 @@
/*
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_photo_media.h"
#include "data/data_photo.h"
#include "data/data_session.h"
#include "data/data_file_origin.h"
#include "data/data_auto_download.h"
#include "main/main_session.h"
#include "history/history_item.h"
#include "history/history.h"
#include "storage/file_download.h"
#include "ui/image/image.h"
namespace Data {
PhotoMedia::PhotoMedia(not_null<PhotoData*> 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.
PhotoMedia::~PhotoMedia() = default;
not_null<PhotoData*> PhotoMedia::owner() const {
return _owner;
}
Image *PhotoMedia::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 *PhotoMedia::image(PhotoSize size) const {
if (const auto image = _images[PhotoSizeIndex(size)].get()) {
return image;
}
return _images[_owner->validSizeIndex(size)].get();
}
void PhotoMedia::wanted(PhotoSize size, Data::FileOrigin origin) {
const auto index = _owner->validSizeIndex(size);
if (!_images[index]) {
_owner->load(size, origin);
}
}
QSize PhotoMedia::size(PhotoSize size) const {
const auto index = PhotoSizeIndex(size);
if (const auto image = _images[index].get()) {
return image->size();
}
const auto &location = _owner->location(size);
return { location.width(), location.height() };
}
void PhotoMedia::set(PhotoSize size, QImage image) {
const auto index = PhotoSizeIndex(size);
const auto limit = PhotoData::SideLimit();
if (image.width() > limit || image.height() > limit) {
image = image.scaled(
limit,
limit,
Qt::KeepAspectRatio,
Qt::SmoothTransformation);
}
_images[index] = std::make_unique<Image>(std::move(image));
_owner->session().downloaderTaskFinished().notify();
}
bool PhotoMedia::loaded() const {
const auto index = PhotoSizeIndex(PhotoSize::Large);
return (_images[index] != nullptr);
}
float64 PhotoMedia::progress() const {
return (_owner->uploading() || _owner->loading())
? _owner->progress()
: (loaded() ? 1. : 0.);
}
void PhotoMedia::automaticLoad(
Data::FileOrigin origin,
const HistoryItem *item) {
const auto index = PhotoSizeIndex(PhotoSize::Large);
if (!item || loaded() || _owner->cancelled()) {
return;
}
const auto loadFromCloud = Data::AutoDownload::Should(
_owner->session().settings().autoDownload(),
item->history()->peer,
_owner);
_owner->load(
origin,
loadFromCloud ? LoadFromCloudOrLocal : LoadFromLocalOnly,
true);
}
void PhotoMedia::collectLocalData(not_null<PhotoMedia*> local) {
if (const auto image = local->_inlineThumbnail.get()) {
_inlineThumbnail = std::make_unique<Image>(image->original());
}
for (auto i = 0; i != kPhotoSizeCount; ++i) {
if (const auto image = local->_images[i].get()) {
_images[i] = std::make_unique<Image>(image->original());
}
}
}
} // namespace Data

View File

@@ -0,0 +1,48 @@
/*
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 "data/data_photo.h"
class FileLoader;
namespace Data {
class PhotoMedia final {
public:
explicit PhotoMedia(not_null<PhotoData*> owner);
~PhotoMedia();
[[nodiscard]] not_null<PhotoData*> owner() const;
[[nodiscard]] Image *thumbnailInline() const;
[[nodiscard]] Image *image(PhotoSize size) const;
[[nodiscard]] QSize size(PhotoSize size) const;
void wanted(PhotoSize size, Data::FileOrigin origin);
void set(PhotoSize size, QImage image);
[[nodiscard]] bool loaded() const;
[[nodiscard]] float64 progress() const;
void automaticLoad(Data::FileOrigin origin, const HistoryItem *item);
void collectLocalData(not_null<PhotoMedia*> local);
private:
// 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<PhotoData*> _owner;
mutable std::unique_ptr<Image> _inlineThumbnail;
std::array<std::unique_ptr<Image>, kPhotoSizeCount> _images;
};
} // namespace Data

View File

@@ -0,0 +1,110 @@
/*
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_reply_preview.h"
#include "data/data_file_origin.h"
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "data/data_photo.h"
#include "data/data_photo_media.h"
#include "ui/image/image.h"
namespace Data {
ReplyPreview::ReplyPreview(not_null<DocumentData*> document)
: _document(document) {
}
ReplyPreview::ReplyPreview(not_null<PhotoData*> photo)
: _photo(photo) {
}
ReplyPreview::~ReplyPreview() = default;
void ReplyPreview::prepare(not_null<Image*> image, Images::Options options) {
if (image->isNull()) {
return;
}
int w = image->width(), h = image->height();
if (w <= 0) w = 1;
if (h <= 0) h = 1;
auto thumbSize = (w > h)
? QSize(
w * st::msgReplyBarSize.height() / h,
st::msgReplyBarSize.height())
: QSize(
st::msgReplyBarSize.height(),
h * st::msgReplyBarSize.height() / w);
thumbSize *= cIntRetinaFactor();
const auto prepareOptions = Images::Option::Smooth
| Images::Option::TransparentBackground
| options;
auto outerSize = st::msgReplyBarSize.height();
auto bitmap = image->pixNoCache(
thumbSize.width(),
thumbSize.height(),
prepareOptions,
outerSize,
outerSize);
_image = std::make_unique<Image>(bitmap.toImage());
_good = ((options & Images::Option::Blurred) == 0);
}
Image *ReplyPreview::image(Data::FileOrigin origin) {
if (_checked) {
return _image.get();
}
if (_document) {
if (!_image || (!_good && _document->hasThumbnail())) {
if (!_documentMedia) {
_documentMedia = _document->createMediaView();
_documentMedia->thumbnailWanted(origin);
}
const auto thumbnail = _documentMedia->thumbnail();
const auto option = _document->isVideoMessage()
? Images::Option::Circled
: Images::Option::None;
if (thumbnail) {
prepare(thumbnail, option);
} else if (!_image) {
if (const auto image = _documentMedia->thumbnailInline()) {
prepare(image, option | Images::Option::Blurred);
}
}
if (_good || !_document->hasThumbnail()) {
_checked = true;
_documentMedia = nullptr;
}
}
} else {
Assert(_photo != nullptr);
if (!_image || !_good) {
if (!_photoMedia) {
_photoMedia = _photo->createMediaView();
_photoMedia->wanted(PhotoSize::Small, origin);
}
if (const auto small = _photoMedia->image(PhotoSize::Small)) {
prepare(small, Images::Option(0));
} else if (const auto large = _photoMedia->image(
PhotoSize::Large)) {
prepare(large, Images::Option(0));
} else if (!_image) {
if (const auto blurred = _photoMedia->thumbnailInline()) {
prepare(blurred, Images::Option::Blurred);
}
}
if (_good) {
_checked = true;
_photoMedia = nullptr;
}
}
}
return _image.get();
}
} // namespace Data

View File

@@ -0,0 +1,41 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
class Image;
class DocumentData;
class PhotoData;
namespace Data {
class PhotoMedia;
class DocumentMedia;
struct FileOrigin;
class ReplyPreview {
public:
explicit ReplyPreview(not_null<DocumentData*> document);
explicit ReplyPreview(not_null<PhotoData*> photo);
~ReplyPreview();
[[nodiscard]] Image *image(Data::FileOrigin origin);
private:
void prepare(not_null<Image*> image, Images::Options options);
std::unique_ptr<Image> _image;
PhotoData *_photo = nullptr;
DocumentData *_document = nullptr;
std::shared_ptr<PhotoMedia> _photoMedia;
std::shared_ptr<DocumentMedia> _documentMedia;
bool _good = false;
bool _checked = false;
};
} // namespace Data

View File

@@ -414,6 +414,7 @@ HistoryItem *ScheduledMessages::append(
}, data.vmedia());
existing->updateReplyMarkup(data.vreply_markup());
existing->updateForwardedInfo(data.vfwd_from());
existing->updateDate(data.vdate().v);
history->owner().requestItemTextRefresh(existing);
}, [&](const auto &data) {});
return existing;

View File

@@ -16,7 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/mime_type.h" // Core::IsMimeSticker
#include "core/crash_reports.h" // CrashReports::SetAnnotation
#include "ui/image/image.h"
#include "ui/image/image_source.h" // Images::LocalFileSource
#include "ui/image/image_location_factory.h" // Images::FromPhotoSize
#include "export/export_controller.h"
#include "export/view/export_view_panel_controller.h"
#include "window/notifications_manager.h"
@@ -74,10 +74,10 @@ using ViewElement = HistoryView::Element;
// b: crop 320x320
// c: crop 640x640
// d: crop 1280x1280
const auto InlineLevels = QByteArray::fromRawData("i", 1);
const auto SmallLevels = QByteArray::fromRawData("sambcxydwi", 10);
const auto ThumbnailLevels = QByteArray::fromRawData("mbcxasydwi", 10);
const auto LargeLevels = QByteArray::fromRawData("yxwmsdcbai", 10);
const auto InlineLevels = "i"_q;
const auto SmallLevels = "sa"_q;
const auto ThumbnailLevels = "mbsa"_q;
const auto LargeLevels = "ydxcwmbsa"_q;
void CheckForSwitchInlineButton(not_null<HistoryItem*> item) {
if (item->out() || !item->hasSwitchInlineButton()) {
@@ -126,22 +126,22 @@ std::vector<UnavailableReason> ExtractUnavailableReasons(
}) | ranges::to_vector;
}
MTPPhotoSize FindDocumentInlineThumbnail(const MTPDdocument &data) {
const auto thumbs = data.vthumbs();
if (!thumbs) {
return MTP_photoSizeEmpty(MTP_string());
}
const auto &list = thumbs->v;
[[nodiscard]] QByteArray FindInlineThumbnail(
const QVector<MTPPhotoSize> &sizes) {
const auto i = ranges::find(
list,
sizes,
mtpc_photoStrippedSize,
&MTPPhotoSize::type);
return (i != list.end())
? (*i)
: MTPPhotoSize(MTP_photoSizeEmpty(MTP_string()));
return (i != sizes.end())
? i->c_photoStrippedSize().vbytes().v
: QByteArray();
}
MTPPhotoSize FindDocumentThumbnail(const MTPDdocument &data) {
[[nodiscard]] QByteArray FindDocumentInlineThumbnail(const MTPDdocument &data) {
return FindInlineThumbnail(data.vthumbs().value_or_empty());
}
[[nodiscard]] MTPPhotoSize FindDocumentThumbnail(const MTPDdocument &data) {
const auto area = [](const MTPPhotoSize &size) {
static constexpr auto kInvalid = 0;
return size.match([](const MTPDphotoSizeEmpty &) {
@@ -163,7 +163,30 @@ MTPPhotoSize FindDocumentThumbnail(const MTPDdocument &data) {
: MTPPhotoSize(MTP_photoSizeEmpty(MTP_string()));
}
rpl::producer<int> PinnedDialogsCountMaxValue(
[[nodiscard]] std::optional<MTPVideoSize> FindDocumentVideoThumbnail(
const MTPDdocument &data) {
const auto area = [](const MTPVideoSize &size) {
static constexpr auto kInvalid = 0;
return size.match([](const MTPDvideoSize &data) {
return (data.vw().v * data.vh().v);
});
};
const auto thumbs = data.vvideo_thumbs();
if (!thumbs) {
return std::nullopt;
}
const auto &list = thumbs->v;
const auto i = ranges::max_element(list, std::less<>(), area);
return (i != list.end() && area(*i) > 0)
? std::make_optional(*i)
: std::nullopt;
}
[[nodiscard]] QByteArray FindPhotoInlineThumbnail(const MTPDphoto &data) {
return FindInlineThumbnail(data.vsizes().v);
}
[[nodiscard]] rpl::producer<int> PinnedDialogsCountMaxValue(
not_null<Main::Session*> session) {
return rpl::single(
rpl::empty_value()
@@ -244,6 +267,18 @@ void Session::clear() {
_photos.clear();
}
void Session::keepAlive(std::shared_ptr<PhotoMedia> media) {
// NB! This allows PhotoMedia to outlive Main::Session!
// In case this is a problem this code should be rewritten.
crl::on_main(&session(), [media = std::move(media)]{});
}
void Session::keepAlive(std::shared_ptr<DocumentMedia> media) {
// NB! This allows DocumentMedia to outlive Main::Session!
// In case this is a problem this code should be rewritten.
crl::on_main(&session(), [media = std::move(media)] {});
}
not_null<PeerData*> Session::peer(PeerId id) {
const auto i = _peers.find(id);
if (i != _peers.cend()) {
@@ -1044,7 +1079,6 @@ Session::~Session() {
_session->notifications().clearAllFast();
clear();
Images::ClearRemote();
}
template <typename Method>
@@ -1078,6 +1112,15 @@ void Session::notifyPhotoLayoutChanged(not_null<const PhotoData*> photo) {
}
}
void Session::requestPhotoViewRepaint(not_null<const PhotoData*> photo) {
const auto i = _photoItems.find(photo);
if (i != end(_photoItems)) {
for (const auto item : i->second) {
requestItemRepaint(item);
}
}
}
void Session::notifyDocumentLayoutChanged(
not_null<const DocumentData*> document) {
const auto i = _documentItems.find(document);
@@ -1113,6 +1156,39 @@ void Session::requestPollViewRepaint(not_null<const PollData*> poll) {
}
}
void Session::documentLoadProgress(not_null<DocumentData*> document) {
requestDocumentViewRepaint(document);
session().documentUpdated.notify(document, true);
if (document->isAudioFile()) {
::Media::Player::instance()->documentLoadProgress(document);
}
}
void Session::documentLoadDone(not_null<DocumentData*> document) {
notifyDocumentLayoutChanged(document);
}
void Session::documentLoadFail(
not_null<DocumentData*> document,
bool started) {
notifyDocumentLayoutChanged(document);
}
void Session::photoLoadProgress(not_null<PhotoData*> photo) {
requestPhotoViewRepaint(photo);
}
void Session::photoLoadDone(not_null<PhotoData*> photo) {
notifyPhotoLayoutChanged(photo);
}
void Session::photoLoadFail(
not_null<PhotoData*> photo,
bool started) {
notifyPhotoLayoutChanged(photo);
}
void Session::markMediaRead(not_null<const DocumentData*> document) {
const auto i = _documentItems.find(document);
if (i != end(_documentItems)) {
@@ -2134,11 +2210,10 @@ not_null<PhotoData*> Session::processPhoto(
const auto image = [&](const QByteArray &levels) {
const auto i = find(levels);
return (i == thumbs.end())
? ImagePtr()
: Images::Create(base::duplicate(i->second), "JPG");
? ImageWithLocation()
: Images::FromImageInMemory(i->second, "JPG");
};
const auto thumbnailInline = image(InlineLevels);
const auto thumbnailSmall = image(SmallLevels);
const auto small = image(SmallLevels);
const auto thumbnail = image(ThumbnailLevels);
const auto large = image(LargeLevels);
return data.match([&](const MTPDphoto &data) {
@@ -2149,8 +2224,8 @@ not_null<PhotoData*> Session::processPhoto(
data.vdate().v,
data.vdc_id().v,
data.is_has_stickers(),
thumbnailInline,
thumbnailSmall,
QByteArray(),
small,
thumbnail,
large);
}, [&](const MTPDphotoEmpty &data) {
@@ -2165,10 +2240,10 @@ not_null<PhotoData*> Session::photo(
TimeId date,
int32 dc,
bool hasSticker,
const ImagePtr &thumbnailInline,
const ImagePtr &thumbnailSmall,
const ImagePtr &thumbnail,
const ImagePtr &large) {
const QByteArray &inlineThumbnailBytes,
const ImageWithLocation &small,
const ImageWithLocation &thumbnail,
const ImageWithLocation &large) {
const auto result = photo(id);
photoApplyFields(
result,
@@ -2177,8 +2252,8 @@ not_null<PhotoData*> Session::photo(
date,
dc,
hasSticker,
thumbnailInline,
thumbnailSmall,
inlineThumbnailBytes,
small,
thumbnail,
large);
return result;
@@ -2190,7 +2265,8 @@ void Session::photoConvert(
const auto id = data.match([](const auto &data) {
return data.vid().v;
});
if (original->id != id) {
const auto idChanged = (original->id != id);
if (idChanged) {
auto i = _photos.find(id);
if (i == _photos.end()) {
const auto j = _photos.find(original->id);
@@ -2212,29 +2288,11 @@ void Session::photoConvert(
PhotoData *Session::photoFromWeb(
const MTPWebDocument &data,
ImagePtr thumbnail,
bool willBecomeNormal) {
const auto large = Images::Create(data);
const auto thumbnailInline = ImagePtr();
if (large->isNull()) {
const ImageLocation &thumbnailLocation) {
const auto large = Images::FromWebDocument(data);
if (!large.valid()) {
return nullptr;
}
auto thumbnailSmall = large;
if (willBecomeNormal) {
const auto width = large->width();
const auto height = large->height();
auto thumbsize = shrinkToKeepAspect(width, height, 100, 100);
thumbnailSmall = Images::Create(thumbsize.width(), thumbsize.height());
if (thumbnail->isNull()) {
auto mediumsize = shrinkToKeepAspect(width, height, 320, 320);
thumbnail = Images::Create(mediumsize.width(), mediumsize.height());
}
} else if (thumbnail->isNull()) {
thumbnail = large;
}
return photo(
rand_value<PhotoId>(),
uint64(0),
@@ -2242,10 +2300,10 @@ PhotoData *Session::photoFromWeb(
base::unixtime::now(),
0,
false,
thumbnailInline,
thumbnailSmall,
thumbnail,
large);
QByteArray(),
ImageWithLocation{},
ImageWithLocation{ .location = thumbnailLocation },
ImageWithLocation{ .location = large });
}
void Session::photoApplyFields(
@@ -2279,13 +2337,12 @@ void Session::photoApplyFields(
};
const auto image = [&](const QByteArray &levels) {
const auto i = find(levels);
return (i == sizes.end()) ? ImagePtr() : Images::Create(data, *i);
return (i == sizes.end())
? ImageWithLocation()
: Images::FromPhotoSize(_session, data, *i);
};
const auto thumbnailInline = image(InlineLevels);
const auto thumbnailSmall = image(SmallLevels);
const auto thumbnail = image(ThumbnailLevels);
const auto large = image(LargeLevels);
if (thumbnailSmall && thumbnail && large) {
if (large.location.valid()) {
photoApplyFields(
photo,
data.vaccess_hash().v,
@@ -2293,9 +2350,9 @@ void Session::photoApplyFields(
data.vdate().v,
data.vdc_id().v,
data.is_has_stickers(),
thumbnailInline,
thumbnailSmall,
thumbnail,
FindPhotoInlineThumbnail(data),
image(SmallLevels),
image(ThumbnailLevels),
large);
}
}
@@ -2307,10 +2364,10 @@ void Session::photoApplyFields(
TimeId date,
int32 dc,
bool hasSticker,
const ImagePtr &thumbnailInline,
const ImagePtr &thumbnailSmall,
const ImagePtr &thumbnail,
const ImagePtr &large) {
const QByteArray &inlineThumbnailBytes,
const ImageWithLocation &small,
const ImageWithLocation &thumbnail,
const ImageWithLocation &large) {
if (!date) {
return;
}
@@ -2318,8 +2375,8 @@ void Session::photoApplyFields(
photo->date = date;
photo->hasSticker = hasSticker;
photo->updateImages(
thumbnailInline,
thumbnailSmall,
inlineThumbnailBytes,
small,
thumbnail,
large);
}
@@ -2335,14 +2392,11 @@ not_null<DocumentData*> Session::document(DocumentId id) {
}
not_null<DocumentData*> Session::processDocument(const MTPDocument &data) {
switch (data.type()) {
case mtpc_document:
return processDocument(data.c_document());
case mtpc_documentEmpty:
return document(data.c_documentEmpty().vid().v);
}
Unexpected("Type in Session::document().");
return data.match([&](const MTPDdocument &data) {
return processDocument(data);
}, [&](const MTPDdocumentEmpty &data) {
return document(data.vid().v);
});
}
not_null<DocumentData*> Session::processDocument(const MTPDdocument &data) {
@@ -2353,32 +2407,23 @@ not_null<DocumentData*> Session::processDocument(const MTPDdocument &data) {
not_null<DocumentData*> Session::processDocument(
const MTPdocument &data,
QImage &&thumb) {
switch (data.type()) {
case mtpc_documentEmpty:
return document(data.c_documentEmpty().vid().v);
case mtpc_document: {
const auto &fields = data.c_document();
const auto mime = qs(fields.vmime_type());
const auto format = Core::IsMimeSticker(mime)
? "WEBP"
: "JPG";
const ImageWithLocation &thumbnail) {
return data.match([&](const MTPDdocument &data) {
return document(
fields.vid().v,
fields.vaccess_hash().v,
fields.vfile_reference().v,
fields.vdate().v,
fields.vattributes().v,
mime,
ImagePtr(),
Images::Create(std::move(thumb), format),
fields.vdc_id().v,
fields.vsize().v,
StorageImageLocation());
} break;
}
Unexpected("Type in Session::document() with thumb.");
data.vid().v,
data.vaccess_hash().v,
data.vfile_reference().v,
data.vdate().v,
data.vattributes().v,
qs(data.vmime_type()),
QByteArray(),
thumbnail,
ImageWithLocation(),
data.vdc_id().v,
data.vsize().v);
}, [&](const MTPDdocumentEmpty &data) {
return document(data.vid().v);
});
}
not_null<DocumentData*> Session::document(
@@ -2388,11 +2433,11 @@ not_null<DocumentData*> Session::document(
TimeId date,
const QVector<MTPDocumentAttribute> &attributes,
const QString &mime,
const ImagePtr &thumbnailInline,
const ImagePtr &thumbnail,
const QByteArray &inlineThumbnailBytes,
const ImageWithLocation &thumbnail,
const ImageWithLocation &videoThumbnail,
int32 dc,
int32 size,
const StorageImageLocation &thumbLocation) {
int32 size) {
const auto result = document(id);
documentApplyFields(
result,
@@ -2401,28 +2446,23 @@ not_null<DocumentData*> Session::document(
date,
attributes,
mime,
thumbnailInline,
inlineThumbnailBytes,
thumbnail,
videoThumbnail,
dc,
size,
thumbLocation);
size);
return result;
}
void Session::documentConvert(
not_null<DocumentData*> original,
const MTPDocument &data) {
const auto id = [&] {
switch (data.type()) {
case mtpc_document: return data.c_document().vid().v;
case mtpc_documentEmpty: return data.c_documentEmpty().vid().v;
}
Unexpected("Type in Session::documentConvert().");
}();
const auto oldKey = original->mediaKey();
const auto id = data.match([](const auto &data) {
return data.vid().v;
});
const auto oldCacheKey = original->cacheKey();
const auto oldGoodKey = original->goodThumbnailCacheKey();
const auto idChanged = (original->id != id);
const auto sentSticker = idChanged && (original->sticker() != nullptr);
if (idChanged) {
auto i = _documents.find(id);
if (i == _documents.end()) {
@@ -2444,6 +2484,7 @@ void Session::documentConvert(
documentApplyFields(original, data);
if (idChanged) {
cache().moveIfEmpty(oldCacheKey, original->cacheKey());
cache().moveIfEmpty(oldGoodKey, original->goodThumbnailCacheKey());
if (savedGifs().indexOf(original) >= 0) {
Local::writeSavedGifs();
}
@@ -2452,21 +2493,20 @@ void Session::documentConvert(
DocumentData *Session::documentFromWeb(
const MTPWebDocument &data,
ImagePtr thumb) {
switch (data.type()) {
case mtpc_webDocument:
return documentFromWeb(data.c_webDocument(), thumb);
case mtpc_webDocumentNoProxy:
return documentFromWeb(data.c_webDocumentNoProxy(), thumb);
}
Unexpected("Type in Session::documentFromWeb.");
const ImageLocation &thumbnailLocation,
const ImageLocation &videoThumbnailLocation) {
return data.match([&](const auto &data) {
return documentFromWeb(
data,
thumbnailLocation,
videoThumbnailLocation);
});
}
DocumentData *Session::documentFromWeb(
const MTPDwebDocument &data,
ImagePtr thumb) {
const ImageLocation &thumbnailLocation,
const ImageLocation &videoThumbnailLocation) {
const auto result = document(
rand_value<DocumentId>(),
uint64(0),
@@ -2474,11 +2514,11 @@ DocumentData *Session::documentFromWeb(
base::unixtime::now(),
data.vattributes().v,
data.vmime_type().v,
ImagePtr(),
thumb,
QByteArray(),
ImageWithLocation{ .location = thumbnailLocation },
ImageWithLocation{ .location = videoThumbnailLocation },
MTP::maindc(),
int32(0), // data.vsize().v
StorageImageLocation());
int32(0)); // data.vsize().v
result->setWebLocation(WebFileLocation(
data.vurl().v,
data.vaccess_hash().v));
@@ -2487,7 +2527,8 @@ DocumentData *Session::documentFromWeb(
DocumentData *Session::documentFromWeb(
const MTPDwebDocumentNoProxy &data,
ImagePtr thumb) {
const ImageLocation &thumbnailLocation,
const ImageLocation &videoThumbnailLocation) {
const auto result = document(
rand_value<DocumentId>(),
uint64(0),
@@ -2495,11 +2536,11 @@ DocumentData *Session::documentFromWeb(
base::unixtime::now(),
data.vattributes().v,
data.vmime_type().v,
ImagePtr(),
thumb,
QByteArray(),
ImageWithLocation{ .location = thumbnailLocation },
ImageWithLocation{ .location = videoThumbnailLocation },
MTP::maindc(),
int32(0), // data.vsize().v
StorageImageLocation());
int32(0)); // data.vsize().v
result->setContentUrl(qs(data.vurl()));
return result;
}
@@ -2515,9 +2556,16 @@ void Session::documentApplyFields(
void Session::documentApplyFields(
not_null<DocumentData*> document,
const MTPDdocument &data) {
const auto thumbnailInline = FindDocumentInlineThumbnail(data);
const auto inlineThumbnailBytes = FindDocumentInlineThumbnail(data);
const auto thumbnailSize = FindDocumentThumbnail(data);
const auto thumbnail = Images::Create(data, thumbnailSize);
const auto videoThumbnailSize = FindDocumentVideoThumbnail(data);
const auto prepared = Images::FromPhotoSize(
_session,
data,
thumbnailSize);
const auto videoThumbnail = videoThumbnailSize
? Images::FromVideoSize(_session, data, *videoThumbnailSize)
: ImageWithLocation();
documentApplyFields(
document,
data.vaccess_hash().v,
@@ -2525,11 +2573,11 @@ void Session::documentApplyFields(
data.vdate().v,
data.vattributes().v,
qs(data.vmime_type()),
Images::Create(data, thumbnailInline),
thumbnail,
inlineThumbnailBytes,
prepared,
videoThumbnail,
data.vdc_id().v,
data.vsize().v,
thumbnail->location());
data.vsize().v);
}
void Session::documentApplyFields(
@@ -2539,17 +2587,20 @@ void Session::documentApplyFields(
TimeId date,
const QVector<MTPDocumentAttribute> &attributes,
const QString &mime,
const ImagePtr &thumbnailInline,
const ImagePtr &thumbnail,
const QByteArray &inlineThumbnailBytes,
const ImageWithLocation &thumbnail,
const ImageWithLocation &videoThumbnail,
int32 dc,
int32 size,
const StorageImageLocation &thumbLocation) {
int32 size) {
if (!date) {
return;
}
document->date = date;
document->setMimeString(mime);
document->updateThumbnails(thumbnailInline, thumbnail);
document->updateThumbnails(
inlineThumbnailBytes,
thumbnail,
videoThumbnail);
document->size = size;
document->setattributes(attributes);
@@ -2558,12 +2609,6 @@ void Session::documentApplyFields(
if (dc != 0 && access != 0) {
document->setRemoteLocation(dc, access, fileReference);
}
if (document->sticker()
&& !document->sticker()->loc.valid()
&& thumbLocation.valid()) {
document->sticker()->loc = thumbLocation;
}
}
not_null<WebPageData*> Session::webpage(WebPageId id) {
@@ -2978,13 +3023,23 @@ void Session::applyUpdate(const MTPDupdateChatDefaultBannedRights &update) {
}
}
not_null<LocationThumbnail*> Session::location(const LocationPoint &point) {
not_null<Data::CloudImage*> Session::location(const LocationPoint &point) {
const auto i = _locations.find(point);
return (i != _locations.cend())
? i->second.get()
: _locations.emplace(
point,
std::make_unique<LocationThumbnail>(point)).first->second.get();
if (i != _locations.cend()) {
return i->second.get();
}
const auto location = Data::ComputeLocation(point);
const auto prepared = ImageWithLocation{
.location = ImageLocation(
{ location },
location.width,
location.height)
};
return _locations.emplace(
point,
std::make_unique<Data::CloudImage>(
_session,
prepared)).first->second.get();
}
void Session::registerPhotoItem(
@@ -3169,43 +3224,19 @@ void Session::unregisterContactItem(
}
}
void Session::registerPlayingVideoFile(not_null<ViewElement*> view) {
if (++_playingVideoFiles[view] == 1) {
registerHeavyViewPart(view);
}
}
void Session::unregisterPlayingVideoFile(not_null<ViewElement*> view) {
const auto i = _playingVideoFiles.find(view);
if (i != _playingVideoFiles.end()) {
if (!--i->second) {
_playingVideoFiles.erase(i);
unregisterHeavyViewPart(view);
}
} else {
unregisterHeavyViewPart(view);
}
}
void Session::stopPlayingVideoFiles() {
for (const auto &[view, count] : base::take(_playingVideoFiles)) {
void Session::checkPlayingAnimations() {
auto check = base::flat_set<not_null<ViewElement*>>();
for (const auto view : _heavyViewParts) {
if (const auto media = view->media()) {
media->stopAnimation();
}
}
}
void Session::checkPlayingVideoFiles() {
const auto old = base::take(_playingVideoFiles);
for (const auto &[view, count] : old) {
if (const auto media = view->media()) {
if (const auto left = media->checkAnimationCount()) {
_playingVideoFiles.emplace(view, left);
registerHeavyViewPart(view);
continue;
if (const auto document = media->getDocument()) {
if (document->isAnimation() || document->isVideoFile()) {
check.emplace(view);
}
}
}
unregisterHeavyViewPart(view);
}
for (const auto view : check) {
view->media()->checkAnimation();
}
}
@@ -3300,6 +3331,8 @@ void Session::registerItemView(not_null<ViewElement*> view) {
}
void Session::unregisterItemView(not_null<ViewElement*> view) {
Expects(!_heavyViewParts.contains(view));
const auto i = _views.find(view->data());
if (i != end(_views)) {
auto &list = i->second;
@@ -3763,10 +3796,7 @@ void Session::setWallpapers(const QVector<MTPWallPaper> &data, int32 hash) {
_wallpapers.push_back(Data::Legacy1DefaultWallPaper());
_wallpapers.back().setLocalImageAsThumbnail(std::make_shared<Image>(
std::make_unique<Images::LocalFileSource>(
qsl(":/gui/art/bg_initial.jpg"),
QByteArray(),
"JPG")));
u":/gui/art/bg_initial.jpg"_q));
for (const auto &paper : data) {
if (const auto parsed = Data::WallPaper::Create(paper)) {
_wallpapers.push_back(*parsed);
@@ -3778,10 +3808,7 @@ void Session::setWallpapers(const QVector<MTPWallPaper> &data, int32 hash) {
if (defaultFound == end(_wallpapers)) {
_wallpapers.push_back(Data::DefaultWallPaper());
_wallpapers.back().setLocalImageAsThumbnail(std::make_shared<Image>(
std::make_unique<Images::LocalFileSource>(
qsl(":/gui/arg/bg.jpg"),
QByteArray(),
"JPG")));
u":/gui/arg/bg.jpg"_q));
}
}

View File

@@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "storage/storage_databases.h"
#include "chat_helpers/stickers.h"
#include "chat_helpers/stickers_set.h"
#include "dialogs/dialogs_key.h"
#include "dialogs/dialogs_indexed_list.h"
#include "dialogs/dialogs_main_list.h"
@@ -63,6 +63,8 @@ class CloudThemes;
class Streaming;
class MediaRotation;
class Histories;
class DocumentMedia;
class PhotoMedia;
class Session final {
public:
@@ -110,6 +112,9 @@ public:
void clear();
void keepAlive(std::shared_ptr<PhotoMedia> media);
void keepAlive(std::shared_ptr<DocumentMedia> media);
void startExport(PeerData *peer = nullptr);
void startExport(const MTPInputPeer &singlePeer);
void suggestStartExport(TimeId availableAt);
@@ -427,12 +432,21 @@ public:
void documentLoadSettingsChanged();
void notifyPhotoLayoutChanged(not_null<const PhotoData*> photo);
void requestPhotoViewRepaint(not_null<const PhotoData*> photo);
void notifyDocumentLayoutChanged(
not_null<const DocumentData*> document);
void requestDocumentViewRepaint(not_null<const DocumentData*> document);
void markMediaRead(not_null<const DocumentData*> document);
void requestPollViewRepaint(not_null<const PollData*> poll);
void photoLoadProgress(not_null<PhotoData*> photo);
void photoLoadDone(not_null<PhotoData*> photo);
void photoLoadFail(not_null<PhotoData*> photo, bool started);
void documentLoadProgress(not_null<DocumentData*> document);
void documentLoadDone(not_null<DocumentData*> document);
void documentLoadFail(not_null<DocumentData*> document, bool started);
HistoryItem *addNewMessage(
const MTPMessage &data,
MTPDmessage_ClientFlags flags,
@@ -469,24 +483,23 @@ public:
TimeId date,
int32 dc,
bool hasSticker,
const ImagePtr &thumbnailInline,
const ImagePtr &thumbnailSmall,
const ImagePtr &thumbnail,
const ImagePtr &large);
const QByteArray &inlineThumbnailBytes,
const ImageWithLocation &small,
const ImageWithLocation &thumbnail,
const ImageWithLocation &large);
void photoConvert(
not_null<PhotoData*> original,
const MTPPhoto &data);
[[nodiscard]] PhotoData *photoFromWeb(
const MTPWebDocument &data,
ImagePtr thumbnail = ImagePtr(),
bool willBecomeNormal = false);
const ImageLocation &thumbnailLocation);
[[nodiscard]] not_null<DocumentData*> document(DocumentId id);
not_null<DocumentData*> processDocument(const MTPDocument &data);
not_null<DocumentData*> processDocument(const MTPDdocument &data);
not_null<DocumentData*> processDocument(
const MTPdocument &data,
QImage &&thumb);
const ImageWithLocation &thumbnail);
[[nodiscard]] not_null<DocumentData*> document(
DocumentId id,
const uint64 &access,
@@ -494,17 +507,18 @@ public:
TimeId date,
const QVector<MTPDocumentAttribute> &attributes,
const QString &mime,
const ImagePtr &thumbnailInline,
const ImagePtr &thumbnail,
const QByteArray &inlineThumbnailBytes,
const ImageWithLocation &thumbnail,
const ImageWithLocation &videoThumbnail,
int32 dc,
int32 size,
const StorageImageLocation &thumbLocation);
int32 size);
void documentConvert(
not_null<DocumentData*> original,
const MTPDocument &data);
[[nodiscard]] DocumentData *documentFromWeb(
const MTPWebDocument &data,
ImagePtr thumb);
const ImageLocation &thumbnailLocation,
const ImageLocation &videoThumbnailLocation);
[[nodiscard]] not_null<WebPageData*> webpage(WebPageId id);
not_null<WebPageData*> processWebpage(const MTPWebPage &data);
@@ -547,7 +561,7 @@ public:
not_null<PollData*> processPoll(const MTPPoll &data);
not_null<PollData*> processPoll(const MTPDmessageMediaPoll &data);
[[nodiscard]] not_null<LocationThumbnail*> location(
[[nodiscard]] not_null<Data::CloudImage*> location(
const LocationPoint &point);
void registerPhotoItem(
@@ -599,10 +613,7 @@ public:
UserId contactId,
not_null<HistoryItem*> item);
void registerPlayingVideoFile(not_null<ViewElement*> view);
void unregisterPlayingVideoFile(not_null<ViewElement*> view);
void checkPlayingVideoFiles();
void stopPlayingVideoFiles();
void checkPlayingAnimations();
HistoryItem *findWebPageItem(not_null<WebPageData*> page) const;
QString findContactPhone(not_null<UserData*> contact) const;
@@ -731,10 +742,10 @@ private:
TimeId date,
int32 dc,
bool hasSticker,
const ImagePtr &thumbnailInline,
const ImagePtr &thumbnailSmall,
const ImagePtr &thumbnail,
const ImagePtr &large);
const QByteArray &inlineThumbnailBytes,
const ImageWithLocation &small,
const ImageWithLocation &thumbnail,
const ImageWithLocation &large);
void documentApplyFields(
not_null<DocumentData*> document,
@@ -749,17 +760,19 @@ private:
TimeId date,
const QVector<MTPDocumentAttribute> &attributes,
const QString &mime,
const ImagePtr &thumbnailInline,
const ImagePtr &thumbnail,
const QByteArray &inlineThumbnailBytes,
const ImageWithLocation &thumbnail,
const ImageWithLocation &videoThumbnail,
int32 dc,
int32 size,
const StorageImageLocation &thumbLocation);
int32 size);
DocumentData *documentFromWeb(
const MTPDwebDocument &data,
ImagePtr thumb);
const ImageLocation &thumbnailLocation,
const ImageLocation &videoThumbnailLocation);
DocumentData *documentFromWeb(
const MTPDwebDocumentNoProxy &data,
ImagePtr thumb);
const ImageLocation &thumbnailLocation,
const ImageLocation &videoThumbnailLocation);
void webpageApplyFields(
not_null<WebPageData*> page,
@@ -923,7 +936,7 @@ private:
base::flat_set<not_null<ViewElement*>>> _webpageViews;
std::unordered_map<
LocationPoint,
std::unique_ptr<LocationThumbnail>> _locations;
std::unique_ptr<Data::CloudImage>> _locations;
std::unordered_map<
PollId,
std::unique_ptr<PollData>> _polls;
@@ -942,7 +955,6 @@ private:
std::unordered_map<
UserId,
base::flat_set<not_null<ViewElement*>>> _contactViews;
base::flat_map<not_null<ViewElement*>, int> _playingVideoFiles;
base::flat_set<not_null<WebPageData*>> _webpagesUpdated;
base::flat_set<not_null<GameData*>> _gamesUpdated;

Some files were not shown because too many files have changed in this diff Show More