Compare commits
193 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae261fcede | ||
|
|
c04cdff7f7 | ||
|
|
466aa5a14d | ||
|
|
4aac633413 | ||
|
|
ad328d35a2 | ||
|
|
c5140f34a7 | ||
|
|
419f6345b3 | ||
|
|
c2c53df886 | ||
|
|
b3f73bb6a9 | ||
|
|
eda5cd47ad | ||
|
|
0c906a5e6d | ||
|
|
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
|
||||
@@ -160,6 +174,8 @@ PRIVATE
|
||||
boxes/peers/edit_peer_permissions_box.h
|
||||
boxes/about_box.cpp
|
||||
boxes/about_box.h
|
||||
boxes/about_sponsored_box.cpp
|
||||
boxes/about_sponsored_box.h
|
||||
boxes/abstract_box.cpp
|
||||
boxes/abstract_box.h
|
||||
boxes/add_contact_box.cpp
|
||||
@@ -278,6 +294,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 +422,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
|
||||
@@ -436,6 +455,8 @@ PRIVATE
|
||||
data/data_shared_media.h
|
||||
data/data_sparse_ids.cpp
|
||||
data/data_sparse_ids.h
|
||||
data/data_sponsored_messages.cpp
|
||||
data/data_sponsored_messages.h
|
||||
data/data_streaming.cpp
|
||||
data/data_streaming.h
|
||||
data/data_types.cpp
|
||||
@@ -456,8 +477,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 +489,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 +604,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
|
||||
@@ -608,6 +633,8 @@ PRIVATE
|
||||
history/view/history_view_service_message.h
|
||||
history/view/history_view_top_bar_widget.cpp
|
||||
history/view/history_view_top_bar_widget.h
|
||||
history/view/history_view_view_button.cpp
|
||||
history/view/history_view_view_button.h
|
||||
history/view/history_view_webpage_preview.cpp
|
||||
history/view/history_view_webpage_preview.h
|
||||
history/history.cpp
|
||||
@@ -618,6 +645,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 +959,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 +1058,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 +1278,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 +1388,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";
|
||||
@@ -1334,6 +1335,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_forwarded_imported" = "This message was imported from another app. It may not be real.";
|
||||
"lng_signed_author" = "Author: {user}";
|
||||
"lng_in_reply_to" = "In reply to";
|
||||
"lng_sponsored" = "sponsored";
|
||||
"lng_edited" = "edited";
|
||||
"lng_edited_date" = "Edited: {date}";
|
||||
"lng_sent_date" = "Sent: {date}";
|
||||
@@ -1617,6 +1619,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 +2240,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 +2872,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";
|
||||
@@ -2877,6 +2889,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_voice_speed_fast" = "Fast";
|
||||
"lng_voice_speed_very_fast" = "Very fast";
|
||||
|
||||
"lng_view_button_user" = "View user";
|
||||
"lng_view_button_bot" = "View bot";
|
||||
"lng_view_button_group" = "View group";
|
||||
"lng_view_button_channel" = "View channel";
|
||||
|
||||
"lng_sponsored_title" = "What are sponsored messages?";
|
||||
"lng_sponsored_info_description1" = "Unlike other apps, Telegram never uses your private data to target ads. You are seeing this message only because someone chose this public one-to-many channel as a space to promote their messages. This means that no user data is mined or analyzed to display ads, and every user viewing a channel on Telegram sees the same sponsored message.";
|
||||
"lng_sponsored_info_description2" = "Unlike other apps, Telegram doesn\'t track whether you tapped on a sponsored message and doesn\'t profile you based on your activity. We also prevent external links in sponsored messages to ensure that third parties can’t spy on our users. We believe that everyone has the right to privacy, and technological platforms should respect that.";
|
||||
"lng_sponsored_info_description3" = "Telegram offers free and unlimited service to hundreds of millions of users, which involves significant server and traffic costs. In order to remain independent and stay true to its values, Telegram developed a paid tool to promote messages with user privacy in mind. We welcome responsible advertisers at:";
|
||||
"lng_sponsored_info_description4" = "Ads should no longer be synonymous with abuse of user privacy. Let us redefine how a tech company should operate – together.";
|
||||
|
||||
// Wnd specific
|
||||
|
||||
"lng_wnd_choose_program_menu" = "Choose Default Program...";
|
||||
|
||||
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.7.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,7,0
|
||||
PRODUCTVERSION 3,1,7,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.7.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "3.0.2.0"
|
||||
VALUE "ProductVersion", "3.1.7.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,7,0
|
||||
PRODUCTVERSION 3,1,7,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.7.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "3.0.2.0"
|
||||
VALUE "ProductVersion", "3.1.7.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()
|
||||
@@ -434,7 +439,7 @@ void SendConfirmedFile(
|
||||
| (localEntities.v.isEmpty()
|
||||
? MTPDmessage::Flag()
|
||||
: MTPDmessage::Flag::f_entities)),
|
||||
MTP_int(newId.msg),
|
||||
MTP_int(0), // Not used (would've been trimmed to 32 bits).
|
||||
peerToMTP(messageFromId),
|
||||
peerToMTP(file->to.peer),
|
||||
MTPMessageFwdHeader(),
|
||||
@@ -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;
|
||||
|
||||
|
||||
135
Telegram/SourceFiles/api/api_views.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
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())
|
||||
, _incrementTimer([=] { viewsIncrement(); }) {
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
76
Telegram/SourceFiles/boxes/about_sponsored_box.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
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 "boxes/about_sponsored_box.h"
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_layers.h"
|
||||
|
||||
#include <QtGui/QDesktopServices>
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
constexpr auto kUrl = "https://telegram.org/ads"_cs;
|
||||
|
||||
} // namespace
|
||||
|
||||
void AboutSponsoredBox(not_null<Ui::GenericBox*> box) {
|
||||
box->setTitle(tr::lng_sponsored_title());
|
||||
box->setWidth(st::boxWideWidth);
|
||||
box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); });
|
||||
|
||||
const auto addUrl = [&] {
|
||||
const auto &st = st::sponsoredUrlButton;
|
||||
const auto row = box->addRow(object_ptr<RpWidget>(box));
|
||||
row->resize(0, st.height + st.padding.top() + st.padding.bottom());
|
||||
const auto button = Ui::CreateChild<RoundButton>(
|
||||
row,
|
||||
rpl::single<QString>(kUrl.utf8()),
|
||||
st);
|
||||
button->setBrushOverride(Qt::NoBrush);
|
||||
button->setPenOverride(QPen(st::historyLinkInFg));
|
||||
button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
|
||||
rpl::combine(
|
||||
row->sizeValue(),
|
||||
button->sizeValue()
|
||||
) | rpl::start_with_next([=](
|
||||
const QSize &rowSize,
|
||||
const QSize &buttonSize) {
|
||||
button->moveToLeft(
|
||||
(rowSize.width() - buttonSize.width()) / 2,
|
||||
(rowSize.height() - buttonSize.height()) / 2);
|
||||
}, row->lifetime());
|
||||
button->addClickHandler([=] {
|
||||
QDesktopServices::openUrl({ kUrl.utf8() });
|
||||
});
|
||||
};
|
||||
|
||||
const auto &stLabel = st::aboutLabel;
|
||||
const auto info1 = box->addRow(object_ptr<FlatLabel>(box, stLabel));
|
||||
info1->setText(tr::lng_sponsored_info_description1(tr::now));
|
||||
box->addSkip(st::sponsoredInfoSkip);
|
||||
|
||||
const auto info2 = box->addRow(object_ptr<FlatLabel>(box, stLabel));
|
||||
info2->setText(tr::lng_sponsored_info_description2(tr::now));
|
||||
box->addSkip(st::sponsoredInfoSkip);
|
||||
|
||||
const auto info3 = box->addRow(object_ptr<FlatLabel>(box, stLabel));
|
||||
info3->setText(tr::lng_sponsored_info_description3(tr::now));
|
||||
box->addSkip(st::sponsoredUrlButtonSkip);
|
||||
|
||||
addUrl();
|
||||
box->addSkip(st::sponsoredUrlButtonSkip);
|
||||
|
||||
const auto info4 = box->addRow(object_ptr<FlatLabel>(box, stLabel));
|
||||
info4->setText(tr::lng_sponsored_info_description4(tr::now));
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
16
Telegram/SourceFiles/boxes/about_sponsored_box.h
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ui/layers/generic_box.h"
|
||||
|
||||
namespace Ui {
|
||||
|
||||
void AboutSponsoredBox(not_null<Ui::GenericBox*> box);
|
||||
|
||||
} // namespace Ui
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -973,3 +973,22 @@ autolockTimeField: InputField(scheduleTimeField) {
|
||||
heightMin: 20px;
|
||||
}
|
||||
autolockTimeWidth: 52px;
|
||||
|
||||
sponsoredInfoSkip: 22px;
|
||||
sponsoredUrlButtonSkip: 11px;
|
||||
sponsoredUrlButton: RoundButton(defaultActiveButton) {
|
||||
height: 32px;
|
||||
width: -42px;
|
||||
textBg: transparent;
|
||||
textBgOver: transparent;
|
||||
radius: roundRadiusLarge;
|
||||
padding: margins(2px, 2px, 2px, 2px);
|
||||
textFg: historyLinkInFg;
|
||||
textFgOver: historyLinkInFg;
|
||||
textTop: 7px;
|
||||
font: normalFont;
|
||||
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
@@ -2306,6 +2361,7 @@ bool GroupCall::tryCreateController() {
|
||||
auto callLogFolder = cWorkingDir() + qsl("DebugLogs");
|
||||
auto callLogPath = callLogFolder + qsl("/last_group_call_log.txt");
|
||||
auto callLogNative = QDir::toNativeSeparators(callLogPath);
|
||||
descriptor.config.need_log = true;
|
||||
#ifdef Q_OS_WIN
|
||||
descriptor.config.logPath.data = callLogNative.toStdWString();
|
||||
#else // Q_OS_WIN
|
||||
@@ -2315,6 +2371,8 @@ bool GroupCall::tryCreateController() {
|
||||
#endif // Q_OS_WIN
|
||||
QFile(callLogPath).remove();
|
||||
QDir().mkpath(callLogFolder);
|
||||
} else {
|
||||
descriptor.config.need_log = false;
|
||||
}
|
||||
|
||||
LOG(("Call Info: Creating group instance"));
|
||||
@@ -2499,6 +2557,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 = 3001007;
|
||||
constexpr auto AppVersionStr = "3.1.7";
|
||||
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;
|
||||
|
||||