Compare commits
182 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
352768053d | ||
|
|
79b1cec4f3 | ||
|
|
8d09190439 | ||
|
|
5cd0a3719e | ||
|
|
8b7cd4a0c7 | ||
|
|
937c2d3dce | ||
|
|
1fa5d273cc | ||
|
|
24fa3dbf8f | ||
|
|
c9b782fd63 | ||
|
|
e7cf560da0 | ||
|
|
86e07518ad | ||
|
|
8c71d03959 | ||
|
|
967e86f4ab | ||
|
|
730412fefe | ||
|
|
576883ddc8 | ||
|
|
992d636680 | ||
|
|
8cdd2f113f | ||
|
|
d5f935b73d | ||
|
|
84f561b251 | ||
|
|
21ac2b8f3a | ||
|
|
1790828b01 | ||
|
|
792b9090a7 | ||
|
|
8c21fad642 | ||
|
|
5136cc3c9c | ||
|
|
b78b27f517 | ||
|
|
85760ea92c | ||
|
|
c2212c719e | ||
|
|
fc8a0d0efd | ||
|
|
c052c37621 | ||
|
|
21f7cec781 | ||
|
|
64af456d29 | ||
|
|
7751c4ac1f | ||
|
|
ececdcb9c0 | ||
|
|
cb8f49aea0 | ||
|
|
e3ef7d6631 | ||
|
|
21aa1f49d7 | ||
|
|
51e80170e2 | ||
|
|
b2526ab7f6 | ||
|
|
e220447bdd | ||
|
|
ead695b101 | ||
|
|
4ea72f8f89 | ||
|
|
4ef550da9b | ||
|
|
1e660fc2a2 | ||
|
|
6adf791b3b | ||
|
|
d2a41a42e0 | ||
|
|
315549b5f8 | ||
|
|
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 ^
|
||||
../..
|
||||
|
||||
@@ -42,7 +42,12 @@ include(cmake/generate_appdata_changelog.cmake)
|
||||
|
||||
if (WIN32)
|
||||
include(cmake/generate_midl.cmake)
|
||||
generate_midl(Telegram ${src_loc}/platform/win/windows_quiethours.idl)
|
||||
generate_midl(Telegram ${src_loc}
|
||||
platform/win/windows_quiethours.idl
|
||||
platform/win/windows_toastactivator.idl
|
||||
)
|
||||
|
||||
nuget_add_winrt(Telegram)
|
||||
endif()
|
||||
|
||||
set_target_properties(Telegram PROPERTIES AUTOMOC ON AUTORCC ON)
|
||||
@@ -52,6 +57,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 +84,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
|
||||
@@ -130,6 +142,8 @@ PRIVATE
|
||||
api/api_updates.h
|
||||
api/api_user_privacy.cpp
|
||||
api/api_user_privacy.h
|
||||
api/api_views.cpp
|
||||
api/api_views.h
|
||||
api/api_who_read.cpp
|
||||
api/api_who_read.h
|
||||
boxes/filters/edit_filter_box.cpp
|
||||
@@ -278,6 +292,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
|
||||
@@ -404,6 +420,7 @@ PRIVATE
|
||||
data/data_media_types.h
|
||||
data/data_messages.cpp
|
||||
data/data_messages.h
|
||||
data/data_msg_id.h
|
||||
data/data_notify_settings.cpp
|
||||
data/data_notify_settings.h
|
||||
data/data_peer.cpp
|
||||
@@ -456,8 +473,6 @@ PRIVATE
|
||||
dialogs/dialogs_inner_widget.h
|
||||
dialogs/dialogs_key.cpp
|
||||
dialogs/dialogs_key.h
|
||||
dialogs/dialogs_layout.cpp
|
||||
dialogs/dialogs_layout.h
|
||||
dialogs/dialogs_list.cpp
|
||||
dialogs/dialogs_list.h
|
||||
dialogs/dialogs_main_list.cpp
|
||||
@@ -470,6 +485,10 @@ PRIVATE
|
||||
dialogs/dialogs_search_from_controllers.h
|
||||
dialogs/dialogs_widget.cpp
|
||||
dialogs/dialogs_widget.h
|
||||
dialogs/ui/dialogs_layout.cpp
|
||||
dialogs/ui/dialogs_layout.h
|
||||
dialogs/ui/dialogs_message_view.cpp
|
||||
dialogs/ui/dialogs_message_view.h
|
||||
editor/color_picker.cpp
|
||||
editor/color_picker.h
|
||||
editor/controllers/controllers.h
|
||||
@@ -581,6 +600,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
|
||||
@@ -618,6 +639,8 @@ PRIVATE
|
||||
history/history_item.h
|
||||
history/history_item_components.cpp
|
||||
history/history_item_components.h
|
||||
history/history_item_reply_markup.cpp
|
||||
history/history_item_reply_markup.h
|
||||
history/history_item_text.cpp
|
||||
history/history_item_text.h
|
||||
history/history_inner_widget.cpp
|
||||
@@ -930,6 +953,8 @@ PRIVATE
|
||||
platform/win/windows_dlls.h
|
||||
platform/win/windows_event_filter.cpp
|
||||
platform/win/windows_event_filter.h
|
||||
platform/win/windows_toast_activator.cpp
|
||||
platform/win/windows_toast_activator.h
|
||||
platform/platform_audio.h
|
||||
platform/platform_file_utilities.h
|
||||
platform/platform_launcher.h
|
||||
@@ -1027,6 +1052,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 +1272,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,12 +1382,14 @@ if (WIN32)
|
||||
/DELAYLOAD:gdiplus.dll
|
||||
/DELAYLOAD:version.dll
|
||||
/DELAYLOAD:dwmapi.dll
|
||||
/DELAYLOAD:uxtheme.dll
|
||||
/DELAYLOAD:crypt32.dll
|
||||
/DELAYLOAD:bcrypt.dll
|
||||
/DELAYLOAD:imm32.dll
|
||||
/DELAYLOAD:netapi32.dll
|
||||
/DELAYLOAD:userenv.dll
|
||||
/DELAYLOAD:wtsapi32.dll
|
||||
/DELAYLOAD:propsys.dll
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
|
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 |
BIN
Telegram/Resources/icons/dialogs/dialogs_mini_play.png
Normal file
|
After Width: | Height: | Size: 165 B |
BIN
Telegram/Resources/icons/dialogs/dialogs_mini_play@2x.png
Normal file
|
After Width: | Height: | Size: 200 B |
BIN
Telegram/Resources/icons/dialogs/dialogs_mini_play@3x.png
Normal file
|
After Width: | Height: | Size: 475 B |
|
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.6.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,6,0
|
||||
PRODUCTVERSION 3,1,6,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.6.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "3.0.2.0"
|
||||
VALUE "ProductVersion", "3.1.6.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,6,0
|
||||
PRODUCTVERSION 3,1,6,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.6.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "3.0.2.0"
|
||||
VALUE "ProductVersion", "3.1.6.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);
|
||||
@@ -128,7 +128,7 @@ void SendExistingMedia(
|
||||
messagePostAuthor,
|
||||
media,
|
||||
caption,
|
||||
MTPReplyMarkup());
|
||||
HistoryMessageMarkupData());
|
||||
|
||||
auto performRequest = [=](const auto &repeatRequest) -> void {
|
||||
auto &histories = history->owner().histories();
|
||||
@@ -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);
|
||||
@@ -288,7 +288,7 @@ bool SendDice(Api::MessageToSend &message) {
|
||||
messagePostAuthor,
|
||||
TextWithEntities(),
|
||||
MTP_messageMediaDice(MTP_int(0), MTP_string(emoji)),
|
||||
MTPReplyMarkup());
|
||||
HistoryMessageMarkupData());
|
||||
|
||||
const auto requestType = Data::Histories::RequestType::Send;
|
||||
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
||||
@@ -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()
|
||||
@@ -466,7 +471,7 @@ void SendConfirmedFile(
|
||||
messagePostAuthor,
|
||||
caption,
|
||||
media,
|
||||
MTPReplyMarkup(),
|
||||
HistoryMessageMarkupData(),
|
||||
groupId);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
134
Telegram/SourceFiles/api/api_views.cpp
Normal file
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
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 "api/api_views.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_peer_id.h"
|
||||
#include "data/data_session.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
// Send channel views each second.
|
||||
constexpr auto kSendViewsTimeout = crl::time(1000);
|
||||
|
||||
} // namespace
|
||||
|
||||
ViewsManager::ViewsManager(not_null<ApiWrap*> api)
|
||||
: _session(&api->session())
|
||||
, _api(&api->instance()) {
|
||||
}
|
||||
|
||||
void ViewsManager::scheduleIncrement(not_null<HistoryItem*> item) {
|
||||
auto peer = item->history()->peer;
|
||||
auto i = _incremented.find(peer);
|
||||
if (i != _incremented.cend()) {
|
||||
if (i->second.contains(item->id)) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
i = _incremented.emplace(peer).first;
|
||||
}
|
||||
i->second.emplace(item->id);
|
||||
auto j = _toIncrement.find(peer);
|
||||
if (j == _toIncrement.cend()) {
|
||||
j = _toIncrement.emplace(peer).first;
|
||||
_incrementTimer.callOnce(kSendViewsTimeout);
|
||||
}
|
||||
j->second.emplace(item->id);
|
||||
}
|
||||
|
||||
void ViewsManager::removeIncremented(not_null<PeerData*> peer) {
|
||||
_incremented.remove(peer);
|
||||
}
|
||||
|
||||
void ViewsManager::viewsIncrement() {
|
||||
for (auto i = _toIncrement.begin(); i != _toIncrement.cend();) {
|
||||
if (_incrementRequests.contains(i->first)) {
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
|
||||
QVector<MTPint> ids;
|
||||
ids.reserve(i->second.size());
|
||||
for (const auto &msgId : i->second) {
|
||||
ids.push_back(MTP_int(msgId));
|
||||
}
|
||||
const auto requestId = _api.request(MTPmessages_GetMessagesViews(
|
||||
i->first->input,
|
||||
MTP_vector<MTPint>(ids),
|
||||
MTP_bool(true)
|
||||
)).done([=](
|
||||
const MTPmessages_MessageViews &result,
|
||||
mtpRequestId requestId) {
|
||||
done(ids, result, requestId);
|
||||
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
|
||||
fail(error, requestId);
|
||||
}).afterDelay(5).send();
|
||||
|
||||
_incrementRequests.emplace(i->first, requestId);
|
||||
i = _toIncrement.erase(i);
|
||||
}
|
||||
}
|
||||
|
||||
void ViewsManager::done(
|
||||
QVector<MTPint> ids,
|
||||
const MTPmessages_MessageViews &result,
|
||||
mtpRequestId requestId) {
|
||||
const auto &data = result.c_messages_messageViews();
|
||||
auto &owner = _session->data();
|
||||
owner.processUsers(data.vusers());
|
||||
owner.processChats(data.vchats());
|
||||
auto &v = data.vviews().v;
|
||||
if (ids.size() == v.size()) {
|
||||
for (const auto &[peer, id] : _incrementRequests) {
|
||||
if (id != requestId) {
|
||||
continue;
|
||||
}
|
||||
const auto channel = peerToChannel(peer->id);
|
||||
for (auto j = 0, l = int(ids.size()); j < l; ++j) {
|
||||
if (const auto item = owner.message(channel, ids[j].v)) {
|
||||
v[j].match([&](const MTPDmessageViews &data) {
|
||||
if (const auto views = data.vviews()) {
|
||||
item->setViewsCount(views->v);
|
||||
}
|
||||
if (const auto forwards = data.vforwards()) {
|
||||
item->setForwardsCount(forwards->v);
|
||||
}
|
||||
if (const auto replies = data.vreplies()) {
|
||||
item->setReplies(*replies);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
_incrementRequests.erase(peer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!_toIncrement.empty() && !_incrementTimer.isActive()) {
|
||||
_incrementTimer.callOnce(kSendViewsTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
void ViewsManager::fail(const MTP::Error &error, mtpRequestId requestId) {
|
||||
for (const auto &[peer, id] : _incrementRequests) {
|
||||
if (id == requestId) {
|
||||
_incrementRequests.erase(peer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!_toIncrement.empty() && !_incrementTimer.isActive()) {
|
||||
_incrementTimer.callOnce(kSendViewsTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
49
Telegram/SourceFiles/api/api_views.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
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 "mtproto/sender.h"
|
||||
#include "base/timer.h"
|
||||
|
||||
class ApiWrap;
|
||||
class PeerData;
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Api {
|
||||
|
||||
class ViewsManager final {
|
||||
public:
|
||||
explicit ViewsManager(not_null<ApiWrap*> api);
|
||||
|
||||
void scheduleIncrement(not_null<HistoryItem*> item);
|
||||
void removeIncremented(not_null<PeerData*> peer);
|
||||
|
||||
private:
|
||||
void viewsIncrement();
|
||||
|
||||
void done(
|
||||
QVector<MTPint> ids,
|
||||
const MTPmessages_MessageViews &result,
|
||||
mtpRequestId requestId);
|
||||
void fail(const MTP::Error &error, mtpRequestId requestId);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
MTP::Sender _api;
|
||||
|
||||
base::flat_map<not_null<PeerData*>, base::flat_set<MsgId>> _incremented;
|
||||
base::flat_map<not_null<PeerData*>, base::flat_set<MsgId>> _toIncrement;
|
||||
base::flat_map<not_null<PeerData*>, mtpRequestId> _incrementRequests;
|
||||
base::flat_map<mtpRequestId, not_null<PeerData*>> _incrementByRequest;
|
||||
base::Timer _incrementTimer;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Api
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_global_privacy.h"
|
||||
#include "api/api_updates.h"
|
||||
#include "api/api_user_privacy.h"
|
||||
#include "api/api_views.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "data/data_drafts.h"
|
||||
#include "data/data_changes.h"
|
||||
@@ -46,6 +47,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"
|
||||
@@ -140,7 +142,8 @@ ApiWrap::ApiWrap(not_null<Main::Session*> session)
|
||||
, _sensitiveContent(std::make_unique<Api::SensitiveContent>(this))
|
||||
, _globalPrivacy(std::make_unique<Api::GlobalPrivacy>(this))
|
||||
, _userPrivacy(std::make_unique<Api::UserPrivacy>(this))
|
||||
, _inviteLinks(std::make_unique<Api::InviteLinks>(this)) {
|
||||
, _inviteLinks(std::make_unique<Api::InviteLinks>(this))
|
||||
, _views(std::make_unique<Api::ViewsManager>(this)) {
|
||||
crl::on_main(session, [=] {
|
||||
// You can't use _session->lifetime() in the constructor,
|
||||
// only queued, because it is not constructed yet.
|
||||
@@ -557,7 +560,8 @@ void ApiWrap::resolveMessageDatas() {
|
||||
)).done([=](
|
||||
const MTPmessages_Messages &result,
|
||||
mtpRequestId requestId) {
|
||||
gotMessageDatas(nullptr, result, requestId);
|
||||
_session->data().processExistingMessages(nullptr, result);
|
||||
finalizeMessageDataRequest(nullptr, requestId);
|
||||
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
|
||||
finalizeMessageDataRequest(nullptr, requestId);
|
||||
}).afterDelay(kSmallDelayMs).send();
|
||||
@@ -583,7 +587,8 @@ void ApiWrap::resolveMessageDatas() {
|
||||
)).done([=](
|
||||
const MTPmessages_Messages &result,
|
||||
mtpRequestId requestId) {
|
||||
gotMessageDatas(channel, result, requestId);
|
||||
_session->data().processExistingMessages(channel, result);
|
||||
finalizeMessageDataRequest(channel, requestId);
|
||||
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
|
||||
finalizeMessageDataRequest(channel, requestId);
|
||||
}).afterDelay(kSmallDelayMs).send();
|
||||
@@ -599,37 +604,6 @@ void ApiWrap::resolveMessageDatas() {
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::gotMessageDatas(ChannelData *channel, const MTPmessages_Messages &msgs, mtpRequestId requestId) {
|
||||
const auto handleResult = [&](auto &&result) {
|
||||
_session->data().processUsers(result.vusers());
|
||||
_session->data().processChats(result.vchats());
|
||||
_session->data().processMessages(
|
||||
result.vmessages(),
|
||||
NewMessageType::Existing);
|
||||
};
|
||||
switch (msgs.type()) {
|
||||
case mtpc_messages_messages:
|
||||
handleResult(msgs.c_messages_messages());
|
||||
break;
|
||||
case mtpc_messages_messagesSlice:
|
||||
handleResult(msgs.c_messages_messagesSlice());
|
||||
break;
|
||||
case mtpc_messages_channelMessages: {
|
||||
auto &d = msgs.c_messages_channelMessages();
|
||||
if (channel) {
|
||||
channel->ptsReceived(d.vpts().v);
|
||||
} else {
|
||||
LOG(("App Error: received messages.channelMessages when no channel was passed! (ApiWrap::gotDependencyItem)"));
|
||||
}
|
||||
handleResult(d);
|
||||
} break;
|
||||
case mtpc_messages_messagesNotModified:
|
||||
LOG(("API Error: received messages.messagesNotModified! (ApiWrap::gotDependencyItem)"));
|
||||
break;
|
||||
}
|
||||
finalizeMessageDataRequest(channel, requestId);
|
||||
}
|
||||
|
||||
void ApiWrap::finalizeMessageDataRequest(
|
||||
ChannelData *channel,
|
||||
mtpRequestId requestId) {
|
||||
@@ -661,8 +635,8 @@ QString ApiWrap::exportDirectMessageLink(
|
||||
const auto fallback = [&] {
|
||||
auto linkChannel = channel;
|
||||
auto linkItemId = item->id;
|
||||
auto linkCommentId = 0;
|
||||
auto linkThreadId = 0;
|
||||
auto linkCommentId = MsgId();
|
||||
auto linkThreadId = MsgId();
|
||||
if (inRepliesContext) {
|
||||
if (const auto rootId = item->replyToTop()) {
|
||||
const auto root = item->history()->owner().message(
|
||||
@@ -692,11 +666,11 @@ QString ApiWrap::exportDirectMessageLink(
|
||||
: "c/" + QString::number(peerToChannel(linkChannel->id).bare);
|
||||
const auto query = base
|
||||
+ '/'
|
||||
+ QString::number(linkItemId)
|
||||
+ QString::number(linkItemId.bare)
|
||||
+ (linkCommentId
|
||||
? "?comment=" + QString::number(linkCommentId)
|
||||
? "?comment=" + QString::number(linkCommentId.bare)
|
||||
: linkThreadId
|
||||
? "?thread=" + QString::number(linkThreadId)
|
||||
? "?thread=" + QString::number(linkThreadId.bare)
|
||||
: "");
|
||||
if (linkChannel->hasUsername()
|
||||
&& !linkChannel->isMegagroup()
|
||||
@@ -1758,7 +1732,7 @@ void ApiWrap::deleteAllFromUser(
|
||||
? history->collectMessagesFromUserToDelete(from)
|
||||
: QVector<MsgId>();
|
||||
const auto channelId = peerToChannel(channel->id);
|
||||
for (const auto msgId : ids) {
|
||||
for (const auto &msgId : ids) {
|
||||
if (const auto item = _session->data().message(channelId, msgId)) {
|
||||
item->destroy();
|
||||
}
|
||||
@@ -3652,8 +3626,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 +3699,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(
|
||||
@@ -3835,7 +3810,7 @@ void ApiWrap::sendSharedContact(
|
||||
MTP_string(lastName),
|
||||
MTP_string(), // vcard
|
||||
MTP_long(userId.bare)),
|
||||
MTPReplyMarkup());
|
||||
HistoryMessageMarkupData());
|
||||
|
||||
const auto media = MTP_inputMediaContact(
|
||||
MTP_string(phone),
|
||||
@@ -4034,7 +4009,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);
|
||||
|
||||
@@ -4099,7 +4074,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
messagePostAuthor,
|
||||
sending,
|
||||
media,
|
||||
MTPReplyMarkup());
|
||||
HistoryMessageMarkupData());
|
||||
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
||||
history->sendRequestId = request(MTPmessages_SendMessage(
|
||||
MTP_flags(sendFlags),
|
||||
@@ -4158,7 +4133,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 +4159,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 +4315,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 +4389,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());
|
||||
@@ -4731,6 +4706,10 @@ Api::InviteLinks &ApiWrap::inviteLinks() {
|
||||
return *_inviteLinks;
|
||||
}
|
||||
|
||||
Api::ViewsManager &ApiWrap::views() {
|
||||
return *_views;
|
||||
}
|
||||
|
||||
void ApiWrap::createPoll(
|
||||
const PollData &data,
|
||||
const SendAction &action,
|
||||
@@ -4768,7 +4747,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)
|
||||
|
||||
@@ -62,6 +62,7 @@ class SensitiveContent;
|
||||
class GlobalPrivacy;
|
||||
class UserPrivacy;
|
||||
class InviteLinks;
|
||||
class ViewsManager;
|
||||
|
||||
namespace details {
|
||||
|
||||
@@ -400,6 +401,7 @@ public:
|
||||
[[nodiscard]] Api::GlobalPrivacy &globalPrivacy();
|
||||
[[nodiscard]] Api::UserPrivacy &userPrivacy();
|
||||
[[nodiscard]] Api::InviteLinks &inviteLinks();
|
||||
[[nodiscard]] Api::ViewsManager &views();
|
||||
|
||||
void createPoll(
|
||||
const PollData &data,
|
||||
@@ -456,7 +458,6 @@ private:
|
||||
void saveDraftsToCloud();
|
||||
|
||||
void resolveMessageDatas();
|
||||
void gotMessageDatas(ChannelData *channel, const MTPmessages_Messages &result, mtpRequestId requestId);
|
||||
void finalizeMessageDataRequest(
|
||||
ChannelData *channel,
|
||||
mtpRequestId requestId);
|
||||
@@ -720,6 +721,7 @@ private:
|
||||
const std::unique_ptr<Api::GlobalPrivacy> _globalPrivacy;
|
||||
const std::unique_ptr<Api::UserPrivacy> _userPrivacy;
|
||||
const std::unique_ptr<Api::InviteLinks> _inviteLinks;
|
||||
const std::unique_ptr<Api::ViewsManager> _views;
|
||||
|
||||
base::flat_map<FullMsgId, mtpRequestId> _pollVotesRequestIds;
|
||||
base::flat_map<FullMsgId, mtpRequestId> _pollCloseRequestIds;
|
||||
|
||||
@@ -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;
|
||||
@@ -288,7 +288,6 @@ bool ServiceCheck::checkRippleStartPosition(QPoint position) const {
|
||||
bool out) {
|
||||
Expects(history->peer->isUser());
|
||||
|
||||
static auto id = ServerMaxMsgId + (ServerMaxMsgId / 3);
|
||||
const auto flags = MessageFlag::FakeHistoryItem
|
||||
| MessageFlag::HasFromId
|
||||
| (out ? MessageFlag::Outgoing : MessageFlag(0));
|
||||
@@ -296,7 +295,7 @@ bool ServiceCheck::checkRippleStartPosition(QPoint position) const {
|
||||
const auto viaBotId = UserId();
|
||||
const auto groupedId = uint64();
|
||||
const auto item = history->makeMessage(
|
||||
++id,
|
||||
history->nextNonHistoryEntryId(),
|
||||
flags,
|
||||
replyTo,
|
||||
viaBotId,
|
||||
@@ -305,7 +304,7 @@ bool ServiceCheck::checkRippleStartPosition(QPoint position) const {
|
||||
QString(),
|
||||
TextWithEntities{ TextUtilities::Clean(text) },
|
||||
MTP_messageMediaEmpty(),
|
||||
MTPReplyMarkup(),
|
||||
HistoryMessageMarkupData(),
|
||||
groupedId);
|
||||
return AdminLog::OwnedItem(delegate, item);
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -453,7 +453,14 @@ void EditAdminBox::transferOwnership() {
|
||||
)).fail([=](const MTP::Error &error) {
|
||||
_checkTransferRequestId = 0;
|
||||
if (!handleTransferPasswordError(error)) {
|
||||
getDelegate()->show(Box<ConfirmBox>(
|
||||
const auto box = std::make_shared<QPointer<ConfirmBox>>();
|
||||
const auto callback = crl::guard(this, [=] {
|
||||
transferOwnershipChecked();
|
||||
if (*box) {
|
||||
(*box)->closeBox();
|
||||
}
|
||||
});
|
||||
*box = getDelegate()->show(Box<ConfirmBox>(
|
||||
tr::lng_rights_transfer_about(
|
||||
tr::now,
|
||||
lt_group,
|
||||
@@ -462,7 +469,7 @@ void EditAdminBox::transferOwnership() {
|
||||
Ui::Text::Bold(user()->shortName()),
|
||||
Ui::Text::RichLangValue),
|
||||
tr::lng_rights_transfer_sure(tr::now),
|
||||
crl::guard(this, [=] { transferOwnershipChecked(); })));
|
||||
callback));
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -1113,7 +1113,7 @@ QString AppendShareGameScoreUrl(
|
||||
auto channelAccessHash = uint64(channel ? channel->access : 0);
|
||||
shareHashDataInts[0] = session->userId().bare;
|
||||
shareHashDataInts[1] = fullId.channel.bare;
|
||||
shareHashDataInts[2] = fullId.msg;
|
||||
shareHashDataInts[2] = uint64(fullId.msg.bare);
|
||||
shareHashDataInts[3] = channelAccessHash;
|
||||
|
||||
// Count SHA1() of data.
|
||||
@@ -1200,7 +1200,6 @@ void ShareGameScoreByHash(
|
||||
//}
|
||||
|
||||
if (((hashDataInts[1] >> 40) != 0)
|
||||
|| ((hashDataInts[2] >> 32) != 0)
|
||||
|| (!hashDataInts[1] && channelAccessHash)) {
|
||||
// If there is no channel id, there should be no channel access_hash.
|
||||
Ui::show(Box<InformBox>(tr::lng_share_wrong_user(tr::now)));
|
||||
@@ -1208,7 +1207,7 @@ void ShareGameScoreByHash(
|
||||
}
|
||||
|
||||
auto channelId = ChannelId(hashDataInts[1]);
|
||||
auto msgId = MsgId(hashDataInts[2]);
|
||||
auto msgId = MsgId(int64(hashDataInts[2]));
|
||||
if (const auto item = session->data().message(channelId, msgId)) {
|
||||
FastShareMessage(item);
|
||||
} else {
|
||||
|
||||
@@ -17,7 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/application.h"
|
||||
#include "mtproto/sender.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "dialogs/dialogs_layout.h"
|
||||
#include "dialogs/ui/dialogs_layout.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/image/image.h"
|
||||
|
||||
@@ -21,7 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/sticker_set_box.h"
|
||||
#include "apiwrap.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "dialogs/dialogs_layout.h"
|
||||
#include "dialogs/ui/dialogs_layout.h"
|
||||
#include "lottie/lottie_single_player.h"
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
@@ -64,7 +64,7 @@ private:
|
||||
void setCounter(int counter);
|
||||
|
||||
QString _text;
|
||||
Dialogs::Layout::UnreadBadgeStyle _st;
|
||||
Dialogs::Ui::UnreadBadgeStyle _st;
|
||||
|
||||
};
|
||||
|
||||
@@ -303,7 +303,7 @@ StickersBox::CounterWidget::CounterWidget(
|
||||
: RpWidget(parent) {
|
||||
setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
_st.sizeId = Dialogs::Layout::UnreadBadgeInStickersBox;
|
||||
_st.sizeId = Dialogs::Ui::UnreadBadgeInStickersBox;
|
||||
_st.textTop = st::stickersFeaturedBadgeTextTop;
|
||||
_st.size = st::stickersFeaturedBadgeSize;
|
||||
_st.padding = st::stickersFeaturedBadgePadding;
|
||||
@@ -323,7 +323,7 @@ void StickersBox::CounterWidget::setCounter(int counter) {
|
||||
Painter p(&dummy);
|
||||
|
||||
auto newWidth = 0;
|
||||
Dialogs::Layout::paintUnreadCount(p, _text, 0, 0, _st, &newWidth);
|
||||
Dialogs::Ui::paintUnreadCount(p, _text, 0, 0, _st, &newWidth);
|
||||
|
||||
resize(newWidth, st::stickersFeaturedBadgeSize);
|
||||
}
|
||||
@@ -334,7 +334,7 @@ void StickersBox::CounterWidget::paintEvent(QPaintEvent *e) {
|
||||
if (!_text.isEmpty()) {
|
||||
auto unreadRight = rtl() ? 0 : width();
|
||||
auto unreadTop = 0;
|
||||
Dialogs::Layout::paintUnreadCount(p, _text, unreadRight, unreadTop, _st);
|
||||
Dialogs::Ui::paintUnreadCount(p, _text, unreadRight, unreadTop, _st);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ private:
|
||||
};
|
||||
|
||||
BoxController::Row::Row(not_null<HistoryItem*> item)
|
||||
: PeerListRow(item->history()->peer, item->id)
|
||||
: PeerListRow(item->history()->peer, item->id.bare)
|
||||
, _items(1, item)
|
||||
, _date(ItemDateTime(item).date())
|
||||
, _type(ComputeType(item))
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -16,7 +16,7 @@ struct SendCommandRequest {
|
||||
not_null<PeerData*> peer;
|
||||
QString command;
|
||||
FullMsgId context;
|
||||
int replyTo = 0;
|
||||
MsgId replyTo = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] QString WrapCommandInChat(
|
||||
|
||||
@@ -199,9 +199,9 @@ bool BotKeyboard::moderateKeyActivate(int key) {
|
||||
if (const auto markup = item->Get<HistoryMessageReplyMarkup>()) {
|
||||
if (key >= Qt::Key_1 && key <= Qt::Key_2) {
|
||||
const auto index = int(key - Qt::Key_1);
|
||||
if (!markup->rows.empty()
|
||||
if (!markup->data.rows.empty()
|
||||
&& index >= 0
|
||||
&& index < int(markup->rows.front().size())) {
|
||||
&& index < int(markup->data.rows.front().size())) {
|
||||
App::activateBotCommand(_controller, item, 0, index);
|
||||
return true;
|
||||
}
|
||||
@@ -257,14 +257,14 @@ bool BotKeyboard::updateMarkup(HistoryItem *to, bool force) {
|
||||
_singleUse = _forceReply || (markupFlags & ReplyMarkupFlag::SingleUse);
|
||||
|
||||
if (const auto markup = to->Get<HistoryMessageReplyMarkup>()) {
|
||||
_placeholder = markup->placeholder;
|
||||
_placeholder = markup->data.placeholder;
|
||||
} else {
|
||||
_placeholder = QString();
|
||||
}
|
||||
|
||||
_impl = nullptr;
|
||||
if (auto markup = to->Get<HistoryMessageReplyMarkup>()) {
|
||||
if (!markup->rows.empty()) {
|
||||
if (!markup->data.rows.empty()) {
|
||||
_impl = std::make_unique<ReplyKeyboard>(
|
||||
to,
|
||||
std::make_unique<Style>(this, *_st));
|
||||
|
||||
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"
|
||||
@@ -168,11 +168,10 @@ FieldAutocomplete::FieldAutocomplete(
|
||||
|
||||
hide();
|
||||
|
||||
connect(
|
||||
_scroll,
|
||||
&Ui::ScrollArea::geometryChanged,
|
||||
_inner,
|
||||
&Inner::onParentGeometryChanged);
|
||||
_scroll->geometryChanged(
|
||||
) | rpl::start_with_next(crl::guard(_inner, [=] {
|
||||
_inner->onParentGeometryChanged();
|
||||
}), lifetime());
|
||||
}
|
||||
|
||||
not_null<Window::SessionController*> FieldAutocomplete::controller() const {
|
||||
@@ -667,7 +666,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;
|
||||
|
||||
};
|
||||
|
||||
@@ -31,7 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "storage/storage_account.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "mainwindow.h"
|
||||
#include "dialogs/dialogs_layout.h"
|
||||
#include "dialogs/ui/dialogs_layout.h"
|
||||
#include "boxes/sticker_set_box.h"
|
||||
#include "boxes/stickers_box.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
|
||||
@@ -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,55 @@ 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"
|
||||
},
|
||||
{
|
||||
3001006,
|
||||
"- Show small media previews in chats list.\n"
|
||||
|
||||
"- Show media album previews and caption text in chats list.\n"
|
||||
|
||||
"- Add \"Quick Reply\" and \"Mark as Read\" "
|
||||
"to native Windows notifications.\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 = 3001006;
|
||||
constexpr auto AppVersionStr = "3.1.6";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -148,7 +148,7 @@ struct MessageUpdate {
|
||||
NewMaybeAdded = (1U << 7),
|
||||
RepliesUnreadCount = (1U << 8),
|
||||
|
||||
LastUsedBit = (1U << 7),
|
||||
LastUsedBit = (1U << 8),
|
||||
};
|
||||
using Flags = base::flags<Flag>;
|
||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -19,6 +19,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace Data {
|
||||
|
||||
DraftKey DraftKey::FromSerializedOld(int32 value) {
|
||||
return !value
|
||||
? DraftKey::None()
|
||||
: (value == kLocalDraftIndex + kEditDraftShiftOld)
|
||||
? DraftKey::LocalEdit()
|
||||
: (value == kScheduledDraftIndex + kEditDraftShiftOld)
|
||||
? DraftKey::ScheduledEdit()
|
||||
: (value > 0 && value < 0x4000'0000)
|
||||
? DraftKey::Replies(int64(value))
|
||||
: (value > kEditDraftShiftOld
|
||||
&& value < kEditDraftShiftOld + 0x4000'000)
|
||||
? DraftKey::RepliesEdit(int64(value - kEditDraftShiftOld))
|
||||
: DraftKey::None();
|
||||
}
|
||||
|
||||
Draft::Draft(
|
||||
const TextWithTags &textWithTags,
|
||||
MsgId msgId,
|
||||
|
||||
@@ -75,19 +75,21 @@ public:
|
||||
return kScheduledDraftIndex + kEditDraftShift;
|
||||
}
|
||||
[[nodiscard]] static DraftKey Replies(MsgId rootId) {
|
||||
return rootId;
|
||||
return rootId.bare;
|
||||
}
|
||||
[[nodiscard]] static DraftKey RepliesEdit(MsgId rootId) {
|
||||
return rootId + kEditDraftShift;
|
||||
return rootId.bare + kEditDraftShift;
|
||||
}
|
||||
|
||||
[[nodiscard]] static DraftKey FromSerialized(int32 value) {
|
||||
[[nodiscard]] static DraftKey FromSerialized(qint64 value) {
|
||||
return value;
|
||||
}
|
||||
[[nodiscard]] int32 serialize() const {
|
||||
[[nodiscard]] qint64 serialize() const {
|
||||
return _value;
|
||||
}
|
||||
|
||||
[[nodiscard]] static DraftKey FromSerializedOld(int32 value);
|
||||
|
||||
inline bool operator<(const DraftKey &other) const {
|
||||
return _value < other._value;
|
||||
}
|
||||
@@ -111,15 +113,16 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
DraftKey(int value) : _value(value) {
|
||||
DraftKey(int64 value) : _value(value) {
|
||||
}
|
||||
|
||||
static constexpr auto kLocalDraftIndex = -1;
|
||||
static constexpr auto kCloudDraftIndex = -2;
|
||||
static constexpr auto kScheduledDraftIndex = -3;
|
||||
static constexpr auto kEditDraftShift = ServerMaxMsgId;
|
||||
static constexpr auto kEditDraftShift = ServerMaxMsgId.bare;
|
||||
static constexpr auto kEditDraftShiftOld = 0x3FFF'FFFF;
|
||||
|
||||
int _value = 0;
|
||||
int64 _value = 0;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/data_groups.h"
|
||||
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "dialogs/ui/dialogs_message_view.h"
|
||||
#include "data/data_media_types.h"
|
||||
#include "data/data_session.h"
|
||||
|
||||
@@ -140,8 +142,13 @@ const Group *Groups::find(not_null<const HistoryItem*> item) const {
|
||||
}
|
||||
|
||||
void Groups::refreshViews(const HistoryItemsList &items) {
|
||||
if (items.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto history = items.front()->history();
|
||||
for (const auto &item : items) {
|
||||
_data->requestItemViewRefresh(item);
|
||||
history->lastItemDialogsView.itemInvalidated(item);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ class Session;
|
||||
|
||||
struct Group {
|
||||
HistoryItemsList items;
|
||||
|
||||
};
|
||||
|
||||
class Groups {
|
||||
|
||||
@@ -72,13 +72,13 @@ void Histories::readInbox(not_null<History*> history) {
|
||||
if (history->lastServerMessageKnown()) {
|
||||
const auto last = history->lastServerMessage();
|
||||
DEBUG_LOG(("Reading: last known, reading till %1."
|
||||
).arg(last ? last->id : 0));
|
||||
).arg(last ? last->id.bare : 0));
|
||||
readInboxTill(history, last ? last->id : 0);
|
||||
return;
|
||||
} else if (history->loadedAtBottom()) {
|
||||
if (const auto lastId = history->maxMsgId()) {
|
||||
DEBUG_LOG(("Reading: loaded at bottom, maxMsgId %1."
|
||||
).arg(lastId));
|
||||
).arg(lastId.bare));
|
||||
readInboxTill(history, lastId);
|
||||
return;
|
||||
} else if (history->loadedAtTop()) {
|
||||
@@ -93,7 +93,7 @@ void Histories::readInbox(not_null<History*> history) {
|
||||
|
||||
const auto last = history->lastServerMessage();
|
||||
DEBUG_LOG(("Reading: got entry, reading till %1."
|
||||
).arg(last ? last->id : 0));
|
||||
).arg(last ? last->id.bare : 0));
|
||||
readInboxTill(history, last ? last->id : 0);
|
||||
});
|
||||
}
|
||||
@@ -147,7 +147,7 @@ void Histories::readInboxTill(
|
||||
Expects(IsServerMsgId(tillId) || (!tillId && !force));
|
||||
|
||||
DEBUG_LOG(("Reading: readInboxTill %1, force %2."
|
||||
).arg(tillId
|
||||
).arg(tillId.bare
|
||||
).arg(Logs::b(force)));
|
||||
|
||||
const auto syncGuard = gsl::finally([&] {
|
||||
@@ -156,8 +156,8 @@ void Histories::readInboxTill(
|
||||
if (history->unreadCount() > 0) {
|
||||
if (const auto last = history->lastServerMessage()) {
|
||||
DEBUG_LOG(("Reading: checking last %1 and %2."
|
||||
).arg(last->id
|
||||
).arg(tillId));
|
||||
).arg(last->id.bare
|
||||
).arg(tillId.bare));
|
||||
if (last->id == tillId) {
|
||||
DEBUG_LOG(("Reading: locally marked as read."));
|
||||
history->setUnreadCount(0);
|
||||
@@ -180,11 +180,11 @@ void Histories::readInboxTill(
|
||||
const auto maybeState = lookup(history);
|
||||
if (maybeState && maybeState->sentReadTill >= tillId) {
|
||||
DEBUG_LOG(("Reading: readInboxTill finish 3 with %1."
|
||||
).arg(maybeState->sentReadTill));
|
||||
).arg(maybeState->sentReadTill.bare));
|
||||
return;
|
||||
} else if (maybeState && maybeState->willReadTill >= tillId) {
|
||||
DEBUG_LOG(("Reading: readInboxTill finish 4 with %1 and force %2."
|
||||
).arg(maybeState->sentReadTill
|
||||
).arg(maybeState->sentReadTill.bare
|
||||
).arg(Logs::b(force)));
|
||||
if (force) {
|
||||
sendPendingReadInbox(history);
|
||||
@@ -200,7 +200,7 @@ void Histories::readInboxTill(
|
||||
&& history->unreadCountKnown()
|
||||
&& *stillUnread == history->unreadCount()) {
|
||||
DEBUG_LOG(("Reading: count didn't change so just update till %1"
|
||||
).arg(tillId));
|
||||
).arg(tillId.bare));
|
||||
history->setInboxReadTill(tillId);
|
||||
return;
|
||||
}
|
||||
@@ -208,7 +208,7 @@ void Histories::readInboxTill(
|
||||
state.willReadTill = tillId;
|
||||
if (force || !stillUnread || !*stillUnread) {
|
||||
DEBUG_LOG(("Reading: will read till %1 with still unread %2"
|
||||
).arg(tillId
|
||||
).arg(tillId.bare
|
||||
).arg(stillUnread.value_or(-666)));
|
||||
state.willReadWhen = 0;
|
||||
sendReadRequests();
|
||||
@@ -216,17 +216,18 @@ void Histories::readInboxTill(
|
||||
return;
|
||||
}
|
||||
} else if (!state.willReadWhen) {
|
||||
DEBUG_LOG(("Reading: will read till %1 with postponed").arg(tillId));
|
||||
DEBUG_LOG(("Reading: will read till %1 with postponed"
|
||||
).arg(tillId.bare));
|
||||
state.willReadWhen = crl::now() + kReadRequestTimeout;
|
||||
if (!_readRequestsTimer.isActive()) {
|
||||
_readRequestsTimer.callOnce(kReadRequestTimeout);
|
||||
}
|
||||
} else {
|
||||
DEBUG_LOG(("Reading: will read till %1 postponed already"
|
||||
).arg(tillId));
|
||||
).arg(tillId.bare));
|
||||
}
|
||||
DEBUG_LOG(("Reading: marking now with till %1 and still %2"
|
||||
).arg(tillId
|
||||
).arg(tillId.bare
|
||||
).arg(*stillUnread));
|
||||
history->setInboxReadTill(tillId);
|
||||
history->setUnreadCount(*stillUnread);
|
||||
@@ -437,10 +438,53 @@ void Histories::requestFakeChatListMessage(
|
||||
});
|
||||
}
|
||||
|
||||
void Histories::requestGroupAround(not_null<HistoryItem*> item) {
|
||||
const auto history = item->history();
|
||||
const auto id = item->id;
|
||||
const auto i = _chatListGroupRequests.find(history);
|
||||
if (i != end(_chatListGroupRequests)) {
|
||||
if (i->second.aroundId == id) {
|
||||
return;
|
||||
} else {
|
||||
cancelRequest(i->second.requestId);
|
||||
_chatListGroupRequests.erase(i);
|
||||
}
|
||||
}
|
||||
constexpr auto kMaxAlbumCount = 10;
|
||||
const auto requestId = sendRequest(history, RequestType::History, [=](
|
||||
Fn<void()> finish) {
|
||||
return session().api().request(MTPmessages_GetHistory(
|
||||
history->peer->input,
|
||||
MTP_int(id),
|
||||
MTP_int(0), // offset_date
|
||||
MTP_int(-kMaxAlbumCount),
|
||||
MTP_int(2 * kMaxAlbumCount - 1),
|
||||
MTP_int(0), // max_id
|
||||
MTP_int(0), // min_id
|
||||
MTP_long(0) // hash
|
||||
)).done([=](const MTPmessages_Messages &result) {
|
||||
_owner->processExistingMessages(
|
||||
history->peer->asChannel(),
|
||||
result);
|
||||
_chatListGroupRequests.remove(history);
|
||||
history->migrateToOrMe()->applyChatListGroup(
|
||||
history->channelId(),
|
||||
result);
|
||||
finish();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_chatListGroupRequests.remove(history);
|
||||
finish();
|
||||
}).send();
|
||||
});
|
||||
_chatListGroupRequests.emplace(
|
||||
history,
|
||||
ChatListGroupRequest{ .aroundId = id, .requestId = requestId });
|
||||
}
|
||||
|
||||
void Histories::sendPendingReadInbox(not_null<History*> history) {
|
||||
if (const auto state = lookup(history)) {
|
||||
DEBUG_LOG(("Reading: send pending now with till %1 and when %2"
|
||||
).arg(state->willReadTill
|
||||
).arg(state->willReadTill.bare
|
||||
).arg(state->willReadWhen));
|
||||
if (state->willReadTill && state->willReadWhen) {
|
||||
state->willReadWhen = 0;
|
||||
@@ -462,7 +506,7 @@ void Histories::sendReadRequests() {
|
||||
continue;
|
||||
} else if (state.willReadWhen <= now) {
|
||||
DEBUG_LOG(("Reading: sending with till %1."
|
||||
).arg(state.willReadTill));
|
||||
).arg(state.willReadTill.bare));
|
||||
sendReadRequest(history, state);
|
||||
} else if (!next || *next > state.willReadWhen) {
|
||||
DEBUG_LOG(("Reading: scheduling for later send."));
|
||||
@@ -483,10 +527,10 @@ void Histories::sendReadRequest(not_null<History*> history, State &state) {
|
||||
state.willReadWhen = 0;
|
||||
state.sentReadDone = false;
|
||||
DEBUG_LOG(("Reading: sending request now with till %1."
|
||||
).arg(tillId));
|
||||
).arg(tillId.bare));
|
||||
sendRequest(history, RequestType::ReadInbox, [=](Fn<void()> finish) {
|
||||
DEBUG_LOG(("Reading: sending request invoked with till %1."
|
||||
).arg(tillId));
|
||||
).arg(tillId.bare));
|
||||
const auto finished = [=] {
|
||||
const auto state = lookup(history);
|
||||
Assert(state != nullptr);
|
||||
|
||||
@@ -59,6 +59,8 @@ public:
|
||||
void changeDialogUnreadMark(not_null<History*> history, bool unread);
|
||||
void requestFakeChatListMessage(not_null<History*> history);
|
||||
|
||||
void requestGroupAround(not_null<HistoryItem*> item);
|
||||
|
||||
void deleteMessages(
|
||||
not_null<History*> history,
|
||||
const QVector<MTPint> &ids,
|
||||
@@ -95,6 +97,10 @@ private:
|
||||
bool sentReadDone = false;
|
||||
bool postponedRequestEntry = false;
|
||||
};
|
||||
struct ChatListGroupRequest {
|
||||
MsgId aroundId = 0;
|
||||
mtpRequestId requestId = 0;
|
||||
};
|
||||
|
||||
void readInboxTill(not_null<History*> history, MsgId tillId, bool force);
|
||||
void sendReadRequests();
|
||||
@@ -130,6 +136,10 @@ private:
|
||||
|
||||
base::flat_set<not_null<History*>> _fakeChatListRequests;
|
||||
|
||||
base::flat_map<
|
||||
not_null<History*>,
|
||||
ChatListGroupRequest> _chatListGroupRequests;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/view/media/history_view_theme_document.h"
|
||||
#include "history/view/media/history_view_slot_machine.h"
|
||||
#include "history/view/media/history_view_dice.h"
|
||||
#include "dialogs/ui/dialogs_message_view.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/text/format_song_document_name.h"
|
||||
#include "ui/text/format_values.h"
|
||||
@@ -37,23 +38,32 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "storage/localstorage.h"
|
||||
#include "chat_helpers/stickers_dice_pack.h" // Stickers::DicePacks::IsSlot.
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_auto_download.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_photo_media.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_game.h"
|
||||
#include "data/data_web_page.h"
|
||||
#include "data/data_poll.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_session_settings.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "storage/file_upload.h"
|
||||
#include "app.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
constexpr auto kFastRevokeRestriction = 24 * 60 * TimeId(60);
|
||||
constexpr auto kMaxPreviewImages = 3;
|
||||
|
||||
using ItemPreview = HistoryView::ItemPreview;
|
||||
using ItemPreviewImage = HistoryView::ItemPreviewImage;
|
||||
|
||||
[[nodiscard]] Call ComputeCallData(const MTPDmessageActionPhoneCall &call) {
|
||||
auto result = Call();
|
||||
@@ -98,20 +108,23 @@ constexpr auto kFastRevokeRestriction = 24 * 60 * TimeId(60);
|
||||
|
||||
[[nodiscard]] QString WithCaptionDialogsText(
|
||||
const QString &attachType,
|
||||
const QString &caption) {
|
||||
const QString &caption,
|
||||
bool hasMiniImages) {
|
||||
if (caption.isEmpty()) {
|
||||
return textcmdLink(1, TextUtilities::Clean(attachType));
|
||||
}
|
||||
|
||||
return tr::lng_dialogs_text_media(
|
||||
tr::now,
|
||||
lt_media_part,
|
||||
textcmdLink(1, tr::lng_dialogs_text_media_wrapped(
|
||||
return hasMiniImages
|
||||
? TextUtilities::Clean(caption)
|
||||
: tr::lng_dialogs_text_media(
|
||||
tr::now,
|
||||
lt_media,
|
||||
TextUtilities::Clean(attachType))),
|
||||
lt_caption,
|
||||
TextUtilities::Clean(caption));
|
||||
lt_media_part,
|
||||
textcmdLink(1, tr::lng_dialogs_text_media_wrapped(
|
||||
tr::now,
|
||||
lt_media,
|
||||
TextUtilities::Clean(attachType))),
|
||||
lt_caption,
|
||||
TextUtilities::Clean(caption));
|
||||
}
|
||||
|
||||
[[nodiscard]] QString WithCaptionNotificationText(
|
||||
@@ -132,6 +145,151 @@ constexpr auto kFastRevokeRestriction = 24 * 60 * TimeId(60);
|
||||
caption);
|
||||
}
|
||||
|
||||
[[nodiscard]] QImage PreparePreviewImage(
|
||||
not_null<const Image*> image,
|
||||
ImageRoundRadius radius = ImageRoundRadius::Small) {
|
||||
const auto original = image->original();
|
||||
if (original.width() * 10 < original.height()
|
||||
|| original.height() * 10 < original.width()) {
|
||||
return QImage();
|
||||
}
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto size = st::dialogsMiniPreview * factor;
|
||||
const auto scaled = original.scaled(
|
||||
QSize(size, size),
|
||||
Qt::KeepAspectRatioByExpanding,
|
||||
Qt::SmoothTransformation);
|
||||
auto square = scaled.copy(
|
||||
(scaled.width() - size) / 2,
|
||||
(scaled.height() - size) / 2,
|
||||
size,
|
||||
size
|
||||
).convertToFormat(QImage::Format_ARGB32_Premultiplied);
|
||||
if (radius == ImageRoundRadius::Small) {
|
||||
struct Cache {
|
||||
base::flat_map<int, std::array<QImage, 4>> all;
|
||||
std::array<QImage, 4> *lastUsed = nullptr;
|
||||
int lastUsedRadius = 0;
|
||||
};
|
||||
static auto cache = Cache();
|
||||
const auto pxRadius = st::dialogsMiniPreviewRadius;
|
||||
if (!cache.lastUsed || cache.lastUsedRadius != pxRadius) {
|
||||
cache.lastUsedRadius = pxRadius;
|
||||
const auto i = cache.all.find(pxRadius);
|
||||
if (i != end(cache.all)) {
|
||||
cache.lastUsed = &i->second;
|
||||
} else {
|
||||
cache.lastUsed = &cache.all.emplace(
|
||||
pxRadius,
|
||||
Images::CornersMask(pxRadius)).first->second;
|
||||
}
|
||||
}
|
||||
Images::prepareRound(square, *cache.lastUsed);
|
||||
} else {
|
||||
Images::prepareRound(square, radius);
|
||||
}
|
||||
square.setDevicePixelRatio(factor);
|
||||
return square;
|
||||
}
|
||||
|
||||
[[nodiscard]] ItemPreviewImage PreparePhotoPreview(
|
||||
not_null<const HistoryItem*> item,
|
||||
const std::shared_ptr<PhotoMedia> &media,
|
||||
ImageRoundRadius radius) {
|
||||
const auto photo = media->owner();
|
||||
const auto readyCacheKey = reinterpret_cast<uint64>(photo.get());
|
||||
if (const auto small = media->image(PhotoSize::Small)) {
|
||||
return { PreparePreviewImage(small, radius), readyCacheKey };
|
||||
} else if (const auto thumbnail = media->image(PhotoSize::Thumbnail)) {
|
||||
return { PreparePreviewImage(thumbnail, radius), readyCacheKey };
|
||||
} else if (const auto large = media->image(PhotoSize::Large)) {
|
||||
return { PreparePreviewImage(large, radius), readyCacheKey };
|
||||
}
|
||||
const auto allowedToDownload = [&] {
|
||||
const auto photo = media->owner();
|
||||
if (media->loaded() || photo->cancelled()) {
|
||||
return false;
|
||||
}
|
||||
return photo->hasExact(PhotoSize::Small)
|
||||
|| photo->hasExact(PhotoSize::Thumbnail)
|
||||
|| AutoDownload::Should(
|
||||
photo->session().settings().autoDownload(),
|
||||
item->history()->peer,
|
||||
photo);
|
||||
}();
|
||||
const auto cacheKey = allowedToDownload ? 0 : readyCacheKey;
|
||||
if (allowedToDownload) {
|
||||
media->owner()->load(PhotoSize::Small, item->fullId());
|
||||
}
|
||||
if (const auto blurred = media->thumbnailInline()) {
|
||||
return { PreparePreviewImage(blurred, radius), cacheKey };
|
||||
}
|
||||
return { QImage(), allowedToDownload ? 0 : cacheKey };
|
||||
}
|
||||
|
||||
[[nodiscard]] ItemPreviewImage PrepareFilePreviewImage(
|
||||
not_null<const HistoryItem*> item,
|
||||
const std::shared_ptr<DocumentMedia> &media,
|
||||
ImageRoundRadius radius) {
|
||||
Expects(media->owner()->hasThumbnail());
|
||||
|
||||
const auto document = media->owner();
|
||||
const auto readyCacheKey = reinterpret_cast<uint64>(document.get());
|
||||
if (const auto thumbnail = media->thumbnail()) {
|
||||
return { PreparePreviewImage(thumbnail, radius), readyCacheKey };
|
||||
}
|
||||
document->loadThumbnail(item->fullId());
|
||||
if (const auto blurred = media->thumbnailInline()) {
|
||||
return { PreparePreviewImage(blurred, radius), 0 };
|
||||
}
|
||||
return { QImage(), 0 };
|
||||
}
|
||||
|
||||
[[nodiscard]] QImage PutPlayIcon(QImage preview) {
|
||||
Expects(!preview.isNull());
|
||||
|
||||
{
|
||||
QPainter p(&preview);
|
||||
st::dialogsMiniPlay.paintInCenter(
|
||||
p,
|
||||
QRect(QPoint(), preview.size() / preview.devicePixelRatio()));
|
||||
}
|
||||
return preview;
|
||||
}
|
||||
|
||||
[[nodiscard]] ItemPreviewImage PrepareFilePreview(
|
||||
not_null<const HistoryItem*> item,
|
||||
const std::shared_ptr<DocumentMedia> &media,
|
||||
ImageRoundRadius radius) {
|
||||
auto result = PrepareFilePreviewImage(item, media, radius);
|
||||
const auto document = media->owner();
|
||||
if (!result.data.isNull()
|
||||
&& (document->isVideoFile() || document->isVideoMessage())) {
|
||||
result.data = PutPlayIcon(std::move(result.data));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool TryFilePreview(not_null<DocumentData*> document) {
|
||||
return document->hasThumbnail()
|
||||
&& !document->sticker()
|
||||
&& !document->isAudioFile();
|
||||
}
|
||||
|
||||
template <typename MediaType>
|
||||
[[nodiscard]] ItemPreviewImage FindCachedPreview(
|
||||
const std::vector<ItemPreviewImage> *existing,
|
||||
not_null<MediaType*> data) {
|
||||
if (!existing) {
|
||||
return {};
|
||||
}
|
||||
const auto i = ranges::find(
|
||||
*existing,
|
||||
reinterpret_cast<uint64>(data.get()),
|
||||
&ItemPreviewImage::cacheKey);
|
||||
return (i != end(*existing)) ? *i : ItemPreviewImage();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TextForMimeData WithCaptionClipboardText(
|
||||
@@ -201,11 +359,12 @@ bool Media::canBeGrouped() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
QString Media::chatListText(DrawInDialog way) const {
|
||||
ItemPreview Media::toPreview(ToPreviewOptions options) const {
|
||||
auto result = notificationText();
|
||||
return result.isEmpty()
|
||||
auto text = result.isEmpty()
|
||||
? QString()
|
||||
: textcmdLink(1, TextUtilities::Clean(std::move(result)));
|
||||
return { .text = std::move(text) };
|
||||
}
|
||||
|
||||
bool Media::hasReplyPreview() const {
|
||||
@@ -270,6 +429,52 @@ std::unique_ptr<HistoryView::Media> Media::createView(
|
||||
return createView(message, message->data(), replacing);
|
||||
}
|
||||
|
||||
ItemPreview Media::toGroupPreview(
|
||||
const HistoryItemsList &items,
|
||||
ToPreviewOptions options) const {
|
||||
const auto genericText = textcmdLink(
|
||||
1,
|
||||
TextUtilities::Clean(tr::lng_in_dlg_album(tr::now)));
|
||||
auto result = ItemPreview();
|
||||
auto loadingContext = std::vector<std::any>();
|
||||
for (const auto &item : items) {
|
||||
if (const auto media = item->media()) {
|
||||
auto copy = options;
|
||||
copy.ignoreGroup = true;
|
||||
const auto already = int(result.images.size());
|
||||
const auto left = kMaxPreviewImages - already;
|
||||
auto single = left ? media->toPreview(copy) : ItemPreview();
|
||||
if (!single.images.empty()) {
|
||||
while (single.images.size() > left) {
|
||||
single.images.pop_back();
|
||||
}
|
||||
result.images.insert(
|
||||
end(result.images),
|
||||
std::make_move_iterator(begin(single.images)),
|
||||
std::make_move_iterator(end(single.images)));
|
||||
}
|
||||
if (single.loadingContext.has_value()) {
|
||||
loadingContext.push_back(std::move(single.loadingContext));
|
||||
}
|
||||
const auto original = item->originalText().text;
|
||||
if (!original.isEmpty()) {
|
||||
if (result.text.isEmpty()) {
|
||||
result.text = TextUtilities::Clean(original);
|
||||
} else {
|
||||
result.text = genericText;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result.text.isEmpty()) {
|
||||
result.text = genericText;
|
||||
}
|
||||
if (!loadingContext.empty()) {
|
||||
result.loadingContext = std::move(loadingContext);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
MediaPhoto::MediaPhoto(
|
||||
not_null<HistoryItem*> parent,
|
||||
not_null<PhotoData*> photo)
|
||||
@@ -341,11 +546,40 @@ QString MediaPhoto::notificationText() const {
|
||||
parent()->originalText().text);
|
||||
}
|
||||
|
||||
QString MediaPhoto::chatListText(DrawInDialog way) const {
|
||||
const auto caption = (way == DrawInDialog::WithoutSenderAndCaption)
|
||||
ItemPreview MediaPhoto::toPreview(ToPreviewOptions options) const {
|
||||
const auto item = parent();
|
||||
if (!options.ignoreGroup && item->groupId()) {
|
||||
if (const auto group = item->history()->owner().groups().find(item)
|
||||
; group && group->items.size() > 1) {
|
||||
return toGroupPreview(group->items, options);
|
||||
}
|
||||
}
|
||||
auto images = std::vector<ItemPreviewImage>();
|
||||
auto context = std::any();
|
||||
if (auto cached = FindCachedPreview(options.existing, _photo)) {
|
||||
images.push_back(std::move(cached));
|
||||
} else {
|
||||
const auto media = _photo->createMediaView();
|
||||
const auto radius = _chat
|
||||
? ImageRoundRadius::Ellipse
|
||||
: ImageRoundRadius::Small;
|
||||
if (auto prepared = PreparePhotoPreview(parent(), media, radius)
|
||||
; prepared || !prepared.cacheKey) {
|
||||
images.push_back(std::move(prepared));
|
||||
if (!prepared.cacheKey) {
|
||||
context = media;
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto type = tr::lng_in_dlg_photo(tr::now);
|
||||
const auto caption = options.hideCaption
|
||||
? QString()
|
||||
: parent()->originalText().text;
|
||||
return WithCaptionDialogsText(tr::lng_in_dlg_photo(tr::now), caption);
|
||||
return {
|
||||
.text = WithCaptionDialogsText(type, caption, !images.empty()),
|
||||
.images = std::move(images),
|
||||
.loadingContext = std::move(context),
|
||||
};
|
||||
}
|
||||
|
||||
QString MediaPhoto::pinnedTextSubstring() const {
|
||||
@@ -511,16 +745,40 @@ bool MediaFile::replyPreviewLoaded() const {
|
||||
return _document->replyPreviewLoaded();
|
||||
}
|
||||
|
||||
QString MediaFile::chatListText(DrawInDialog way) const {
|
||||
ItemPreview MediaFile::toPreview(ToPreviewOptions options) const {
|
||||
const auto item = parent();
|
||||
if (!options.ignoreGroup && item->groupId()) {
|
||||
if (const auto group = item->history()->owner().groups().find(item)
|
||||
; group && group->items.size() > 1) {
|
||||
return toGroupPreview(group->items, options);
|
||||
}
|
||||
}
|
||||
if (const auto sticker = _document->sticker()) {
|
||||
return Media::chatListText(way);
|
||||
return Media::toPreview(options);
|
||||
}
|
||||
auto images = std::vector<ItemPreviewImage>();
|
||||
auto context = std::any();
|
||||
if (auto cached = FindCachedPreview(options.existing, _document)) {
|
||||
images.push_back(std::move(cached));
|
||||
} else if (TryFilePreview(_document)) {
|
||||
const auto media = _document->createMediaView();
|
||||
const auto radius = _document->isVideoMessage()
|
||||
? ImageRoundRadius::Ellipse
|
||||
: ImageRoundRadius::Small;
|
||||
if (auto prepared = PrepareFilePreview(parent(), media, radius)
|
||||
; prepared || !prepared.cacheKey) {
|
||||
images.push_back(std::move(prepared));
|
||||
if (!prepared.cacheKey) {
|
||||
context = media;
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto type = [&] {
|
||||
using namespace Ui::Text;
|
||||
if (_document->isVideoMessage()) {
|
||||
return tr::lng_in_dlg_video_message(tr::now);
|
||||
} else if (_document->isAnimation()) {
|
||||
return qsl("GIF");
|
||||
return u"GIF"_q;
|
||||
} else if (_document->isVideoFile()) {
|
||||
return tr::lng_in_dlg_video(tr::now);
|
||||
} else if (_document->isVoiceMessage()) {
|
||||
@@ -533,10 +791,14 @@ QString MediaFile::chatListText(DrawInDialog way) const {
|
||||
}
|
||||
return tr::lng_in_dlg_file(tr::now);
|
||||
}();
|
||||
const auto caption = (way == DrawInDialog::WithoutSenderAndCaption)
|
||||
const auto caption = options.hideCaption
|
||||
? QString()
|
||||
: parent()->originalText().text;
|
||||
return WithCaptionDialogsText(type, caption);
|
||||
return {
|
||||
.text = WithCaptionDialogsText(type, caption, !images.empty()),
|
||||
.images = std::move(images),
|
||||
.loadingContext = std::move(context),
|
||||
};
|
||||
}
|
||||
|
||||
QString MediaFile::notificationText() const {
|
||||
@@ -855,8 +1117,10 @@ Data::CloudImage *MediaLocation::location() const {
|
||||
return _location;
|
||||
}
|
||||
|
||||
QString MediaLocation::chatListText(DrawInDialog way) const {
|
||||
return WithCaptionDialogsText(tr::lng_maps_point(tr::now), _title);
|
||||
ItemPreview MediaLocation::toPreview(ToPreviewOptions options) const {
|
||||
const auto type = tr::lng_maps_point(tr::now);
|
||||
const auto hasMiniImages = false;
|
||||
return { .text = WithCaptionDialogsText(type, _title, hasMiniImages) };
|
||||
}
|
||||
|
||||
QString MediaLocation::notificationText() const {
|
||||
@@ -1049,8 +1313,8 @@ bool MediaWebPage::replyPreviewLoaded() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
QString MediaWebPage::chatListText(DrawInDialog way) const {
|
||||
return notificationText();
|
||||
ItemPreview MediaWebPage::toPreview(ToPreviewOptions options) const {
|
||||
return { .text = notificationText() };
|
||||
}
|
||||
|
||||
QString MediaWebPage::notificationText() const {
|
||||
|
||||
@@ -27,7 +27,9 @@ namespace HistoryView {
|
||||
enum class Context : char;
|
||||
class Element;
|
||||
class Media;
|
||||
enum class DrawInDialog;
|
||||
struct ItemPreview;
|
||||
struct ItemPreviewImage;
|
||||
struct ToPreviewOptions;
|
||||
} // namespace HistoryView
|
||||
|
||||
namespace Data {
|
||||
@@ -73,7 +75,9 @@ public:
|
||||
|
||||
not_null<HistoryItem*> parent() const;
|
||||
|
||||
using DrawInDialog = HistoryView::DrawInDialog;
|
||||
using ToPreviewOptions = HistoryView::ToPreviewOptions;
|
||||
using ItemPreviewImage = HistoryView::ItemPreviewImage;
|
||||
using ItemPreview = HistoryView::ItemPreview;
|
||||
|
||||
virtual std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) = 0;
|
||||
|
||||
@@ -95,7 +99,7 @@ public:
|
||||
virtual bool replyPreviewLoaded() const;
|
||||
// Returns text with link-start and link-end commands for service-color highlighting.
|
||||
// Example: "[link1-start]You:[link1-end] [link1-start]Photo,[link1-end] caption text"
|
||||
virtual QString chatListText(DrawInDialog way) const;
|
||||
virtual ItemPreview toPreview(ToPreviewOptions way) const;
|
||||
virtual QString notificationText() const = 0;
|
||||
virtual QString pinnedTextSubstring() const = 0;
|
||||
virtual TextForMimeData clipboardText() const = 0;
|
||||
@@ -125,6 +129,11 @@ public:
|
||||
not_null<HistoryView::Element*> message,
|
||||
HistoryView::Element *replacing = nullptr);
|
||||
|
||||
protected:
|
||||
[[nodiscard]] ItemPreview toGroupPreview(
|
||||
const HistoryItemsList &items,
|
||||
ToPreviewOptions options) const;
|
||||
|
||||
private:
|
||||
const not_null<HistoryItem*> _parent;
|
||||
|
||||
@@ -151,7 +160,7 @@ public:
|
||||
bool hasReplyPreview() const override;
|
||||
Image *replyPreview() const override;
|
||||
bool replyPreviewLoaded() const override;
|
||||
QString chatListText(DrawInDialog way) const override;
|
||||
ItemPreview toPreview(ToPreviewOptions options) const override;
|
||||
QString notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
TextForMimeData clipboardText() const override;
|
||||
@@ -189,7 +198,7 @@ public:
|
||||
bool hasReplyPreview() const override;
|
||||
Image *replyPreview() const override;
|
||||
bool replyPreviewLoaded() const override;
|
||||
QString chatListText(DrawInDialog way) const override;
|
||||
ItemPreview toPreview(ToPreviewOptions options) const override;
|
||||
QString notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
TextForMimeData clipboardText() const override;
|
||||
@@ -255,7 +264,7 @@ public:
|
||||
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
|
||||
|
||||
Data::CloudImage *location() const override;
|
||||
QString chatListText(DrawInDialog way) const override;
|
||||
ItemPreview toPreview(ToPreviewOptions options) const override;
|
||||
QString notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
TextForMimeData clipboardText() const override;
|
||||
@@ -323,7 +332,7 @@ public:
|
||||
bool hasReplyPreview() const override;
|
||||
Image *replyPreview() const override;
|
||||
bool replyPreviewLoaded() const override;
|
||||
QString chatListText(DrawInDialog way) const override;
|
||||
ItemPreview toPreview(ToPreviewOptions options) const override;
|
||||
QString notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
TextForMimeData clipboardText() const override;
|
||||
|
||||
192
Telegram/SourceFiles/data/data_msg_id.h
Normal file
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "data/data_peer_id.h"
|
||||
|
||||
struct MsgId {
|
||||
constexpr MsgId() noexcept = default;
|
||||
constexpr MsgId(int64 value) noexcept : bare(value) {
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr explicit operator bool() const noexcept {
|
||||
return (bare != 0);
|
||||
}
|
||||
[[nodiscard]] constexpr bool operator!() const noexcept {
|
||||
return !bare;
|
||||
}
|
||||
[[nodiscard]] constexpr MsgId operator-() const noexcept {
|
||||
return -bare;
|
||||
}
|
||||
constexpr MsgId operator++() noexcept {
|
||||
return ++bare;
|
||||
}
|
||||
constexpr MsgId operator++(int) noexcept {
|
||||
return bare++;
|
||||
}
|
||||
constexpr MsgId operator--() noexcept {
|
||||
return --bare;
|
||||
}
|
||||
constexpr MsgId operator--(int) noexcept {
|
||||
return bare--;
|
||||
}
|
||||
|
||||
int64 bare = 0;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(MsgId);
|
||||
|
||||
[[nodiscard]] inline constexpr MsgId operator+(MsgId a, MsgId b) noexcept {
|
||||
return MsgId(a.bare + b.bare);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr MsgId operator-(MsgId a, MsgId b) noexcept {
|
||||
return MsgId(a.bare - b.bare);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator==(MsgId a, MsgId b) noexcept {
|
||||
return (a.bare == b.bare);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator!=(MsgId a, MsgId b) noexcept {
|
||||
return (a.bare != b.bare);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator<(MsgId a, MsgId b) noexcept {
|
||||
return (a.bare < b.bare);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator>(MsgId a, MsgId b) noexcept {
|
||||
return (a.bare > b.bare);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator<=(MsgId a, MsgId b) noexcept {
|
||||
return (a.bare <= b.bare);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator>=(MsgId a, MsgId b) noexcept {
|
||||
return (a.bare >= b.bare);
|
||||
}
|
||||
|
||||
constexpr auto StartClientMsgId = MsgId(0x01 - (1LL << 58));
|
||||
constexpr auto EndClientMsgId = MsgId(-(1LL << 57));
|
||||
constexpr auto ServerMaxMsgId = MsgId(1LL << 56);
|
||||
constexpr auto ShowAtUnreadMsgId = MsgId(0);
|
||||
|
||||
constexpr auto SpecialMsgIdShift = EndClientMsgId.bare;
|
||||
constexpr auto ShowAtTheEndMsgId = MsgId(SpecialMsgIdShift + 1);
|
||||
constexpr auto SwitchAtTopMsgId = MsgId(SpecialMsgIdShift + 2);
|
||||
constexpr auto ShowAtProfileMsgId = MsgId(SpecialMsgIdShift + 3);
|
||||
constexpr auto ShowAndStartBotMsgId = MsgId(SpecialMsgIdShift + 4);
|
||||
constexpr auto ShowAtGameShareMsgId = MsgId(SpecialMsgIdShift + 5);
|
||||
constexpr auto ShowForChooseMessagesMsgId = MsgId(SpecialMsgIdShift + 6);
|
||||
|
||||
static_assert(SpecialMsgIdShift + 0xFF < 0);
|
||||
static_assert(-(SpecialMsgIdShift + 0xFF) > ServerMaxMsgId);
|
||||
|
||||
[[nodiscard]] constexpr inline bool IsClientMsgId(MsgId id) noexcept {
|
||||
return (id >= StartClientMsgId && id < EndClientMsgId);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr inline bool IsServerMsgId(MsgId id) noexcept {
|
||||
return (id > 0 && id < ServerMaxMsgId);
|
||||
}
|
||||
|
||||
struct MsgRange {
|
||||
constexpr MsgRange() noexcept = default;
|
||||
constexpr MsgRange(MsgId from, MsgId till) noexcept
|
||||
: from(from)
|
||||
, till(till) {
|
||||
}
|
||||
|
||||
MsgId from = 0;
|
||||
MsgId till = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator==(
|
||||
MsgRange a,
|
||||
MsgRange b) noexcept {
|
||||
return (a.from == b.from) && (a.till == b.till);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator!=(
|
||||
MsgRange a,
|
||||
MsgRange b) noexcept {
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
struct FullMsgId {
|
||||
constexpr FullMsgId() noexcept = default;
|
||||
constexpr FullMsgId(ChannelId channel, MsgId msg) noexcept
|
||||
: channel(channel), msg(msg) {
|
||||
}
|
||||
|
||||
constexpr explicit operator bool() const noexcept {
|
||||
return msg != 0;
|
||||
}
|
||||
constexpr bool operator!() const noexcept {
|
||||
return msg == 0;
|
||||
}
|
||||
|
||||
ChannelId channel = NoChannel;
|
||||
MsgId msg = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator<(
|
||||
const FullMsgId &a,
|
||||
const FullMsgId &b) noexcept {
|
||||
if (a.channel < b.channel) {
|
||||
return true;
|
||||
} else if (a.channel > b.channel) {
|
||||
return false;
|
||||
}
|
||||
return a.msg < b.msg;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator>(
|
||||
const FullMsgId &a,
|
||||
const FullMsgId &b) noexcept {
|
||||
return b < a;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator<=(
|
||||
const FullMsgId &a,
|
||||
const FullMsgId &b) noexcept {
|
||||
return !(b < a);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator>=(
|
||||
const FullMsgId &a,
|
||||
const FullMsgId &b) noexcept {
|
||||
return !(a < b);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator==(
|
||||
const FullMsgId &a,
|
||||
const FullMsgId &b) noexcept {
|
||||
return (a.channel == b.channel) && (a.msg == b.msg);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool operator!=(
|
||||
const FullMsgId &a,
|
||||
const FullMsgId &b) noexcept {
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(FullMsgId);
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<MsgId> : private hash<int64> {
|
||||
size_t operator()(MsgId value) const noexcept {
|
||||
return hash<int64>::operator()(value.bare);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
@@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ constexpr auto kMessagesPerPage = 50;
|
||||
TimeId date,
|
||||
const QString &text) {
|
||||
return history->makeServiceMessage(
|
||||
history->session().data().nextNonHistoryEntryId(),
|
||||
history->nextNonHistoryEntryId(),
|
||||
MessageFlag::FakeHistoryItem,
|
||||
date,
|
||||
HistoryService::PreparedText{ text });
|
||||
@@ -101,7 +101,7 @@ rpl::producer<MessagesSlice> RepliesList::source(
|
||||
_partLoaded.events(
|
||||
) | rpl::start_with_next(pushDelayed, lifetime);
|
||||
|
||||
_history->session().data().channelDifferenceTooLong(
|
||||
_history->owner().channelDifferenceTooLong(
|
||||
) | rpl::filter([=](not_null<ChannelData*> channel) {
|
||||
if (_history->peer != channel || !_skippedAfter.has_value()) {
|
||||
return false;
|
||||
|
||||
@@ -32,18 +32,18 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000);
|
||||
&& (item->date() > base::unixtime::now());
|
||||
}
|
||||
|
||||
MTPMessage PrepareMessage(const MTPMessage &message, MsgId id) {
|
||||
MTPMessage PrepareMessage(const MTPMessage &message) {
|
||||
return message.match([&](const MTPDmessageEmpty &data) {
|
||||
return MTP_messageEmpty(
|
||||
data.vflags(),
|
||||
MTP_int(id),
|
||||
data.vid(),
|
||||
data.vpeer_id() ? *data.vpeer_id() : MTPPeer());
|
||||
}, [&](const MTPDmessageService &data) {
|
||||
return MTP_messageService(
|
||||
MTP_flags(data.vflags().v
|
||||
| MTPDmessageService::Flag(
|
||||
MTPDmessage::Flag::f_from_scheduled)),
|
||||
MTP_int(id),
|
||||
data.vid(),
|
||||
data.vfrom_id() ? *data.vfrom_id() : MTPPeer(),
|
||||
data.vpeer_id(),
|
||||
data.vreply_to() ? *data.vreply_to() : MTPMessageReplyHeader(),
|
||||
@@ -53,7 +53,7 @@ MTPMessage PrepareMessage(const MTPMessage &message, MsgId id) {
|
||||
}, [&](const MTPDmessage &data) {
|
||||
return MTP_message(
|
||||
MTP_flags(data.vflags().v | MTPDmessage::Flag::f_from_scheduled),
|
||||
MTP_int(id),
|
||||
data.vid(),
|
||||
data.vfrom_id() ? *data.vfrom_id() : MTPPeer(),
|
||||
data.vpeer_id(),
|
||||
data.vfwd_from() ? *data.vfwd_from() : MTPMessageFwdHeader(),
|
||||
@@ -192,6 +192,7 @@ void ScheduledMessages::sendNowSimpleMessage(
|
||||
const auto views = 1;
|
||||
const auto forwards = 0;
|
||||
history->addNewMessage(
|
||||
update.vid().v,
|
||||
MTP_message(
|
||||
MTP_flags(flags),
|
||||
update.vid(),
|
||||
@@ -269,7 +270,8 @@ void ScheduledMessages::checkEntitiesAndUpdate(const MTPDmessage &data) {
|
||||
qs(data.vmessage()),
|
||||
Api::EntitiesFromMTP(_session, data.ventities().value_or_empty())
|
||||
}, data.vmedia());
|
||||
existing->updateReplyMarkup(data.vreply_markup());
|
||||
existing->updateReplyMarkup(
|
||||
HistoryMessageMarkupData(data.vreply_markup()));
|
||||
existing->updateForwardedInfo(data.vfwd_from());
|
||||
_session->data().requestItemTextRefresh(existing);
|
||||
|
||||
@@ -454,7 +456,8 @@ HistoryItem *ScheduledMessages::append(
|
||||
_session,
|
||||
data.ventities().value_or_empty())
|
||||
}, data.vmedia());
|
||||
existing->updateReplyMarkup(data.vreply_markup());
|
||||
existing->updateReplyMarkup(
|
||||
HistoryMessageMarkupData(data.vreply_markup()));
|
||||
existing->updateForwardedInfo(data.vfwd_from());
|
||||
existing->updateDate(data.vdate().v);
|
||||
history->owner().requestItemTextRefresh(existing);
|
||||
@@ -463,7 +466,8 @@ HistoryItem *ScheduledMessages::append(
|
||||
}
|
||||
|
||||
const auto item = _session->data().addNewMessage(
|
||||
PrepareMessage(message, history->nextNonHistoryEntryId()),
|
||||
history->nextNonHistoryEntryId(),
|
||||
PrepareMessage(message),
|
||||
MessageFlags(), // localFlags
|
||||
NewMessageType::Existing);
|
||||
if (!item || item->history() != history) {
|
||||
@@ -546,7 +550,7 @@ uint64 ScheduledMessages::countListHash(const List &list) const {
|
||||
}) | ranges::views::reverse;
|
||||
for (const auto &item : serverside) {
|
||||
const auto j = list.idByItem.find(item.get());
|
||||
HashUpdate(hash, j->second);
|
||||
HashUpdate(hash, j->second.bare);
|
||||
if (const auto edited = item->Get<HistoryMessageEdited>()) {
|
||||
HashUpdate(hash, edited->date);
|
||||
} else {
|
||||
|
||||
@@ -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
|
||||
@@ -95,7 +95,7 @@ void CheckForSwitchInlineButton(not_null<HistoryItem*> item) {
|
||||
return;
|
||||
}
|
||||
if (const auto markup = item->Get<HistoryMessageReplyMarkup>()) {
|
||||
for (const auto &row : markup->rows) {
|
||||
for (const auto &row : markup->data.rows) {
|
||||
for (const auto &button : row) {
|
||||
using ButtonType = HistoryMessageMarkupButton::Type;
|
||||
if (button.type == ButtonType::SwitchInline) {
|
||||
@@ -1382,6 +1382,10 @@ void Session::requestItemRepaint(not_null<const HistoryItem*> item) {
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto history = item->history();
|
||||
if (history->lastItemDialogsView.dependsOn(item)) {
|
||||
history->updateChatListEntry();
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<not_null<const HistoryItem*>> Session::itemRepaintRequest() const {
|
||||
@@ -1838,8 +1842,8 @@ void Session::processMessages(
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const auto id = IdFromMessage(message);
|
||||
indices.emplace((uint64(uint32(id)) << 32) | uint64(i), i);
|
||||
const auto id = IdFromMessage(message); // Only 32 bit values here.
|
||||
indices.emplace((uint64(uint32(id.bare)) << 32) | uint64(i), i);
|
||||
}
|
||||
for (const auto &[position, index] : indices) {
|
||||
addNewMessage(
|
||||
@@ -1855,6 +1859,26 @@ void Session::processMessages(
|
||||
processMessages(data.v, type);
|
||||
}
|
||||
|
||||
void Session::processExistingMessages(
|
||||
ChannelData *channel,
|
||||
const MTPmessages_Messages &data) {
|
||||
data.match([&](const MTPDmessages_channelMessages &data) {
|
||||
if (channel) {
|
||||
channel->ptsReceived(data.vpts().v);
|
||||
} else {
|
||||
LOG(("App Error: received messages.channelMessages!"));
|
||||
}
|
||||
}, [](const auto &) {});
|
||||
|
||||
data.match([&](const MTPDmessages_messagesNotModified&) {
|
||||
LOG(("API Error: received messages.messagesNotModified!"));
|
||||
}, [&](const auto &data) {
|
||||
processUsers(data.vusers());
|
||||
processChats(data.vchats());
|
||||
processMessages(data.vmessages(), NewMessageType::Existing);
|
||||
});
|
||||
}
|
||||
|
||||
const Session::Messages *Session::messagesList(ChannelId channelId) const {
|
||||
if (channelId == NoChannel) {
|
||||
return &_messages;
|
||||
@@ -2166,12 +2190,21 @@ HistoryItem *Session::addNewMessage(
|
||||
const MTPMessage &data,
|
||||
MessageFlags localFlags,
|
||||
NewMessageType type) {
|
||||
return addNewMessage(IdFromMessage(data), data, localFlags, type);
|
||||
}
|
||||
|
||||
HistoryItem *Session::addNewMessage(
|
||||
MsgId id,
|
||||
const MTPMessage &data,
|
||||
MessageFlags localFlags,
|
||||
NewMessageType type) {
|
||||
const auto peerId = PeerFromMessage(data);
|
||||
if (!peerId) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto result = history(peerId)->addNewMessage(
|
||||
id,
|
||||
data,
|
||||
localFlags,
|
||||
type);
|
||||
@@ -2427,7 +2460,7 @@ PhotoData *Session::photoFromWeb(
|
||||
return nullptr;
|
||||
}
|
||||
return photo(
|
||||
openssl::RandomValue<PhotoId>(),
|
||||
base::RandomValue<PhotoId>(),
|
||||
uint64(0),
|
||||
QByteArray(),
|
||||
base::unixtime::now(),
|
||||
@@ -2692,7 +2725,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 +2747,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(),
|
||||
|
||||
@@ -341,6 +341,9 @@ public:
|
||||
void processMessages(
|
||||
const MTPVector<MTPMessage> &data,
|
||||
NewMessageType type);
|
||||
void processExistingMessages(
|
||||
ChannelData *channel,
|
||||
const MTPmessages_Messages &data);
|
||||
void processMessagesDeleted(
|
||||
ChannelId channelId,
|
||||
const QVector<MTPint> &data);
|
||||
@@ -397,6 +400,11 @@ public:
|
||||
const MTPMessage &data,
|
||||
MessageFlags localFlags,
|
||||
NewMessageType type);
|
||||
HistoryItem *addNewMessage( // Override message id.
|
||||
MsgId id,
|
||||
const MTPMessage &data,
|
||||
MessageFlags localFlags,
|
||||
NewMessageType type);
|
||||
|
||||
[[nodiscard]] int unreadBadge() const;
|
||||
[[nodiscard]] bool unreadBadgeMuted() const;
|
||||
|
||||