Compare commits

...

61 Commits

Author SHA1 Message Date
John Preston
c1f3fe1961 Version 2.1.
- Access a catalog of over 20,000 stickers made by professional
artists from the updated Sticker Panel by clicking the '+' icon.
- Use sticker search to find the stickers you're looking for - or
scroll from the latest packs all the way to the classics.
- Add explanations that appear after users respond to a quiz question.
- See how much time you have left to answer a question from @QuizBot
with the new countdown animation.
- Send a single 🎯 emoji to see if you hit the bullseye.
2020-04-24 09:08:08 +04:00
John Preston
cfd733c54c Add confirmation box for suspicious urls. 2020-04-23 19:00:19 +04:00
John Preston
3fa5e004fe Allow editing messages in channels indefinitely. 2020-04-23 16:21:30 +04:00
John Preston
862e4e45ad Closed alpha version 2.0.1.3. 2020-04-21 18:55:56 +04:00
John Preston
53df4d1b10 Fix magic for initConnection. 2020-04-21 18:55:35 +04:00
John Preston
5cfd402b70 Make darker toasts for quiz solutions. 2020-04-21 18:55:35 +04:00
John Preston
57e9651a8a Fix visual glitch in poll results viewing.
We need a visible widget to mark more button height.
An invisible 'more' button doesn't receive geometry change events.
2020-04-21 18:55:35 +04:00
John Preston
53d206c12c Custom tab-order for create poll box. 2020-04-21 18:55:35 +04:00
John Preston
46f3cf3395 Load more official sets while scrolling. 2020-04-21 18:55:35 +04:00
John Preston
dfc0491524 Improve trending stickers layout and position. 2020-04-21 18:55:35 +04:00
John Preston
54f757e770 Allow sending dice from dice media tooltip. 2020-04-21 18:55:35 +04:00
John Preston
abfd3ad1b9 Use built-in zero-value dice animations. 2020-04-21 18:55:35 +04:00
John Preston
bed208d621 Send dice media based on appconfig. 2020-04-21 18:55:35 +04:00
John Preston
33c453a13c Scroll history to bottom on sending a message. 2020-04-21 18:55:35 +04:00
John Preston
e118972d5c Support generic dice media display. 2020-04-21 18:55:35 +04:00
John Preston
fb8a9a930c Closed alpha version 2.0.1.2. 2020-04-21 18:55:35 +04:00
John Preston
7a9cfcc40d Improve poll closing by timer and results reloading. 2020-04-21 18:55:35 +04:00
John Preston
e1dc15321a Work around 32-bitness of GetLastInputInfo.
Fixes #7637.
2020-04-21 18:55:35 +04:00
John Preston
2b5e575b67 Closed alpha version 2.0.1.1: Fix macOS fonts. 2020-04-21 18:55:34 +04:00
John Preston
42e216603c Closed alpha version 2.0.1.1. 2020-04-21 18:55:34 +04:00
23rd
d46e145c61 Updated Qt to 5.12.8. 2020-04-21 18:55:34 +04:00
John Preston
5dcb232b77 Force reload results on auto-closed quiz. 2020-04-21 18:55:34 +04:00
John Preston
b34d5b8306 Check solution length in CreatePollBox. 2020-04-21 18:55:34 +04:00
John Preston
76d81ff197 Improve polls solution icon color. 2020-04-21 18:55:34 +04:00
John Preston
71637d2a0e Show progress left to close by timer in polls. 2020-04-21 18:55:34 +04:00
John Preston
423daecbde Add view solution button to polls. 2020-04-21 18:55:34 +04:00
John Preston
3cb76fb80b Support poll closing by date. 2020-04-21 18:55:34 +04:00
John Preston
6882093ed1 Send init connection params. 2020-04-21 18:55:34 +04:00
John Preston
699761b42f Support poll solution display in a toast. 2020-04-21 18:55:34 +04:00
John Preston
f50c50a152 Fix path choosing for Windows Store version. 2020-04-21 18:55:34 +04:00
John Preston
13d22947df Send poll solution with entities. 2020-04-21 18:55:34 +04:00
John Preston
6c08bab550 Add explanation block to CreatePollBox. 2020-04-21 18:55:34 +04:00
John Preston
3e2f4bed50 Update scheme to layer 102.
Support different dice-like media.
2020-04-21 18:55:34 +04:00
Ilya Fedin
41d39012d2 Synchronize AppMenu availability check with Qt 2020-04-21 14:06:03 +04:00
Ilya Fedin
a7764f84f0 Proper usage of pkg-config 2020-04-21 14:05:22 +04:00
VictorienXP
85fcec2fb5 Add .opus and .oga files as song formats 2020-04-21 14:04:51 +04:00
Ilya Fedin
82e835fbc2 Fix snap action 2020-04-20 10:52:10 +04:00
Ilya Fedin
80684d9073 Remove unnecessary files from snap 2020-04-20 10:52:10 +04:00
Ilya Fedin
b04f0e0d3d Use kde-neon extension for better desktop integration 2020-04-20 10:52:10 +04:00
23rd
65cc9bcd87 Updated parser of issue closer since template was changed.
The issue template was changed in de78f4255e.
2020-04-13 17:18:59 +03:00
Ilya Fedin
bc06a3aea3 Make actions ignore .md files not only in the root of repository 2020-04-13 17:39:00 +04:00
Ilya Fedin
de78f4255e Add installation method to bug report template 2020-04-13 17:39:00 +04:00
John Preston
d67dafaccb Fix check for 4K frame size in streaming. 2020-04-13 15:32:20 +04:00
John Preston
4f8ea4c807 Allow to play in-app large videos. 2020-04-13 15:32:14 +04:00
John Preston
15b19f8565 Allow sending large images again (up to 108MP). 2020-04-13 15:32:06 +04:00
John Preston
b16696db93 Don't scroll down when read from another device. 2020-04-13 15:31:54 +04:00
John Preston
63129072ba Mark voice/video message as read on mention click.
Fixes #5623.
2020-04-13 15:30:56 +04:00
John Preston
1fdd591aa0 Change manage folders button icon. 2020-04-13 15:30:40 +04:00
John Preston
f370ca97d0 Fix folders visibility above passcode lock. 2020-04-13 15:30:23 +04:00
John Preston
f5aba5a907 Fix build with new lib_ui commits. 2020-04-13 15:26:09 +04:00
Ilya Fedin
1d613995db Disable building of dav1d tools and tests in snap 2020-04-13 15:16:33 +04:00
Ilya Fedin
5bb1c77199 Use OpenAL without direct channels 2020-04-13 15:15:29 +04:00
Ilya Fedin
5b39c7013a Better algorithm for font choosing 2020-04-13 11:48:14 +04:00
Ilya Fedin
ed91c07f99 Restore the old behavior with fallback fontconfig configuration
With current code fallback works only through time and replaces the config even if it is changed by the user.

This commit fixes that.
2020-04-13 10:49:30 +04:00
Ilya Fedin
a66b2a4056 Synchronize snap ffmpeg arguments with generic linux one 2020-04-13 10:44:29 +04:00
Ilya Fedin
a1a7399023 Don't remove SNI object when SNI is lost 2020-04-13 10:43:37 +04:00
Ilya Fedin
e71b7dd384 Don't overwrite artifacts by multiple runs 2020-04-13 10:42:32 +04:00
Ilya Fedin
664b43acd7 Fixes for linux action:
* Disable building of unneeded openal tools and tests
* Disable ffmpeg linkage with unneeded libraries
* Disable unneeded dtd validation for libwayland
* Omit Qt flags that set to default values
* Fix prefix usage
* Build dependencies in release mode to reduce build size
2020-04-12 19:21:59 +04:00
Ilya Fedin
eac867ce85 Add possibility to enable autoupdate on non-special target 2020-04-10 15:06:09 +04:00
John Preston
2ad48f18f2 Use only safe file saving in localstorage. 2020-04-02 18:31:15 +04:00
John Preston
e823fe5891 Fix support / media shortcuts. 2020-04-02 16:20:53 +04:00
114 changed files with 1651 additions and 670 deletions

View File

@@ -25,6 +25,8 @@ Tell us what happens instead
**Version of Telegram Desktop:**
**Installation source (Linux Only)** - the official website / GitHub releases / flatpak / snap / distribution package:
**Used theme**:
<details><summary><b>Logs</b>:</summary>

View File

@@ -21,14 +21,24 @@ jobs:
script: |
let errorStr = "Version not found.";
function maxIndexOf(str, i) {
let index = str.indexOf(i);
return (index == -1) ? Number.MAX_SAFE_INTEGER : index;
}
let item1 = "Version of Telegram Desktop";
let item2 = "Used theme";
let item2 = "Installation source";
let item3 = "Used theme";
let item4 = "<details>";
let body = context.payload.issue.body;
console.log("Body of issue:\n" + body);
let index1 = body.indexOf(item1);
let index2 = body.indexOf(item2);
index2 = (index2 == -1) ? Number.MAX_SAFE_INTEGER : index2;
let index2 = Math.min(
Math.min(
maxIndexOf(body, item2),
maxIndexOf(body, item3)),
maxIndexOf(body, item4));
console.log("Index 1: " + index1);
console.log("Index 2: " + index2);

View File

@@ -4,11 +4,11 @@ on:
push:
paths-ignore:
- 'docs/**'
- '*.md'
- '**.md'
pull_request:
paths-ignore:
- 'docs/**'
- '*.md'
- '**.md'
jobs:
@@ -25,8 +25,8 @@ jobs:
env:
GIT: "https://github.com"
QT: "5_12_5"
QT_PREFIX: "/usr/local/desktop-app/Qt-5.12.5"
QT: "5_12_8"
QT_PREFIX: "/usr/local/desktop-app/Qt-5.12.8"
OPENSSL_VER: "1_1_1"
OPENSSL_PREFIX: "/usr/local/desktop-app/openssl-1.1.1"
CMAKE_VER: "3.17.0"
@@ -189,23 +189,28 @@ jobs:
git clone --branch release/3.4 $GIT/FFmpeg/FFmpeg ffmpeg
cd ffmpeg
./configure --prefix=$LibrariesPath/ffmpeg-cache \
--enable-protocol=file --enable-libopus \
./configure \
--disable-debug \
--disable-programs \
--disable-doc \
--disable-network \
--disable-autodetect \
--disable-everything \
--disable-neon \
--disable-iconv \
--enable-libopus \
--enable-vaapi \
--enable-vdpau \
--enable-protocol=file \
--enable-hwaccel=h264_vaapi \
--enable-hwaccel=h264_vdpau \
--enable-hwaccel=mpeg4_vaapi \
--enable-hwaccel=mpeg4_vdpau \
--enable-decoder=aac \
--enable-decoder=aac_at \
--enable-decoder=aac_fixed \
--enable-decoder=aac_latm \
--enable-decoder=aasc \
--enable-decoder=alac \
--enable-decoder=alac_at \
--enable-decoder=flac \
--enable-decoder=gif \
--enable-decoder=h264 \
@@ -227,14 +232,12 @@ jobs:
--enable-decoder=msmpeg4v3 \
--enable-decoder=opus \
--enable-decoder=pcm_alaw \
--enable-decoder=pcm_alaw_at \
--enable-decoder=pcm_f32be \
--enable-decoder=pcm_f32le \
--enable-decoder=pcm_f64be \
--enable-decoder=pcm_f64le \
--enable-decoder=pcm_lxf \
--enable-decoder=pcm_mulaw \
--enable-decoder=pcm_mulaw_at \
--enable-decoder=pcm_s16be \
--enable-decoder=pcm_s16be_planar \
--enable-decoder=pcm_s16le \
@@ -289,7 +292,7 @@ jobs:
--enable-muxer=opus
make -j$(nproc)
sudo make install
sudo make DESTDIR="$LibrariesPath/ffmpeg-cache" install
cd ..
rm -rf ffmpeg
- name: FFmpeg install.
@@ -298,7 +301,7 @@ jobs:
#List of files from cmake/external/ffmpeg/CMakeLists.txt.
copyLib() {
mkdir -p ffmpeg/$1
yes | cp -i ffmpeg-cache/lib/$1.a ffmpeg/$1/$1.a
yes | cp -i ffmpeg-cache/usr/local/lib/$1.a ffmpeg/$1/$1.a
}
copyLib libavformat
copyLib libavcodec
@@ -306,7 +309,7 @@ jobs:
copyLib libswscale
copyLib libavutil
sudo cp -R ffmpeg-cache/. /usr/local/
sudo cp -R ffmpeg-cache/. /
- name: PortAudio.
run: |
@@ -327,7 +330,13 @@ jobs:
git clone -b openal-soft-1.20.1 --depth=1 $GIT/kcat/openal-soft.git
cd openal-soft/build
cmake -D LIBTYPE:STRING=STATIC ..
cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DLIBTYPE:STRING=STATIC \
-DALSOFT_EXAMPLES=OFF \
-DALSOFT_TESTS=OFF \
-DALSOFT_UTILS=OFF \
-DALSOFT_CONFIG=OFF
make -j$(nproc)
sudo make install
cd -
@@ -348,16 +357,15 @@ jobs:
git clone -b OpenSSL_${OPENSSL_VER}-stable --depth=1 \
$GIT/openssl/openssl $opensslDir
cd $opensslDir
./config --prefix=$LibrariesPath/openssl-cache
./config --prefix="$OPENSSL_PREFIX"
make -j$(nproc)
sudo make install_sw
sudo make DESTDIR="$LibrariesPath/openssl-cache" install_sw
cd ..
rm -rf $opensslDir
- name: OpenSSL install.
run: |
cd $LibrariesPath
sudo mkdir -p $OPENSSL_PREFIX
sudo cp -R openssl-cache/. $OPENSSL_PREFIX/
sudo cp -R openssl-cache/. /
- name: Libxkbcommon.
run: |
@@ -377,24 +385,24 @@ jobs:
git clone -b 1.16 https://gitlab.freedesktop.org/wayland/wayland
cd wayland
./autogen.sh --enable-static --disable-documentation
./autogen.sh --enable-static --disable-documentation --disable-dtd-validation
make -j$(nproc)
sudo make install
cd ..
rm -rf wayland
- name: Qt 5.12.5 cache.
- 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_5.diff') }}
- name: Qt 5.12.5 build.
key: ${{ runner.OS }}-qt-${{ env.CACHE_KEY }}-${{ hashFiles('**/qtbase_5_12_8.diff') }}
- name: Qt 5.12.8 build.
if: steps.cache-qt.outputs.cache-hit != 'true'
run: |
cd $LibrariesPath
git clone -b v5.12.5 --depth=1 git://code.qt.io/qt/qt5.git qt_${QT}
git clone -b v5.12.8 --depth=1 git://code.qt.io/qt/qt5.git qt_${QT}
cd qt_${QT}
perl init-repository --module-subset=qtbase,qtwayland,qtimageformats,qtsvg
git submodule update qtbase qtwayland qtimageformats qtsvg
@@ -406,9 +414,8 @@ jobs:
git clone $GIT/desktop-app/nimf.git
cd ../../../..
./configure -prefix "$LibrariesPath/qt-cache" \
./configure -prefix "$QT_PREFIX" \
-release \
-force-debug-info \
-opensource \
-confirm-license \
-qt-zlib \
@@ -417,8 +424,6 @@ jobs:
-qt-harfbuzz \
-qt-pcre \
-qt-xcb \
-system-freetype \
-fontconfig \
-no-gtk \
-static \
-dbus-runtime \
@@ -428,14 +433,13 @@ jobs:
-nomake tests
make -j$(nproc)
sudo make install
sudo make INSTALL_ROOT="$LibrariesPath/qt-cache" install
cd ..
rm -rf qt_${QT}
- name: Qt 5.12.5 install.
- name: Qt 5.12.8 install.
run: |
cd $LibrariesPath
sudo mkdir -p $QT_PREFIX
sudo cp -R qt-cache/. $QT_PREFIX/
sudo cp -R qt-cache/. /
- name: Breakpad cache.
id: cache-breakpad
@@ -467,9 +471,9 @@ jobs:
cd ..
cd breakpad
./configure --prefix=$BreakpadCache
./configure
make -j$(nproc)
sudo make install
sudo make DESTDIR="$BreakpadCache" install
cd src
rm -r testing
git clone $GIT/google/googletest testing
@@ -486,7 +490,7 @@ jobs:
- name: Breakpad install.
run: |
cd $LibrariesPath
sudo cp -R breakpad-cache/. /usr/local/
sudo cp -R breakpad-cache/. /
mkdir -p breakpad/out/Default/
cp breakpad-cache/dump_syms breakpad/out/Default/dump_syms
@@ -499,6 +503,9 @@ jobs:
if [ -n "${{ matrix.defines }}" ]; then
DEFINE="-D ${{ matrix.defines }}=ON"
echo Define from matrix: $DEFINE
echo ::set-env name=ARTIFACT_NAME::Telegram_${{ matrix.defines }}
else
echo ::set-env name=ARTIFACT_NAME::Telegram
fi
./configure.sh \
@@ -534,5 +541,5 @@ jobs:
if: env.UPLOAD_ARTIFACT == 'true'
name: Upload artifact.
with:
name: Telegram
name: ${{ env.ARTIFACT_NAME }}
path: ${{ env.REPO_NAME }}/out/Debug/bin/artifact/

View File

@@ -4,11 +4,11 @@ on:
push:
paths-ignore:
- 'docs/**'
- '*.md'
- '**.md'
pull_request:
paths-ignore:
- 'docs/**'
- '*.md'
- '**.md'
jobs:
@@ -27,9 +27,9 @@ jobs:
PREFIX: "/usr/local/macos"
MACOSX_DEPLOYMENT_TARGET: "10.12"
XZ: "xz-5.2.4"
QT: "5_12_5"
QT: "5_12_8"
OPENSSL_VER: "1_1_1"
QT_PREFIX: "/usr/local/desktop-app/Qt-5.12.5"
QT_PREFIX: "/usr/local/desktop-app/Qt-5.12.8"
LIBICONV_VER: "libiconv-1.16"
UPLOAD_ARTIFACT: "false"
ONLY_CACHE: "false"
@@ -376,20 +376,20 @@ jobs:
build/gyp_crashpad.py -Dmac_deployment_target=10.10
ninja -C out/Debug
- name: Qt 5.12.5 cache.
- 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_5.diff') }}
- name: Use cached Qt 5.12.5.
key: ${{ runner.OS }}-qt-${{ env.CACHE_KEY }}-${{ hashFiles('**/qtbase_5_12_8.diff') }}
- name: Use cached Qt 5.12.8.
if: steps.cache-qt.outputs.cache-hit == 'true'
run: |
cd $LibrariesPath
mv qt-cache Qt-5.12.5
mv qt-cache Qt-5.12.8
sudo mkdir -p $QT_PREFIX
sudo mv -f Qt-5.12.5 "$(dirname "$QT_PREFIX")"/
- name: Qt 5.12.5 build.
sudo mv -f Qt-5.12.8 "$(dirname "$QT_PREFIX")"/
- name: Qt 5.12.8 build.
if: steps.cache-qt.outputs.cache-hit != 'true'
run: |
cd $LibrariesPath
@@ -397,7 +397,7 @@ jobs:
git clone git://code.qt.io/qt/qt5.git qt$QT
cd qt$QT
perl init-repository --module-subset=qtbase,qtimageformats
git checkout v5.12.5
git checkout v5.12.8
git submodule update qtbase
git submodule update qtimageformats
cd qtbase
@@ -433,6 +433,9 @@ jobs:
if [ -n "${{ matrix.defines }}" ]; then
DEFINE="-D ${{ matrix.defines }}=ON"
echo Define from matrix: $DEFINE
echo ::set-env name=ARTIFACT_NAME::Telegram_${{ matrix.defines }}
else
echo ::set-env name=ARTIFACT_NAME::Telegram
fi
./configure.sh -D TDESKTOP_API_TEST=ON -D DESKTOP_APP_USE_PACKAGED=OFF $DEFINE
@@ -453,5 +456,5 @@ jobs:
if: env.UPLOAD_ARTIFACT == 'true'
name: Upload artifact.
with:
name: Telegram
name: ${{ env.ARTIFACT_NAME }}
path: ${{ env.REPO_NAME }}/out/Debug/artifact/

View File

