Compare commits
136 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd4a543bab | ||
|
|
d525e56053 | ||
|
|
dab5d1f994 | ||
|
|
de3b52425c | ||
|
|
844fd58a97 | ||
|
|
de2bad51d3 | ||
|
|
1424ea3540 | ||
|
|
a8efd0ef3d | ||
|
|
1204e282d3 | ||
|
|
6588242793 | ||
|
|
b1ba9a42c6 | ||
|
|
ab0d2bf9c6 | ||
|
|
80028e41f3 | ||
|
|
2c581adc55 | ||
|
|
f0e8c1e325 | ||
|
|
a2db9de4d7 | ||
|
|
a228c62286 | ||
|
|
37d940eca6 | ||
|
|
f7c24c54a1 | ||
|
|
19ce1edc16 | ||
|
|
21b10cebe0 | ||
|
|
50435f7783 | ||
|
|
1b789de4f4 | ||
|
|
a50310f0c1 | ||
|
|
eb02a7861a | ||
|
|
8759ca4577 | ||
|
|
d5c6d9a231 | ||
|
|
63f179e93e | ||
|
|
cfcc1b1ce7 | ||
|
|
da1945d0ca | ||
|
|
12252ef1aa | ||
|
|
1eef94e8d9 | ||
|
|
0984e631fa | ||
|
|
ec064a904d | ||
|
|
b47692e920 | ||
|
|
132f127f3f | ||
|
|
5c44b851fe | ||
|
|
2f5bed2899 | ||
|
|
cf76933352 | ||
|
|
eaa4c5e5b1 | ||
|
|
a4b5b6e370 | ||
|
|
c1be1ca4ae | ||
|
|
b2df781b76 | ||
|
|
38815c1ca8 | ||
|
|
2ec92f541c | ||
|
|
7ce8b42216 | ||
|
|
17511749de | ||
|
|
4f6c7657bf | ||
|
|
54085c70a4 | ||
|
|
e6c4b96c54 | ||
|
|
b75221737a | ||
|
|
c336d725ea | ||
|
|
d0fcc40d25 | ||
|
|
422bfd973b | ||
|
|
d4db679ce8 | ||
|
|
2c7d8858c0 | ||
|
|
155bbed3f4 | ||
|
|
b1517c68fb | ||
|
|
d206ba7e1d | ||
|
|
af100c2d13 | ||
|
|
1f25777929 | ||
|
|
a566405598 | ||
|
|
b02967a44e | ||
|
|
e0135e509d | ||
|
|
8274fddcbc | ||
|
|
82c45871c7 | ||
|
|
2164caaab7 | ||
|
|
f4b162cbaf | ||
|
|
4bc4584868 | ||
|
|
890a126423 | ||
|
|
42cc24e167 | ||
|
|
26b9146c32 | ||
|
|
ab6f5ae2ac | ||
|
|
559d4cf4da | ||
|
|
449f2d2f94 | ||
|
|
038f19d055 | ||
|
|
10d405aef4 | ||
|
|
efd4cceb19 | ||
|
|
822a3b69b5 | ||
|
|
a632798383 | ||
|
|
cca08e3946 | ||
|
|
4d267327b8 | ||
|
|
06798adce4 | ||
|
|
34c0d97c54 | ||
|
|
4b7f594b0e | ||
|
|
cfb43081c7 | ||
|
|
703ea9aacd | ||
|
|
de9b21e436 | ||
|
|
73c0ea4b7d | ||
|
|
3e681e5449 | ||
|
|
bc2f96251f | ||
|
|
15f83892a1 | ||
|
|
b6fafdd8f7 | ||
|
|
139b9723d7 | ||
|
|
d152782115 | ||
|
|
35356a1736 | ||
|
|
ef27670954 | ||
|
|
8cf9dc3319 | ||
|
|
59f2f750b4 | ||
|
|
8069fdd873 | ||
|
|
52721847f4 | ||
|
|
e492bbb883 | ||
|
|
7f20cc7b44 | ||
|
|
7fbce765c9 | ||
|
|
f771ad8cb1 | ||
|
|
6a53fc7edc | ||
|
|
889c3293e7 | ||
|
|
fa4b7145f5 | ||
|
|
a5be9d78d8 | ||
|
|
21c562fcb7 | ||
|
|
626c062bf0 | ||
|
|
e92ae40ecb | ||
|
|
ce256161f1 | ||
|
|
3bf9a1c70b | ||
|
|
3202a5f081 | ||
|
|
e99f650eaa | ||
|
|
c6097d3d11 | ||
|
|
58b5b3deec | ||
|
|
5a63428093 | ||
|
|
0c42bca111 | ||
|
|
13bf089672 | ||
|
|
f73264025d | ||
|
|
0a6fb696a3 | ||
|
|
bc2e6c4fd1 | ||
|
|
d822f8e9ff | ||
|
|
86362875dd | ||
|
|
0a4a96d4cd | ||
|
|
1c33eee80a | ||
|
|
4f5558d28c | ||
|
|
3fbd68cff9 | ||
|
|
ee8c6f68d7 | ||
|
|
8d4174afb5 | ||
|
|
9150cc77f9 | ||
|
|
8d31769846 | ||
|
|
13c00949ed | ||
|
|
c4d822ba02 |
2
.github/workflows/linux.yml
vendored
@@ -53,7 +53,7 @@ jobs:
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: scl enable devtoolset-9 -- bash --noprofile --norc -eo pipefail {0}
|
||||
shell: scl enable llvm-toolset-7.0 -- scl enable devtoolset-9 -- bash --noprofile --norc -eo pipefail {0}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
|
||||
445
.github/workflows/mac.yml
vendored
@@ -13,7 +13,6 @@ on:
|
||||
- '!.github/workflows/mac.yml'
|
||||
- 'lib/xdg/**'
|
||||
- 'snap/**'
|
||||
- 'Telegram/build/**'
|
||||
- 'Telegram/Resources/uwp/**'
|
||||
- 'Telegram/Resources/winrc/**'
|
||||
- 'Telegram/SourceFiles/platform/win/**'
|
||||
@@ -31,7 +30,6 @@ on:
|
||||
- '!.github/workflows/mac.yml'
|
||||
- 'lib/xdg/**'
|
||||
- 'snap/**'
|
||||
- 'Telegram/build/**'
|
||||
- 'Telegram/Resources/uwp/**'
|
||||
- 'Telegram/Resources/winrc/**'
|
||||
- 'Telegram/SourceFiles/platform/win/**'
|
||||
@@ -49,19 +47,8 @@ jobs:
|
||||
defines:
|
||||
- ""
|
||||
env:
|
||||
MIN_MAC: "-mmacosx-version-min=10.12"
|
||||
UNGUARDED: "-Werror=unguarded-availability-new"
|
||||
GIT: "https://github.com"
|
||||
MACOSX_DEPLOYMENT_TARGET: "10.12"
|
||||
XZ: "xz-5.2.5"
|
||||
QT: "5_15_2"
|
||||
OPENSSL_VER: "1_1_1"
|
||||
LIBICONV_VER: "libiconv-1.16"
|
||||
UPLOAD_ARTIFACT: "false"
|
||||
ONLY_CACHE: "false"
|
||||
MANUAL_CACHING: "2"
|
||||
DOC_PATH: "docs/building-mac.md"
|
||||
AUTO_CACHING: "1"
|
||||
|
||||
steps:
|
||||
- name: Get repository name.
|
||||
@@ -84,433 +71,23 @@ jobs:
|
||||
|
||||
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
|
||||
|
||||
xcodebuild -version > CACHE_KEY.txt
|
||||
echo $MIN_MAC >> CACHE_KEY.txt
|
||||
echo $MANUAL_CACHING >> CACHE_KEY.txt
|
||||
echo "$GITHUB_WORKSPACE" >> CACHE_KEY.txt
|
||||
if [ "$AUTO_CACHING" == "1" ]; then
|
||||
thisFile=$REPO_NAME/.github/workflows/mac.yml
|
||||
echo `md5 -q $thisFile` >> CACHE_KEY.txt
|
||||
fi
|
||||
echo "CACHE_KEY=`md5 -q CACHE_KEY.txt`" >> $GITHUB_ENV
|
||||
|
||||
echo "$PWD/Libraries/depot_tools" >> $GITHUB_PATH
|
||||
|
||||
mkdir -p Libraries
|
||||
cd Libraries
|
||||
echo "LibrariesPath=`pwd`" >> $GITHUB_ENV
|
||||
echo "PREFIX=`pwd`/local" >> $GITHUB_ENV
|
||||
echo "QT_PREFIX=`pwd`/local/Qt-5.15.2" >> $GITHUB_ENV
|
||||
|
||||
curl -o tg_owt-version.json https://api.github.com/repos/desktop-app/tg_owt/git/refs/heads/master
|
||||
|
||||
- name: Patches.
|
||||
run: |
|
||||
echo "Find necessary commit from doc."
|
||||
checkoutCommit=$(grep -A 1 "cd patches" $REPO_NAME/$DOC_PATH | sed -n 2p)
|
||||
cd $LibrariesPath
|
||||
git clone $GIT/desktop-app/patches.git
|
||||
cd Patches
|
||||
eval $checkoutCommit
|
||||
|
||||
- name: XZ.
|
||||
run: |
|
||||
cd $LibrariesPath
|
||||
|
||||
wget https://tukaani.org/xz/$XZ.tar.gz
|
||||
tar -xvzf $XZ.tar.gz
|
||||
cd $XZ
|
||||
CFLAGS="$MIN_MAC" LDFLAGS="$MIN_MAC" ./configure --prefix=$PREFIX
|
||||
make -j$(nproc)
|
||||
make install
|
||||
|
||||
- name: Zlib.
|
||||
run: |
|
||||
cd $LibrariesPath
|
||||
|
||||
git clone $GIT/desktop-app/zlib.git
|
||||
cd zlib
|
||||
CFLAGS="$MIN_MAC $UNGUARDED" LDFLAGS="$MIN_MAC" ./configure --prefix=$PREFIX
|
||||
make -j$(nproc)
|
||||
make install
|
||||
|
||||
- name: MozJPEG.
|
||||
run: |
|
||||
cd $LibrariesPath
|
||||
|
||||
git clone -b v4.0.1-rc2 $GIT/mozilla/mozjpeg.git
|
||||
cd mozjpeg
|
||||
cmake -B build . \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=$PREFIX \
|
||||
-DWITH_JPEG8=ON \
|
||||
-DPNG_SUPPORTED=OFF
|
||||
cmake --build build -j$(nproc)
|
||||
cmake --install build
|
||||
|
||||
- name: OpenSSL cache.
|
||||
id: cache-openssl
|
||||
- name: ThirdParty cache.
|
||||
id: cache-third-party
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/openssl_${{ env.OPENSSL_VER }}
|
||||
key: ${{ runner.OS }}-${{ env.OPENSSL_VER }}-${{ env.CACHE_KEY }}
|
||||
- name: OpenSSL.
|
||||
if: steps.cache-openssl.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cd $LibrariesPath
|
||||
path: ThirdParty
|
||||
key: ${{ runner.OS }}-third-party
|
||||
|
||||
git clone $GIT/openssl/openssl openssl
|
||||
cd openssl
|
||||
git checkout OpenSSL_"$OPENSSL_VER"-stable
|
||||
./Configure \
|
||||
--prefix=$PREFIX \
|
||||
no-tests \
|
||||
darwin64-x86_64-cc \
|
||||
-static \
|
||||
$MIN_MAC
|
||||
make build_libs -j$(nproc)
|
||||
|
||||
SSL_DIR=$LibrariesPath/openssl_$OPENSSL_VER
|
||||
mkdir -p $SSL_DIR/include
|
||||
copyLib() {
|
||||
cp $1.a $SSL_DIR/$1.a
|
||||
}
|
||||
copyLib libssl
|
||||
copyLib libcrypto
|
||||
cp -R include/. $SSL_DIR/include/
|
||||
|
||||
- name: Opus cache.
|
||||
id: cache-opus
|
||||
- name: Libraries cache.
|
||||
id: cache-libs
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/opus-cache
|
||||
key: ${{ runner.OS }}-opus-${{ env.CACHE_KEY }}
|
||||
- name: Opus.
|
||||
if: steps.cache-opus.outputs.cache-hit != 'true'
|
||||
path: Libraries
|
||||
key: ${{ runner.OS }}-libs
|
||||
|
||||
- name: Libraries.
|
||||
run: |
|
||||
cd $LibrariesPath
|
||||
|
||||
git clone $GIT/xiph/opus
|
||||
cd opus
|
||||
git checkout v1.3
|
||||
./autogen.sh
|
||||
CFLAGS="$MIN_MAC $UNGUARDED" CPPFLAGS="$MIN_MAC $UNGUARDED" LDFLAGS="$MIN_MAC" ./configure --prefix=$PREFIX
|
||||
make -j$(nproc)
|
||||
make DESTDIR="$LibrariesPath/opus-cache" install
|
||||
- name: Opus install.
|
||||
run: |
|
||||
cd $LibrariesPath
|
||||
cp -R opus-cache/. /
|
||||
|
||||
- name: Rnnoise.
|
||||
run: |
|
||||
cd $LibrariesPath
|
||||
|
||||
git clone $GIT/desktop-app/rnnoise.git
|
||||
mkdir -p rnnoise/out/Debug
|
||||
cd rnnoise/out/Debug
|
||||
cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug ../..
|
||||
ninja
|
||||
|
||||
- name: Libiconv cache.
|
||||
id: cache-libiconv
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/libiconv-cache
|
||||
key: ${{ runner.OS }}-${{ env.LIBICONV_VER }}-${{ env.CACHE_KEY }}
|
||||
- name: Libiconv.
|
||||
if: steps.cache-libiconv.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cd $LibrariesPath
|
||||
|
||||
wget https://ftp.gnu.org/pub/gnu/libiconv/"$LIBICONV_VER".tar.gz
|
||||
tar -xvzf "$LIBICONV_VER".tar.gz
|
||||
cd $LIBICONV_VER
|
||||
CFLAGS="$MIN_MAC $UNGUARDED" CPPFLAGS="$MIN_MAC $UNGUARDED" LDFLAGS="$MIN_MAC" ./configure --enable-static --prefix=$PREFIX
|
||||
make -j$(nproc)
|
||||
make DESTDIR="$LibrariesPath/libiconv-cache" install
|
||||
- name: Libiconv install.
|
||||
run: |
|
||||
cd $LibrariesPath
|
||||
cp -R libiconv-cache/. /
|
||||
|
||||
- name: FFmpeg cache.
|
||||
id: cache-ffmpeg
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/ffmpeg-cache
|
||||
key: ${{ runner.OS }}-ffmpeg-${{ env.CACHE_KEY }}
|
||||
- name: FFmpeg.
|
||||
if: steps.cache-ffmpeg.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cd $LibrariesPath
|
||||
|
||||
git clone $GIT/FFmpeg/FFmpeg.git ffmpeg
|
||||
cd ffmpeg
|
||||
git checkout release/4.4
|
||||
CFLAGS=`freetype-config --cflags`
|
||||
LDFLAGS=`freetype-config --libs`
|
||||
PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig:/usr/X11/lib/pkgconfig
|
||||
|
||||
./configure --prefix=$LibrariesPath/ffmpeg-cache \
|
||||
--extra-cflags="$MIN_MAC $UNGUARDED" \
|
||||
--extra-cxxflags="$MIN_MAC $UNGUARDED" \
|
||||
--extra-ldflags="$MIN_MAC" \
|
||||
--x86asmexe=`pwd`/macos_yasm_wrap.sh \
|
||||
--enable-protocol=file \
|
||||
--enable-libopus \
|
||||
--disable-programs \
|
||||
--disable-doc \
|
||||
--disable-network \
|
||||
--disable-everything \
|
||||
--enable-hwaccel=h264_videotoolbox \
|
||||
--enable-hwaccel=hevc_videotoolbox \
|
||||
--enable-hwaccel=mpeg1_videotoolbox \
|
||||
--enable-hwaccel=mpeg2_videotoolbox \
|
||||
--enable-hwaccel=mpeg4_videotoolbox \
|
||||
--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 \
|
||||
--enable-decoder=hevc \
|
||||
--enable-decoder=mp1 \
|
||||
--enable-decoder=mp1float \
|
||||
--enable-decoder=mp2 \
|
||||
--enable-decoder=mp2float \
|
||||
--enable-decoder=mp3 \
|
||||
--enable-decoder=mp3adu \
|
||||
--enable-decoder=mp3adufloat \
|
||||
--enable-decoder=mp3float \
|
||||
--enable-decoder=mp3on4 \
|
||||
--enable-decoder=mp3on4float \
|
||||
--enable-decoder=mpeg4 \
|
||||
--enable-decoder=msmpeg4v2 \
|
||||
--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 \
|
||||
--enable-decoder=pcm_s16le_planar \
|
||||
--enable-decoder=pcm_s24be \
|
||||
--enable-decoder=pcm_s24daud \
|
||||
--enable-decoder=pcm_s24le \
|
||||
--enable-decoder=pcm_s24le_planar \
|
||||
--enable-decoder=pcm_s32be \
|
||||
--enable-decoder=pcm_s32le \
|
||||
--enable-decoder=pcm_s32le_planar \
|
||||
--enable-decoder=pcm_s64be \
|
||||
--enable-decoder=pcm_s64le \
|
||||
--enable-decoder=pcm_s8 \
|
||||
--enable-decoder=pcm_s8_planar \
|
||||
--enable-decoder=pcm_u16be \
|
||||
--enable-decoder=pcm_u16le \
|
||||
--enable-decoder=pcm_u24be \
|
||||
--enable-decoder=pcm_u24le \
|
||||
--enable-decoder=pcm_u32be \
|
||||
--enable-decoder=pcm_u32le \
|
||||
--enable-decoder=pcm_u8 \
|
||||
--enable-decoder=vorbis \
|
||||
--enable-decoder=wavpack \
|
||||
--enable-decoder=wmalossless \
|
||||
--enable-decoder=wmapro \
|
||||
--enable-decoder=wmav1 \
|
||||
--enable-decoder=wmav2 \
|
||||
--enable-decoder=wmavoice \
|
||||
--enable-encoder=libopus \
|
||||
--enable-parser=aac \
|
||||
--enable-parser=aac_latm \
|
||||
--enable-parser=flac \
|
||||
--enable-parser=h264 \
|
||||
--enable-parser=hevc \
|
||||
--enable-parser=mpeg4video \
|
||||
--enable-parser=mpegaudio \
|
||||
--enable-parser=opus \
|
||||
--enable-parser=vorbis \
|
||||
--enable-demuxer=aac \
|
||||
--enable-demuxer=flac \
|
||||
--enable-demuxer=gif \
|
||||
--enable-demuxer=h264 \
|
||||
--enable-demuxer=hevc \
|
||||
--enable-demuxer=m4v \
|
||||
--enable-demuxer=mov \
|
||||
--enable-demuxer=mp3 \
|
||||
--enable-demuxer=ogg \
|
||||
--enable-demuxer=wav \
|
||||
--enable-muxer=ogg \
|
||||
--enable-muxer=opus
|
||||
|
||||
make -j$(nproc)
|
||||
make install
|
||||
- name: FFmpeg install.
|
||||
run: |
|
||||
cd $LibrariesPath
|
||||
# List of files from cmake/external/ffmpeg/CMakeLists.txt.
|
||||
copyLib() {
|
||||
mkdir -p ffmpeg/$1
|
||||
\cp -fR ffmpeg-cache/lib/$1.a ffmpeg/$1/$1.a
|
||||
}
|
||||
copyLib libavformat
|
||||
copyLib libavcodec
|
||||
copyLib libswresample
|
||||
copyLib libswscale
|
||||
copyLib libavutil
|
||||
|
||||
cp -R ffmpeg-cache/. $PREFIX
|
||||
cp -R ffmpeg-cache/include/. ffmpeg/
|
||||
|
||||
- name: OpenAL Soft.
|
||||
run: |
|
||||
cd $LibrariesPath
|
||||
|
||||
git clone --branch capture_with_webrtc $GIT/telegramdesktop/openal-soft.git
|
||||
cd openal-soft/build
|
||||
|
||||
CFLAGS="$UNGUARDED" CPPFLAGS="$UNGUARDED" cmake \
|
||||
-D CMAKE_INSTALL_PREFIX:PATH=$PREFIX \
|
||||
-D ALSOFT_EXAMPLES=OFF \
|
||||
-D LIBTYPE:STRING=STATIC \
|
||||
-D CMAKE_OSX_DEPLOYMENT_TARGET:STRING=$MACOSX_DEPLOYMENT_TARGET ..
|
||||
|
||||
make -j$(nproc)
|
||||
make install
|
||||
|
||||
- name: Crashpad cache.
|
||||
id: cache-crashpad
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/crashpad
|
||||
key: ${{ runner.OS }}-crashpad-${{ env.CACHE_KEY }}-${{ hashFiles('**/crashpad.diff') }}-${{ hashFiles('**/mini_chromium.diff') }}
|
||||
- name: Crashpad.
|
||||
if: steps.cache-crashpad.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cd Libraries
|
||||
echo Install GYP for Crashpad.
|
||||
git clone https://chromium.googlesource.com/external/gyp
|
||||
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
|
||||
cd gyp
|
||||
git checkout 9f2a7bb1
|
||||
git apply $LibrariesPath/patches/gyp.diff
|
||||
./setup.py build
|
||||
sudo ./setup.py install
|
||||
|
||||
cd $LibrariesPath
|
||||
|
||||
git clone https://chromium.googlesource.com/crashpad/crashpad.git
|
||||
cd crashpad
|
||||
git checkout feb3aa3923
|
||||
git apply ../patches/crashpad.diff
|
||||
cd third_party/mini_chromium
|
||||
git clone https://chromium.googlesource.com/chromium/mini_chromium
|
||||
cd mini_chromium
|
||||
git checkout 7c5b0c1ab4
|
||||
git apply ../../../../patches/mini_chromium.diff
|
||||
cd ../../gtest
|
||||
git clone https://chromium.googlesource.com/external/github.com/google/googletest gtest
|
||||
cd gtest
|
||||
git checkout d62d6c6556
|
||||
cd ../../..
|
||||
|
||||
build/gyp_crashpad.py -Dmac_deployment_target=10.10
|
||||
ninja -C out/Debug
|
||||
|
||||
- name: Qt 5.15.2 cache.
|
||||
id: cache-qt
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/qt-cache
|
||||
key: ${{ runner.OS }}-qt-${{ env.CACHE_KEY }}-${{ hashFiles('**/qtbase_5_15_2/*') }}
|
||||
- name: Use cached Qt 5.15.2.
|
||||
if: steps.cache-qt.outputs.cache-hit == 'true'
|
||||
run: |
|
||||
cd $LibrariesPath
|
||||
mv qt-cache Qt-5.15.2
|
||||
mkdir -p $QT_PREFIX
|
||||
mv -f Qt-5.15.2 "$(dirname "$QT_PREFIX")"/
|
||||
- name: Qt 5.15.2 build.
|
||||
if: steps.cache-qt.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cd $LibrariesPath
|
||||
|
||||
git clone git://code.qt.io/qt/qt5.git qt_$QT
|
||||
cd qt_$QT
|
||||
perl init-repository --module-subset=qtbase,qtimageformats,qtsvg
|
||||
git checkout v5.15.2
|
||||
git submodule update qtbase
|
||||
git submodule update qtimageformats
|
||||
git submodule update qtsvg
|
||||
cd qtbase
|
||||
find ../../patches/qtbase_$QT -type f -print0 | sort -z | xargs -0 git apply
|
||||
cd ..
|
||||
|
||||
./configure \
|
||||
-prefix "$QT_PREFIX" \
|
||||
-debug \
|
||||
-force-debug-info \
|
||||
-opensource \
|
||||
-confirm-license \
|
||||
-static \
|
||||
-opengl desktop \
|
||||
-no-openssl \
|
||||
-securetransport \
|
||||
-nomake examples \
|
||||
-nomake tests \
|
||||
-platform macx-clang \
|
||||
-I "$PREFIX/include" \
|
||||
LIBJPEG_LIBS="$PREFIX/lib/libjpeg.a" \
|
||||
ZLIB_LIBS="$PREFIX/lib/libz.a"
|
||||
|
||||
make -j$(nproc)
|
||||
make install
|
||||
|
||||
make clean
|
||||
cp -r $QT_PREFIX $LibrariesPath/qt-cache
|
||||
|
||||
- name: WebRTC cache.
|
||||
id: cache-webrtc
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/tg_owt
|
||||
key: ${{ runner.OS }}-webrtc-${{ env.CACHE_KEY }}-${{ hashFiles('**/tg_owt-version.json') }}
|
||||
- name: WebRTC.
|
||||
if: steps.cache-webrtc.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cd $LibrariesPath
|
||||
|
||||
git clone --recursive $GIT/desktop-app/tg_owt.git
|
||||
mkdir -p tg_owt/out/Debug
|
||||
cd tg_owt/out/Debug
|
||||
cmake -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DTG_OWT_SPECIAL_TARGET=mac \
|
||||
-DTG_OWT_BUILD_AUDIO_BACKENDS=OFF \
|
||||
-DTG_OWT_LIBJPEG_INCLUDE_PATH=$PREFIX/include \
|
||||
-DTG_OWT_OPENSSL_INCLUDE_PATH=`pwd`/../../../openssl_$OPENSSL_VER/include \
|
||||
-DTG_OWT_OPUS_INCLUDE_PATH=$PREFIX/include/opus \
|
||||
-DTG_OWT_FFMPEG_INCLUDE_PATH=$PREFIX/include \
|
||||
../..
|
||||
ninja
|
||||
|
||||
# Cleanup.
|
||||
cd $LibrariesPath/tg_owt
|
||||
mv out/Debug/libtg_owt.a libtg_owt.a
|
||||
rm -rf out
|
||||
mkdir -p out/Debug
|
||||
mv libtg_owt.a out/Debug/libtg_owt.a
|
||||
./$REPO_NAME/Telegram/build/prepare/mac.sh skip-release silent
|
||||
|
||||
- name: Telegram Desktop build.
|
||||
if: env.ONLY_CACHE == 'false'
|
||||
|
||||
84
.github/workflows/win.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
||||
UPLOAD_ARTIFACT: "false"
|
||||
ONLY_CACHE: "false"
|
||||
MANUAL_CACHING: "0"
|
||||
DOC_PATH: "docs/building-win.md"
|
||||
PREPARE_PATH: "Telegram/build/prepare/prepare.py"
|
||||
AUTO_CACHING: "1"
|
||||
|
||||
defaults:
|
||||
@@ -132,24 +132,12 @@ jobs:
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
echo "Find necessary commit from doc."
|
||||
checkoutCommit=$(grep -A 1 "cd patches" $REPO_NAME/$DOC_PATH | sed -n 2p)
|
||||
checkoutCommit=$(grep -A 1 "cd patches" $REPO_NAME/$PREPARE_PATH | sed -n 2p)
|
||||
cd $LibrariesPath
|
||||
git clone $GIT/desktop-app/patches.git
|
||||
cd Patches
|
||||
cd patches
|
||||
eval $checkoutCommit
|
||||
|
||||
- name: Find any version of Python 2.
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Find any version of Python 2."
|
||||
p=`ls /c/hostedtoolcache/windows/python | grep "^2" | tail -1`
|
||||
if [ -z "$p" ]; then
|
||||
echo "Python 2 is not found."
|
||||
exit 1
|
||||
fi
|
||||
echo "PY2=C:\\hostedtoolcache\\windows\\Python\\$p\\x64" >> $GITHUB_ENV
|
||||
echo "Found $p."
|
||||
|
||||
- name: LZMA.
|
||||
run: |
|
||||
git clone %GIT%/telegramdesktop/lzma.git
|
||||
@@ -161,28 +149,27 @@ jobs:
|
||||
id: cache-openssl
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/openssl_${{ env.OPENSSL_VER }}
|
||||
path: ${{ env.LibrariesPath }}/openssl
|
||||
key: ${{ runner.OS }}-${{ env.CACHE_KEY }}-${{ env.OPENSSL_VER }}
|
||||
- name: OpenSSL.
|
||||
if: steps.cache-openssl.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
git clone %GIT%/openssl/openssl.git openssl_%OPENSSL_VER%
|
||||
cd openssl_%OPENSSL_VER%
|
||||
git checkout OpenSSL_%OPENSSL_VER%-stable
|
||||
git clone -b OpenSSL_%OPENSSL_VER%-stable %GIT%/openssl/openssl.git
|
||||
cd openssl
|
||||
perl Configure no-shared no-tests debug-VC-WIN32
|
||||
nmake
|
||||
mkdir out32.dbg
|
||||
move libcrypto.lib out32.dbg
|
||||
move libssl.lib out32.dbg
|
||||
move ossl_static.pdb out32.dbg\ossl_static
|
||||
mkdir out.dbg
|
||||
move libcrypto.lib out.dbg
|
||||
move libssl.lib out.dbg
|
||||
move ossl_static.pdb out.dbg\ossl_static
|
||||
nmake clean
|
||||
move out32.dbg\ossl_static out32.dbg\ossl_static.pdb
|
||||
move out.dbg\ossl_static out.dbg\ossl_static.pdb
|
||||
perl Configure no-shared no-tests VC-WIN32
|
||||
nmake
|
||||
mkdir out32
|
||||
move libcrypto.lib out32
|
||||
move libssl.lib out32
|
||||
move ossl_static.pdb out32
|
||||
mkdir out
|
||||
move libcrypto.lib out
|
||||
move libssl.lib out
|
||||
move ossl_static.pdb out
|
||||
|
||||
rmdir /S /Q test
|
||||
rmdir /S /Q .git
|
||||
@@ -239,16 +226,17 @@ jobs:
|
||||
GYP_MSVS_VERSION: 2019
|
||||
if: steps.cache-breakpad.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
git clone %GIT%/telegramdesktop/gyp.git
|
||||
git clone https://chromium.googlesource.com/external/gyp
|
||||
cd gyp
|
||||
SET PATH=%PY2%;%cd%;%PATH%
|
||||
git checkout tdesktop
|
||||
SET PATH=%cd%;%PATH%
|
||||
git checkout d6c5dd51dc
|
||||
git apply ../patches/gyp.diff
|
||||
|
||||
cd %LibrariesPath%
|
||||
|
||||
git clone %GIT%/google/breakpad
|
||||
git clone https://chromium.googlesource.com/breakpad/breakpad
|
||||
cd breakpad
|
||||
git checkout a1dbcdcb43
|
||||
git checkout bc8fb886
|
||||
git apply ../patches/breakpad.diff
|
||||
cd src
|
||||
git clone %GIT%/google/googletest testing
|
||||
@@ -258,10 +246,9 @@ jobs:
|
||||
ninja -C out/Debug common crash_generation_client exception_handler
|
||||
ninja -C out/Release common crash_generation_client exception_handler
|
||||
cd tools\windows\dump_syms
|
||||
call gyp dump_syms.gyp
|
||||
|
||||
call vcvars32.bat
|
||||
msbuild -m dump_syms.vcxproj /property:Configuration=Release
|
||||
call gyp dump_syms.gyp --format=ninja
|
||||
cd ..\..\..
|
||||
ninja -C out/Release dump_syms
|
||||
|
||||
- name: Opus cache.
|
||||
id: cache-opus
|
||||
@@ -272,12 +259,17 @@ jobs:
|
||||
- name: Opus.
|
||||
if: steps.cache-opus.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
git clone %GIT%/telegramdesktop/opus.git
|
||||
git clone -b v1.3.1 %GIT%/xiph/opus.git
|
||||
cd opus
|
||||
git checkout tdesktop
|
||||
cd win32\VS2015
|
||||
msbuild -m opus.sln /property:Configuration=Debug /property:Platform="Win32"
|
||||
msbuild -m opus.sln /property:Configuration=Release /property:Platform="Win32"
|
||||
git cherry-pick 927de8453c
|
||||
cmake -B out . ^
|
||||
-A Win32 ^
|
||||
-DCMAKE_INSTALL_PREFIX=%LibrariesPath%/local/opus ^
|
||||
-DCMAKE_C_FLAGS_DEBUG="/MTd /Zi /Ob0 /Od /RTC1" ^
|
||||
-DCMAKE_C_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG"
|
||||
cmake --build out --config Debug
|
||||
cmake --build out --config Release
|
||||
cmake --install out --config Release
|
||||
|
||||
- name: Rnnoise.
|
||||
run: |
|
||||
@@ -352,7 +344,7 @@ jobs:
|
||||
for /r %%i in (..\..\patches\qtbase_%QT%\*) do git apply %%i
|
||||
cd ..
|
||||
|
||||
SET SSL=%LibrariesPath%\openssl_%OPENSSL_VER%
|
||||
SET SSL=%LibrariesPath%\openssl
|
||||
SET SSL_LIBS=libcrypto.lib Ws2_32.lib Gdi32.lib Advapi32.lib Crypt32.lib User32.lib
|
||||
|
||||
SET ANGLE=%LibrariesPath%\tg_angle
|
||||
@@ -380,8 +372,8 @@ jobs:
|
||||
QMAKE_LIBS_EGL_RELEASE="%ANGLE%\out\Release\tg_angle.lib %ZLIB%\ZlibStatReleaseWithoutAsm\zlibstat.lib %ANGLE_LIBS% Gdi32.lib User32.lib" ^
|
||||
-openssl-linked ^
|
||||
-I "%SSL%\include" ^
|
||||
OPENSSL_LIBS_DEBUG="%SSL%\out32.dbg\libssl.lib %SSL%\out32.dbg\%SSL_LIBS%" ^
|
||||
OPENSSL_LIBS_RELEASE="%SSL%\out32\libssl.lib %SSL%\out32\%SSL_LIBS%" ^
|
||||
OPENSSL_LIBS_DEBUG="%SSL%\out.dbg\libssl.lib %SSL%\out.dbg\%SSL_LIBS%" ^
|
||||
OPENSSL_LIBS_RELEASE="%SSL%\out\libssl.lib %SSL%\out\%SSL_LIBS%" ^
|
||||
-I "%LibrariesPath%\mozjpeg" ^
|
||||
LIBJPEG_LIBS_DEBUG="%LibrariesPath%\mozjpeg\Debug\jpeg-static.lib" ^
|
||||
LIBJPEG_LIBS_RELEASE="%LibrariesPath%\mozjpeg\Release\jpeg-static.lib" ^
|
||||
@@ -418,7 +410,7 @@ jobs:
|
||||
-DTG_OWT_SPECIAL_TARGET=win ^
|
||||
-DTG_OWT_BUILD_AUDIO_BACKENDS=OFF ^
|
||||
-DTG_OWT_LIBJPEG_INCLUDE_PATH=%cd%/../../../mozjpeg ^
|
||||
-DTG_OWT_OPENSSL_INCLUDE_PATH=%cd%/../../../openssl_%OPENSSL_VER%/include ^
|
||||
-DTG_OWT_OPENSSL_INCLUDE_PATH=%cd%/../../../openssl/include ^
|
||||
-DTG_OWT_OPUS_INCLUDE_PATH=%cd%/../../../opus/include ^
|
||||
-DTG_OWT_FFMPEG_INCLUDE_PATH=%cd%/../../../ffmpeg ^
|
||||
../..
|
||||
|
||||
@@ -43,6 +43,8 @@ include(cmake/generate_appdata_changelog.cmake)
|
||||
if (WIN32)
|
||||
include(cmake/generate_midl.cmake)
|
||||
generate_midl(Telegram ${src_loc}/platform/win/windows_quiethours.idl)
|
||||
|
||||
nuget_add_winrt(Telegram)
|
||||
endif()
|
||||
|
||||
set_target_properties(Telegram PROPERTIES AUTOMOC ON AUTORCC ON)
|
||||
@@ -52,6 +54,14 @@ PRIVATE
|
||||
tdesktop::lib_tgcalls_legacy
|
||||
tdesktop::lib_tgcalls
|
||||
tdesktop::lib_tgvoip
|
||||
|
||||
# Order in this list defines the order of include paths in command line.
|
||||
# We need to place desktop-app::external_minizip this early to have its
|
||||
# include paths (usually ${PREFIX}/include/minizip) before any depend that
|
||||
# would add ${PREFIX}/include. This path may have a different <zip.h>,
|
||||
# for example installed by libzip (https://libzip.org).
|
||||
desktop-app::external_minizip
|
||||
|
||||
tdesktop::td_export
|
||||
tdesktop::td_mtproto
|
||||
tdesktop::td_lang
|
||||
@@ -71,7 +81,6 @@ PRIVATE
|
||||
desktop-app::external_lz4
|
||||
desktop-app::external_rlottie
|
||||
desktop-app::external_zlib
|
||||
desktop-app::external_minizip
|
||||
desktop-app::external_qt_static_plugins
|
||||
desktop-app::external_qt
|
||||
desktop-app::external_qr_code_generator
|
||||
@@ -278,6 +287,8 @@ PRIVATE
|
||||
chat_helpers/bot_command.h
|
||||
chat_helpers/bot_keyboard.cpp
|
||||
chat_helpers/bot_keyboard.h
|
||||
chat_helpers/emoji_interactions.cpp
|
||||
chat_helpers/emoji_interactions.h
|
||||
chat_helpers/emoji_keywords.cpp
|
||||
chat_helpers/emoji_keywords.h
|
||||
chat_helpers/emoji_list_widget.cpp
|
||||
@@ -581,6 +592,8 @@ PRIVATE
|
||||
history/view/history_view_cursor_state.h
|
||||
history/view/history_view_element.cpp
|
||||
history/view/history_view_element.h
|
||||
history/view/history_view_emoji_interactions.cpp
|
||||
history/view/history_view_emoji_interactions.h
|
||||
history/view/history_view_empty_list_bubble.cpp
|
||||
history/view/history_view_empty_list_bubble.h
|
||||
history/view/history_view_group_call_tracker.cpp
|
||||
@@ -1027,6 +1040,8 @@ PRIVATE
|
||||
ui/chat/attach/attach_item_single_file_preview.h
|
||||
ui/chat/attach/attach_item_single_media_preview.cpp
|
||||
ui/chat/attach/attach_item_single_media_preview.h
|
||||
ui/chat/choose_theme_controller.cpp
|
||||
ui/chat/choose_theme_controller.h
|
||||
ui/effects/fireworks_animation.cpp
|
||||
ui/effects/fireworks_animation.h
|
||||
ui/effects/round_checkbox.cpp
|
||||
@@ -1245,17 +1260,13 @@ elseif (APPLE)
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
desktop-app::external_glibmm
|
||||
desktop-app::external_glib
|
||||
)
|
||||
|
||||
if (NOT DESKTOP_APP_DISABLE_DBUS_INTEGRATION)
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
desktop-app::external_statusnotifieritem
|
||||
desktop-app::external_dbusmenu_qt
|
||||
desktop-app::external_glibmm
|
||||
desktop-app::external_glib
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -1359,6 +1370,7 @@ if (WIN32)
|
||||
/DELAYLOAD:gdiplus.dll
|
||||
/DELAYLOAD:version.dll
|
||||
/DELAYLOAD:dwmapi.dll
|
||||
/DELAYLOAD:uxtheme.dll
|
||||
/DELAYLOAD:crypt32.dll
|
||||
/DELAYLOAD:bcrypt.dll
|
||||
/DELAYLOAD:imm32.dll
|
||||
|
||||
|
Before Width: | Height: | Size: 117 KiB |
BIN
Telegram/Resources/art/themeimage.jpg
Normal file
|
After Width: | Height: | Size: 114 KiB |
BIN
Telegram/Resources/day-custom-base.tdesktop-theme
Normal file
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 862 KiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 594 KiB |
|
Before Width: | Height: | Size: 321 B After Width: | Height: | Size: 453 B |
|
Before Width: | Height: | Size: 637 B After Width: | Height: | Size: 973 B |
|
Before Width: | Height: | Size: 717 B After Width: | Height: | Size: 946 B |
|
Before Width: | Height: | Size: 651 B After Width: | Height: | Size: 710 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.4 KiB |
@@ -357,6 +357,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_update_fail" = "Update check failed :(";
|
||||
"lng_settings_workmode_tray" = "Show tray icon";
|
||||
"lng_settings_workmode_window" = "Show taskbar icon";
|
||||
"lng_settings_close_to_taskbar" = "Close to taskbar";
|
||||
"lng_settings_native_frame" = "Use system window frame";
|
||||
"lng_settings_auto_start" = "Launch Telegram when system starts";
|
||||
"lng_settings_start_min" = "Launch minimized";
|
||||
@@ -1617,6 +1618,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_user_action_upload_file" = "{user} is sending a file";
|
||||
"lng_send_action_choose_sticker" = "choosing a sticker";
|
||||
"lng_user_action_choose_sticker" = "{user} is choosing a sticker";
|
||||
"lng_user_action_watching_animations" = "watching {emoji}";
|
||||
"lng_unread_bar#one" = "{count} unread message";
|
||||
"lng_unread_bar#other" = "{count} unread messages";
|
||||
"lng_unread_bar_some" = "Unread messages";
|
||||
@@ -2237,7 +2239,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_call_recording_start_checkbox" = "Also record video";
|
||||
"lng_group_call_recording_start_audio_subtitle" = "This chat will be recorded into an audio file";
|
||||
"lng_group_call_recording_start_video_subtitle" = "Choose video orientation";
|
||||
"lng_group_call_is_recorded" = "Voice chat is being recorded.";
|
||||
"lng_group_call_is_recorded" = "The audio stream is being recorded.";
|
||||
"lng_group_call_is_recorded_video" = "The video stream is being recorded.";
|
||||
"lng_group_call_is_recorded_channel" = "Live stream is being recorded.";
|
||||
"lng_group_call_can_speak_here" = "You can now speak.";
|
||||
"lng_group_call_can_speak" = "You can now speak in {chat}.";
|
||||
@@ -2868,6 +2871,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_filters_remove_sure" = "This will remove the folder, your chats will not be deleted.";
|
||||
"lng_filters_remove_yes" = "Remove";
|
||||
|
||||
"lng_chat_theme_change" = "Change colors";
|
||||
"lng_chat_theme_none" = "No\nTheme";
|
||||
"lng_chat_theme_apply" = "Apply Theme";
|
||||
"lng_chat_theme_reset" = "Reset Theme";
|
||||
"lng_chat_theme_dont" = "Do Not Set Theme";
|
||||
"lng_chat_theme_title" = "Select theme";
|
||||
"lng_chat_theme_cant_voice" = "Sorry, you can't change the chat theme while you're having an unsent voice message.";
|
||||
|
||||
"lng_photo_editor_menu_delete" = "Delete";
|
||||
"lng_photo_editor_menu_flip" = "Flip";
|
||||
"lng_photo_editor_menu_duplicate" = "Duplicate";
|
||||
|
||||
BIN
Telegram/Resources/night-custom-base.tdesktop-theme
Normal file
@@ -47,7 +47,7 @@
|
||||
<file alias="art/bg_initial.jpg">../../art/bg_initial.jpg</file>
|
||||
<file alias="art/logo_256.png">../../art/logo_256.png</file>
|
||||
<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/themeimage.jpg">../../art/themeimage.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="art/bball_idle.tgs">../../art/bball_idle.tgs</file>
|
||||
@@ -60,6 +60,8 @@
|
||||
<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>
|
||||
<file alias="day-custom-base.tdesktop-theme">../../day-custom-base.tdesktop-theme</file>
|
||||
<file alias="night-custom-base.tdesktop-theme">../../night-custom-base.tdesktop-theme</file>
|
||||
<file alias="icons/calls/hands.lottie">../../icons/calls/hands.lottie</file>
|
||||
<file alias="icons/calls/voice.lottie">../../icons/calls/voice.lottie</file>
|
||||
<file alias="recording/info_audio.svg">../../art/recording/recording_info_audio.svg</file>
|
||||
|
||||
@@ -467,7 +467,7 @@ sendMessageUploadRoundAction#243e1c66 progress:int = SendMessageAction;
|
||||
speakingInGroupCallAction#d92c2285 = SendMessageAction;
|
||||
sendMessageHistoryImportAction#dbda9246 progress:int = SendMessageAction;
|
||||
sendMessageChooseStickerAction#b05ac6b1 = SendMessageAction;
|
||||
sendMessageEmojiInteraction#6a3233b6 emoticon:string interaction:DataJSON = SendMessageAction;
|
||||
sendMessageEmojiInteraction#25972bcb emoticon:string msg_id:int interaction:DataJSON = SendMessageAction;
|
||||
sendMessageEmojiInteractionSeen#b665902e emoticon:string = SendMessageAction;
|
||||
|
||||
contacts.found#b3134d9d my_results:Vector<Peer> results:Vector<Peer> chats:Vector<Chat> users:Vector<User> = contacts.Found;
|
||||
@@ -560,6 +560,7 @@ inputStickerSetID#9de7a269 id:long access_hash:long = InputStickerSet;
|
||||
inputStickerSetShortName#861cc8a0 short_name:string = InputStickerSet;
|
||||
inputStickerSetAnimatedEmoji#28703c8 = InputStickerSet;
|
||||
inputStickerSetDice#e67f520e emoticon:string = InputStickerSet;
|
||||
inputStickerSetAnimatedEmojiAnimations#cde3739 = InputStickerSet;
|
||||
|
||||
stickerSet#d7df217a 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 thumbs:flags.4?Vector<PhotoSize> thumb_dc_id:flags.4?int thumb_version:flags.4?int count:int hash:int = StickerSet;
|
||||
|
||||
@@ -907,7 +908,6 @@ channelAdminLogEventActionExportedInviteRevoke#410a134e invite:ExportedChatInvit
|
||||
channelAdminLogEventActionExportedInviteEdit#e90ebb59 prev_invite:ExportedChatInvite new_invite:ExportedChatInvite = ChannelAdminLogEventAction;
|
||||
channelAdminLogEventActionParticipantVolume#3e7f6847 participant:GroupCallParticipant = ChannelAdminLogEventAction;
|
||||
channelAdminLogEventActionChangeHistoryTTL#6e941a38 prev_value:int new_value:int = ChannelAdminLogEventAction;
|
||||
channelAdminLogEventActionChangeTheme#fe69018d prev_value:string new_value:string = ChannelAdminLogEventAction;
|
||||
|
||||
channelAdminLogEvent#1fad68cd id:long date:int user_id:long action:ChannelAdminLogEventAction = ChannelAdminLogEvent;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="3.0.2.0" />
|
||||
Version="3.1.5.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 3,0,2,0
|
||||
PRODUCTVERSION 3,0,2,0
|
||||
FILEVERSION 3,1,5,0
|
||||
PRODUCTVERSION 3,1,5,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "3.0.2.0"
|
||||
VALUE "FileVersion", "3.1.5.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "3.0.2.0"
|
||||
VALUE "ProductVersion", "3.1.5.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 3,0,2,0
|
||||
PRODUCTVERSION 3,0,2,0
|
||||
FILEVERSION 3,1,5,0
|
||||
PRODUCTVERSION 3,1,5,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "3.0.2.0"
|
||||
VALUE "FileVersion", "3.1.5.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "3.0.2.0"
|
||||
VALUE "ProductVersion", "3.1.5.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "api/api_cloud_password.h"
|
||||
|
||||
#include "base/openssl_help.h"
|
||||
#include "base/random.h"
|
||||
#include "core/core_cloud_password.h"
|
||||
#include "apiwrap.h"
|
||||
|
||||
@@ -27,7 +27,7 @@ void CloudPassword::reload() {
|
||||
)).done([=](const MTPaccount_Password &result) {
|
||||
_requestId = 0;
|
||||
result.match([&](const MTPDaccount_password &data) {
|
||||
openssl::AddRandomSeed(bytes::make_span(data.vsecure_random().v));
|
||||
base::RandomAddSeed(bytes::make_span(data.vsecure_random().v));
|
||||
if (_state) {
|
||||
*_state = Core::ParseCloudPasswordState(data);
|
||||
} else {
|
||||
|
||||
@@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_sending.h"
|
||||
|
||||
#include "api/api_text_entities.h"
|
||||
#include "base/openssl_help.h"
|
||||
#include "base/random.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_photo.h"
|
||||
@@ -76,7 +76,7 @@ void SendExistingMedia(
|
||||
const auto newId = FullMsgId(
|
||||
peerToChannel(peer->id),
|
||||
session->data().nextLocalMessageId());
|
||||
const auto randomId = openssl::RandomValue<uint64>();
|
||||
const auto randomId = base::RandomValue<uint64>();
|
||||
|
||||
auto flags = NewMessageFlags(peer);
|
||||
auto sendFlags = MTPmessages_SendMedia::Flags(0);
|
||||
@@ -248,7 +248,7 @@ bool SendDice(Api::MessageToSend &message) {
|
||||
const auto newId = FullMsgId(
|
||||
peerToChannel(peer->id),
|
||||
session->data().nextLocalMessageId());
|
||||
const auto randomId = openssl::RandomValue<uint64>();
|
||||
const auto randomId = base::RandomValue<uint64>();
|
||||
|
||||
auto &histories = history->owner().histories();
|
||||
auto flags = NewMessageFlags(peer);
|
||||
@@ -397,6 +397,11 @@ void SendConfirmedFile(
|
||||
} else {
|
||||
flags |= MessageFlag::LocalHistoryEntry;
|
||||
}
|
||||
if (file->type == SendMediaType::Audio) {
|
||||
if (!peer->isChannel() || peer->isMegagroup()) {
|
||||
flags |= MessageFlag::MediaIsUnread;
|
||||
}
|
||||
}
|
||||
|
||||
const auto messageFromId = anonymousPost ? 0 : session->userPeerId();
|
||||
const auto messagePostAuthor = peer->isBroadcast()
|
||||
|
||||
@@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_folder.h"
|
||||
#include "data/data_scheduled_messages.h"
|
||||
#include "data/data_send_action.h"
|
||||
#include "chat_helpers/emoji_interactions.h"
|
||||
#include "lang/lang_cloud_manager.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
@@ -984,36 +985,18 @@ void Updates::handleSendActionUpdate(
|
||||
const auto from = (fromId == session().userPeerId())
|
||||
? session().user().get()
|
||||
: session().data().peerLoaded(fromId);
|
||||
const auto isSpeakingInCall = (action.type()
|
||||
== mtpc_speakingInGroupCallAction);
|
||||
if (isSpeakingInCall) {
|
||||
if (!peer->isChat() && !peer->isChannel()) {
|
||||
return;
|
||||
}
|
||||
const auto call = peer->groupCall();
|
||||
const auto now = crl::now();
|
||||
if (call) {
|
||||
call->applyActiveUpdate(
|
||||
fromId,
|
||||
Data::LastSpokeTimes{ .anything = now, .voice = now },
|
||||
from);
|
||||
} else {
|
||||
const auto chat = peer->asChat();
|
||||
const auto channel = peer->asChannel();
|
||||
const auto active = chat
|
||||
? (chat->flags() & ChatDataFlag::CallActive)
|
||||
: (channel->flags() & ChannelDataFlag::CallActive);
|
||||
if (active) {
|
||||
_pendingSpeakingCallParticipants.emplace(
|
||||
peer).first->second[fromId] = now;
|
||||
if (peerIsUser(fromId)) {
|
||||
session().api().requestFullPeer(peer);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (action.type() == mtpc_speakingInGroupCallAction) {
|
||||
handleSpeakingInCall(peer, fromId, from);
|
||||
}
|
||||
if (!from || !from->isUser() || from->isSelf()) {
|
||||
return;
|
||||
} else if (action.type() == mtpc_sendMessageEmojiInteraction) {
|
||||
handleEmojiInteraction(peer, action.c_sendMessageEmojiInteraction());
|
||||
return;
|
||||
} else if (action.type() == mtpc_sendMessageEmojiInteractionSeen) {
|
||||
const auto &data = action.c_sendMessageEmojiInteractionSeen();
|
||||
handleEmojiInteraction(peer, qs(data.vemoticon()));
|
||||
return;
|
||||
}
|
||||
const auto when = requestingDifference()
|
||||
? 0
|
||||
@@ -1026,6 +1009,76 @@ void Updates::handleSendActionUpdate(
|
||||
when);
|
||||
}
|
||||
|
||||
void Updates::handleEmojiInteraction(
|
||||
not_null<PeerData*> peer,
|
||||
const MTPDsendMessageEmojiInteraction &data) {
|
||||
const auto json = data.vinteraction().match([&](
|
||||
const MTPDdataJSON &data) {
|
||||
return data.vdata().v;
|
||||
});
|
||||
handleEmojiInteraction(
|
||||
peer,
|
||||
data.vmsg_id().v,
|
||||
qs(data.vemoticon()),
|
||||
ChatHelpers::EmojiInteractions::Parse(json));
|
||||
}
|
||||
|
||||
void Updates::handleSpeakingInCall(
|
||||
not_null<PeerData*> peer,
|
||||
PeerId participantPeerId,
|
||||
PeerData *participantPeerLoaded) {
|
||||
if (!peer->isChat() && !peer->isChannel()) {
|
||||
return;
|
||||
}
|
||||
const auto call = peer->groupCall();
|
||||
const auto now = crl::now();
|
||||
if (call) {
|
||||
call->applyActiveUpdate(
|
||||
participantPeerId,
|
||||
Data::LastSpokeTimes{ .anything = now, .voice = now },
|
||||
participantPeerLoaded);
|
||||
} else {
|
||||
const auto chat = peer->asChat();
|
||||
const auto channel = peer->asChannel();
|
||||
const auto active = chat
|
||||
? (chat->flags() & ChatDataFlag::CallActive)
|
||||
: (channel->flags() & ChannelDataFlag::CallActive);
|
||||
if (active) {
|
||||
_pendingSpeakingCallParticipants.emplace(
|
||||
peer).first->second[participantPeerId] = now;
|
||||
if (peerIsUser(participantPeerId)) {
|
||||
session().api().requestFullPeer(peer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Updates::handleEmojiInteraction(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId messageId,
|
||||
const QString &emoticon,
|
||||
ChatHelpers::EmojiInteractionsBunch bunch) {
|
||||
if (session().windows().empty()) {
|
||||
return;
|
||||
}
|
||||
const auto window = session().windows().front();
|
||||
window->emojiInteractions().startIncoming(
|
||||
peer,
|
||||
messageId,
|
||||
emoticon,
|
||||
std::move(bunch));
|
||||
}
|
||||
|
||||
void Updates::handleEmojiInteraction(
|
||||
not_null<PeerData*> peer,
|
||||
const QString &emoticon) {
|
||||
if (session().windows().empty()) {
|
||||
return;
|
||||
}
|
||||
const auto window = session().windows().front();
|
||||
window->emojiInteractions().seenOutgoing(peer, emoticon);
|
||||
}
|
||||
|
||||
void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
|
||||
switch (updates.type()) {
|
||||
case mtpc_updateShortMessage: {
|
||||
|
||||
@@ -21,6 +21,10 @@ namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace ChatHelpers {
|
||||
struct EmojiInteractionsBunch;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace Api {
|
||||
|
||||
class Updates final {
|
||||
@@ -139,6 +143,21 @@ private:
|
||||
MsgId rootId,
|
||||
PeerId fromId,
|
||||
const MTPSendMessageAction &action);
|
||||
void handleEmojiInteraction(
|
||||
not_null<PeerData*> peer,
|
||||
const MTPDsendMessageEmojiInteraction &data);
|
||||
void handleSpeakingInCall(
|
||||
not_null<PeerData*> peer,
|
||||
PeerId participantPeerId,
|
||||
PeerData *participantPeerLoaded);
|
||||
void handleEmojiInteraction(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId messageId,
|
||||
const QString &emoticon,
|
||||
ChatHelpers::EmojiInteractionsBunch bunch);
|
||||
void handleEmojiInteraction(
|
||||
not_null<PeerData*> peer,
|
||||
const QString &emoticon);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
|
||||
@@ -130,7 +130,6 @@ struct State {
|
||||
not_null<HistoryItem*> item,
|
||||
not_null<QWidget*> context) {
|
||||
auto weak = QPointer<QWidget>(context.get());
|
||||
const auto fullId = item->fullId();
|
||||
const auto session = &item->history()->session();
|
||||
return [=](auto consumer) {
|
||||
if (!weak) {
|
||||
@@ -203,7 +202,7 @@ bool UpdateUserpics(
|
||||
}
|
||||
auto &was = state->userpics;
|
||||
auto now = std::vector<Userpic>();
|
||||
for (const auto peer : peers) {
|
||||
for (const auto &peer : peers) {
|
||||
if (ranges::contains(now, peer, &Userpic::peer)) {
|
||||
continue;
|
||||
}
|
||||
@@ -356,6 +355,8 @@ rpl::producer<Ui::WhoReadContent> WhoRead(
|
||||
} else if (UpdateUserpics(state, item, peers)) {
|
||||
RegenerateParticipants(state, small, large);
|
||||
pushNext();
|
||||
} else if (peers.empty()) {
|
||||
pushNext();
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/core_cloud_password.h"
|
||||
#include "core/application.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "base/random.h"
|
||||
#include "base/qt_adapters.h"
|
||||
#include "base/call_delayed.h"
|
||||
#include "lang/lang_keys.h"
|
||||
@@ -3652,8 +3653,9 @@ void ApiWrap::forwardMessages(
|
||||
const auto history = action.history;
|
||||
const auto peer = history->peer;
|
||||
|
||||
histories.readInbox(history);
|
||||
|
||||
if (!action.options.scheduled) {
|
||||
histories.readInbox(history);
|
||||
}
|
||||
const auto anonymousPost = peer->amAnonymous();
|
||||
const auto silentPost = ShouldSendSilent(peer, action.options);
|
||||
|
||||
@@ -3724,7 +3726,7 @@ void ApiWrap::forwardMessages(
|
||||
ids.reserve(count);
|
||||
randomIds.reserve(count);
|
||||
for (const auto item : draft.items) {
|
||||
const auto randomId = openssl::RandomValue<uint64>();
|
||||
const auto randomId = base::RandomValue<uint64>();
|
||||
if (genClientSideMessage) {
|
||||
if (const auto message = item->toHistoryMessage()) {
|
||||
const auto newId = FullMsgId(
|
||||
@@ -4034,7 +4036,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
auto newId = FullMsgId(
|
||||
peerToChannel(peer->id),
|
||||
_session->data().nextLocalMessageId());
|
||||
auto randomId = openssl::RandomValue<uint64>();
|
||||
auto randomId = base::RandomValue<uint64>();
|
||||
|
||||
TextUtilities::Trim(sending);
|
||||
|
||||
@@ -4158,7 +4160,7 @@ void ApiWrap::sendBotStart(not_null<UserData*> bot, PeerData *chat) {
|
||||
sendMessage(std::move(message));
|
||||
return;
|
||||
}
|
||||
const auto randomId = openssl::RandomValue<uint64>();
|
||||
const auto randomId = base::RandomValue<uint64>();
|
||||
request(MTPmessages_StartBot(
|
||||
bot->inputUser,
|
||||
chat ? chat->input : MTP_inputPeerEmpty(),
|
||||
@@ -4184,7 +4186,7 @@ void ApiWrap::sendInlineResult(
|
||||
const auto newId = FullMsgId(
|
||||
peerToChannel(peer->id),
|
||||
_session->data().nextLocalMessageId());
|
||||
const auto randomId = openssl::RandomValue<uint64>();
|
||||
const auto randomId = base::RandomValue<uint64>();
|
||||
|
||||
auto flags = NewMessageFlags(peer);
|
||||
auto sendFlags = MTPmessages_SendInlineBotResult::Flag::f_clear_draft | 0;
|
||||
@@ -4340,7 +4342,7 @@ void ApiWrap::sendMedia(
|
||||
not_null<HistoryItem*> item,
|
||||
const MTPInputMedia &media,
|
||||
Api::SendOptions options) {
|
||||
const auto randomId = openssl::RandomValue<uint64>();
|
||||
const auto randomId = base::RandomValue<uint64>();
|
||||
_session->data().registerMessageRandomId(randomId, item->fullId());
|
||||
|
||||
sendMediaWithRandomId(item, media, options, randomId);
|
||||
@@ -4414,7 +4416,7 @@ void ApiWrap::sendAlbumWithUploaded(
|
||||
const MessageGroupId &groupId,
|
||||
const MTPInputMedia &media) {
|
||||
const auto localId = item->fullId();
|
||||
const auto randomId = openssl::RandomValue<uint64>();
|
||||
const auto randomId = base::RandomValue<uint64>();
|
||||
_session->data().registerMessageRandomId(randomId, localId);
|
||||
|
||||
const auto albumIt = _sendingAlbums.find(groupId.raw());
|
||||
@@ -4768,7 +4770,7 @@ void ApiWrap::createPoll(
|
||||
MTP_int(replyTo),
|
||||
PollDataToInputMedia(&data),
|
||||
MTP_string(),
|
||||
MTP_long(openssl::RandomValue<uint64>()),
|
||||
MTP_long(base::RandomValue<uint64>()),
|
||||
MTPReplyMarkup(),
|
||||
MTPVector<MTPMessageEntity>(),
|
||||
MTP_int(action.options.scheduled)
|
||||
|
||||
@@ -10,7 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lang/lang_keys.h"
|
||||
#include "mtproto/sender.h"
|
||||
#include "base/flat_set.h"
|
||||
#include "base/openssl_help.h"
|
||||
#include "base/random.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "boxes/peers/add_participants_box.h"
|
||||
@@ -383,7 +383,7 @@ void AddContactBox::save() {
|
||||
lastName = QString();
|
||||
}
|
||||
_sentName = firstName;
|
||||
_contactId = openssl::RandomValue<uint64>();
|
||||
_contactId = base::RandomValue<uint64>();
|
||||
_addRequest = _session->api().request(MTPcontacts_ImportContacts(
|
||||
MTP_vector<MTPInputContact>(
|
||||
1,
|
||||
|
||||
@@ -210,7 +210,7 @@ void ServiceCheck::Generator::paintFrame(
|
||||
const auto frames = framesForStyle(st);
|
||||
auto &image = frames->image;
|
||||
const auto count = int(frames->ready.size());
|
||||
const auto index = int(std::round(toggled * (count - 1)));
|
||||
const auto index = int(base::SafeRound(toggled * (count - 1)));
|
||||
Assert(index >= 0 && index < count);
|
||||
if (!frames->ready[index]) {
|
||||
frames->ready[index] = true;
|
||||
|
||||
@@ -30,7 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/unique_qptr.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "base/call_delayed.h"
|
||||
#include "base/openssl_help.h"
|
||||
#include "base/random.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
@@ -885,7 +885,7 @@ not_null<Ui::InputField*> CreatePollBox::setupSolution(
|
||||
object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
|
||||
using namespace Settings;
|
||||
|
||||
const auto id = openssl::RandomValue<uint64>();
|
||||
const auto id = base::RandomValue<uint64>();
|
||||
const auto error = lifetime().make_state<Errors>(Error::Question);
|
||||
|
||||
auto result = object_ptr<Ui::VerticalLayout>(this);
|
||||
|
||||
@@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
|
||||
#include "base/openssl_help.h"
|
||||
#include "base/random.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/ui_utility.h"
|
||||
@@ -36,7 +36,7 @@ void ShareBotGame(not_null<UserData*> bot, not_null<PeerData*> chat) {
|
||||
auto &histories = history->owner().histories();
|
||||
const auto requestType = Data::Histories::RequestType::Send;
|
||||
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
||||
const auto randomId = openssl::RandomValue<uint64>();
|
||||
const auto randomId = base::RandomValue<uint64>();
|
||||
const auto api = &chat->session().api();
|
||||
history->sendRequestId = api->request(MTPmessages_SendMedia(
|
||||
MTP_flags(0),
|
||||
|
||||
@@ -268,7 +268,8 @@ void Row::update(const InviteLinkData &data, TimeId now) {
|
||||
|
||||
void Row::updateExpireProgress(TimeId now) {
|
||||
const auto updated = ComputeProgress(_data, now);
|
||||
if (std::round(_progressTillExpire * 360) != std::round(updated * 360)) {
|
||||
if (base::SafeRound(_progressTillExpire * 360)
|
||||
!= base::SafeRound(updated * 360)) {
|
||||
_progressTillExpire = updated;
|
||||
const auto color = ComputeColor(_data, _progressTillExpire);
|
||||
if (_color != color) {
|
||||
@@ -291,7 +292,8 @@ crl::time Row::updateExpireIn() const {
|
||||
if (_data.expireDate <= start) {
|
||||
return 0;
|
||||
}
|
||||
return std::round((_data.expireDate - start) * crl::time(1000) / 720.);
|
||||
return base::SafeRound(
|
||||
(_data.expireDate - start) * crl::time(1000) / 720.);
|
||||
}
|
||||
|
||||
QString Row::generateName() {
|
||||
|
||||
@@ -549,9 +549,6 @@ void SendFilesBox::pushBlock(int from, int till) {
|
||||
block.takeWidget(),
|
||||
QMargins(0, _inner->count() ? st::sendMediaRowSkip : 0, 0, 0));
|
||||
|
||||
const auto preventDelete =
|
||||
widget->lifetime().make_state<rpl::event_stream<int>>();
|
||||
|
||||
block.itemDeleteRequest(
|
||||
) | rpl::filter([=] {
|
||||
return !_removingIndex;
|
||||
@@ -562,9 +559,9 @@ void SendFilesBox::pushBlock(int from, int till) {
|
||||
if (index < 0 || index >= _list.files.size()) {
|
||||
return;
|
||||
}
|
||||
// Prevent item delete if it is the only one.
|
||||
// Just close the box if it is the only one.
|
||||
if (_list.files.size() == 1) {
|
||||
preventDelete->fire_copy(0);
|
||||
closeBox();
|
||||
return;
|
||||
}
|
||||
_list.files.erase(_list.files.begin() + index);
|
||||
@@ -572,9 +569,7 @@ void SendFilesBox::pushBlock(int from, int till) {
|
||||
});
|
||||
}, widget->lifetime());
|
||||
|
||||
rpl::merge(
|
||||
block.itemReplaceRequest(),
|
||||
preventDelete->events()
|
||||
block.itemReplaceRequest(
|
||||
) | rpl::start_with_next([=](int index) {
|
||||
const auto replace = [=](Ui::PreparedList list) {
|
||||
if (list.files.empty()) {
|
||||
|
||||
@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/rate_call_box.h"
|
||||
#include "calls/calls_instance.h"
|
||||
#include "base/openssl_help.h"
|
||||
#include "base/random.h"
|
||||
#include "mtproto/mtproto_dh_utils.h"
|
||||
#include "mtproto/mtproto_config.h"
|
||||
#include "core/application.h"
|
||||
@@ -233,7 +234,7 @@ void Call::startOutgoing() {
|
||||
_api.request(MTPphone_RequestCall(
|
||||
MTP_flags(flags),
|
||||
_user->inputUser,
|
||||
MTP_int(openssl::RandomValue<int32>()),
|
||||
MTP_int(base::RandomValue<int32>()),
|
||||
MTP_bytes(_gaHash),
|
||||
MTP_phoneCallProtocol(
|
||||
MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p
|
||||
|
||||
@@ -209,7 +209,7 @@ void VideoBubble::updateSizeToFrame(QSize frame) {
|
||||
size = frame.scaled((_min + _max) / 2, Qt::KeepAspectRatio);
|
||||
} else {
|
||||
const auto area = size.width() * size.height();
|
||||
const auto w = int(std::round(std::max(
|
||||
const auto w = int(base::SafeRound(std::max(
|
||||
std::sqrt((frame.width() * float64(area)) / (frame.height() * 1.)),
|
||||
1.)));
|
||||
const auto h = area / w;
|
||||
|
||||
@@ -27,7 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_peer_values.h"
|
||||
#include "data/data_session.h"
|
||||
#include "base/global_shortcuts.h"
|
||||
#include "base/openssl_help.h"
|
||||
#include "base/random.h"
|
||||
#include "webrtc/webrtc_video_track.h"
|
||||
#include "webrtc/webrtc_media_devices.h"
|
||||
#include "webrtc/webrtc_create_adm.h"
|
||||
@@ -74,6 +74,11 @@ constexpr auto kMaxMediumQualities = 16; // 4 Fulls or 16 Mediums.
|
||||
return msgId / double(1ULL << 32);
|
||||
}
|
||||
|
||||
[[nodiscard]] int64 TimestampInMsFromMsgId(mtpMsgId msgId) {
|
||||
// return (msgId * 1000) / (1ULL << 32); // Almost... But this overflows.
|
||||
return ((msgId / (1ULL << 10)) * 1000) / (1ULL << 22);
|
||||
}
|
||||
|
||||
[[nodiscard]] uint64 FindLocalRaisedHandRating(
|
||||
const std::vector<Data::GroupCallParticipant> &list) {
|
||||
const auto i = ranges::max_element(
|
||||
@@ -95,6 +100,41 @@ using JoinClientFields = std::variant<
|
||||
JoinVideoEndpoint,
|
||||
JoinBroadcastStream>;
|
||||
|
||||
class RequestCurrentTimeTask final : public tgcalls::BroadcastPartTask {
|
||||
public:
|
||||
RequestCurrentTimeTask(
|
||||
base::weak_ptr<GroupCall> call,
|
||||
Fn<void(int64)> done);
|
||||
|
||||
void done(int64 value);
|
||||
void cancel() override;
|
||||
|
||||
private:
|
||||
const base::weak_ptr<GroupCall> _call;
|
||||
Fn<void(int64)> _done;
|
||||
QMutex _mutex;
|
||||
|
||||
};
|
||||
|
||||
RequestCurrentTimeTask::RequestCurrentTimeTask(
|
||||
base::weak_ptr<GroupCall> call,
|
||||
Fn<void(int64)> done)
|
||||
: _call(call)
|
||||
, _done(std::move(done)) {
|
||||
}
|
||||
|
||||
void RequestCurrentTimeTask::done(int64 value) {
|
||||
QMutexLocker lock(&_mutex);
|
||||
if (_done) {
|
||||
base::take(_done)(value);
|
||||
}
|
||||
}
|
||||
|
||||
void RequestCurrentTimeTask::cancel() {
|
||||
QMutexLocker lock(&_mutex);
|
||||
_done = nullptr;
|
||||
}
|
||||
|
||||
[[nodiscard]] JoinClientFields ParseJoinResponse(const QByteArray &json) {
|
||||
auto error = QJsonParseError{ 0, QJsonParseError::NoError };
|
||||
const auto document = QJsonDocument::fromJson(json, &error);
|
||||
@@ -968,7 +1008,7 @@ void GroupCall::start(TimeId scheduleDate) {
|
||||
_createRequestId = _api.request(MTPphone_CreateGroupCall(
|
||||
MTP_flags(scheduleDate ? Flag::f_schedule_date : Flag(0)),
|
||||
_peer->input,
|
||||
MTP_int(openssl::RandomValue<int32>()),
|
||||
MTP_int(base::RandomValue<int32>()),
|
||||
MTPstring(), // title
|
||||
MTP_int(scheduleDate)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
@@ -1273,7 +1313,12 @@ void GroupCall::rejoin(not_null<PeerData*> as) {
|
||||
joinAs()->input,
|
||||
MTP_string(_joinHash),
|
||||
MTP_dataJSON(MTP_bytes(json))
|
||||
)).done([=](const MTPUpdates &updates) {
|
||||
)).done([=](
|
||||
const MTPUpdates &updates,
|
||||
const MTP::Response &response) {
|
||||
_serverTimeMs = TimestampInMsFromMsgId(response.outerMsgId);
|
||||
_serverTimeMsGotAt = crl::now();
|
||||
|
||||
_joinState.finish(ssrc);
|
||||
_mySsrcs.emplace(ssrc);
|
||||
|
||||
@@ -2253,6 +2298,16 @@ bool GroupCall::tryCreateController() {
|
||||
.createAudioDeviceModule = Webrtc::AudioDeviceModuleCreator(
|
||||
settings.callAudioBackend()),
|
||||
.videoCapture = _cameraCapture,
|
||||
.requestCurrentTime = [=, call = base::make_weak(this)](
|
||||
std::function<void(int64_t)> done) {
|
||||
auto result = std::make_shared<RequestCurrentTimeTask>(
|
||||
call,
|
||||
std::move(done));
|
||||
crl::on_main(weak, [=] {
|
||||
result->done(approximateServerTimeInMs());
|
||||
});
|
||||
return result;
|
||||
},
|
||||
.requestAudioBroadcastPart = [=, call = base::make_weak(this)](
|
||||
int64_t time,
|
||||
int64_t period,
|
||||
@@ -2499,6 +2554,12 @@ void GroupCall::mediaChannelDescriptionsCancel(
|
||||
}
|
||||
}
|
||||
|
||||
int64 GroupCall::approximateServerTimeInMs() const {
|
||||
Expects(_serverTimeMs != 0);
|
||||
|
||||
return _serverTimeMs + (crl::now() - _serverTimeMsGotAt);
|
||||
}
|
||||
|
||||
void GroupCall::updateRequestedVideoChannels() {
|
||||
_requestedVideoChannelsUpdateScheduled = false;
|
||||
const auto real = lookupReal();
|
||||
|
||||
@@ -406,16 +406,6 @@ public:
|
||||
private:
|
||||
class LoadPartTask;
|
||||
class MediaChannelDescriptionsTask;
|
||||
|
||||
public:
|
||||
void broadcastPartStart(std::shared_ptr<LoadPartTask> task);
|
||||
void broadcastPartCancel(not_null<LoadPartTask*> task);
|
||||
void mediaChannelDescriptionsStart(
|
||||
std::shared_ptr<MediaChannelDescriptionsTask> task);
|
||||
void mediaChannelDescriptionsCancel(
|
||||
not_null<MediaChannelDescriptionsTask*> task);
|
||||
|
||||
private:
|
||||
using GlobalShortcutValue = base::GlobalShortcutValue;
|
||||
using Error = Group::Error;
|
||||
struct SinkPointer;
|
||||
@@ -464,6 +454,14 @@ private:
|
||||
return true;
|
||||
}
|
||||
|
||||
void broadcastPartStart(std::shared_ptr<LoadPartTask> task);
|
||||
void broadcastPartCancel(not_null<LoadPartTask*> task);
|
||||
void mediaChannelDescriptionsStart(
|
||||
std::shared_ptr<MediaChannelDescriptionsTask> task);
|
||||
void mediaChannelDescriptionsCancel(
|
||||
not_null<MediaChannelDescriptionsTask*> task);
|
||||
[[nodiscard]] int64 approximateServerTimeInMs() const;
|
||||
|
||||
[[nodiscard]] bool mediaChannelDescriptionsFill(
|
||||
not_null<MediaChannelDescriptionsTask*> task,
|
||||
Fn<bool(uint32)> resolved = nullptr);
|
||||
@@ -571,11 +569,14 @@ private:
|
||||
base::flat_set<
|
||||
std::shared_ptr<
|
||||
MediaChannelDescriptionsTask>,
|
||||
base::pointer_comparator<MediaChannelDescriptionsTask>> _mediaChannelDescriptionses;
|
||||
base::pointer_comparator<
|
||||
MediaChannelDescriptionsTask>> _mediaChannelDescriptionses;
|
||||
|
||||
rpl::variable<not_null<PeerData*>> _joinAs;
|
||||
std::vector<not_null<PeerData*>> _possibleJoinAs;
|
||||
QString _joinHash;
|
||||
int64 _serverTimeMs = 0;
|
||||
crl::time _serverTimeMsGotAt = 0;
|
||||
|
||||
rpl::variable<MuteState> _muted = MuteState::Muted;
|
||||
rpl::variable<bool> _canManage = false;
|
||||
|
||||
@@ -57,7 +57,7 @@ auto RowBlobs() -> std::array<Ui::Paint::Blobs::BlobData, 2> {
|
||||
}
|
||||
|
||||
[[nodiscard]] QString StatusPercentString(float volume) {
|
||||
return QString::number(int(std::round(volume * 200))) + '%';
|
||||
return QString::number(int(base::SafeRound(volume * 200))) + '%';
|
||||
}
|
||||
|
||||
[[nodiscard]] int StatusPercentWidth(const QString &percent) {
|
||||
@@ -492,7 +492,7 @@ int MembersRow::statusIconWidth(bool skipIcon) const {
|
||||
const auto full = iconWidth
|
||||
+ _statusIcon->percentWidth
|
||||
+ st::normalFont->spacew;
|
||||
return int(std::round(shown * full));
|
||||
return int(base::SafeRound(shown * full));
|
||||
}
|
||||
|
||||
int MembersRow::statusIconHeight() const {
|
||||
|
||||
@@ -1000,6 +1000,8 @@ void Panel::subscribeToChanges(not_null<Data::GroupCall*> real) {
|
||||
_recordingMark->setClickedCallback([=] {
|
||||
showToast({ (livestream
|
||||
? tr::lng_group_call_is_recorded_channel
|
||||
: real->recordVideo()
|
||||
? tr::lng_group_call_is_recorded_video
|
||||
: tr::lng_group_call_is_recorded)(tr::now) });
|
||||
});
|
||||
const auto animate = [=] {
|
||||
|
||||
@@ -461,15 +461,15 @@ Viewport::Layout Viewport::countWide(int outerWidth, int outerHeight) const {
|
||||
const auto columns = slices;
|
||||
const auto sizew = (outerWidth + skip) / float64(columns);
|
||||
for (auto column = 0; column != columns; ++column) {
|
||||
const auto left = int(std::round(column * sizew));
|
||||
const auto width = int(std::round(column * sizew + sizew - skip))
|
||||
- left;
|
||||
const auto rows = int(std::round((count - index)
|
||||
const auto left = int(base::SafeRound(column * sizew));
|
||||
const auto width = int(
|
||||
base::SafeRound(column * sizew + sizew - skip)) - left;
|
||||
const auto rows = int(base::SafeRound((count - index)
|
||||
/ float64(columns - column)));
|
||||
const auto sizeh = (outerHeight + skip) / float64(rows);
|
||||
for (auto row = 0; row != rows; ++row) {
|
||||
const auto top = int(std::round(row * sizeh));
|
||||
const auto height = int(std::round(
|
||||
const auto top = int(base::SafeRound(row * sizeh));
|
||||
const auto height = int(base::SafeRound(
|
||||
row * sizeh + sizeh - skip)) - top;
|
||||
auto &geometry = sizes[index];
|
||||
geometry.columns = {
|
||||
@@ -493,15 +493,15 @@ Viewport::Layout Viewport::countWide(int outerWidth, int outerHeight) const {
|
||||
const auto rows = slices;
|
||||
const auto sizeh = (outerHeight + skip) / float64(rows);
|
||||
for (auto row = 0; row != rows; ++row) {
|
||||
const auto top = int(std::round(row * sizeh));
|
||||
const auto height = int(std::round(row * sizeh + sizeh - skip))
|
||||
- top;
|
||||
const auto columns = int(std::round((count - index)
|
||||
const auto top = int(base::SafeRound(row * sizeh));
|
||||
const auto height = int(
|
||||
base::SafeRound(row * sizeh + sizeh - skip)) - top;
|
||||
const auto columns = int(base::SafeRound((count - index)
|
||||
/ float64(rows - row)));
|
||||
const auto sizew = (outerWidth + skip) / float64(columns);
|
||||
for (auto column = 0; column != columns; ++column) {
|
||||
const auto left = int(std::round(column * sizew));
|
||||
const auto width = int(std::round(
|
||||
const auto left = int(base::SafeRound(column * sizew));
|
||||
const auto width = int(base::SafeRound(
|
||||
column * sizew + sizew - skip)) - left;
|
||||
auto &geometry = sizes[index];
|
||||
geometry.rows = {
|
||||
|
||||
@@ -242,7 +242,7 @@ vec4 background() {
|
||||
QSize outer,
|
||||
float factor) {
|
||||
factor *= kBlurTextureSizeFactor;
|
||||
const auto area = outer / int(std::round(factor * cScale() / 100));
|
||||
const auto area = outer / int(base::SafeRound(factor * cScale() / 100));
|
||||
const auto scaled = unscaled.scaled(area, Qt::KeepAspectRatio);
|
||||
return (scaled.width() > unscaled.width()
|
||||
|| scaled.height() > unscaled.height())
|
||||
|
||||
@@ -35,6 +35,14 @@ Viewport::VideoTile::VideoTile(
|
||||
Expects(track.track != nullptr);
|
||||
Expects(track.row != nullptr);
|
||||
|
||||
using namespace rpl::mappers;
|
||||
_track.track->stateValue(
|
||||
) | rpl::filter(
|
||||
_1 == Webrtc::VideoState::Paused
|
||||
) | rpl::take(1) | rpl::start_with_next([=] {
|
||||
_wasPaused = true;
|
||||
}, _lifetime);
|
||||
|
||||
setup(std::move(pinned));
|
||||
}
|
||||
|
||||
@@ -68,11 +76,8 @@ QSize Viewport::VideoTile::PausedVideoSize() {
|
||||
QSize Viewport::VideoTile::trackOrUserpicSize() const {
|
||||
if (const auto size = trackSize(); !size.isEmpty()) {
|
||||
return size;
|
||||
} else if (_userpicSize.isEmpty()
|
||||
&& _track.track->state() == Webrtc::VideoState::Paused) {
|
||||
_userpicSize = PausedVideoSize();
|
||||
}
|
||||
return _userpicSize;
|
||||
return _wasPaused ? PausedVideoSize() : QSize();
|
||||
}
|
||||
|
||||
bool Viewport::VideoTile::screencast() const {
|
||||
|
||||
@@ -110,12 +110,12 @@ private:
|
||||
QRect _geometry;
|
||||
TileAnimation _animation;
|
||||
rpl::variable<QSize> _trackSize;
|
||||
mutable QSize _userpicSize;
|
||||
QRect _pinOuter;
|
||||
QRect _pinInner;
|
||||
QRect _backOuter;
|
||||
QRect _backInner;
|
||||
Ui::Animations::Simple _topControlsShownAnimation;
|
||||
bool _wasPaused = false;
|
||||
bool _topControlsShown = false;
|
||||
bool _pinned = false;
|
||||
bool _hidden = true;
|
||||
|
||||
@@ -92,7 +92,7 @@ MenuVolumeItem::MenuVolumeItem(
|
||||
|
||||
const auto volume = _localMuted
|
||||
? 0
|
||||
: std::round(_slider->value() * kMaxVolumePercent);
|
||||
: base::SafeRound(_slider->value() * kMaxVolumePercent);
|
||||
const auto muteProgress =
|
||||
_crossLineAnimation.value((!volume) ? 1. : 0.);
|
||||
|
||||
@@ -140,7 +140,7 @@ MenuVolumeItem::MenuVolumeItem(
|
||||
};
|
||||
|
||||
_slider->setChangeFinishedCallback([=](float64 value) {
|
||||
const auto newVolume = std::round(value * _maxVolume);
|
||||
const auto newVolume = base::SafeRound(value * _maxVolume);
|
||||
const auto muted = (value == 0);
|
||||
|
||||
if (!_cloudMuted && muted) {
|
||||
@@ -175,7 +175,7 @@ MenuVolumeItem::MenuVolumeItem(
|
||||
}
|
||||
if (_waitingForUpdateVolume) {
|
||||
const auto localVolume =
|
||||
std::round(_slider->value() * _maxVolume);
|
||||
base::SafeRound(_slider->value() * _maxVolume);
|
||||
if ((localVolume != newVolume)
|
||||
&& (_cloudVolume == newVolume)) {
|
||||
_changeVolumeRequests.fire(int(localVolume));
|
||||
|
||||
528
Telegram/SourceFiles/chat_helpers/emoji_interactions.cpp
Normal file
@@ -0,0 +1,528 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "chat_helpers/emoji_interactions.h"
|
||||
|
||||
#include "chat_helpers/stickers_emoji_pack.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "history/view/media/history_view_sticker.h"
|
||||
#include "main/main_session.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "ui/emoji_config.h"
|
||||
#include "base/random.h"
|
||||
#include "apiwrap.h"
|
||||
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QJsonArray>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QJsonValue>
|
||||
|
||||
namespace ChatHelpers {
|
||||
namespace {
|
||||
|
||||
constexpr auto kMinDelay = crl::time(200);
|
||||
constexpr auto kAccumulateDelay = crl::time(1000);
|
||||
constexpr auto kAccumulateSeenRequests = kAccumulateDelay;
|
||||
constexpr auto kAcceptSeenSinceRequest = 3 * crl::time(1000);
|
||||
constexpr auto kMaxDelay = 2 * crl::time(1000);
|
||||
constexpr auto kTimeNever = std::numeric_limits<crl::time>::max();
|
||||
constexpr auto kJsonVersion = 1;
|
||||
|
||||
} // namespace
|
||||
|
||||
auto EmojiInteractions::Combine(CheckResult a, CheckResult b) -> CheckResult {
|
||||
return {
|
||||
.nextCheckAt = std::min(a.nextCheckAt, b.nextCheckAt),
|
||||
.waitingForDownload = a.waitingForDownload || b.waitingForDownload,
|
||||
};
|
||||
}
|
||||
|
||||
EmojiInteractions::EmojiInteractions(not_null<Main::Session*> session)
|
||||
: _session(session)
|
||||
, _checkTimer([=] { check(); }) {
|
||||
_session->changes().messageUpdates(
|
||||
Data::MessageUpdate::Flag::Destroyed
|
||||
| Data::MessageUpdate::Flag::Edited
|
||||
) | rpl::start_with_next([=](const Data::MessageUpdate &update) {
|
||||
if (update.flags & Data::MessageUpdate::Flag::Destroyed) {
|
||||
_outgoing.remove(update.item);
|
||||
_incoming.remove(update.item);
|
||||
} else if (update.flags & Data::MessageUpdate::Flag::Edited) {
|
||||
checkEdition(update.item, _outgoing);
|
||||
checkEdition(update.item, _incoming);
|
||||
}
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
EmojiInteractions::~EmojiInteractions() = default;
|
||||
|
||||
void EmojiInteractions::checkEdition(
|
||||
not_null<HistoryItem*> item,
|
||||
base::flat_map<not_null<HistoryItem*>, std::vector<Animation>> &map) {
|
||||
const auto i = map.find(item);
|
||||
if (i != end(map)
|
||||
&& (i->second.front().emoji != chooseInteractionEmoji(item))) {
|
||||
map.erase(i);
|
||||
}
|
||||
}
|
||||
|
||||
EmojiPtr EmojiInteractions::chooseInteractionEmoji(
|
||||
not_null<HistoryItem*> item) const {
|
||||
return chooseInteractionEmoji(item->originalText().text);
|
||||
}
|
||||
|
||||
EmojiPtr EmojiInteractions::chooseInteractionEmoji(
|
||||
const QString &emoticon) const {
|
||||
const auto emoji = Ui::Emoji::Find(emoticon);
|
||||
if (!emoji) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto &pack = _session->emojiStickersPack();
|
||||
if (!pack.animationsForEmoji(emoji).empty()) {
|
||||
return emoji;
|
||||
}
|
||||
if (const auto original = emoji->original(); original != emoji) {
|
||||
if (!pack.animationsForEmoji(original).empty()) {
|
||||
return original;
|
||||
}
|
||||
}
|
||||
static const auto kHearts = {
|
||||
QString::fromUtf8("\xf0\x9f\x92\x9b"),
|
||||
QString::fromUtf8("\xf0\x9f\x92\x99"),
|
||||
QString::fromUtf8("\xf0\x9f\x92\x9a"),
|
||||
QString::fromUtf8("\xf0\x9f\x92\x9c"),
|
||||
QString::fromUtf8("\xf0\x9f\xa7\xa1"),
|
||||
QString::fromUtf8("\xf0\x9f\x96\xa4"),
|
||||
QString::fromUtf8("\xf0\x9f\xa4\x8e"),
|
||||
QString::fromUtf8("\xf0\x9f\xa4\x8d"),
|
||||
};
|
||||
return ranges::contains(kHearts, emoji->id())
|
||||
? Ui::Emoji::Find(QString::fromUtf8("\xe2\x9d\xa4"))
|
||||
: emoji;
|
||||
}
|
||||
|
||||
void EmojiInteractions::startOutgoing(
|
||||
not_null<const HistoryView::Element*> view) {
|
||||
const auto item = view->data();
|
||||
if (!IsServerMsgId(item->id) || !item->history()->peer->isUser()) {
|
||||
return;
|
||||
}
|
||||
const auto emoticon = item->originalText().text;
|
||||
const auto emoji = chooseInteractionEmoji(emoticon);
|
||||
if (!emoji) {
|
||||
return;
|
||||
}
|
||||
const auto &pack = _session->emojiStickersPack();
|
||||
const auto &list = pack.animationsForEmoji(emoji);
|
||||
if (list.empty()) {
|
||||
return;
|
||||
}
|
||||
auto &animations = _outgoing[item];
|
||||
if (!animations.empty() && animations.front().emoji != emoji) {
|
||||
// The message was edited, forget the old emoji.
|
||||
animations.clear();
|
||||
}
|
||||
const auto last = !animations.empty() ? &animations.back() : nullptr;
|
||||
const auto listSize = int(list.size());
|
||||
const auto chooseDifferent = (last && listSize > 1);
|
||||
const auto index = chooseDifferent
|
||||
? base::RandomIndex(listSize - 1)
|
||||
: base::RandomIndex(listSize);
|
||||
const auto selected = (begin(list) + index)->second;
|
||||
const auto document = (chooseDifferent && selected == last->document)
|
||||
? (begin(list) + index + 1)->second
|
||||
: selected;
|
||||
const auto media = document->createMediaView();
|
||||
media->checkStickerLarge();
|
||||
const auto now = crl::now();
|
||||
animations.push_back({
|
||||
.emoticon = emoticon,
|
||||
.emoji = emoji,
|
||||
.document = document,
|
||||
.media = media,
|
||||
.scheduledAt = now,
|
||||
.index = index,
|
||||
});
|
||||
check(now);
|
||||
}
|
||||
|
||||
void EmojiInteractions::startIncoming(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId messageId,
|
||||
const QString &emoticon,
|
||||
EmojiInteractionsBunch &&bunch) {
|
||||
if (!peer->isUser()
|
||||
|| bunch.interactions.empty()
|
||||
|| !IsServerMsgId(messageId)) {
|
||||
return;
|
||||
}
|
||||
const auto item = _session->data().message(nullptr, messageId);
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
const auto emoji = chooseInteractionEmoji(item);
|
||||
if (!emoji || emoji != chooseInteractionEmoji(emoticon)) {
|
||||
return;
|
||||
}
|
||||
const auto &pack = _session->emojiStickersPack();
|
||||
const auto &list = pack.animationsForEmoji(emoji);
|
||||
if (list.empty()) {
|
||||
return;
|
||||
}
|
||||
auto &animations = _incoming[item];
|
||||
if (!animations.empty() && animations.front().emoji != emoji) {
|
||||
// The message was edited, forget the old emoji.
|
||||
animations.clear();
|
||||
}
|
||||
const auto now = crl::now();
|
||||
for (const auto &single : bunch.interactions) {
|
||||
const auto at = now + crl::time(base::SafeRound(single.time * 1000));
|
||||
if (!animations.empty() && animations.back().scheduledAt >= at) {
|
||||
continue;
|
||||
}
|
||||
const auto listSize = int(list.size());
|
||||
const auto index = (single.index - 1);
|
||||
if (index < listSize) {
|
||||
const auto document = (begin(list) + index)->second;
|
||||
const auto media = document->createMediaView();
|
||||
media->checkStickerLarge();
|
||||
animations.push_back({
|
||||
.emoticon = emoticon,
|
||||
.emoji = emoji,
|
||||
.document = document,
|
||||
.media = media,
|
||||
.scheduledAt = at,
|
||||
.incoming = true,
|
||||
.index = index,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (animations.empty()) {
|
||||
_incoming.remove(item);
|
||||
} else {
|
||||
check(now);
|
||||
}
|
||||
}
|
||||
|
||||
void EmojiInteractions::seenOutgoing(
|
||||
not_null<PeerData*> peer,
|
||||
const QString &emoticon) {
|
||||
if (const auto i = _playsSent.find(peer); i != end(_playsSent)) {
|
||||
if (const auto emoji = chooseInteractionEmoji(emoticon)) {
|
||||
if (const auto j = i->second.find(emoji); j != end(i->second)) {
|
||||
const auto last = j->second.lastDoneReceivedAt;
|
||||
if (!last || last + kAcceptSeenSinceRequest > crl::now()) {
|
||||
_seen.fire({ peer, emoticon });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto EmojiInteractions::checkAnimations(crl::time now) -> CheckResult {
|
||||
return Combine(
|
||||
checkAnimations(now, _outgoing),
|
||||
checkAnimations(now, _incoming));
|
||||
}
|
||||
|
||||
auto EmojiInteractions::checkAnimations(
|
||||
crl::time now,
|
||||
base::flat_map<not_null<HistoryItem*>, std::vector<Animation>> &map
|
||||
) -> CheckResult {
|
||||
auto nearest = kTimeNever;
|
||||
auto waitingForDownload = false;
|
||||
for (auto i = begin(map); i != end(map);) {
|
||||
auto lastStartedAt = crl::time();
|
||||
|
||||
auto &animations = i->second;
|
||||
// Erase too old requests.
|
||||
const auto j = ranges::find_if(animations, [&](const Animation &a) {
|
||||
return !a.startedAt && (a.scheduledAt + kMaxDelay <= now);
|
||||
});
|
||||
if (j == begin(animations)) {
|
||||
i = map.erase(i);
|
||||
continue;
|
||||
} else if (j != end(animations)) {
|
||||
animations.erase(j, end(animations));
|
||||
}
|
||||
const auto item = i->first;
|
||||
for (auto &animation : animations) {
|
||||
if (animation.startedAt) {
|
||||
lastStartedAt = animation.startedAt;
|
||||
} else if (!animation.media->loaded()) {
|
||||
animation.media->checkStickerLarge();
|
||||
waitingForDownload = true;
|
||||
break;
|
||||
} else if (!lastStartedAt || lastStartedAt + kMinDelay <= now) {
|
||||
animation.startedAt = now;
|
||||
_playRequests.fire({
|
||||
animation.emoticon,
|
||||
item,
|
||||
animation.media,
|
||||
animation.scheduledAt,
|
||||
animation.incoming,
|
||||
});
|
||||
break;
|
||||
} else {
|
||||
nearest = std::min(nearest, lastStartedAt + kMinDelay);
|
||||
break;
|
||||
}
|
||||
}
|
||||
++i;
|
||||
}
|
||||
return {
|
||||
.nextCheckAt = nearest,
|
||||
.waitingForDownload = waitingForDownload,
|
||||
};
|
||||
}
|
||||
|
||||
void EmojiInteractions::sendAccumulatedOutgoing(
|
||||
crl::time now,
|
||||
not_null<HistoryItem*> item,
|
||||
std::vector<Animation> &animations) {
|
||||
Expects(!animations.empty());
|
||||
|
||||
const auto firstStartedAt = animations.front().startedAt;
|
||||
const auto intervalEnd = firstStartedAt + kAccumulateDelay;
|
||||
if (intervalEnd > now) {
|
||||
return;
|
||||
}
|
||||
const auto from = begin(animations);
|
||||
const auto till = ranges::find_if(animations, [&](const auto &animation) {
|
||||
return !animation.startedAt || (animation.startedAt >= intervalEnd);
|
||||
});
|
||||
auto bunch = EmojiInteractionsBunch();
|
||||
bunch.interactions.reserve(till - from);
|
||||
for (const auto &animation : ranges::make_subrange(from, till)) {
|
||||
bunch.interactions.push_back({
|
||||
.index = animation.index + 1,
|
||||
.time = (animation.startedAt - firstStartedAt) / 1000.,
|
||||
});
|
||||
}
|
||||
if (bunch.interactions.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto peer = item->history()->peer;
|
||||
const auto emoji = from->emoji;
|
||||
const auto requestId = _session->api().request(MTPmessages_SetTyping(
|
||||
MTP_flags(0),
|
||||
peer->input,
|
||||
MTPint(), // top_msg_id
|
||||
MTP_sendMessageEmojiInteraction(
|
||||
MTP_string(from->emoticon),
|
||||
MTP_int(item->id),
|
||||
MTP_dataJSON(MTP_bytes(ToJson(bunch))))
|
||||
)).done([=](const MTPBool &result, mtpRequestId requestId) {
|
||||
auto &sent = _playsSent[peer][emoji];
|
||||
if (sent.lastRequestId == requestId) {
|
||||
sent.lastDoneReceivedAt = crl::now();
|
||||
if (!_checkTimer.isActive()) {
|
||||
_checkTimer.callOnce(kAcceptSeenSinceRequest);
|
||||
}
|
||||
}
|
||||
}).send();
|
||||
_playsSent[peer][emoji] = PlaySent{ .lastRequestId = requestId };
|
||||
animations.erase(from, till);
|
||||
}
|
||||
|
||||
void EmojiInteractions::clearAccumulatedIncoming(
|
||||
crl::time now,
|
||||
std::vector<Animation> &animations) {
|
||||
Expects(!animations.empty());
|
||||
|
||||
const auto from = begin(animations);
|
||||
const auto till = ranges::find_if(animations, [&](const auto &animation) {
|
||||
return !animation.startedAt
|
||||
|| (animation.startedAt + kMinDelay) > now;
|
||||
});
|
||||
animations.erase(from, till);
|
||||
}
|
||||
|
||||
auto EmojiInteractions::checkAccumulated(crl::time now) -> CheckResult {
|
||||
auto nearest = kTimeNever;
|
||||
for (auto i = begin(_outgoing); i != end(_outgoing);) {
|
||||
auto &[item, animations] = *i;
|
||||
sendAccumulatedOutgoing(now, item, animations);
|
||||
if (animations.empty()) {
|
||||
i = _outgoing.erase(i);
|
||||
continue;
|
||||
} else if (const auto firstStartedAt = animations.front().startedAt) {
|
||||
nearest = std::min(nearest, firstStartedAt + kAccumulateDelay);
|
||||
Assert(nearest > now);
|
||||
}
|
||||
++i;
|
||||
}
|
||||
for (auto i = begin(_incoming); i != end(_incoming);) {
|
||||
auto &animations = i->second;
|
||||
clearAccumulatedIncoming(now, animations);
|
||||
if (animations.empty()) {
|
||||
i = _incoming.erase(i);
|
||||
continue;
|
||||
} else {
|
||||
// Doesn't really matter when, just clear them finally.
|
||||
nearest = std::min(nearest, now + kAccumulateDelay);
|
||||
}
|
||||
++i;
|
||||
}
|
||||
return {
|
||||
.nextCheckAt = nearest,
|
||||
};
|
||||
}
|
||||
|
||||
void EmojiInteractions::check(crl::time now) {
|
||||
if (!now) {
|
||||
now = crl::now();
|
||||
}
|
||||
checkSeenRequests(now);
|
||||
checkSentRequests(now);
|
||||
const auto result1 = checkAnimations(now);
|
||||
const auto result2 = checkAccumulated(now);
|
||||
const auto result = Combine(result1, result2);
|
||||
if (result.nextCheckAt < kTimeNever) {
|
||||
Assert(result.nextCheckAt > now);
|
||||
_checkTimer.callOnce(result.nextCheckAt - now);
|
||||
} else if (!_playStarted.empty()) {
|
||||
_checkTimer.callOnce(kAccumulateSeenRequests);
|
||||
} else if (!_playsSent.empty()) {
|
||||
_checkTimer.callOnce(kAcceptSeenSinceRequest);
|
||||
}
|
||||
setWaitingForDownload(result.waitingForDownload);
|
||||
}
|
||||
|
||||
void EmojiInteractions::checkSeenRequests(crl::time now) {
|
||||
for (auto i = begin(_playStarted); i != end(_playStarted);) {
|
||||
auto &animations = i->second;
|
||||
for (auto j = begin(animations); j != end(animations);) {
|
||||
if (j->second + kAccumulateSeenRequests <= now) {
|
||||
j = animations.erase(j);
|
||||
} else {
|
||||
++j;
|
||||
}
|
||||
}
|
||||
if (animations.empty()) {
|
||||
i = _playStarted.erase(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EmojiInteractions::checkSentRequests(crl::time now) {
|
||||
for (auto i = begin(_playsSent); i != end(_playsSent);) {
|
||||
auto &animations = i->second;
|
||||
for (auto j = begin(animations); j != end(animations);) {
|
||||
const auto last = j->second.lastDoneReceivedAt;
|
||||
if (last && last + kAcceptSeenSinceRequest <= now) {
|
||||
j = animations.erase(j);
|
||||
} else {
|
||||
++j;
|
||||
}
|
||||
}
|
||||
if (animations.empty()) {
|
||||
i = _playsSent.erase(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EmojiInteractions::setWaitingForDownload(bool waiting) {
|
||||
if (_waitingForDownload == waiting) {
|
||||
return;
|
||||
}
|
||||
_waitingForDownload = waiting;
|
||||
if (_waitingForDownload) {
|
||||
_session->downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
check();
|
||||
}, _downloadCheckLifetime);
|
||||
} else {
|
||||
_downloadCheckLifetime.destroy();
|
||||
_downloadCheckLifetime.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
void EmojiInteractions::playStarted(not_null<PeerData*> peer, QString emoji) {
|
||||
auto &map = _playStarted[peer];
|
||||
const auto i = map.find(emoji);
|
||||
const auto now = crl::now();
|
||||
if (i != end(map) && now - i->second < kAccumulateSeenRequests) {
|
||||
return;
|
||||
}
|
||||
_session->api().request(MTPmessages_SetTyping(
|
||||
MTP_flags(0),
|
||||
peer->input,
|
||||
MTPint(), // top_msg_id
|
||||
MTP_sendMessageEmojiInteractionSeen(MTP_string(emoji))
|
||||
)).send();
|
||||
map[emoji] = now;
|
||||
if (!_checkTimer.isActive()) {
|
||||
_checkTimer.callOnce(kAccumulateSeenRequests);
|
||||
}
|
||||
}
|
||||
|
||||
EmojiInteractionsBunch EmojiInteractions::Parse(const QByteArray &json) {
|
||||
auto error = QJsonParseError{ 0, QJsonParseError::NoError };
|
||||
const auto document = QJsonDocument::fromJson(json, &error);
|
||||
if (error.error != QJsonParseError::NoError || !document.isObject()) {
|
||||
LOG(("API Error: Bad interactions json received."));
|
||||
return {};
|
||||
}
|
||||
const auto root = document.object();
|
||||
const auto version = root.value("v").toInt();
|
||||
if (version != kJsonVersion) {
|
||||
LOG(("API Error: Bad interactions version: %1").arg(version));
|
||||
return {};
|
||||
}
|
||||
const auto actions = root.value("a").toArray();
|
||||
if (actions.empty()) {
|
||||
LOG(("API Error: Empty interactions list."));
|
||||
return {};
|
||||
}
|
||||
auto result = EmojiInteractionsBunch();
|
||||
for (const auto &interaction : actions) {
|
||||
const auto object = interaction.toObject();
|
||||
const auto index = object.value("i").toInt();
|
||||
if (index < 0 || index > 10) {
|
||||
LOG(("API Error: Bad interaction index: %1").arg(index));
|
||||
return {};
|
||||
}
|
||||
const auto time = object.value("t").toDouble();
|
||||
if (time < 0.
|
||||
|| time > 1.
|
||||
|| (!result.interactions.empty()
|
||||
&& time <= result.interactions.back().time)) {
|
||||
LOG(("API Error: Bad interaction time: %1").arg(time));
|
||||
continue;
|
||||
}
|
||||
result.interactions.push_back({ .index = index, .time = time });
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QByteArray EmojiInteractions::ToJson(const EmojiInteractionsBunch &bunch) {
|
||||
auto list = QJsonArray();
|
||||
for (const auto &single : bunch.interactions) {
|
||||
list.push_back(QJsonObject{
|
||||
{ "i", single.index },
|
||||
{ "t", single.time },
|
||||
});
|
||||
}
|
||||
return QJsonDocument(QJsonObject{
|
||||
{ "v", kJsonVersion },
|
||||
{ "a", std::move(list) },
|
||||
}).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
|
||||
} // namespace ChatHelpers
|
||||
147
Telegram/SourceFiles/chat_helpers/emoji_interactions.h
Normal file
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/timer.h"
|
||||
|
||||
class PeerData;
|
||||
class HistoryItem;
|
||||
class DocumentData;
|
||||
|
||||
namespace Data {
|
||||
class DocumentMedia;
|
||||
} // namespace Data
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace HistoryView {
|
||||
class Element;
|
||||
} // namespace HistoryView
|
||||
|
||||
namespace ChatHelpers {
|
||||
|
||||
struct EmojiInteractionPlayRequest {
|
||||
QString emoticon;
|
||||
not_null<HistoryItem*> item;
|
||||
std::shared_ptr<Data::DocumentMedia> media;
|
||||
crl::time shouldHaveStartedAt = 0;
|
||||
bool incoming = false;
|
||||
};
|
||||
|
||||
struct EmojiInteractionsBunch {
|
||||
struct Single {
|
||||
int index = 0;
|
||||
double time = 0;
|
||||
};
|
||||
std::vector<Single> interactions;
|
||||
};
|
||||
|
||||
struct EmojiInteractionSeen {
|
||||
not_null<PeerData*> peer;
|
||||
QString emoticon;
|
||||
};
|
||||
|
||||
class EmojiInteractions final {
|
||||
public:
|
||||
explicit EmojiInteractions(not_null<Main::Session*> session);
|
||||
~EmojiInteractions();
|
||||
|
||||
using PlayRequest = EmojiInteractionPlayRequest;
|
||||
|
||||
void startOutgoing(not_null<const HistoryView::Element*> view);
|
||||
void startIncoming(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId messageId,
|
||||
const QString &emoticon,
|
||||
EmojiInteractionsBunch &&bunch);
|
||||
|
||||
void seenOutgoing(not_null<PeerData*> peer, const QString &emoticon);
|
||||
[[nodiscard]] rpl::producer<EmojiInteractionSeen> seen() const {
|
||||
return _seen.events();
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<PlayRequest> playRequests() const {
|
||||
return _playRequests.events();
|
||||
}
|
||||
void playStarted(not_null<PeerData*> peer, QString emoji);
|
||||
|
||||
[[nodiscard]] static EmojiInteractionsBunch Parse(const QByteArray &json);
|
||||
[[nodiscard]] static QByteArray ToJson(
|
||||
const EmojiInteractionsBunch &bunch);
|
||||
|
||||
private:
|
||||
struct Animation {
|
||||
QString emoticon;
|
||||
not_null<EmojiPtr> emoji;
|
||||
not_null<DocumentData*> document;
|
||||
std::shared_ptr<Data::DocumentMedia> media;
|
||||
crl::time scheduledAt = 0;
|
||||
crl::time startedAt = 0;
|
||||
bool incoming = false;
|
||||
int index = 0;
|
||||
};
|
||||
struct PlaySent {
|
||||
mtpRequestId lastRequestId = 0;
|
||||
crl::time lastDoneReceivedAt = 0;
|
||||
};
|
||||
struct CheckResult {
|
||||
crl::time nextCheckAt = 0;
|
||||
bool waitingForDownload = false;
|
||||
};
|
||||
[[nodiscard]] static CheckResult Combine(CheckResult a, CheckResult b);
|
||||
|
||||
[[nodiscard]] EmojiPtr chooseInteractionEmoji(
|
||||
not_null<HistoryItem*> item) const;
|
||||
[[nodiscard]] EmojiPtr chooseInteractionEmoji(
|
||||
const QString &emoticon) const;
|
||||
|
||||
void check(crl::time now = 0);
|
||||
[[nodiscard]] CheckResult checkAnimations(crl::time now);
|
||||
[[nodiscard]] CheckResult checkAnimations(
|
||||
crl::time now,
|
||||
base::flat_map<not_null<HistoryItem*>, std::vector<Animation>> &map);
|
||||
[[nodiscard]] CheckResult checkAccumulated(crl::time now);
|
||||
void sendAccumulatedOutgoing(
|
||||
crl::time now,
|
||||
not_null<HistoryItem*> item,
|
||||
std::vector<Animation> &animations);
|
||||
void clearAccumulatedIncoming(
|
||||
crl::time now,
|
||||
std::vector<Animation> &animations);
|
||||
void setWaitingForDownload(bool waiting);
|
||||
|
||||
void checkSeenRequests(crl::time now);
|
||||
void checkSentRequests(crl::time now);
|
||||
void checkEdition(
|
||||
not_null<HistoryItem*> item,
|
||||
base::flat_map<not_null<HistoryItem*>, std::vector<Animation>> &map);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
base::flat_map<not_null<HistoryItem*>, std::vector<Animation>> _outgoing;
|
||||
base::flat_map<not_null<HistoryItem*>, std::vector<Animation>> _incoming;
|
||||
base::Timer _checkTimer;
|
||||
rpl::event_stream<PlayRequest> _playRequests;
|
||||
base::flat_map<
|
||||
not_null<PeerData*>,
|
||||
base::flat_map<QString, crl::time>> _playStarted;
|
||||
base::flat_map<
|
||||
not_null<PeerData*>,
|
||||
base::flat_map<not_null<EmojiPtr>, PlaySent>> _playsSent;
|
||||
rpl::event_stream<EmojiInteractionSeen> _seen;
|
||||
|
||||
bool _waitingForDownload = false;
|
||||
rpl::lifetime _downloadCheckLifetime;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace ChatHelpers
|
||||
@@ -39,10 +39,10 @@ inline auto PreviewPath(int i) {
|
||||
}
|
||||
|
||||
const auto kSets = {
|
||||
Set{ {0, 0, 0, "Mac"}, PreviewPath(0) },
|
||||
Set{ {1, 713, 7'313'166, "Android"}, PreviewPath(1) },
|
||||
Set{ {2, 714, 4'690'333, "Twemoji"}, PreviewPath(2) },
|
||||
Set{ {3, 716, 5'968'021, "JoyPixels"}, PreviewPath(3) },
|
||||
Set{ { 0, 0, 0, "Mac" }, PreviewPath(0) },
|
||||
Set{ { 1, 1112, 7'914'459, "Android" }, PreviewPath(1) },
|
||||
Set{ { 2, 1113, 5'287'724, "Twemoji" }, PreviewPath(2) },
|
||||
Set{ { 3, 1114, 6'687'347, "JoyPixels" }, PreviewPath(3) },
|
||||
};
|
||||
|
||||
using Loading = MTP::DedicatedLoader::Progress;
|
||||
|
||||
@@ -35,7 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/ui_utility.h"
|
||||
#include "ui/cached_round_corners.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "base/openssl_help.h"
|
||||
#include "base/random.h"
|
||||
#include "window/window_adaptive.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_chat.h"
|
||||
@@ -667,7 +667,7 @@ void FieldAutocomplete::showAnimated() {
|
||||
return;
|
||||
}
|
||||
if (_cache.isNull()) {
|
||||
_stickersSeed = openssl::RandomValue<uint64>();
|
||||
_stickersSeed = base::RandomValue<uint64>();
|
||||
_scroll->show();
|
||||
_cache = Ui::GrabWidget(this);
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ QImage EmojiImageLoader::prepare(EmojiPtr emoji) const {
|
||||
{ -1, 1 },
|
||||
{ 1, 1 },
|
||||
} };
|
||||
const auto corrected = int(std::round(delta / sqrt(2.)));
|
||||
const auto corrected = int(base::SafeRound(delta / sqrt(2.)));
|
||||
for (const auto &shift : diagonal) {
|
||||
for (auto i = 0; i != corrected; ++i) {
|
||||
p.drawImage(QPoint(delta, delta) + shift * (i + 1), tinted);
|
||||
|
||||
@@ -30,6 +30,18 @@ namespace {
|
||||
|
||||
constexpr auto kRefreshTimeout = 7200 * crl::time(1000);
|
||||
|
||||
[[nodiscard]] std::optional<int> IndexFromEmoticon(const QString &emoticon) {
|
||||
if (emoticon.size() < 2) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto first = emoticon[0].unicode();
|
||||
return (first >= '1' && first <= '9')
|
||||
? std::make_optional(first - '1')
|
||||
: (first == 55357 && emoticon[1].unicode() == 56607)
|
||||
? std::make_optional(9)
|
||||
: std::nullopt;
|
||||
}
|
||||
|
||||
[[nodiscard]] QSize SingleSize() {
|
||||
const auto single = st::largeEmojiSize;
|
||||
const auto outline = st::largeEmojiOutline;
|
||||
@@ -207,6 +219,13 @@ std::shared_ptr<LargeEmojiImage> EmojiPack::image(EmojiPtr emoji) {
|
||||
return result;
|
||||
}
|
||||
|
||||
auto EmojiPack::animationsForEmoji(EmojiPtr emoji) const
|
||||
-> const base::flat_map<int, not_null<DocumentData*>> & {
|
||||
static const auto empty = base::flat_map<int, not_null<DocumentData*>>();
|
||||
const auto i = _animations.find(emoji);
|
||||
return (i != end(_animations)) ? i->second : empty;
|
||||
}
|
||||
|
||||
void EmojiPack::refresh() {
|
||||
if (_requestId) {
|
||||
return;
|
||||
@@ -215,7 +234,7 @@ void EmojiPack::refresh() {
|
||||
MTP_inputStickerSetAnimatedEmoji()
|
||||
)).done([=](const MTPmessages_StickerSet &result) {
|
||||
_requestId = 0;
|
||||
refreshDelayed();
|
||||
refreshAnimations();
|
||||
result.match([&](const MTPDmessages_stickerSet &data) {
|
||||
applySet(data);
|
||||
});
|
||||
@@ -225,6 +244,24 @@ void EmojiPack::refresh() {
|
||||
}).send();
|
||||
}
|
||||
|
||||
void EmojiPack::refreshAnimations() {
|
||||
if (_animationsRequestId) {
|
||||
return;
|
||||
}
|
||||
_animationsRequestId = _session->api().request(MTPmessages_GetStickerSet(
|
||||
MTP_inputStickerSetAnimatedEmojiAnimations()
|
||||
)).done([=](const MTPmessages_StickerSet &result) {
|
||||
_animationsRequestId = 0;
|
||||
refreshDelayed();
|
||||
result.match([&](const MTPDmessages_stickerSet &data) {
|
||||
applyAnimationsSet(data);
|
||||
});
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_animationsRequestId = 0;
|
||||
refreshDelayed();
|
||||
}).send();
|
||||
}
|
||||
|
||||
void EmojiPack::applySet(const MTPDmessages_stickerSet &data) {
|
||||
const auto stickers = collectStickers(data.vdocuments().v);
|
||||
auto was = base::take(_map);
|
||||
@@ -251,6 +288,55 @@ void EmojiPack::applySet(const MTPDmessages_stickerSet &data) {
|
||||
}
|
||||
}
|
||||
|
||||
void EmojiPack::applyAnimationsSet(const MTPDmessages_stickerSet &data) {
|
||||
const auto stickers = collectStickers(data.vdocuments().v);
|
||||
const auto &packs = data.vpacks().v;
|
||||
const auto indices = collectAnimationsIndices(packs);
|
||||
|
||||
_animations.clear();
|
||||
for (const auto &pack : packs) {
|
||||
pack.match([&](const MTPDstickerPack &data) {
|
||||
const auto emoticon = qs(data.vemoticon());
|
||||
if (IndexFromEmoticon(emoticon).has_value()) {
|
||||
return;
|
||||
}
|
||||
const auto emoji = Ui::Emoji::Find(emoticon);
|
||||
if (!emoji) {
|
||||
return;
|
||||
}
|
||||
for (const auto &id : data.vdocuments().v) {
|
||||
const auto i = indices.find(id.v);
|
||||
if (i == end(indices)) {
|
||||
continue;
|
||||
}
|
||||
const auto j = stickers.find(id.v);
|
||||
if (j == end(stickers)) {
|
||||
continue;
|
||||
}
|
||||
for (const auto index : i->second) {
|
||||
_animations[emoji].emplace(index, j->second);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
auto EmojiPack::collectAnimationsIndices(
|
||||
const QVector<MTPStickerPack> &packs
|
||||
) const -> base::flat_map<uint64, base::flat_set<int>> {
|
||||
auto result = base::flat_map<uint64, base::flat_set<int>>();
|
||||
for (const auto &pack : packs) {
|
||||
pack.match([&](const MTPDstickerPack &data) {
|
||||
if (const auto index = IndexFromEmoticon(qs(data.vemoticon()))) {
|
||||
for (const auto &id : data.vdocuments().v) {
|
||||
result[id.v].emplace(*index);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void EmojiPack::refreshAll() {
|
||||
for (const auto &[emoji, list] : _items) {
|
||||
refreshItems(list);
|
||||
|
||||
@@ -67,17 +67,25 @@ public:
|
||||
[[nodiscard]] Sticker stickerForEmoji(const IsolatedEmoji &emoji);
|
||||
[[nodiscard]] std::shared_ptr<LargeEmojiImage> image(EmojiPtr emoji);
|
||||
|
||||
[[nodiscard]] auto animationsForEmoji(EmojiPtr emoji) const
|
||||
-> const base::flat_map<int, not_null<DocumentData*>> &;
|
||||
|
||||
private:
|
||||
class ImageLoader;
|
||||
|
||||
void refresh();
|
||||
void refreshDelayed();
|
||||
void refreshAnimations();
|
||||
void applySet(const MTPDmessages_stickerSet &data);
|
||||
void applyPack(
|
||||
const MTPDstickerPack &data,
|
||||
const base::flat_map<uint64, not_null<DocumentData*>> &map);
|
||||
base::flat_map<uint64, not_null<DocumentData*>> collectStickers(
|
||||
const QVector<MTPDocument> &list) const;
|
||||
void applyAnimationsSet(const MTPDmessages_stickerSet &data);
|
||||
[[nodiscard]] auto collectStickers(const QVector<MTPDocument> &list) const
|
||||
-> base::flat_map<uint64, not_null<DocumentData*>>;
|
||||
[[nodiscard]] auto collectAnimationsIndices(
|
||||
const QVector<MTPStickerPack> &packs) const
|
||||
-> base::flat_map<uint64, base::flat_set<int>>;
|
||||
void refreshAll();
|
||||
void refreshItems(EmojiPtr emoji);
|
||||
void refreshItems(const base::flat_set<not_null<HistoryItem*>> &list);
|
||||
@@ -90,6 +98,11 @@ private:
|
||||
base::flat_map<EmojiPtr, std::weak_ptr<LargeEmojiImage>> _images;
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
base::flat_map<
|
||||
EmojiPtr,
|
||||
base::flat_map<int, not_null<DocumentData*>>> _animations;
|
||||
mtpRequestId _animationsRequestId = 0;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
@@ -44,6 +44,10 @@ enum class StickerLottieSize : uchar {
|
||||
StickersFooter,
|
||||
SetsListThumbnail,
|
||||
InlineResults,
|
||||
EmojiInteraction,
|
||||
EmojiInteractionReserved1,
|
||||
EmojiInteractionReserved2,
|
||||
EmojiInteractionReserved3,
|
||||
};
|
||||
|
||||
[[nodiscard]] std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(
|
||||
|
||||
@@ -47,6 +47,46 @@ std::map<int, const char*> BetaLogs() {
|
||||
"from the context menu.\n"
|
||||
|
||||
"- Enable recording with video in live streams and video chats."
|
||||
},
|
||||
{
|
||||
3000004,
|
||||
"- Fix a crash when joining video chat or live broadcast.\n"
|
||||
|
||||
"- Add a \"Close to Taskbar\" option when tray icon is disabled "
|
||||
"(Windows and Linux)."
|
||||
},
|
||||
{
|
||||
3000005,
|
||||
"- Add support for Emoji 13.1."
|
||||
},
|
||||
{
|
||||
3001002,
|
||||
"- Control video in fullscreen mode using arrows and numbers.\n"
|
||||
|
||||
"- Open locations in browser if default Bing Maps is not installed.\n"
|
||||
|
||||
"- Reconnect without timeout when network availability changes.\n"
|
||||
|
||||
"- Crash fixes."
|
||||
},
|
||||
{
|
||||
3001005,
|
||||
"- Choose one of 8 new preset themes for any individual private chat.\n"
|
||||
|
||||
"- Click on '...' menu > 'Change Colors' to pick a theme.\n"
|
||||
|
||||
"- Both chat participants will see the same theme in that chat "
|
||||
"– on all their devices.\n"
|
||||
|
||||
"- Each new theme features colorful gradient message bubbles, "
|
||||
"beautifully animated backgrounds and unique background patterns.\n"
|
||||
|
||||
"- All chat themes have day and night versions and will follow "
|
||||
"your overall dark mode settings.\n"
|
||||
|
||||
"- Implement main window rounded corners on Windows 11.\n"
|
||||
|
||||
"- Fix audio capture from AirPods on macOS.\n"
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -220,7 +220,8 @@ QByteArray Settings::serialize() const {
|
||||
<< qint32(_disableOpenGL ? 1 : 0)
|
||||
<< _photoEditorBrush
|
||||
<< qint32(_groupCallNoiseSuppression ? 1 : 0)
|
||||
<< qint32(_voicePlaybackSpeed * 100);
|
||||
<< qint32(_voicePlaybackSpeed * 100)
|
||||
<< qint32(_closeToTaskbar.current() ? 1 : 0);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -303,6 +304,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
QByteArray proxy;
|
||||
qint32 hiddenGroupCallTooltips = qint32(_hiddenGroupCallTooltips.value());
|
||||
QByteArray photoEditorBrush = _photoEditorBrush;
|
||||
qint32 closeToTaskbar = _closeToTaskbar.current() ? 1 : 0;
|
||||
|
||||
stream >> themesAccentColors;
|
||||
if (!stream.atEnd()) {
|
||||
@@ -460,6 +462,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
if (!stream.atEnd()) {
|
||||
stream >> voicePlaybackSpeed;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> closeToTaskbar;
|
||||
}
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("App Error: "
|
||||
"Bad data for Core::Settings::constructFromSerialized()"));
|
||||
@@ -600,6 +605,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
: Tooltip(0));
|
||||
}();
|
||||
_photoEditorBrush = photoEditorBrush;
|
||||
_closeToTaskbar = (closeToTaskbar == 1);
|
||||
}
|
||||
|
||||
QString Settings::getSoundPath(const QString &key) const {
|
||||
|
||||
@@ -601,10 +601,23 @@ public:
|
||||
_hiddenGroupCallTooltips |= value;
|
||||
}
|
||||
|
||||
void setCloseToTaskbar(bool value) {
|
||||
_closeToTaskbar = value;
|
||||
}
|
||||
[[nodiscard]] bool closeToTaskbar() const {
|
||||
return _closeToTaskbar.current();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<bool> closeToTaskbarValue() const {
|
||||
return _closeToTaskbar.value();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<bool> closeToTaskbarChanges() const {
|
||||
return _closeToTaskbar.changes();
|
||||
}
|
||||
|
||||
[[nodiscard]] static bool ThirdColumnByDefault();
|
||||
[[nodiscard]] static float64 DefaultDialogsWidthRatio();
|
||||
[[nodiscard]] static qint32 SerializePlaybackSpeed(float64 speed) {
|
||||
return int(std::round(std::clamp(speed, 0.5, 2.0) * 100));
|
||||
return int(base::SafeRound(std::clamp(speed, 0.5, 2.0) * 100));
|
||||
}
|
||||
[[nodiscard]] static float64 DeserializePlaybackSpeed(qint32 speed) {
|
||||
if (speed < 10) {
|
||||
@@ -700,6 +713,7 @@ private:
|
||||
bool _disableOpenGL = false;
|
||||
rpl::variable<WorkMode> _workMode = WorkMode::WindowAndTray;
|
||||
base::flags<Calls::Group::StickedTooltip> _hiddenGroupCallTooltips;
|
||||
rpl::variable<bool> _closeToTaskbar = false;
|
||||
|
||||
bool _tabbedReplacedWithInfo = false; // per-window
|
||||
rpl::event_stream<bool> _tabbedReplacedWithInfoValue; // per-window
|
||||
|
||||
@@ -464,6 +464,7 @@ void Launcher::processArguments() {
|
||||
{ "-noupdate" , KeyFormat::NoValues },
|
||||
{ "-tosettings" , KeyFormat::NoValues },
|
||||
{ "-startintray" , KeyFormat::NoValues },
|
||||
{ "-quit" , KeyFormat::NoValues },
|
||||
{ "-sendpath" , KeyFormat::AllLeftValues },
|
||||
{ "-workdir" , KeyFormat::OneValue },
|
||||
{ "--" , KeyFormat::OneValue },
|
||||
@@ -504,6 +505,7 @@ void Launcher::processArguments() {
|
||||
gNoStartUpdate = parseResult.contains("-noupdate");
|
||||
gStartToSettings = parseResult.contains("-tosettings");
|
||||
gStartInTray = parseResult.contains("-startintray");
|
||||
gQuit = parseResult.contains("-quit");
|
||||
gSendPaths = parseResult.value("-sendpath", {});
|
||||
gWorkingDir = parseResult.value("-workdir", {}).join(QString());
|
||||
if (!gWorkingDir.isEmpty()) {
|
||||
|
||||
@@ -35,10 +35,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "media/player/media_player_instance.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "window/themes/window_theme_editor_box.h" // GenerateSlug.
|
||||
#include "settings/settings_common.h"
|
||||
#include "mainwidget.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_session_settings.h"
|
||||
#include "history/history.h"
|
||||
#include "apiwrap.h"
|
||||
|
||||
#include <QtGui/QGuiApplication>
|
||||
@@ -307,7 +309,11 @@ bool ResolveUsername(
|
||||
}
|
||||
: Navigation::RepliesByLinkInfo{ v::null },
|
||||
.startToken = startToken,
|
||||
.voicechatHash = (params.contains(u"voicechat"_q)
|
||||
.voicechatHash = (params.contains(u"livestream"_q)
|
||||
? std::make_optional(params.value(u"livestream"_q))
|
||||
: params.contains(u"videochat"_q)
|
||||
? std::make_optional(params.value(u"videochat"_q))
|
||||
: params.contains(u"voicechat"_q)
|
||||
? std::make_optional(params.value(u"voicechat"_q))
|
||||
: std::nullopt),
|
||||
.clickFromMessageId = fromMessageId,
|
||||
@@ -464,6 +470,126 @@ bool ShowInviteLink(
|
||||
return true;
|
||||
}
|
||||
|
||||
void ExportTestChatTheme(
|
||||
not_null<Main::Session*> session,
|
||||
not_null<const Data::CloudTheme*> theme) {
|
||||
if (!theme->paper
|
||||
|| !theme->paper->isPattern()
|
||||
|| theme->paper->backgroundColors().empty()
|
||||
|| !theme->accentColor
|
||||
|| !theme->paper->hasShareUrl()) {
|
||||
Ui::Toast::Show("Something went wrong :(");
|
||||
return;
|
||||
}
|
||||
const auto &bg = theme->paper->backgroundColors();
|
||||
const auto url = theme->paper->shareUrl(session);
|
||||
const auto from = url.indexOf("bg/");
|
||||
const auto till = url.indexOf("?");
|
||||
if (from < 0 || till <= from) {
|
||||
Ui::Toast::Show("Bad WallPaper link: " + url);
|
||||
return;
|
||||
}
|
||||
|
||||
using Flag = MTPaccount_CreateTheme::Flag;
|
||||
using Setting = MTPDinputThemeSettings::Flag;
|
||||
using Paper = MTPDwallPaperSettings::Flag;
|
||||
const auto color = [](const QColor &color) {
|
||||
const auto red = color.red();
|
||||
const auto green = color.green();
|
||||
const auto blue = color.blue();
|
||||
return int(((uint32(red) & 0xFFU) << 16)
|
||||
| ((uint32(green) & 0xFFU) << 8)
|
||||
| (uint32(blue) & 0xFFU));
|
||||
};
|
||||
const auto colors = [&](const std::vector<QColor> &colors) {
|
||||
auto result = QVector<MTPint>();
|
||||
result.reserve(colors.size());
|
||||
for (const auto &single : colors) {
|
||||
result.push_back(MTP_int(color(single)));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
const auto slug = url.mid(from + 3, till - from - 3);
|
||||
const auto flags = Flag::f_settings;
|
||||
const auto settings = Setting::f_wallpaper
|
||||
| Setting::f_wallpaper_settings
|
||||
| (theme->outgoingAccentColor
|
||||
? Setting::f_outbox_accent_color
|
||||
: Setting(0))
|
||||
| (!theme->outgoingMessagesColors.empty()
|
||||
? Setting::f_message_colors
|
||||
: Setting(0));
|
||||
const auto papers = Paper::f_background_color
|
||||
| Paper::f_intensity
|
||||
| (bg.size() > 1
|
||||
? Paper::f_second_background_color
|
||||
: Paper(0))
|
||||
| (bg.size() > 2
|
||||
? Paper::f_third_background_color
|
||||
: Paper(0))
|
||||
| (bg.size() > 3
|
||||
? Paper::f_fourth_background_color
|
||||
: Paper(0));
|
||||
session->api().request(MTPaccount_CreateTheme(
|
||||
MTP_flags(flags),
|
||||
MTP_string(Window::Theme::GenerateSlug()),
|
||||
MTP_string(theme->title + " Desktop"),
|
||||
MTPInputDocument(),
|
||||
MTP_inputThemeSettings(
|
||||
MTP_flags(settings),
|
||||
(theme->basedOnDark
|
||||
? MTP_baseThemeTinted()
|
||||
: MTP_baseThemeClassic()),
|
||||
MTP_int(color(theme->accentColor.value_or(Qt::black))),
|
||||
MTP_int(color(theme->outgoingAccentColor.value_or(
|
||||
Qt::black))),
|
||||
MTP_vector<MTPint>(colors(
|
||||
theme->outgoingMessagesColors)),
|
||||
MTP_inputWallPaperSlug(MTP_string(slug)),
|
||||
MTP_wallPaperSettings(
|
||||
MTP_flags(papers),
|
||||
MTP_int(color(bg[0])),
|
||||
MTP_int(color(bg.size() > 1 ? bg[1] : Qt::black)),
|
||||
MTP_int(color(bg.size() > 2 ? bg[2] : Qt::black)),
|
||||
MTP_int(color(bg.size() > 3 ? bg[3] : Qt::black)),
|
||||
MTP_int(theme->paper->patternIntensity()),
|
||||
MTP_int(0)))
|
||||
)).done([=](const MTPTheme &result) {
|
||||
const auto slug = Data::CloudTheme::Parse(session, result, true).slug;
|
||||
QGuiApplication::clipboard()->setText(
|
||||
session->createInternalLinkFull("addtheme/" + slug));
|
||||
Ui::Toast::Show(tr::lng_background_link_copied(tr::now));
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
Ui::Toast::Show("Error: " + error.type());
|
||||
}).send();
|
||||
}
|
||||
|
||||
bool ResolveTestChatTheme(
|
||||
Window::SessionController *controller,
|
||||
const Match &match,
|
||||
const QVariant &context) {
|
||||
if (!controller) {
|
||||
return false;
|
||||
}
|
||||
const auto params = url_parse_params(
|
||||
match->captured(1),
|
||||
qthelp::UrlParamNameTransform::ToLower);
|
||||
if (const auto history = controller->activeChatCurrent().history()) {
|
||||
controller->clearCachedChatThemes();
|
||||
const auto theme = history->owner().cloudThemes().updateThemeFromLink(
|
||||
history->peer->themeEmoji(),
|
||||
params);
|
||||
if (theme) {
|
||||
if (!params["export"].isEmpty()) {
|
||||
ExportTestChatTheme(&controller->session(), &*theme);
|
||||
}
|
||||
[[maybe_unused]] auto value = controller->cachedChatThemeValue(
|
||||
*theme);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
|
||||
@@ -524,6 +650,10 @@ const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
|
||||
qsl("^settings(/folders|/devices|/language)?$"),
|
||||
ResolveSettings
|
||||
},
|
||||
{
|
||||
qsl("^test_chat_theme/?\\?(.+)(#|$)"),
|
||||
ResolveTestChatTheme,
|
||||
},
|
||||
{
|
||||
qsl("^([^\\?]+)(\\?|#|$)"),
|
||||
HandleUnknown
|
||||
|
||||
@@ -237,6 +237,8 @@ void Sandbox::socketConnected() {
|
||||
}
|
||||
if (!cStartUrl().isEmpty()) {
|
||||
commands += qsl("OPEN:") + _escapeTo7bit(cStartUrl()) + ';';
|
||||
} else if (cQuit()) {
|
||||
commands += qsl("CMD:quit;");
|
||||
} else {
|
||||
commands += qsl("CMD:show;");
|
||||
}
|
||||
@@ -305,6 +307,10 @@ void Sandbox::socketError(QLocalSocket::LocalSocketError e) {
|
||||
return App::quit();
|
||||
}
|
||||
|
||||
if (cQuit()) {
|
||||
return App::quit();
|
||||
}
|
||||
|
||||
singleInstanceChecked();
|
||||
}
|
||||
|
||||
@@ -605,6 +611,8 @@ void Sandbox::execExternal(const QString &cmd) {
|
||||
} else if (PreLaunchWindow::instance()) {
|
||||
PreLaunchWindow::instance()->activate();
|
||||
}
|
||||
} else if (cmd == "quit") {
|
||||
App::quit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -586,7 +586,7 @@ bool ParseCommonMap(
|
||||
}
|
||||
return string.toULongLong();
|
||||
} else if ((*version).isDouble()) {
|
||||
return uint64(std::round((*version).toDouble()));
|
||||
return uint64(base::SafeRound((*version).toDouble()));
|
||||
}
|
||||
return 0ULL;
|
||||
}();
|
||||
|
||||
@@ -371,11 +371,6 @@ char *hashMd5Hex(const int32 *hashmd5, void *dest) {
|
||||
return md5To;
|
||||
}
|
||||
|
||||
void memset_rand(void *data, uint32 len) {
|
||||
Assert(_sslInited);
|
||||
RAND_bytes((uchar*)data, len);
|
||||
}
|
||||
|
||||
namespace {
|
||||
QMap<QString, QString> fastRusEng;
|
||||
QHash<QChar, QString> fastLetterRusEng;
|
||||
|
||||
@@ -109,9 +109,6 @@ inline std::array<char, 32> hashMd5Hex(const void *data, int size) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// good random (using openssl implementation)
|
||||
void memset_rand(void *data, uint32 len);
|
||||
|
||||
QString translitRusEng(const QString &rus);
|
||||
QString rusKeyboardLayoutSwitch(const QString &from);
|
||||
|
||||
|
||||
@@ -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 = 3000002;
|
||||
constexpr auto AppVersionStr = "3.0.2";
|
||||
constexpr auto AppVersion = 3001005;
|
||||
constexpr auto AppVersionStr = "3.1.5";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -27,6 +27,8 @@ namespace {
|
||||
constexpr auto kFirstReloadTimeout = 10 * crl::time(1000);
|
||||
constexpr auto kReloadTimeout = 3600 * crl::time(1000);
|
||||
|
||||
bool IsTestingColors/* = false*/;
|
||||
|
||||
} // namespace
|
||||
|
||||
CloudTheme CloudTheme::Parse(
|
||||
@@ -385,30 +387,169 @@ rpl::producer<> CloudThemes::chatThemesUpdated() const {
|
||||
}
|
||||
|
||||
std::optional<ChatTheme> CloudThemes::themeForEmoji(
|
||||
const QString &emoji) const {
|
||||
if (emoji.isEmpty()) {
|
||||
const QString &emoticon) const {
|
||||
const auto emoji = Ui::Emoji::Find(emoticon);
|
||||
if (!emoji) {
|
||||
return {};
|
||||
}
|
||||
const auto i = ranges::find(_chatThemes, emoji, &ChatTheme::emoji);
|
||||
const auto i = ranges::find(_chatThemes, emoji, [](const ChatTheme &v) {
|
||||
return Ui::Emoji::Find(v.emoticon);
|
||||
});
|
||||
return (i != end(_chatThemes)) ? std::make_optional(*i) : std::nullopt;
|
||||
}
|
||||
|
||||
rpl::producer<std::optional<ChatTheme>> CloudThemes::themeForEmojiValue(
|
||||
const QString &emoji) {
|
||||
if (emoji.isEmpty()) {
|
||||
const QString &emoticon) {
|
||||
const auto testing = TestingColors();
|
||||
if (!Ui::Emoji::Find(emoticon)) {
|
||||
return rpl::single<std::optional<ChatTheme>>(std::nullopt);
|
||||
} else if (auto result = themeForEmoji(emoji)) {
|
||||
} else if (auto result = themeForEmoji(emoticon)) {
|
||||
if (testing) {
|
||||
return rpl::single(
|
||||
std::move(result)
|
||||
) | rpl::then(chatThemesUpdated(
|
||||
) | rpl::map([=] {
|
||||
return themeForEmoji(emoticon);
|
||||
}) | rpl::filter([](const std::optional<ChatTheme> &theme) {
|
||||
return theme.has_value();
|
||||
}));
|
||||
}
|
||||
return rpl::single(std::move(result));
|
||||
}
|
||||
refreshChatThemes();
|
||||
const auto limit = testing ? (1 << 20) : 1;
|
||||
return rpl::single<std::optional<ChatTheme>>(
|
||||
std::nullopt
|
||||
) | rpl::then(chatThemesUpdated(
|
||||
) | rpl::map([=] {
|
||||
return themeForEmoji(emoji);
|
||||
return themeForEmoji(emoticon);
|
||||
}) | rpl::filter([](const std::optional<ChatTheme> &theme) {
|
||||
return theme.has_value();
|
||||
}) | rpl::take(1));
|
||||
}) | rpl::take(limit));
|
||||
}
|
||||
|
||||
bool CloudThemes::TestingColors() {
|
||||
return IsTestingColors;
|
||||
}
|
||||
|
||||
void CloudThemes::SetTestingColors(bool testing) {
|
||||
IsTestingColors = testing;
|
||||
}
|
||||
|
||||
QString CloudThemes::prepareTestingLink(const CloudTheme &theme) const {
|
||||
const auto hex = [](int value) {
|
||||
return QChar((value < 10) ? ('0' + value) : ('a' + (value - 10)));
|
||||
};
|
||||
const auto hex2 = [&](int value) {
|
||||
return QString() + hex(value / 16) + hex(value % 16);
|
||||
};
|
||||
const auto color = [&](const QColor &color) {
|
||||
return hex2(color.red()) + hex2(color.green()) + hex2(color.blue());
|
||||
};
|
||||
const auto colors = [&](const std::vector<QColor> &colors) {
|
||||
auto list = QStringList();
|
||||
for (const auto &c : colors) {
|
||||
list.push_back(color(c));
|
||||
}
|
||||
return list.join(",");
|
||||
};
|
||||
auto arguments = QStringList();
|
||||
if (theme.basedOnDark) {
|
||||
arguments.push_back("dark=1");
|
||||
}
|
||||
if (theme.accentColor) {
|
||||
arguments.push_back("accent=" + color(*theme.accentColor));
|
||||
}
|
||||
if (theme.paper && !theme.paper->backgroundColors().empty()) {
|
||||
arguments.push_back("bg=" + colors(theme.paper->backgroundColors()));
|
||||
}
|
||||
if (theme.paper/* && theme.paper->hasShareUrl()*/) {
|
||||
arguments.push_back("intensity="
|
||||
+ QString::number(theme.paper->patternIntensity()));
|
||||
//const auto url = theme.paper->shareUrl(_session);
|
||||
//const auto from = url.indexOf("bg/");
|
||||
//const auto till = url.indexOf("?");
|
||||
//if (from > 0 && till > from) {
|
||||
// arguments.push_back("slug=" + url.mid(from + 3, till - from - 3));
|
||||
//}
|
||||
}
|
||||
if (theme.outgoingAccentColor) {
|
||||
arguments.push_back("out_accent" + color(*theme.outgoingAccentColor));
|
||||
}
|
||||
if (!theme.outgoingMessagesColors.empty()) {
|
||||
arguments.push_back("out_bg=" + colors(theme.outgoingMessagesColors));
|
||||
}
|
||||
return arguments.isEmpty()
|
||||
? QString()
|
||||
: ("tg://test_chat_theme?" + arguments.join("&"));
|
||||
}
|
||||
|
||||
std::optional<CloudTheme> CloudThemes::updateThemeFromLink(
|
||||
const QString &emoticon,
|
||||
const QMap<QString, QString> ¶ms) {
|
||||
const auto emoji = Ui::Emoji::Find(emoticon);
|
||||
if (!TestingColors() || !emoji) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto i = ranges::find(_chatThemes, emoji, [](const ChatTheme &v) {
|
||||
return Ui::Emoji::Find(v.emoticon);
|
||||
});
|
||||
if (i == end(_chatThemes)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto hex = [](const QString &value) {
|
||||
return (value.size() != 1)
|
||||
? std::nullopt
|
||||
: (value[0] >= 'a' && value[0] <= 'f')
|
||||
? std::make_optional(10 + int(value[0].unicode() - 'a'))
|
||||
: (value[0] >= 'A' && value[0] <= 'F')
|
||||
? std::make_optional(10 + int(value[0].unicode() - 'A'))
|
||||
: (value[0] >= '0' && value[0] <= '9')
|
||||
? std::make_optional(int(value[0].unicode() - '0'))
|
||||
: std::nullopt;
|
||||
};
|
||||
const auto hex2 = [&](const QString &value) {
|
||||
const auto first = hex(value.mid(0, 1));
|
||||
const auto second = hex(value.mid(1, 1));
|
||||
return (first && second)
|
||||
? std::make_optional((*first) * 16 + (*second))
|
||||
: std::nullopt;
|
||||
};
|
||||
const auto color = [&](const QString &value) {
|
||||
const auto red = hex2(value.mid(0, 2));
|
||||
const auto green = hex2(value.mid(2, 2));
|
||||
const auto blue = hex2(value.mid(4, 2));
|
||||
return (red && green && blue)
|
||||
? std::make_optional(QColor(*red, *green, *blue))
|
||||
: std::nullopt;
|
||||
};
|
||||
const auto colors = [&](const QString &value) {
|
||||
auto list = value.split(",");
|
||||
auto result = std::vector<QColor>();
|
||||
for (const auto &single : list) {
|
||||
if (const auto c = color(single)) {
|
||||
result.push_back(*c);
|
||||
} else {
|
||||
return std::vector<QColor>();
|
||||
}
|
||||
}
|
||||
return (result.size() > 4) ? std::vector<QColor>() : result;
|
||||
};
|
||||
|
||||
auto &applyTo = params["dark"].isEmpty() ? i->light : i->dark;
|
||||
applyTo.accentColor = color(params["accent"]);
|
||||
const auto bg = colors(params["bg"]);
|
||||
applyTo.paper = (applyTo.paper && !bg.empty())
|
||||
? std::make_optional(applyTo.paper->withBackgroundColors(bg))
|
||||
: applyTo.paper;
|
||||
applyTo.paper = (applyTo.paper && params["intensity"].toInt())
|
||||
? std::make_optional(
|
||||
applyTo.paper->withPatternIntensity(params["intensity"].toInt()))
|
||||
: applyTo.paper;
|
||||
applyTo.outgoingAccentColor = color(params["out_accent"]);
|
||||
applyTo.outgoingMessagesColors = colors(params["out_bg"]);
|
||||
_chatThemesUpdates.fire({});
|
||||
return applyTo;
|
||||
}
|
||||
|
||||
void CloudThemes::parseChatThemes(const QVector<MTPChatTheme> &list) {
|
||||
@@ -417,7 +558,7 @@ void CloudThemes::parseChatThemes(const QVector<MTPChatTheme> &list) {
|
||||
for (const auto &theme : list) {
|
||||
theme.match([&](const MTPDchatTheme &data) {
|
||||
_chatThemes.push_back({
|
||||
.emoji = qs(data.vemoticon()),
|
||||
.emoticon = qs(data.vemoticon()),
|
||||
.light = CloudTheme::Parse(_session, data.vtheme(), true),
|
||||
.dark = CloudTheme::Parse(_session, data.vdark_theme(), true),
|
||||
});
|
||||
|
||||
@@ -50,7 +50,7 @@ struct CloudTheme {
|
||||
};
|
||||
|
||||
struct ChatTheme {
|
||||
QString emoji;
|
||||
QString emoticon;
|
||||
CloudTheme light;
|
||||
CloudTheme dark;
|
||||
};
|
||||
@@ -71,9 +71,16 @@ public:
|
||||
[[nodiscard]] const std::vector<ChatTheme> &chatThemes() const;
|
||||
[[nodiscard]] rpl::producer<> chatThemesUpdated() const;
|
||||
[[nodiscard]] std::optional<ChatTheme> themeForEmoji(
|
||||
const QString &emoji) const;
|
||||
const QString &emoticon) const;
|
||||
[[nodiscard]] rpl::producer<std::optional<ChatTheme>> themeForEmojiValue(
|
||||
const QString &emoji);
|
||||
const QString &emoticon);
|
||||
|
||||
[[nodiscard]] static bool TestingColors();
|
||||
static void SetTestingColors(bool testing);
|
||||
[[nodiscard]] QString prepareTestingLink(const CloudTheme &theme) const;
|
||||
[[nodiscard]] std::optional<CloudTheme> updateThemeFromLink(
|
||||
const QString &emoticon,
|
||||
const QMap<QString, QString> ¶ms);
|
||||
|
||||
void applyUpdate(const MTPTheme &theme);
|
||||
|
||||
|
||||
@@ -1004,19 +1004,24 @@ PeerId PeerData::groupCallDefaultJoinAs() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PeerData::setThemeEmoji(const QString &emoji) {
|
||||
if (_themeEmoji == emoji) {
|
||||
void PeerData::setThemeEmoji(const QString &emoticon) {
|
||||
if (_themeEmoticon == emoticon) {
|
||||
return;
|
||||
}
|
||||
_themeEmoji = emoji;
|
||||
if (!emoji.isEmpty() && !owner().cloudThemes().themeForEmoji(emoji)) {
|
||||
if (Ui::Emoji::Find(_themeEmoticon) == Ui::Emoji::Find(emoticon)) {
|
||||
_themeEmoticon = emoticon;
|
||||
return;
|
||||
}
|
||||
_themeEmoticon = emoticon;
|
||||
if (!emoticon.isEmpty()
|
||||
&& !owner().cloudThemes().themeForEmoji(emoticon)) {
|
||||
owner().cloudThemes().refreshChatThemes();
|
||||
}
|
||||
session().changes().peerUpdated(this, UpdateFlag::ChatThemeEmoji);
|
||||
}
|
||||
|
||||
const QString &PeerData::themeEmoji() const {
|
||||
return _themeEmoji;
|
||||
return _themeEmoticon;
|
||||
}
|
||||
|
||||
void PeerData::setIsBlocked(bool is) {
|
||||
|
||||
@@ -459,7 +459,7 @@ public:
|
||||
[[nodiscard]] Data::GroupCall *groupCall() const;
|
||||
[[nodiscard]] PeerId groupCallDefaultJoinAs() const;
|
||||
|
||||
void setThemeEmoji(const QString &emoji);
|
||||
void setThemeEmoji(const QString &emoticon);
|
||||
[[nodiscard]] const QString &themeEmoji() const;
|
||||
|
||||
const PeerId id;
|
||||
@@ -506,7 +506,7 @@ private:
|
||||
LoadedStatus _loadedStatus = LoadedStatus::Not;
|
||||
|
||||
QString _about;
|
||||
QString _themeEmoji;
|
||||
QString _themeEmoticon;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "base/call_delayed.h"
|
||||
#include "base/openssl_help.h"
|
||||
#include "base/random.h"
|
||||
#include "facades.h" // Notify::switchInlineBotButtonReceived
|
||||
#include "app.h"
|
||||
#include "styles/style_boxes.h" // st::backgroundSize
|
||||
@@ -2427,7 +2427,7 @@ PhotoData *Session::photoFromWeb(
|
||||
return nullptr;
|
||||
}
|
||||
return photo(
|
||||
openssl::RandomValue<PhotoId>(),
|
||||
base::RandomValue<PhotoId>(),
|
||||
uint64(0),
|
||||
QByteArray(),
|
||||
base::unixtime::now(),
|
||||
@@ -2692,7 +2692,7 @@ DocumentData *Session::documentFromWeb(
|
||||
const ImageLocation &thumbnailLocation,
|
||||
const ImageLocation &videoThumbnailLocation) {
|
||||
const auto result = document(
|
||||
openssl::RandomValue<DocumentId>(),
|
||||
base::RandomValue<DocumentId>(),
|
||||
uint64(0),
|
||||
QByteArray(),
|
||||
base::unixtime::now(),
|
||||
@@ -2714,7 +2714,7 @@ DocumentData *Session::documentFromWeb(
|
||||
const ImageLocation &thumbnailLocation,
|
||||
const ImageLocation &videoThumbnailLocation) {
|
||||
const auto result = document(
|
||||
openssl::RandomValue<DocumentId>(),
|
||||
base::RandomValue<DocumentId>(),
|
||||
uint64(0),
|
||||
QByteArray(),
|
||||
base::unixtime::now(),
|
||||
|
||||
@@ -80,8 +80,9 @@ Storage::Cache::Key GeoPointCacheKey(const GeoPointLocation &location) {
|
||||
| (uint32(location.height) & 0xFFFFU);
|
||||
return Storage::Cache::Key{
|
||||
Data::kGeoPointCacheTag | (uint64(zoomscale) << 32) | widthheight,
|
||||
(uint64(std::round(std::abs(location.lat + 360.) * 1000000)) << 32)
|
||||
| uint64(std::round(std::abs(location.lon + 360.) * 1000000))
|
||||
(uint64(base::SafeRound(
|
||||
std::abs(location.lat + 360.) * 1000000)) << 32)
|
||||
| uint64(base::SafeRound(std::abs(location.lon + 360.) * 1000000))
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -341,6 +341,9 @@ enum class MessageFlag : uint32 {
|
||||
|
||||
// Fake message for some UI element.
|
||||
FakeHistoryItem = (1U << 27),
|
||||
|
||||
// Contact sign-up message, notification should be skipped for Silent.
|
||||
IsContactSignUp = (1U << 28),
|
||||
};
|
||||
inline constexpr bool is_flag_type(MessageFlag) { return true; }
|
||||
using MessageFlags = base::flags<MessageFlag>;
|
||||
|
||||
@@ -24,7 +24,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "mtproto/mtproto_config.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/image/image_location_factory.h"
|
||||
#include "base/openssl_help.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace Editor {
|
||||
|
||||
QImage ImageModified(QImage image, const PhotoModifications &mods) {
|
||||
Expects(!image.isNull());
|
||||
|
||||
if (!mods) {
|
||||
return image;
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ void ItemBase::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
|
||||
const auto angle = Normalized((isLeft ? 180 : 0)
|
||||
+ (std::atan2(diff.y(), diff.x()) * 180 / M_PI));
|
||||
setRotation(shift
|
||||
? (std::round(angle / kSnapAngle) * kSnapAngle) // Snap rotation.
|
||||
? (base::SafeRound(angle / kSnapAngle) * kSnapAngle)
|
||||
: angle);
|
||||
} else {
|
||||
QGraphicsItem::mouseMoveEvent(event);
|
||||
|
||||
@@ -14,7 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "mtproto/mtproto_response.h"
|
||||
#include "base/value_ordering.h"
|
||||
#include "base/bytes.h"
|
||||
#include "base/openssl_help.h"
|
||||
#include "base/random.h"
|
||||
#include <set>
|
||||
#include <deque>
|
||||
|
||||
@@ -1748,7 +1748,7 @@ auto ApiWrap::prepareFileProcess(
|
||||
result->location = file.location;
|
||||
result->size = file.size;
|
||||
result->origin = origin;
|
||||
result->randomId = openssl::RandomValue<uint64>();
|
||||
result->randomId = base::RandomValue<uint64>();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -674,6 +674,9 @@ not_null<Ui::PathShiftGradient*> InnerWidget::elementPathShiftGradient() {
|
||||
void InnerWidget::elementReplyTo(const FullMsgId &to) {
|
||||
}
|
||||
|
||||
void InnerWidget::elementStartInteraction(not_null<const Element*> view) {
|
||||
}
|
||||
|
||||
void InnerWidget::saveState(not_null<SectionMemento*> memento) {
|
||||
memento->setFilter(std::move(_filter));
|
||||
memento->setAdmins(std::move(_admins));
|
||||
|
||||
@@ -137,6 +137,8 @@ public:
|
||||
bool elementIsChatWide() override;
|
||||
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
|
||||
void elementReplyTo(const FullMsgId &to) override;
|
||||
void elementStartInteraction(
|
||||
not_null<const HistoryView::Element*> view) override;
|
||||
|
||||
~InnerWidget();
|
||||
|
||||
|
||||
@@ -1076,12 +1076,6 @@ void GenerateItems(
|
||||
addSimpleServiceMessage(text);
|
||||
};
|
||||
|
||||
auto createChangeTheme = [&](const MTPDchannelAdminLogEventActionChangeTheme &data) {
|
||||
const auto was = qs(data.vprev_value());
|
||||
const auto now = qs(data.vnew_value());
|
||||
// #TODO themes
|
||||
};
|
||||
|
||||
action.match([&](const MTPDchannelAdminLogEventActionChangeTitle &data) {
|
||||
createChangeTitle(data);
|
||||
}, [&](const MTPDchannelAdminLogEventActionChangeAbout &data) {
|
||||
@@ -1146,8 +1140,6 @@ void GenerateItems(
|
||||
createParticipantVolume(data);
|
||||
}, [&](const MTPDchannelAdminLogEventActionChangeHistoryTTL &data) {
|
||||
createChangeHistoryTTL(data);
|
||||
}, [&](const MTPDchannelAdminLogEventActionChangeTheme &data) {
|
||||
createChangeTheme(data);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -487,6 +487,19 @@ not_null<HistoryItem*> History::addNewItem(
|
||||
} else if (!item->isHistoryEntry()) {
|
||||
return item;
|
||||
}
|
||||
|
||||
// In case we've loaded a new 'last' message
|
||||
// and it is not in blocks and we think that
|
||||
// we have all the messages till the bottom
|
||||
// we should unload known history or mark
|
||||
// currently loaded slice as not reaching bottom.
|
||||
const auto shouldMarkBottomNotLoaded = loadedAtBottom()
|
||||
&& !unread
|
||||
&& !isEmpty();
|
||||
if (shouldMarkBottomNotLoaded) {
|
||||
setNotLoadedAtBottom();
|
||||
}
|
||||
|
||||
if (!loadedAtBottom() || peer->migrateTo()) {
|
||||
setLastMessage(item);
|
||||
if (unread) {
|
||||
|
||||
@@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "history/history_inner_widget.h"
|
||||
|
||||
#include <rpl/merge.h>
|
||||
#include "core/file_utilities.h"
|
||||
#include "core/crash_reports.h"
|
||||
#include "core/click_handler_types.h"
|
||||
@@ -22,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/view/history_view_service_message.h"
|
||||
#include "history/view/history_view_cursor_state.h"
|
||||
#include "history/view/history_view_context_menu.h"
|
||||
#include "history/view/history_view_emoji_interactions.h"
|
||||
#include "ui/chat/chat_theme.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
@@ -44,6 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "boxes/sticker_set_box.h"
|
||||
#include "chat_helpers/message_field.h"
|
||||
#include "chat_helpers/emoji_interactions.h"
|
||||
#include "history/history_widget.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/unixtime.h"
|
||||
@@ -160,6 +161,8 @@ HistoryInner::HistoryInner(
|
||||
, _controller(controller)
|
||||
, _peer(history->peer)
|
||||
, _history(history)
|
||||
, _emojiInteractions(std::make_unique<HistoryView::EmojiInteractions>(
|
||||
&controller->session()))
|
||||
, _migrated(history->migrateFrom())
|
||||
, _pathGradient(
|
||||
HistoryView::MakePathShiftGradient(
|
||||
@@ -195,6 +198,26 @@ HistoryInner::HistoryInner(
|
||||
update();
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
using PlayRequest = ChatHelpers::EmojiInteractionPlayRequest;
|
||||
_controller->emojiInteractions().playRequests(
|
||||
) | rpl::filter([=](const PlayRequest &request) {
|
||||
return (request.item->history() == _history)
|
||||
&& _controller->widget()->isActive();
|
||||
}) | rpl::start_with_next([=](PlayRequest &&request) {
|
||||
if (const auto view = request.item->mainView()) {
|
||||
_emojiInteractions->play(std::move(request), view);
|
||||
}
|
||||
}, lifetime());
|
||||
_emojiInteractions->updateRequests(
|
||||
) | rpl::start_with_next([=](QRect rect) {
|
||||
update(rect.translated(0, _historyPaddingTop));
|
||||
}, lifetime());
|
||||
_emojiInteractions->playStarted(
|
||||
) | rpl::start_with_next([=](QString &&emoji) {
|
||||
_controller->emojiInteractions().playStarted(_peer, std::move(emoji));
|
||||
}, lifetime());
|
||||
|
||||
session().data().itemRemoved(
|
||||
) | rpl::start_with_next(
|
||||
[this](auto item) { itemRemoved(item); },
|
||||
@@ -591,8 +614,8 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
||||
|
||||
const auto historyDisplayedEmpty = _history->isDisplayedEmpty()
|
||||
&& (!_migrated || _migrated->isDisplayedEmpty());
|
||||
bool noHistoryDisplayed = _firstLoading || historyDisplayedEmpty;
|
||||
if (!_firstLoading && _botAbout && !_botAbout->info->text.isEmpty() && _botAbout->height > 0) {
|
||||
bool noHistoryDisplayed = historyDisplayedEmpty;
|
||||
if (_botAbout && !_botAbout->info->text.isEmpty() && _botAbout->height > 0) {
|
||||
const auto st = context.st;
|
||||
const auto stm = &st->messageStyle(false, false);
|
||||
if (clip.y() < _botAbout->rect.y() + _botAbout->rect.height() && clip.y() + clip.height() > _botAbout->rect.y()) {
|
||||
@@ -834,6 +857,9 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
||||
}
|
||||
return true;
|
||||
});
|
||||
p.setOpacity(1.);
|
||||
p.translate(0, _historyPaddingTop);
|
||||
_emojiInteractions->paint(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2302,11 +2328,6 @@ bool HistoryInner::wasSelectedText() const {
|
||||
return _wasSelectedText;
|
||||
}
|
||||
|
||||
void HistoryInner::setFirstLoading(bool loading) {
|
||||
_firstLoading = loading;
|
||||
update();
|
||||
}
|
||||
|
||||
void HistoryInner::visibleAreaUpdated(int top, int bottom) {
|
||||
auto scrolledUp = (top < _visibleAreaTop);
|
||||
_visibleAreaTop = top;
|
||||
@@ -2357,6 +2378,10 @@ void HistoryInner::visibleAreaUpdated(int top, int bottom) {
|
||||
const auto till = _visibleAreaBottom + pages * visibleAreaHeight;
|
||||
session().data().unloadHeavyViewParts(ElementDelegate(), from, till);
|
||||
checkHistoryActivation();
|
||||
|
||||
_emojiInteractions->visibleAreaUpdated(
|
||||
_visibleAreaTop - _historyPaddingTop,
|
||||
_visibleAreaBottom - _historyPaddingTop);
|
||||
}
|
||||
|
||||
bool HistoryInner::displayScrollDate() const {
|
||||
@@ -2454,7 +2479,12 @@ void HistoryInner::updateSize() {
|
||||
_botAbout->rect = QRect(descAtX, descAtY, _botAbout->width + st::msgPadding.left() + st::msgPadding.right(), descH - st::msgMargin.top() - st::msgMargin.bottom());
|
||||
}
|
||||
|
||||
_historyPaddingTop = newHistoryPaddingTop;
|
||||
if (_historyPaddingTop != newHistoryPaddingTop) {
|
||||
_historyPaddingTop = newHistoryPaddingTop;
|
||||
_emojiInteractions->visibleAreaUpdated(
|
||||
_visibleAreaTop - _historyPaddingTop,
|
||||
_visibleAreaBottom - _historyPaddingTop);
|
||||
}
|
||||
|
||||
int newHeight = _historyPaddingTop + itemsHeight + st::historyPaddingBottom;
|
||||
if (width() != _scroll->width() || height() != newHeight) {
|
||||
@@ -2689,6 +2719,10 @@ void HistoryInner::elementReplyTo(const FullMsgId &to) {
|
||||
return _widget->replyToMessage(to);
|
||||
}
|
||||
|
||||
void HistoryInner::elementStartInteraction(not_null<const Element*> view) {
|
||||
_controller->emojiInteractions().startOutgoing(view);
|
||||
}
|
||||
|
||||
auto HistoryInner::getSelectionState() const
|
||||
-> HistoryView::TopBarWidget::SelectedState {
|
||||
auto result = HistoryView::TopBarWidget::SelectedState {};
|
||||
@@ -3601,6 +3635,11 @@ not_null<HistoryView::ElementDelegate*> HistoryInner::ElementDelegate() {
|
||||
Instance->elementReplyTo(to);
|
||||
}
|
||||
}
|
||||
void elementStartInteraction(not_null<const Element*> view) override {
|
||||
if (Instance) {
|
||||
Instance->elementStartInteraction(view);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static Result result;
|
||||
|
||||
@@ -21,6 +21,7 @@ class CloudImageView;
|
||||
|
||||
namespace HistoryView {
|
||||
class ElementDelegate;
|
||||
class EmojiInteractions;
|
||||
struct TextState;
|
||||
struct StateRequest;
|
||||
enum class CursorState : char;
|
||||
@@ -115,11 +116,11 @@ public:
|
||||
bool elementIsChatWide();
|
||||
not_null<Ui::PathShiftGradient*> elementPathShiftGradient();
|
||||
void elementReplyTo(const FullMsgId &to);
|
||||
void elementStartInteraction(not_null<const Element*> view);
|
||||
|
||||
void updateBotInfo(bool recount = true);
|
||||
|
||||
bool wasSelectedText() const;
|
||||
void setFirstLoading(bool loading);
|
||||
|
||||
// updates history->scrollTopItem/scrollTopOffset
|
||||
void visibleAreaUpdated(int top, int bottom);
|
||||
@@ -353,6 +354,7 @@ private:
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
const not_null<PeerData*> _peer;
|
||||
const not_null<History*> _history;
|
||||
const std::unique_ptr<HistoryView::EmojiInteractions> _emojiInteractions;
|
||||
std::shared_ptr<Ui::ChatTheme> _theme;
|
||||
|
||||
History *_migrated = nullptr;
|
||||
@@ -375,8 +377,6 @@ private:
|
||||
mutable int _curBlock = 0;
|
||||
mutable int _curItem = 0;
|
||||
|
||||
bool _firstLoading = false;
|
||||
|
||||
style::cursor _cursor = style::cur_default;
|
||||
SelectedItems _selected;
|
||||
std::optional<Ui::ReportReason> _chooseForReportReason;
|
||||
|
||||
@@ -459,6 +459,17 @@ bool HistoryItem::isScheduled() const {
|
||||
&& (_flags & MessageFlag::IsOrWasScheduled);
|
||||
}
|
||||
|
||||
bool HistoryItem::skipNotification() const {
|
||||
if (isSilent() && (_flags & MessageFlag::IsContactSignUp)) {
|
||||
return true;
|
||||
} else if (const auto forwarded = Get<HistoryMessageForwarded>()) {
|
||||
if (forwarded->imported) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void HistoryItem::destroy() {
|
||||
_history->destroyMessage(this);
|
||||
}
|
||||
|
||||
@@ -109,6 +109,7 @@ public:
|
||||
[[nodiscard]] bool isAdminLogEntry() const;
|
||||
[[nodiscard]] bool isFromScheduled() const;
|
||||
[[nodiscard]] bool isScheduled() const;
|
||||
[[nodiscard]] bool skipNotification() const;
|
||||
|
||||
void addLogEntryOriginal(
|
||||
WebPageId localId,
|
||||
|
||||
@@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "history/history_message.h"
|
||||
|
||||
#include "base/openssl_help.h"
|
||||
#include "base/random.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "mainwidget.h"
|
||||
@@ -279,10 +279,10 @@ void FastShareMessage(not_null<HistoryItem*> item) {
|
||||
for (const auto &fullId : data->msgIds) {
|
||||
msgIds.push_back(MTP_int(fullId.msg));
|
||||
}
|
||||
auto generateRandom = [&] {
|
||||
const auto generateRandom = [&] {
|
||||
auto result = QVector<MTPlong>(data->msgIds.size());
|
||||
for (auto &value : result) {
|
||||
value = openssl::RandomValue<MTPlong>();
|
||||
value = base::RandomValue<MTPlong>();
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -554,6 +554,8 @@ void HistoryService::applyAction(const MTPMessageAction &action) {
|
||||
_flags |= MessageFlag::IsGroupEssential;
|
||||
}, [&](const MTPDmessageActionChannelMigrateFrom &) {
|
||||
_flags |= MessageFlag::IsGroupEssential;
|
||||
}, [&](const MTPDmessageActionContactSignUp &) {
|
||||
_flags |= MessageFlag::IsContactSignUp;
|
||||
}, [](const auto &) {
|
||||
});
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/special_buttons.h"
|
||||
#include "ui/emoji_config.h"
|
||||
#include "ui/chat/attach/attach_prepare.h"
|
||||
#include "ui/chat/choose_theme_controller.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/inner_dropdown.h"
|
||||
#include "ui/widgets/dropdown_menu.h"
|
||||
@@ -632,6 +633,7 @@ HistoryWidget::HistoryWidget(
|
||||
| PeerUpdateFlag::Slowmode
|
||||
| PeerUpdateFlag::BotStartToken
|
||||
| PeerUpdateFlag::MessagesTTL
|
||||
| PeerUpdateFlag::ChatThemeEmoji
|
||||
) | rpl::filter([=](const Data::PeerUpdate &update) {
|
||||
return (update.peer.get() == _peer);
|
||||
}) | rpl::map([](const Data::PeerUpdate &update) {
|
||||
@@ -675,6 +677,32 @@ HistoryWidget::HistoryWidget(
|
||||
if (flags & PeerUpdateFlag::MessagesTTL) {
|
||||
checkMessagesTTL();
|
||||
}
|
||||
if ((flags & PeerUpdateFlag::ChatThemeEmoji) && _list) {
|
||||
const auto emoji = _peer->themeEmoji();
|
||||
if (Data::CloudThemes::TestingColors() && !emoji.isEmpty()) {
|
||||
_peer->owner().cloudThemes().themeForEmojiValue(
|
||||
emoji
|
||||
) | rpl::filter_optional(
|
||||
) | rpl::take(
|
||||
1
|
||||
) | rpl::start_with_next([=](const Data::ChatTheme &theme) {
|
||||
auto text = QStringList();
|
||||
const auto push = [&](QString label, const auto &theme) {
|
||||
using namespace Data;
|
||||
const auto &themes = _peer->owner().cloudThemes();
|
||||
const auto l = themes.prepareTestingLink(theme);
|
||||
if (!l.isEmpty()) {
|
||||
text.push_back(label + ": " + l);
|
||||
}
|
||||
};
|
||||
push("Light", theme.light);
|
||||
push("Dark", theme.dark);
|
||||
if (!text.isEmpty()) {
|
||||
_field->setText(text.join("\n\n"));
|
||||
}
|
||||
}, _list->lifetime());
|
||||
}
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
rpl::merge(
|
||||
@@ -1255,11 +1283,39 @@ void HistoryWidget::insertHashtagOrBotCommand(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
InlineBotQuery HistoryWidget::parseInlineBotQuery() const {
|
||||
return (isChoosingTheme() || _editMsgId)
|
||||
? InlineBotQuery()
|
||||
: ParseInlineBotQuery(&session(), _field);
|
||||
}
|
||||
|
||||
AutocompleteQuery HistoryWidget::parseMentionHashtagBotCommandQuery() const {
|
||||
const auto result = (isChoosingTheme()
|
||||
|| (_inlineBot && !_inlineLookingUpBot))
|
||||
? AutocompleteQuery()
|
||||
: ParseMentionHashtagBotCommandQuery(_field);
|
||||
if (result.query.isEmpty()) {
|
||||
return result;
|
||||
} else if (result.query[0] == '#'
|
||||
&& cRecentWriteHashtags().isEmpty()
|
||||
&& cRecentSearchHashtags().isEmpty()) {
|
||||
session().local().readRecentHashtagsAndBots();
|
||||
} else if (result.query[0] == '@'
|
||||
&& cRecentInlineBots().isEmpty()) {
|
||||
session().local().readRecentHashtagsAndBots();
|
||||
} else if (result.query[0] == '/'
|
||||
&& ((_peer->isUser() && !_peer->asUser()->isBot()) || _editMsgId)) {
|
||||
return AutocompleteQuery();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void HistoryWidget::updateInlineBotQuery() {
|
||||
if (!_history) {
|
||||
return;
|
||||
}
|
||||
const auto query = ParseInlineBotQuery(&session(), _field);
|
||||
const auto query = parseInlineBotQuery();
|
||||
if (_inlineBotUsername != query.username) {
|
||||
_inlineBotUsername = query.username;
|
||||
if (_inlineBotResolveRequestId) {
|
||||
@@ -1342,10 +1398,11 @@ void HistoryWidget::orderWidgets() {
|
||||
if (_groupCallBar) {
|
||||
_groupCallBar->raise();
|
||||
}
|
||||
_topShadow->raise();
|
||||
if (_fieldAutocomplete) {
|
||||
_fieldAutocomplete->raise();
|
||||
if (_chooseTheme) {
|
||||
_chooseTheme->raise();
|
||||
}
|
||||
_topShadow->raise();
|
||||
_fieldAutocomplete->raise();
|
||||
if (_membersDropdown) {
|
||||
_membersDropdown->raise();
|
||||
}
|
||||
@@ -1383,16 +1440,48 @@ bool HistoryWidget::updateStickersByEmoji() {
|
||||
return (emoji != nullptr);
|
||||
}
|
||||
|
||||
void HistoryWidget::toggleChooseChatTheme(not_null<PeerData*> peer) {
|
||||
const auto update = [=] {
|
||||
updateInlineBotQuery();
|
||||
updateControlsGeometry();
|
||||
updateControlsVisibility();
|
||||
};
|
||||
if (peer.get() != _peer) {
|
||||
return;
|
||||
} else if (_chooseTheme) {
|
||||
if (isChoosingTheme()) {
|
||||
const auto was = base::take(_chooseTheme);
|
||||
if (Ui::InFocusChain(this)) {
|
||||
setInnerFocus();
|
||||
}
|
||||
update();
|
||||
}
|
||||
return;
|
||||
} else if (_voiceRecordBar->isActive()) {
|
||||
Ui::ShowMultilineToast({
|
||||
.text = { tr::lng_chat_theme_cant_voice(tr::now) },
|
||||
});
|
||||
return;
|
||||
}
|
||||
_chooseTheme = std::make_unique<Ui::ChooseThemeController>(
|
||||
this,
|
||||
controller(),
|
||||
peer);
|
||||
_chooseTheme->shouldBeShownValue(
|
||||
) | rpl::start_with_next(update, _chooseTheme->lifetime());
|
||||
}
|
||||
|
||||
void HistoryWidget::fieldChanged() {
|
||||
const auto typing = (_history
|
||||
&& !_inlineBot
|
||||
&& !_editMsgId
|
||||
&& (_textUpdateEvents & TextUpdateEvent::SendTyping));
|
||||
const auto updateTyping = (_textUpdateEvents & TextUpdateEvent::SendTyping);
|
||||
|
||||
InvokeQueued(this, [=] {
|
||||
updateInlineBotQuery();
|
||||
const auto choosingSticker = updateStickersByEmoji();
|
||||
if (!choosingSticker && typing) {
|
||||
if (_history
|
||||
&& !_inlineBot
|
||||
&& !_editMsgId
|
||||
&& !choosingSticker
|
||||
&& updateTyping) {
|
||||
session().sendProgressManager().update(
|
||||
_history,
|
||||
Api::SendProgressType::Typing);
|
||||
@@ -1528,7 +1617,9 @@ void HistoryWidget::setInnerFocus() {
|
||||
if (_scroll->isHidden()) {
|
||||
setFocus();
|
||||
} else if (_list) {
|
||||
if (_nonEmptySelection
|
||||
if (_chooseTheme && _chooseTheme->shouldBeShown()) {
|
||||
_chooseTheme->setFocus();
|
||||
} else if (_nonEmptySelection
|
||||
|| (_list && _list->wasSelectedText())
|
||||
|| isRecording()
|
||||
|| isBotStart()
|
||||
@@ -1897,6 +1988,7 @@ void HistoryWidget::showHistory(
|
||||
_pinnedTracker = nullptr;
|
||||
_groupCallBar = nullptr;
|
||||
_groupCallTracker = nullptr;
|
||||
_chooseTheme = nullptr;
|
||||
_membersDropdown.destroy();
|
||||
_scrollToAnimation.stop();
|
||||
|
||||
@@ -2294,52 +2386,42 @@ void HistoryWidget::updateControlsVisibility() {
|
||||
if (_contactStatus) {
|
||||
_contactStatus->show();
|
||||
}
|
||||
if (!editingMessage() && (isBlocked() || isJoinChannel() || isMuteUnmute() || isBotStart() || isReportMessages())) {
|
||||
if (isReportMessages()) {
|
||||
_unblock->hide();
|
||||
_joinChannel->hide();
|
||||
_muteUnmute->hide();
|
||||
_botStart->hide();
|
||||
if (_reportMessages->isHidden()) {
|
||||
_reportMessages->clearState();
|
||||
_reportMessages->show();
|
||||
}
|
||||
if (isChoosingTheme()
|
||||
|| (!editingMessage()
|
||||
&& (isBlocked()
|
||||
|| isJoinChannel()
|
||||
|| isMuteUnmute()
|
||||
|| isBotStart()
|
||||
|| isReportMessages()))) {
|
||||
const auto toggle = [&](Ui::FlatButton *shown) {
|
||||
const auto toggleOne = [&](not_null<Ui::FlatButton*> button) {
|
||||
if (button.get() != shown) {
|
||||
button->hide();
|
||||
} else if (button->isHidden()) {
|
||||
button->clearState();
|
||||
button->show();
|
||||
}
|
||||
};
|
||||
toggleOne(_reportMessages);
|
||||
toggleOne(_joinChannel);
|
||||
toggleOne(_muteUnmute);
|
||||
toggleOne(_botStart);
|
||||
toggleOne(_unblock);
|
||||
};
|
||||
if (isChoosingTheme()) {
|
||||
_chooseTheme->show();
|
||||
setInnerFocus();
|
||||
toggle(nullptr);
|
||||
} else if (isReportMessages()) {
|
||||
toggle(_reportMessages);
|
||||
} else if (isBlocked()) {
|
||||
_reportMessages->hide();
|
||||
_joinChannel->hide();
|
||||
_muteUnmute->hide();
|
||||
_botStart->hide();
|
||||
if (_unblock->isHidden()) {
|
||||
_unblock->clearState();
|
||||
_unblock->show();
|
||||
}
|
||||
toggle(_unblock);
|
||||
} else if (isJoinChannel()) {
|
||||
_reportMessages->hide();
|
||||
_unblock->hide();
|
||||
_muteUnmute->hide();
|
||||
_botStart->hide();
|
||||
if (_joinChannel->isHidden()) {
|
||||
_joinChannel->clearState();
|
||||
_joinChannel->show();
|
||||
}
|
||||
toggle(_joinChannel);
|
||||
} else if (isMuteUnmute()) {
|
||||
_reportMessages->hide();
|
||||
_unblock->hide();
|
||||
_joinChannel->hide();
|
||||
_botStart->hide();
|
||||
if (_muteUnmute->isHidden()) {
|
||||
_muteUnmute->clearState();
|
||||
_muteUnmute->show();
|
||||
}
|
||||
toggle(_muteUnmute);
|
||||
} else if (isBotStart()) {
|
||||
_reportMessages->hide();
|
||||
_unblock->hide();
|
||||
_joinChannel->hide();
|
||||
_muteUnmute->hide();
|
||||
if (_botStart->isHidden()) {
|
||||
_botStart->clearState();
|
||||
_botStart->show();
|
||||
}
|
||||
toggle(_botStart);
|
||||
}
|
||||
_kbShown = false;
|
||||
_fieldAutocomplete->hide();
|
||||
@@ -3261,6 +3343,9 @@ void HistoryWidget::hideChildWidgets() {
|
||||
if (_voiceRecordBar) {
|
||||
_voiceRecordBar->hideFast();
|
||||
}
|
||||
if (_chooseTheme) {
|
||||
_chooseTheme->hide();
|
||||
}
|
||||
hideChildren();
|
||||
}
|
||||
|
||||
@@ -3880,7 +3965,7 @@ void HistoryWidget::inlineBotResolveDone(
|
||||
}();
|
||||
session().data().processChats(data.vchats());
|
||||
|
||||
const auto query = ParseInlineBotQuery(&session(), _field);
|
||||
const auto query = parseInlineBotQuery();
|
||||
if (_inlineBotUsername == query.username) {
|
||||
applyInlineBotQuery(
|
||||
query.lookingUpBot ? resolvedBot : query.bot,
|
||||
@@ -3925,6 +4010,10 @@ bool HistoryWidget::isJoinChannel() const {
|
||||
return _peer && _peer->isChannel() && !_peer->asChannel()->amIn();
|
||||
}
|
||||
|
||||
bool HistoryWidget::isChoosingTheme() const {
|
||||
return _chooseTheme && _chooseTheme->shouldBeShown();
|
||||
}
|
||||
|
||||
bool HistoryWidget::isMuteUnmute() const {
|
||||
return _peer
|
||||
&& ((_peer->isBroadcast() && !_peer->asChannel()->canPublish())
|
||||
@@ -4309,24 +4398,7 @@ void HistoryWidget::checkFieldAutocomplete() {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto isInlineBot = _inlineBot && !_inlineLookingUpBot;
|
||||
const auto autocomplete = isInlineBot
|
||||
? AutocompleteQuery()
|
||||
: ParseMentionHashtagBotCommandQuery(_field);
|
||||
if (!autocomplete.query.isEmpty()) {
|
||||
if (autocomplete.query[0] == '#'
|
||||
&& cRecentWriteHashtags().isEmpty()
|
||||
&& cRecentSearchHashtags().isEmpty()) {
|
||||
session().local().readRecentHashtagsAndBots();
|
||||
} else if (autocomplete.query[0] == '@'
|
||||
&& cRecentInlineBots().isEmpty()) {
|
||||
session().local().readRecentHashtagsAndBots();
|
||||
} else if (autocomplete.query[0] == '/'
|
||||
&& ((_peer->isUser() && !_peer->asUser()->isBot())
|
||||
|| _editMsgId)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const auto autocomplete = parseMentionHashtagBotCommandQuery();
|
||||
_fieldAutocomplete->showFiltered(
|
||||
_peer,
|
||||
autocomplete.query,
|
||||
@@ -4894,7 +4966,14 @@ void HistoryWidget::updateHistoryGeometry(
|
||||
if (_contactStatus) {
|
||||
newScrollHeight -= _contactStatus->height();
|
||||
}
|
||||
if (!editingMessage() && (isBlocked() || isBotStart() || isJoinChannel() || isMuteUnmute() || isReportMessages())) {
|
||||
if (isChoosingTheme()) {
|
||||
newScrollHeight -= _chooseTheme->height();
|
||||
} else if (!editingMessage()
|
||||
&& (isBlocked()
|
||||
|| isBotStart()
|
||||
|| isJoinChannel()
|
||||
|| isMuteUnmute()
|
||||
|| isReportMessages())) {
|
||||
newScrollHeight -= _unblock->height();
|
||||
} else {
|
||||
if (editingMessage() || _canSendMessages) {
|
||||
@@ -6108,16 +6187,17 @@ void HistoryWidget::editMessage(FullMsgId itemId) {
|
||||
}
|
||||
|
||||
void HistoryWidget::editMessage(not_null<HistoryItem*> item) {
|
||||
if (_voiceRecordBar->isActive()) {
|
||||
controller()->show(
|
||||
Box<InformBox>(tr::lng_edit_caption_voice(tr::now)));
|
||||
return;
|
||||
}
|
||||
if (const auto media = item->media()) {
|
||||
if (media->allowsEditCaption()) {
|
||||
controller()->show(Box<EditCaptionBox>(controller(), item));
|
||||
return;
|
||||
}
|
||||
} else if (_chooseTheme) {
|
||||
toggleChooseChatTheme(_peer);
|
||||
} else if (_voiceRecordBar->isActive()) {
|
||||
controller()->show(
|
||||
Box<InformBox>(tr::lng_edit_caption_voice(tr::now)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (isRecording()) {
|
||||
|
||||
@@ -25,6 +25,8 @@ struct FileLoadResult;
|
||||
struct SendingAlbum;
|
||||
enum class SendMediaType;
|
||||
class MessageLinksParser;
|
||||
struct InlineBotQuery;
|
||||
struct AutocompleteQuery;
|
||||
|
||||
namespace MTP {
|
||||
class Error;
|
||||
@@ -77,6 +79,7 @@ enum class ReportReason;
|
||||
namespace Toast {
|
||||
class Instance;
|
||||
} // namespace Toast
|
||||
class ChooseThemeController;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Window {
|
||||
@@ -237,6 +240,8 @@ public:
|
||||
void clearDelayedShowAt();
|
||||
void saveFieldToHistoryLocalDraft();
|
||||
|
||||
void toggleChooseChatTheme(not_null<PeerData*> peer);
|
||||
|
||||
void applyCloudDraft(History *history);
|
||||
|
||||
void updateHistoryDownPosition();
|
||||
@@ -454,6 +459,10 @@ private:
|
||||
std::optional<QString> writeRestriction() const;
|
||||
void orderWidgets();
|
||||
|
||||
[[nodiscard]] InlineBotQuery parseInlineBotQuery() const;
|
||||
[[nodiscard]] auto parseMentionHashtagBotCommandQuery() const
|
||||
-> AutocompleteQuery;
|
||||
|
||||
void clearInlineBot();
|
||||
void inlineBotChanged();
|
||||
|
||||
@@ -585,19 +594,21 @@ private:
|
||||
void inlineBotResolveDone(const MTPcontacts_ResolvedPeer &result);
|
||||
void inlineBotResolveFail(const MTP::Error &error, const QString &username);
|
||||
|
||||
bool isRecording() const;
|
||||
[[nodiscard]] bool isRecording() const;
|
||||
|
||||
bool isBotStart() const;
|
||||
bool isBlocked() const;
|
||||
bool isJoinChannel() const;
|
||||
bool isMuteUnmute() const;
|
||||
bool isReportMessages() const;
|
||||
[[nodiscard]] bool isBotStart() const;
|
||||
[[nodiscard]] bool isBlocked() const;
|
||||
[[nodiscard]] bool isJoinChannel() const;
|
||||
[[nodiscard]] bool isMuteUnmute() const;
|
||||
[[nodiscard]] bool isReportMessages() const;
|
||||
bool updateCmdStartShown();
|
||||
void updateSendButtonType();
|
||||
bool showRecordButton() const;
|
||||
bool showInlineBotCancel() const;
|
||||
[[nodiscard]] bool showRecordButton() const;
|
||||
[[nodiscard]] bool showInlineBotCancel() const;
|
||||
void refreshSilentToggle();
|
||||
|
||||
[[nodiscard]] bool isChoosingTheme() const;
|
||||
|
||||
void setupScheduledToggle();
|
||||
void refreshScheduledToggle();
|
||||
|
||||
@@ -689,7 +700,7 @@ private:
|
||||
bool _unreadMentionsIsShown = false;
|
||||
object_ptr<Ui::HistoryDownButton> _unreadMentions;
|
||||
|
||||
object_ptr<FieldAutocomplete> _fieldAutocomplete;
|
||||
const object_ptr<FieldAutocomplete> _fieldAutocomplete;
|
||||
object_ptr<Support::Autocomplete> _supportAutocomplete;
|
||||
std::unique_ptr<MessageLinksParser> _fieldLinksParser;
|
||||
|
||||
@@ -726,6 +737,8 @@ private:
|
||||
object_ptr<Ui::ScrollArea> _kbScroll;
|
||||
const not_null<BotKeyboard*> _keyboard;
|
||||
|
||||
std::unique_ptr<Ui::ChooseThemeController> _chooseTheme;
|
||||
|
||||
object_ptr<Ui::InnerDropdown> _membersDropdown = { nullptr };
|
||||
base::Timer _membersDropdownShowTimer;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "api/api_send_progress.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "base/openssl_help.h"
|
||||
#include "base/random.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "core/application.h"
|
||||
@@ -98,7 +98,7 @@ enum class FilterType {
|
||||
[[nodiscard]] not_null<DocumentData*> DummyDocument(
|
||||
not_null<Data::Session*> owner) {
|
||||
return owner->document(
|
||||
openssl::RandomValue<DocumentId>(),
|
||||
base::RandomValue<DocumentId>(),
|
||||
uint64(0),
|
||||
QByteArray(),
|
||||
base::unixtime::now(),
|
||||
@@ -130,7 +130,7 @@ void PaintWaveform(
|
||||
const auto samplesCount = wf
|
||||
? wf->size()
|
||||
: ::Media::Player::kWaveformSamplesCount;
|
||||
const auto activeWidth = std::round(availableWidth * progress);
|
||||
const auto activeWidth = base::SafeRound(availableWidth * progress);
|
||||
|
||||
const auto &barWidth = st::historyRecordWaveformBar;
|
||||
const auto barFullWidth = barWidth + st::msgWaveformSkip;
|
||||
@@ -774,7 +774,7 @@ void RecordLock::drawProgress(Painter &p) {
|
||||
_lockToStopProgress);
|
||||
const auto blockRectTop = anim::interpolateF(
|
||||
size.height() - blockHeight,
|
||||
std::round((size.height() - blockRectHeight) / 2.),
|
||||
base::SafeRound((size.height() - blockRectHeight) / 2.),
|
||||
_lockToStopProgress);
|
||||
|
||||
const auto blockRect = QRectF(
|
||||
|
||||
@@ -67,7 +67,8 @@ std::unique_ptr<Ui::PathShiftGradient> MakePathShiftGradient(
|
||||
return std::make_unique<Ui::PathShiftGradient>(
|
||||
st->msgServiceBg(),
|
||||
st->msgServiceBgSelected(),
|
||||
std::move(update));
|
||||
std::move(update),
|
||||
st->paletteChanged());
|
||||
}
|
||||
|
||||
SimpleElementDelegate::SimpleElementDelegate(
|
||||
@@ -176,6 +177,11 @@ auto SimpleElementDelegate::elementPathShiftGradient()
|
||||
void SimpleElementDelegate::elementReplyTo(const FullMsgId &to) {
|
||||
}
|
||||
|
||||
|
||||
void SimpleElementDelegate::elementStartInteraction(
|
||||
not_null<const Element*> view) {
|
||||
}
|
||||
|
||||
TextSelection UnshiftItemSelection(
|
||||
TextSelection selection,
|
||||
uint16 byLength) {
|
||||
@@ -218,24 +224,6 @@ QString DateTooltipText(not_null<Element*> view) {
|
||||
tr::now,
|
||||
lt_date,
|
||||
base::unixtime::parse(forwarded->originalDate).toString(format));
|
||||
if (const auto media = view->media()) {
|
||||
if (media->hidesForwardedInfo()) {
|
||||
const auto from = forwarded->originalSender
|
||||
? forwarded->originalSender->shortName()
|
||||
: forwarded->hiddenSenderInfo->firstName;
|
||||
if (forwarded->imported) {
|
||||
dateText += '\n' + tr::lng_signed_author(
|
||||
tr::now,
|
||||
lt_user,
|
||||
from);
|
||||
} else {
|
||||
dateText += '\n' + tr::lng_forwarded(
|
||||
tr::now,
|
||||
lt_user,
|
||||
from);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (forwarded->imported) {
|
||||
dateText = tr::lng_forwarded_imported(tr::now)
|
||||
+ "\n\n" + dateText;
|
||||
|
||||
@@ -90,6 +90,7 @@ public:
|
||||
virtual bool elementIsChatWide() = 0;
|
||||
virtual not_null<Ui::PathShiftGradient*> elementPathShiftGradient() = 0;
|
||||
virtual void elementReplyTo(const FullMsgId &to) = 0;
|
||||
virtual void elementStartInteraction(not_null<const Element*> view) = 0;
|
||||
|
||||
virtual ~ElementDelegate() {
|
||||
}
|
||||
@@ -146,6 +147,7 @@ public:
|
||||
bool elementIsChatWide() override;
|
||||
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
|
||||
void elementReplyTo(const FullMsgId &to) override;
|
||||
void elementStartInteraction(not_null<const Element*> view) override;
|
||||
|
||||
protected:
|
||||
[[nodiscard]] not_null<Window::SessionController*> controller() const {
|
||||
|
||||
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "history/view/history_view_emoji_interactions.h"
|
||||
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "history/view/media/history_view_sticker.h"
|
||||
#include "history/history.h"
|
||||
#include "chat_helpers/emoji_interactions.h"
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
#include "main/main_session.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "lottie/lottie_common.h"
|
||||
#include "lottie/lottie_single_player.h"
|
||||
#include "base/random.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
||||
namespace HistoryView {
|
||||
namespace {
|
||||
|
||||
constexpr auto kSizeMultiplier = 3;
|
||||
constexpr auto kCachesCount = 4;
|
||||
constexpr auto kMaxPlays = 5;
|
||||
constexpr auto kMaxPlaysWithSmallDelay = 3;
|
||||
constexpr auto kSmallDelay = crl::time(200);
|
||||
constexpr auto kDropDelayedAfterDelay = crl::time(2000);
|
||||
|
||||
[[nodiscard]] QPoint GenerateRandomShift(QSize emoji) {
|
||||
// Random shift in [-0.08 ... 0.08] of animated emoji size.
|
||||
const auto maxShift = emoji * 2 / 25;
|
||||
return {
|
||||
base::RandomIndex(maxShift.width() * 2 + 1) - maxShift.width(),
|
||||
base::RandomIndex(maxShift.height() * 2 + 1) - maxShift.height(),
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EmojiInteractions::EmojiInteractions(not_null<Main::Session*> session)
|
||||
: _session(session) {
|
||||
_session->data().viewRemoved(
|
||||
) | rpl::filter([=] {
|
||||
return !_plays.empty();
|
||||
}) | rpl::start_with_next([=](not_null<const Element*> view) {
|
||||
_plays.erase(ranges::remove(_plays, view, &Play::view), end(_plays));
|
||||
}, _lifetime);
|
||||
|
||||
_emojiSize = Sticker::EmojiSize();
|
||||
}
|
||||
|
||||
EmojiInteractions::~EmojiInteractions() = default;
|
||||
|
||||
void EmojiInteractions::play(
|
||||
ChatHelpers::EmojiInteractionPlayRequest request,
|
||||
not_null<Element*> view) {
|
||||
if (!view->media()) {
|
||||
// Large emoji may be disabled.
|
||||
return;
|
||||
} else if (_plays.empty()) {
|
||||
play(
|
||||
std::move(request.emoticon),
|
||||
view,
|
||||
std::move(request.media),
|
||||
request.incoming);
|
||||
} else {
|
||||
const auto now = crl::now();
|
||||
_delayed.push_back({
|
||||
request.emoticon,
|
||||
view,
|
||||
std::move(request.media),
|
||||
now,
|
||||
request.incoming,
|
||||
});
|
||||
checkDelayed();
|
||||
}
|
||||
}
|
||||
|
||||
void EmojiInteractions::play(
|
||||
QString emoticon,
|
||||
not_null<Element*> view,
|
||||
std::shared_ptr<Data::DocumentMedia> media,
|
||||
bool incoming) {
|
||||
const auto top = view->block()->y() + view->y();
|
||||
const auto bottom = top + view->height();
|
||||
if (_visibleTop >= bottom
|
||||
|| _visibleBottom <= top
|
||||
|| _visibleTop == _visibleBottom) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lottie = preparePlayer(media.get());
|
||||
|
||||
const auto shift = GenerateRandomShift(_emojiSize);
|
||||
lottie->updates(
|
||||
) | rpl::start_with_next([=](Lottie::Update update) {
|
||||
v::match(update.data, [&](const Lottie::Information &information) {
|
||||
}, [&](const Lottie::DisplayFrameRequest &request) {
|
||||
const auto rect = computeRect(view).translated(shift);
|
||||
if (rect.y() + rect.height() >= _visibleTop
|
||||
&& rect.y() <= _visibleBottom) {
|
||||
_updateRequests.fire_copy(rect);
|
||||
}
|
||||
});
|
||||
}, lottie->lifetime());
|
||||
_plays.push_back({
|
||||
.view = view,
|
||||
.lottie = std::move(lottie),
|
||||
.shift = shift,
|
||||
});
|
||||
if (incoming) {
|
||||
_playStarted.fire(std::move(emoticon));
|
||||
}
|
||||
if (const auto media = view->media()) {
|
||||
media->stickerClearLoopPlayed();
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Lottie::SinglePlayer> EmojiInteractions::preparePlayer(
|
||||
not_null<Data::DocumentMedia*> media) {
|
||||
// Shortened copy from stickers_lottie module.
|
||||
const auto document = media->owner();
|
||||
const auto baseKey = document->bigFileBaseCacheKey();
|
||||
const auto tag = uint8(0);
|
||||
const auto keyShift = ((tag << 4) & 0xF0)
|
||||
| (uint8(ChatHelpers::StickerLottieSize::EmojiInteraction) & 0x0F);
|
||||
const auto key = Storage::Cache::Key{
|
||||
baseKey.high,
|
||||
baseKey.low + keyShift
|
||||
};
|
||||
const auto get = [=](int i, FnMut<void(QByteArray &&cached)> handler) {
|
||||
document->owner().cacheBigFile().get(
|
||||
{ key.high, key.low + i },
|
||||
std::move(handler));
|
||||
};
|
||||
const auto weak = base::make_weak(&document->session());
|
||||
const auto put = [=](int i, QByteArray &&cached) {
|
||||
crl::on_main(weak, [=, data = std::move(cached)]() mutable {
|
||||
weak->data().cacheBigFile().put(
|
||||
{ key.high, key.low + i },
|
||||
std::move(data));
|
||||
});
|
||||
};
|
||||
const auto data = media->bytes();
|
||||
const auto filepath = document->filepath();
|
||||
const auto request = Lottie::FrameRequest{
|
||||
_emojiSize * kSizeMultiplier * style::DevicePixelRatio(),
|
||||
};
|
||||
auto &weakProvider = _sharedProviders[document];
|
||||
auto shared = [&] {
|
||||
if (const auto result = weakProvider.lock()) {
|
||||
return result;
|
||||
}
|
||||
const auto result = Lottie::SinglePlayer::SharedProvider(
|
||||
kCachesCount,
|
||||
get,
|
||||
put,
|
||||
Lottie::ReadContent(data, filepath),
|
||||
request,
|
||||
Lottie::Quality::High);
|
||||
weakProvider = result;
|
||||
return result;
|
||||
}();
|
||||
return std::make_unique<Lottie::SinglePlayer>(std::move(shared), request);
|
||||
}
|
||||
|
||||
void EmojiInteractions::visibleAreaUpdated(
|
||||
int visibleTop,
|
||||
int visibleBottom) {
|
||||
_visibleTop = visibleTop;
|
||||
_visibleBottom = visibleBottom;
|
||||
}
|
||||
|
||||
QRect EmojiInteractions::computeRect(not_null<Element*> view) const {
|
||||
const auto fullWidth = view->width();
|
||||
const auto shift = (_emojiSize.width() * kSizeMultiplier) / 40;
|
||||
const auto skip = (view->hasFromPhoto() ? st::msgPhotoSkip : 0)
|
||||
+ st::msgMargin.left();
|
||||
const auto rightAligned = view->hasOutLayout()
|
||||
&& !view->delegate()->elementIsChatWide();
|
||||
const auto left = rightAligned
|
||||
? (fullWidth - skip + shift - _emojiSize.width() * kSizeMultiplier)
|
||||
: (skip - shift);
|
||||
const auto viewTop = view->block()->y() + view->y() + view->marginTop();
|
||||
const auto top = viewTop - _emojiSize.height();
|
||||
return QRect(QPoint(left, top), _emojiSize * kSizeMultiplier);
|
||||
}
|
||||
|
||||
void EmojiInteractions::paint(QPainter &p) {
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
for (auto &play : _plays) {
|
||||
if (!play.lottie->ready()) {
|
||||
continue;
|
||||
}
|
||||
auto request = Lottie::FrameRequest();
|
||||
request.box = _emojiSize * kSizeMultiplier * factor;
|
||||
const auto rightAligned = play.view->hasOutLayout()
|
||||
&& !play.view->delegate()->elementIsChatWide();
|
||||
if (!rightAligned) {
|
||||
request.mirrorHorizontal = true;
|
||||
}
|
||||
const auto frame = play.lottie->frameInfo(request);
|
||||
play.frame = frame.index;
|
||||
if (!play.framesCount) {
|
||||
const auto &information = play.lottie->information();
|
||||
play.framesCount = information.framesCount;
|
||||
play.frameRate = information.frameRate;
|
||||
}
|
||||
if (play.frame + 1 == play.framesCount) {
|
||||
play.finished = true;
|
||||
}
|
||||
const auto rect = computeRect(play.view);
|
||||
p.drawImage(
|
||||
QRect(rect.topLeft() + play.shift, frame.image.size() / factor),
|
||||
frame.image);
|
||||
play.lottie->markFrameShown();
|
||||
}
|
||||
_plays.erase(ranges::remove(_plays, true, &Play::finished), end(_plays));
|
||||
checkDelayed();
|
||||
}
|
||||
|
||||
void EmojiInteractions::checkDelayed() {
|
||||
if (_delayed.empty() || _plays.size() >= kMaxPlays) {
|
||||
return;
|
||||
}
|
||||
auto withTooLittleDelay = false;
|
||||
auto withHalfPlayed = false;
|
||||
for (const auto &play : _plays) {
|
||||
if (!play.framesCount
|
||||
|| !play.frameRate
|
||||
|| !play.frame
|
||||
|| (play.frame * crl::time(1000)
|
||||
< kSmallDelay * play.frameRate)) {
|
||||
withTooLittleDelay = true;
|
||||
break;
|
||||
} else if (play.frame * 2 > play.framesCount) {
|
||||
withHalfPlayed = true;
|
||||
}
|
||||
}
|
||||
if (withTooLittleDelay) {
|
||||
return;
|
||||
} else if (_plays.size() >= kMaxPlaysWithSmallDelay && !withHalfPlayed) {
|
||||
return;
|
||||
}
|
||||
const auto now = crl::now();
|
||||
const auto i = ranges::find_if(_delayed, [&](const Delayed &delayed) {
|
||||
return (delayed.shouldHaveStartedAt + kDropDelayedAfterDelay > now);
|
||||
});
|
||||
if (i == end(_delayed)) {
|
||||
_delayed.clear();
|
||||
return;
|
||||
}
|
||||
auto good = std::move(*i);
|
||||
_delayed.erase(begin(_delayed), i + 1);
|
||||
play(
|
||||
std::move(good.emoticon),
|
||||
good.view,
|
||||
std::move(good.media),
|
||||
good.incoming);
|
||||
}
|
||||
|
||||
rpl::producer<QRect> EmojiInteractions::updateRequests() const {
|
||||
return _updateRequests.events();
|
||||
}
|
||||
|
||||
rpl::producer<QString> EmojiInteractions::playStarted() const {
|
||||
return _playStarted.events();
|
||||
}
|
||||
|
||||
} // namespace HistoryView
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Data {
|
||||
class DocumentMedia;
|
||||
} // namespace Data
|
||||
|
||||
namespace ChatHelpers {
|
||||
struct EmojiInteractionPlayRequest;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace Lottie {
|
||||
class SinglePlayer;
|
||||
class FrameProvider;
|
||||
} // namespace Lottie
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace HistoryView {
|
||||
|
||||
class Element;
|
||||
|
||||
class EmojiInteractions final {
|
||||
public:
|
||||
explicit EmojiInteractions(not_null<Main::Session*> session);
|
||||
~EmojiInteractions();
|
||||
|
||||
void play(
|
||||
ChatHelpers::EmojiInteractionPlayRequest request,
|
||||
not_null<Element*> view);
|
||||
void visibleAreaUpdated(int visibleTop, int visibleBottom);
|
||||
|
||||
void paint(QPainter &p);
|
||||
[[nodiscard]] rpl::producer<QRect> updateRequests() const;
|
||||
[[nodiscard]] rpl::producer<QString> playStarted() const;
|
||||
|
||||
private:
|
||||
struct Play {
|
||||
not_null<Element*> view;
|
||||
std::unique_ptr<Lottie::SinglePlayer> lottie;
|
||||
QPoint shift;
|
||||
int frame = 0;
|
||||
int framesCount = 0;
|
||||
int frameRate = 0;
|
||||
bool finished = false;
|
||||
};
|
||||
struct Delayed {
|
||||
QString emoticon;
|
||||
not_null<Element*> view;
|
||||
std::shared_ptr<Data::DocumentMedia> media;
|
||||
crl::time shouldHaveStartedAt = 0;
|
||||
bool incoming = false;
|
||||
};
|
||||
|
||||
[[nodiscard]] QRect computeRect(not_null<Element*> view) const;
|
||||
|
||||
void play(
|
||||
QString emoticon,
|
||||
not_null<Element*> view,
|
||||
std::shared_ptr<Data::DocumentMedia> media,
|
||||
bool incoming);
|
||||
void checkDelayed();
|
||||
|
||||
[[nodiscard]] std::unique_ptr<Lottie::SinglePlayer> preparePlayer(
|
||||
not_null<Data::DocumentMedia*> media);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
int _visibleTop = 0;
|
||||
int _visibleBottom = 0;
|
||||
QSize _emojiSize;
|
||||
|
||||
std::vector<Play> _plays;
|
||||
std::vector<Delayed> _delayed;
|
||||
rpl::event_stream<QRect> _updateRequests;
|
||||
rpl::event_stream<QString> _playStarted;
|
||||
base::flat_map<
|
||||
not_null<DocumentData*>,
|
||||
std::weak_ptr<Lottie::FrameProvider>> _sharedProviders;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace HistoryView
|
||||
@@ -477,7 +477,8 @@ void ListWidget::scrollToAnimationCallback(
|
||||
int relativeTo) {
|
||||
if (!attachToId) {
|
||||
// Animated scroll to bottom.
|
||||
const auto current = int(std::round(_scrollToAnimation.value(0)));
|
||||
const auto current = int(base::SafeRound(
|
||||
_scrollToAnimation.value(0)));
|
||||
_delegate->listScrollTo(height()
|
||||
- (_visibleBottom - _visibleTop)
|
||||
+ current);
|
||||
@@ -488,7 +489,7 @@ void ListWidget::scrollToAnimationCallback(
|
||||
if (!attachToView) {
|
||||
_scrollToAnimation.stop();
|
||||
} else {
|
||||
const auto current = int(std::round(_scrollToAnimation.value(
|
||||
const auto current = int(base::SafeRound(_scrollToAnimation.value(
|
||||
relativeTo)));
|
||||
_delegate->listScrollTo(itemTop(attachToView) + current);
|
||||
}
|
||||
@@ -1391,6 +1392,9 @@ void ListWidget::elementReplyTo(const FullMsgId &to) {
|
||||
replyToMessageRequestNotify(to);
|
||||
}
|
||||
|
||||
void ListWidget::elementStartInteraction(not_null<const Element*> view) {
|
||||
}
|
||||
|
||||
void ListWidget::saveState(not_null<ListMemento*> memento) {
|
||||
memento->setAroundPosition(_aroundPosition);
|
||||
auto state = countScrollState();
|
||||
|
||||
@@ -260,6 +260,7 @@ public:
|
||||
bool elementIsChatWide() override;
|
||||
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
|
||||
void elementReplyTo(const FullMsgId &to) override;
|
||||
void elementStartInteraction(not_null<const Element*> view) override;
|
||||
|
||||
void setEmptyInfoWidget(base::unique_qptr<Ui::RpWidget> &&w);
|
||||
|
||||
|
||||
@@ -1421,6 +1421,7 @@ QPixmap RepliesWidget::grabForShowAnimation(const Window::SectionSlideParams &pa
|
||||
_composeControls->showForGrab();
|
||||
auto result = Ui::GrabWidget(this);
|
||||
if (params.withTopBarShadow) _topBarShadow->show();
|
||||
_rootView->hide();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1537,7 +1538,7 @@ void RepliesWidget::restoreState(not_null<RepliesMemento*> memento) {
|
||||
? (areComments
|
||||
? tr::lng_comments_header
|
||||
: tr::lng_replies_header)(
|
||||
lt_count,
|
||||
lt_count_decimal,
|
||||
rpl::single(count) | tr::to_count())
|
||||
: (areComments
|
||||
? tr::lng_comments_header_none
|
||||
@@ -1706,6 +1707,7 @@ void RepliesWidget::showAnimatedHook(
|
||||
void RepliesWidget::showFinishedHook() {
|
||||
_topBar->setAnimatingMode(false);
|
||||
_composeControls->showFinished();
|
||||
_rootView->show();
|
||||
|
||||
// We should setup the drag area only after
|
||||
// the section animation is finished,
|
||||
|
||||
@@ -119,7 +119,7 @@ bool SendActionPainter::updateNeedsAnimating(
|
||||
Type::ChooseSticker,
|
||||
kStatusShowClientsideChooseSticker);
|
||||
}, [&](const MTPDsendMessageEmojiInteraction &) {
|
||||
// #TODO interaction
|
||||
Unexpected("EmojiInteraction here.");
|
||||
}, [&](const MTPDsendMessageEmojiInteractionSeen &) {
|
||||
// #TODO interaction
|
||||
}, [&](const MTPDsendMessageCancelAction &) {
|
||||
@@ -308,8 +308,8 @@ bool SendActionPainter::updateNeedsAnimating(crl::time now, bool force) {
|
||||
// We have to use QFontMetricsF instead of
|
||||
// FontData::spacew for more precise calculation.
|
||||
const auto mf = QFontMetricsF(_st.font->f);
|
||||
_spacesCount = std::round(
|
||||
_sendActionAnimation.width()
|
||||
_spacesCount = base::SafeRound(
|
||||
_sendActionAnimation.widthNoMargins()
|
||||
/ mf.horizontalAdvance(' '));
|
||||
}
|
||||
newTypingString = newTypingString.replace(
|
||||
|
||||