Compare commits
238 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
335095a332 | ||
|
|
15fcb73e19 | ||
|
|
a8b0f2934b | ||
|
|
7d67b3d00a | ||
|
|
b60c7e97ab | ||
|
|
440ebfcbf6 | ||
|
|
8d93eb919b | ||
|
|
d3df2dc1e5 | ||
|
|
677314dacb | ||
|
|
20f53c89ad | ||
|
|
1cb92ef69a | ||
|
|
b518f7c4c4 | ||
|
|
9fdc099c3a | ||
|
|
1db8ada2aa | ||
|
|
b753448052 | ||
|
|
e29c6d2f23 | ||
|
|
1ee53ee0c1 | ||
|
|
57438867b6 | ||
|
|
86c04424f6 | ||
|
|
632639d581 | ||
|
|
99c8edb3eb | ||
|
|
4582e61cfc | ||
|
|
a970fe93c1 | ||
|
|
91f5c72cf0 | ||
|
|
d2f3d04ad4 | ||
|
|
07b93e1e02 | ||
|
|
ab5e7b1588 | ||
|
|
0f4aadddfc | ||
|
|
a3c79feeba | ||
|
|
e6b76fefa1 | ||
|
|
a37f512949 | ||
|
|
3f34c0ce37 | ||
|
|
7cc9a0b9aa | ||
|
|
fbaa79f168 | ||
|
|
2b0aa8e418 | ||
|
|
1fa2e76c9b | ||
|
|
cb166e2591 | ||
|
|
187c2dda20 | ||
|
|
b962d2b550 | ||
|
|
d69bcfa138 | ||
|
|
0960a3b21d | ||
|
|
aec220fd2c | ||
|
|
666dacf73b | ||
|
|
67ce27afaa | ||
|
|
3f6d184435 | ||
|
|
a2fad84dae | ||
|
|
4a84f9fa00 | ||
|
|
7abc921d20 | ||
|
|
a307e7a798 | ||
|
|
3a9eb8463d | ||
|
|
4befb125e3 | ||
|
|
ca00a19736 | ||
|
|
296969bcd1 | ||
|
|
3218c30983 | ||
|
|
3658b32db5 | ||
|
|
592d46c8f2 | ||
|
|
8b86f12c23 | ||
|
|
2e2d8d2af3 | ||
|
|
3eec43cacd | ||
|
|
ba7cd25f21 | ||
|
|
969152e949 | ||
|
|
b78443cf2d | ||
|
|
4288ba2449 | ||
|
|
22a3093815 | ||
|
|
98ba2c7ce4 | ||
|
|
36962b8c62 | ||
|
|
d0a8bd1f03 | ||
|
|
c9e0f50f0f | ||
|
|
1dc310586d | ||
|
|
6f71d21bb7 | ||
|
|
c93f047056 | ||
|
|
ef98d4ece7 | ||
|
|
2383bf2c71 | ||
|
|
2c8c92c2a4 | ||
|
|
bad2dc30c3 | ||
|
|
fe7f4233b9 | ||
|
|
68454a9841 | ||
|
|
d3bcf63cf7 | ||
|
|
a5f1209f28 | ||
|
|
9a44ca2769 | ||
|
|
155305f0f7 | ||
|
|
6abce8d976 | ||
|
|
f16d1f034f | ||
|
|
dab107cf90 | ||
|
|
d1d1aa3d21 | ||
|
|
d0536cc31f | ||
|
|
c47f5e9995 | ||
|
|
0916836ff9 | ||
|
|
5dd9ff1062 | ||
|
|
d1e3b9f15d | ||
|
|
ca3c179b75 | ||
|
|
c2d5924508 | ||
|
|
016d0395c3 | ||
|
|
925c9239bd | ||
|
|
fdcbe3cf3a | ||
|
|
f6b9cc5ce1 | ||
|
|
8c915e6dc3 | ||
|
|
1db426da2e | ||
|
|
d9be363962 | ||
|
|
88daa37e34 | ||
|
|
931fa01337 | ||
|
|
8e1595eb29 | ||
|
|
29e97232d8 | ||
|
|
f05191e668 | ||
|
|
1ba189e59d | ||
|
|
c40ca70aa6 | ||
|
|
521f991167 | ||
|
|
edf1417bbb | ||
|
|
686e9643ad | ||
|
|
975460d268 | ||
|
|
6b0c606d25 | ||
|
|
93ff0bdcff | ||
|
|
bf9d90ca4e | ||
|
|
d6e5e1e8f7 | ||
|
|
d3ae2ef9ea | ||
|
|
40b0854704 | ||
|
|
9aec6b6496 | ||
|
|
d01f977960 | ||
|
|
1444009ee2 | ||
|
|
14ee9bee26 | ||
|
|
93587ddc3c | ||
|
|
0c4d03477e | ||
|
|
a35092f012 | ||
|
|
49ee7ee52b | ||
|
|
52c77a1970 | ||
|
|
e4269ae7fb | ||
|
|
bd6011c524 | ||
|
|
46cb3ec103 | ||
|
|
0241129948 | ||
|
|
e44aca06cb | ||
|
|
19d386f977 | ||
|
|
a67fdda913 | ||
|
|
5286c7b1c3 | ||
|
|
d9d96d0a6f | ||
|
|
13353bb615 | ||
|
|
d78348fd16 | ||
|
|
1e8e660133 | ||
|
|
5196982c98 | ||
|
|
99f857fbf6 | ||
|
|
0982aa166a | ||
|
|
3ae9f86097 | ||
|
|
9efd9b0d68 | ||
|
|
267a73f355 | ||
|
|
437d8ea890 | ||
|
|
7ed92ec402 | ||
|
|
678d9ffbf9 | ||
|
|
bc4b427ed1 | ||
|
|
36141a9df9 | ||
|
|
d68ba75457 | ||
|
|
00ad32c5f4 | ||
|
|
064bab60ff | ||
|
|
5b146217c0 | ||
|
|
202c81b2e5 | ||
|
|
4520480604 | ||
|
|
aea87bb5cb | ||
|
|
19492f7e7b | ||
|
|
51030e3c45 | ||
|
|
e6b8b4be18 | ||
|
|
6ac13b7f80 | ||
|
|
7e5e6003a9 | ||
|
|
f445440995 | ||
|
|
d81547f091 | ||
|
|
2b185d491b | ||
|
|
ca47440950 | ||
|
|
5e32602f4a | ||
|
|
1f4516028c | ||
|
|
7f29d269a3 | ||
|
|
62c3374911 | ||
|
|
2116e04af5 | ||
|
|
a97d5e80c7 | ||
|
|
b5098038d0 | ||
|
|
6fce718252 | ||
|
|
40cf96202d | ||
|
|
c18e59218e | ||
|
|
216865a20d | ||
|
|
6c5036ee8d | ||
|
|
dc2f59ca24 | ||
|
|
6f6457137e | ||
|
|
bc5cb6e2a2 | ||
|
|
16825fff41 | ||
|
|
5024f1db8c | ||
|
|
a60385fc3d | ||
|
|
b20e2c37c1 | ||
|
|
acd40cbeb6 | ||
|
|
6a45a862dd | ||
|
|
5bd45e9a20 | ||
|
|
52e42f23ab | ||
|
|
96b7755cde | ||
|
|
c589ee1ca5 | ||
|
|
18c9ee093b | ||
|
|
b82fa3112c | ||
|
|
8d0f66d562 | ||
|
|
20a5e0ba73 | ||
|
|
5a2667c71e | ||
|
|
1f4a8d7eb6 | ||
|
|
4430bd0328 | ||
|
|
ab2e7f4c03 | ||
|
|
54e5c06b4d | ||
|
|
add5a6a0be | ||
|
|
89c2ba4293 | ||
|
|
dd100fb709 | ||
|
|
e8dd2b9e7b | ||
|
|
71e3cd227c | ||
|
|
1648c31a22 | ||
|
|
f8c820f319 | ||
|
|
300f35e78f | ||
|
|
2aa5849997 | ||
|
|
fb1b845211 | ||
|
|
3d2af9db8e | ||
|
|
6129e5a1cf | ||
|
|
7f70ee1227 | ||
|
|
c9f7da6e82 | ||
|
|
f956c0f227 | ||
|
|
df935e0477 | ||
|
|
841f1afe1e | ||
|
|
fb8b88557e | ||
|
|
2b502b22b9 | ||
|
|
5ac80d2655 | ||
|
|
8aa7499e63 | ||
|
|
8cd5e51982 | ||
|
|
e5c2133446 | ||
|
|
3dccdf2f05 | ||
|
|
8a708c6655 | ||
|
|
9e1d9eee4b | ||
|
|
f30aabc365 | ||
|
|
a5546d016f | ||
|
|
f79d70d112 | ||
|
|
ec28f258fb | ||
|
|
08f3a6fb40 | ||
|
|
8823d5256f | ||
|
|
60e7aa90d2 | ||
|
|
71357a9546 | ||
|
|
a5b06e9c56 | ||
|
|
0f94419f6d | ||
|
|
94e7aabea5 | ||
|
|
f6c816cafe | ||
|
|
4cf160e8dc | ||
|
|
9252be5e8c |
17
.github/workflows/mac_packaged.yml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
|
||||
macos:
|
||||
name: MacOS
|
||||
runs-on: macos-13
|
||||
runs-on: macos-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
|
||||
env:
|
||||
GIT: "https://github.com"
|
||||
CMAKE_PREFIX_PATH: "/usr/local/opt/ffmpeg@6:/usr/local/opt/openal-soft"
|
||||
CMAKE_PREFIX_PATH: "/opt/homebrew/opt/ffmpeg@6:/opt/homebrew/opt/openal-soft"
|
||||
UPLOAD_ARTIFACT: "true"
|
||||
ONLY_CACHE: "false"
|
||||
MANUAL_CACHING: "1"
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
run: |
|
||||
brew update
|
||||
brew upgrade || true
|
||||
brew install ada-url autoconf automake boost cmake ffmpeg@6 openal-soft openh264 openssl opus ninja pkg-config python qt yasm xz
|
||||
brew install ada-url autoconf automake boost cmake ffmpeg@6 libtool openal-soft openh264 openssl opus ninja pkg-config python qt yasm xz
|
||||
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
|
||||
|
||||
xcodebuild -version > CACHE_KEY.txt
|
||||
@@ -95,7 +95,7 @@ jobs:
|
||||
./autogen.sh
|
||||
./configure --disable-examples --disable-doc
|
||||
make -j$(sysctl -n hw.logicalcpu)
|
||||
make install
|
||||
sudo make install
|
||||
|
||||
- name: WebRTC cache.
|
||||
id: cache-webrtc
|
||||
@@ -111,7 +111,11 @@ jobs:
|
||||
git clone --depth=1 --recursive --shallow-submodules $GIT/desktop-app/tg_owt.git
|
||||
cd tg_owt
|
||||
|
||||
cmake -B build . -GNinja -DCMAKE_BUILD_TYPE=Debug
|
||||
cmake -Bbuild -GNinja . \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DCMAKE_C_FLAGS_DEBUG="" \
|
||||
-DCMAKE_CXX_FLAGS_DEBUG=""
|
||||
|
||||
cmake --build build --parallel
|
||||
|
||||
- name: Telegram Desktop build.
|
||||
@@ -132,6 +136,9 @@ jobs:
|
||||
|
||||
cmake -Bbuild -GNinja . \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DCMAKE_C_FLAGS_DEBUG="" \
|
||||
-DCMAKE_CXX_FLAGS_DEBUG="" \
|
||||
-DCMAKE_EXE_LINKER_FLAGS="-s" \
|
||||
-DCMAKE_FIND_FRAMEWORK=LAST \
|
||||
-DTDESKTOP_API_TEST=ON \
|
||||
-DDESKTOP_APP_USE_PACKAGED_LAZY=ON \
|
||||
|
||||
2
.github/workflows/snap.yml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
|
||||
snap:
|
||||
name: Ubuntu
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
UPLOAD_ARTIFACT: "true"
|
||||
|
||||
10
.github/workflows/win.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
|
||||
windows:
|
||||
name: Windows
|
||||
runs-on: windows-2022
|
||||
runs-on: windows-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -94,6 +94,14 @@ jobs:
|
||||
nuget sources Disable -Name "Microsoft Visual Studio Offline Packages"
|
||||
nuget sources Add -Source https://api.nuget.org/v3/index.json & exit 0
|
||||
|
||||
- name: ThirdParty cache.
|
||||
id: cache-third-party
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.TBUILD }}\ThirdParty
|
||||
key: ${{ runner.OS }}-${{ matrix.arch }}-third-party-${{ env.CACHE_KEY }}
|
||||
restore-keys: ${{ runner.OS }}-${{ matrix.arch }}-third-party-
|
||||
|
||||
- name: Libraries cache.
|
||||
id: cache-libs
|
||||
uses: actions/cache@v4
|
||||
|
||||
@@ -64,6 +64,7 @@ Version **1.8.15** was the last that supports older systems
|
||||
* QR Code generator ([MIT License](https://github.com/nayuki/QR-Code-generator#license))
|
||||
* CMake ([New BSD License](https://github.com/Kitware/CMake/blob/master/Copyright.txt))
|
||||
* Hunspell ([LGPL](https://github.com/hunspell/hunspell/blob/master/COPYING.LESSER))
|
||||
* Ada ([Apache License 2.0](https://github.com/ada-url/ada/blob/main/LICENSE-APACHE))
|
||||
|
||||
## Build instructions
|
||||
|
||||
|
||||
@@ -569,6 +569,8 @@ PRIVATE
|
||||
data/data_lastseen_status.h
|
||||
data/data_location.cpp
|
||||
data/data_location.h
|
||||
data/data_media_preload.cpp
|
||||
data/data_media_preload.h
|
||||
data/data_media_rotation.cpp
|
||||
data/data_media_rotation.h
|
||||
data/data_media_types.cpp
|
||||
@@ -1482,6 +1484,8 @@ PRIVATE
|
||||
support/support_templates.h
|
||||
ui/boxes/edit_invite_link_session.cpp
|
||||
ui/boxes/edit_invite_link_session.h
|
||||
ui/boxes/peer_qr_box.cpp
|
||||
ui/boxes/peer_qr_box.h
|
||||
ui/chat/attach/attach_item_single_file_preview.cpp
|
||||
ui/chat/attach/attach_item_single_file_preview.h
|
||||
ui/chat/attach/attach_item_single_media_preview.cpp
|
||||
|
||||
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 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.1 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: 832 KiB After Width: | Height: | Size: 935 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 234 KiB |
@@ -582,3 +582,55 @@ div.toast_shown {
|
||||
.bot_button_column_separator {
|
||||
width: 2px
|
||||
}
|
||||
|
||||
.reactions {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.reactions .reaction {
|
||||
display: inline-flex;
|
||||
height: 20px;
|
||||
border-radius: 15px;
|
||||
background-color: #e8f5fc;
|
||||
color: #168acd;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.reactions .reaction.active {
|
||||
background-color: #40a6e2;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.reactions .reaction.paid {
|
||||
background-color: #fdf6e1;
|
||||
color: #c58523;
|
||||
}
|
||||
|
||||
.reactions .reaction.active.paid {
|
||||
background-color: #ecae0a;
|
||||
color: #fdf6e1;
|
||||
}
|
||||
|
||||
.reactions .reaction .emoji {
|
||||
line-height: 20px;
|
||||
margin: 0 5px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.reactions .reaction .userpic:not(:first-child) {
|
||||
margin-left: -8px;
|
||||
}
|
||||
|
||||
.reactions .reaction .userpic {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.reactions .reaction .userpic .initials {
|
||||
font-size: 8px;
|
||||
}
|
||||
|
||||
.reactions .reaction .count {
|
||||
margin-right: 8px;
|
||||
line-height: 20px;
|
||||
}
|
||||
BIN
Telegram/Resources/icons/inline_button_copy.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
Telegram/Resources/icons/inline_button_copy@2x.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
Telegram/Resources/icons/inline_button_copy@3x.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
1
Telegram/Resources/icons/plane_white.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="1000px" height="1000px" viewBox="0 0 1000 1000" version="1.1" xmlns="http://www.w3.org/2000/svg"><g id="Artboard" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><path d="M226.328419,494.722069 C372.088573,431.216685 469.284839,389.350049 517.917216,369.122161 C656.772535,311.36743 685.625481,301.334815 704.431427,301.003532 C708.567621,300.93067 717.815839,301.955743 723.806446,306.816707 C728.864797,310.92121 730.256552,316.46581 730.922551,320.357329 C731.588551,324.248848 732.417879,333.113828 731.758626,340.040666 C724.234007,419.102486 691.675104,610.964674 675.110982,699.515267 C668.10208,736.984342 654.301336,749.547532 640.940618,750.777006 C611.904684,753.448938 589.856115,731.588035 561.733393,713.153237 C517.726886,684.306416 492.866009,666.349181 450.150074,638.200013 C400.78442,605.66878 432.786119,587.789048 460.919462,558.568563 C468.282091,550.921423 596.21508,434.556479 598.691227,424.000355 C599.00091,422.680135 599.288312,417.758981 596.36474,415.160431 C593.441168,412.561881 589.126229,413.450484 586.012448,414.157198 C581.598758,415.158943 511.297793,461.625274 375.109553,553.556189 C355.154858,567.258623 337.080515,573.934908 320.886524,573.585046 C303.033948,573.199351 268.692754,563.490928 243.163606,555.192408 C211.851067,545.013936 186.964484,539.632504 189.131547,522.346309 C190.260287,513.342589 202.659244,504.134509 226.328419,494.722069 Z" id="Path-3" fill="#FFFFFF"></path></g></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
BIN
Telegram/Resources/icons/qr_mini.png
Normal file
|
After Width: | Height: | Size: 641 B |
BIN
Telegram/Resources/icons/qr_mini@2x.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/qr_mini@3x.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
@@ -31,6 +31,7 @@
|
||||
<file alias="topic_icons/gray.svg">../../art/topic_icons/gray.svg</file>
|
||||
<file alias="topic_icons/general.svg">../../art/topic_icons/general.svg</file>
|
||||
<file alias="links_subscription.svg">../../icons/info/edit/links_subscription.svg</file>
|
||||
<file alias="plane_white.svg">../../icons/plane_white.svg</file>
|
||||
</qresource>
|
||||
<qresource prefix="/icons">
|
||||
<file alias="calls/hands.lottie">../../icons/calls/hands.lottie</file>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="5.4.3.0" />
|
||||
Version="5.5.8.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 5,4,3,0
|
||||
PRODUCTVERSION 5,4,3,0
|
||||
FILEVERSION 5,5,8,0
|
||||
PRODUCTVERSION 5,5,8,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "5.4.3.0"
|
||||
VALUE "FileVersion", "5.5.8.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.4.3.0"
|
||||
VALUE "ProductVersion", "5.5.8.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,4,3,0
|
||||
PRODUCTVERSION 5,4,3,0
|
||||
FILEVERSION 5,5,8,0
|
||||
PRODUCTVERSION 5,5,8,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", "5.4.3.0"
|
||||
VALUE "FileVersion", "5.5.8.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.4.3.0"
|
||||
VALUE "ProductVersion", "5.5.8.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -39,6 +39,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtGui/QClipboard>
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
@@ -503,11 +506,19 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
|
||||
bot->session().attachWebView().open({
|
||||
.bot = bot,
|
||||
.context = { .controller = controller },
|
||||
.button = {.text = button->text, .url = button->data },
|
||||
.button = { .text = button->text, .url = button->data },
|
||||
.source = InlineBots::WebViewSourceButton{ .simple = true },
|
||||
});
|
||||
}
|
||||
} break;
|
||||
|
||||
case ButtonType::CopyText: {
|
||||
const auto text = QString::fromUtf8(button->data);
|
||||
if (!text.isEmpty()) {
|
||||
QGuiApplication::clipboard()->setText(text);
|
||||
controller->showToast(tr::lng_text_copied(tr::now));
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -232,12 +232,12 @@ void ImportInvite(
|
||||
api->request(MTPchatlists_JoinChatlistInvite(
|
||||
MTP_string(slug),
|
||||
MTP_vector<MTPInputPeer>(std::move(inputs))
|
||||
)).done(callback).fail(error).send();
|
||||
)).done(callback).fail(error).handleFloodErrors().send();
|
||||
} else {
|
||||
api->request(MTPchatlists_JoinChatlistUpdates(
|
||||
MTP_inputChatlistDialogFilter(MTP_int(filterId)),
|
||||
MTP_vector<MTPInputPeer>(std::move(inputs))
|
||||
)).done(callback).fail(error).send();
|
||||
)).done(callback).fail(error).handleFloodErrors().send();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -517,6 +517,8 @@ void ShowImportError(
|
||||
} else {
|
||||
window->showToast((error == u"INVITE_SLUG_EXPIRED"_q)
|
||||
? tr::lng_group_invite_bad_link(tr::now)
|
||||
: error.startsWith(u"FLOOD_WAIT_"_q)
|
||||
? tr::lng_flood_error(tr::now)
|
||||
: error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,14 +264,24 @@ ChatParticipant::ChatParticipant(
|
||||
_rank = qs(data.vrank().value_or_empty());
|
||||
_rights = ChatAdminRightsInfo(data.vadmin_rights());
|
||||
_by = peerToUser(peerFromUser(data.vpromoted_by()));
|
||||
_date = data.vdate().v;
|
||||
}, [&](const MTPDchannelParticipantSelf &data) {
|
||||
_type = Type::Member;
|
||||
_date = data.vdate().v;
|
||||
_by = peerToUser(peerFromUser(data.vinviter_id()));
|
||||
if (data.vsubscription_until_date()) {
|
||||
_subscriptionDate = data.vsubscription_until_date()->v;
|
||||
}
|
||||
}, [&](const MTPDchannelParticipant &data) {
|
||||
_type = Type::Member;
|
||||
_date = data.vdate().v;
|
||||
if (data.vsubscription_until_date()) {
|
||||
_subscriptionDate = data.vsubscription_until_date()->v;
|
||||
}
|
||||
}, [&](const MTPDchannelParticipantBanned &data) {
|
||||
_restrictions = ChatRestrictionsInfo(data.vbanned_rights());
|
||||
_by = peerToUser(peerFromUser(data.vkicked_by()));
|
||||
_date = data.vdate().v;
|
||||
|
||||
_type = (_restrictions.flags & ChatRestriction::ViewMessages)
|
||||
? Type::Banned
|
||||
@@ -348,6 +358,24 @@ ChatAdminRightsInfo ChatParticipant::rights() const {
|
||||
return _rights;
|
||||
}
|
||||
|
||||
TimeId ChatParticipant::subscriptionDate() const {
|
||||
return _subscriptionDate;
|
||||
}
|
||||
|
||||
TimeId ChatParticipant::promotedSince() const {
|
||||
return (_type == Type::Admin) ? _date : TimeId(0);
|
||||
}
|
||||
|
||||
TimeId ChatParticipant::restrictedSince() const {
|
||||
return (_type == Type::Restricted || _type == Type::Banned)
|
||||
? _date
|
||||
: TimeId(0);
|
||||
}
|
||||
|
||||
TimeId ChatParticipant::memberSince() const {
|
||||
return (_type == Type::Member) ? _date : TimeId(0);
|
||||
}
|
||||
|
||||
ChatParticipant::Type ChatParticipant::type() const {
|
||||
return _type;
|
||||
}
|
||||
|
||||
@@ -60,6 +60,11 @@ public:
|
||||
ChatRestrictionsInfo restrictions() const;
|
||||
ChatAdminRightsInfo rights() const;
|
||||
|
||||
TimeId subscriptionDate() const;
|
||||
TimeId promotedSince() const;
|
||||
TimeId restrictedSince() const;
|
||||
TimeId memberSince() const;
|
||||
|
||||
Type type() const;
|
||||
QString rank() const;
|
||||
|
||||
@@ -73,6 +78,8 @@ private:
|
||||
bool _canBeEdited = false;
|
||||
|
||||
QString _rank;
|
||||
TimeId _subscriptionDate = 0;
|
||||
TimeId _date = 0;
|
||||
|
||||
ChatRestrictionsInfo _restrictions;
|
||||
ChatAdminRightsInfo _rights;
|
||||
|
||||
@@ -12,6 +12,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/core_cloud_password.h"
|
||||
#include "passport/passport_encryption.h"
|
||||
|
||||
#include "base/unixtime.h"
|
||||
#include "base/call_delayed.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
|
||||
@@ -79,6 +79,8 @@ constexpr auto kTransactionsLimit = 100;
|
||||
.credits = tl.data().vstars().v,
|
||||
.bareMsgId = uint64(tl.data().vmsg_id().value_or_empty()),
|
||||
.barePeerId = barePeerId,
|
||||
.bareGiveawayMsgId = uint64(
|
||||
tl.data().vgiveaway_post_id().value_or_empty()),
|
||||
.peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::Peer;
|
||||
}, [](const MTPDstarsTransactionPeerPlayMarket &) {
|
||||
@@ -215,6 +217,10 @@ rpl::producer<rpl::no_value, QString> CreditsTopupOptions::request() {
|
||||
};
|
||||
}
|
||||
|
||||
Data::CreditTopupOptions CreditsTopupOptions::options() const {
|
||||
return _options;
|
||||
}
|
||||
|
||||
CreditsStatus::CreditsStatus(not_null<PeerData*> peer)
|
||||
: _peer(peer)
|
||||
, _api(&peer->session().api().instance()) {
|
||||
@@ -294,10 +300,6 @@ void CreditsHistory::requestSubscriptions(
|
||||
}).send();
|
||||
}
|
||||
|
||||
Data::CreditTopupOptions CreditsTopupOptions::options() const {
|
||||
return _options;
|
||||
}
|
||||
|
||||
rpl::producer<not_null<PeerData*>> PremiumPeerBot(
|
||||
not_null<Main::Session*> session) {
|
||||
const auto username = session->appConfig().get<QString>(
|
||||
@@ -385,4 +387,58 @@ Data::CreditsEarnStatistics CreditsEarnStatistics::data() const {
|
||||
return _data;
|
||||
}
|
||||
|
||||
CreditsGiveawayOptions::CreditsGiveawayOptions(not_null<PeerData*> peer)
|
||||
: _peer(peer)
|
||||
, _api(&peer->session().api().instance()) {
|
||||
}
|
||||
|
||||
rpl::producer<rpl::no_value, QString> CreditsGiveawayOptions::request() {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
using TLOption = MTPStarsGiveawayOption;
|
||||
|
||||
const auto optionsFromTL = [=](const auto &options) {
|
||||
return ranges::views::all(
|
||||
options
|
||||
) | ranges::views::transform([=](const auto &option) {
|
||||
return Data::CreditsGiveawayOption{
|
||||
.winners = ranges::views::all(
|
||||
option.data().vwinners().v
|
||||
) | ranges::views::transform([](const auto &winner) {
|
||||
return Data::CreditsGiveawayOption::Winner{
|
||||
.users = winner.data().vusers().v,
|
||||
.perUserStars = winner.data().vper_user_stars().v,
|
||||
.isDefault = winner.data().is_default(),
|
||||
};
|
||||
}) | ranges::to_vector,
|
||||
.storeProduct = qs(
|
||||
option.data().vstore_product().value_or_empty()),
|
||||
.currency = qs(option.data().vcurrency()),
|
||||
.amount = option.data().vamount().v,
|
||||
.credits = option.data().vstars().v,
|
||||
.yearlyBoosts = option.data().vyearly_boosts().v,
|
||||
.isExtended = option.data().is_extended(),
|
||||
.isDefault = option.data().is_default(),
|
||||
};
|
||||
}) | ranges::to_vector;
|
||||
};
|
||||
const auto fail = [=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
};
|
||||
|
||||
_api.request(MTPpayments_GetStarsGiveawayOptions(
|
||||
)).done([=](const MTPVector<TLOption> &result) {
|
||||
_options = optionsFromTL(result.v);
|
||||
consumer.put_done();
|
||||
}).fail(fail).send();
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
Data::CreditsGiveawayOptions CreditsGiveawayOptions::options() const {
|
||||
return _options;
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -36,6 +36,22 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class CreditsGiveawayOptions final {
|
||||
public:
|
||||
CreditsGiveawayOptions(not_null<PeerData*> peer);
|
||||
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> request();
|
||||
[[nodiscard]] Data::CreditsGiveawayOptions options() const;
|
||||
|
||||
private:
|
||||
const not_null<PeerData*> _peer;
|
||||
|
||||
Data::CreditsGiveawayOptions _options;
|
||||
|
||||
MTP::Sender _api;
|
||||
|
||||
};
|
||||
|
||||
class CreditsStatus final {
|
||||
public:
|
||||
CreditsStatus(not_null<PeerData*> peer);
|
||||
|
||||
@@ -115,6 +115,28 @@ rpl::producer<bool> GlobalPrivacy::newRequirePremium() const {
|
||||
return _newRequirePremium.value();
|
||||
}
|
||||
|
||||
void GlobalPrivacy::loadPaidReactionAnonymous() {
|
||||
if (_paidReactionAnonymousLoaded) {
|
||||
return;
|
||||
}
|
||||
_paidReactionAnonymousLoaded = true;
|
||||
_api.request(MTPmessages_GetPaidReactionPrivacy(
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_session->api().applyUpdates(result);
|
||||
}).send();
|
||||
}
|
||||
|
||||
void GlobalPrivacy::updatePaidReactionAnonymous(bool value) {
|
||||
_paidReactionAnonymous = value;
|
||||
}
|
||||
|
||||
bool GlobalPrivacy::paidReactionAnonymousCurrent() const {
|
||||
return _paidReactionAnonymous.current();
|
||||
}
|
||||
|
||||
rpl::producer<bool> GlobalPrivacy::paidReactionAnonymous() const {
|
||||
return _paidReactionAnonymous.value();
|
||||
}
|
||||
|
||||
void GlobalPrivacy::updateArchiveAndMute(bool value) {
|
||||
update(
|
||||
|
||||
@@ -49,6 +49,11 @@ public:
|
||||
[[nodiscard]] bool newRequirePremiumCurrent() const;
|
||||
[[nodiscard]] rpl::producer<bool> newRequirePremium() const;
|
||||
|
||||
void loadPaidReactionAnonymous();
|
||||
void updatePaidReactionAnonymous(bool value);
|
||||
[[nodiscard]] bool paidReactionAnonymousCurrent() const;
|
||||
[[nodiscard]] rpl::producer<bool> paidReactionAnonymous() const;
|
||||
|
||||
private:
|
||||
void apply(const MTPGlobalPrivacySettings &data);
|
||||
|
||||
@@ -67,7 +72,9 @@ private:
|
||||
rpl::variable<bool> _showArchiveAndMute = false;
|
||||
rpl::variable<bool> _hideReadTime = false;
|
||||
rpl::variable<bool> _newRequirePremium = false;
|
||||
rpl::variable<bool> _paidReactionAnonymous = false;
|
||||
std::vector<Fn<void()>> _callbacks;
|
||||
bool _paidReactionAnonymousLoaded = false;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -37,7 +37,8 @@ MTPVector<MTPDocumentAttribute> ComposeSendingDocumentAttributes(
|
||||
MTP_int(dimensions.width()),
|
||||
MTP_int(dimensions.height()),
|
||||
MTPint(), // preload_prefix_size
|
||||
MTPdouble())); // video_start_ts
|
||||
MTPdouble(), // video_start_ts
|
||||
MTPstring())); // video_codec
|
||||
} else {
|
||||
attributes.push_back(MTP_documentAttributeImageSize(
|
||||
MTP_int(dimensions.width()),
|
||||
|
||||
@@ -361,9 +361,10 @@ void Premium::resolveGiveawayInfo(
|
||||
? GiveawayState::Refunded
|
||||
: GiveawayState::Finished;
|
||||
info.giftCode = qs(data.vgift_code_slug().value_or_empty());
|
||||
info.activatedCount = data.vactivated_count().v;
|
||||
info.activatedCount = data.vactivated_count().value_or_empty();
|
||||
info.finishDate = data.vfinish_date().v;
|
||||
info.startDate = data.vstart_date().v;
|
||||
info.credits = data.vstars_prize().value_or_empty();
|
||||
});
|
||||
_giveawayInfoDone(std::move(info));
|
||||
}).fail([=] {
|
||||
@@ -508,7 +509,9 @@ rpl::producer<rpl::no_value, QString> PremiumGiftCodeOptions::applyPrepaid(
|
||||
_api.request(MTPpayments_LaunchPrepaidGiveaway(
|
||||
_peer->input,
|
||||
MTP_long(prepaidId),
|
||||
Payments::InvoicePremiumGiftCodeGiveawayToTL(invoice)
|
||||
invoice.creditsAmount
|
||||
? Payments::InvoiceCreditsGiveawayToTL(invoice)
|
||||
: Payments::InvoicePremiumGiftCodeGiveawayToTL(invoice)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_peer->session().api().applyUpdates(result);
|
||||
consumer.put_done();
|
||||
@@ -537,10 +540,10 @@ Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice(
|
||||
const auto token = Token{ users, months };
|
||||
const auto &store = _stores[token];
|
||||
return Payments::InvoicePremiumGiftCode{
|
||||
.randomId = randomId,
|
||||
.currency = _optionsForOnePerson.currency,
|
||||
.amount = store.amount,
|
||||
.storeProduct = store.product,
|
||||
.randomId = randomId,
|
||||
.amount = store.amount,
|
||||
.storeQuantity = store.quantity,
|
||||
.users = token.users,
|
||||
.months = token.months,
|
||||
|
||||
@@ -57,6 +57,7 @@ struct GiveawayInfo {
|
||||
TimeId tooEarlyDate = 0;
|
||||
TimeId finishDate = 0;
|
||||
TimeId startDate = 0;
|
||||
uint64 credits = 0;
|
||||
int winnersCount = 0;
|
||||
int activatedCount = 0;
|
||||
bool participating = false;
|
||||
|
||||
@@ -44,7 +44,10 @@ void InnerFillMessagePostFlags(
|
||||
if (ShouldSendSilent(peer, options)) {
|
||||
flags |= MessageFlag::Silent;
|
||||
}
|
||||
if (!peer->amAnonymous()) {
|
||||
if (!peer->amAnonymous()
|
||||
|| (!peer->isBroadcast()
|
||||
&& options.sendAs
|
||||
&& options.sendAs != peer)) {
|
||||
flags |= MessageFlag::HasFromId;
|
||||
}
|
||||
const auto channel = peer->asBroadcast();
|
||||
@@ -544,7 +547,7 @@ void SendConfirmedFile(
|
||||
MTP_flags(Flag::f_document
|
||||
| (file->spoiler ? Flag::f_spoiler : Flag())),
|
||||
file->document,
|
||||
MTPDocument(), // alt_document
|
||||
MTPVector<MTPDocument>(), // alt_documents
|
||||
MTPint());
|
||||
} else if (file->type == SendMediaType::Audio) {
|
||||
const auto ttlSeconds = file->to.options.ttlSeconds;
|
||||
@@ -569,7 +572,7 @@ void SendConfirmedFile(
|
||||
| (isVoice ? Flag::f_voice : Flag())
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())),
|
||||
file->document,
|
||||
MTPDocument(), // alt_document
|
||||
MTPVector<MTPDocument>(), // alt_documents
|
||||
MTP_int(ttlSeconds));
|
||||
} else {
|
||||
Unexpected("Type in sendFilesConfirmed.");
|
||||
|
||||
@@ -571,13 +571,22 @@ rpl::producer<rpl::no_value, QString> Boosts::request() {
|
||||
_boostStatus.prepaidGiveaway = ranges::views::all(
|
||||
data.vprepaid_giveaways()->v
|
||||
) | ranges::views::transform([](const MTPPrepaidGiveaway &r) {
|
||||
return Data::BoostPrepaidGiveaway{
|
||||
.months = r.data().vmonths().v,
|
||||
.id = r.data().vid().v,
|
||||
.quantity = r.data().vquantity().v,
|
||||
.date = QDateTime::fromSecsSinceEpoch(
|
||||
r.data().vdate().v),
|
||||
};
|
||||
return r.match([&](const MTPDprepaidGiveaway &data) {
|
||||
return Data::BoostPrepaidGiveaway{
|
||||
.date = base::unixtime::parse(data.vdate().v),
|
||||
.id = data.vid().v,
|
||||
.months = data.vmonths().v,
|
||||
.quantity = data.vquantity().v,
|
||||
};
|
||||
}, [&](const MTPDprepaidStarsGiveaway &data) {
|
||||
return Data::BoostPrepaidGiveaway{
|
||||
.date = base::unixtime::parse(data.vdate().v),
|
||||
.id = data.vid().v,
|
||||
.credits = data.vstars().v,
|
||||
.quantity = data.vquantity().v,
|
||||
.boosts = data.vboosts().v,
|
||||
};
|
||||
});
|
||||
}) | ranges::to_vector;
|
||||
}
|
||||
|
||||
@@ -635,19 +644,21 @@ void Boosts::requestBoosts(
|
||||
}
|
||||
: Data::GiftCodeLink();
|
||||
list.push_back({
|
||||
data.is_gift(),
|
||||
data.is_giveaway(),
|
||||
data.is_unclaimed(),
|
||||
qs(data.vid()),
|
||||
data.vuser_id().value_or_empty(),
|
||||
data.vgiveaway_msg_id()
|
||||
.id = qs(data.vid()),
|
||||
.userId = UserId(data.vuser_id().value_or_empty()),
|
||||
.giveawayMessage = data.vgiveaway_msg_id()
|
||||
? FullMsgId{ _peer->id, data.vgiveaway_msg_id()->v }
|
||||
: FullMsgId(),
|
||||
QDateTime::fromSecsSinceEpoch(data.vdate().v),
|
||||
QDateTime::fromSecsSinceEpoch(data.vexpires().v),
|
||||
(data.vexpires().v - data.vdate().v) / kMonthsDivider,
|
||||
std::move(giftCodeLink),
|
||||
data.vmultiplier().value_or_empty(),
|
||||
.date = base::unixtime::parse(data.vdate().v),
|
||||
.expiresAt = base::unixtime::parse(data.vexpires().v),
|
||||
.expiresAfterMonths = ((data.vexpires().v - data.vdate().v)
|
||||
/ kMonthsDivider),
|
||||
.giftCodeLink = std::move(giftCodeLink),
|
||||
.multiplier = data.vmultiplier().value_or_empty(),
|
||||
.credits = data.vstars().value_or_empty(),
|
||||
.isGift = data.is_gift(),
|
||||
.isGiveaway = data.is_giveaway(),
|
||||
.isUnclaimed = data.is_unclaimed(),
|
||||
});
|
||||
}
|
||||
done(Data::BoostsListSlice{
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_authorizations.h"
|
||||
#include "api/api_user_names.h"
|
||||
#include "api/api_chat_participants.h"
|
||||
#include "api/api_global_privacy.h"
|
||||
#include "api/api_ringtones.h"
|
||||
#include "api/api_text_entities.h"
|
||||
#include "api/api_user_privacy.h"
|
||||
@@ -416,13 +417,12 @@ void Updates::channelDifferenceDone(
|
||||
"{ good - after not final channelDifference was received }%1"
|
||||
).arg(_session->mtp().isTestMode() ? " TESTMODE" : ""));
|
||||
getChannelDifference(channel);
|
||||
} else if (ranges::contains(
|
||||
_activeChats,
|
||||
channel,
|
||||
[](const auto &pair) { return pair.second.peer; })) {
|
||||
channel->ptsWaitingForShortPoll(timeout
|
||||
} else if (inActiveChats(channel)) {
|
||||
channel->ptsSetWaitingForShortPoll(timeout
|
||||
? (timeout * crl::time(1000))
|
||||
: kWaitForChannelGetDifference);
|
||||
} else {
|
||||
channel->ptsSetWaitingForShortPoll(-1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -654,6 +654,7 @@ void Updates::getDifferenceAfterFail() {
|
||||
wait = wait ? std::min(wait, i->second - now) : (i->second - now);
|
||||
++i;
|
||||
} else {
|
||||
i->first->ptsSetRequesting(false);
|
||||
getChannelDifference(i->first, ChannelDifferenceRequest::AfterFail);
|
||||
i = _whenGetDiffAfterFail.erase(i);
|
||||
}
|
||||
@@ -702,7 +703,9 @@ void Updates::getChannelDifference(
|
||||
_whenGetDiffByPts.remove(channel);
|
||||
}
|
||||
|
||||
if (!channel->ptsInited() || channel->ptsRequesting()) return;
|
||||
if (!channel->ptsInited() || channel->ptsRequesting()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (from != ChannelDifferenceRequest::AfterFail) {
|
||||
_whenGetDiffAfterFail.remove(channel);
|
||||
@@ -739,16 +742,32 @@ void Updates::addActiveChat(rpl::producer<PeerData*> chat) {
|
||||
std::move(
|
||||
chat
|
||||
) | rpl::start_with_next_done([=](PeerData *peer) {
|
||||
_activeChats[key].peer = peer;
|
||||
if (const auto channel = peer ? peer->asChannel() : nullptr) {
|
||||
channel->ptsWaitingForShortPoll(
|
||||
kWaitForChannelGetDifference);
|
||||
auto &active = _activeChats[key];
|
||||
const auto was = active.peer;
|
||||
if (was != peer) {
|
||||
active.peer = peer;
|
||||
if (const auto channel = was ? was->asChannel() : nullptr) {
|
||||
if (!inActiveChats(channel)) {
|
||||
channel->ptsSetWaitingForShortPoll(-1);
|
||||
}
|
||||
}
|
||||
if (const auto channel = peer ? peer->asChannel() : nullptr) {
|
||||
channel->ptsSetWaitingForShortPoll(
|
||||
kWaitForChannelGetDifference);
|
||||
}
|
||||
}
|
||||
}, [=] {
|
||||
_activeChats.erase(key);
|
||||
}, _activeChats[key].lifetime);
|
||||
}
|
||||
|
||||
bool Updates::inActiveChats(not_null<PeerData*> peer) const {
|
||||
return ranges::contains(
|
||||
_activeChats,
|
||||
peer.get(),
|
||||
[](const auto &pair) { return pair.second.peer; });
|
||||
}
|
||||
|
||||
void Updates::requestChannelRangeDifference(not_null<History*> history) {
|
||||
Expects(history->peer->isChannel());
|
||||
|
||||
@@ -1553,6 +1572,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
}
|
||||
if (channel && !_handlingChannelDifference) {
|
||||
if (channel->ptsRequesting()) { // skip global updates while getting channel difference
|
||||
MTP_LOG(0, ("Skipping new channel message because getting the difference."));
|
||||
return;
|
||||
}
|
||||
channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
|
||||
@@ -1645,6 +1665,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
|
||||
if (channel && !_handlingChannelDifference) {
|
||||
if (channel->ptsRequesting()) { // skip global updates while getting channel difference
|
||||
MTP_LOG(0, ("Skipping channel message edit because getting the difference."));
|
||||
return;
|
||||
} else {
|
||||
channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
|
||||
@@ -1660,6 +1681,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
|
||||
if (channel && !_handlingChannelDifference) {
|
||||
if (channel->ptsRequesting()) { // skip global updates while getting channel difference
|
||||
MTP_LOG(0, ("Skipping pinned channel messages because getting the difference."));
|
||||
return;
|
||||
} else {
|
||||
channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
|
||||
@@ -1774,6 +1796,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
|
||||
if (channel && !_handlingChannelDifference) {
|
||||
if (channel->ptsRequesting()) { // skip global updates while getting channel difference
|
||||
MTP_LOG(0, ("Skipping delete channel messages because getting the difference."));
|
||||
return;
|
||||
}
|
||||
channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
|
||||
@@ -1837,6 +1860,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
auto channel = session().data().channelLoaded(d.vchannel_id());
|
||||
if (channel && !_handlingChannelDifference) {
|
||||
if (channel->ptsRequesting()) { // skip global updates while getting channel difference
|
||||
MTP_LOG(0, ("Skipping channel web page update because getting the difference."));
|
||||
return;
|
||||
} else {
|
||||
channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
|
||||
@@ -2622,6 +2646,12 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
_session->credits().apply(data);
|
||||
} break;
|
||||
|
||||
case mtpc_updatePaidReactionPrivacy: {
|
||||
const auto &data = update.c_updatePaidReactionPrivacy();
|
||||
_session->api().globalPrivacy().updatePaidReactionAnonymous(
|
||||
mtpIsTrue(data.vprivate()));
|
||||
} break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ public:
|
||||
void requestChannelRangeDifference(not_null<History*> history);
|
||||
|
||||
void addActiveChat(rpl::producer<PeerData*> chat);
|
||||
[[nodiscard]] bool inActiveChats(not_null<PeerData*> peer) const;
|
||||
|
||||
private:
|
||||
enum class ChannelDifferenceRequest {
|
||||
|
||||
@@ -214,7 +214,10 @@ struct State {
|
||||
|
||||
[[nodiscard]] QImage GenerateUserpic(Userpic &userpic, int size) {
|
||||
size *= style::DevicePixelRatio();
|
||||
auto result = userpic.peer->generateUserpicImage(userpic.view, size);
|
||||
auto result = PeerData::GenerateUserpicImage(
|
||||
userpic.peer,
|
||||
userpic.view,
|
||||
size);
|
||||
result.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -2028,7 +2028,7 @@ void ApiWrap::deleteHistory(
|
||||
}
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
if (!justClear && !revoke) {
|
||||
channel->ptsWaitingForShortPoll(-1);
|
||||
channel->ptsSetWaitingForShortPoll(-1);
|
||||
leaveChannel(channel);
|
||||
} else {
|
||||
if (const auto migrated = peer->migrateFrom()) {
|
||||
@@ -4160,8 +4160,10 @@ void ApiWrap::sendMediaWithRandomId(
|
||||
Data::Histories::ReplyToPlaceholder(),
|
||||
(options.price
|
||||
? MTPInputMedia(MTP_inputMediaPaidMedia(
|
||||
MTP_flags(0),
|
||||
MTP_long(options.price),
|
||||
MTP_vector<MTPInputMedia>(1, media)))
|
||||
MTP_vector<MTPInputMedia>(1, media),
|
||||
MTPstring()))
|
||||
: media),
|
||||
MTP_string(caption.text),
|
||||
MTP_long(randomId),
|
||||
@@ -4232,8 +4234,10 @@ void ApiWrap::sendMultiPaidMedia(
|
||||
peer->input,
|
||||
Data::Histories::ReplyToPlaceholder(),
|
||||
MTP_inputMediaPaidMedia(
|
||||
MTP_flags(0),
|
||||
MTP_long(options.price),
|
||||
MTP_vector<MTPInputMedia>(std::move(medias))),
|
||||
MTP_vector<MTPInputMedia>(std::move(medias)),
|
||||
MTPstring()),
|
||||
MTP_string(caption.text),
|
||||
MTP_long(randomId),
|
||||
MTPReplyMarkup(),
|
||||
|
||||
@@ -217,7 +217,9 @@ void ShowAddParticipantsError(
|
||||
channel,
|
||||
user,
|
||||
ChatAdminRightsInfo(),
|
||||
QString());
|
||||
QString(),
|
||||
0,
|
||||
nullptr);
|
||||
box->setSaveCallback(saveCallback);
|
||||
*weak = box.data();
|
||||
show->showBox(std::move(box));
|
||||
|
||||
@@ -597,8 +597,6 @@ rightsHeaderLabel: FlatLabel(boxLabel) {
|
||||
}
|
||||
textFg: windowActiveTextFg;
|
||||
}
|
||||
rightsUntilMargin: margins(0px, 8px, 0px, 20px);
|
||||
rightsRankMargin: margins(0px, 7px, 0px, 20px);
|
||||
|
||||
groupStickersRemove: defaultMultiSelectSearchCancel;
|
||||
groupStickersRemovePosition: point(6px, 6px);
|
||||
@@ -787,7 +785,7 @@ backgroundConfirmPadding: margins(24px, 16px, 24px, 16px);
|
||||
backgroundConfirm: RoundButton(defaultActiveButton) {
|
||||
height: 44px;
|
||||
textTop: 12px;
|
||||
font: font(13px semibold);
|
||||
style: semiboldTextStyle;
|
||||
}
|
||||
backgroundConfirmCancel: RoundButton(backgroundConfirm) {
|
||||
textFg: mediaviewSaveMsgFg;
|
||||
@@ -799,7 +797,7 @@ backgroundConfirmCancel: RoundButton(backgroundConfirm) {
|
||||
|
||||
height: 44px;
|
||||
textTop: 12px;
|
||||
font: font(13px semibold);
|
||||
style: semiboldTextStyle;
|
||||
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: shadowFg;
|
||||
@@ -951,7 +949,7 @@ sponsoredUrlButton: RoundButton(defaultActiveButton) {
|
||||
textFg: historyLinkInFg;
|
||||
textFgOver: historyLinkInFg;
|
||||
textTop: 7px;
|
||||
font: normalFont;
|
||||
style: defaultTextStyle;
|
||||
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
@@ -1122,3 +1120,10 @@ moderateBoxDividerLabel: FlatLabel(boxDividerLabel) {
|
||||
selectLinkFg: windowActiveTextFg;
|
||||
}
|
||||
}
|
||||
|
||||
profileQrFont: font(fsize bold);
|
||||
profileQrCenterSize: 34px;
|
||||
profileQrBackgroundRadius: 12px;
|
||||
profileQrIcon: icon{{ "qr_mini", windowActiveTextFg }};
|
||||
profileQrBackgroundMargins: margins(36px, 12px, 36px, 12px);
|
||||
profileQrBackgroundPadding: margins(0px, 24px, 0px, 24px);
|
||||
|
||||
@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/premium_limits_box.h"
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "chat_helpers/emoji_suggestions_widget.h"
|
||||
#include "chat_helpers/field_autocomplete.h"
|
||||
#include "chat_helpers/message_field.h"
|
||||
#include "chat_helpers/tabbed_panel.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
@@ -371,6 +372,14 @@ void EditCaptionBox::StartPhotoEdit(
|
||||
});
|
||||
}
|
||||
|
||||
void EditCaptionBox::showFinished() {
|
||||
if (const auto raw = _autocomplete.get()) {
|
||||
InvokeQueued(raw, [=] {
|
||||
raw->raise();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void EditCaptionBox::prepare() {
|
||||
const auto button = addButton(tr::lng_settings_save(), [=] { save(); });
|
||||
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||
@@ -525,6 +534,7 @@ void EditCaptionBox::setupField() {
|
||||
_field.get(),
|
||||
Window::GifPauseReason::Layer,
|
||||
allow);
|
||||
setupFieldAutocomplete();
|
||||
Ui::Emoji::SuggestionsController::Init(
|
||||
getDelegate()->outerContainer(),
|
||||
_field,
|
||||
@@ -562,6 +572,55 @@ void EditCaptionBox::setupField() {
|
||||
});
|
||||
}
|
||||
|
||||
void EditCaptionBox::setupFieldAutocomplete() {
|
||||
const auto parent = getDelegate()->outerContainer();
|
||||
ChatHelpers::InitFieldAutocomplete(_autocomplete, {
|
||||
.parent = parent,
|
||||
.show = _controller->uiShow(),
|
||||
.field = _field.get(),
|
||||
.peer = _historyItem->history()->peer,
|
||||
.features = [=] {
|
||||
auto result = ChatHelpers::ComposeFeatures();
|
||||
result.autocompleteCommands = false;
|
||||
result.suggestStickersByEmoji = false;
|
||||
return result;
|
||||
},
|
||||
});
|
||||
const auto raw = _autocomplete.get();
|
||||
const auto scheduled = std::make_shared<bool>();
|
||||
const auto recountPostponed = [=] {
|
||||
if (*scheduled) {
|
||||
return;
|
||||
}
|
||||
*scheduled = true;
|
||||
Ui::PostponeCall(raw, [=] {
|
||||
*scheduled = false;
|
||||
|
||||
auto field = Ui::MapFrom(parent, this, _field->geometry());
|
||||
_autocomplete->setBoundings(QRect(
|
||||
field.x() - _field->x(),
|
||||
st::defaultBox.margin.top(),
|
||||
width(),
|
||||
(field.y()
|
||||
+ st::defaultComposeFiles.caption.textMargins.top()
|
||||
+ st::defaultComposeFiles.caption.placeholderShift
|
||||
+ st::defaultComposeFiles.caption.placeholderFont->height
|
||||
- st::defaultBox.margin.top())));
|
||||
});
|
||||
};
|
||||
for (auto w = (QWidget*)_field.get(); w; w = w->parentWidget()) {
|
||||
base::install_event_filter(raw, w, [=](not_null<QEvent*> e) {
|
||||
if (e->type() == QEvent::Move || e->type() == QEvent::Resize) {
|
||||
recountPostponed();
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
if (w == parent) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditCaptionBox::setInitialText() {
|
||||
_field->setTextWithTags(
|
||||
_initialText,
|
||||
|
||||
@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace ChatHelpers {
|
||||
class TabbedPanel;
|
||||
class FieldAutocomplete;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace Window {
|
||||
@@ -68,6 +69,8 @@ public:
|
||||
bool invertCaption,
|
||||
Fn<void()> saved);
|
||||
|
||||
void showFinished() override;
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
void setInnerFocus() override;
|
||||
@@ -81,6 +84,7 @@ private:
|
||||
void setupEditEventHandler();
|
||||
void setupPhotoEditorEventHandler();
|
||||
void setupField();
|
||||
void setupFieldAutocomplete();
|
||||
void setupControls();
|
||||
void setInitialText();
|
||||
|
||||
@@ -115,6 +119,8 @@ private:
|
||||
const base::unique_qptr<Ui::InputField> _field;
|
||||
const base::unique_qptr<Ui::EmojiButton> _emojiToggle;
|
||||
|
||||
std::unique_ptr<ChatHelpers::FieldAutocomplete> _autocomplete;
|
||||
|
||||
base::unique_qptr<Ui::AbstractSinglePreview> _content;
|
||||
base::unique_qptr<ChatHelpers::TabbedPanel> _emojiPanel;
|
||||
base::unique_qptr<QObject> _emojiFilter;
|
||||
|
||||
@@ -195,7 +195,7 @@ PaintRoundImageCallback PremiumsRow::generatePaintUserpicCallback(
|
||||
const auto radius = size * Ui::ForumUserpicRadiusMultiplier();
|
||||
p.drawRoundedRect(x, y, size, size, radius, radius);
|
||||
}
|
||||
st::settingsPrivacyPremium.paintInCenter(p, { x, y, size, size });
|
||||
st::settingsPrivacyPremium.paintInCenter(p, QRect(x, y, size, size));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_info.h"
|
||||
@@ -581,6 +582,7 @@ void LinkController::addLinkBlock(not_null<Ui::VerticalLayout*> container) {
|
||||
});
|
||||
const auto getLinkQr = crl::guard(weak, [=] {
|
||||
delegate()->peerListUiShow()->showBox(InviteLinkQrBox(
|
||||
nullptr,
|
||||
link,
|
||||
tr::lng_group_invite_qr_title(),
|
||||
tr::lng_filters_link_qr_about()));
|
||||
@@ -889,6 +891,7 @@ base::unique_qptr<Ui::PopupMenu> LinksController::createRowContextMenu(
|
||||
};
|
||||
const auto getLinkQr = [=] {
|
||||
delegate()->peerListUiShow()->showBox(InviteLinkQrBox(
|
||||
nullptr,
|
||||
link,
|
||||
tr::lng_group_invite_qr_title(),
|
||||
tr::lng_filters_link_qr_about()));
|
||||
@@ -965,9 +968,9 @@ void LinksController::rowPaintIcon(
|
||||
p.setBrush(*bg);
|
||||
{
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.drawEllipse(QRect(0, 0, inner, inner));
|
||||
p.drawEllipse(Rect(Size(inner)));
|
||||
}
|
||||
st::inviteLinkIcon.paintInCenter(p, { 0, 0, inner, inner });
|
||||
st::inviteLinkIcon.paintInCenter(p, Rect(Size(inner)));
|
||||
}
|
||||
p.drawImage(x + skip, y + skip, icon);
|
||||
}
|
||||
@@ -1113,7 +1116,7 @@ QString FilterChatStatusText(not_null<PeerData*> peer) {
|
||||
? tr::lng_chat_status_subscribers
|
||||
: tr::lng_chat_status_members)(
|
||||
tr::now,
|
||||
lt_count,
|
||||
lt_count_decimal,
|
||||
channel->membersCount());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ void GiftCreditsBox(
|
||||
) | rpl::map([](TextWithEntities text) {
|
||||
return Ui::Text::Link(
|
||||
std::move(text),
|
||||
tr::lng_credits_box_history_entry_gift_about_url(tr::now));
|
||||
u"internal:stars_examples"_q);
|
||||
});
|
||||
content->add(
|
||||
object_ptr<Ui::CenterWrap<>>(
|
||||
|
||||
@@ -69,6 +69,24 @@ constexpr auto kUserpicsMax = size_t(3);
|
||||
using GiftOption = Data::PremiumSubscriptionOption;
|
||||
using GiftOptions = Data::PremiumSubscriptionOptions;
|
||||
|
||||
[[nodiscard]] QString CreateMessageLink(
|
||||
not_null<Main::Session*> session,
|
||||
PeerId peerId,
|
||||
uint64 messageId) {
|
||||
if (const auto msgId = MsgId(peerId ? messageId : 0)) {
|
||||
const auto peer = session->data().peer(peerId);
|
||||
if (const auto channel = peer->asBroadcast()) {
|
||||
const auto username = channel->username();
|
||||
const auto base = username.isEmpty()
|
||||
? u"c/%1"_q.arg(peerToChannel(channel->id).bare)
|
||||
: username;
|
||||
const auto query = base + '/' + QString::number(msgId.bare);
|
||||
return session->createInternalLink(query);
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
};
|
||||
|
||||
GiftOptions GiftOptionFromTL(const MTPDuserFull &data) {
|
||||
auto result = GiftOptions();
|
||||
const auto gifts = data.vpremium_gifts();
|
||||
@@ -1419,21 +1437,56 @@ void GiveawayInfoBox(
|
||||
: !start->channels.empty()
|
||||
? start->channels.front()->name()
|
||||
: u"channel"_q;
|
||||
auto text = TextWithEntities();
|
||||
|
||||
if (!info.giftCode.isEmpty()) {
|
||||
text.append("\n\n");
|
||||
text.append(Ui::Text::Bold(tr::lng_prizes_you_won(
|
||||
tr::now,
|
||||
auto resultText = (!info.giftCode.isEmpty())
|
||||
? tr::lng_prizes_you_won(
|
||||
lt_cup,
|
||||
QString::fromUtf8("\xf0\x9f\x8f\x86"))));
|
||||
text.append("\n\n");
|
||||
} else if (info.state == State::Finished) {
|
||||
text.append("\n\n");
|
||||
text.append(Ui::Text::Bold(tr::lng_prizes_you_didnt(tr::now)));
|
||||
text.append("\n\n");
|
||||
rpl::single(
|
||||
TextWithEntities{ QString::fromUtf8("\xf0\x9f\x8f\x86") }),
|
||||
Ui::Text::WithEntities)
|
||||
: (info.credits)
|
||||
? tr::lng_prizes_you_won_credits(
|
||||
lt_amount,
|
||||
tr::lng_prizes_you_won_credits_amount(
|
||||
lt_count,
|
||||
rpl::single(float64(info.credits)),
|
||||
Ui::Text::Bold),
|
||||
lt_cup,
|
||||
rpl::single(
|
||||
TextWithEntities{ QString::fromUtf8("\xf0\x9f\x8f\x86") }),
|
||||
Ui::Text::WithEntities)
|
||||
: (info.state == State::Finished)
|
||||
? tr::lng_prizes_you_didnt(Ui::Text::WithEntities)
|
||||
: (rpl::producer<TextWithEntities>)(nullptr);
|
||||
|
||||
if (resultText) {
|
||||
const auto &st = st::changePhoneDescription;
|
||||
const auto skip = st.style.font->height * 0.5;
|
||||
auto label = object_ptr<Ui::FlatLabel>(
|
||||
box.get(),
|
||||
std::move(resultText),
|
||||
st);
|
||||
if ((!info.giftCode.isEmpty()) || info.credits) {
|
||||
label->setTextColorOverride(st::windowActiveTextFg->c);
|
||||
}
|
||||
const auto result = box->addRow(
|
||||
object_ptr<Ui::PaddingWrap<Ui::CenterWrap<Ui::FlatLabel>>>(
|
||||
box.get(),
|
||||
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
|
||||
box.get(),
|
||||
std::move(label)),
|
||||
QMargins(0, skip, 0, skip)));
|
||||
result->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = QPainter(result);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::boxDividerBg);
|
||||
p.drawRoundedRect(result->rect(), st::boxRadius, st::boxRadius);
|
||||
}, result->lifetime());
|
||||
Ui::AddSkip(box->verticalLayout());
|
||||
}
|
||||
|
||||
auto text = TextWithEntities();
|
||||
|
||||
const auto quantity = start
|
||||
? start->quantity
|
||||
: (results->winnersCount + results->unclaimedCount);
|
||||
@@ -1442,22 +1495,39 @@ void GiveawayInfoBox(
|
||||
? results->channel->isMegagroup()
|
||||
: (!start->channels.empty()
|
||||
&& start->channels.front()->isMegagroup());
|
||||
const auto credits = start
|
||||
? start->credits
|
||||
: (results ? results->credits : 0);
|
||||
text.append((finished
|
||||
? tr::lng_prizes_end_text
|
||||
: tr::lng_prizes_how_text)(
|
||||
tr::now,
|
||||
lt_admins,
|
||||
(group
|
||||
? tr::lng_prizes_admins_group
|
||||
: tr::lng_prizes_admins)(
|
||||
tr::now,
|
||||
lt_count,
|
||||
quantity,
|
||||
lt_channel,
|
||||
Ui::Text::Bold(first),
|
||||
lt_duration,
|
||||
TextWithEntities{ GiftDuration(months) },
|
||||
Ui::Text::RichLangValue),
|
||||
credits
|
||||
? (group
|
||||
? tr::lng_prizes_credits_admins_group
|
||||
: tr::lng_prizes_credits_admins)(
|
||||
tr::now,
|
||||
lt_channel,
|
||||
Ui::Text::Bold(first),
|
||||
lt_amount,
|
||||
tr::lng_prizes_credits_admins_amount(
|
||||
tr::now,
|
||||
lt_count_decimal,
|
||||
float64(credits),
|
||||
Ui::Text::Bold),
|
||||
Ui::Text::RichLangValue)
|
||||
: (group
|
||||
? tr::lng_prizes_admins_group
|
||||
: tr::lng_prizes_admins)(
|
||||
tr::now,
|
||||
lt_count,
|
||||
quantity,
|
||||
lt_channel,
|
||||
Ui::Text::Bold(first),
|
||||
lt_duration,
|
||||
TextWithEntities{ GiftDuration(months) },
|
||||
Ui::Text::RichLangValue),
|
||||
Ui::Text::RichLangValue));
|
||||
const auto many = start
|
||||
? (start->channels.size() > 1)
|
||||
@@ -1651,6 +1721,7 @@ void AddCreditsHistoryEntryTable(
|
||||
st::giveawayGiftCodeTable),
|
||||
st::giveawayGiftCodeTableMargin);
|
||||
const auto peerId = PeerId(entry.barePeerId);
|
||||
const auto session = &controller->session();
|
||||
if (peerId) {
|
||||
auto text = entry.in
|
||||
? tr::lng_credits_box_history_entry_peer_in()
|
||||
@@ -1658,15 +1729,12 @@ void AddCreditsHistoryEntryTable(
|
||||
AddTableRow(table, std::move(text), controller, peerId);
|
||||
}
|
||||
if (const auto msgId = MsgId(peerId ? entry.bareMsgId : 0)) {
|
||||
const auto session = &controller->session();
|
||||
const auto peer = session->data().peer(peerId);
|
||||
if (const auto channel = peer->asBroadcast()) {
|
||||
const auto username = channel->username();
|
||||
const auto base = username.isEmpty()
|
||||
? u"c/%1"_q.arg(peerToChannel(channel->id).bare)
|
||||
: username;
|
||||
const auto query = base + '/' + QString::number(msgId.bare);
|
||||
const auto link = session->createInternalLink(query);
|
||||
const auto link = CreateMessageLink(
|
||||
session,
|
||||
peerId,
|
||||
entry.bareMsgId);
|
||||
auto label = object_ptr<Ui::FlatLabel>(
|
||||
table,
|
||||
rpl::single(Ui::Text::Link(link)),
|
||||
@@ -1717,6 +1785,37 @@ void AddCreditsHistoryEntryTable(
|
||||
tr::lng_credits_box_history_entry_via_premium_bot(
|
||||
Ui::Text::RichLangValue));
|
||||
}
|
||||
if (entry.bareGiveawayMsgId) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_to(),
|
||||
controller,
|
||||
controller->session().userId());
|
||||
}
|
||||
if (entry.bareGiveawayMsgId && entry.credits) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_gift(),
|
||||
tr::lng_gift_stars_title(
|
||||
lt_count,
|
||||
rpl::single(float64(entry.credits)),
|
||||
Ui::Text::RichLangValue));
|
||||
}
|
||||
{
|
||||
const auto link = CreateMessageLink(
|
||||
session,
|
||||
peerId,
|
||||
entry.bareGiveawayMsgId);
|
||||
if (!link.isEmpty()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_reason(),
|
||||
tr::lng_gift_link_reason_giveaway(
|
||||
) | rpl::map([link](const QString &text) {
|
||||
return Ui::Text::Link(text, link);
|
||||
}));
|
||||
}
|
||||
}
|
||||
if (!entry.id.isEmpty()) {
|
||||
constexpr auto kOneLineCount = 18;
|
||||
const auto oneLine = entry.id.length() <= kOneLineCount;
|
||||
@@ -1813,3 +1912,60 @@ void AddSubscriberEntryTable(
|
||||
rpl::single(Ui::Text::WithEntities(langDateTime(d))));
|
||||
}
|
||||
}
|
||||
|
||||
void AddCreditsBoostTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
const Data::Boost &b) {
|
||||
auto table = container->add(
|
||||
object_ptr<Ui::TableLayout>(
|
||||
container,
|
||||
st::giveawayGiftCodeTable),
|
||||
st::giveawayGiftCodeTableMargin);
|
||||
const auto peerId = b.giveawayMessage.peer;
|
||||
if (!peerId) {
|
||||
return;
|
||||
}
|
||||
const auto from = controller->session().data().peer(peerId);
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_peer_in(),
|
||||
controller,
|
||||
from->id);
|
||||
if (b.credits) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_gift(),
|
||||
tr::lng_gift_stars_title(
|
||||
lt_count,
|
||||
rpl::single(float64(b.credits)),
|
||||
Ui::Text::RichLangValue));
|
||||
}
|
||||
{
|
||||
const auto link = CreateMessageLink(
|
||||
&controller->session(),
|
||||
peerId,
|
||||
b.giveawayMessage.msg.bare);
|
||||
if (!link.isEmpty()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_reason(),
|
||||
tr::lng_gift_link_reason_giveaway(
|
||||
) | rpl::map([link](const QString &text) {
|
||||
return Ui::Text::Link(text, link);
|
||||
}));
|
||||
}
|
||||
}
|
||||
if (!b.date.isNull()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_date(),
|
||||
rpl::single(Ui::Text::WithEntities(langDateTime(b.date))));
|
||||
}
|
||||
if (!b.expiresAt.isNull()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_until(),
|
||||
rpl::single(Ui::Text::WithEntities(langDateTime(b.expiresAt))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ struct GiftCode;
|
||||
} // namespace Api
|
||||
|
||||
namespace Data {
|
||||
struct Boost;
|
||||
struct CreditsHistoryEntry;
|
||||
struct GiveawayStart;
|
||||
struct GiveawayResults;
|
||||
@@ -89,3 +90,8 @@ void AddSubscriberEntryTable(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<PeerData*> peer,
|
||||
TimeId date);
|
||||
|
||||
void AddCreditsBoostTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
const Data::Boost &boost);
|
||||
|
||||
@@ -436,14 +436,16 @@ void CreateModerateMessagesBox(
|
||||
return result;
|
||||
}();
|
||||
|
||||
auto [checkboxes, getRestrictions, changes] = CreateEditRestrictions(
|
||||
box,
|
||||
Ui::AddSubsectionTitle(
|
||||
inner,
|
||||
rpl::conditional(
|
||||
rpl::single(isSingle),
|
||||
tr::lng_restrict_users_part_single_header(),
|
||||
tr::lng_restrict_users_part_header(
|
||||
lt_count,
|
||||
rpl::single(participants.size()) | tr::to_count())),
|
||||
rpl::single(participants.size()) | tr::to_count())));
|
||||
auto [checkboxes, getRestrictions, changes] = CreateEditRestrictions(
|
||||
box,
|
||||
prepareFlags,
|
||||
disabledMessages,
|
||||
{ .isForum = peer->isForum() });
|
||||
|
||||
@@ -170,11 +170,15 @@ void AddBotToGroupBoxController::requestExistingRights(
|
||||
channel);
|
||||
_existingRights = participant.rights().flags;
|
||||
_existingRank = participant.rank();
|
||||
_promotedSince = participant.promotedSince();
|
||||
_promotedBy = participant.by();
|
||||
addBotToGroup(_existingRightsChannel);
|
||||
});
|
||||
}).fail([=] {
|
||||
_existingRights = ChatAdminRights();
|
||||
_existingRank = QString();
|
||||
_promotedSince = 0;
|
||||
_promotedBy = 0;
|
||||
addBotToGroup(_existingRightsChannel);
|
||||
}).send();
|
||||
}
|
||||
@@ -191,6 +195,8 @@ void AddBotToGroupBoxController::addBotToGroup(not_null<PeerData*> chat) {
|
||||
_existingRights = {};
|
||||
_existingRank = QString();
|
||||
_existingRightsChannel = nullptr;
|
||||
_promotedSince = 0;
|
||||
_promotedBy = 0;
|
||||
_bot->session().api().request(_existingRightsRequestId).cancel();
|
||||
}
|
||||
const auto requestedAddAdmin = (_scope == Scope::GroupAdmin)
|
||||
@@ -241,9 +247,12 @@ void AddBotToGroupBoxController::addBotToGroup(not_null<PeerData*> chat) {
|
||||
bot,
|
||||
ChatAdminRightsInfo(rights),
|
||||
_existingRank,
|
||||
_promotedSince,
|
||||
_promotedBy ? chat->owner().user(_promotedBy).get() : nullptr,
|
||||
EditAdminBotFields{
|
||||
_token,
|
||||
_existingRights.value_or(ChatAdminRights()) });
|
||||
_existingRights.value_or(ChatAdminRights()),
|
||||
});
|
||||
box->setSaveCallback(saveCallback);
|
||||
controller->show(std::move(box));
|
||||
} else {
|
||||
|
||||
@@ -65,6 +65,8 @@ private:
|
||||
mtpRequestId _existingRightsRequestId = 0;
|
||||
std::optional<ChatAdminRights> _existingRights;
|
||||
QString _existingRank;
|
||||
TimeId _promotedSince = 0;
|
||||
UserId _promotedBy = 0;
|
||||
|
||||
rpl::event_stream<not_null<PeerData*>> _groups;
|
||||
rpl::event_stream<not_null<PeerData*>> _channels;
|
||||
|
||||
@@ -1276,7 +1276,9 @@ void AddSpecialBoxController::showAdmin(
|
||||
_peer,
|
||||
user,
|
||||
currentRights,
|
||||
_additional.adminRank(user));
|
||||
_additional.adminRank(user),
|
||||
_additional.adminPromotedSince(user),
|
||||
_additional.adminPromotedBy(user));
|
||||
const auto show = delegate()->peerListUiShow();
|
||||
if (_additional.canAddOrEditAdmin(user)) {
|
||||
const auto done = crl::guard(this, [=](
|
||||
@@ -1354,7 +1356,9 @@ void AddSpecialBoxController::showRestricted(
|
||||
_peer,
|
||||
user,
|
||||
_additional.adminRights(user).has_value(),
|
||||
currentRights);
|
||||
currentRights,
|
||||
_additional.restrictedBy(user),
|
||||
_additional.restrictedSince(user));
|
||||
if (_additional.canRestrictParticipant(user)) {
|
||||
const auto done = crl::guard(this, [=](
|
||||
ChatRestrictionsInfo newRights) {
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/controls/userpic_button.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
@@ -63,6 +64,10 @@ public:
|
||||
template <typename Widget>
|
||||
Widget *addControl(object_ptr<Widget> widget, QMargins margin);
|
||||
|
||||
[[nodiscard]] not_null<Ui::VerticalLayout*> verticalLayout() const {
|
||||
return _rows;
|
||||
}
|
||||
|
||||
protected:
|
||||
int resizeGetHeight(int newWidth) override;
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
@@ -164,6 +169,10 @@ EditParticipantBox::EditParticipantBox(
|
||||
, _hasAdminRights(hasAdminRights) {
|
||||
}
|
||||
|
||||
not_null<Ui::VerticalLayout*> EditParticipantBox::verticalLayout() const {
|
||||
return _inner->verticalLayout();
|
||||
}
|
||||
|
||||
void EditParticipantBox::prepare() {
|
||||
_inner = setInnerWidget(object_ptr<Inner>(
|
||||
this,
|
||||
@@ -197,6 +206,8 @@ EditAdminBox::EditAdminBox(
|
||||
not_null<UserData*> user,
|
||||
ChatAdminRightsInfo rights,
|
||||
const QString &rank,
|
||||
TimeId promotedSince,
|
||||
UserData *by,
|
||||
std::optional<EditAdminBotFields> addingBot)
|
||||
: EditParticipantBox(
|
||||
nullptr,
|
||||
@@ -205,6 +216,8 @@ EditAdminBox::EditAdminBox(
|
||||
(rights.flags != 0))
|
||||
, _oldRights(rights)
|
||||
, _oldRank(rank)
|
||||
, _promotedSince(promotedSince)
|
||||
, _by(by)
|
||||
, _addingBot(std::move(addingBot)) {
|
||||
}
|
||||
|
||||
@@ -279,9 +292,26 @@ void EditAdminBox::prepare() {
|
||||
object_ptr<Ui::VerticalLayout>(this)));
|
||||
const auto inner = _adminControlsWrap->entity();
|
||||
|
||||
inner->add(
|
||||
object_ptr<Ui::BoxContentDivider>(inner),
|
||||
st::rightsDividerMargin);
|
||||
if (_promotedSince) {
|
||||
const auto parsed = base::unixtime::parse(_promotedSince);
|
||||
const auto label = Ui::AddDividerText(
|
||||
inner,
|
||||
tr::lng_rights_about_by(
|
||||
lt_user,
|
||||
rpl::single(_by
|
||||
? Ui::Text::Link(_by->name(), 1)
|
||||
: TextWithEntities{ QString::fromUtf8("\U0001F47B") }),
|
||||
lt_date,
|
||||
rpl::single(TextWithEntities{ langDateTimeFull(parsed) }),
|
||||
Ui::Text::WithEntities));
|
||||
if (_by) {
|
||||
label->setLink(1, _by->createOpenLink());
|
||||
}
|
||||
Ui::AddSkip(inner);
|
||||
} else {
|
||||
Ui::AddDivider(inner);
|
||||
Ui::AddSkip(inner);
|
||||
}
|
||||
|
||||
const auto chat = peer()->asChat();
|
||||
const auto channel = peer()->asChannel();
|
||||
@@ -335,9 +365,9 @@ void EditAdminBox::prepare() {
|
||||
.isForum = peer()->isForum(),
|
||||
.anyoneCanAddMembers = anyoneCanAddMembers,
|
||||
};
|
||||
Ui::AddSubsectionTitle(inner, tr::lng_rights_edit_admin_header());
|
||||
auto [checkboxes, getChecked, changes] = CreateEditAdminRights(
|
||||
inner,
|
||||
tr::lng_rights_edit_admin_header(),
|
||||
prepareFlags,
|
||||
disabledMessages,
|
||||
options);
|
||||
@@ -348,17 +378,47 @@ void EditAdminBox::prepare() {
|
||||
) | rpl::then(std::move(
|
||||
changes
|
||||
));
|
||||
_aboutAddAdmins = inner->add(
|
||||
object_ptr<Ui::FlatLabel>(inner, st::boxDividerLabel),
|
||||
st::rightsAboutMargin);
|
||||
rpl::duplicate(
|
||||
selectedFlags
|
||||
) | rpl::map(
|
||||
(_1 & Flag::AddAdmins) != 0
|
||||
) | rpl::distinct_until_changed(
|
||||
) | rpl::start_with_next([=](bool checked) {
|
||||
refreshAboutAddAdminsText(checked);
|
||||
}, lifetime());
|
||||
|
||||
const auto hasRank = canSave() && (chat || channel->isMegagroup());
|
||||
|
||||
{
|
||||
const auto aboutAddAdminsInner = inner->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
inner,
|
||||
object_ptr<Ui::VerticalLayout>(inner)));
|
||||
const auto emptyAboutAddAdminsInner = inner->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
inner,
|
||||
object_ptr<Ui::VerticalLayout>(inner)));
|
||||
aboutAddAdminsInner->toggle(false, anim::type::instant);
|
||||
emptyAboutAddAdminsInner->toggle(false, anim::type::instant);
|
||||
Ui::AddSkip(emptyAboutAddAdminsInner->entity());
|
||||
if (hasRank) {
|
||||
Ui::AddDivider(emptyAboutAddAdminsInner->entity());
|
||||
Ui::AddSkip(emptyAboutAddAdminsInner->entity());
|
||||
}
|
||||
Ui::AddSkip(aboutAddAdminsInner->entity());
|
||||
Ui::AddDividerText(
|
||||
aboutAddAdminsInner->entity(),
|
||||
rpl::duplicate(
|
||||
selectedFlags
|
||||
) | rpl::map(
|
||||
(_1 & Flag::AddAdmins) != 0
|
||||
) | rpl::distinct_until_changed(
|
||||
) | rpl::map([=](bool canAddAdmins) -> rpl::producer<QString> {
|
||||
const auto empty = (amCreator() && user()->isSelf());
|
||||
aboutAddAdminsInner->toggle(!empty, anim::type::instant);
|
||||
emptyAboutAddAdminsInner->toggle(empty, anim::type::instant);
|
||||
if (empty) {
|
||||
return rpl::single(QString());
|
||||
} else if (!canSave()) {
|
||||
return tr::lng_rights_about_admin_cant_edit();
|
||||
} else if (canAddAdmins) {
|
||||
return tr::lng_rights_about_add_admins_yes();
|
||||
}
|
||||
return tr::lng_rights_about_add_admins_no();
|
||||
}) | rpl::flatten_latest());
|
||||
}
|
||||
|
||||
if (canTransferOwnership()) {
|
||||
const auto allFlags = AdminRightsForOwnershipTransfer(options);
|
||||
@@ -373,9 +433,7 @@ void EditAdminBox::prepare() {
|
||||
}
|
||||
|
||||
if (canSave()) {
|
||||
_rank = (chat || channel->isMegagroup())
|
||||
? addRankInput(inner).get()
|
||||
: nullptr;
|
||||
_rank = hasRank ? addRankInput(inner).get() : nullptr;
|
||||
_finishSave = [=, value = getChecked] {
|
||||
const auto newFlags = (value() | ChatAdminRight::Other)
|
||||
& ((!channel || channel->amCreator())
|
||||
@@ -441,9 +499,7 @@ void EditAdminBox::refreshButtons() {
|
||||
|
||||
not_null<Ui::InputField*> EditAdminBox::addRankInput(
|
||||
not_null<Ui::VerticalLayout*> container) {
|
||||
container->add(
|
||||
object_ptr<Ui::BoxContentDivider>(container),
|
||||
st::rightsRankMargin);
|
||||
// Ui::AddDivider(container);
|
||||
|
||||
container->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
@@ -480,14 +536,13 @@ not_null<Ui::InputField*> EditAdminBox::addRankInput(
|
||||
}
|
||||
}, result->lifetime());
|
||||
|
||||
container->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
container,
|
||||
tr::lng_rights_edit_admin_rank_about(
|
||||
lt_title,
|
||||
(isOwner ? tr::lng_owner_badge : tr::lng_admin_badge)()),
|
||||
st::boxDividerLabel),
|
||||
st::rightsAboutMargin);
|
||||
Ui::AddSkip(container);
|
||||
Ui::AddDividerText(
|
||||
container,
|
||||
tr::lng_rights_edit_admin_rank_about(
|
||||
lt_title,
|
||||
(isOwner ? tr::lng_owner_badge : tr::lng_admin_badge)()));
|
||||
Ui::AddSkip(container);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -681,27 +736,18 @@ void EditAdminBox::sendTransferRequestFrom(
|
||||
})).handleFloodErrors().send();
|
||||
}
|
||||
|
||||
void EditAdminBox::refreshAboutAddAdminsText(bool canAddAdmins) {
|
||||
_aboutAddAdmins->setText([&] {
|
||||
if (amCreator() && user()->isSelf()) {
|
||||
return QString();
|
||||
} else if (!canSave()) {
|
||||
return tr::lng_rights_about_admin_cant_edit(tr::now);
|
||||
} else if (canAddAdmins) {
|
||||
return tr::lng_rights_about_add_admins_yes(tr::now);
|
||||
}
|
||||
return tr::lng_rights_about_add_admins_no(tr::now);
|
||||
}());
|
||||
}
|
||||
|
||||
EditRestrictedBox::EditRestrictedBox(
|
||||
QWidget*,
|
||||
not_null<PeerData*> peer,
|
||||
not_null<UserData*> user,
|
||||
bool hasAdminRights,
|
||||
ChatRestrictionsInfo rights)
|
||||
ChatRestrictionsInfo rights,
|
||||
UserData *by,
|
||||
TimeId since)
|
||||
: EditParticipantBox(nullptr, peer, user, hasAdminRights)
|
||||
, _oldRights(rights) {
|
||||
, _oldRights(rights)
|
||||
, _by(by)
|
||||
, _since(since) {
|
||||
}
|
||||
|
||||
void EditRestrictedBox::prepare() {
|
||||
@@ -712,9 +758,8 @@ void EditRestrictedBox::prepare() {
|
||||
|
||||
setTitle(tr::lng_rights_user_restrictions());
|
||||
|
||||
addControl(
|
||||
object_ptr<Ui::BoxContentDivider>(this),
|
||||
st::rightsDividerMargin);
|
||||
Ui::AddDivider(verticalLayout());
|
||||
Ui::AddSkip(verticalLayout());
|
||||
|
||||
const auto chat = peer()->asChat();
|
||||
const auto channel = peer()->asChannel();
|
||||
@@ -749,16 +794,20 @@ void EditRestrictedBox::prepare() {
|
||||
return result;
|
||||
}();
|
||||
|
||||
Ui::AddSubsectionTitle(
|
||||
verticalLayout(),
|
||||
tr::lng_rights_user_restrictions_header());
|
||||
auto [checkboxes, getRestrictions, changes] = CreateEditRestrictions(
|
||||
this,
|
||||
tr::lng_rights_user_restrictions_header(),
|
||||
prepareFlags,
|
||||
disabledMessages,
|
||||
{ .isForum = peer()->isForum() });
|
||||
addControl(std::move(checkboxes), QMargins());
|
||||
|
||||
_until = prepareRights.until;
|
||||
addControl(object_ptr<Ui::BoxContentDivider>(this), st::rightsUntilMargin);
|
||||
addControl(
|
||||
object_ptr<Ui::FixedHeightWidget>(this, st::defaultVerticalListSkip));
|
||||
Ui::AddDivider(verticalLayout());
|
||||
addControl(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
this,
|
||||
@@ -773,6 +822,29 @@ void EditRestrictedBox::prepare() {
|
||||
// tr::lng_rights_chat_banned_block(tr::now),
|
||||
// st::boxLinkButton));
|
||||
|
||||
if (_since) {
|
||||
const auto parsed = base::unixtime::parse(_since);
|
||||
const auto inner = addControl(object_ptr<Ui::VerticalLayout>(this));
|
||||
const auto isBanned = (_oldRights.flags
|
||||
& ChatRestriction::ViewMessages);
|
||||
Ui::AddSkip(inner);
|
||||
const auto label = Ui::AddDividerText(
|
||||
inner,
|
||||
(isBanned
|
||||
? tr::lng_rights_chat_banned_by
|
||||
: tr::lng_rights_chat_restricted_by)(
|
||||
lt_user,
|
||||
rpl::single(_by
|
||||
? Ui::Text::Link(_by->name(), 1)
|
||||
: TextWithEntities{ QString::fromUtf8("\U0001F47B") }),
|
||||
lt_date,
|
||||
rpl::single(TextWithEntities{ langDateTimeFull(parsed) }),
|
||||
Ui::Text::WithEntities));
|
||||
if (_by) {
|
||||
label->setLink(1, _by->createOpenLink());
|
||||
}
|
||||
}
|
||||
|
||||
if (canSave()) {
|
||||
const auto save = [=, value = getRestrictions] {
|
||||
if (!_saveCallback) {
|
||||
|
||||
@@ -36,6 +36,8 @@ public:
|
||||
not_null<UserData*> user,
|
||||
bool hasAdminRights);
|
||||
|
||||
[[nodiscard]] not_null<Ui::VerticalLayout*> verticalLayout() const;
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
|
||||
@@ -77,6 +79,8 @@ public:
|
||||
not_null<UserData*> user,
|
||||
ChatAdminRightsInfo rights,
|
||||
const QString &rank,
|
||||
TimeId promotedSince,
|
||||
UserData *by,
|
||||
std::optional<EditAdminBotFields> addingBot = {});
|
||||
|
||||
void setSaveCallback(
|
||||
@@ -108,7 +112,6 @@ private:
|
||||
}
|
||||
void finishAddAdmin();
|
||||
void refreshButtons();
|
||||
void refreshAboutAddAdminsText(bool canAddAdmins);
|
||||
bool canTransferOwnership() const;
|
||||
not_null<Ui::SlideWrap<Ui::RpWidget>*> setupTransferButton(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
@@ -125,11 +128,12 @@ private:
|
||||
Ui::Checkbox *_addAsAdmin = nullptr;
|
||||
Ui::SlideWrap<Ui::VerticalLayout> *_adminControlsWrap = nullptr;
|
||||
Ui::InputField *_rank = nullptr;
|
||||
QPointer<Ui::FlatLabel> _aboutAddAdmins;
|
||||
mtpRequestId _checkTransferRequestId = 0;
|
||||
mtpRequestId _transferRequestId = 0;
|
||||
Fn<void()> _save, _finishSave;
|
||||
|
||||
TimeId _promotedSince = 0;
|
||||
UserData *_by = nullptr;
|
||||
std::optional<EditAdminBotFields> _addingBot;
|
||||
|
||||
};
|
||||
@@ -144,7 +148,9 @@ public:
|
||||
not_null<PeerData*> peer,
|
||||
not_null<UserData*> user,
|
||||
bool hasAdminRights,
|
||||
ChatRestrictionsInfo rights);
|
||||
ChatRestrictionsInfo rights,
|
||||
UserData *by,
|
||||
TimeId since);
|
||||
|
||||
void setSaveCallback(
|
||||
Fn<void(ChatRestrictionsInfo, ChatRestrictionsInfo)> callback) {
|
||||
@@ -168,6 +174,8 @@ private:
|
||||
TimeId getRealUntilValue() const;
|
||||
|
||||
const ChatRestrictionsInfo _oldRights;
|
||||
UserData *_by = nullptr;
|
||||
TimeId _since = 0;
|
||||
TimeId _until = 0;
|
||||
Fn<void(ChatRestrictionsInfo, ChatRestrictionsInfo)> _saveCallback;
|
||||
|
||||
|
||||
@@ -387,6 +387,24 @@ QString ParticipantsAdditionalData::adminRank(
|
||||
return (i != end(_adminRanks)) ? i->second : QString();
|
||||
}
|
||||
|
||||
TimeId ParticipantsAdditionalData::adminPromotedSince(
|
||||
not_null<UserData*> user) const {
|
||||
const auto i = _adminPromotedSince.find(user);
|
||||
return (i != end(_adminPromotedSince)) ? i->second : TimeId(0);
|
||||
}
|
||||
|
||||
TimeId ParticipantsAdditionalData::restrictedSince(
|
||||
not_null<PeerData*> peer) const {
|
||||
const auto i = _restrictedSince.find(peer);
|
||||
return (i != end(_restrictedSince)) ? i->second : TimeId(0);
|
||||
}
|
||||
|
||||
TimeId ParticipantsAdditionalData::memberSince(
|
||||
not_null<UserData*> user) const {
|
||||
const auto i = _memberSince.find(user);
|
||||
return (i != end(_memberSince)) ? i->second : TimeId(0);
|
||||
}
|
||||
|
||||
auto ParticipantsAdditionalData::restrictedRights(
|
||||
not_null<PeerData*> participant) const
|
||||
-> std::optional<ChatRestrictionsInfo> {
|
||||
@@ -689,6 +707,11 @@ UserData *ParticipantsAdditionalData::applyAdmin(
|
||||
} else {
|
||||
_adminRanks.remove(user);
|
||||
}
|
||||
if (data.promotedSince()) {
|
||||
_adminPromotedSince[user] = data.promotedSince();
|
||||
} else {
|
||||
_adminPromotedSince.remove(user);
|
||||
}
|
||||
if (const auto by = _peer->owner().userLoaded(data.by())) {
|
||||
const auto i = _adminPromotedBy.find(user);
|
||||
if (i == _adminPromotedBy.end()) {
|
||||
@@ -741,6 +764,11 @@ PeerData *ParticipantsAdditionalData::applyBanned(
|
||||
} else {
|
||||
_kicked.erase(participant);
|
||||
}
|
||||
if (data.restrictedSince()) {
|
||||
_restrictedSince[participant] = data.restrictedSince();
|
||||
} else {
|
||||
_restrictedSince.remove(participant);
|
||||
}
|
||||
_restrictedRights[participant] = data.restrictions();
|
||||
if (const auto by = _peer->owner().userLoaded(data.by())) {
|
||||
const auto i = _restrictedBy.find(participant);
|
||||
@@ -1720,7 +1748,9 @@ void ParticipantsBoxController::showAdmin(not_null<UserData*> user) {
|
||||
_peer,
|
||||
user,
|
||||
currentRights,
|
||||
_additional.adminRank(user));
|
||||
_additional.adminRank(user),
|
||||
_additional.adminPromotedSince(user),
|
||||
_additional.adminPromotedBy(user));
|
||||
if (_additional.canAddOrEditAdmin(user)) {
|
||||
const auto done = crl::guard(this, [=](
|
||||
ChatAdminRightsInfo newRights,
|
||||
@@ -1776,7 +1806,9 @@ void ParticipantsBoxController::showRestricted(not_null<UserData*> user) {
|
||||
_peer,
|
||||
user,
|
||||
hasAdminRights,
|
||||
currentRights);
|
||||
currentRights,
|
||||
_additional.restrictedBy(user),
|
||||
_additional.restrictedSince(user));
|
||||
if (_additional.canRestrictParticipant(user)) {
|
||||
const auto done = crl::guard(this, [=](
|
||||
ChatRestrictionsInfo newRights) {
|
||||
|
||||
@@ -106,14 +106,19 @@ public:
|
||||
not_null<PeerData*> participant) const;
|
||||
[[nodiscard]] std::optional<ChatAdminRightsInfo> adminRights(
|
||||
not_null<UserData*> user) const;
|
||||
QString adminRank(not_null<UserData*> user) const;
|
||||
[[nodiscard]] QString adminRank(not_null<UserData*> user) const;
|
||||
[[nodiscard]] std::optional<ChatRestrictionsInfo> restrictedRights(
|
||||
not_null<PeerData*> participant) const;
|
||||
[[nodiscard]] bool isCreator(not_null<UserData*> user) const;
|
||||
[[nodiscard]] bool isExternal(not_null<PeerData*> participant) const;
|
||||
[[nodiscard]] bool isKicked(not_null<PeerData*> participant) const;
|
||||
[[nodiscard]] UserData *adminPromotedBy(not_null<UserData*> user) const;
|
||||
[[nodiscard]] UserData *restrictedBy(not_null<PeerData*> participant) const;
|
||||
[[nodiscard]] UserData *restrictedBy(
|
||||
not_null<PeerData*> participant) const;
|
||||
|
||||
[[nodiscard]] TimeId adminPromotedSince(not_null<UserData*>) const;
|
||||
[[nodiscard]] TimeId restrictedSince(not_null<PeerData*>) const;
|
||||
[[nodiscard]] TimeId memberSince(not_null<UserData*>) const;
|
||||
|
||||
void migrate(not_null<ChatData*> chat, not_null<ChannelData*> channel);
|
||||
|
||||
@@ -144,6 +149,9 @@ private:
|
||||
// Data for channels.
|
||||
base::flat_map<not_null<UserData*>, ChatAdminRightsInfo> _adminRights;
|
||||
base::flat_map<not_null<UserData*>, QString> _adminRanks;
|
||||
base::flat_map<not_null<UserData*>, TimeId> _adminPromotedSince;
|
||||
base::flat_map<not_null<PeerData*>, TimeId> _restrictedSince;
|
||||
base::flat_map<not_null<UserData*>, TimeId> _memberSince;
|
||||
base::flat_set<not_null<UserData*>> _adminCanEdit;
|
||||
base::flat_map<not_null<UserData*>, not_null<UserData*>> _adminPromotedBy;
|
||||
std::map<not_null<PeerData*>, ChatRestrictionsInfo> _restrictedRights;
|
||||
|
||||
@@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/boxes/edit_invite_link.h"
|
||||
#include "ui/boxes/edit_invite_link_session.h"
|
||||
#include "ui/boxes/peer_qr_box.h"
|
||||
#include "ui/controls/invite_link_buttons.h"
|
||||
#include "ui/controls/invite_link_label.h"
|
||||
#include "ui/controls/userpic_button.h"
|
||||
@@ -64,8 +65,8 @@ namespace {
|
||||
|
||||
constexpr auto kFirstPage = 20;
|
||||
constexpr auto kPerPage = 100;
|
||||
constexpr auto kShareQrSize = 768;
|
||||
constexpr auto kShareQrPadding = 16;
|
||||
// constexpr auto kShareQrSize = 768;
|
||||
// constexpr auto kShareQrPadding = 16;
|
||||
|
||||
using LinkData = Api::InviteLink;
|
||||
|
||||
@@ -282,6 +283,8 @@ private:
|
||||
return updated.link.isEmpty() || (!revoked && updated.revoked);
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
QImage QrExact(const Qr::Data &data, int pixel, QColor color) {
|
||||
const auto image = [](int size) {
|
||||
auto result = QImage(
|
||||
@@ -383,6 +386,8 @@ void QrBox(
|
||||
box->addLeftButton(tr::lng_group_invite_context_copy(), copyCallback);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
Controller::Controller(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<UserData*> admin,
|
||||
@@ -421,6 +426,7 @@ void Controller::addHeaderBlock(not_null<Ui::VerticalLayout*> container) {
|
||||
});
|
||||
const auto getLinkQr = crl::guard(weak, [=] {
|
||||
delegate()->peerListUiShow()->showBox(InviteLinkQrBox(
|
||||
_peer,
|
||||
link,
|
||||
tr::lng_group_invite_qr_title(),
|
||||
tr::lng_group_invite_qr_about()));
|
||||
@@ -1253,6 +1259,7 @@ void AddPermanentLinkBlock(
|
||||
const auto getLinkQr = crl::guard(weak, [=] {
|
||||
if (const auto current = value->current(); !current.link.isEmpty()) {
|
||||
show->showBox(InviteLinkQrBox(
|
||||
peer,
|
||||
current.link,
|
||||
tr::lng_group_invite_qr_title(),
|
||||
tr::lng_group_invite_qr_about()));
|
||||
@@ -1510,16 +1517,14 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
|
||||
}
|
||||
|
||||
object_ptr<Ui::BoxContent> InviteLinkQrBox(
|
||||
PeerData *peer,
|
||||
const QString &link,
|
||||
rpl::producer<QString> title,
|
||||
rpl::producer<QString> about) {
|
||||
return Box(QrBox, link, std::move(title), std::move(about), [=](
|
||||
const QImage &image,
|
||||
std::shared_ptr<Ui::Show> show) {
|
||||
auto mime = std::make_unique<QMimeData>();
|
||||
mime->setImageData(image);
|
||||
QGuiApplication::clipboard()->setMimeData(mime.release());
|
||||
show->showToast(tr::lng_group_invite_qr_copied(tr::now));
|
||||
return Box([=, t = std::move(title), a = std::move(about)](
|
||||
not_null<Ui::GenericBox*> box) {
|
||||
Ui::FillPeerQrBox(box, peer, link, std::move(a));
|
||||
box->setTitle(std::move(t));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ void CopyInviteLink(std::shared_ptr<Ui::Show> show, const QString &link);
|
||||
const QString &link,
|
||||
const QString &copied = {});
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> InviteLinkQrBox(
|
||||
PeerData *peer,
|
||||
const QString &link,
|
||||
rpl::producer<QString> title,
|
||||
rpl::producer<QString> about);
|
||||
|
||||
@@ -587,6 +587,7 @@ base::unique_qptr<Ui::PopupMenu> LinksController::createRowContextMenu(
|
||||
}, &st::menuIconShare);
|
||||
result->addAction(tr::lng_group_invite_context_qr(tr::now), [=] {
|
||||
delegate()->peerListUiShow()->showBox(InviteLinkQrBox(
|
||||
nullptr,
|
||||
link,
|
||||
tr::lng_group_invite_qr_title(),
|
||||
tr::lng_group_invite_qr_about()));
|
||||
@@ -734,7 +735,7 @@ void LinksController::rowPaintIcon(
|
||||
} else {
|
||||
(color == Color::Revoked
|
||||
? st::inviteLinkRevokedIcon
|
||||
: st::inviteLinkIcon).paintInCenter(p, { 0, 0, inner, inner });
|
||||
: st::inviteLinkIcon).paintInCenter(p, Rect(Size(inner)));
|
||||
}
|
||||
}
|
||||
p.drawImage(x + skip, y + skip, icon);
|
||||
|
||||
@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
@@ -583,14 +584,6 @@ template <typename Flags>
|
||||
ApplyDependencies(state->checkViews, dependencies, view);
|
||||
};
|
||||
|
||||
if (descriptor.header) {
|
||||
container->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
container,
|
||||
std::move(descriptor.header),
|
||||
st::rightsHeaderLabel),
|
||||
st::rightsHeaderMargin);
|
||||
}
|
||||
const auto addCheckbox = [&](
|
||||
not_null<Ui::VerticalLayout*> verticalLayout,
|
||||
bool isInner,
|
||||
@@ -1136,19 +1129,24 @@ void ShowEditPeerPermissionsBox(
|
||||
disabledByAdminRights,
|
||||
tr::lng_rights_permission_cant_edit(tr::now));
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
if (channel->isPublic()
|
||||
|| (channel->isMegagroup() && channel->linkedChat())) {
|
||||
if (channel->isPublic()) {
|
||||
result.emplace(
|
||||
Flag::ChangeInfo | Flag::PinMessages,
|
||||
tr::lng_rights_permission_unavailable(tr::now));
|
||||
} else if (channel->isMegagroup() && channel->linkedChat()) {
|
||||
result.emplace(
|
||||
Flag::ChangeInfo | Flag::PinMessages,
|
||||
tr::lng_rights_permission_in_discuss(tr::now));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}();
|
||||
|
||||
Ui::AddSubsectionTitle(
|
||||
inner,
|
||||
tr::lng_rights_default_restrictions_header());
|
||||
auto [checkboxes, getRestrictions, changes] = CreateEditRestrictions(
|
||||
inner,
|
||||
tr::lng_rights_default_restrictions_header(),
|
||||
restrictions,
|
||||
disabledMessages,
|
||||
{ .isForum = peer->isForum() });
|
||||
@@ -1312,7 +1310,6 @@ std::vector<AdminRightLabel> AdminRightLabels(
|
||||
|
||||
EditFlagsControl<ChatRestrictions> CreateEditRestrictions(
|
||||
QWidget *parent,
|
||||
rpl::producer<QString> header,
|
||||
ChatRestrictions restrictions,
|
||||
base::flat_map<ChatRestrictions, QString> disabledMessages,
|
||||
Data::RestrictionsSetOptions options) {
|
||||
@@ -1321,7 +1318,6 @@ EditFlagsControl<ChatRestrictions> CreateEditRestrictions(
|
||||
widget.data(),
|
||||
NegateRestrictions(restrictions),
|
||||
{
|
||||
.header = std::move(header),
|
||||
.labels = NestedRestrictionLabelsList(options),
|
||||
.disabledMessages = std::move(disabledMessages),
|
||||
});
|
||||
@@ -1338,7 +1334,6 @@ EditFlagsControl<ChatRestrictions> CreateEditRestrictions(
|
||||
|
||||
EditFlagsControl<ChatAdminRights> CreateEditAdminRights(
|
||||
QWidget *parent,
|
||||
rpl::producer<QString> header,
|
||||
ChatAdminRights rights,
|
||||
base::flat_map<ChatAdminRights, QString> disabledMessages,
|
||||
Data::AdminRightsSetOptions options) {
|
||||
@@ -1347,7 +1342,6 @@ EditFlagsControl<ChatAdminRights> CreateEditAdminRights(
|
||||
widget.data(),
|
||||
rights,
|
||||
{
|
||||
.header = std::move(header),
|
||||
.labels = NestedAdminRightLabels(options),
|
||||
.disabledMessages = std::move(disabledMessages),
|
||||
});
|
||||
|
||||
@@ -73,7 +73,6 @@ struct NestedEditFlagsLabels {
|
||||
|
||||
template <typename Flags>
|
||||
struct EditFlagsDescriptor {
|
||||
rpl::producer<QString> header;
|
||||
std::vector<NestedEditFlagsLabels<Flags>> labels;
|
||||
base::flat_map<Flags, QString> disabledMessages;
|
||||
const style::SettingsButton *st = nullptr;
|
||||
@@ -90,7 +89,6 @@ using AdminRightLabel = EditFlagsLabel<ChatAdminRights>;
|
||||
|
||||
[[nodiscard]] auto CreateEditRestrictions(
|
||||
QWidget *parent,
|
||||
rpl::producer<QString> header,
|
||||
ChatRestrictions restrictions,
|
||||
base::flat_map<ChatRestrictions, QString> disabledMessages,
|
||||
Data::RestrictionsSetOptions options)
|
||||
@@ -98,7 +96,6 @@ using AdminRightLabel = EditFlagsLabel<ChatAdminRights>;
|
||||
|
||||
[[nodiscard]] auto CreateEditAdminRights(
|
||||
QWidget *parent,
|
||||
rpl::producer<QString> header,
|
||||
ChatAdminRights rights,
|
||||
base::flat_map<ChatAdminRights, QString> disabledMessages,
|
||||
Data::AdminRightsSetOptions options)
|
||||
|
||||
@@ -241,8 +241,8 @@ RequestsBoxController::RowHelper::RowHelper(bool isGroup)
|
||||
? tr::lng_group_requests_add(tr::now)
|
||||
: tr::lng_group_requests_add_channel(tr::now))
|
||||
, _rejectText(tr::lng_group_requests_dismiss(tr::now))
|
||||
, _acceptTextWidth(st::requestsAcceptButton.font->width(_acceptText))
|
||||
, _rejectTextWidth(st::requestsRejectButton.font->width(_rejectText)) {
|
||||
, _acceptTextWidth(st::requestsAcceptButton.style.font->width(_acceptText))
|
||||
, _rejectTextWidth(st::requestsRejectButton.style.font->width(_rejectText)) {
|
||||
}
|
||||
|
||||
RequestsBoxController::RequestsBoxController(
|
||||
@@ -491,7 +491,7 @@ void RequestsBoxController::RowHelper::paintButton(
|
||||
const auto textLeft = geometry.x()
|
||||
+ ((geometry.width() - textWidth) / 2);
|
||||
const auto textTop = geometry.y() + st.textTop;
|
||||
p.setFont(st.font);
|
||||
p.setFont(st.style.font);
|
||||
p.setPen(over ? st.textFgOver : st.textFg);
|
||||
p.drawTextLeft(textLeft, textTop, outerWidth, text);
|
||||
}
|
||||
|
||||
@@ -721,6 +721,7 @@ void PeerShortInfoBox::prepare() {
|
||||
_roundedTop.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
refreshRoundedTopImage(getDelegate()->style().bg->c);
|
||||
|
||||
setCustomCornersFilling(RectPart::FullTop);
|
||||
setDimensionsToContent(st::shortInfoWidth, _rows);
|
||||
}
|
||||
|
||||
@@ -795,10 +796,6 @@ void PeerShortInfoBox::prepareRows() {
|
||||
tr::lng_mediaview_copy(tr::now));
|
||||
}
|
||||
|
||||
RectParts PeerShortInfoBox::customCornersFilling() {
|
||||
return RectPart::FullTop;
|
||||
}
|
||||
|
||||
void PeerShortInfoBox::resizeEvent(QResizeEvent *e) {
|
||||
BoxContent::resizeEvent(e);
|
||||
|
||||
|
||||
@@ -162,7 +162,6 @@ public:
|
||||
private:
|
||||
void prepare() override;
|
||||
void prepareRows();
|
||||
RectParts customCornersFilling() override;
|
||||
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
|
||||
@@ -79,7 +79,8 @@ void ProcessUserpic(
|
||||
if (!state->userpicView.cloud) {
|
||||
GenerateImage(
|
||||
state,
|
||||
peer->generateUserpicImage(
|
||||
PeerData::GenerateUserpicImage(
|
||||
peer,
|
||||
state->userpicView,
|
||||
st::shortInfoWidth * style::DevicePixelRatio(),
|
||||
0),
|
||||
|
||||
@@ -23,7 +23,7 @@ using Type = SelfDestructionBox::Type;
|
||||
|
||||
[[nodiscard]] std::vector<int> Values(Type type) {
|
||||
switch (type) {
|
||||
case Type::Account: return { 30, 90, 180, 365 };
|
||||
case Type::Account: return { 30, 90, 180, 365, 548, 720 };
|
||||
case Type::Sessions: return { 7, 30, 90, 180, 365 };
|
||||
}
|
||||
Unexpected("SelfDestructionBox::Type in Values.");
|
||||
@@ -113,8 +113,8 @@ void SelfDestructionBox::showContent() {
|
||||
QString SelfDestructionBox::DaysLabel(int days) {
|
||||
return !days
|
||||
? QString()
|
||||
: (days > 364)
|
||||
? tr::lng_years(tr::now, lt_count, days / 365)
|
||||
//: (days > 364)
|
||||
//? tr::lng_years(tr::now, lt_count, days / 365)
|
||||
: (days > 25)
|
||||
? tr::lng_months(tr::now, lt_count, std::max(days / 30, 1))
|
||||
: tr::lng_weeks(tr::now, lt_count, std::max(days / 7, 1));
|
||||
|
||||
@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "chat_helpers/message_field.h"
|
||||
#include "menu/menu_send.h"
|
||||
#include "chat_helpers/emoji_suggestions_widget.h"
|
||||
#include "chat_helpers/field_autocomplete.h"
|
||||
#include "chat_helpers/tabbed_panel.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
#include "editor/photo_editor_layer_widget.h"
|
||||
@@ -32,7 +33,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/premium_limits_box.h"
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "boxes/send_credits_box.h"
|
||||
#include "platform/platform_file_utilities.h"
|
||||
#include "ui/effects/scroll_content_shadow.h"
|
||||
#include "ui/widgets/fields/number_input.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
@@ -72,7 +72,7 @@ constexpr auto kMaxMessageLength = 4096;
|
||||
using Ui::SendFilesWay;
|
||||
|
||||
[[nodiscard]] inline bool CanAddUrls(const QList<QUrl> &urls) {
|
||||
return !urls.isEmpty() && ranges::all_of(urls, Core::UrlIsLocal);
|
||||
return !urls.isEmpty() && ranges::all_of(urls, &QUrl::isLocalFile);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool CanAddFiles(not_null<const QMimeData*> data) {
|
||||
@@ -1274,6 +1274,7 @@ void SendFilesBox::setupCaption() {
|
||||
[=] { return show->paused(Window::GifPauseReason::Layer); },
|
||||
allow,
|
||||
&_st.files.caption);
|
||||
setupCaptionAutocomplete();
|
||||
Ui::Emoji::SuggestionsController::Init(
|
||||
getDelegate()->outerContainer(),
|
||||
_caption,
|
||||
@@ -1333,6 +1334,59 @@ void SendFilesBox::setupCaption() {
|
||||
}, _caption->lifetime());
|
||||
}
|
||||
|
||||
void SendFilesBox::setupCaptionAutocomplete() {
|
||||
if (!_captionToPeer || !_caption) {
|
||||
return;
|
||||
}
|
||||
const auto parent = getDelegate()->outerContainer();
|
||||
ChatHelpers::InitFieldAutocomplete(_autocomplete, {
|
||||
.parent = parent,
|
||||
.show = _show,
|
||||
.field = _caption.data(),
|
||||
.peer = _captionToPeer,
|
||||
.features = [=] {
|
||||
auto result = ChatHelpers::ComposeFeatures();
|
||||
result.autocompleteCommands = false;
|
||||
result.suggestStickersByEmoji = false;
|
||||
return result;
|
||||
},
|
||||
.sendMenuDetails = _sendMenuDetails,
|
||||
});
|
||||
const auto raw = _autocomplete.get();
|
||||
const auto scheduled = std::make_shared<bool>();
|
||||
const auto recountPostponed = [=] {
|
||||
if (*scheduled) {
|
||||
return;
|
||||
}
|
||||
*scheduled = true;
|
||||
Ui::PostponeCall(raw, [=] {
|
||||
*scheduled = false;
|
||||
|
||||
auto field = Ui::MapFrom(parent, this, _caption->geometry());
|
||||
_autocomplete->setBoundings(QRect(
|
||||
field.x() - _caption->x(),
|
||||
st::defaultBox.margin.top(),
|
||||
width(),
|
||||
(field.y()
|
||||
+ _st.files.caption.textMargins.top()
|
||||
+ _st.files.caption.placeholderShift
|
||||
+ _st.files.caption.placeholderFont->height
|
||||
- st::defaultBox.margin.top())));
|
||||
});
|
||||
};
|
||||
for (auto w = (QWidget*)_caption.data(); w; w = w->parentWidget()) {
|
||||
base::install_event_filter(raw, w, [=](not_null<QEvent*> e) {
|
||||
if (e->type() == QEvent::Move || e->type() == QEvent::Resize) {
|
||||
recountPostponed();
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
if (w == parent) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SendFilesBox::checkCharsLimitation() {
|
||||
const auto limits = Data::PremiumLimits(&_show->session());
|
||||
const auto caption = (_caption && !_caption->isHidden())
|
||||
@@ -1648,6 +1702,14 @@ void SendFilesBox::updateControlsGeometry() {
|
||||
_scroll->move(0, _titleHeight.current());
|
||||
}
|
||||
|
||||
void SendFilesBox::showFinished() {
|
||||
if (const auto raw = _autocomplete.get()) {
|
||||
InvokeQueued(raw, [=] {
|
||||
raw->raise();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void SendFilesBox::setInnerFocus() {
|
||||
if (_caption && !_caption->isHidden()) {
|
||||
_caption->setFocusFast();
|
||||
|
||||
@@ -28,6 +28,7 @@ enum class SendType;
|
||||
namespace ChatHelpers {
|
||||
class TabbedPanel;
|
||||
class Show;
|
||||
class FieldAutocomplete;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace Ui {
|
||||
@@ -126,6 +127,8 @@ public:
|
||||
_cancelledCallback = std::move(callback);
|
||||
}
|
||||
|
||||
void showFinished() override;
|
||||
|
||||
~SendFilesBox();
|
||||
|
||||
protected:
|
||||
@@ -206,6 +209,7 @@ private:
|
||||
void refreshControls(bool initial = false);
|
||||
void setupSendWayControls();
|
||||
void setupCaption();
|
||||
void setupCaptionAutocomplete();
|
||||
|
||||
void setupEmojiPanel();
|
||||
void updateSendWayControls();
|
||||
@@ -257,6 +261,7 @@ private:
|
||||
SendFilesLimits _limits = {};
|
||||
Fn<MenuDetails()> _sendMenuDetails;
|
||||
Fn<void(MenuAction, MenuDetails)> _sendMenuCallback;
|
||||
|
||||
PeerData *_captionToPeer = nullptr;
|
||||
SendFilesCheck _check;
|
||||
SendFilesConfirmed _confirmedCallback;
|
||||
@@ -268,6 +273,7 @@ private:
|
||||
bool _invertCaption = false;
|
||||
|
||||
object_ptr<Ui::InputField> _caption = { nullptr };
|
||||
std::unique_ptr<ChatHelpers::FieldAutocomplete> _autocomplete;
|
||||
TextWithTags _prefilledCaptionText;
|
||||
object_ptr<Ui::EmojiButton> _emojiToggle = { nullptr };
|
||||
base::unique_qptr<ChatHelpers::TabbedPanel> _emojiPanel;
|
||||
|
||||
@@ -7,7 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/send_gif_with_caption_box.h"
|
||||
|
||||
#include "base/event_filter.h"
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "chat_helpers/field_autocomplete.h"
|
||||
#include "chat_helpers/message_field.h"
|
||||
#include "chat_helpers/tabbed_panel.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
@@ -30,9 +32,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/controls/emoji_button.h"
|
||||
#include "ui/controls/emoji_button_factory.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_boxes.h"
|
||||
@@ -129,7 +132,9 @@ namespace {
|
||||
not_null<Window::SessionController*> controller) {
|
||||
using Limit = HistoryView::Controls::CharactersLimitLabel;
|
||||
|
||||
const auto wrap = box->verticalLayout()->add(
|
||||
const auto bottomContainer = box->setPinnedToBottomContent(
|
||||
object_ptr<Ui::VerticalLayout>(box));
|
||||
const auto wrap = bottomContainer->add(
|
||||
object_ptr<Ui::RpWidget>(box),
|
||||
st::boxRowPadding);
|
||||
const auto input = Ui::CreateChild<Ui::InputField>(
|
||||
@@ -224,6 +229,7 @@ namespace {
|
||||
void SendGifWithCaptionBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<DocumentData*> document,
|
||||
not_null<PeerData*> peer,
|
||||
const SendMenu::Details &details,
|
||||
Fn<void(Api::SendOptions, TextWithTags)> done) {
|
||||
const auto window = Core::App().findWindow(box);
|
||||
@@ -233,6 +239,7 @@ void SendGifWithCaptionBox(
|
||||
}
|
||||
box->setTitle(tr::lng_send_gif_with_caption());
|
||||
box->setWidth(st::boxWidth);
|
||||
box->getDelegate()->setStyle(st::sendGifBox);
|
||||
|
||||
const auto container = box->verticalLayout();
|
||||
[[maybe_unused]] const auto gifWidget = AddGifWidget(
|
||||
@@ -252,6 +259,61 @@ void SendGifWithCaptionBox(
|
||||
return true;
|
||||
});
|
||||
|
||||
const auto sendMenuDetails = [=] { return details; };
|
||||
struct Autocomplete {
|
||||
std::unique_ptr<ChatHelpers::FieldAutocomplete> dropdown;
|
||||
bool geometryUpdateScheduled = false;
|
||||
};
|
||||
const auto autocomplete = box->lifetime().make_state<Autocomplete>();
|
||||
const auto outer = box->getDelegate()->outerContainer();
|
||||
ChatHelpers::InitFieldAutocomplete(autocomplete->dropdown, {
|
||||
.parent = outer,
|
||||
.show = controller->uiShow(),
|
||||
.field = input,
|
||||
.peer = peer,
|
||||
.features = [=] {
|
||||
auto result = ChatHelpers::ComposeFeatures();
|
||||
result.autocompleteCommands = false;
|
||||
result.suggestStickersByEmoji = false;
|
||||
return result;
|
||||
},
|
||||
.sendMenuDetails = sendMenuDetails,
|
||||
});
|
||||
const auto raw = autocomplete->dropdown.get();
|
||||
const auto recountPostponed = [=] {
|
||||
if (autocomplete->geometryUpdateScheduled) {
|
||||
return;
|
||||
}
|
||||
autocomplete->geometryUpdateScheduled = true;
|
||||
Ui::PostponeCall(raw, [=] {
|
||||
autocomplete->geometryUpdateScheduled = false;
|
||||
|
||||
const auto from = input->parentWidget();
|
||||
auto field = Ui::MapFrom(outer, from, input->geometry());
|
||||
const auto &st = st::defaultComposeFiles;
|
||||
autocomplete->dropdown->setBoundings(QRect(
|
||||
field.x() - input->x(),
|
||||
st::defaultBox.margin.top(),
|
||||
input->width(),
|
||||
(field.y()
|
||||
+ st.caption.textMargins.top()
|
||||
+ st.caption.placeholderShift
|
||||
+ st.caption.placeholderFont->height
|
||||
- st::defaultBox.margin.top())));
|
||||
});
|
||||
};
|
||||
for (auto w = (QWidget*)input; w; w = w->parentWidget()) {
|
||||
base::install_event_filter(raw, w, [=](not_null<QEvent*> e) {
|
||||
if (e->type() == QEvent::Move || e->type() == QEvent::Resize) {
|
||||
recountPostponed();
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
if (w == outer) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const auto send = [=](Api::SendOptions options) {
|
||||
done(std::move(options), input->getTextWithTags());
|
||||
};
|
||||
@@ -261,8 +323,15 @@ void SendGifWithCaptionBox(
|
||||
SendMenu::SetupMenuAndShortcuts(
|
||||
confirm,
|
||||
controller->uiShow(),
|
||||
[=] { return details; },
|
||||
sendMenuDetails,
|
||||
SendMenu::DefaultCallback(controller->uiShow(), send));
|
||||
box->setShowFinishedCallback([=] {
|
||||
if (const auto raw = autocomplete->dropdown.get()) {
|
||||
InvokeQueued(raw, [=] {
|
||||
raw->raise();
|
||||
});
|
||||
}
|
||||
});
|
||||
box->addButton(tr::lng_cancel(), [=] {
|
||||
box->closeBox();
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
class PeerData;
|
||||
class DocumentData;
|
||||
|
||||
namespace Api {
|
||||
@@ -24,6 +25,7 @@ class GenericBox;
|
||||
void SendGifWithCaptionBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<DocumentData*> document,
|
||||
not_null<PeerData*> peer,
|
||||
const SendMenu::Details &details,
|
||||
Fn<void(Api::SendOptions, TextWithTags)> done);
|
||||
|
||||
|
||||
@@ -1216,11 +1216,12 @@ StickersBox::Inner::Inner(
|
||||
})
|
||||
, _itemsTop(st::lineWidth)
|
||||
, _addText(tr::lng_stickers_featured_add(tr::now))
|
||||
, _addWidth(st::stickersTrendingAdd.font->width(_addText))
|
||||
, _addWidth(st::stickersTrendingAdd.style.font->width(_addText))
|
||||
, _undoText(tr::lng_stickers_return(tr::now))
|
||||
, _undoWidth(st::stickersUndoRemove.font->width(_undoText))
|
||||
, _undoWidth(st::stickersUndoRemove.style.font->width(_undoText))
|
||||
, _installedText(tr::lng_stickers_featured_installed(tr::now))
|
||||
, _installedWidth(st::stickersTrendingInstalled.font->width(_installedText)) {
|
||||
, _installedWidth(st::stickersTrendingInstalled.style.font->width(
|
||||
_installedText)) {
|
||||
setup();
|
||||
}
|
||||
|
||||
@@ -1666,7 +1667,7 @@ void StickersBox::Inner::paintFakeButton(Painter &p, not_null<Row*> row, int ind
|
||||
row->ripple.reset();
|
||||
}
|
||||
}
|
||||
p.setFont(st.font);
|
||||
p.setFont(st.style.font);
|
||||
p.setPen(st.textFg);
|
||||
p.drawTextLeft(rect.x() - (st.width / 2), rect.y() + st.textTop, width(), text, textWidth);
|
||||
} else {
|
||||
@@ -1700,7 +1701,7 @@ void StickersBox::Inner::paintFakeButton(Painter &p, not_null<Row*> row, int ind
|
||||
row->ripple.reset();
|
||||
}
|
||||
}
|
||||
p.setFont(st.font);
|
||||
p.setFont(st.style.font);
|
||||
p.setPen(selected ? st.textFgOver : st.textFg);
|
||||
p.drawTextLeft(rect.x() - (st.width / 2), rect.y() + st.textTop, width(), text, textWidth);
|
||||
}
|
||||
|
||||
@@ -460,7 +460,8 @@ void Viewport::RendererGL::validateUserpicFrame(
|
||||
return;
|
||||
}
|
||||
const auto size = tile->trackOrUserpicSize();
|
||||
tileData.userpicFrame = tile->row()->peer()->generateUserpicImage(
|
||||
tileData.userpicFrame = PeerData::GenerateUserpicImage(
|
||||
tile->row()->peer(),
|
||||
tile->row()->ensureUserpicView(),
|
||||
size.width(),
|
||||
0);
|
||||
|
||||
@@ -77,7 +77,8 @@ void Viewport::RendererSW::validateUserpicFrame(
|
||||
}
|
||||
const auto size = tile->trackOrUserpicSize();
|
||||
data.userpicFrame = Images::BlurLargeImage(
|
||||
tile->row()->peer()->generateUserpicImage(
|
||||
PeerData::GenerateUserpicImage(
|
||||
tile->row()->peer(),
|
||||
tile->row()->ensureUserpicView(),
|
||||
size.width(),
|
||||
0),
|
||||
|
||||
@@ -23,7 +23,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include <tgcalls/desktop_capturer/DesktopCaptureSourceManager.h>
|
||||
#include <tgcalls/desktop_capturer/DesktopCaptureSourceHelper.h>
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtGui/QWindow>
|
||||
|
||||
namespace Calls::Group::Ui::DesktopCapture {
|
||||
@@ -585,13 +584,7 @@ void ChooseSourceProcess::setupSourcesGeometry() {
|
||||
|
||||
void ChooseSourceProcess::setupGeometryWithParent(
|
||||
not_null<QWidget*> parent) {
|
||||
const auto parentScreen = [&] {
|
||||
if (const auto screen = QGuiApplication::screenAt(
|
||||
parent->geometry().center())) {
|
||||
return screen;
|
||||
}
|
||||
return parent->screen();
|
||||
}();
|
||||
const auto parentScreen = parent->screen();
|
||||
const auto myScreen = _window->screen();
|
||||
if (parentScreen && myScreen != parentScreen) {
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
|
||||
@@ -296,7 +296,9 @@ emojiPanButton: RoundButton(defaultActiveButton) {
|
||||
textTop: 2px;
|
||||
}
|
||||
emojiPanExpand: RoundButton(defaultActiveButton) {
|
||||
font: font(12px bold);
|
||||
style: TextStyle(semiboldTextStyle) {
|
||||
font: font(12px bold);
|
||||
}
|
||||
width: -8px;
|
||||
height: 19px;
|
||||
textTop: 1px;
|
||||
@@ -1499,5 +1501,11 @@ pickLocationChooseOnMap: RoundButton(defaultActiveButton) {
|
||||
height: 44px;
|
||||
textTop: 11px;
|
||||
width: -96px;
|
||||
font: font(15px semibold);
|
||||
style: TextStyle(semiboldTextStyle) {
|
||||
font: font(15px semibold);
|
||||
}
|
||||
}
|
||||
|
||||
sendGifBox: Box(defaultBox) {
|
||||
shadowIgnoreBottomSkip: true;
|
||||
}
|
||||
|
||||
@@ -10,20 +10,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace ChatHelpers {
|
||||
|
||||
struct ComposeFeatures {
|
||||
bool likes = false;
|
||||
bool sendAs = true;
|
||||
bool ttlInfo = true;
|
||||
bool botCommandSend = true;
|
||||
bool silentBroadcastToggle = true;
|
||||
bool attachBotsMenu = true;
|
||||
bool inlineBots = true;
|
||||
bool megagroupSet = true;
|
||||
bool stickersSettings = true;
|
||||
bool openStickerSets = true;
|
||||
bool autocompleteHashtags = true;
|
||||
bool autocompleteMentions = true;
|
||||
bool autocompleteCommands = true;
|
||||
bool commonTabbedPanel = true;
|
||||
bool likes : 1 = false;
|
||||
bool sendAs : 1 = true;
|
||||
bool ttlInfo : 1 = true;
|
||||
bool botCommandSend : 1 = true;
|
||||
bool silentBroadcastToggle : 1 = true;
|
||||
bool attachBotsMenu : 1 = true;
|
||||
bool inlineBots : 1 = true;
|
||||
bool megagroupSet : 1 = true;
|
||||
bool stickersSettings : 1 = true;
|
||||
bool openStickerSets : 1 = true;
|
||||
bool autocompleteHashtags : 1 = true;
|
||||
bool autocompleteMentions : 1 = true;
|
||||
bool autocompleteCommands : 1 = true;
|
||||
bool suggestStickersByEmoji : 1 = true;
|
||||
bool commonTabbedPanel : 1 = true;
|
||||
};
|
||||
|
||||
} // namespace ChatHelpers
|
||||
|
||||
@@ -1450,17 +1450,17 @@ void EmojiListWidget::drawCollapsedBadge(
|
||||
int count) {
|
||||
const auto &st = st::emojiPanExpand;
|
||||
const auto text = u"+%1"_q.arg(count - _columnCount * kCollapsedRows + 1);
|
||||
const auto textWidth = st.font->width(text);
|
||||
const auto textWidth = st.style.font->width(text);
|
||||
const auto buttonw = std::max(textWidth - st.width, st.height);
|
||||
const auto buttonh = st.height;
|
||||
const auto buttonx = position.x() + (_singleSize.width() - buttonw) / 2;
|
||||
const auto buttony = position.y() + (_singleSize.height() - buttonh) / 2;
|
||||
_collapsedBg.paint(p, QRect(buttonx, buttony, buttonw, buttonh));
|
||||
p.setPen(this->st().bg);
|
||||
p.setFont(st.font);
|
||||
p.setFont(st.style.font);
|
||||
p.drawText(
|
||||
buttonx + (buttonw - textWidth) / 2,
|
||||
(buttony + st.textTop + st.font->ascent),
|
||||
(buttony + st.textTop + st.style.font->ascent),
|
||||
text);
|
||||
}
|
||||
|
||||
@@ -2546,12 +2546,12 @@ int EmojiListWidget::paintButtonGetWidth(
|
||||
: selected
|
||||
? st::emojiPanButton.textFgOver
|
||||
: st::emojiPanButton.textFg);
|
||||
p.setFont(st::emojiPanButton.font);
|
||||
p.setFont(st::emojiPanButton.style.font);
|
||||
p.drawText(
|
||||
rect.x() - (st::emojiPanButton.width / 2),
|
||||
(rect.y()
|
||||
+ st::emojiPanButton.textTop
|
||||
+ st::emojiPanButton.font->ascent),
|
||||
+ st::emojiPanButton.style.font->ascent),
|
||||
button.text);
|
||||
return emojiRight() - rect.x();
|
||||
}
|
||||
@@ -2678,7 +2678,7 @@ void EmojiListWidget::initButton(
|
||||
const QString &text,
|
||||
bool gradient) {
|
||||
button.text = text;
|
||||
button.textWidth = st::emojiPanButton.font->width(text);
|
||||
button.textWidth = st::emojiPanButton.style.font->width(text);
|
||||
const auto width = button.textWidth - st::emojiPanButton.width;
|
||||
const auto height = st::emojiPanButton.height;
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
|
||||
@@ -41,9 +41,9 @@ inline auto PreviewPath(int i) {
|
||||
|
||||
const auto kSets = {
|
||||
Set{ { 0, 0, 0, "Mac" }, PreviewPath(0) },
|
||||
Set{ { 1, 1804, 8'115'639, "Android" }, PreviewPath(1) },
|
||||
Set{ { 2, 1805, 5'481'197, "Twemoji" }, PreviewPath(2) },
|
||||
Set{ { 3, 1806, 7'047'594, "JoyPixels" }, PreviewPath(3) },
|
||||
Set{ { 1, 2290, 8'306'943, "Android" }, PreviewPath(1) },
|
||||
Set{ { 2, 2291, 5'694'303, "Twemoji" }, PreviewPath(2) },
|
||||
Set{ { 3, 2292, 7'261'223, "JoyPixels" }, PreviewPath(3) },
|
||||
};
|
||||
|
||||
using Loading = MTP::DedicatedLoader::Progress;
|
||||
|
||||
@@ -713,11 +713,13 @@ void SuggestionsWidget::leaveEventHook(QEvent *e) {
|
||||
}
|
||||
|
||||
SuggestionsController::SuggestionsController(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<QWidget*> outer,
|
||||
not_null<QTextEdit*> field,
|
||||
not_null<Main::Session*> session,
|
||||
const Options &options)
|
||||
: _st(options.st ? *options.st : st::defaultEmojiSuggestions)
|
||||
: QObject(parent)
|
||||
, _st(options.st ? *options.st : st::defaultEmojiSuggestions)
|
||||
, _field(field)
|
||||
, _session(session)
|
||||
, _showExactTimer([=] { showWithQuery(getEmojiQuery()); })
|
||||
|
||||
@@ -37,7 +37,7 @@ class SuggestionsWidget;
|
||||
|
||||
using SuggestionsQuery = std::variant<QString, EmojiPtr>;
|
||||
|
||||
class SuggestionsController {
|
||||
class SuggestionsController final : public QObject {
|
||||
public:
|
||||
struct Options {
|
||||
bool suggestExactFirstWord = true;
|
||||
@@ -47,6 +47,7 @@ public:
|
||||
};
|
||||
|
||||
SuggestionsController(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<QWidget*> outer,
|
||||
not_null<QTextEdit*> field,
|
||||
not_null<Main::Session*> session,
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/business/data_shortcut_messages.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_user.h"
|
||||
@@ -53,6 +54,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
namespace ChatHelpers {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] QString PrimaryUsername(not_null<UserData*> user) {
|
||||
@@ -60,6 +62,18 @@ namespace {
|
||||
return usernames.empty() ? user->username() : usernames.front();
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
inline int indexOfInFirstN(const T &v, const U &elem, int last) {
|
||||
for (auto b = v.cbegin(), i = b, e = b + std::max(int(v.size()), last)
|
||||
; i != e
|
||||
; ++i) {
|
||||
if (i->user == elem) {
|
||||
return (i - b);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class FieldAutocomplete::Inner final : public Ui::RpWidget {
|
||||
@@ -70,7 +84,7 @@ public:
|
||||
};
|
||||
|
||||
Inner(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
std::shared_ptr<Show> show,
|
||||
const style::EmojiPan &st,
|
||||
not_null<FieldAutocomplete*> parent,
|
||||
not_null<MentionRows*> mrows,
|
||||
@@ -127,7 +141,7 @@ private:
|
||||
Media::Clip::Notification notification,
|
||||
not_null<DocumentData*> document);
|
||||
|
||||
const std::shared_ptr<ChatHelpers::Show> _show;
|
||||
const std::shared_ptr<Show> _show;
|
||||
const not_null<Main::Session*> _session;
|
||||
const style::EmojiPan &_st;
|
||||
const not_null<FieldAutocomplete*> _parent;
|
||||
@@ -191,13 +205,7 @@ struct FieldAutocomplete::BotCommandRow {
|
||||
|
||||
FieldAutocomplete::FieldAutocomplete(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller)
|
||||
: FieldAutocomplete(parent, controller->uiShow()) {
|
||||
}
|
||||
|
||||
FieldAutocomplete::FieldAutocomplete(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
std::shared_ptr<Show> show,
|
||||
const style::EmojiPan *stOverride)
|
||||
: RpWidget(parent)
|
||||
, _show(std::move(show))
|
||||
@@ -235,10 +243,26 @@ FieldAutocomplete::FieldAutocomplete(
|
||||
}), lifetime());
|
||||
}
|
||||
|
||||
std::shared_ptr<ChatHelpers::Show> FieldAutocomplete::uiShow() const {
|
||||
std::shared_ptr<Show> FieldAutocomplete::uiShow() const {
|
||||
return _show;
|
||||
}
|
||||
|
||||
void FieldAutocomplete::requestRefresh() {
|
||||
_refreshRequests.fire({});
|
||||
}
|
||||
|
||||
rpl::producer<> FieldAutocomplete::refreshRequests() const {
|
||||
return _refreshRequests.events();
|
||||
}
|
||||
|
||||
void FieldAutocomplete::requestStickersUpdate() {
|
||||
_stickersUpdateRequests.fire({});
|
||||
}
|
||||
|
||||
rpl::producer<> FieldAutocomplete::stickersUpdateRequests() const {
|
||||
return _stickersUpdateRequests.events();
|
||||
}
|
||||
|
||||
auto FieldAutocomplete::mentionChosen() const
|
||||
-> rpl::producer<FieldAutocomplete::MentionChosen> {
|
||||
return _inner->mentionChosen();
|
||||
@@ -365,6 +389,10 @@ void FieldAutocomplete::showStickers(EmojiPtr emoji) {
|
||||
updateFiltered(resetScroll);
|
||||
}
|
||||
|
||||
EmojiPtr FieldAutocomplete::stickersEmoji() const {
|
||||
return _emoji;
|
||||
}
|
||||
|
||||
bool FieldAutocomplete::clearFilteredBotCommands() {
|
||||
if (_brows.empty()) {
|
||||
return false;
|
||||
@@ -373,18 +401,6 @@ bool FieldAutocomplete::clearFilteredBotCommands() {
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace {
|
||||
template <typename T, typename U>
|
||||
inline int indexOfInFirstN(const T &v, const U &elem, int last) {
|
||||
for (auto b = v.cbegin(), i = b, e = b + std::max(int(v.size()), last); i != e; ++i) {
|
||||
if (i->user == elem) {
|
||||
return (i - b);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
FieldAutocomplete::StickerRows FieldAutocomplete::getStickerSuggestions() {
|
||||
const auto data = &_session->data().stickers();
|
||||
const auto list = data->getListByEmoji({ _emoji }, _stickersSeed);
|
||||
@@ -871,7 +887,7 @@ bool FieldAutocomplete::eventFilter(QObject *obj, QEvent *e) {
|
||||
}
|
||||
|
||||
FieldAutocomplete::Inner::Inner(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
std::shared_ptr<Show> show,
|
||||
const style::EmojiPan &st,
|
||||
not_null<FieldAutocomplete*> parent,
|
||||
not_null<MentionRows*> mrows,
|
||||
@@ -963,8 +979,8 @@ void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) {
|
||||
|
||||
media->checkStickerSmall();
|
||||
const auto paused = _show->paused(
|
||||
ChatHelpers::PauseReason::TabbedPanel);
|
||||
const auto size = ChatHelpers::ComputeStickerSize(
|
||||
PauseReason::TabbedPanel);
|
||||
const auto size = ComputeStickerSize(
|
||||
document,
|
||||
stickerBoundingBox());
|
||||
const auto ppos = pos + QPoint(
|
||||
@@ -989,7 +1005,7 @@ void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) {
|
||||
} else if (const auto image = media->getStickerSmall()) {
|
||||
p.drawPixmapLeft(ppos, width(), image->pix(size));
|
||||
} else {
|
||||
ChatHelpers::PaintStickerThumbnailPath(
|
||||
PaintStickerThumbnailPath(
|
||||
p,
|
||||
media.get(),
|
||||
QRect(ppos, size),
|
||||
@@ -1250,7 +1266,7 @@ bool FieldAutocomplete::Inner::chooseAtIndex(
|
||||
const auto bounding = selectedRect(index);
|
||||
auto contentRect = QRect(
|
||||
QPoint(),
|
||||
ChatHelpers::ComputeStickerSize(
|
||||
ComputeStickerSize(
|
||||
document,
|
||||
stickerBoundingBox()));
|
||||
contentRect.moveCenter(bounding.center());
|
||||
@@ -1464,9 +1480,9 @@ auto FieldAutocomplete::Inner::getLottieRenderer()
|
||||
|
||||
void FieldAutocomplete::Inner::setupLottie(StickerSuggestion &suggestion) {
|
||||
const auto document = suggestion.document;
|
||||
suggestion.lottie = ChatHelpers::LottiePlayerFromDocument(
|
||||
suggestion.lottie = LottiePlayerFromDocument(
|
||||
suggestion.documentMedia.get(),
|
||||
ChatHelpers::StickerLottieSize::InlineResults,
|
||||
StickerLottieSize::InlineResults,
|
||||
stickerBoundingBox() * style::DevicePixelRatio(),
|
||||
Lottie::Quality::Default,
|
||||
getLottieRenderer());
|
||||
@@ -1534,7 +1550,7 @@ void FieldAutocomplete::Inner::clipCallback(
|
||||
} else if (i->webm->state() == State::Error) {
|
||||
i->webm.setBad();
|
||||
} else if (i->webm->ready() && !i->webm->started()) {
|
||||
const auto size = ChatHelpers::ComputeStickerSize(
|
||||
const auto size = ComputeStickerSize(
|
||||
i->document,
|
||||
stickerBoundingBox());
|
||||
i->webm->start({ .frame = size, .keepAlpha = true });
|
||||
@@ -1632,3 +1648,171 @@ auto FieldAutocomplete::Inner::scrollToRequested() const
|
||||
-> rpl::producer<ScrollTo> {
|
||||
return _scrollToRequested.events();
|
||||
}
|
||||
|
||||
void InitFieldAutocomplete(
|
||||
std::unique_ptr<FieldAutocomplete> &autocomplete,
|
||||
FieldAutocompleteDescriptor &&descriptor) {
|
||||
Expects(!autocomplete);
|
||||
|
||||
autocomplete = std::make_unique<FieldAutocomplete>(
|
||||
descriptor.parent,
|
||||
descriptor.show,
|
||||
descriptor.stOverride);
|
||||
const auto raw = autocomplete.get();
|
||||
const auto field = descriptor.field;
|
||||
|
||||
field->rawTextEdit()->installEventFilter(raw);
|
||||
field->customTab(true);
|
||||
|
||||
raw->mentionChosen(
|
||||
) | rpl::start_with_next([=](FieldAutocomplete::MentionChosen data) {
|
||||
const auto user = data.user;
|
||||
if (data.mention.isEmpty()) {
|
||||
field->insertTag(
|
||||
user->firstName.isEmpty() ? user->name() : user->firstName,
|
||||
PrepareMentionTag(user));
|
||||
} else {
|
||||
field->insertTag('@' + data.mention);
|
||||
}
|
||||
}, raw->lifetime());
|
||||
|
||||
const auto sendCommand = descriptor.sendBotCommand;
|
||||
const auto setText = descriptor.setText;
|
||||
|
||||
raw->hashtagChosen(
|
||||
) | rpl::start_with_next([=](FieldAutocomplete::HashtagChosen data) {
|
||||
field->insertTag(data.hashtag);
|
||||
}, raw->lifetime());
|
||||
|
||||
const auto peer = descriptor.peer;
|
||||
const auto features = descriptor.features;
|
||||
const auto processShortcut = descriptor.processShortcut;
|
||||
const auto shortcutMessages = (processShortcut != nullptr)
|
||||
? &peer->owner().shortcutMessages()
|
||||
: nullptr;
|
||||
raw->botCommandChosen(
|
||||
) | rpl::start_with_next([=](FieldAutocomplete::BotCommandChosen data) {
|
||||
if (!features().autocompleteCommands) {
|
||||
return;
|
||||
}
|
||||
using Method = FieldAutocompleteChooseMethod;
|
||||
const auto byTab = (data.method == Method::ByTab);
|
||||
const auto shortcut = data.user->isSelf();
|
||||
|
||||
// Send bot command at once, if it was not inserted by pressing Tab.
|
||||
if (byTab && data.command.size() > 1) {
|
||||
field->insertTag(data.command);
|
||||
} else if (!shortcut) {
|
||||
sendCommand(data.command);
|
||||
setText(
|
||||
field->getTextWithTagsPart(field->textCursor().position()));
|
||||
} else if (processShortcut) {
|
||||
processShortcut(data.command.mid(1));
|
||||
}
|
||||
}, raw->lifetime());
|
||||
|
||||
raw->setModerateKeyActivateCallback(std::move(descriptor.moderateKeyActivateCallback));
|
||||
|
||||
if (const auto stickerChoosing = descriptor.stickerChoosing) {
|
||||
raw->choosingProcesses(
|
||||
) | rpl::start_with_next([=](FieldAutocomplete::Type type) {
|
||||
if (type == FieldAutocomplete::Type::Stickers) {
|
||||
stickerChoosing();
|
||||
}
|
||||
}, raw->lifetime());
|
||||
}
|
||||
if (const auto chosen = descriptor.stickerChosen) {
|
||||
raw->stickerChosen(
|
||||
) | rpl::start_with_next(chosen, raw->lifetime());
|
||||
}
|
||||
|
||||
field->tabbed(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (!raw->isHidden()) {
|
||||
raw->chooseSelected(FieldAutocomplete::ChooseMethod::ByTab);
|
||||
}
|
||||
}, raw->lifetime());
|
||||
|
||||
const auto check = [=] {
|
||||
auto parsed = ParseMentionHashtagBotCommandQuery(field, features());
|
||||
if (parsed.query.isEmpty()) {
|
||||
} else if (parsed.query[0] == '#'
|
||||
&& cRecentWriteHashtags().isEmpty()
|
||||
&& cRecentSearchHashtags().isEmpty()) {
|
||||
peer->session().local().readRecentHashtagsAndBots();
|
||||
} else if (parsed.query[0] == '@'
|
||||
&& cRecentInlineBots().isEmpty()) {
|
||||
peer->session().local().readRecentHashtagsAndBots();
|
||||
} else if (parsed.query[0] == '/'
|
||||
&& peer->isUser()
|
||||
&& !peer->asUser()->isBot()
|
||||
&& (!shortcutMessages
|
||||
|| shortcutMessages->shortcuts().list.empty())) {
|
||||
parsed = {};
|
||||
}
|
||||
raw->showFiltered(peer, parsed.query, parsed.fromStart);
|
||||
};
|
||||
|
||||
const auto updateStickersByEmoji = [=] {
|
||||
const auto errorForStickers = Data::RestrictionError(
|
||||
peer,
|
||||
ChatRestriction::SendStickers);
|
||||
if (features().suggestStickersByEmoji && !errorForStickers) {
|
||||
const auto &text = field->getTextWithTags().text;
|
||||
auto length = 0;
|
||||
if (const auto emoji = Ui::Emoji::Find(text, &length)) {
|
||||
if (text.size() <= length) {
|
||||
raw->showStickers(emoji);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
raw->showStickers(nullptr);
|
||||
};
|
||||
|
||||
raw->refreshRequests(
|
||||
) | rpl::start_with_next(check, raw->lifetime());
|
||||
|
||||
raw->stickersUpdateRequests(
|
||||
) | rpl::start_with_next(updateStickersByEmoji, raw->lifetime());
|
||||
|
||||
peer->owner().botCommandsChanges(
|
||||
) | rpl::filter([=](not_null<PeerData*> changed) {
|
||||
return (peer == changed);
|
||||
}) | rpl::start_with_next([=] {
|
||||
if (raw->clearFilteredBotCommands()) {
|
||||
check();
|
||||
}
|
||||
}, raw->lifetime());
|
||||
|
||||
peer->owner().stickers().updated(
|
||||
Data::StickersType::Stickers
|
||||
) | rpl::start_with_next(updateStickersByEmoji, raw->lifetime());
|
||||
|
||||
QObject::connect(
|
||||
field->rawTextEdit(),
|
||||
&QTextEdit::cursorPositionChanged,
|
||||
raw,
|
||||
check,
|
||||
Qt::QueuedConnection);
|
||||
|
||||
field->changes() | rpl::start_with_next(
|
||||
updateStickersByEmoji,
|
||||
raw->lifetime());
|
||||
|
||||
peer->session().changes().peerUpdates(
|
||||
Data::PeerUpdate::Flag::Rights
|
||||
) | rpl::filter([=](const Data::PeerUpdate &update) {
|
||||
return (update.peer == peer);
|
||||
}) | rpl::start_with_next(updateStickersByEmoji, raw->lifetime());
|
||||
|
||||
if (shortcutMessages) {
|
||||
shortcutMessages->shortcutsChanged(
|
||||
) | rpl::start_with_next(check, raw->lifetime());
|
||||
}
|
||||
|
||||
raw->setSendMenuDetails(std::move(descriptor.sendMenuDetails));
|
||||
raw->hideFast();
|
||||
}
|
||||
|
||||
} // namespace ChatHelpers
|
||||
|
||||
@@ -46,46 +46,49 @@ struct Details;
|
||||
} // namespace SendMenu
|
||||
|
||||
namespace ChatHelpers {
|
||||
|
||||
struct ComposeFeatures;
|
||||
struct FileChosen;
|
||||
class Show;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
enum class FieldAutocompleteChooseMethod {
|
||||
ByEnter,
|
||||
ByTab,
|
||||
ByClick,
|
||||
};
|
||||
|
||||
class FieldAutocomplete final : public Ui::RpWidget {
|
||||
public:
|
||||
FieldAutocomplete(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller);
|
||||
FieldAutocomplete(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
std::shared_ptr<Show> show,
|
||||
const style::EmojiPan *stOverride = nullptr);
|
||||
~FieldAutocomplete();
|
||||
|
||||
[[nodiscard]] std::shared_ptr<ChatHelpers::Show> uiShow() const;
|
||||
[[nodiscard]] std::shared_ptr<Show> uiShow() const;
|
||||
|
||||
bool clearFilteredBotCommands();
|
||||
void showFiltered(
|
||||
not_null<PeerData*> peer,
|
||||
QString query,
|
||||
bool addInlineBots);
|
||||
|
||||
void showStickers(EmojiPtr emoji);
|
||||
[[nodiscard]] EmojiPtr stickersEmoji() const;
|
||||
|
||||
void setBoundings(QRect boundings);
|
||||
|
||||
const QString &filter() const;
|
||||
ChatData *chat() const;
|
||||
ChannelData *channel() const;
|
||||
UserData *user() const;
|
||||
[[nodiscard]] const QString &filter() const;
|
||||
[[nodiscard]] ChatData *chat() const;
|
||||
[[nodiscard]] ChannelData *channel() const;
|
||||
[[nodiscard]] UserData *user() const;
|
||||
|
||||
int32 innerTop();
|
||||
int32 innerBottom();
|
||||
[[nodiscard]] int32 innerTop();
|
||||
[[nodiscard]] int32 innerBottom();
|
||||
|
||||
bool eventFilter(QObject *obj, QEvent *e) override;
|
||||
|
||||
enum class ChooseMethod {
|
||||
ByEnter,
|
||||
ByTab,
|
||||
ByClick,
|
||||
};
|
||||
using ChooseMethod = FieldAutocompleteChooseMethod;
|
||||
struct MentionChosen {
|
||||
not_null<UserData*> user;
|
||||
QString mention;
|
||||
@@ -100,7 +103,7 @@ public:
|
||||
QString command;
|
||||
ChooseMethod method = ChooseMethod::ByEnter;
|
||||
};
|
||||
using StickerChosen = ChatHelpers::FileChosen;
|
||||
using StickerChosen = FileChosen;
|
||||
enum class Type {
|
||||
Mentions,
|
||||
Hashtags,
|
||||
@@ -110,13 +113,14 @@ public:
|
||||
|
||||
bool chooseSelected(ChooseMethod method) const;
|
||||
|
||||
bool stickersShown() const {
|
||||
[[nodiscard]] bool stickersShown() const {
|
||||
return !_srows.empty();
|
||||
}
|
||||
|
||||
bool overlaps(const QRect &globalRect) {
|
||||
if (isHidden() || !testAttribute(Qt::WA_OpaquePaintEvent)) return false;
|
||||
|
||||
[[nodiscard]] bool overlaps(const QRect &globalRect) {
|
||||
if (isHidden() || !testAttribute(Qt::WA_OpaquePaintEvent)) {
|
||||
return false;
|
||||
}
|
||||
return rect().contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size()));
|
||||
}
|
||||
|
||||
@@ -129,11 +133,16 @@ public:
|
||||
void showAnimated();
|
||||
void hideAnimated();
|
||||
|
||||
rpl::producer<MentionChosen> mentionChosen() const;
|
||||
rpl::producer<HashtagChosen> hashtagChosen() const;
|
||||
rpl::producer<BotCommandChosen> botCommandChosen() const;
|
||||
rpl::producer<StickerChosen> stickerChosen() const;
|
||||
rpl::producer<Type> choosingProcesses() const;
|
||||
void requestRefresh();
|
||||
[[nodiscard]] rpl::producer<> refreshRequests() const;
|
||||
void requestStickersUpdate();
|
||||
[[nodiscard]] rpl::producer<> stickersUpdateRequests() const;
|
||||
|
||||
[[nodiscard]] rpl::producer<MentionChosen> mentionChosen() const;
|
||||
[[nodiscard]] rpl::producer<HashtagChosen> hashtagChosen() const;
|
||||
[[nodiscard]] rpl::producer<BotCommandChosen> botCommandChosen() const;
|
||||
[[nodiscard]] rpl::producer<StickerChosen> stickerChosen() const;
|
||||
[[nodiscard]] rpl::producer<Type> choosingProcesses() const;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
@@ -157,7 +166,7 @@ private:
|
||||
void recount(bool resetScroll = false);
|
||||
StickerRows getStickerSuggestions();
|
||||
|
||||
const std::shared_ptr<ChatHelpers::Show> _show;
|
||||
const std::shared_ptr<Show> _show;
|
||||
const not_null<Main::Session*> _session;
|
||||
const style::EmojiPan &_st;
|
||||
QPixmap _cache;
|
||||
@@ -189,7 +198,30 @@ private:
|
||||
bool _hiding = false;
|
||||
|
||||
Ui::Animations::Simple _a_opacity;
|
||||
rpl::event_stream<> _refreshRequests;
|
||||
rpl::event_stream<> _stickersUpdateRequests;
|
||||
|
||||
Fn<bool(int)> _moderateKeyActivateCallback;
|
||||
|
||||
};
|
||||
|
||||
struct FieldAutocompleteDescriptor {
|
||||
not_null<QWidget*> parent;
|
||||
std::shared_ptr<Show> show;
|
||||
not_null<Ui::InputField*> field;
|
||||
const style::EmojiPan *stOverride = nullptr;
|
||||
not_null<PeerData*> peer;
|
||||
Fn<ComposeFeatures()> features;
|
||||
Fn<SendMenu::Details()> sendMenuDetails;
|
||||
Fn<void()> stickerChoosing;
|
||||
Fn<void(FileChosen&&)> stickerChosen;
|
||||
Fn<void(TextWithTags)> setText;
|
||||
Fn<void(QString)> sendBotCommand;
|
||||
Fn<void(QString)> processShortcut;
|
||||
Fn<bool(int)> moderateKeyActivateCallback;
|
||||
};
|
||||
void InitFieldAutocomplete(
|
||||
std::unique_ptr<FieldAutocomplete> &autocomplete,
|
||||
FieldAutocompleteDescriptor &&descriptor);
|
||||
|
||||
} // namespace ChatHelpers
|
||||
|
||||
@@ -409,17 +409,19 @@ base::unique_qptr<Ui::PopupMenu> GifsListWidget::fillContextMenu(
|
||||
SendMenu::DefaultCallback(_show, send),
|
||||
icons);
|
||||
|
||||
if (!isInlineResult) {
|
||||
if (!isInlineResult && _inlineQueryPeer) {
|
||||
auto done = crl::guard(this, [=](
|
||||
Api::SendOptions options,
|
||||
TextWithTags text) {
|
||||
selectInlineResult(selected, options, true, std::move(text));
|
||||
});
|
||||
const auto show = _show;
|
||||
const auto peer = _inlineQueryPeer;
|
||||
menu->addAction(tr::lng_send_gif_with_caption(tr::now), [=] {
|
||||
show->show(Box(
|
||||
Ui::SendGifWithCaptionBox,
|
||||
item->getDocument(),
|
||||
peer,
|
||||
copyDetails,
|
||||
std::move(done)));
|
||||
}, &st::menuIconEdit);
|
||||
|
||||
@@ -63,6 +63,12 @@ constexpr auto kParseLinksTimeout = crl::time(1000);
|
||||
constexpr auto kTypesDuration = 4 * crl::time(1000);
|
||||
constexpr auto kCodeLanguageLimit = 32;
|
||||
|
||||
constexpr auto kLinkProtocols = {
|
||||
"http://",
|
||||
"https://",
|
||||
"tonsite://"
|
||||
};
|
||||
|
||||
// For mention / custom emoji tags save and validate selfId,
|
||||
// ignore tags for different users.
|
||||
[[nodiscard]] Fn<QString(QStringView)> FieldTagMimeProcessor(
|
||||
@@ -147,13 +153,23 @@ void EditLinkBox(
|
||||
object_ptr<Ui::RpWidget>(content),
|
||||
st::markdownLinkFieldPadding);
|
||||
placeholder->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
const auto link = [&] {
|
||||
if (!startLink.trimmed().isEmpty()) {
|
||||
return startLink.trimmed();
|
||||
}
|
||||
const auto clipboard = QGuiApplication::clipboard()->text().trimmed();
|
||||
const auto starts = [&](const auto &protocol) {
|
||||
return clipboard.startsWith(protocol);
|
||||
};
|
||||
return std::ranges::any_of(kLinkProtocols, starts) ? clipboard : QString();
|
||||
}();
|
||||
const auto url = Ui::AttachParentChild(
|
||||
content,
|
||||
object_ptr<Ui::InputField>(
|
||||
content,
|
||||
fieldSt,
|
||||
tr::lng_formatting_link_url(),
|
||||
startLink.trimmed()));
|
||||
link));
|
||||
url->heightValue(
|
||||
) | rpl::start_with_next([placeholder](int height) {
|
||||
placeholder->resize(placeholder->width(), height);
|
||||
@@ -209,6 +225,9 @@ void EditLinkBox(
|
||||
if (startText.isEmpty()) {
|
||||
text->setFocusFast();
|
||||
} else {
|
||||
if (!url->empty()) {
|
||||
url->selectAll();
|
||||
}
|
||||
url->setFocusFast();
|
||||
}
|
||||
});
|
||||
@@ -216,12 +235,31 @@ void EditLinkBox(
|
||||
url->customTab(true);
|
||||
text->customTab(true);
|
||||
|
||||
const auto clearFullSelection = [=](not_null<Ui::InputField*> input) {
|
||||
if (input->empty()) {
|
||||
return;
|
||||
}
|
||||
auto cursor = input->rawTextEdit()->textCursor();
|
||||
const auto hasFull = (!cursor.selectionStart()
|
||||
&& (cursor.selectionEnd()
|
||||
== (input->rawTextEdit()->document()->characterCount() - 1)));
|
||||
if (hasFull) {
|
||||
cursor.clearSelection();
|
||||
input->setTextCursor(cursor);
|
||||
}
|
||||
};
|
||||
|
||||
url->tabbed(
|
||||
) | rpl::start_with_next([=] {
|
||||
clearFullSelection(url);
|
||||
text->setFocus();
|
||||
}, url->lifetime());
|
||||
text->tabbed(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (!url->empty()) {
|
||||
url->selectAll();
|
||||
}
|
||||
clearFullSelection(text);
|
||||
url->setFocus();
|
||||
}, text->lifetime());
|
||||
}
|
||||
@@ -543,7 +581,6 @@ void InitMessageField(
|
||||
[=] { return show->paused(ChatHelpers::PauseReason::Any); },
|
||||
std::move(allowPremiumEmoji));
|
||||
InitMessageFieldGeometry(field);
|
||||
field->customTab(true);
|
||||
}
|
||||
|
||||
void InitMessageField(
|
||||
|
||||
@@ -213,11 +213,14 @@ StickersListWidget::StickersListWidget(
|
||||
st().pathBg,
|
||||
st().pathFg,
|
||||
[=] { update(); }))
|
||||
, _megagroupSetAbout(st::columnMinimalWidthThird - st::emojiScroll.width - st().headerLeft)
|
||||
, _megagroupSetAbout(st::columnMinimalWidthThird
|
||||
- st::emojiScroll.width
|
||||
- st().headerLeft)
|
||||
, _addText(tr::lng_stickers_featured_add(tr::now))
|
||||
, _addWidth(st::stickersTrendingAdd.font->width(_addText))
|
||||
, _addWidth(st::stickersTrendingAdd.style.font->width(_addText))
|
||||
, _installedText(tr::lng_stickers_featured_installed(tr::now))
|
||||
, _installedWidth(st::stickersTrendingInstalled.font->width(_installedText))
|
||||
, _installedWidth(
|
||||
st::stickersTrendingInstalled.style.font->width(_installedText))
|
||||
, _settings(this, tr::lng_stickers_you_have(tr::now))
|
||||
, _previewTimer([=] { showPreview(); })
|
||||
, _premiumMark(std::make_unique<StickerPremiumMark>(
|
||||
@@ -974,7 +977,7 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
|
||||
const auto &st = installedSet
|
||||
? st::stickersTrendingInstalled
|
||||
: st::stickersTrendingAdd;
|
||||
p.setFont(st.font);
|
||||
p.setFont(st.style.font);
|
||||
p.setPen(selected ? st.textFgOver : st.textFg);
|
||||
p.drawTextLeft(
|
||||
add.x() - (st.width / 2),
|
||||
@@ -1238,7 +1241,7 @@ void StickersListWidget::paintMegagroupEmptySet(Painter &p, int y, bool buttonSe
|
||||
_megagroupSetButtonRipple.reset();
|
||||
}
|
||||
}
|
||||
p.setFont(st::stickerGroupCategoryAdd.font);
|
||||
p.setFont(st::stickerGroupCategoryAdd.style.font);
|
||||
p.setPen(buttonSelected ? st::stickerGroupCategoryAdd.textFgOver : st::stickerGroupCategoryAdd.textFg);
|
||||
p.drawTextLeft(button.x() - (st::stickerGroupCategoryAdd.width / 2), button.y() + st::stickerGroupCategoryAdd.textTop, width(), _megagroupSetButtonText, _megagroupSetButtonTextWidth);
|
||||
}
|
||||
@@ -2734,7 +2737,7 @@ void StickersListWidget::refreshMegagroupSetGeometry() {
|
||||
auto left = megagroupSetInfoLeft();
|
||||
auto availableWidth = (width() - left);
|
||||
auto top = _megagroupSetAbout.countHeight(availableWidth) + st::stickerGroupCategoryAddMargin.top();
|
||||
_megagroupSetButtonTextWidth = st::stickerGroupCategoryAdd.font->width(_megagroupSetButtonText);
|
||||
_megagroupSetButtonTextWidth = st::stickerGroupCategoryAdd.style.font->width(_megagroupSetButtonText);
|
||||
auto buttonWidth = _megagroupSetButtonTextWidth - st::stickerGroupCategoryAdd.width;
|
||||
_megagroupSetButtonRect = QRect(left, top, buttonWidth, st::stickerGroupCategoryAdd.height);
|
||||
}
|
||||
|
||||
@@ -221,7 +221,8 @@ QByteArray Settings::serialize() const {
|
||||
+ Serialize::stringSize(noWarningExtensions)
|
||||
+ Serialize::stringSize(_customFontFamily)
|
||||
+ sizeof(qint32) * 3
|
||||
+ Serialize::bytearraySize(_tonsiteStorageToken);
|
||||
+ Serialize::bytearraySize(_tonsiteStorageToken)
|
||||
+ sizeof(qint32);
|
||||
|
||||
auto result = QByteArray();
|
||||
result.reserve(size);
|
||||
@@ -375,7 +376,8 @@ QByteArray Settings::serialize() const {
|
||||
1000000))
|
||||
<< qint32(_systemUnlockEnabled ? 1 : 0)
|
||||
<< qint32(!_weatherInCelsius ? 0 : *_weatherInCelsius ? 1 : 2)
|
||||
<< _tonsiteStorageToken;
|
||||
<< _tonsiteStorageToken
|
||||
<< qint32(_includeMutedCounterFolders ? 1 : 0);
|
||||
}
|
||||
|
||||
Ensures(result.size() == size);
|
||||
@@ -423,6 +425,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
qint32 sendFilesWay = _sendFilesWay.serialize();
|
||||
qint32 sendSubmitWay = static_cast<qint32>(_sendSubmitWay.current());
|
||||
qint32 includeMutedCounter = _includeMutedCounter ? 1 : 0;
|
||||
qint32 includeMutedCounterFolders = _includeMutedCounterFolders ? 1 : 0;
|
||||
qint32 countUnreadMessages = _countUnreadMessages ? 1 : 0;
|
||||
std::optional<QString> noWarningExtensions;
|
||||
qint32 legacyExeLaunchWarning = 1;
|
||||
@@ -804,6 +807,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
if (!stream.atEnd()) {
|
||||
stream >> tonsiteStorageToken;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> includeMutedCounterFolders;
|
||||
}
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("App Error: "
|
||||
"Bad data for Core::Settings::constructFromSerialized()"));
|
||||
@@ -844,6 +850,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
case ScreenCorner::BottomLeft: _notificationsCorner = uncheckedNotificationsCorner; break;
|
||||
}
|
||||
_includeMutedCounter = (includeMutedCounter == 1);
|
||||
_includeMutedCounterFolders = (includeMutedCounterFolders == 1);
|
||||
_countUnreadMessages = (countUnreadMessages == 1);
|
||||
_notifyAboutPinned = (notifyAboutPinned == 1);
|
||||
_autoLock = autoLock;
|
||||
@@ -864,8 +871,6 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
case Ui::InputSubmitSettings::Enter:
|
||||
case Ui::InputSubmitSettings::CtrlEnter: _sendSubmitWay = uncheckedSendSubmitWay; break;
|
||||
}
|
||||
_includeMutedCounter = (includeMutedCounter == 1);
|
||||
_countUnreadMessages = (countUnreadMessages == 1);
|
||||
if (noWarningExtensions) {
|
||||
const auto list = noWarningExtensions->mid(0, 10240)
|
||||
.split(' ', Qt::SkipEmptyParts)
|
||||
@@ -1345,6 +1350,7 @@ void Settings::resetOnLastLogout() {
|
||||
//_notificationsCount = 3;
|
||||
//_notificationsCorner = ScreenCorner::BottomRight;
|
||||
_includeMutedCounter = true;
|
||||
_includeMutedCounterFolders = true;
|
||||
_countUnreadMessages = true;
|
||||
_notifyAboutPinned = true;
|
||||
//_autoLock = 3600;
|
||||
|
||||
@@ -238,6 +238,12 @@ public:
|
||||
void setIncludeMutedCounter(bool value) {
|
||||
_includeMutedCounter = value;
|
||||
}
|
||||
[[nodiscard]] bool includeMutedCounterFolders() const {
|
||||
return _includeMutedCounterFolders;
|
||||
}
|
||||
void setIncludeMutedCounterFolders(bool value) {
|
||||
_includeMutedCounterFolders = value;
|
||||
}
|
||||
[[nodiscard]] bool countUnreadMessages() const {
|
||||
return _countUnreadMessages;
|
||||
}
|
||||
@@ -951,6 +957,7 @@ private:
|
||||
int _notificationsCount = 3;
|
||||
ScreenCorner _notificationsCorner = ScreenCorner::BottomRight;
|
||||
bool _includeMutedCounter = true;
|
||||
bool _includeMutedCounterFolders = true;
|
||||
bool _countUnreadMessages = true;
|
||||
rpl::variable<bool> _notifyAboutPinned = true;
|
||||
int _autoLock = 3600;
|
||||
|
||||
@@ -7,10 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Core {
|
||||
bool UrlIsLocal(const QUrl &url);
|
||||
} // namespace Core
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
@@ -49,7 +45,7 @@ void ShowInFolder(const QString &filepath);
|
||||
namespace internal {
|
||||
|
||||
inline QString UrlToLocalDefault(const QUrl &url) {
|
||||
return Core::UrlIsLocal(url) ? url.toLocalFile() : QString();
|
||||
return url.toLocalFile();
|
||||
}
|
||||
|
||||
void UnsafeOpenUrlDefault(const QString &url);
|
||||
|
||||
@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/update_checker.h"
|
||||
#include "core/application.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "dialogs/ui/dialogs_suggestions.h"
|
||||
#include "boxes/background_preview_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/boxes/edit_birthday_box.h"
|
||||
@@ -923,6 +924,17 @@ bool ShowCollectibleUsername(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShowStarsExamples(
|
||||
Window::SessionController *controller,
|
||||
const Match &match,
|
||||
const QVariant &context) {
|
||||
if (!controller) {
|
||||
return false;
|
||||
}
|
||||
controller->show(Dialogs::StarsExamplesBox(controller));
|
||||
return true;
|
||||
}
|
||||
|
||||
void ExportTestChatTheme(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<const Data::CloudTheme*> theme) {
|
||||
@@ -1380,6 +1392,10 @@ const std::vector<LocalUrlHandler> &InternalUrlHandlers() {
|
||||
u"^collectible_username/([a-zA-Z0-9\\-\\_\\.]+)@([0-9]+)$"_q,
|
||||
ShowCollectibleUsername,
|
||||
},
|
||||
{
|
||||
u"^stars_examples$"_q,
|
||||
ShowStarsExamples,
|
||||
},
|
||||
};
|
||||
return Result;
|
||||
}
|
||||
|
||||
@@ -226,24 +226,13 @@ bool CanSendFiles(not_null<const QMimeData*> data) {
|
||||
if (data->hasImage()) {
|
||||
return true;
|
||||
} else if (const auto urls = ReadMimeUrls(data); !urls.empty()) {
|
||||
if (ranges::all_of(urls, UrlIsLocal)) {
|
||||
if (ranges::all_of(urls, &QUrl::isLocalFile)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UrlIsLocal(const QUrl &url) {
|
||||
if (!url.isLocalFile()) {
|
||||
return false;
|
||||
}
|
||||
const auto result = url.toLocalFile();
|
||||
if (result.startsWith("//")) {
|
||||
return false;
|
||||
}
|
||||
return !result.isEmpty();
|
||||
}
|
||||
|
||||
QString FileExtension(const QString &filepath) {
|
||||
const auto reversed = ranges::views::reverse(filepath);
|
||||
const auto last = ranges::find_first_of(reversed, ".\\/");
|
||||
|
||||
@@ -68,7 +68,6 @@ struct MimeImageData {
|
||||
[[nodiscard]] QString ReadMimeText(not_null<const QMimeData*> data);
|
||||
[[nodiscard]] QList<QUrl> ReadMimeUrls(not_null<const QMimeData*> data);
|
||||
[[nodiscard]] bool CanSendFiles(not_null<const QMimeData*> data);
|
||||
[[nodiscard]] bool UrlIsLocal(const QUrl &url);
|
||||
|
||||
enum class NameType : uchar {
|
||||
Unknown,
|
||||
|
||||
@@ -13,7 +13,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/application.h"
|
||||
#include "core/sandbox.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "data/components/sponsored_messages.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "data/data_session.h"
|
||||
#include "iv/iv_instance.h"
|
||||
@@ -296,16 +295,6 @@ Fn<void()> UiIntegration::createSpoilerRepaint(const std::any &context) {
|
||||
return my ? my->customEmojiRepaint : nullptr;
|
||||
}
|
||||
|
||||
bool UiIntegration::allowClickHandlerActivation(
|
||||
const std::shared_ptr<ClickHandler> &handler,
|
||||
const ClickContext &context) {
|
||||
const auto my = context.other.value<ClickHandlerContext>();
|
||||
if (const auto window = my.sessionWindow.get()) {
|
||||
window->session().sponsoredMessages().clicked(my.itemId);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
rpl::producer<> UiIntegration::forcePopupMenuHideRequests() {
|
||||
return Core::App().passcodeLockChanges() | rpl::to_empty;
|
||||
}
|
||||
|
||||
@@ -61,9 +61,6 @@ public:
|
||||
QStringView data,
|
||||
const std::any &context) override;
|
||||
Fn<void()> createSpoilerRepaint(const std::any &context) override;
|
||||
bool allowClickHandlerActivation(
|
||||
const std::shared_ptr<ClickHandler> &handler,
|
||||
const ClickContext &context) override;
|
||||
|
||||
QString phraseContextCopyText() override;
|
||||
QString phraseContextCopyEmail() override;
|
||||
|
||||
@@ -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 = 5004003;
|
||||
constexpr auto AppVersionStr = "5.4.3";
|
||||
constexpr auto AppVersion = 5005008;
|
||||
constexpr auto AppVersionStr = "5.5.8";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -12,6 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/click_handler_types.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_media_preload.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
@@ -69,16 +71,17 @@ void SponsoredMessages::clearOldRequests() {
|
||||
}
|
||||
}
|
||||
|
||||
bool SponsoredMessages::append(not_null<History*> history) {
|
||||
SponsoredMessages::AppendResult SponsoredMessages::append(
|
||||
not_null<History*> history) {
|
||||
const auto it = _data.find(history);
|
||||
if (it == end(_data)) {
|
||||
return false;
|
||||
return SponsoredMessages::AppendResult::None;
|
||||
}
|
||||
auto &list = it->second;
|
||||
if (list.showedAll
|
||||
|| !TooEarlyForRequest(list.received)
|
||||
|| list.postsBetween) {
|
||||
return false;
|
||||
return SponsoredMessages::AppendResult::None;
|
||||
}
|
||||
|
||||
const auto entryIt = ranges::find_if(list.entries, [](const Entry &e) {
|
||||
@@ -86,19 +89,16 @@ bool SponsoredMessages::append(not_null<History*> history) {
|
||||
});
|
||||
if (entryIt == end(list.entries)) {
|
||||
list.showedAll = true;
|
||||
return false;
|
||||
return SponsoredMessages::AppendResult::None;
|
||||
} else if (entryIt->preload) {
|
||||
return SponsoredMessages::AppendResult::MediaLoading;
|
||||
}
|
||||
// SponsoredMessages::Details can be requested within
|
||||
// the constructor of HistoryItem, so itemFullId is used as a key.
|
||||
entryIt->itemFullId = FullMsgId(
|
||||
history->peer->id,
|
||||
_session->data().nextLocalMessageId());
|
||||
entryIt->item.reset(history->addSponsoredMessage(
|
||||
entryIt->itemFullId.msg,
|
||||
entryIt->sponsored.from,
|
||||
entryIt->sponsored.textWithEntities));
|
||||
|
||||
return true;
|
||||
return SponsoredMessages::AppendResult::Appended;
|
||||
}
|
||||
|
||||
void SponsoredMessages::inject(
|
||||
@@ -221,8 +221,7 @@ void SponsoredMessages::request(not_null<History*> history, Fn<void()> done) {
|
||||
const auto channel = history->peer->asChannel();
|
||||
Assert(channel != nullptr);
|
||||
request.requestId = _session->api().request(
|
||||
MTPchannels_GetSponsoredMessages(
|
||||
channel->inputChannel)
|
||||
MTPchannels_GetSponsoredMessages(channel->inputChannel)
|
||||
).done([=](const MTPmessages_sponsoredMessages &result) {
|
||||
parse(history, result);
|
||||
if (done) {
|
||||
@@ -270,15 +269,14 @@ void SponsoredMessages::append(
|
||||
const MTPSponsoredMessage &message) {
|
||||
const auto &data = message.data();
|
||||
const auto randomId = data.vrandom_id().v;
|
||||
auto mediaPhotoId = PhotoId(0);
|
||||
auto mediaDocumentId = DocumentId(0);
|
||||
auto mediaPhoto = (PhotoData*)nullptr;
|
||||
auto mediaDocument = (DocumentData*)nullptr;
|
||||
{
|
||||
if (data.vmedia()) {
|
||||
data.vmedia()->match([&](const MTPDmessageMediaPhoto &media) {
|
||||
if (const auto tlPhoto = media.vphoto()) {
|
||||
tlPhoto->match([&](const MTPDphoto &data) {
|
||||
const auto p = history->owner().processPhoto(data);
|
||||
mediaPhotoId = p->id;
|
||||
mediaPhoto = history->owner().processPhoto(data);
|
||||
}, [](const MTPDphotoEmpty &) {
|
||||
});
|
||||
}
|
||||
@@ -290,7 +288,7 @@ void SponsoredMessages::append(
|
||||
|| d->isSilentVideo()
|
||||
|| d->isAnimation()
|
||||
|| d->isGifv()) {
|
||||
mediaDocumentId = d->id;
|
||||
mediaDocument = d;
|
||||
}
|
||||
}, [](const MTPDdocumentEmpty &) {
|
||||
});
|
||||
@@ -306,8 +304,8 @@ void SponsoredMessages::append(
|
||||
.photoId = data.vphoto()
|
||||
? history->session().data().processPhoto(*data.vphoto())->id
|
||||
: PhotoId(0),
|
||||
.mediaPhotoId = mediaPhotoId,
|
||||
.mediaDocumentId = mediaDocumentId,
|
||||
.mediaPhotoId = (mediaPhoto ? mediaPhoto->id : 0),
|
||||
.mediaDocumentId = (mediaDocument ? mediaDocument->id : 0),
|
||||
.backgroundEmojiId = data.vcolor().has_value()
|
||||
? data.vcolor()->data().vbackground_emoji_id().value_or_empty()
|
||||
: uint64(0),
|
||||
@@ -341,7 +339,56 @@ void SponsoredMessages::append(
|
||||
.sponsorInfo = std::move(sponsorInfo),
|
||||
.additionalInfo = std::move(additionalInfo),
|
||||
};
|
||||
list.entries.push_back({ nullptr, {}, std::move(sharedMessage) });
|
||||
list.entries.push_back({
|
||||
.sponsored = std::move(sharedMessage),
|
||||
});
|
||||
auto &entry = list.entries.back();
|
||||
const auto itemId = entry.itemFullId = FullMsgId(
|
||||
history->peer->id,
|
||||
_session->data().nextLocalMessageId());
|
||||
const auto fileOrigin = FileOrigin(); // No way to refresh in ads.
|
||||
|
||||
static const auto kFlaggedPreload = ((MediaPreload*)quintptr(0x01));
|
||||
const auto preloaded = [=] {
|
||||
const auto i = _data.find(history);
|
||||
if (i == end(_data)) {
|
||||
return;
|
||||
}
|
||||
auto &entries = i->second.entries;
|
||||
const auto j = ranges::find(entries, itemId, &Entry::itemFullId);
|
||||
if (j == end(entries)) {
|
||||
return;
|
||||
}
|
||||
auto &entry = *j;
|
||||
if (entry.preload.get() == kFlaggedPreload) {
|
||||
entry.preload.release();
|
||||
} else {
|
||||
entry.preload = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
auto preload = std::unique_ptr<MediaPreload>();
|
||||
entry.preload.reset(kFlaggedPreload);
|
||||
if (mediaPhoto) {
|
||||
preload = std::make_unique<PhotoPreload>(
|
||||
mediaPhoto,
|
||||
fileOrigin,
|
||||
preloaded);
|
||||
} else if (mediaDocument && VideoPreload::Can(mediaDocument)) {
|
||||
preload = std::make_unique<VideoPreload>(
|
||||
mediaDocument,
|
||||
fileOrigin,
|
||||
preloaded);
|
||||
}
|
||||
// Preload constructor may have called preloaded(), which zero-ed
|
||||
// entry.preload, that way we're ready and don't need to save it.
|
||||
// Otherwise we're preloading and need to save the task.
|
||||
if (entry.preload.get() == kFlaggedPreload) {
|
||||
entry.preload.release();
|
||||
if (preload) {
|
||||
entry.preload = std::move(preload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SponsoredMessages::clearItems(not_null<History*> history) {
|
||||
@@ -433,7 +480,10 @@ SponsoredMessages::Details SponsoredMessages::lookupDetails(
|
||||
};
|
||||
}
|
||||
|
||||
void SponsoredMessages::clicked(const FullMsgId &fullId) {
|
||||
void SponsoredMessages::clicked(
|
||||
const FullMsgId &fullId,
|
||||
bool isMedia,
|
||||
bool isFullscreen) {
|
||||
const auto entryPtr = find(fullId);
|
||||
if (!entryPtr) {
|
||||
return;
|
||||
@@ -441,7 +491,11 @@ void SponsoredMessages::clicked(const FullMsgId &fullId) {
|
||||
const auto randomId = entryPtr->sponsored.randomId;
|
||||
const auto channel = entryPtr->item->history()->peer->asChannel();
|
||||
Assert(channel != nullptr);
|
||||
using Flag = MTPchannels_ClickSponsoredMessage::Flag;
|
||||
_session->api().request(MTPchannels_ClickSponsoredMessage(
|
||||
MTP_flags(Flag(0)
|
||||
| (isMedia ? Flag::f_media : Flag(0))
|
||||
| (isFullscreen ? Flag::f_fullscreen : Flag(0))),
|
||||
channel->inputChannel,
|
||||
MTP_bytes(randomId)
|
||||
)).send();
|
||||
|
||||