@@ -4,11 +4,11 @@ on:
push:
paths-ignore:
- 'docs/**'
- '*.md'
- '**.md'
pull_request:
paths-ignore:
- 'docs/**'
- '*.md'
- '**.md'
jobs:
@@ -19,7 +19,7 @@ jobs:
env:
UPLOAD_ARTIFACT: "false"
ONLY_CACHE: "false"
MANUAL_CACHING: "3"
MANUAL_CACHING: "4"
steps:
- name: Clone.
@@ -29,9 +29,6 @@ jobs:
- name: First set up.
run: |
# Workaround for Heroku
curl https://cli-assets.heroku.com/apt/release.key | sudo apt-key add -
sudo apt-get update
sudo apt-get install gcc-8 g++-8 -y
sudo snap install --classic snapcraft
@@ -50,7 +47,7 @@ jobs:
md5 $keyName
}
snapcraft --version > CACHE_KEY.txt
snap run snapcraft --version > CACHE_KEY.txt
gcc-8 --version >> CACHE_KEY.txt
echo $MANUAL_CACHING >> CACHE_KEY.txt
md5 CACHE_KEY
@@ -67,7 +64,7 @@ jobs:
- name: CMake build.
if: steps.cache-cmake.outputs.cache-hit != 'true'
run: sudo snapcraft build --destructive-mode cmake
run: sudo snap run snapcraft build --destructive-mode cmake
- name: FFmpeg cache.
id: cache-ffmpeg
@@ -78,11 +75,11 @@ jobs:
- name: FFmpeg build.
if: steps.cache-ffmpeg.outputs.cache-hit != 'true'
run: sudo snapcraft build --destructive-mode ffmpeg
run: sudo snap run snapcraft build --destructive-mode ffmpeg
- name: Telegram Desktop snap build.
if: env.ONLY_CACHE == 'false'
run: sudo snapcraft --destructive-mode
run: sudo snap run snapcraft --destructive-mode
- name: Move artifact.
if: env.UPLOAD_ARTIFACT == 'true'
@@ -102,5 +99,5 @@ jobs:
- name: Remove unneeded directories for cache.
run: |
sudo rm -rf parts/{cmake,ffmpeg}/{build,src,ubuntu}
sudo rm -rf parts/{cmake,ffmpeg}/state/{stage,prime}
sudo rm -rf parts/*/{build,src,ubuntu}
sudo rm -rf parts/*/state/{stage,prime}

View File

@@ -4,11 +4,11 @@ on:
push:
paths-ignore:
- 'docs/**'
- '*.md'
- '**.md'
pull_request:
paths-ignore:
- 'docs/**'
- '*.md'
- '**.md'
jobs:
@@ -24,7 +24,7 @@ jobs:
SDK: "10.0.18362.0"
VC: "call vcvars32.bat && cd Libraries"
GIT: "https://github.com"
QT: "5_12_5"
QT: "5_12_8"
OPENSSL_VER: "1_1_1"
UPLOAD_ARTIFACT: "false"
ONLY_CACHE: "false"
@@ -263,13 +263,13 @@ jobs:
rmdir /S /Q .git
- name: Qt 5.12.5 cache.
- name: Qt 5.12.8 cache.
id: cache-qt
uses: actions/cache@v1
with:
path: ${{ env.LibrariesPath }}/Qt-5.12.5
key: ${{ runner.OS }}-qt-${{ env.CACHE_KEY }}-${{ hashFiles('**/qtbase_5_12_5.diff') }}
- name: Configure Qt 5.12.5.
path: ${{ env.LibrariesPath }}/Qt-5.12.8
key: ${{ runner.OS }}-qt-${{ env.CACHE_KEY }}-${{ hashFiles('**/qtbase_5_12_8.diff') }}
- name: Configure Qt 5.12.8.
if: steps.cache-qt.outputs.cache-hit != 'true'
shell: cmd
run: |
@@ -278,7 +278,7 @@ jobs:
git clone git://code.qt.io/qt/qt5.git qt_%QT%
cd qt_%QT%
perl init-repository --module-subset=qtbase,qtimageformats
git checkout v5.12.5
git checkout v5.12.8
git submodule update qtbase
git submodule update qtimageformats
cd qtbase
@@ -289,7 +289,7 @@ jobs:
SET LIBS=libcrypto.lib Ws2_32.lib Gdi32.lib Advapi32.lib Crypt32.lib User32.lib
configure ^
-prefix "%LibrariesPath%\Qt-5.12.5" ^
-prefix "%LibrariesPath%\Qt-5.12.8" ^
-debug ^
-force-debug-info ^
-opensource ^
@@ -304,7 +304,7 @@ jobs:
-nomake examples ^
-nomake tests ^
-platform win32-msvc
- name: Qt 5.12.5 build.
- name: Qt 5.12.8 build.
if: steps.cache-qt.outputs.cache-hit != 'true'
shell: cmd
run: |
@@ -324,6 +324,9 @@ jobs:
if [ -n "${{ matrix.defines }}" ]; then
DEFINE="-D ${{ matrix.defines }}=ON"
echo Define from matrix: $DEFINE
echo ::set-env name=ARTIFACT_NAME::Telegram_${{ matrix.defines }}
else
echo ::set-env name=ARTIFACT_NAME::Telegram
fi
echo "::set-env name=TDESKTOP_BUILD_DEFINE::$DEFINE"
@@ -355,5 +358,5 @@ jobs:
name: Upload artifact.
if: env.UPLOAD_ARTIFACT == 'true'
with:
name: Telegram
name: ${{ env.ARTIFACT_NAME }}
path: ${{ env.REPO_NAME }}\out\Debug\artifact\

View File

@@ -23,7 +23,7 @@ The source code is published under GPLv3 with OpenSSL exception, the license is
## Third-party
* Qt 5.12.5 and 5.6.2, slightly patched ([LGPL](http://doc.qt.io/qt-5/lgpl.html))
* Qt 5.12.8 and 5.6.2, slightly patched ([LGPL](http://doc.qt.io/qt-5/lgpl.html))
* OpenSSL 1.1.1 ([OpenSSL License](https://www.openssl.org/source/license.html))
* zlib 1.2.11 ([zlib License](http://www.zlib.net/zlib_license.html))
* LZMA SDK 9.20 ([public domain](http://www.7-zip.org/sdk.html))

View File

@@ -1187,7 +1187,7 @@ endif()
set_target_properties(Telegram PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_folder})
if ((NOT disable_autoupdate OR NOT LINUX) AND NOT build_macstore AND NOT build_winstore)
if ((NOT DESKTOP_APP_DISABLE_AUTOUPDATE OR NOT LINUX) AND NOT build_macstore AND NOT build_winstore)
add_executable(Updater WIN32)
init_target(Updater)

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 763 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 686 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 819 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 B

After

Width:  |  Height:  |  Size: 506 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 B

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 B

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1329,7 +1329,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_dialogs_skip_archive_in_search" = "Skip results from archive";
"lng_dialogs_show_archive_in_search" = "With results from archive";
"lng_about_dice" = "Send a 🎲 emoji to any chat to get a random number from Telegram.";
"lng_about_random" = "Send a {emoji} emoji to any chat to get a random number from Telegram.";
"lng_about_random_send" = "Send";
"lng_open_this_link" = "Open this link?";
"lng_open_link" = "Open";
@@ -2223,6 +2224,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_polls_choose_question" = "Please enter a question.";
"lng_polls_choose_answers" = "Please enter at least two options.";
"lng_polls_choose_correct" = "Please choose the correct answer.";
"lng_polls_solution_title" = "Explanation";
"lng_polls_solution_placeholder" = "Add a Comment (Optional)";
"lng_polls_solution_about" = "Users will see this comment after choosing a wrong answer, good for educational purposes.";
"lng_polls_poll_results_title" = "Poll results";
"lng_polls_quiz_results_title" = "Quiz results";

View File

@@ -48,6 +48,7 @@
<file alias="art/logo_256_no_margin.png">../../art/logo_256_no_margin.png</file>
<file alias="art/sunrise.jpg">../../art/sunrise.jpg</file>
<file alias="art/dice_idle.tgs">../../art/dice_idle.tgs</file>
<file alias="art/dart_idle.tgs">../../art/dart_idle.tgs</file>
<file alias="day-blue.tdesktop-theme">../../day-blue.tdesktop-theme</file>
<file alias="night.tdesktop-theme">../../night.tdesktop-theme</file>
<file alias="night-green.tdesktop-theme">../../night-green.tdesktop-theme</file>

View File

@@ -71,8 +71,8 @@ inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int =
inputMediaGame#d33f43f3 id:InputGame = InputMedia;
inputMediaInvoice#f4e096c3 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string = InputMedia;
inputMediaGeoLive#ce4e82fd flags:# stopped:flags.0?true geo_point:InputGeoPoint period:flags.1?int = InputMedia;
inputMediaPoll#abe9ca25 flags:# poll:Poll correct_answers:flags.0?Vector<bytes> = InputMedia;
inputMediaDice#aeffa807 = InputMedia;
inputMediaPoll#f94e5f1 flags:# poll:Poll correct_answers:flags.0?Vector<bytes> solution:flags.1?string solution_entities:flags.1?Vector<MessageEntity> = InputMedia;
inputMediaDice#e66fbf7b emoticon:string = InputMedia;
inputChatPhotoEmpty#1ca48f57 = InputChatPhoto;
inputChatUploadedPhoto#927c55b4 file:InputFile = InputChatPhoto;
@@ -157,7 +157,7 @@ messageMediaGame#fdb19008 game:Game = MessageMedia;
messageMediaInvoice#84551347 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string = MessageMedia;
messageMediaGeoLive#7c3c2609 geo:GeoPoint period:int = MessageMedia;
messageMediaPoll#4bd6e798 poll:Poll results:PollResults = MessageMedia;
messageMediaDice#638fe46b value:int = MessageMedia;
messageMediaDice#3f7ee58b value:int emoticon:string = MessageMedia;
messageActionEmpty#b6aef7b0 = MessageAction;
messageActionChatCreate#a6638b9a title:string users:Vector<int> = MessageAction;
@@ -533,7 +533,7 @@ inputStickerSetEmpty#ffb62b95 = InputStickerSet;
inputStickerSetID#9de7a269 id:long access_hash:long = InputStickerSet;
inputStickerSetShortName#861cc8a0 short_name:string = InputStickerSet;
inputStickerSetAnimatedEmoji#28703c8 = InputStickerSet;
inputStickerSetDice#79e21a53 = InputStickerSet;
inputStickerSetDice#e67f520e emoticon:string = InputStickerSet;
stickerSet#eeb46f27 flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumb:flags.4?PhotoSize thumb_dc_id:flags.4?int count:int hash:int = StickerSet;
@@ -692,8 +692,8 @@ contacts.topPeersDisabled#b52c939d = contacts.TopPeers;
draftMessageEmpty#1b0c841a flags:# date:flags.0?int = DraftMessage;
draftMessage#fd8e711f flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int message:string entities:flags.3?Vector<MessageEntity> date:int = DraftMessage;
messages.featuredStickersNotModified#4ede3cf = messages.FeaturedStickers;
messages.featuredStickers#f89d88e5 hash:int sets:Vector<StickerSetCovered> unread:Vector<long> = messages.FeaturedStickers;
messages.featuredStickersNotModified#c6dc0c66 count:int = messages.FeaturedStickers;
messages.featuredStickers#b6abc341 hash:int count:int sets:Vector<StickerSetCovered> unread:Vector<long> = messages.FeaturedStickers;
messages.recentStickersNotModified#b17f890 = messages.RecentStickers;
messages.recentStickers#22f3afb3 hash:int packs:Vector<StickerPack> stickers:Vector<Document> dates:Vector<int> = messages.RecentStickers;
@@ -1024,11 +1024,11 @@ help.userInfo#1eb3758 message:string entities:Vector<MessageEntity> author:strin
pollAnswer#6ca9c2e9 text:string option:bytes = PollAnswer;
poll#d5529d06 id:long flags:# closed:flags.0?true public_voters:flags.1?true multiple_choice:flags.2?true quiz:flags.3?true question:string answers:Vector<PollAnswer> = Poll;
poll#86e18161 id:long flags:# closed:flags.0?true public_voters:flags.1?true multiple_choice:flags.2?true quiz:flags.3?true question:string answers:Vector<PollAnswer> close_period:flags.4?int close_date:flags.5?int = Poll;
pollAnswerVoters#3b6ddad2 flags:# chosen:flags.0?true correct:flags.1?true option:bytes voters:int = PollAnswerVoters;
pollResults#c87024a2 flags:# min:flags.0?true results:flags.1?Vector<PollAnswerVoters> total_voters:flags.2?int recent_voters:flags.3?Vector<int> = PollResults;
pollResults#badcc1a3 flags:# min:flags.0?true results:flags.1?Vector<PollAnswerVoters> total_voters:flags.2?int recent_voters:flags.3?Vector<int> solution:flags.4?string solution_entities:flags.4?Vector<MessageEntity> = PollResults;
chatOnlines#f041e250 onlines:int = ChatOnlines;
@@ -1144,7 +1144,7 @@ stats.broadcastStats#bdf78394 period:StatsDateRangeDays followers:StatsAbsValueA
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
invokeAfterMsgs#3dc4b4f0 {X:Type} msg_ids:Vector<long> query:!X = X;
initConnection#785188b8 {X:Type} flags:# api_id:int device_model:string system_version:string app_version:string system_lang_code:string lang_pack:string lang_code:string proxy:flags.0?InputClientProxy query:!X = X;
initConnection#c1cd5ea9 {X:Type} flags:# api_id:int device_model:string system_version:string app_version:string system_lang_code:string lang_pack:string lang_code:string proxy:flags.0?InputClientProxy params:flags.1?JSONValue query:!X = X;
invokeWithLayer#da9b0d0d {X:Type} layer:int query:!X = X;
invokeWithoutUpdates#bf9459b7 {X:Type} query:!X = X;
invokeWithMessagesRange#365275f2 {X:Type} range:MessageRange query:!X = X;
@@ -1382,6 +1382,7 @@ messages.getDialogFilters#f19ed96d = Vector<DialogFilter>;
messages.getSuggestedDialogFilters#a29cd42c = Vector<DialogFilterSuggested>;
messages.updateDialogFilter#1ad4a04a flags:# id:int filter:flags.0?DialogFilter = Bool;
messages.updateDialogFiltersOrder#c563c1e4 order:Vector<int> = Bool;
messages.getOldFeaturedStickers#5fe7025b offset:int limit:int hash:int = messages.FeaturedStickers;
updates.getState#edd4882a = updates.State;
updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
@@ -1459,6 +1460,7 @@ channels.getInactiveChannels#11e831ee = messages.InactiveChats;
bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON;
bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool;
bots.setBotCommands#805d46f6 commands:Vector<BotCommand> = Bool;
payments.getPaymentForm#99f09745 msg_id:int = payments.PaymentForm;
payments.getPaymentReceipt#a092a980 msg_id:int = payments.PaymentReceipt;
@@ -1468,10 +1470,11 @@ payments.getSavedInfo#227d824b = payments.SavedInfo;
payments.clearSavedInfo#d83d70c1 flags:# credentials:flags.0?true info:flags.1?true = Bool;
payments.getBankCardData#2e79d779 number:string = payments.BankCardData;
stickers.createStickerSet#9bd86e6a flags:# masks:flags.0?true user_id:InputUser title:string short_name:string stickers:Vector<InputStickerSetItem> = messages.StickerSet;
stickers.createStickerSet#f1036780 flags:# masks:flags.0?true animated:flags.1?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector<InputStickerSetItem> = messages.StickerSet;
stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet;
stickers.changeStickerPosition#ffb6d4ca sticker:InputDocument position:int = messages.StickerSet;
stickers.addStickerToSet#8653febe stickerset:InputStickerSet sticker:InputStickerSetItem = messages.StickerSet;
stickers.setStickerSetThumb#9a364e30 stickerset:InputStickerSet thumb:InputDocument = messages.StickerSet;
phone.getCallConfig#55451fa9 = DataJSON;
phone.requestCall#42ff96ed flags:# video:flags.0?true user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
@@ -1491,7 +1494,7 @@ langpack.getLanguage#6a596502 lang_pack:string lang_code:string = LangPackLangua
folders.editPeerFolders#6847d0ab folder_peers:Vector<InputFolderPeer> = Updates;
folders.deleteFolder#1c295881 folder_id:int = Updates;
stats.getBroadcastStats#ab42441a flags:# dark:flags.0?true channel:InputChannel = stats.BroadcastStats;
stats.getBroadcastStats#e6300dba flags:# dark:flags.0?true channel:InputChannel tz_offset:int = stats.BroadcastStats;
stats.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph;
// LAYER 111
// LAYER 112

View File

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

View File

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

View File

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

View File

@@ -21,6 +21,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/message_field.h" // ConvertTextTagsToEntities.
#include "ui/text/text_entity.h" // TextWithEntities.
#include "main/main_session.h"
#include "main/main_account.h"
#include "main/main_app_config.h"
#include "mainwidget.h"
#include "apiwrap.h"
#include "app.h"
@@ -199,8 +201,23 @@ void SendExistingPhoto(
}
bool SendDice(Api::MessageToSend &message) {
static const auto kDiceString = QString::fromUtf8("\xF0\x9F\x8E\xB2");
if (message.textWithTags.text.midRef(0).trimmed() != kDiceString) {
const auto full = message.textWithTags.text.midRef(0).trimmed();
auto length = 0;
if (!Ui::Emoji::Find(full.data(), full.data() + full.size(), &length)
|| length != full.size()) {
return false;
}
auto &account = message.action.history->session().account();
auto &config = account.appConfig();
static const auto hardcoded = std::vector<QString>{
QString::fromUtf8("\xF0\x9F\x8E\xB2"),
QString::fromUtf8("\xF0\x9F\x8E\xAF")
};
const auto list = config.get<std::vector<QString>>(
"emojies_send_dice",
hardcoded);
const auto emoji = full.toString();
if (!ranges::contains(list, emoji)) {
return false;
}
const auto history = message.action.history;
@@ -266,7 +283,7 @@ bool SendDice(Api::MessageToSend &message) {
MTP_int(HistoryItem::NewMessageDate(
message.action.options.scheduled)),
MTP_string(),
MTP_messageMediaDice(MTP_int(0)),
MTP_messageMediaDice(MTP_int(0), MTP_string(emoji)),
MTPReplyMarkup(),
MTP_vector<MTPMessageEntity>(),
MTP_int(1),
@@ -284,7 +301,7 @@ bool SendDice(Api::MessageToSend &message) {
MTP_flags(sendFlags),
peer->input,
MTP_int(replyTo),
MTP_inputMediaDice(),
MTP_inputMediaDice(MTP_string(emoji)),
MTP_string(),
MTP_long(randomId),
MTPReplyMarkup(),

View File

@@ -23,6 +23,6 @@ void SendExistingPhoto(
Api::MessageToSend &&message,
not_null<PhotoData*> photo);
[[nodiscard]] bool SendDice(Api::MessageToSend &message);
bool SendDice(Api::MessageToSend &message);
} // namespace Api

View File

@@ -5754,16 +5754,6 @@ void ApiWrap::createPoll(
if (action.options.scheduled) {
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
}
const auto inputFlags = data.quiz()
? MTPDinputMediaPoll::Flag::f_correct_answers
: MTPDinputMediaPoll::Flag(0);
auto correct = QVector<MTPbytes>();
for (const auto &answer : data.answers) {
if (answer.correct) {
correct.push_back(MTP_bytes(answer.option));
}
}
auto &histories = history->owner().histories();
const auto requestType = Data::Histories::RequestType::Send;
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
@@ -5772,10 +5762,7 @@ void ApiWrap::createPoll(
MTP_flags(sendFlags),
peer->input,
MTP_int(replyTo),
MTP_inputMediaPoll(
MTP_flags(inputFlags),
PollDataToMTP(&data),
MTP_vector<MTPbytes>(correct)),
PollDataToInputMedia(&data),
MTP_string(),
MTP_long(rand_value<uint64>()),
MTPReplyMarkup(),
@@ -5852,25 +5839,12 @@ void ApiWrap::closePoll(not_null<HistoryItem*> item) {
if (!poll) {
return;
}
const auto inputFlags = poll->quiz()
? MTPDinputMediaPoll::Flag::f_correct_answers
: MTPDinputMediaPoll::Flag(0);
auto correct = QVector<MTPbytes>();
for (const auto &answer : poll->answers) {
if (answer.correct) {
correct.push_back(MTP_bytes(answer.option));
}
}
const auto requestId = request(MTPmessages_EditMessage(
MTP_flags(MTPmessages_EditMessage::Flag::f_media),
item->history()->peer->input,
MTP_int(item->id),
MTPstring(),
MTP_inputMediaPoll(
MTP_flags(inputFlags),
PollDataToMTP(poll, true),
MTP_vector<MTPbytes>(correct)),
PollDataToInputMedia(poll, true),
MTPReplyMarkup(),
MTPVector<MTPMessageEntity>(),
MTP_int(0) // schedule_date

View File

@@ -57,7 +57,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace {
constexpr auto kImageAreaLimit = 6'016 * 3'384;
constexpr auto kImageAreaLimit = 12'032 * 9'024;
App::LaunchState _launchState = App::Launched;

View File

@@ -800,7 +800,7 @@ themesMenuPosition: point(-2px, 25px);
createPollField: InputField(defaultInputField) {
font: boxTextFont;
textMargins: margins(0px, 0px, 0px, 0px);
textMargins: margins(0px, 4px, 0px, 4px);
textAlign: align(left);
heightMin: 36px;
heightMax: 86px;
@@ -822,6 +822,11 @@ createPollOptionField: InputField(createPollField) {
placeholderMargins: margins(2px, 0px, 2px, 0px);
heightMax: 68px;
}
createPollSolutionField: InputField(createPollField) {
textMargins: margins(0px, 4px, 0px, 4px);
border: 1px;
borderActive: 2px;
}
createPollLimitLabel: FlatLabel(defaultFlatLabel) {
minWidth: 274px;
align: align(topleft);

View File

@@ -38,6 +38,8 @@ constexpr auto kMaxOptionsCount = PollData::kMaxOptions;
constexpr auto kOptionLimit = 100;
constexpr auto kWarnQuestionLimit = 80;
constexpr auto kWarnOptionLimit = 30;
constexpr auto kSolutionLimit = 200;
constexpr auto kWarnSolutionLimit = 60;
constexpr auto kErrorLimit = 99;
class Options {
@@ -59,6 +61,7 @@ public:
[[nodiscard]] rpl::producer<int> usedCount() const;
[[nodiscard]] rpl::producer<not_null<QWidget*>> scrollToWidget() const;
[[nodiscard]] rpl::producer<> backspaceInFront() const;
[[nodiscard]] rpl::producer<> tabbed() const;
private:
class Option {
@@ -146,6 +149,7 @@ private:
bool _hasCorrect = false;
rpl::event_stream<not_null<QWidget*>> _scrollToWidget;
rpl::event_stream<> _backspaceInFront;
rpl::event_stream<> _tabbed;
};
@@ -217,6 +221,7 @@ Options::Option::Option(
InitField(outer, _field, session);
_field->setMaxLength(kOptionLimit + kErrorLimit);
_field->show();
_field->customTab(true);
_wrap->hide(anim::type::instant);
@@ -497,6 +502,10 @@ rpl::producer<> Options::backspaceInFront() const {
return _backspaceInFront.events();
}
rpl::producer<> Options::tabbed() const {
return _tabbed.events();
}
void Options::Option::show(anim::type animated) {
_wrap->show(animated);
}
@@ -647,6 +656,14 @@ void Options::addEmptyOption() {
QObject::connect(field, &Ui::InputField::focused, [=] {
_scrollToWidget.fire_copy(field);
});
QObject::connect(field, &Ui::InputField::tabbed, [=] {
const auto index = findField(field);
if (index + 1 < _list.size()) {
_list[index + 1]->setFocus();
} else {
_tabbed.fire({});
}
});
base::install_event_filter(field, [=](not_null<QEvent*> event) {
if (event->type() != QEvent::KeyPress
|| !field->getLastText().isEmpty()) {
@@ -768,6 +785,7 @@ not_null<Ui::InputField*> CreatePollBox::setupQuestion(
InitField(getDelegate()->outerContainer(), question, _session);
question->setMaxLength(kQuestionLimit + kErrorLimit);
question->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
question->customTab(true);
const auto warning = CreateWarningLabel(
container,
@@ -794,6 +812,69 @@ not_null<Ui::InputField*> CreatePollBox::setupQuestion(
return question;
}
not_null<Ui::InputField*> CreatePollBox::setupSolution(
not_null<Ui::VerticalLayout*> container,
rpl::producer<bool> shown) {
using namespace Settings;
const auto outer = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(container))
)->setDuration(0)->toggleOn(std::move(shown));
const auto inner = outer->entity();
AddSkip(inner);
AddSubsectionTitle(inner, tr::lng_polls_solution_title());
const auto solution = inner->add(
object_ptr<Ui::InputField>(
inner,
st::createPollSolutionField,
Ui::InputField::Mode::MultiLine,
tr::lng_polls_solution_placeholder()),
st::createPollFieldPadding);
InitField(getDelegate()->outerContainer(), solution, _session);
solution->setMaxLength(kSolutionLimit + kErrorLimit);
solution->setInstantReplaces(Ui::InstantReplaces::Default());
solution->setInstantReplacesEnabled(
_session->settings().replaceEmojiValue());
solution->setMarkdownReplacesEnabled(rpl::single(true));
solution->setEditLinkCallback(
DefaultEditLinkCallback(_session, solution));
solution->customTab(true);
const auto warning = CreateWarningLabel(
inner,
solution,
kSolutionLimit,
kWarnSolutionLimit);
rpl::combine(
solution->geometryValue(),
warning->sizeValue()
) | rpl::start_with_next([=](QRect geometry, QSize label) {
warning->moveToLeft(
(inner->width()
- label.width()
- st::createPollWarningPosition.x()),
(geometry.y()
- st::createPollFieldPadding.top()
- st::settingsSubsectionTitlePadding.bottom()
- st::settingsSubsectionTitle.style.font->height
+ st::settingsSubsectionTitle.style.font->ascent
- st::createPollWarning.style.font->ascent),
geometry.width());
}, warning->lifetime());
inner->add(
object_ptr<Ui::FlatLabel>(
inner,
tr::lng_polls_solution_about(),
st::boxDividerLabel),
st::createPollFieldTitlePadding);
return solution;
}
object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
using namespace Settings;
@@ -836,6 +917,10 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
st::boxDividerLabel),
st::createPollLimitPadding));
connect(question, &Ui::InputField::tabbed, [=] {
options->focusFirst();
});
AddSkip(container);
AddSubsectionTitle(container, tr::lng_polls_create_settings());
@@ -866,6 +951,24 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
(_chosen & PollData::Flag::Quiz),
st::defaultCheckbox),
st::createPollCheckboxMargin);
const auto solution = setupSolution(
container,
rpl::single(quiz->checked()) | rpl::then(quiz->checkedChanges()));
options->tabbed(
) | rpl::start_with_next([=] {
if (quiz->checked()) {
solution->setFocus();
} else {
question->setFocus();
}
}, question->lifetime());
connect(solution, &Ui::InputField::tabbed, [=] {
question->setFocus();
});
quiz->setDisabled(_disabled & PollData::Flag::Quiz);
if (multiple) {
multiple->setDisabled((_disabled & PollData::Flag::MultiChoice)
@@ -911,6 +1014,13 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
auto result = PollData(&_session->data(), id);
result.question = question->getLastText().trimmed();
result.answers = options->toPollAnswers();
const auto solutionWithTags = quiz->checked()
? solution->getTextWithAppliedMarkdown()
: TextWithTags();
result.solution = TextWithEntities{
solutionWithTags.text,
TextUtilities::ConvertTextTagsToEntities(solutionWithTags.tags)
};
const auto publicVotes = (anonymous && !anonymous->checked());
const auto multiChoice = (multiple && multiple->checked());
result.setFlags(Flag(0)
@@ -937,6 +1047,12 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
} else {
*error &= ~Error::Correct;
}
if (quiz->checked()
&& solution->getLastText().trimmed().size() > kSolutionLimit) {
*error |= Error::Solution;
} else {
*error &= ~Error::Solution;
}
};
const auto showError = [=](const QString &text) {
Ui::Toast::Show(text);
@@ -951,6 +1067,8 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
options->focusFirst();
} else if (*error & Error::Correct) {
showError(tr::lng_polls_choose_correct(tr::now));
} else if (*error & Error::Solution) {
solution->showError();
} else if (!*error) {
_submitRequests.fire({ collectResult(), sendOptions });
}

View File

@@ -36,7 +36,7 @@ public:
PollData::Flags disabled,
Api::SendType sendType);
rpl::producer<Result> submitRequests() const;
[[nodiscard]] rpl::producer<Result> submitRequests() const;
void submitFailed(const QString &error);
void setInnerFocus() override;
@@ -50,13 +50,17 @@ private:
Options = 0x02,
Correct = 0x04,
Other = 0x08,
Solution = 0x10,
};
friend constexpr inline bool is_flag_type(Error) { return true; }
using Errors = base::flags<Error>;
object_ptr<Ui::RpWidget> setupContent();
not_null<Ui::InputField*> setupQuestion(
[[nodiscard]] object_ptr<Ui::RpWidget> setupContent();
[[nodiscard]] not_null<Ui::InputField*> setupQuestion(
not_null<Ui::VerticalLayout*> container);
[[nodiscard]] not_null<Ui::InputField*> setupSolution(
not_null<Ui::VerticalLayout*> container,
rpl::producer<bool> shown);
const not_null<Main::Session*> _session;
const PollData::Flags _chosen = PollData::Flags();

View File

@@ -89,11 +89,16 @@ stickersRowDisabledOpacity: 0.4;
stickersRowDuration: 200;
stickersSettings: icon {{ "emoji_settings", emojiIconFg }};
stickersTrending: icon {{ "emoji_trending", emojiIconFg }};
stickersTrending: icon {{ "stickers_add", emojiIconFg }};
stickersTrendingUnread: icon {
{ "stickers_add_unread", emojiIconFg },
{ "stickers_add_dot", dialogsUnreadBg }
};
stickersRecent: icon {{ "stickers_recent", emojiIconFg }};
stickersSearch: icon {{ "stickers_search", emojiIconFg, point(0px, 1px) }};
stickersSettingsUnreadSize: 17px;
stickersSettingsUnreadPosition: point(4px, 5px);
stickersSettingsUnreadSize: 6px;
stickersSettingsUnreadPosition: point(6px, 10px);
filtersRemove: IconButton(stickersRemove) {
ripple: defaultRippleAnimation;

View File

@@ -78,7 +78,7 @@ void ApplyArchivedResult(const MTPDmessages_stickerSetInstallResultArchive &d) {
Local::writeArchivedStickers();
auto toast = Ui::Toast::Config();
toast.text = tr::lng_stickers_packs_archived(tr::now);
toast.text = { tr::lng_stickers_packs_archived(tr::now) };
toast.maxWidth = toast.minWidth = st::stickersToastMaxWidth;
toast.multiline = true;
toast.padding = st::stickersToastPadding;

View File

@@ -24,8 +24,9 @@ constexpr auto kZeroDiceDocumentId = 0xa3b83c9f84fa9e83ULL;
} // namespace
DicePack::DicePack(not_null<Main::Session*> session)
: _session(session) {
DicePack::DicePack(not_null<Main::Session*> session, const QString &emoji)
: _session(session)
, _emoji(emoji) {
}
DicePack::~DicePack() = default;
@@ -34,10 +35,7 @@ DocumentData *DicePack::lookup(int value) {
if (!_requestId) {
load();
}
if (!value) {
ensureZeroGenerated();
return _zero;
}
tryGenerateLocalZero();
const auto i = _map.find(value);
return (i != end(_map)) ? i->second.get() : nullptr;
}
@@ -47,7 +45,7 @@ void DicePack::load() {
return;
}
_requestId = _session->api().request(MTPmessages_GetStickerSet(
MTP_inputStickerSetDice()
MTP_inputStickerSetDice(MTP_string(_emoji))
)).done([=](const MTPmessages_StickerSet &result) {
result.match([&](const MTPDmessages_stickerSet &data) {
applySet(data);
@@ -58,22 +56,50 @@ void DicePack::load() {
}
void DicePack::applySet(const MTPDmessages_stickerSet &data) {
auto index = 0;
_map.clear();
auto documents = base::flat_map<DocumentId, not_null<DocumentData*>>();
for (const auto &sticker : data.vdocuments().v) {
const auto document = _session->data().processDocument(
sticker);
if (document->sticker()) {
_map.emplace(++index, document);
documents.emplace(document->id, document);
}
}
for (const auto pack : data.vpacks().v) {
pack.match([&](const MTPDstickerPack &data) {
const auto emoji = qs(data.vemoticon());
if (emoji.isEmpty()) {
return;
}
const auto ch = int(emoji[0].unicode());
const auto index = (ch == '#') ? 0 : (ch + 1 - '1');
if (index < 0 || index > 6) {
return;
}
for (const auto id : data.vdocuments().v) {
if (const auto document = documents.take(id.v)) {
_map.emplace(index, *document);
}
}
});
}
}
void DicePack::ensureZeroGenerated() {
if (_zero) {
void DicePack::tryGenerateLocalZero() {
if (!_map.empty()) {
return;
}
const auto path = qsl(":/gui/art/dice_idle.tgs");
static const auto kDiceString = QString::fromUtf8("\xF0\x9F\x8E\xB2");
static const auto kDartString = QString::fromUtf8("\xF0\x9F\x8E\xAF");
const auto path = (_emoji == kDiceString)
? qsl(":/gui/art/dice_idle.tgs")
: (_emoji == kDartString)
? qsl(":/gui/art/dart_idle.tgs")
: QString();
if (path.isEmpty()) {
return;
}
auto task = FileLoadTask(
path,
QByteArray(),
@@ -84,13 +110,29 @@ void DicePack::ensureZeroGenerated() {
task.process();
const auto result = task.peekResult();
Assert(result != nullptr);
_zero = _session->data().processDocument(
const auto document = _session->data().processDocument(
result->document,
std::move(result->thumb));
_zero->setLocation(FileLocation(path));
document->setLocation(FileLocation(path));
Ensures(_zero->sticker());
Ensures(_zero->sticker()->animated);
_map.emplace(0, document);
Ensures(document->sticker());
Ensures(document->sticker()->animated);
}
DicePacks::DicePacks(not_null<Main::Session*> session) : _session(session) {
}
DocumentData *DicePacks::lookup(const QString &emoji, int value) {
const auto i = _packs.find(emoji);
if (i != end(_packs)) {
return i->second->lookup(value);
}
return _packs.emplace(
emoji,
std::make_unique<DicePack>(_session, emoji)
).first->second->lookup(value);
}
} // namespace Stickers

View File

@@ -1,4 +1,5 @@
/*
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
@@ -17,7 +18,7 @@ namespace Stickers {
class DicePack final {
public:
explicit DicePack(not_null<Main::Session*> session);
DicePack(not_null<Main::Session*> session, const QString &emoji);
~DicePack();
DocumentData *lookup(int value);
@@ -25,13 +26,26 @@ public:
private:
void load();
void applySet(const MTPDmessages_stickerSet &data);
void ensureZeroGenerated();
void tryGenerateLocalZero();
not_null<Main::Session*> _session;
const not_null<Main::Session*> _session;
QString _emoji;
base::flat_map<int, not_null<DocumentData*>> _map;
DocumentData *_zero = nullptr;
mtpRequestId _requestId = 0;
};
class DicePacks final {
public:
explicit DicePacks(not_null<Main::Session*> session);
DocumentData *lookup(const QString &emoji, int value);
private:
const not_null<Main::Session*> _session;
base::flat_map<QString, std::unique_ptr<DicePack>> _packs;
};
} // namespace Stickers

View File

@@ -44,6 +44,8 @@ namespace {
constexpr auto kInlineItemsMaxPerRow = 5;
constexpr auto kSearchRequestDelay = 400;
constexpr auto kRecentDisplayLimit = 20;
constexpr auto kPreloadOfficialPages = 4;
constexpr auto kOfficialLoadLimit = 40;
bool SetInMyList(MTPDstickerSet::Flags flags) {
return (flags & MTPDstickerSet::Flag::f_installed_date)
@@ -138,7 +140,6 @@ private:
void finishDragging();
void paintStickerSettingsIcon(Painter &p) const;
void paintSearchIcon(Painter &p) const;
void paintFeaturedStickerSetsBadge(Painter &p, int iconLeft) const;
void paintSetIcon(Painter &p, const StickerIcon &icon, int x) const;
void paintSelectionBar(Painter &p) const;
void paintLeftRightFading(Painter &p) const;
@@ -694,18 +695,6 @@ void StickersListWidget::Footer::paintSearchIcon(Painter &p) const {
st::stickersSearch.paint(p, searchLeft + (st::stickerIconWidth - st::stickersSearch.width()) / 2, _iconsTop + st::emojiCategory.iconPosition.y(), width());
}
void StickersListWidget::Footer::paintFeaturedStickerSetsBadge(Painter &p, int iconLeft) const {
if (const auto unread = _pan->session().data().featuredStickerSetsUnreadCount()) {
Dialogs::Layout::UnreadBadgeStyle unreadSt;
unreadSt.sizeId = Dialogs::Layout::UnreadBadgeInStickersPanel;
unreadSt.size = st::stickersSettingsUnreadSize;
int unreadRight = iconLeft + st::stickerIconWidth - st::stickersSettingsUnreadPosition.x();
if (rtl()) unreadRight = width() - unreadRight;
int unreadTop = _iconsTop + st::stickersSettingsUnreadPosition.y();
Dialogs::Layout::paintUnreadCount(p, QString::number(unread), unreadRight, unreadTop, unreadSt);
}
}
void StickersListWidget::Footer::validateIconLottieAnimation(
const StickerIcon &icon) {
if (icon.lottie
@@ -788,19 +777,22 @@ void StickersListWidget::Footer::paintSetIcon(
} else if (icon.megagroup) {
icon.megagroup->paintUserpicLeft(p, x + (st::stickerIconWidth - st::stickerGroupCategorySize) / 2, _iconsTop + (st::emojiFooterHeight - st::stickerGroupCategorySize) / 2, width(), st::stickerGroupCategorySize);
} else {
auto getSpecialSetIcon = [](uint64 setId) {
if (setId == Stickers::FeaturedSetId) {
return &st::stickersTrending;
const auto paintedIcon = [&] {
if (icon.setId == Stickers::FeaturedSetId) {
const auto session = &_pan->session();
return session->data().featuredStickerSetsUnreadCount()
? &st::stickersTrendingUnread
: &st::stickersTrending;
//} else if (setId == Stickers::FavedSetId) {
// return &st::stickersFaved;
}
return &st::emojiRecent;
};
auto paintedIcon = getSpecialSetIcon(icon.setId);
paintedIcon->paint(p, x + (st::stickerIconWidth - paintedIcon->width()) / 2, _iconsTop + st::emojiCategory.iconPosition.y(), width());
if (icon.setId == Stickers::FeaturedSetId) {
paintFeaturedStickerSetsBadge(p, x);
}
return &st::stickersRecent;
}();
paintedIcon->paint(
p,
x + (st::stickerIconWidth - paintedIcon->width()) / 2,
_iconsTop + (st::emojiFooterHeight - paintedIcon->height()) / 2,
width());
}
}
@@ -901,25 +893,86 @@ void StickersListWidget::checkVisibleFeatured(
readVisibleFeatured(visibleTop, visibleBottom);
const auto visibleHeight = visibleBottom - visibleTop;
if (visibleBottom > height() - visibleHeight * kPreloadOfficialPages) {
preloadMoreOfficial();
}
const auto rowHeight = featuredRowHeight();
const auto destroyAbove = floorclamp(visibleTop - visibleHeight, rowHeight, 0, _featuredSets.size());
const auto destroyBelow = ceilclamp(visibleBottom + visibleHeight, rowHeight, 0, _featuredSets.size());
const auto destroyAbove = floorclamp(visibleTop - visibleHeight, rowHeight, 0, _officialSets.size());
const auto destroyBelow = ceilclamp(visibleBottom + visibleHeight, rowHeight, 0, _officialSets.size());
for (auto i = 0; i != destroyAbove; ++i) {
destroyLottieIn(_featuredSets[i]);
destroyLottieIn(_officialSets[i]);
}
for (auto i = destroyBelow; i != _featuredSets.size(); ++i) {
destroyLottieIn(_featuredSets[i]);
for (auto i = destroyBelow; i != _officialSets.size(); ++i) {
destroyLottieIn(_officialSets[i]);
}
}
void StickersListWidget::preloadMoreOfficial() {
if (_officialRequestId) {
return;
}
_officialRequestId = _api.request(MTPmessages_GetOldFeaturedStickers(
MTP_int(_officialOffset),
MTP_int(kOfficialLoadLimit),
MTP_int(0)
)).done([=](const MTPmessages_FeaturedStickers &result) {
_officialRequestId = 0;
result.match([&](const MTPDmessages_featuredStickersNotModified &d) {
LOG(("Api Error: messages.featuredStickersNotModified."));
}, [&](const MTPDmessages_featuredStickers &data) {
const auto &list = data.vsets().v;
_officialOffset += list.size();
for (int i = 0, l = list.size(); i != l; ++i) {
auto &data = list[i];
const auto setData = data.match([&](const auto &data) {
return data.vset().match([](const MTPDstickerSet &data) {
return &data;
});
});
const auto covers = data.match([](const MTPDstickerSetCovered &) {
return Stickers::Pack();
}, [&](const MTPDstickerSetMultiCovered &data) {
auto result = Stickers::Pack();
for (const auto &cover : data.vcovers().v) {
const auto document = session().data().processDocument(cover);
if (document->sticker()) {
result.push_back(document);
}
}
return result;
});
if (const auto set = Stickers::FeedSet(*setData)) {
if (!covers.empty()) {
set->covers = covers;
}
if (set->stickers.empty() && set->covers.empty()) {
continue;
}
const auto externalLayout = true;
appendSet(
_officialSets,
set->id,
externalLayout,
AppendSkip::Installed);
}
}
});
resizeToWidth(width());
update();
}).fail([=](const RPCError &error) {
}).send();
}
void StickersListWidget::readVisibleFeatured(
int visibleTop,
int visibleBottom) {
const auto rowHeight = featuredRowHeight();
const auto rowFrom = floorclamp(visibleTop, rowHeight, 0, _featuredSets.size());
const auto rowTo = ceilclamp(visibleBottom, rowHeight, 0, _featuredSets.size());
const auto rowFrom = floorclamp(visibleTop, rowHeight, 0, _featuredSetsCount);
const auto rowTo = ceilclamp(visibleBottom, rowHeight, 0, _featuredSetsCount);
for (auto i = rowFrom; i < rowTo; ++i) {
auto &set = _featuredSets[i];
auto &set = _officialSets[i];
if (!(set.flags & MTPDstickerSet_ClientFlag::f_unread)) {
continue;
}
@@ -935,7 +988,7 @@ void StickersListWidget::readVisibleFeatured(
++loaded;
}
}
if (loaded == count) {
if (count > 0 && loaded == count) {
session().api().readFeaturedSetDelayed(set.id);
}
}
@@ -1227,7 +1280,7 @@ void StickersListWidget::addSearchRow(not_null<const Stickers::Set*> set) {
auto StickersListWidget::shownSets() const -> const std::vector<Set> & {
switch (_section) {
case Section::Featured: return _featuredSets;
case Section::Featured: return _officialSets;
case Section::Search: return _searchSets;
case Section::Stickers: return _mySets;
}
@@ -1236,7 +1289,7 @@ auto StickersListWidget::shownSets() const -> const std::vector<Set> & {
auto StickersListWidget::shownSets() -> std::vector<Set> & {
switch (_section) {
case Section::Featured: return _featuredSets;
case Section::Featured: return _officialSets;
case Section::Search: return _searchSets;
case Section::Stickers: return _mySets;
}
@@ -1349,10 +1402,11 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
}
auto &set = sets[info.section];
if (set.externalLayout) {
const auto size = (set.flags
const auto loadedCount = int(set.stickers.size());
const auto count = (set.flags
& MTPDstickerSet_ClientFlag::f_not_loaded)
? set.count
: int(set.stickers.size());
: loadedCount;
auto widthForTitle = stickersRight() - (st::emojiPanHeaderLeft - st::buttonRadius);
if (featuredHasAddButton(info.section)) {
@@ -1402,7 +1456,7 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
}
}
auto statusText = (size > 0) ? tr::lng_stickers_count(tr::now, lt_count, size) : tr::lng_contacts_loading(tr::now);
auto statusText = (count > 0) ? tr::lng_stickers_count(tr::now, lt_count, count) : tr::lng_contacts_loading(tr::now);
p.setFont(st::stickersTrendingSubheaderFont);
p.setPen(st::stickersTrendingSubheaderFg);
p.drawTextLeft(st::emojiPanHeaderLeft - st::buttonRadius, info.top + st::stickersTrendingSubheaderTop, width(), statusText);
@@ -1413,7 +1467,7 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
for (int j = fromColumn; j < toColumn; ++j) {
int index = j;
if (index >= size) break;
if (index >= loadedCount) break;
auto selected = selectedSticker ? (selectedSticker->section == info.section && selectedSticker->index == index) : false;
auto deleteSelected = false;
@@ -2116,12 +2170,33 @@ void StickersListWidget::refreshMySets() {
}
void StickersListWidget::refreshFeaturedSets() {
_featuredSets.clear();
_featuredSets.reserve(session().data().featuredStickerSetsOrder().size());
auto wasFeaturedSetsCount = base::take(_featuredSetsCount);
auto wereOfficial = base::take(_officialSets);
_officialSets.reserve(
session().data().featuredStickerSetsOrder().size()
+ wereOfficial.size()
- wasFeaturedSetsCount);
for (const auto setId : session().data().featuredStickerSetsOrder()) {
const auto externalLayout = true;
appendSet(_featuredSets, setId, externalLayout, AppendSkip::Installed);
appendSet(_officialSets, setId, externalLayout, AppendSkip::Installed);
}
_featuredSetsCount = _officialSets.size();
if (wereOfficial.size() > wasFeaturedSetsCount) {
auto &sets = session().data().stickerSets();
const auto from = begin(wereOfficial) + wasFeaturedSetsCount;
const auto till = end(wereOfficial);
for (auto i = from; i != till; ++i) {
auto &set = *i;
auto it = sets.constFind(set.id);
if (it == sets.cend()
|| ((it->flags & MTPDstickerSet::Flag::f_installed_date)
&& !(it->flags & MTPDstickerSet::Flag::f_archived)
&& !_installedLocallySets.contains(set.id))) {
continue;
}
set.flags = it->flags;
_officialSets.push_back(std::move(set));
}
}
}
@@ -2247,28 +2322,27 @@ uint64 StickersListWidget::currentSet(int yOffset) const {
: sets[sectionInfoByOffset(yOffset).section].id;
}
void StickersListWidget::appendSet(
bool StickersListWidget::appendSet(
std::vector<Set> &to,
uint64 setId,
bool externalLayout,
AppendSkip skip) {
auto &sets = session().data().stickerSets();
auto it = sets.constFind(setId);
if (it == sets.cend() || it->stickers.isEmpty()) {
return;
if (it == sets.cend() || (!externalLayout && it->stickers.isEmpty())) {
return false;
}
if ((skip == AppendSkip::Archived)
&& (it->flags & MTPDstickerSet::Flag::f_archived)) {
return;
return false;
}
if ((skip == AppendSkip::Installed)
&& (it->flags & MTPDstickerSet::Flag::f_installed_date)
&& !(it->flags & MTPDstickerSet::Flag::f_archived)) {
if (!_installedLocallySets.contains(setId)) {
return;
return false;
}
}
to.emplace_back(
it->id,
it->flags,
@@ -2277,7 +2351,10 @@ void StickersListWidget::appendSet(
it->thumbnail,
externalLayout,
it->count,
PrepareStickers(it->stickers));
PrepareStickers((it->stickers.empty() && externalLayout)
? it->covers
: it->stickers));
return true;
}
void StickersListWidget::refreshRecent() {
@@ -2487,8 +2564,7 @@ void StickersListWidget::refreshMegagroupStickers(GroupStickersPlace place) {
void StickersListWidget::fillIcons(QList<StickerIcon> &icons) {
icons.clear();
icons.reserve(_mySets.size() + 1);
if (session().data().featuredStickerSetsUnreadCount()
&& !_featuredSets.empty()) {
if (!_officialSets.empty()) {
icons.push_back(StickerIcon(Stickers::FeaturedSetId));
}
@@ -2535,11 +2611,6 @@ void StickersListWidget::fillIcons(QList<StickerIcon> &icons) {
pixw,
pixh));
}
if (!session().data().featuredStickerSetsUnreadCount()
&& !_featuredSets.empty()) {
icons.push_back(StickerIcon(Stickers::FeaturedSetId));
}
}
bool StickersListWidget::preventAutoHide() {

View File

@@ -179,6 +179,11 @@ private:
bool externalLayout = false;
int count = 0;
};
struct FeaturedSet {
uint64 id = 0;
MTPDstickerSet::Flags flags = MTPDstickerSet::Flags();
std::vector<Sticker> stickers;
};
struct LottieSet {
struct Item {
not_null<Lottie::Animation*> animation;
@@ -192,6 +197,7 @@ private:
static std::vector<Sticker> PrepareStickers(const Stickers::Pack &pack);
void preloadMoreOfficial();
QSize boundingBoxSize() const;
template <typename Callback>
@@ -273,7 +279,7 @@ private:
Archived,
Installed,
};
void appendSet(
bool appendSet(
std::vector<Set> &to,
uint64 setId,
bool externalLayout,
@@ -303,13 +309,17 @@ private:
ChannelData *_megagroupSet = nullptr;
uint64 _megagroupSetIdRequested = 0;
std::vector<Set> _mySets;
std::vector<Set> _featuredSets;
std::vector<Set> _officialSets;
std::vector<Set> _searchSets;
int _featuredSetsCount = 0;
base::flat_set<uint64> _installedLocallySets;
std::vector<bool> _custom;
base::flat_set<not_null<DocumentData*>> _favedStickersMap;
std::weak_ptr<Lottie::FrameRenderer> _lottieRenderer;
mtpRequestId _officialRequestId = 0;
int _officialOffset = 0;
Section _section = Section::Stickers;
bool _displayingSet = false;

View File

@@ -51,9 +51,14 @@ void HiddenUrlClickHandler::Open(QString url, QVariant context) {
if (UrlRequiresConfirmation(url)
&& QGuiApplication::keyboardModifiers() != Qt::ControlModifier) {
Core::App().hideMediaView();
const auto displayUrl = parsedUrl.isValid()
const auto displayed = parsedUrl.isValid()
? parsedUrl.toDisplayString()
: url;
const auto displayUrl = !IsSuspicious(displayed)
? displayed
: parsedUrl.isValid()
? QString::fromUtf8(parsedUrl.toEncoded())
: ShowEncoded(displayed);
Ui::show(
Box<ConfirmBox>(
(tr::lng_open_this_link(tr::now)

View File

@@ -42,9 +42,7 @@ PreLaunchWindow::PreLaunchWindow(QString title) {
p.setColor(QPalette::Background, QColor(255, 255, 255));
setPalette(p);
QLabel tmp(this);
tmp.setText(qsl("Tmp"));
_size = tmp.sizeHint().height();
_size = QFontInfo(QApplication::font()).pixelSize();
int paddingVertical = (_size / 2);
int paddingHorizontal = _size;
@@ -74,7 +72,7 @@ PreLaunchWindow::~PreLaunchWindow() {
PreLaunchLabel::PreLaunchLabel(QWidget *parent) : QLabel(parent) {
QFont labelFont(font());
labelFont.setFamily(style::internal::GetFontOverride(qsl("Open Sans Semibold")));
labelFont.setFamily(style::internal::GetFontOverride(style::internal::FontSemibold));
labelFont.setPixelSize(static_cast<PreLaunchWindow*>(parent)->basicSize());
setFont(labelFont);
@@ -92,7 +90,6 @@ void PreLaunchLabel::setText(const QString &text) {
PreLaunchInput::PreLaunchInput(QWidget *parent, bool password) : QLineEdit(parent) {
QFont logFont(font());
logFont.setFamily(style::internal::GetFontOverride(qsl("Open Sans")));
logFont.setPixelSize(static_cast<PreLaunchWindow*>(parent)->basicSize());
setFont(logFont);
@@ -110,7 +107,6 @@ PreLaunchInput::PreLaunchInput(QWidget *parent, bool password) : QLineEdit(paren
PreLaunchLog::PreLaunchLog(QWidget *parent) : QTextEdit(parent) {
QFont logFont(font());
logFont.setFamily(style::internal::GetFontOverride(qsl("Open Sans")));
logFont.setPixelSize(static_cast<PreLaunchWindow*>(parent)->basicSize());
setFont(logFont);
@@ -132,7 +128,7 @@ PreLaunchButton::PreLaunchButton(QWidget *parent, bool confirm) : QPushButton(pa
setObjectName(confirm ? "confirm" : "cancel");
QFont closeFont(font());
closeFont.setFamily(style::internal::GetFontOverride(qsl("Open Sans Semibold")));
closeFont.setFamily(style::internal::GetFontOverride(style::internal::FontSemibold));
closeFont.setPixelSize(static_cast<PreLaunchWindow*>(parent)->basicSize());
setFont(closeFont);
@@ -151,7 +147,7 @@ PreLaunchCheckbox::PreLaunchCheckbox(QWidget *parent) : QCheckBox(parent) {
setCheckState(Qt::Checked);
QFont closeFont(font());
closeFont.setFamily(style::internal::GetFontOverride(qsl("Open Sans Semibold")));
closeFont.setFamily(style::internal::GetFontOverride(style::internal::FontSemibold));
closeFont.setPixelSize(static_cast<PreLaunchWindow*>(parent)->basicSize());
setFont(closeFont);

View File

@@ -427,7 +427,6 @@ void Manager::set(const QString &keys, Command command, bool replace) {
} else if (replace) {
unregister(std::exchange(i->second, std::move(shortcut)));
} else {
shortcut = nullptr;
id = i->second->id();
}
if (!id) {
@@ -435,10 +434,10 @@ void Manager::set(const QString &keys, Command command, bool replace) {
return;
}
_commandByShortcutId.emplace(id, command);
if (shortcut && isMediaShortcut) {
if (!shortcut && isMediaShortcut) {
_mediaShortcuts.emplace(i->second.get());
}
if (shortcut && isSupportShortcut) {
if (!shortcut && isSupportShortcut) {
_supportShortcuts.emplace(i->second.get());
}
}

View File

@@ -66,6 +66,11 @@ std::shared_ptr<ClickHandler> UiIntegration::createLinkHandler(
const QString &data,
const TextParseOptions &options) {
switch (type) {
case EntityType::Url:
return (!data.isEmpty() && UrlClickHandler::IsSuspicious(data))
? std::make_shared<HiddenUrlClickHandler>(data)
: nullptr;
case EntityType::CustomUrl:
return !data.isEmpty()
? std::make_shared<HiddenUrlClickHandler>(data)

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 = 2000001;
constexpr auto AppVersionStr = "2.0.1";
constexpr auto AppVersion = 2001000;
constexpr auto AppVersionStr = "2.1";
constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View File

@@ -1326,21 +1326,26 @@ std::unique_ptr<HistoryView::Media> MediaPoll::createView(
return std::make_unique<HistoryView::Poll>(message, _poll);
}
MediaDice::MediaDice(not_null<HistoryItem*> parent, int value)
MediaDice::MediaDice(not_null<HistoryItem*> parent, QString emoji, int value)
: Media(parent)
, _emoji(emoji)
, _value(value) {
}
std::unique_ptr<Media> MediaDice::clone(not_null<HistoryItem*> parent) {
return std::make_unique<MediaDice>(parent, _value);
return std::make_unique<MediaDice>(parent, _emoji, _value);
}
int MediaDice::diceValue() const {
QString MediaDice::emoji() const {
return _emoji;
}
int MediaDice::value() const {
return _value;
}
QString MediaDice::notificationText() const {
return QString::fromUtf8("\xF0\x9F\x8E\xB2");
return _emoji;
}
QString MediaDice::pinnedTextSubstring() const {

View File

@@ -407,11 +407,12 @@ private:
class MediaDice final : public Media {
public:
MediaDice(not_null<HistoryItem*> parent, int value);
MediaDice(not_null<HistoryItem*> parent, QString emoji, int value);
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
int diceValue() const;
[[nodiscard]] QString emoji() const;
[[nodiscard]] int value() const;
QString notificationText() const override;
QString pinnedTextSubstring() const override;
@@ -423,6 +424,7 @@ public:
not_null<HistoryItem*> realParent) override;
private:
QString _emoji;
int _value = 0;
};

View File

@@ -406,7 +406,8 @@ bool PeerData::canPinMessages() const {
if (const auto user = asUser()) {
return user->fullFlags() & MTPDuserFull::Flag::f_can_pin_message;
} else if (const auto chat = asChat()) {
return chat->amIn() && !chat->amRestricted(ChatRestriction::f_pin_messages);
return chat->amIn()
&& !chat->amRestricted(ChatRestriction::f_pin_messages);
} else if (const auto channel = asChannel()) {
return channel->isMegagroup()
? !channel->amRestricted(ChatRestriction::f_pin_messages)
@@ -416,6 +417,19 @@ bool PeerData::canPinMessages() const {
Unexpected("Peer type in PeerData::canPinMessages.");
}
bool PeerData::canEditMessagesIndefinitely() const {
if (const auto user = asUser()) {
return user->isSelf();
} else if (const auto chat = asChat()) {
return false;
} else if (const auto channel = asChannel()) {
return channel->isMegagroup()
? channel->canPinMessages()
: channel->canEditMessages();
}
Unexpected("Peer type in PeerData::canEditMessagesIndefinitely.");
}
void PeerData::setPinnedMessageId(MsgId messageId) {
const auto min = [&] {
if (const auto channel = asChannel()) {

View File

@@ -297,6 +297,7 @@ public:
[[nodiscard]] ImagePtr currentUserpic() const;
[[nodiscard]] bool canPinMessages() const;
[[nodiscard]] bool canEditMessagesIndefinitely() const;
[[nodiscard]] MsgId pinnedMessageId() const {
return _pinnedMessageId;
}

View File

@@ -10,11 +10,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "data/data_user.h"
#include "data/data_session.h"
#include "base/call_delayed.h"
#include "main/main_session.h"
#include "api/api_text_entities.h"
#include "ui/text_options.h"
namespace {
constexpr auto kShortPollTimeout = 30 * crl::time(1000);
constexpr auto kReloadAfterAutoCloseDelay = crl::time(1000);
const PollAnswer *AnswerByOption(
const std::vector<PollAnswer> &list,
@@ -41,6 +45,20 @@ PollData::PollData(not_null<Data::Session*> owner, PollId id)
, _owner(owner) {
}
bool PollData::closeByTimer() {
if (closed()) {
return false;
}
_flags |= Flag::Closed;
++version;
base::call_delayed(kReloadAfterAutoCloseDelay, &_owner->session(), [=] {
_lastResultsUpdate = -1; // Force reload results.
++version;
_owner->notifyPollUpdateDelayed(this);
});
return true;
}
bool PollData::applyChanges(const MTPDpoll &poll) {
Expects(poll.vid().v == id);
@@ -49,6 +67,8 @@ bool PollData::applyChanges(const MTPDpoll &poll) {
| (poll.is_public_voters() ? Flag::PublicVotes : Flag(0))
| (poll.is_multiple_choice() ? Flag::MultiChoice : Flag(0))
| (poll.is_quiz() ? Flag::Quiz : Flag(0));
const auto newCloseDate = poll.vclose_date().value_or_empty();
const auto newClosePeriod = poll.vclose_period().value_or_empty();
auto newAnswers = ranges::view::all(
poll.vanswers().v
) | ranges::view::transform([](const MTPPollAnswer &data) {
@@ -63,6 +83,8 @@ bool PollData::applyChanges(const MTPDpoll &poll) {
) | ranges::to_vector;
const auto changed1 = (question != newQuestion)
|| (closeDate != newCloseDate)
|| (closePeriod != newClosePeriod)
|| (_flags != newFlags);
const auto changed2 = (answers != newAnswers);
if (!changed1 && !changed2) {
@@ -70,6 +92,8 @@ bool PollData::applyChanges(const MTPDpoll &poll) {
}
if (changed1) {
question = newQuestion;
closeDate = newCloseDate;
closePeriod = newClosePeriod;
_flags = newFlags;
}
if (changed2) {
@@ -88,7 +112,7 @@ bool PollData::applyChanges(const MTPDpoll &poll) {
bool PollData::applyResults(const MTPPollResults &results) {
return results.match([&](const MTPDpollResults &results) {
lastResultsUpdate = crl::now();
_lastResultsUpdate = crl::now();
const auto newTotalVoters =
results.vtotal_voters().value_or(totalVoters);
@@ -123,6 +147,15 @@ bool PollData::applyResults(const MTPPollResults &results) {
}) | ranges::to_vector;
}
}
auto newSolution = TextWithEntities{
results.vsolution().value_or_empty(),
Api::EntitiesFromMTP(
results.vsolution_entities().value_or_empty())
};
if (solution != newSolution) {
solution = std::move(newSolution);
changed = true;
}
if (!changed) {
return false;
}
@@ -132,13 +165,16 @@ bool PollData::applyResults(const MTPPollResults &results) {
});
}
void PollData::checkResultsReload(not_null<HistoryItem*> item, crl::time now) {
if (lastResultsUpdate && lastResultsUpdate + kShortPollTimeout > now) {
void PollData::checkResultsReload(
not_null<HistoryItem*> item,
crl::time now) {
if (_lastResultsUpdate > 0
&& _lastResultsUpdate + kShortPollTimeout > now) {
return;
} else if (closed()) {
} else if (closed() && _lastResultsUpdate >= 0) {
return;
}
lastResultsUpdate = now;
_lastResultsUpdate = now;
_owner->session().api().reloadPollResults(item);
}
@@ -226,10 +262,49 @@ MTPPoll PollDataToMTP(not_null<const PollData*> poll, bool close) {
const auto flags = ((poll->closed() || close) ? Flag::f_closed : Flag(0))
| (poll->multiChoice() ? Flag::f_multiple_choice : Flag(0))
| (poll->publicVotes() ? Flag::f_public_voters : Flag(0))
| (poll->quiz() ? Flag::f_quiz : Flag(0));
| (poll->quiz() ? Flag::f_quiz : Flag(0))
| (poll->closePeriod > 0 ? Flag::f_close_period : Flag(0))
| (poll->closeDate > 0 ? Flag::f_close_date : Flag(0));
return MTP_poll(
MTP_long(poll->id),
MTP_flags(flags),
MTP_string(poll->question),
MTP_vector<MTPPollAnswer>(answers));
MTP_vector<MTPPollAnswer>(answers),
MTP_int(poll->closePeriod),
MTP_int(poll->closeDate));
}
MTPInputMedia PollDataToInputMedia(
not_null<const PollData*> poll,
bool close) {
auto inputFlags = MTPDinputMediaPoll::Flag(0)
| (poll->quiz()
? MTPDinputMediaPoll::Flag::f_correct_answers
: MTPDinputMediaPoll::Flag(0));
auto correct = QVector<MTPbytes>();
for (const auto &answer : poll->answers) {
if (answer.correct) {
correct.push_back(MTP_bytes(answer.option));
}
}
auto solution = poll->solution;
const auto prepareFlags = Ui::ItemTextDefaultOptions().flags;
TextUtilities::PrepareForSending(solution, prepareFlags);
TextUtilities::Trim(solution);
const auto sentEntities = Api::EntitiesToMTP(
solution.entities,
Api::ConvertOption::SkipLocal);
if (!solution.text.isEmpty()) {
inputFlags |= MTPDinputMediaPoll::Flag::f_solution;
}
if (!sentEntities.v.isEmpty()) {
inputFlags |= MTPDinputMediaPoll::Flag::f_solution_entities;
}
return MTP_inputMediaPoll(
MTP_flags(inputFlags),
PollDataToMTP(poll, close),
MTP_vector<MTPbytes>(correct),
MTP_string(solution.text),
sentEntities);
}

View File

@@ -40,6 +40,7 @@ struct PollData {
friend inline constexpr bool is_flag_type(Flag) { return true; };
using Flags = base::flags<Flag>;
bool closeByTimer();
bool applyChanges(const MTPDpoll &poll);
bool applyResults(const MTPPollResults &results);
void checkResultsReload(not_null<HistoryItem*> item, crl::time now);
@@ -60,10 +61,11 @@ struct PollData {
QString question;
std::vector<PollAnswer> answers;
std::vector<not_null<UserData*>> recentVoters;
int totalVoters = 0;
std::vector<QByteArray> sendingVotes;
crl::time lastResultsUpdate = 0;
TextWithEntities solution;
TimeId closePeriod = 0;
TimeId closeDate = 0;
int totalVoters = 0;
int version = 0;
static constexpr auto kMaxOptions = 10;
@@ -73,9 +75,15 @@ private:
const MTPPollAnswerVoters &result,
bool isMinResults);
not_null<Data::Session*> _owner;
const not_null<Data::Session*> _owner;
Flags _flags = Flags();
crl::time _lastResultsUpdate = 0; // < 0 means force reload.
};
MTPPoll PollDataToMTP(not_null<const PollData*> poll, bool close = false);
[[nodiscard]] MTPPoll PollDataToMTP(
not_null<const PollData*> poll,
bool close = false);
[[nodiscard]] MTPInputMedia PollDataToInputMedia(
not_null<const PollData*> poll,
bool close = false);

View File

@@ -191,6 +191,7 @@ Session::Session(not_null<Main::Session*> session)
, _sendActionsAnimation([=](crl::time now) {
return sendActionsAnimationCallback(now);
})
, _pollsClosingTimer([=] { checkPollsClosings(); })
, _unmuteByFinishedTimer([=] { unmuteByFinished(); })
, _groups(this)
, _chatsFilters(std::make_unique<ChatFilters>(this))
@@ -2877,6 +2878,10 @@ not_null<PollData*> Session::processPoll(const MTPPoll &data) {
if (changed) {
notifyPollUpdateDelayed(result);
}
if (result->closeDate > 0 && !result->closed()) {
_pollsClosings.emplace(result->closeDate, result);
checkPollsClosings();
}
return result;
});
}
@@ -2890,6 +2895,29 @@ not_null<PollData*> Session::processPoll(const MTPDmessageMediaPoll &data) {
return result;
}
void Session::checkPollsClosings() {
const auto now = base::unixtime::now();
auto closest = 0;
for (auto i = _pollsClosings.begin(); i != _pollsClosings.end();) {
if (i->first <= now) {
if (i->second->closeByTimer()) {
notifyPollUpdateDelayed(i->second);
}
i = _pollsClosings.erase(i);
} else {
if (!closest) {
closest = i->first;
}
++i;
}
}
if (closest) {
_pollsClosingTimer.callOnce((closest - now) * crl::time(1000));
} else {
_pollsClosingTimer.cancel();
}
}
void Session::applyUpdate(const MTPDupdateMessagePoll &update) {
const auto updated = [&] {
const auto poll = update.vpoll();

View File

@@ -822,6 +822,8 @@ private:
void setWallpapers(const QVector<MTPWallPaper> &data, int32 hash);
void checkPollsClosings();
not_null<Main::Session*> _session;
Storage::DatabasePointer _cache;
@@ -943,6 +945,9 @@ private:
base::flat_set<not_null<GameData*>> _gamesUpdated;
base::flat_set<not_null<PollData*>> _pollsUpdated;
base::flat_multi_map<TimeId, not_null<PollData*>> _pollsClosings;
base::Timer _pollsClosingTimer;
base::flat_map<FolderId, std::unique_ptr<Folder>> _folders;
//rpl::variable<FeedId> _defaultFeedId = FeedId(); // #feed

View File

@@ -1706,9 +1706,6 @@ void History::applyInboxReadUpdate(
}
void History::inboxRead(MsgId upTo, std::optional<int> stillUnread) {
if (unreadCount() > 0 && loadedAtBottom()) {
App::main()->historyToDown(this);
}
if (stillUnread.has_value() && folderKnown()) {
setUnreadCount(*stillUnread);
} else if (const auto still = countStillUnreadLocal(upTo)) {

View File

@@ -618,6 +618,15 @@ historyAudioInDownloadSelected: icon {{ "history_audio_download", historyFileInI
historyAudioOutDownload: icon {{ "history_audio_download", historyFileOutIconFg }};
historyAudioOutDownloadSelected: icon {{ "history_audio_download", historyFileOutIconFgSelected }};
historyQuizExplainIn: icon {{ "quiz_explain", msgFileThumbLinkInFg }};
historyQuizExplainInSelected: icon {{ "quiz_explain", msgFileThumbLinkInFgSelected }};
historyQuizExplainOut: icon {{ "quiz_explain", msgFileThumbLinkOutFg }};
historyQuizExplainOutSelected: icon {{ "quiz_explain", msgFileThumbLinkOutFgSelected }};
historyQuizTimerIn: icon {{ "quiz_timer", msgFileThumbLinkInFg }};
historyQuizTimerInSelected: icon {{ "quiz_timer", msgFileThumbLinkInFgSelected }};
historyQuizTimerOut: icon {{ "quiz_timer", msgFileThumbLinkOutFg }};
historyQuizTimerOutSelected: icon {{ "quiz_timer", msgFileThumbLinkOutFgSelected }};
historySlowmodeCounterMargins: margins(0px, 0px, 10px, 0px);
largeEmojiSize: 36px;

View File

@@ -768,15 +768,8 @@ bool HistoryMessage::allowsSendNow() const {
}
bool HistoryMessage::isTooOldForEdit(TimeId now) const {
const auto peer = _history->peer;
if (peer->isSelf()) {
return false;
} else if (const auto megagroup = peer->asMegagroup()) {
if (megagroup->canPinMessages()) {
return false;
}
}
return (now - date() >= Global::EditTimeLimit());
return !_history->peer->canEditMessagesIndefinitely()
&& (now - date() >= Global::EditTimeLimit());
}
bool HistoryMessage::allowsEdit(TimeId now) const {
@@ -1025,7 +1018,10 @@ std::unique_ptr<Data::Media> HistoryMessage::CreateMedia(
item,
item->history()->owner().processPoll(media));
}, [&](const MTPDmessageMediaDice &media) -> Result {
return std::make_unique<Data::MediaDice>(item, media.vvalue().v);
return std::make_unique<Data::MediaDice>(
item,
qs(media.vemoticon()),
media.vvalue().v);
}, [](const MTPDmessageMediaEmpty &) -> Result {
return nullptr;
}, [](const MTPDmessageMediaUnsupported &) -> Result {

View File

@@ -238,7 +238,7 @@ void ShowErrorToast(const QString &text) {
auto config = Ui::Toast::Config();
config.multiline = true;
config.minWidth = st::msgMinWidth;
config.text = text;
config.text = { text };
Ui::Toast::Show(config);
}
@@ -2913,7 +2913,26 @@ void HistoryWidget::historyDownClicked() {
}
void HistoryWidget::showNextUnreadMention() {
showHistory(_peer->id, _history->getMinLoadedUnreadMention());
const auto msgId = _history->getMinLoadedUnreadMention();
const auto already = (_showAtMsgId == msgId);
// Mark mention voice/video message as read.
// See https://github.com/telegramdesktop/tdesktop/issues/5623
if (msgId && already) {
const auto item = _history->owner().message(
_history->channelId(),
msgId);
if (const auto media = item ? item->media() : nullptr) {
if (const auto document = media->document()) {
if (!media->webpage()
&& (document->isVoiceMessage()
|| document->isVideoMessage())) {
document->owner().markMediaRead(document);
}
}
}
}
showHistory(_peer->id, msgId);
}
void HistoryWidget::saveEditMsg() {
@@ -3083,6 +3102,8 @@ void HistoryWidget::send(Api::SendOptions options) {
if (!_keyboard->hasMarkup() && _keyboard->forceReply() && !_kbReplyTo) {
toggleKeyboard();
}
App::main()->historyToDown(_history);
App::main()->dialogsToUp();
}
void HistoryWidget::sendWithModifiers(Qt::KeyboardModifiers modifiers) {
@@ -3628,7 +3649,7 @@ void HistoryWidget::botCallbackDone(
updateSendAction(item->history(), SendAction::Type::PlayGame);
}
} else {
UrlClickHandler(link).onClick({});
UrlClickHandler::Open(link);
}
}
});
@@ -6810,12 +6831,9 @@ void HistoryWidget::paintEditHeader(Painter &p, const QRect &rect, int left, int
p.setFont(st::msgServiceNameFont);
p.drawTextLeft(left, top + st::msgReplyPadding.top(), width(), tr::lng_edit_message(tr::now));
if (!_replyEditMsg || _replyEditMsg->history()->peer->isSelf()) return;
if (const auto megagroup = _replyEditMsg->history()->peer->asMegagroup()) {
if (megagroup->amCreator() || megagroup->hasAdminRights()) {
return;
}
if (!_replyEditMsg
|| _replyEditMsg->history()->peer->canEditMessagesIndefinitely()) {
return;
}
QString editTimeLeftText;

View File

@@ -51,7 +51,7 @@ void ShowErrorToast(const QString &text) {
auto config = Ui::Toast::Config();
config.multiline = true;
config.minWidth = st::msgMinWidth;
config.text = text;
config.text = { text };
Ui::Toast::Show(config);
}

View File

@@ -9,20 +9,56 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "chat_helpers/stickers_dice_pack.h"
#include "api/api_sending.h"
#include "api/api_common.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/history_item_components.h"
#include "history/view/history_view_element.h"
#include "ui/toast/toast.h"
#include "ui/text/text_utilities.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
namespace HistoryView {
namespace {
DocumentData *Lookup(not_null<Element*> view, int value) {
[[nodiscard]] DocumentData *Lookup(
not_null<Element*> view,
const QString &emoji,
int value) {
const auto &session = view->data()->history()->session();
return session.diceStickersPack().lookup(value);
return session.diceStickersPacks().lookup(emoji, value);
}
[[nodiscard]] ClickHandlerPtr MakeDiceHandler(
not_null<History*> history,
const QString &emoji) {
return std::make_shared<LambdaClickHandler>([=] {
auto config = Ui::Toast::Config();
config.multiline = true;
config.minWidth = st::msgMinWidth;
config.maxWidth = st::toastMaxWidth + st::msgMinWidth;
config.text = { tr::lng_about_random(tr::now, lt_emoji, emoji) };
config.durationMs = Ui::Toast::kDefaultDuration * 2;
auto link = Ui::Text::Link(
tr::lng_about_random_send(tr::now).toUpper());
link.entities.push_back(
EntityInText(EntityType::Bold, 0, link.text.size()));
config.text.append(' ').append(std::move(link));
config.filter = crl::guard(&history->session(), [=](
const ClickHandlerPtr &handler,
Qt::MouseButton button) {
if (button == Qt::LeftButton) {
auto message = Api::MessageToSend(history);
message.action.clearDraft = false;
message.textWithTags.text = emoji;
Api::SendDice(message);
}
return false;
});
Ui::Toast::Show(config);
});
}
} // namespace
@@ -30,9 +66,14 @@ DocumentData *Lookup(not_null<Element*> view, int value) {
Dice::Dice(not_null<Element*> parent, not_null<Data::MediaDice*> dice)
: _parent(parent)
, _dice(dice)
, _start(parent, Lookup(parent, 0)) {
, _link(_parent->data()->Has<HistoryMessageForwarded>()
? nullptr
: MakeDiceHandler(_parent->history(), dice->emoji())) {
if (const auto document = Lookup(parent, dice->emoji(), 0)) {
_start.emplace(parent, document);
_start->setDiceIndex(_dice->emoji(), 0);
}
_showLastFrame = _parent->data()->Has<HistoryMessageForwarded>();
_start.setDiceIndex(0);
if (_showLastFrame) {
_drawingEnd = true;
}
@@ -41,28 +82,27 @@ Dice::Dice(not_null<Element*> parent, not_null<Data::MediaDice*> dice)
Dice::~Dice() = default;
QSize Dice::size() {
return _start.size();
return _start
? _start->size()
: Sticker::GetAnimatedEmojiSize(&_parent->history()->session());
}
ClickHandlerPtr Dice::link() {
if (_parent->data()->Has<HistoryMessageForwarded>()) {
return nullptr;
}
static auto kHandler = std::make_shared<LambdaClickHandler>([] {
auto config = Ui::Toast::Config();
config.multiline = true;
config.minWidth = st::msgMinWidth;
config.text = tr::lng_about_dice(tr::now);
Ui::Toast::Show(config);
});
return kHandler;
return _link;
}
void Dice::draw(Painter &p, const QRect &r, bool selected) {
if (const auto value = _end ? 0 : _dice->diceValue()) {
if (const auto document = Lookup(_parent, value)) {
if (!_start) {
if (const auto document = Lookup(_parent, _dice->emoji(), 0)) {
_start.emplace(_parent, document);
_start->setDiceIndex(_dice->emoji(), 0);
_start->initSize();
}
}
if (const auto value = _end ? 0 : _dice->value()) {
if (const auto document = Lookup(_parent, _dice->emoji(), value)) {
_end.emplace(_parent, document);
_end->setDiceIndex(value);
_end->setDiceIndex(_dice->emoji(), value);
_end->initSize();
}
}
@@ -71,9 +111,9 @@ void Dice::draw(Painter &p, const QRect &r, bool selected) {
}
if (_drawingEnd) {
_end->draw(p, r, selected);
} else {
_start.draw(p, r, selected);
if (_end && _end->readyToDrawLottie() && _start.atTheEnd()) {
} else if (_start) {
_start->draw(p, r, selected);
if (_end && _end->readyToDrawLottie() && _start->atTheEnd()) {
_drawingEnd = true;
}
}

View File

@@ -29,7 +29,9 @@ public:
void clearStickerLoopPlayed() override {
}
void unloadHeavyPart() override {
_start.unloadHeavyPart();
if (_start) {
_start->unloadHeavyPart();
}
if (_end) {
_end->unloadHeavyPart();
}
@@ -41,8 +43,9 @@ public:
private:
const not_null<Element*> _parent;
const not_null<Data::MediaDice*> _dice;
ClickHandlerPtr _link;
std::optional<Sticker> _start;
std::optional<Sticker> _end;
Sticker _start;
mutable bool _showLastFrame = false;
mutable bool _drawingEnd = false;

View File

@@ -267,8 +267,10 @@ void Gif::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms
const auto selected = (selection == FullSelection);
const auto autoPaused = App::wnd()->sessionController()->isGifPausedAtLeastFor(Window::GifPauseReason::Any);
const auto cornerDownload = downloadInCorner();
const auto canBePlayed = _data->canBePlayed() && CanPlayInline(_data);
const auto autoplay = autoplayEnabled() && canBePlayed;
const auto canBePlayed = _data->canBePlayed();
const auto autoplay = autoplayEnabled()
&& canBePlayed
&& CanPlayInline(_data);
const auto activeRoundPlaying = activeRoundStreamed();
const auto startPlay = autoplay
&& !_streamed
@@ -871,8 +873,11 @@ void Gif::drawGrouped(
const auto autoPaused = App::wnd()->sessionController()->isGifPausedAtLeastFor(Window::GifPauseReason::Any);
const auto fullFeatured = fullFeaturedGrouped(sides);
const auto cornerDownload = fullFeatured && downloadInCorner();
const auto canBePlayed = _data->canBePlayed() && CanPlayInline(_data);;
const auto autoplay = fullFeatured && autoplayEnabled() && canBePlayed;
const auto canBePlayed = _data->canBePlayed();
const auto autoplay = fullFeatured
&& autoplayEnabled()
&& canBePlayed
&& CanPlayInline(_data);
const auto startPlay = autoplay && !_streamed;
if (startPlay) {
const_cast<Gif*>(this)->playAnimation(true);

View File

@@ -19,15 +19,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/radial_animation.h"
#include "ui/effects/ripple_animation.h"
#include "ui/effects/fireworks_animation.h"
#include "ui/toast/toast.h"
#include "data/data_media_types.h"
#include "data/data_poll.h"
#include "data/data_user.h"
#include "data/data_session.h"
#include "base/unixtime.h"
#include "base/timer.h"
#include "layout.h"
#include "main/main_session.h"
#include "apiwrap.h"
#include "styles/style_history.h"
#include "styles/style_widgets.h"
#include "styles/style_window.h"
namespace HistoryView {
namespace {
@@ -38,6 +42,8 @@ constexpr auto kRotateAmplitude = 3.;
constexpr auto kScaleSegments = 2;
constexpr auto kScaleAmplitude = 0.03;
constexpr auto kRollDuration = crl::time(400);
constexpr auto kLargestRadialDuration = 30 * crl::time(1000);
constexpr auto kCriticalCloseDuration = 5 * crl::time(1000);
struct PercentCounterItem {
int index = 0;
@@ -116,6 +122,13 @@ void CountNicePercent(
}
}
[[nodiscard]] crl::time CountToastDuration(const TextWithEntities &text) {
return std::clamp(
crl::time(1000) * text.text.size() / 14,
crl::time(1000) * 5,
crl::time(1000) * 8);
}
} // namespace
struct Poll::AnswerAnimation {
@@ -161,6 +174,16 @@ struct Poll::Answer {
mutable std::unique_ptr<Ui::RippleAnimation> ripple;
};
struct Poll::CloseInformation {
CloseInformation(TimeId date, TimeId period, Fn<void()> repaint);
crl::time start = 0;
crl::time finish = 0;
crl::time duration = 0;
base::Timer timer;
Ui::Animations::Basic radial;
};
template <typename Callback>
Poll::SendingAnimation::SendingAnimation(
const QByteArray &option,
@@ -188,6 +211,16 @@ void Poll::Answer::fillData(
Ui::WebpageTextTitleOptions());
}
Poll::CloseInformation::CloseInformation(
TimeId date,
TimeId period,
Fn<void()> repaint)
: duration(period * crl::time(1000))
, timer(std::move(repaint)) {
const auto left = std::clamp(date - base::unixtime::now(), 0, period);
finish = crl::now() + left * crl::time(1000);
}
Poll::Poll(
not_null<Element*> parent,
not_null<PollData*> poll)
@@ -405,9 +438,23 @@ void Poll::checkQuizAnswered() {
1.,
kRollDuration,
anim::linear);
showSolution();
}
}
void Poll::showSolution() const {
if (_poll->solution.text.isEmpty()) {
return;
}
auto config = Ui::Toast::Config();
config.multiline = config.dark = true;
config.minWidth = st::msgMinWidth;
config.maxWidth = st::windowMinWidth;
config.text = _poll->solution;
config.durationMs = CountToastDuration(config.text);
Ui::Toast::Show(config);
}
void Poll::updateRecentVoters() {
auto &&sliced = ranges::view::all(
_poll->recentVoters
@@ -653,6 +700,8 @@ void Poll::draw(Painter &p, const QRect &r, TextSelection selection, crl::time m
p.setPen(regular);
_subtitle.drawLeftElided(p, padding.left(), tshift, paintw, width());
paintRecentVoters(p, padding.left() + _subtitle.maxWidth(), tshift, selection);
paintCloseByTimer(p, padding.left() + paintw, tshift, selection);
paintShowSolution(p, padding.left() + paintw, tshift, selection);
tshift += st::msgDateFont->height + st::historyPollAnswersSkip;
const auto progress = _answersAnimation
@@ -799,6 +848,103 @@ void Poll::paintRecentVoters(
}
}
void Poll::paintCloseByTimer(
Painter &p,
int right,
int top,
TextSelection selection) const {
if (!canVote() || _poll->closeDate <= 0 || _poll->closePeriod <= 0) {
_close = nullptr;
return;
}
if (!_close) {
_close = std::make_unique<CloseInformation>(
_poll->closeDate,
_poll->closePeriod,
[=] { history()->owner().requestViewRepaint(_parent); });
}
const auto now = crl::now();
const auto left = std::max(_close->finish - now, crl::time(0));
const auto radial = std::min(_close->duration, kLargestRadialDuration);
if (!left) {
_close->radial.stop();
} else if (left < radial && !anim::Disabled()) {
if (!_close->radial.animating()) {
_close->radial.init([=] {
history()->owner().requestViewRepaint(_parent);
});
_close->radial.start();
}
} else {
_close->radial.stop();
}
const auto time = formatDurationText(int(std::ceil(left / 1000.)));
const auto outbg = _parent->hasOutLayout();
const auto selected = (selection == FullSelection);
const auto &icon = selected
? (outbg
? st::historyQuizTimerOutSelected
: st::historyQuizTimerInSelected)
: (outbg ? st::historyQuizTimerOut : st::historyQuizTimerIn);
const auto x = right - icon.width();
const auto y = top
+ (st::normalFont->height - icon.height()) / 2
- st::lineWidth;
const auto &regular = (left < kCriticalCloseDuration)
? st::boxTextFgError
: selected
? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected)
: (outbg ? st::msgOutDateFg : st::msgInDateFg);
p.setPen(regular);
const auto timeWidth = st::normalFont->width(time);
p.drawTextLeft(x - timeWidth, top, width(), time, timeWidth);
if (left < radial) {
auto hq = PainterHighQualityEnabler(p);
const auto part = std::max(
left / float64(radial),
1. / FullArcLength);
const auto length = int(std::round(FullArcLength * part));
auto pen = regular->p;
pen.setWidth(st::historyPollRadio.thickness);
pen.setCapStyle(Qt::RoundCap);
p.setPen(pen);
const auto size = icon.width() / 2;
const auto left = (x + (icon.width() - size) / 2);
const auto top = (y + (icon.height() - size) / 2) + st::lineWidth;
p.drawArc(left, top, size, size, (FullArcLength / 4), length);
} else {
icon.paint(p, x, y, width());
}
if (left > (anim::Disabled() ? 0 : (radial - 1))) {
const auto next = (left % 1000);
_close->timer.callOnce((next ? next : 1000) + 1);
}
}
void Poll::paintShowSolution(
Painter &p,
int right,
int top,
TextSelection selection) const {
if (!showVotes() || _poll->solution.text.isEmpty()) {
return;
}
if (!_showSolutionLink) {
_showSolutionLink = std::make_shared<LambdaClickHandler>(
crl::guard(this, [=] { showSolution(); }));
}
const auto outbg = _parent->hasOutLayout();
const auto &icon = (selection == FullSelection)
? (outbg
? st::historyQuizExplainOutSelected
: st::historyQuizExplainInSelected)
: (outbg ? st::historyQuizExplainOut : st::historyQuizExplainIn);
const auto x = right - icon.width();
const auto y = top + (st::normalFont->height - icon.height()) / 2;
icon.paint(p, x, y, width());
}
int Poll::paintAnswer(
Painter &p,
const Answer &answer,
@@ -1111,6 +1257,10 @@ TextState Poll::textState(QPoint point, StateRequest request) const {
paintw -= padding.left() + padding.right();
tshift += _question.countHeight(paintw) + st::historyPollSubtitleSkip;
if (inShowSolution(point, padding.left() + paintw, tshift)) {
result.link = _showSolutionLink;
return result;
}
tshift += st::msgDateFont->height + st::historyPollAnswersSkip;
const auto awidth = paintw
- st::historyPollAnswerPadding.left()
@@ -1244,6 +1394,23 @@ void Poll::toggleRipple(Answer &answer, bool pressed) {
}
}
bool Poll::canShowSolution() const {
return showVotes() && !_poll->solution.text.isEmpty();
}
bool Poll::inShowSolution(
QPoint point,
int right,
int top) const {
if (!canShowSolution()) {
return false;
}
const auto &icon = st::historyQuizExplainIn;
const auto x = right - icon.width();
const auto y = top + (st::normalFont->height - icon.height()) / 2;
return QRect(x, y, icon.width(), icon.height()).contains(point);
}
int Poll::bottomButtonHeight() const {
const auto skip = st::historyPollChoiceRight.height()
- st::historyPollFillingBottom

View File

@@ -24,6 +24,7 @@ public:
Poll(
not_null<Element*> parent,
not_null<PollData*> poll);
~Poll();
void draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const override;
TextState textState(QPoint point, StateRequest request) const override;
@@ -53,13 +54,12 @@ public:
const ClickHandlerPtr &handler,
bool pressed) override;
~Poll();
private:
struct AnswerAnimation;
struct AnswersAnimation;
struct SendingAnimation;
struct Answer;
struct CloseInformation;
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
@@ -96,6 +96,16 @@ private:
int left,
int top,
TextSelection selection) const;
void paintCloseByTimer(
Painter &p,
int right,
int top,
TextSelection selection) const;
void paintShowSolution(
Painter &p,
int right,
int top,
TextSelection selection) const;
int paintAnswer(
Painter &p,
const Answer &answer,
@@ -155,6 +165,13 @@ private:
void sendMultiOptions();
void showResults();
void checkQuizAnswered();
void showSolution() const;
[[nodiscard]] bool canShowSolution() const;
[[nodiscard]] bool inShowSolution(
QPoint point,
int right,
int top) const;
[[nodiscard]] int bottomButtonHeight() const;
@@ -173,6 +190,7 @@ private:
Ui::Text::String _totalVotesLabel;
ClickHandlerPtr _showResultsLink;
ClickHandlerPtr _sendVotesLink;
mutable ClickHandlerPtr _showSolutionLink;
mutable std::unique_ptr<Ui::RippleAnimation> _linkRipple;
mutable std::unique_ptr<AnswersAnimation> _answersAnimation;
@@ -181,6 +199,8 @@ private:
Ui::Animations::Simple _wrongAnswerAnimation;
mutable QPoint _lastLinkPoint;
mutable std::unique_ptr<CloseInformation> _close;
bool _hasSelected = false;
bool _votedFromHere = false;
mutable bool _wrongAnswerAnimated = false;

View File

@@ -37,13 +37,17 @@ namespace {
0.625);
}
[[nodiscard]] QImage CacheDiceImage(int index, const QImage &image) {
static auto Cache = base::flat_map<int, QImage>();
const auto i = Cache.find(index);
[[nodiscard]] QImage CacheDiceImage(
const QString &emoji,
int index,
const QImage &image) {
static auto Cache = base::flat_map<std::pair<QString, int>, QImage>();
const auto key = std::make_pair(emoji, index);
const auto i = Cache.find(key);
if (i != end(Cache) && i->second.size() == image.size()) {
return i->second;
}
Cache[index] = image;
Cache[key] = image;
return image;
}
@@ -70,12 +74,7 @@ bool Sticker::isEmojiSticker() const {
void Sticker::initSize() {
_size = _document->dimensions;
if (isEmojiSticker() || _diceIndex >= 0) {
constexpr auto kIdealStickerSize = 512;
const auto zoom = GetEmojiStickerZoom(&_document->session());
const auto convert = [&](int size) {
return int(size * st::maxStickerSize * zoom / kIdealStickerSize);
};
_size = QSize(convert(_size.width()), convert(_size.height()));
_size = GetAnimatedEmojiSize(&_document->session(), _size);
[[maybe_unused]] bool result = readyToDrawLottie();
} else {
_size = DownscaledSize(
@@ -106,6 +105,21 @@ bool Sticker::readyToDrawLottie() {
return (_lottie && _lottie->ready());
}
QSize Sticker::GetAnimatedEmojiSize(not_null<Main::Session*> session) {
return GetAnimatedEmojiSize(session, { 512, 512 });
}
QSize Sticker::GetAnimatedEmojiSize(
not_null<Main::Session*> session,
QSize documentSize) {
constexpr auto kIdealStickerSize = 512;
const auto zoom = GetEmojiStickerZoom(session);
const auto convert = [&](int size) {
return int(size * st::maxStickerSize * zoom / kIdealStickerSize);
};
return { convert(documentSize.width()), convert(documentSize.height()) };
}
void Sticker::draw(Painter &p, const QRect &r, bool selected) {
if (readyToDrawLottie()) {
paintLottie(p, r, selected);
@@ -126,7 +140,7 @@ void Sticker::paintLottie(Painter &p, const QRect &r, bool selected) {
: Lottie::Animation::FrameInfo();
if (_nextLastDiceFrame) {
_nextLastDiceFrame = false;
_lastDiceFrame = CacheDiceImage(_diceIndex, frame.image);
_lastDiceFrame = CacheDiceImage(_diceEmoji, _diceIndex, frame.image);
}
const auto &image = _lastDiceFrame.isNull()
? frame.image
@@ -237,7 +251,8 @@ void Sticker::refreshLink() {
}
}
void Sticker::setDiceIndex(int index) {
void Sticker::setDiceIndex(const QString &emoji, int index) {
_diceEmoji = emoji;
_diceIndex = index;
}

View File

@@ -10,6 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/media/history_view_media_unwrapped.h"
#include "base/weak_ptr.h"
namespace Main {
class Session;
} // namespace Main
namespace Data {
struct FileOrigin;
} // namespace Data
@@ -49,12 +53,18 @@ public:
}
void refreshLink() override;
void setDiceIndex(int index);
void setDiceIndex(const QString &emoji, int index);
[[nodiscard]] bool atTheEnd() const {
return _atTheEnd;
}
[[nodiscard]] bool readyToDrawLottie();
[[nodiscard]] static QSize GetAnimatedEmojiSize(
not_null<Main::Session*> session);
[[nodiscard]] static QSize GetAnimatedEmojiSize(
not_null<Main::Session*> session,
QSize documentSize);
private:
[[nodiscard]] bool isEmojiSticker() const;
void paintLottie(Painter &p, const QRect &r, bool selected);
@@ -71,6 +81,7 @@ private:
ClickHandlerPtr _link;
QSize _size;
QImage _lastDiceFrame;
QString _diceEmoji;
int _diceIndex = -1;
mutable bool _lottieOncePlayed = false;
mutable bool _atTheEnd = false;

View File

@@ -127,7 +127,8 @@ QSize WebPage::countOptimalSize() {
}
return true;
}();
_openl = previewOfHiddenUrl
_openl = (previewOfHiddenUrl
|| UrlClickHandler::IsSuspicious(_data->url))
? std::make_shared<HiddenUrlClickHandler>(_data->url)
: std::make_shared<UrlClickHandler>(_data->url, true);
if (_data->document

View File

@@ -579,6 +579,9 @@ ListController *CreateAnswerRows(
headerWrap->resize(headerWrap->width(), height);
}, header->lifetime());
auto moreTopWidget = object_ptr<Ui::RpWidget>(container);
moreTopWidget->resize(0, 0);
const auto moreTop = container->add(std::move(moreTopWidget));
const auto more = container->add(
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
container,
@@ -604,7 +607,7 @@ ListController *CreateAnswerRows(
rpl::combine(
std::move(visibleTop),
headerWrap->geometryValue(),
more->topValue()
moreTop->topValue()
) | rpl::filter([=](int, QRect headerRect, int moreTop) {
return moreTop >= headerRect.y() + headerRect.height();
}) | rpl::start_with_next([=](

View File

@@ -341,9 +341,10 @@ void start(not_null<Core::Launcher*> launcher) {
if (cAlphaVersion()) {
workingDirChosen = true;
} else {
#if defined Q_OS_MAC || defined Q_OS_LINUX
} else {
if (!cWorkingDir().isEmpty()) {
// This value must come from TelegramForcePortable
// or from the "-workdir" command line argument.
@@ -362,11 +363,12 @@ void start(not_null<Core::Launcher*> launcher) {
#endif // Q_OS_LINUX && !_DEBUG
#elif defined Q_OS_WINRT // Q_OS_MAC || Q_OS_LINUX
} else {
cForceWorkingDir(psAppDataPath());
workingDirChosen = true;
#elif defined OS_WIN_STORE // Q_OS_MAC || Q_OS_LINUX || Q_OS_WINRT
#ifdef _DEBUG
cForceWorkingDir(cExeDir());
#else // _DEBUG
@@ -375,7 +377,7 @@ void start(not_null<Core::Launcher*> launcher) {
workingDirChosen = true;
#elif defined Q_OS_WIN
} else {
if (!cWorkingDir().isEmpty()) {
// This value must come from TelegramForcePortable
// or from the "-workdir" command line argument.
@@ -384,6 +386,7 @@ void start(not_null<Core::Launcher*> launcher) {
}
#endif // Q_OS_MAC || Q_OS_LINUX || Q_OS_WINRT || OS_WIN_STORE
}
LogsData = new LogsDataFields();

View File

@@ -57,7 +57,7 @@ Session::Session(
, _data(std::make_unique<Data::Session>(this))
, _user(_data->processUser(user))
, _emojiStickersPack(std::make_unique<Stickers::EmojiPack>(this))
, _diceStickersPack(std::make_unique<Stickers::DicePack>(this))
, _diceStickersPacks(std::make_unique<Stickers::DicePacks>(this))
, _changelogs(Core::Changelogs::Create(this))
, _supportHelper(Support::Helper::Create(this)) {
Core::App().passcodeLockChanges(

View File

@@ -46,7 +46,7 @@ class Instance;
namespace Stickers {
class EmojiPack;
class DicePack;
class DicePacks;
} // namespace Stickers;
namespace Core {
@@ -93,8 +93,8 @@ public:
[[nodiscard]] Stickers::EmojiPack &emojiStickersPack() const {
return *_emojiStickersPack;
}
[[nodiscard]] Stickers::DicePack &diceStickersPack() const {
return *_diceStickersPack;
[[nodiscard]] Stickers::DicePacks &diceStickersPacks() const {
return *_diceStickersPacks;
}
[[nodiscard]] base::Observable<void> &downloaderTaskFinished();
@@ -161,7 +161,7 @@ private:
// _emojiStickersPack depends on _data.
const std::unique_ptr<Stickers::EmojiPack> _emojiStickersPack;
const std::unique_ptr<Stickers::DicePack> _diceStickersPack;
const std::unique_ptr<Stickers::DicePacks> _diceStickersPacks;
// _changelogs depends on _data, subscribes on chats loading event.
const std::unique_ptr<Core::Changelogs> _changelogs;

View File

@@ -702,6 +702,7 @@ bool MainWindow::takeThirdSectionFromLayer() {
}
void MainWindow::fixOrder() {
if (_passcodeLock) _passcodeLock->raise();
if (_layer) _layer->raise();
if (_mediaPreview) _mediaPreview->raise();
if (_testingThemeWarning) _testingThemeWarning->raise();

View File

@@ -314,7 +314,8 @@ void Mixer::Track::createStream(AudioMsgId::Type type) {
alSource3f(stream.source, AL_POSITION, 0, 0, 0);
alSource3f(stream.source, AL_VELOCITY, 0, 0, 0);
alSourcei(stream.source, AL_LOOPING, 0);
alSourcei(stream.source, AL_DIRECT_CHANNELS_SOFT, 1);
alSourcei(stream.source, AL_SOURCE_RELATIVE, 1);
alSourcei(stream.source, AL_ROLLOFF_FACTOR, 0);
alGenBuffers(3, stream.buffers);
#ifndef TDESKTOP_DISABLE_OPENAL_EFFECTS
if (speedEffect) {

View File

@@ -512,7 +512,7 @@ bool VideoTrackObject::tryReadFirstFrame(FFmpeg::Packet &&packet) {
}
bool VideoTrackObject::processFirstFrame() {
if (_stream.frame->width * _stream.frame->height >= kMaxFrameArea) {
if (_stream.frame->width * _stream.frame->height > kMaxFrameArea) {
return false;
}
auto frame = ConvertFrame(

View File

@@ -2396,9 +2396,9 @@ void OverlayWidget::initThemePreview() {
_themeShare->setClickedCallback([=] {
QGuiApplication::clipboard()->setText(
Core::App().createInternalLinkFull("addtheme/" + slug));
auto config = Ui::Toast::Config();
config.text = tr::lng_background_link_copied(tr::now);
Ui::Toast::Show(this, config);
Ui::Toast::Show(
this,
tr::lng_background_link_copied(tr::now));
});
} else {
_themeShare.destroy();

View File

@@ -482,6 +482,27 @@ mtpMsgId SessionPrivate::placeToContainer(
return msgId;
}
MTPVector<MTPJSONObjectValue> SessionPrivate::prepareInitParams() {
const auto local = QDateTime::currentDateTime();
const auto utc = QDateTime(local.date(), local.time(), Qt::UTC);
const auto shift = base::unixtime::now() - (TimeId)::time(nullptr);
const auto delta = int(utc.toTime_t()) - int(local.toTime_t()) - shift;
auto sliced = delta;
while (sliced < -12 * 3600) {
sliced += 24 * 3600;
}
while (sliced > 14 * 3600) {
sliced -= 24 * 3600;
}
const auto sign = (sliced < 0) ? -1 : 1;
const auto rounded = std::round(std::abs(sliced) / 900.) * 900 * sign;
return MTP_vector<MTPJSONObjectValue>(
1,
MTP_jsonObjectValue(
MTP_string("tz_offset"),
MTP_jsonNumber(MTP_double(rounded))));
}
void SessionPrivate::tryToSend() {
DEBUG_LOG(("MTP Info: tryToSend for dc %1.").arg(_shiftedDcId));
if (!_connection) {
@@ -612,7 +633,8 @@ void SessionPrivate::tryToSend() {
: MTPInputClientProxy();
using Flag = MTPInitConnection<SerializedRequest>::Flag;
initWrapper = MTPInitConnection<SerializedRequest>(
MTP_flags(mtprotoProxy ? Flag::f_proxy : Flag(0)),
MTP_flags(Flag::f_params
| (mtprotoProxy ? Flag::f_proxy : Flag(0))),
MTP_int(ApiId),
MTP_string(deviceModel),
MTP_string(systemVersion),
@@ -621,6 +643,7 @@ void SessionPrivate::tryToSend() {
MTP_string(langPackName),
MTP_string(cloudLangCode),
clientProxyFields,
MTP_jsonObject(prepareInitParams()),
SerializedRequest());
initSizeInInts = (tl::count_length(initWrapper) >> 2) + 2;
initSize = initSizeInInts * sizeof(mtpPrime);
@@ -2007,9 +2030,9 @@ void SessionPrivate::requestsAcked(const QVector<MTPlong> &ids, bool byResponse)
} else {
DEBUG_LOG(("Message Info: acked msgId %1 that was prepared to resend, requestId %2").arg(msgId).arg(requestId));
}
_ackedIds.emplace(msgId, j->second->requestId);
toSend.erase(j);
continue;
}

View File

@@ -166,6 +166,7 @@ private:
[[nodiscard]] uint32 nextRequestSeqNumber(bool needAck);
[[nodiscard]] bool realDcTypeChanged();
[[nodiscard]] MTPVector<MTPJSONObjectValue> prepareInitParams();
const not_null<Instance*> _instance;
const ShiftedDcId _shiftedDcId = 0;

View File

@@ -1619,7 +1619,9 @@ const style::RoundCheckbox &Link::checkboxStyle() const {
Link::LinkEntry::LinkEntry(const QString &url, const QString &text)
: text(text)
, width(st::normalFont->width(text))
, lnk(std::make_shared<UrlClickHandler>(url)) {
, lnk(UrlClickHandler::IsSuspicious(url)
? std::make_shared<HiddenUrlClickHandler>(url)
: std::make_shared<UrlClickHandler>(url)) {
}
} // namespace Layout

View File

@@ -238,9 +238,13 @@ QIcon TrayIconGen(int counter, bool muted) {
bool IsIndicatorApplication() {
// Hack for indicator-application, which doesn't handle icons sent across D-Bus:
// save the icon to a temp file and set the icon name to that filename.
static const auto IndicatorApplication = [&] {
static const auto IndicatorApplication = [] {
const auto interface = QDBusConnection::sessionBus().interface();
if (!interface) {
return false;
}
const auto ubuntuIndicator = interface->isServiceRegistered(
qsl("com.canonical.indicator.application"));
@@ -338,10 +342,15 @@ quint32 djbStringHash(QString string) {
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
bool AppMenuSupported() {
static const auto Available = QDBusInterface(
kAppMenuService.utf16(),
kAppMenuObjectPath.utf16(),
kAppMenuInterface.utf16()).isValid();
static const auto Available = []() -> bool {
const auto interface = QDBusConnection::sessionBus().interface();
if (!interface) {
return false;
}
return interface->isServiceRegistered(kAppMenuService.utf16());
}();
return Available;
}
@@ -451,7 +460,7 @@ void MainWindow::initHook() {
bool MainWindow::hasTrayIcon() const {
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
return trayIcon || _sniTrayIcon;
return trayIcon || (SNIAvailable && _sniTrayIcon);
#else
return trayIcon;
#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION
@@ -545,12 +554,6 @@ void MainWindow::onSNIOwnerChanged(
return;
}
if (_sniTrayIcon) {
_sniTrayIcon->setContextMenu(0);
_sniTrayIcon->deleteLater();
}
_sniTrayIcon = nullptr;
if (trayIcon) {
trayIcon->setContextMenu(0);
trayIcon->deleteLater();

View File

@@ -130,7 +130,7 @@ bool RunShellCommand(const QByteArray &command) {
LOG(("Fontconfig version: %1.").arg(version.toString()));
if (version < QVersionNumber::fromString("2.13")) {
if (qgetenv("TDESKTOP_FORCE_CUSTOM_FONTCONFIG").isEmpty()) {
if (!qEnvironmentVariableIsSet("TDESKTOP_FORCE_CUSTOM_FONTCONFIG")) {
return false;
}
}
@@ -138,13 +138,24 @@ bool RunShellCommand(const QByteArray &command) {
}
void FallbackFontConfig() {
if (BadFontConfigVersion()) {
const auto custom = cWorkingDir() + "tdata/fc-custom-1.conf";
QFile(":/fc/fc-custom.conf").copy(custom);
const auto custom = cWorkingDir() + "tdata/fc-custom-1.conf";
auto doFallback = [&] {
if (QFile(custom).exists()) {
LOG(("Custom FONTCONFIG_FILE: ") + custom);
qputenv("FONTCONFIG_FILE", QFile::encodeName(custom));
return true;
}
return false;
};
if (doFallback()) {
return;
}
if (BadFontConfigVersion()) {
QFile(":/fc/fc-custom.conf").copy(custom);
doFallback();
}
}
@@ -696,7 +707,7 @@ void psAutoStart(bool start, bool silent) {
if (InSandbox()) {
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
SandboxAutostart(start, silent);
#endif // !DESKTOP_APP_USE_PACKAGED
#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION
} else {
const auto autostart = [&] {
if (InSnap()) {

View File

@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "platform/win/notifications_manager_win.h"
#include "platform/win/windows_app_user_model_id.h"
#include "platform/win/windows_dlls.h"
#include "base/call_delayed.h"
#include "lang/lang_keys.h"
#include "mainwindow.h"
#include "mainwidget.h"
@@ -71,6 +72,8 @@ using namespace Platform;
namespace {
constexpr auto kRefreshBadLastUserInputTimeout = 10 * crl::time(1000);
QStringList _initLogs;
bool themeInited = false;
@@ -336,9 +339,51 @@ QString SingleInstanceLocalServerName(const QString &hash) {
std::optional<crl::time> LastUserInputTime() {
auto lii = LASTINPUTINFO{ 0 };
lii.cbSize = sizeof(LASTINPUTINFO);
return GetLastInputInfo(&lii)
? std::make_optional(crl::now() + lii.dwTime - GetTickCount())
: std::nullopt;
if (!GetLastInputInfo(&lii)) {
return std::nullopt;
}
const auto now = crl::now();
const auto input = crl::time(lii.dwTime);
static auto LastTrackedInput = input;
static auto LastTrackedWhen = now;
const auto ticks32 = crl::time(GetTickCount());
const auto ticks64 = crl::time(GetTickCount64());
const auto elapsed = std::max(ticks32, ticks64) - input;
const auto good = (std::abs(ticks32 - ticks64) <= crl::time(1000))
&& (elapsed >= 0);
if (good) {
LastTrackedInput = input;
LastTrackedWhen = now;
return (now > elapsed) ? (now - elapsed) : crl::time(0);
}
static auto WaitingDelayed = false;
if (!WaitingDelayed) {
WaitingDelayed = true;
base::call_delayed(kRefreshBadLastUserInputTimeout, [=] {
WaitingDelayed = false;
[[maybe_unused]] const auto cheked = LastUserInputTime();
});
}
constexpr auto OverrunLimit = std::numeric_limits<DWORD>::max();
constexpr auto OverrunThreshold = OverrunLimit / 4;
if (LastTrackedInput == input) {
return LastTrackedWhen;
}
const auto guard = gsl::finally([&] {
LastTrackedInput = input;
LastTrackedWhen = now;
});
if (input > LastTrackedInput) {
const auto add = input - LastTrackedInput;
return std::min(LastTrackedWhen + add, now);
} else if (crl::time(OverrunLimit) + input - LastTrackedInput
< crl::time(OverrunThreshold)) {
const auto add = crl::time(OverrunLimit) + input - LastTrackedInput;
return std::min(LastTrackedWhen + add, now);
}
return LastTrackedWhen;
}
} // namespace Platform

View File

@@ -552,6 +552,7 @@ bool FileLoadTask::CheckForSong(
qstr("audio/aac"),
qstr("audio/ogg"),
qstr("audio/flac"),
qstr("audio/opus"),
};
static const auto extensions = {
qstr(".mp3"),
@@ -559,6 +560,8 @@ bool FileLoadTask::CheckForSong(
qstr(".aac"),
qstr(".ogg"),
qstr(".flac"),
qstr(".opus"),
qstr(".oga"),
};
if (!filepath.isEmpty()
&& !CheckMimeOrExtensions(

View File

@@ -111,68 +111,62 @@ bool _userWorking() {
return _manager && !_basePath.isEmpty() && !_userBasePath.isEmpty();
}
enum class FileOption {
User = (1 << 0),
Safe = (1 << 1),
enum class FileOwner {
User = (1 << 0),
Global = (1 << 1),
};
using FileOptions = base::flags<FileOption>;
inline constexpr auto is_flag_type(FileOption) { return true; };
bool keyAlreadyUsed(QString &name, FileOptions options = FileOption::User | FileOption::Safe) {
[[nodiscard]] bool KeyAlreadyUsed(QString &name) {
name += '0';
if (QFileInfo(name).exists()) {
return true;
}
if (options & (FileOption::Safe)) {
name[name.size() - 1] = '1';
if (QFileInfo(name).exists()) {
return true;
}
name[name.size() - 1] = 's';
if (QFileInfo(name).exists()) {
return true;
}
name[name.size() - 1] = '1';
if (QFileInfo(name).exists()) {
return true;
}
name[name.size() - 1] = 's';
if (QFileInfo(name).exists()) {
return true;
}
return false;
}
FileKey genKey(FileOptions options = FileOption::User | FileOption::Safe) {
if (options & FileOption::User) {
[[nodiscard]] FileKey GenerateKey(FileOwner owner = FileOwner::User) {
if (owner == FileOwner::User) {
if (!_userWorking()) return 0;
} else {
if (!_working()) return 0;
}
FileKey result;
QString base = (options & FileOption::User) ? _userBasePath : _basePath, path;
QString base = (owner == FileOwner::User) ? _userBasePath : _basePath, path;
path.reserve(base.size() + 0x11);
path += base;
do {
result = rand_value<FileKey>();
path.resize(base.size());
path += toFilePart(result);
} while (!result || keyAlreadyUsed(path, options));
} while (!result || KeyAlreadyUsed(path));
return result;
}
void clearKey(const FileKey &key, FileOptions options = FileOption::User | FileOption::Safe) {
if (options & FileOption::User) {
void ClearKey(const FileKey &key, FileOwner owner = FileOwner::User) {
if (owner == FileOwner::User) {
if (!_userWorking()) return;
} else {
if (!_working()) return;
}
QString base = (options & FileOption::User) ? _userBasePath : _basePath, name;
QString base = (owner == FileOwner::User) ? _userBasePath : _basePath, name;
name.reserve(base.size() + 0x11);
name.append(base).append(toFilePart(key)).append('0');
QFile::remove(name);
if (options & FileOption::Safe) {
name[name.size() - 1] = '1';
QFile::remove(name);
name[name.size() - 1] = 's';
QFile::remove(name);
}
name[name.size() - 1] = '1';
QFile::remove(name);
name[name.size() - 1] = 's';
QFile::remove(name);
}
bool _checkStreamStatus(QDataStream &stream) {
@@ -251,31 +245,37 @@ struct EncryptedDescriptor {
}
};
struct FileWriteDescriptor {
FileWriteDescriptor(
class FileWriteDescriptor final {
public:
explicit FileWriteDescriptor(
const FileKey &key,
FileOptions options = FileOption::User | FileOption::Safe);
FileWriteDescriptor(
FileOwner owner = FileOwner::User);
explicit FileWriteDescriptor(
const QString &name,
FileOptions options = FileOption::User | FileOption::Safe);
FileOwner owner = FileOwner::User);
~FileWriteDescriptor();
void init(const QString &name, FileOptions options);
bool writeData(const QByteArray &data);
bool writeEncrypted(
void writeData(const QByteArray &data);
void writeEncrypted(
EncryptedDescriptor &data,
const MTP::AuthKeyPtr &key = LocalKey);
private:
void init(const QString &name);
[[nodiscard]] QString path(char postfix) const;
template <typename File>
[[nodiscard]] bool open(File &file, char postfix);
[[nodiscard]] bool writeHeader(QFileDevice &file);
void writeFooter(QFileDevice &file);
void finish();
QFile plainFile;
QSaveFile saveFile;
not_null<QFileDevice*> file;
QDataStream stream;
QString toDelete;
HashMd5 md5;
int32 dataSize = 0;
const FileOwner _owner = FileOwner();
QBuffer _buffer;
QDataStream _stream;
QByteArray _safeData;
QString _base;
HashMd5 _md5;
int _fullSize = 0;
};
@@ -302,152 +302,168 @@ struct FileWriteDescriptor {
FileWriteDescriptor::FileWriteDescriptor(
const FileKey &key,
FileOptions options)
: file((options & FileOption::Safe) ? (QFileDevice*)&saveFile : &plainFile) {
init(toFilePart(key), options);
FileOwner owner)
: FileWriteDescriptor(toFilePart(key), owner) {
}
FileWriteDescriptor::FileWriteDescriptor(
const QString &name,
FileOptions options)
: file((options & FileOption::Safe) ? (QFileDevice*)&saveFile : &plainFile) {
init(name, options);
FileOwner owner)
: _owner(owner) {
init(name);
}
FileWriteDescriptor::~FileWriteDescriptor() {
finish();
}
void FileWriteDescriptor::init(const QString &name, FileOptions options) {
if (options & FileOption::User) {
if (!_userWorking()) return;
} else {
if (!_working()) return;
}
const auto base = ((options & FileOption::User) ? _userBasePath : _basePath) + name;
saveFile.setFileName(base + 's');
plainFile.setFileName(base + '0');
if (options & FileOption::Safe) {
toDelete = base;
}
if (!file->open(QIODevice::WriteOnly)) {
LOG(("Storage Error: Could not open '%1' for writing.").arg(file->fileName()));
if (!(options & FileOption::Safe)) {
return;
}
file = &plainFile;
LOG(("Storage Info: Trying to fallback to '%1'.").arg(file->fileName()));
if (!file->open(QIODevice::WriteOnly)) {
LOG(("Storage Error: Could not open '%1' for safe writing.").arg(file->fileName()));
return;
}
}
file->write(tdfMagic, tdfMagicLen);
qint32 version = AppVersion;
file->write((const char*)&version, sizeof(version));
stream.setDevice(file);
stream.setVersion(QDataStream::Qt_5_1);
QString FileWriteDescriptor::path(char postfix) const {
return _base + postfix;
}
bool FileWriteDescriptor::writeData(const QByteArray &data) {
if (!file->isOpen()) {
template <typename File>
bool FileWriteDescriptor::open(File &file, char postfix) {
const auto name = path(postfix);
file.setFileName(name);
if (!writeHeader(file)) {
LOG(("Storage Error: Could not open '%1' for writing.").arg(name));
return false;
}
return true;
}
stream << data;
bool FileWriteDescriptor::writeHeader(QFileDevice &file) {
if (!file.open(QIODevice::WriteOnly)) {
return false;
}
file.write(tdfMagic, tdfMagicLen);
const auto version = qint32(AppVersion);
file.write((const char*)&version, sizeof(version));
return true;
}
void FileWriteDescriptor::writeFooter(QFileDevice &file) {
file.write((const char*)_md5.result(), 0x10);
}
void FileWriteDescriptor::init(const QString &name) {
const auto working = (_owner == FileOwner::User)
? _userWorking()
: _working();
if (!working) {
return;
}
const auto basePath = (_owner == FileOwner::User)
? _userBasePath
: _basePath;
_base = basePath + name;
_buffer.setBuffer(&_safeData);
const auto opened = _buffer.open(QIODevice::WriteOnly);
Assert(opened);
_stream.setDevice(&_buffer);
}
void FileWriteDescriptor::writeData(const QByteArray &data) {
if (!_stream.device()) {
return;
}
_stream << data;
quint32 len = data.isNull() ? 0xffffffff : data.size();
if (QSysInfo::ByteOrder != QSysInfo::BigEndian) {
len = qbswap(len);
}
md5.feed(&len, sizeof(len));
md5.feed(data.constData(), data.size());
dataSize += sizeof(len) + data.size();
return true;
_md5.feed(&len, sizeof(len));
_md5.feed(data.constData(), data.size());
_fullSize += sizeof(len) + data.size();
}
bool FileWriteDescriptor::writeEncrypted(
void FileWriteDescriptor::writeEncrypted(
EncryptedDescriptor &data,
const MTP::AuthKeyPtr &key) {
return writeData(PrepareEncrypted(data, key));
writeData(PrepareEncrypted(data, key));
}
void FileWriteDescriptor::finish() {
if (!file->isOpen()) {
if (!_stream.device()) {
return;
}
stream.setDevice(nullptr);
md5.feed(&dataSize, sizeof(dataSize));
_stream.setDevice(nullptr);
_md5.feed(&_fullSize, sizeof(_fullSize));
qint32 version = AppVersion;
md5.feed(&version, sizeof(version));
md5.feed(tdfMagic, tdfMagicLen);
file->write((const char*)md5.result(), 0x10);
_md5.feed(&version, sizeof(version));
_md5.feed(tdfMagic, tdfMagicLen);
if (saveFile.isOpen()) {
if (!saveFile.commit()) {
LOG(("Storage Error: Could not commit safe writing in '%1'."
).arg(saveFile.fileName()));
}
} else {
plainFile.close();
if (!toDelete.isEmpty()) {
QFile::remove(toDelete + '1');
if (!base::Platform::RenameWithOverwrite(
plainFile.fileName(),
saveFile.fileName())) {
LOG(("Storage Error: Could not rename '%1' to '%2'"
).arg(plainFile.fileName()
).arg(saveFile.fileName()));
}
_buffer.close();
const auto safe = path('s');
const auto simple = path('0');
const auto backup = path('1');
QSaveFile save;
if (open(save, 's')) {
save.write(_safeData);
writeFooter(save);
if (save.commit()) {
QFile::remove(simple);
QFile::remove(backup);
return;
}
LOG(("Storage Error: Could not commit '%1'.").arg(safe));
}
QFile plain;
if (open(plain, '0')) {
plain.write(_safeData);
writeFooter(plain);
base::Platform::FlushFileData(plain);
plain.close();
if (!toDelete.isEmpty()) {
QFile::remove(toDelete + '0');
QFile::remove(toDelete + '1');
QFile::remove(backup);
if (base::Platform::RenameWithOverwrite(simple, safe)) {
return;
}
QFile::remove(safe);
LOG(("Storage Error: Could not rename '%1' to '%2', removing."
).arg(simple
).arg(safe));
}
}
bool readFile(FileReadDescriptor &result, const QString &name, FileOptions options = FileOption::User | FileOption::Safe) {
if (options & FileOption::User) {
bool ReadFile(
FileReadDescriptor &result,
const QString &name,
FileOwner owner = FileOwner::User) {
if (owner == FileOwner::User) {
if (!_userWorking()) return false;
} else {
if (!_working()) return false;
}
const auto base = ((options & FileOption::User) ? _userBasePath : _basePath) + name;
const auto base = ((owner == FileOwner::User) ? _userBasePath : _basePath) + name;
// detect order of read attempts
QString toTry[2];
if (options & FileOption::Safe) {
const auto modern = base + 's';
if (QFileInfo(modern).exists()) {
toTry[0] = modern;
} else {
// Legacy way.
toTry[0] = base + '0';
QFileInfo toTry0(toTry[0]);
if (toTry0.exists()) {
toTry[1] = ((options & FileOption::User) ? _userBasePath : _basePath) + name + '1';
QFileInfo toTry1(toTry[1]);
if (toTry1.exists()) {
QDateTime mod0 = toTry0.lastModified(), mod1 = toTry1.lastModified();
if (mod0 < mod1) {
qSwap(toTry[0], toTry[1]);
}
} else {
toTry[1] = QString();
const auto modern = base + 's';
if (QFileInfo(modern).exists()) {
toTry[0] = modern;
} else {
// Legacy way.
toTry[0] = base + '0';
QFileInfo toTry0(toTry[0]);
if (toTry0.exists()) {
toTry[1] = ((owner == FileOwner::User) ? _userBasePath : _basePath) + name + '1';
QFileInfo toTry1(toTry[1]);
if (toTry1.exists()) {
QDateTime mod0 = toTry0.lastModified(), mod1 = toTry1.lastModified();
if (mod0 < mod1) {
qSwap(toTry[0], toTry[1]);
}
} else {
toTry[0][toTry[0].size() - 1] = '1';
toTry[1] = QString();
}
} else {
toTry[0][toTry[0].size() - 1] = '1';
}
} else {
toTry[0] = base + '0';
}
for (int32 i = 0; i < 2; ++i) {
QString fname(toTry[i]);
@@ -555,8 +571,12 @@ bool decryptLocal(EncryptedDescriptor &result, const QByteArray &encrypted, cons
return true;
}
bool readEncryptedFile(FileReadDescriptor &result, const QString &name, FileOptions options = FileOption::User | FileOption::Safe, const MTP::AuthKeyPtr &key = LocalKey) {
if (!readFile(result, name, options)) {
bool ReadEncryptedFile(
FileReadDescriptor &result,
const QString &name,
FileOwner owner = FileOwner::User,
const MTP::AuthKeyPtr &key = LocalKey) {
if (!ReadFile(result, name, owner)) {
return false;
}
QByteArray encrypted;
@@ -585,8 +605,12 @@ bool readEncryptedFile(FileReadDescriptor &result, const QString &name, FileOpti
return true;
}
bool readEncryptedFile(FileReadDescriptor &result, const FileKey &fkey, FileOptions options = FileOption::User | FileOption::Safe, const MTP::AuthKeyPtr &key = LocalKey) {
return readEncryptedFile(result, toFilePart(fkey), options, key);
bool ReadEncryptedFile(
FileReadDescriptor &result,
const FileKey &fkey,
FileOwner owner = FileOwner::User,
const MTP::AuthKeyPtr &key = LocalKey) {
return ReadEncryptedFile(result, toFilePart(fkey), owner, key);
}
FileKey _dataNameKey = 0;
@@ -810,14 +834,14 @@ void _writeLocations(WriteMapWhen when = WriteMapWhen::Soon) {
_manager->writingLocations();
if (_fileLocations.isEmpty()) {
if (_locationsKey) {
clearKey(_locationsKey);
ClearKey(_locationsKey);
_locationsKey = 0;
_mapChanged = true;
_writeMap();
}
} else {
if (!_locationsKey) {
_locationsKey = genKey();
_locationsKey = GenerateKey();
_mapChanged = true;
_writeMap(WriteMapWhen::Fast);
}
@@ -874,8 +898,8 @@ void _writeLocations(WriteMapWhen when = WriteMapWhen::Soon) {
void _readLocations() {
FileReadDescriptor locations;
if (!readEncryptedFile(locations, _locationsKey)) {
clearKey(_locationsKey);
if (!ReadEncryptedFile(locations, _locationsKey)) {
ClearKey(_locationsKey);
_locationsKey = 0;
_writeMap();
return;
@@ -924,7 +948,7 @@ void _readLocations() {
quint64 key;
qint32 size;
locations.stream >> url >> key >> size;
clearKey(key, FileOption::User);
ClearKey(key, FileOwner::User);
}
}
}
@@ -2136,7 +2160,7 @@ void _writeUserSettings() {
LOG(("App Info: writing encrypted user settings..."));
if (!_userSettingsKey) {
_userSettingsKey = genKey();
_userSettingsKey = GenerateKey();
_mapChanged = true;
_writeMap(WriteMapWhen::Fast);
}
@@ -2231,7 +2255,7 @@ void _writeUserSettings() {
void _readUserSettings() {
ReadSettingsContext context;
FileReadDescriptor userSettings;
if (!readEncryptedFile(userSettings, _userSettingsKey)) {
if (!ReadEncryptedFile(userSettings, _userSettingsKey)) {
LOG(("App Info: could not read encrypted user settings..."));
_readOldUserSettings(true, context);
@@ -2262,7 +2286,7 @@ void _readUserSettings() {
}
void _writeMtpData() {
FileWriteDescriptor mtp(toFilePart(_dataNameKey), FileOption::Safe);
FileWriteDescriptor mtp(toFilePart(_dataNameKey), FileOwner::Global);
if (!LocalKey) {
LOG(("App Error: localkey not created in _writeMtpData()"));
return;
@@ -2280,7 +2304,7 @@ void _writeMtpData() {
void _readMtpData() {
ReadSettingsContext context;
FileReadDescriptor mtp;
if (!readEncryptedFile(mtp, toFilePart(_dataNameKey), FileOption::Safe)) {
if (!ReadEncryptedFile(mtp, toFilePart(_dataNameKey), FileOwner::Global)) {
if (LocalKey) {
_readOldMtpData(true, context);
applyReadContext(std::move(context));
@@ -2318,7 +2342,7 @@ ReadMapState _readMap(const QByteArray &pass) {
+ '/';
FileReadDescriptor mapData;
if (!readFile(mapData, qsl("map"))) {
if (!ReadFile(mapData, qsl("map"))) {
return ReadMapFailed;
}
LOG(("App Info: reading map..."));
@@ -2411,7 +2435,7 @@ ReadMapState _readMap(const QByteArray &pass) {
} break;
case lskReportSpamStatusesOld: {
map.stream >> reportSpamStatusesKey;
clearKey(reportSpamStatusesKey);
ClearKey(reportSpamStatusesKey);
} break;
case lskTrustedBots: {
map.stream >> trustedBotsKey;
@@ -2666,7 +2690,7 @@ void start() {
ReadSettingsContext context;
FileReadDescriptor settingsData;
if (!readFile(settingsData, cTestMode() ? qsl("settings_test") : qsl("settings"), FileOption::Safe)) {
if (!ReadFile(settingsData, cTestMode() ? qsl("settings_test") : qsl("settings"), FileOwner::Global)) {
_readOldSettings(true, context);
_readOldUserSettings(false, context); // needed further in _readUserSettings
_readOldMtpData(false, context); // needed further in _readMtpData
@@ -2727,7 +2751,9 @@ void writeSettings() {
if (!QDir().exists(_basePath)) QDir().mkpath(_basePath);
FileWriteDescriptor settings(cTestMode() ? qsl("settings_test") : qsl("settings"), FileOption::Safe);
FileWriteDescriptor settings(
cTestMode() ? qsl("settings_test") : qsl("settings"),
FileOwner::Global);
if (_settingsSalt.isEmpty() || !SettingsKey) {
_settingsSalt.resize(LocalEncryptSaltSize);
memset_rand(_settingsSalt.data(), _settingsSalt.size());
@@ -3008,7 +3034,7 @@ void writeDrafts(const PeerId &peer, const MessageDraft &localDraft, const Messa
if (localDraft.msgId <= 0 && localDraft.textWithTags.text.isEmpty() && editDraft.msgId <= 0) {
auto i = _draftsMap.find(peer);
if (i != _draftsMap.cend()) {
clearKey(i.value());
ClearKey(i.value());
_draftsMap.erase(i);
_mapChanged = true;
_writeMap();
@@ -3018,7 +3044,7 @@ void writeDrafts(const PeerId &peer, const MessageDraft &localDraft, const Messa
} else {
auto i = _draftsMap.constFind(peer);
if (i == _draftsMap.cend()) {
i = _draftsMap.insert(peer, genKey());
i = _draftsMap.insert(peer, GenerateKey());
_mapChanged = true;
_writeMap(WriteMapWhen::Fast);
}
@@ -3049,7 +3075,7 @@ void writeDrafts(const PeerId &peer, const MessageDraft &localDraft, const Messa
void clearDraftCursors(const PeerId &peer) {
DraftsMap::iterator i = _draftCursorsMap.find(peer);
if (i != _draftCursorsMap.cend()) {
clearKey(i.value());
ClearKey(i.value());
_draftCursorsMap.erase(i);
_mapChanged = true;
_writeMap();
@@ -3063,7 +3089,7 @@ void _readDraftCursors(const PeerId &peer, MessageCursor &localCursor, MessageCu
}
FileReadDescriptor draft;
if (!readEncryptedFile(draft, j.value())) {
if (!ReadEncryptedFile(draft, j.value())) {
clearDraftCursors(peer);
return;
}
@@ -3097,8 +3123,8 @@ void readDraftsWithCursors(History *h) {
return;
}
FileReadDescriptor draft;
if (!readEncryptedFile(draft, j.value())) {
clearKey(j.value());
if (!ReadEncryptedFile(draft, j.value())) {
ClearKey(j.value());
_draftsMap.erase(j);
clearDraftCursors(peer);
return;
@@ -3126,7 +3152,7 @@ void readDraftsWithCursors(History *h) {
}
}
if (draftPeer != peer) {
clearKey(j.value());
ClearKey(j.value());
_draftsMap.erase(j);
clearDraftCursors(peer);
return;
@@ -3172,7 +3198,7 @@ void writeDraftCursors(const PeerId &peer, const MessageCursor &msgCursor, const
} else {
DraftsMap::const_iterator i = _draftCursorsMap.constFind(peer);
if (i == _draftCursorsMap.cend()) {
i = _draftCursorsMap.insert(peer, genKey());
i = _draftCursorsMap.insert(peer, GenerateKey());
_mapChanged = true;
_writeMap(WriteMapWhen::Fast);
}
@@ -3453,7 +3479,7 @@ void _writeStickerSets(FileKey &stickersKey, CheckSet checkSet, const Stickers::
const auto &sets = Auth().data().stickerSets();
if (sets.isEmpty()) {
if (stickersKey) {
clearKey(stickersKey);
ClearKey(stickersKey);
stickersKey = 0;
_mapChanged = true;
}
@@ -3505,7 +3531,7 @@ void _writeStickerSets(FileKey &stickersKey, CheckSet checkSet, const Stickers::
}
if (!setsCount && order.isEmpty()) {
if (stickersKey) {
clearKey(stickersKey);
ClearKey(stickersKey);
stickersKey = 0;
_mapChanged = true;
}
@@ -3515,7 +3541,7 @@ void _writeStickerSets(FileKey &stickersKey, CheckSet checkSet, const Stickers::
size += sizeof(qint32) + (order.size() * sizeof(quint64));
if (!stickersKey) {
stickersKey = genKey();
stickersKey = GenerateKey();
_mapChanged = true;
_writeMap(WriteMapWhen::Fast);
}
@@ -3541,15 +3567,15 @@ void _writeStickerSets(FileKey &stickersKey, CheckSet checkSet, const Stickers::
void _readStickerSets(FileKey &stickersKey, Stickers::Order *outOrder = nullptr, MTPDstickerSet::Flags readingFlags = 0) {
FileReadDescriptor stickers;
if (!readEncryptedFile(stickers, stickersKey)) {
clearKey(stickersKey);
if (!ReadEncryptedFile(stickers, stickersKey)) {
ClearKey(stickersKey);
stickersKey = 0;
_writeMap();
return;
}
const auto failed = [&] {
clearKey(stickersKey);
ClearKey(stickersKey);
stickersKey = 0;
};
@@ -3841,8 +3867,8 @@ void importOldRecentStickers() {
if (!_recentStickersKeyOld) return;
FileReadDescriptor stickers;
if (!readEncryptedFile(stickers, _recentStickersKeyOld)) {
clearKey(_recentStickersKeyOld);
if (!ReadEncryptedFile(stickers, _recentStickersKeyOld)) {
ClearKey(_recentStickersKeyOld);
_recentStickersKeyOld = 0;
_writeMap();
return;
@@ -3940,7 +3966,7 @@ void importOldRecentStickers() {
writeInstalledStickers();
writeUserSettings();
clearKey(_recentStickersKeyOld);
ClearKey(_recentStickersKeyOld);
_recentStickersKeyOld = 0;
_writeMap();
}
@@ -4061,7 +4087,7 @@ void writeSavedGifs() {
auto &saved = Auth().data().savedGifs();
if (saved.isEmpty()) {
if (_savedGifsKey) {
clearKey(_savedGifsKey);
ClearKey(_savedGifsKey);
_savedGifsKey = 0;
_mapChanged = true;
}
@@ -4073,7 +4099,7 @@ void writeSavedGifs() {
}
if (!_savedGifsKey) {
_savedGifsKey = genKey();
_savedGifsKey = GenerateKey();
_mapChanged = true;
_writeMap(WriteMapWhen::Fast);
}
@@ -4091,8 +4117,8 @@ void readSavedGifs() {
if (!_savedGifsKey) return;
FileReadDescriptor gifs;
if (!readEncryptedFile(gifs, _savedGifsKey)) {
clearKey(_savedGifsKey);
if (!ReadEncryptedFile(gifs, _savedGifsKey)) {
ClearKey(_savedGifsKey);
_savedGifsKey = 0;
_writeMap();
return;
@@ -4100,7 +4126,7 @@ void readSavedGifs() {
auto &saved = Auth().data().savedGifsRef();
const auto failed = [&] {
clearKey(_savedGifsKey);
ClearKey(_savedGifsKey);
_savedGifsKey = 0;
saved.clear();
};
@@ -4168,7 +4194,7 @@ void writeBackground(const Data::WallPaper &paper, const QImage &image) {
}
}
if (!backgroundKey) {
backgroundKey = genKey();
backgroundKey = GenerateKey();
_mapChanged = true;
_writeMap(WriteMapWhen::Fast);
}
@@ -4191,9 +4217,9 @@ bool readBackground() {
auto &backgroundKey = Window::Theme::IsNightMode()
? _backgroundKeyNight
: _backgroundKeyDay;
if (!readEncryptedFile(bg, backgroundKey)) {
if (!ReadEncryptedFile(bg, backgroundKey)) {
if (backgroundKey) {
clearKey(backgroundKey);
ClearKey(backgroundKey);
backgroundKey = 0;
_mapChanged = true;
_writeMap();
@@ -4308,7 +4334,7 @@ Window::Theme::Saved readThemeUsingKey(FileKey key) {
using namespace Window::Theme;
FileReadDescriptor theme;
if (!readEncryptedFile(theme, key, FileOption::Safe, SettingsKey)) {
if (!ReadEncryptedFile(theme, key, FileOwner::Global, SettingsKey)) {
return {};
}
@@ -4397,7 +4423,7 @@ void writeTheme(const Window::Theme::Saved &saved) {
: _themeKeyDay;
if (saved.object.content.isEmpty()) {
if (themeKey) {
clearKey(themeKey);
ClearKey(themeKey, FileOwner::Global);
themeKey = 0;
writeSettings();
}
@@ -4405,7 +4431,7 @@ void writeTheme(const Window::Theme::Saved &saved) {
}
if (!themeKey) {
themeKey = genKey(FileOption::Safe);
themeKey = GenerateKey(FileOwner::Global);
writeSettings();
}
@@ -4442,7 +4468,7 @@ void writeTheme(const Window::Theme::Saved &saved) {
<< cache.background
<< quint32(cache.tiled ? 1 : 0);
FileWriteDescriptor file(themeKey, FileOption::Safe);
FileWriteDescriptor file(themeKey, FileOwner::Global);
file.writeEncrypted(data, SettingsKey);
}
@@ -4494,7 +4520,7 @@ Window::Theme::Saved readThemeAfterSwitch() {
void readLangPack() {
FileReadDescriptor langpack;
if (!_langPackKey || !readEncryptedFile(langpack, _langPackKey, FileOption::Safe, SettingsKey)) {
if (!_langPackKey || !ReadEncryptedFile(langpack, _langPackKey, FileOwner::Global, SettingsKey)) {
return;
}
auto data = QByteArray();
@@ -4507,21 +4533,21 @@ void readLangPack() {
void writeLangPack() {
auto langpack = Lang::Current().serialize();
if (!_langPackKey) {
_langPackKey = genKey(FileOption::Safe);
_langPackKey = GenerateKey(FileOwner::Global);
writeSettings();
}
EncryptedDescriptor data(Serialize::bytearraySize(langpack));
data.stream << langpack;
FileWriteDescriptor file(_langPackKey, FileOption::Safe);
FileWriteDescriptor file(_langPackKey, FileOwner::Global);
file.writeEncrypted(data, SettingsKey);
}
void saveRecentLanguages(const std::vector<Lang::Language> &list) {
if (list.empty()) {
if (_languagesKey) {
clearKey(_languagesKey, FileOption::Safe);
ClearKey(_languagesKey, FileOwner::Global);
_languagesKey = 0;
writeSettings();
}
@@ -4537,7 +4563,7 @@ void saveRecentLanguages(const std::vector<Lang::Language> &list) {
+ Serialize::stringSize(language.nativeName);
}
if (!_languagesKey) {
_languagesKey = genKey(FileOption::Safe);
_languagesKey = GenerateKey(FileOwner::Global);
writeSettings();
}
@@ -4552,7 +4578,7 @@ void saveRecentLanguages(const std::vector<Lang::Language> &list) {
<< language.nativeName;
}
FileWriteDescriptor file(_languagesKey, FileOption::Safe);
FileWriteDescriptor file(_languagesKey, FileOwner::Global);
file.writeEncrypted(data, SettingsKey);
}
@@ -4584,7 +4610,7 @@ void removeRecentLanguage(const QString &id) {
std::vector<Lang::Language> readRecentLanguages() {
FileReadDescriptor languages;
if (!_languagesKey || !readEncryptedFile(languages, _languagesKey, FileOption::Safe, SettingsKey)) {
if (!_languagesKey || !ReadEncryptedFile(languages, _languagesKey, FileOwner::Global, SettingsKey)) {
return {};
}
qint32 count = 0;
@@ -4619,7 +4645,7 @@ Window::Theme::Object ReadThemeContent() {
}
FileReadDescriptor theme;
if (!readEncryptedFile(theme, themeKey, FileOption::Safe, SettingsKey)) {
if (!ReadEncryptedFile(theme, themeKey, FileOwner::Global, SettingsKey)) {
return Object();
}
@@ -4644,14 +4670,14 @@ void writeRecentHashtagsAndBots() {
if (write.isEmpty() && search.isEmpty() && bots.isEmpty()) readRecentHashtagsAndBots();
if (write.isEmpty() && search.isEmpty() && bots.isEmpty()) {
if (_recentHashtagsAndBotsKey) {
clearKey(_recentHashtagsAndBotsKey);
ClearKey(_recentHashtagsAndBotsKey);
_recentHashtagsAndBotsKey = 0;
_mapChanged = true;
}
_writeMap();
} else {
if (!_recentHashtagsAndBotsKey) {
_recentHashtagsAndBotsKey = genKey();
_recentHashtagsAndBotsKey = GenerateKey();
_mapChanged = true;
_writeMap(WriteMapWhen::Fast);
}
@@ -4696,8 +4722,8 @@ void readRecentHashtagsAndBots() {
if (!_recentHashtagsAndBotsKey) return;
FileReadDescriptor hashtags;
if (!readEncryptedFile(hashtags, _recentHashtagsAndBotsKey)) {
clearKey(_recentHashtagsAndBotsKey);
if (!ReadEncryptedFile(hashtags, _recentHashtagsAndBotsKey)) {
ClearKey(_recentHashtagsAndBotsKey);
_recentHashtagsAndBotsKey = 0;
_writeMap();
return;
@@ -4851,14 +4877,14 @@ void WriteExportSettings(const Export::Settings &settings) {
&& settings.availableAt == check.availableAt
&& !settings.onlySinglePeer()) {
if (_exportSettingsKey) {
clearKey(_exportSettingsKey);
ClearKey(_exportSettingsKey);
_exportSettingsKey = 0;
_mapChanged = true;
}
_writeMap();
} else {
if (!_exportSettingsKey) {
_exportSettingsKey = genKey();
_exportSettingsKey = GenerateKey();
_mapChanged = true;
_writeMap(WriteMapWhen::Fast);
}
@@ -4905,8 +4931,8 @@ void WriteExportSettings(const Export::Settings &settings) {
Export::Settings ReadExportSettings() {
FileReadDescriptor file;
if (!readEncryptedFile(file, _exportSettingsKey)) {
clearKey(_exportSettingsKey);
if (!ReadEncryptedFile(file, _exportSettingsKey)) {
ClearKey(_exportSettingsKey);
_exportSettingsKey = 0;
_writeMap();
return Export::Settings();
@@ -5006,14 +5032,14 @@ void writeTrustedBots() {
if (_trustedBots.isEmpty()) {
if (_trustedBotsKey) {
clearKey(_trustedBotsKey);
ClearKey(_trustedBotsKey);
_trustedBotsKey = 0;
_mapChanged = true;
_writeMap();
}
} else {
if (!_trustedBotsKey) {
_trustedBotsKey = genKey();
_trustedBotsKey = GenerateKey();
_mapChanged = true;
_writeMap(WriteMapWhen::Fast);
}
@@ -5033,8 +5059,8 @@ void readTrustedBots() {
if (!_trustedBotsKey) return;
FileReadDescriptor trusted;
if (!readEncryptedFile(trusted, _trustedBotsKey)) {
clearKey(_trustedBotsKey);
if (!ReadEncryptedFile(trusted, _trustedBotsKey)) {
ClearKey(_trustedBotsKey);
_trustedBotsKey = 0;
_writeMap();
return;

View File

@@ -130,6 +130,11 @@ const auto kIcons = std::vector<FilterIcons>{
&st::foldersWorkActive,
"\xF0\x9F\x92\xBC"_cs.utf16()
},
{
&st::filtersEdit,
&st::filtersEdit,
QString()
}
};
} // namespace
@@ -145,6 +150,9 @@ std::optional<FilterIcon> LookupFilterIconByEmoji(const QString &emoji) {
auto result = base::flat_map<EmojiPtr, FilterIcon>();
auto index = 0;
for (const auto &entry : kIcons) {
if (entry.emoji.isEmpty()) {
continue;
}
const auto emoji = Ui::Emoji::Find(entry.emoji);
Assert(emoji != nullptr);
result.emplace(emoji, static_cast<FilterIcon>(index++));

View File

@@ -44,6 +44,8 @@ enum class FilterIcon : uchar {
Trade,
Travel,
Work,
Edit,
};
struct FilterIcons {

View File

@@ -26,6 +26,8 @@ filtersCustomActive: icon {{ "filters/filters_custom_active", sideBarIconFgActiv
filtersSetup: icon {{ "filters/filters_setup", sideBarIconFg }};
filtersSetupActive: icon {{ "filters/filters_setup", sideBarIconFgActive }};
filtersEdit: icon {{ "filters/filters_edit", sideBarIconFg }};
foldersCat: icon {{ "filters/folders_cat", sideBarIconFg }};
foldersCatActive: icon {{ "filters/folders_cat_active", sideBarIconFgActive }};
foldersCrown: icon {{ "filters/folders_crown", sideBarIconFg }};

View File

@@ -273,9 +273,7 @@ void SeparatePanel::showBox(
}
void SeparatePanel::showToast(const QString &text) {
auto toast = Ui::Toast::Config();
toast.text = text;
Ui::Toast::Show(this, toast);
Ui::Toast::Show(this, text);
}
void SeparatePanel::ensureLayerCreated() {

View File

@@ -106,6 +106,7 @@ void Controller::showRightColumn(object_ptr<TWidget> widget) {
void Controller::sideBarChanged() {
_widget.setMinimumWidth(_widget.computeMinWidth());
_widget.updateControlsGeometry();
_widget.fixOrder();
}
void Controller::activate() {

View File

@@ -198,7 +198,7 @@ void FiltersMenu::setupList() {
_container,
-1,
tr::lng_filters_setup(tr::now),
Ui::FilterIcon::Setup);
Ui::FilterIcon::Edit);
_reorder = std::make_unique<Ui::VerticalLayoutReorder>(_list, &_scroll);
_reorder->updates(

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