Compare commits
373 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02c01e258c | ||
|
|
b2f912868d | ||
|
|
ecf0eba0a5 | ||
|
|
8e5fec2fa8 | ||
|
|
89c35e8512 | ||
|
|
3fa88fad79 | ||
|
|
5f037462ed | ||
|
|
93c01e5f1e | ||
|
|
52953626a7 | ||
|
|
f77fdc799d | ||
|
|
7bf1f9bd71 | ||
|
|
363ffc1c04 | ||
|
|
4001899bdf | ||
|
|
fabdd89c4a | ||
|
|
70f1675085 | ||
|
|
07e1e2d9d6 | ||
|
|
ebff6c6370 | ||
|
|
233eb6d916 | ||
|
|
d568cab2fc | ||
|
|
8447b95c50 | ||
|
|
e959d1e1b0 | ||
|
|
ead5dbe368 | ||
|
|
f091f2b344 | ||
|
|
267a51e800 | ||
|
|
939f6095ba | ||
|
|
a333615e53 | ||
|
|
e1b33fbc40 | ||
|
|
99c5d994b5 | ||
|
|
0971485367 | ||
|
|
3cfa963f69 | ||
|
|
7096a4231f | ||
|
|
07f0c182e6 | ||
|
|
535b223333 | ||
|
|
ffa8c2be79 | ||
|
|
2873e64ca2 | ||
|
|
8bd28bce69 | ||
|
|
b97ce43fca | ||
|
|
d34c4cc2f2 | ||
|
|
1ca1f0fa7d | ||
|
|
8c3c8f888d | ||
|
|
a75d7f0381 | ||
|
|
7684466acf | ||
|
|
0067245739 | ||
|
|
4a5d8aa217 | ||
|
|
2d786aa02c | ||
|
|
b0933b96ef | ||
|
|
20fb73b626 | ||
|
|
ae7bd7112b | ||
|
|
2365363dcc | ||
|
|
e0e4a7bec6 | ||
|
|
ebd0c3696a | ||
|
|
e0a0d9c039 | ||
|
|
dae9f2ab2b | ||
|
|
7168a00ee4 | ||
|
|
1e2d0ced20 | ||
|
|
d6ac883efa | ||
|
|
a386d70ae4 | ||
|
|
bc8bf672b4 | ||
|
|
e38998214f | ||
|
|
472a2fe802 | ||
|
|
2d6d89b1cf | ||
|
|
66be2ac6ca | ||
|
|
3137c9f3f7 | ||
|
|
b9ebb02e72 | ||
|
|
1e02c475d6 | ||
|
|
4d2cda0692 | ||
|
|
cbd2b8f428 | ||
|
|
93605db690 | ||
|
|
2567096de0 | ||
|
|
6ed25d012f | ||
|
|
c2afef2bde | ||
|
|
0991e7d8a4 | ||
|
|
cf52f2a743 | ||
|
|
37dddda1a0 | ||
|
|
3f2f3ebd51 | ||
|
|
7ba78540ac | ||
|
|
19afb49fce | ||
|
|
46ab553fa5 | ||
|
|
68cc42047e | ||
|
|
e25cf27ba5 | ||
|
|
3895e6d958 | ||
|
|
24cf3984c8 | ||
|
|
6237675744 | ||
|
|
30dae049ff | ||
|
|
1dc30caee9 | ||
|
|
b2d340cbfb | ||
|
|
7d52787e54 | ||
|
|
ae3f16ccbd | ||
|
|
057222757b | ||
|
|
119f109904 | ||
|
|
23a77b1ba4 | ||
|
|
c076daa91f | ||
|
|
b7ef5325ac | ||
|
|
8b535c58fa | ||
|
|
6bc8daaeda | ||
|
|
7a2562e5bb | ||
|
|
9e0c731b32 | ||
|
|
2e0e4006a1 | ||
|
|
187139473d | ||
|
|
dbb0a5ad28 | ||
|
|
24aaed44b9 | ||
|
|
32b8d83c04 | ||
|
|
bf55c325ce | ||
|
|
3af288c74e | ||
|
|
e306d9ba35 | ||
|
|
b23a877d7e | ||
|
|
08fda055fc | ||
|
|
84055ed74e | ||
|
|
2db30690ce | ||
|
|
304bcfd343 | ||
|
|
8a1cf2bb3a | ||
|
|
c857c24a64 | ||
|
|
bbdcb047d0 | ||
|
|
78f2e70956 | ||
|
|
75a75626ce | ||
|
|
cec9688d58 | ||
|
|
81492b7d3a | ||
|
|
9166acbbb9 | ||
|
|
36de2b6ca6 | ||
|
|
21f909dd4b | ||
|
|
f2a92c9122 | ||
|
|
7ee2e3d8bc | ||
|
|
f89aeb6ad4 | ||
|
|
0397006894 | ||
|
|
d6863074b2 | ||
|
|
9c185a30e0 | ||
|
|
a8f492a027 | ||
|
|
0a92b1dc68 | ||
|
|
e6d661f8ee | ||
|
|
f48dfb5d81 | ||
|
|
cd041e8366 | ||
|
|
6787ea883e | ||
|
|
78937d716f | ||
|
|
9713abc002 | ||
|
|
b44b45cca0 | ||
|
|
9e2cf0ed73 | ||
|
|
b01d7ea5b9 | ||
|
|
ae89b65a98 | ||
|
|
9b9c3d788d | ||
|
|
ccc6c6daa5 | ||
|
|
9ce6636c6a | ||
|
|
6287d306c2 | ||
|
|
6cfa053328 | ||
|
|
9514b6eecd | ||
|
|
c8d4818d22 | ||
|
|
4142ada729 | ||
|
|
d7ffdbd78d | ||
|
|
e8d87d37bb | ||
|
|
343ffc23eb | ||
|
|
95e0086eed | ||
|
|
c010ecfe38 | ||
|
|
302e9371c8 | ||
|
|
7060c0e6d7 | ||
|
|
20a4c7f9f4 | ||
|
|
e59e4afd3e | ||
|
|
f74dd3ca1e | ||
|
|
511cfc524f | ||
|
|
4cf6173d25 | ||
|
|
17996757fd | ||
|
|
6bc1049858 | ||
|
|
ff44f626ba | ||
|
|
552343fa37 | ||
|
|
4dc7fd8cd1 | ||
|
|
285c96fd2e | ||
|
|
e6af33367e | ||
|
|
7092fe2242 | ||
|
|
a32ff46579 | ||
|
|
7f20cf59d1 | ||
|
|
a8a1b08127 | ||
|
|
1a1e777b87 | ||
|
|
9e76e64064 | ||
|
|
975ae17ef9 | ||
|
|
ed9dcef66f | ||
|
|
b1e537e54e | ||
|
|
e5886862c3 | ||
|
|
d85b668d4f | ||
|
|
b363d8bfb5 | ||
|
|
754d467440 | ||
|
|
598f08d6c7 | ||
|
|
224fdc1864 | ||
|
|
e646b4dc9a | ||
|
|
9077db2e97 | ||
|
|
7e14277ead | ||
|
|
d351a7d697 | ||
|
|
70ed43b811 | ||
|
|
913083ebc6 | ||
|
|
588a95a7ae | ||
|
|
c0a0ad4ec5 | ||
|
|
5eafe96525 | ||
|
|
b41ac0fc2a | ||
|
|
cfe93530b8 | ||
|
|
4492e72ffa | ||
|
|
1a0fd35f36 | ||
|
|
db475ef0b4 | ||
|
|
b3dddc1dfe | ||
|
|
b64c610abb | ||
|
|
c5add2fca9 | ||
|
|
9ea78f7d28 | ||
|
|
54b0d965ae | ||
|
|
7b4bd5696b | ||
|
|
9064f3ba4b | ||
|
|
e00e562b5f | ||
|
|
73f38c896f | ||
|
|
5767cbd0e3 | ||
|
|
ba31756bf9 | ||
|
|
a88f48cd93 | ||
|
|
f2e0e481de | ||
|
|
566f279137 | ||
|
|
394ef13955 | ||
|
|
6812e17d07 | ||
|
|
fdf826b686 | ||
|
|
e57742e7de | ||
|
|
73b63aa414 | ||
|
|
44aa2aec5d | ||
|
|
8d9d7c4cea | ||
|
|
9557f0c844 | ||
|
|
a32a9aa3fc | ||
|
|
86fa98dfbb | ||
|
|
6a69447d90 | ||
|
|
2d20e7a9e2 | ||
|
|
ac7b2e0da0 | ||
|
|
388325a496 | ||
|
|
af728e82fc | ||
|
|
3cb33f0825 | ||
|
|
1038baf467 | ||
|
|
828ecabc78 | ||
|
|
88703ba1eb | ||
|
|
b0ecb2c535 | ||
|
|
c70482dbc4 | ||
|
|
51f1999412 | ||
|
|
dd4fbc256c | ||
|
|
cc2265583f | ||
|
|
1e7a4db57f | ||
|
|
9a9e30c88b | ||
|
|
3d98ebff42 | ||
|
|
a6f4b1ae8e | ||
|
|
3e6ea8109c | ||
|
|
ec407d57a5 | ||
|
|
838ad66166 | ||
|
|
c6bf905253 | ||
|
|
8310230582 | ||
|
|
1f21af0bdb | ||
|
|
127e4a5086 | ||
|
|
6a30967f23 | ||
|
|
df2b020b42 | ||
|
|
a74c5a89a6 | ||
|
|
3cb0be5be5 | ||
|
|
d56f3cfecf | ||
|
|
9716a901d1 | ||
|
|
649d242e9a | ||
|
|
8ac3c2157f | ||
|
|
7ca4ca21fa | ||
|
|
e565acba91 | ||
|
|
ede771e51b | ||
|
|
0282786b4c | ||
|
|
42dd08ace5 | ||
|
|
cf1d274b0d | ||
|
|
d361d5f3b2 | ||
|
|
3e63b40564 | ||
|
|
c5139ed06a | ||
|
|
3dc93526ff | ||
|
|
3edf8e10e2 | ||
|
|
00215622cc | ||
|
|
479b7c3140 | ||
|
|
a3ca8ddcfc | ||
|
|
9ace04d2c9 | ||
|
|
8b11d2d5e7 | ||
|
|
05fb0f81f9 | ||
|
|
da49efa1ed | ||
|
|
39fb0a5b66 | ||
|
|
962d4d29ee | ||
|
|
779e9b658b | ||
|
|
f9ee4dcb51 | ||
|
|
1acdbb69ae | ||
|
|
c022a1c838 | ||
|
|
52afd3d5a8 | ||
|
|
79b1c0edee | ||
|
|
4803bd4b3f | ||
|
|
215a262076 | ||
|
|
7a35577d3a | ||
|
|
efbd3ca8fa | ||
|
|
18904412cd | ||
|
|
0d0d0ab994 | ||
|
|
71deef61f5 | ||
|
|
46c8a55f56 | ||
|
|
37f837dcb7 | ||
|
|
a5be0a685a | ||
|
|
4cdd1fec95 | ||
|
|
761617c1ce | ||
|
|
e3bc4dab85 | ||
|
|
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 |
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
|
||||
|
||||
2
.gitmodules
vendored
@@ -3,7 +3,7 @@
|
||||
url = https://github.com/telegramdesktop/libtgvoip
|
||||
[submodule "Telegram/ThirdParty/GSL"]
|
||||
path = Telegram/ThirdParty/GSL
|
||||
url = https://github.com/desktop-app/GSL.git
|
||||
url = https://github.com/Microsoft/GSL.git
|
||||
[submodule "Telegram/ThirdParty/xxHash"]
|
||||
path = Telegram/ThirdParty/xxHash
|
||||
url = https://github.com/Cyan4973/xxHash.git
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -322,6 +322,8 @@ PRIVATE
|
||||
boxes/sessions_box.h
|
||||
boxes/share_box.cpp
|
||||
boxes/share_box.h
|
||||
boxes/star_gift_box.cpp
|
||||
boxes/star_gift_box.h
|
||||
boxes/sticker_set_box.cpp
|
||||
boxes/sticker_set_box.h
|
||||
boxes/stickers_box.cpp
|
||||
@@ -569,6 +571,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
|
||||
@@ -604,6 +608,7 @@ PRIVATE
|
||||
data/data_replies_list.h
|
||||
data/data_reply_preview.cpp
|
||||
data/data_reply_preview.h
|
||||
data/data_report.h
|
||||
data/data_saved_messages.cpp
|
||||
data/data_saved_messages.h
|
||||
data/data_saved_sublist.cpp
|
||||
@@ -947,6 +952,10 @@ PRIVATE
|
||||
info/media/info_media_widget.h
|
||||
info/members/info_members_widget.cpp
|
||||
info/members/info_members_widget.h
|
||||
info/peer_gifts/info_peer_gifts_common.cpp
|
||||
info/peer_gifts/info_peer_gifts_common.h
|
||||
info/peer_gifts/info_peer_gifts_widget.cpp
|
||||
info/peer_gifts/info_peer_gifts_widget.h
|
||||
info/polls/info_polls_results_inner_widget.cpp
|
||||
info/polls/info_polls_results_inner_widget.h
|
||||
info/polls/info_polls_results_widget.cpp
|
||||
@@ -986,6 +995,7 @@ PRIVATE
|
||||
info/statistics/info_statistics_list_controllers.h
|
||||
info/statistics/info_statistics_recent_message.cpp
|
||||
info/statistics/info_statistics_recent_message.h
|
||||
info/statistics/info_statistics_tag.h
|
||||
info/statistics/info_statistics_widget.cpp
|
||||
info/statistics/info_statistics_widget.h
|
||||
info/stories/info_stories_inner_widget.cpp
|
||||
@@ -1153,6 +1163,8 @@ PRIVATE
|
||||
media/streaming/media_streaming_player.h
|
||||
media/streaming/media_streaming_reader.cpp
|
||||
media/streaming/media_streaming_reader.h
|
||||
media/streaming/media_streaming_round_preview.cpp
|
||||
media/streaming/media_streaming_round_preview.h
|
||||
media/streaming/media_streaming_utility.cpp
|
||||
media/streaming/media_streaming_utility.h
|
||||
media/streaming/media_streaming_video_track.cpp
|
||||
@@ -1492,6 +1504,8 @@ PRIVATE
|
||||
ui/chat/choose_send_as.h
|
||||
ui/chat/choose_theme_controller.cpp
|
||||
ui/chat/choose_theme_controller.h
|
||||
ui/chat/sponsored_message_bar.cpp
|
||||
ui/chat/sponsored_message_bar.h
|
||||
ui/controls/emoji_button_factory.cpp
|
||||
ui/controls/emoji_button_factory.h
|
||||
ui/controls/location_picker.cpp
|
||||
@@ -1840,7 +1854,7 @@ endif()
|
||||
|
||||
set_target_properties(Telegram PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_folder})
|
||||
|
||||
if (WIN32 AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
|
||||
if (MSVC)
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
delayimp
|
||||
@@ -1937,7 +1951,7 @@ if (NOT DESKTOP_APP_DISABLE_AUTOUPDATE AND NOT build_macstore AND NOT build_wins
|
||||
base/platform/win/base_windows_safe_library.h
|
||||
)
|
||||
target_include_directories(Updater PRIVATE ${lib_base_loc})
|
||||
if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
|
||||
if (MSVC)
|
||||
target_link_libraries(Updater
|
||||
PRIVATE
|
||||
delayimp
|
||||
|
||||
BIN
Telegram/Resources/art/round_placeholder.jpg
Normal file
|
After Width: | Height: | Size: 787 B |
|
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/chat/input_video.png
Normal file
|
After Width: | Height: | Size: 621 B |
BIN
Telegram/Resources/icons/chat/input_video@2x.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Telegram/Resources/icons/chat/input_video@3x.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
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 |
BIN
Telegram/Resources/icons/menu/edited_status.png
Normal file
|
After Width: | Height: | Size: 586 B |
BIN
Telegram/Resources/icons/menu/edited_status@2x.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
Telegram/Resources/icons/menu/edited_status@3x.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
Telegram/Resources/icons/player/player_settings.png
Normal file
|
After Width: | Height: | Size: 997 B |
BIN
Telegram/Resources/icons/player/player_settings@2x.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
Telegram/Resources/icons/player/player_settings@3x.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
Telegram/Resources/icons/voice_lock/input_round_s.png
Normal file
|
After Width: | Height: | Size: 498 B |
BIN
Telegram/Resources/icons/voice_lock/input_round_s@2x.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/voice_lock/input_round_s@3x.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
@@ -12,6 +12,7 @@ body {
|
||||
margin: 0;
|
||||
background-color: var(--td-window-bg);
|
||||
color: var(--td-window-fg);
|
||||
zoom: var(--td-zoom-percentage);
|
||||
}
|
||||
|
||||
html.custom_scroll ::-webkit-scrollbar {
|
||||
|
||||
@@ -72,6 +72,9 @@ var IV = {
|
||||
}
|
||||
},
|
||||
frameKeyDown: function (e) {
|
||||
const key0 = (e.key === '0')
|
||||
|| (e.code === 'Key0')
|
||||
|| (e.keyCode === 48);
|
||||
const keyW = (e.key === 'w')
|
||||
|| (e.code === 'KeyW')
|
||||
|| (e.keyCode === 87);
|
||||
@@ -81,12 +84,12 @@ var IV = {
|
||||
const keyM = (e.key === 'm')
|
||||
|| (e.code === 'KeyM')
|
||||
|| (e.keyCode === 77);
|
||||
if ((e.metaKey || e.ctrlKey) && (keyW || keyQ || keyM)) {
|
||||
if ((e.metaKey || e.ctrlKey) && (keyW || keyQ || keyM || key0)) {
|
||||
e.preventDefault();
|
||||
IV.notify({
|
||||
event: 'keydown',
|
||||
modifier: e.ctrlKey ? 'ctrl' : 'cmd',
|
||||
key: keyW ? 'w' : keyQ ? 'q' : 'm',
|
||||
key: key0 ? '0' : keyW ? 'w' : keyQ ? 'q' : 'm',
|
||||
});
|
||||
} else if (e.key === 'Escape' || e.keyCode === 27) {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -453,6 +453,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_username_app_not_found" = "Bot application not found.";
|
||||
"lng_username_link" = "This link opens a chat with you:";
|
||||
"lng_username_copied" = "Link copied to clipboard.";
|
||||
"lng_username_text_copied" = "Username copied to clipboard.";
|
||||
|
||||
"lng_usernames_edit" = "click to edit";
|
||||
"lng_usernames_active" = "active";
|
||||
@@ -487,6 +488,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_collectible_phone_info" = "This phone number was bought on **Fragment** on {date} for {price}";
|
||||
"lng_collectible_phone_copy" = "Copy Phone Number";
|
||||
"lng_collectible_learn_more" = "Learn More";
|
||||
"lng_collectible_phone_copied" = "Phone number copied to clipboard.";
|
||||
|
||||
"lng_settings_section_info" = "Info";
|
||||
|
||||
@@ -497,8 +499,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_notify_global" = "Global settings";
|
||||
"lng_settings_notify_title" = "Notifications for chats";
|
||||
"lng_settings_desktop_notify" = "Desktop notifications";
|
||||
"lng_settings_native_title" = "Native notifications";
|
||||
"lng_settings_native_title" = "System integration";
|
||||
"lng_settings_use_windows" = "Use Windows notifications";
|
||||
"lng_settings_skip_in_focus" = "Respect system Focus mode";
|
||||
"lng_settings_use_native_notifications" = "Use native notifications";
|
||||
"lng_settings_notifications_position" = "Location on the screen";
|
||||
"lng_settings_notifications_count" = "Notifications count";
|
||||
@@ -508,6 +511,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_alert_linux" = "Draw attention to the window";
|
||||
"lng_settings_badge_title" = "Badge counter";
|
||||
"lng_settings_include_muted" = "Include muted chats in unread count";
|
||||
"lng_settings_include_muted_folders" = "Include muted chats in folder counters";
|
||||
"lng_settings_count_unread" = "Count unread messages";
|
||||
"lng_settings_events_title" = "Events";
|
||||
"lng_settings_events_joined" = "Contact joined Telegram";
|
||||
@@ -1322,6 +1326,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_profile_similar_channels#other" = "{count} similar channels";
|
||||
"lng_profile_saved_messages#one" = "{count} saved message";
|
||||
"lng_profile_saved_messages#other" = "{count} saved messages";
|
||||
"lng_profile_peer_gifts#one" = "{count} gift";
|
||||
"lng_profile_peer_gifts#other" = "{count} gifts";
|
||||
"lng_profile_participants_section" = "Members";
|
||||
"lng_profile_subscribers_section" = "Subscribers";
|
||||
"lng_profile_add_contact" = "Add Contact";
|
||||
@@ -1376,6 +1382,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_profile_copy_fullname" = "Copy Name";
|
||||
"lng_profile_photo_by_you" = "photo set by you";
|
||||
"lng_profile_public_photo" = "public photo";
|
||||
"lng_profile_administrators#one" = "{count} administrator";
|
||||
"lng_profile_administrators#other" = "{count} administrators";
|
||||
"lng_profile_manage" = "Channel settings";
|
||||
|
||||
"lng_invite_upgrade_title" = "Upgrade to Premium";
|
||||
"lng_invite_upgrade_group_invite#one" = "{users} only accepts invitations to groups from Contacts and **Premium** users.";
|
||||
@@ -1585,6 +1594,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_manage_peer_bot_public_link" = "Public Link";
|
||||
"lng_manage_peer_bot_public_links" = "Public Links";
|
||||
"lng_manage_peer_bot_balance" = "Balance";
|
||||
"lng_manage_peer_bot_balance_currency" = "Toncoin";
|
||||
"lng_manage_peer_bot_balance_credits" = "Stars";
|
||||
"lng_manage_peer_bot_edit_intro" = "Edit Intro";
|
||||
"lng_manage_peer_bot_edit_commands" = "Edit Commands";
|
||||
"lng_manage_peer_bot_edit_settings" = "Change Bot Settings";
|
||||
@@ -1659,6 +1670,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_report_and_ban_button" = "Ban user";
|
||||
"lng_report_details_about" = "Please enter any additional details relevant to your report.";
|
||||
"lng_report_details" = "Additional Details";
|
||||
"lng_report_details_optional" = "Add Comment (Optional)";
|
||||
"lng_report_details_non_optional" = "Add Comment";
|
||||
"lng_report_details_message_about" = "Please help us by telling what is wrong with the message you have selected";
|
||||
"lng_report_reason_spam" = "Spam";
|
||||
"lng_report_reason_fake" = "Fake Account";
|
||||
"lng_report_reason_violence" = "Violence";
|
||||
@@ -1852,8 +1866,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_action_proximity_distance_km#other" = "{count} km";
|
||||
"lng_action_webview_data_done" = "Data from the \"{text}\" button was transferred to the bot.";
|
||||
"lng_action_gift_received" = "{user} sent you a gift for {cost}";
|
||||
"lng_action_gift_received_me" = "You sent to {user} a gift for {cost}";
|
||||
"lng_action_gift_sent" = "You sent a gift for {cost}";
|
||||
"lng_action_gift_received_anonymous" = "Unknown user sent you a gift for {cost}";
|
||||
"lng_action_gift_for_stars#one" = "{count} Star";
|
||||
"lng_action_gift_for_stars#other" = "{count} Stars";
|
||||
"lng_action_gift_got_subtitle" = "Gift from {user}";
|
||||
"lng_action_gift_got_stars_text#one" = "Display this gift on your page or convert it to **{count}** Star.";
|
||||
"lng_action_gift_got_stars_text#other" = "Display this gift on your page or convert it to **{count}** Stars.";
|
||||
"lng_action_gift_got_gift_text" = "You can keep this gift on your page.";
|
||||
"lng_action_gift_sent_subtitle" = "Gift for {user}";
|
||||
"lng_action_gift_sent_text#one" = "{user} can display this gift on their page or convert it to {count} Star.";
|
||||
"lng_action_gift_sent_text#other" = "{user} can display this gift on their page or convert it to {count} Stars.";
|
||||
"lng_action_gift_premium_months#one" = "{count} Month Premium";
|
||||
"lng_action_gift_premium_months#other" = "{count} Months Premium";
|
||||
"lng_action_gift_premium_about" = "Subscription for exclusive Telegram features.";
|
||||
"lng_action_suggested_photo_me" = "You suggested this photo for {user}'s Telegram profile.";
|
||||
"lng_action_suggested_photo" = "{user} suggests this photo for your Telegram profile.";
|
||||
"lng_action_suggested_photo_button" = "View Photo";
|
||||
@@ -1915,6 +1941,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_similar_channels_premium_all_link" = "Telegram Premium";
|
||||
"lng_similar_channels_show_more" = "Show more channels";
|
||||
|
||||
"lng_peer_gifts_title" = "Gifts";
|
||||
"lng_peer_gifts_about" = "These gifts were sent to {user} by other users.";
|
||||
"lng_peer_gifts_about_mine" = "These gifts were sent to you by other users. Click on a gift to convert it to Stars or change its privacy settings.";
|
||||
|
||||
"lng_premium_gift_duration_months#one" = "for {count} month";
|
||||
"lng_premium_gift_duration_months#other" = "for {count} months";
|
||||
"lng_premium_gift_duration_years#one" = "for {count} year";
|
||||
@@ -2071,6 +2101,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
"lng_channel_public_link_copied" = "Link copied to clipboard.";
|
||||
"lng_context_about_private_link" = "This link will only work for members of this chat.";
|
||||
"lng_public_post_private_hint_ctrl" = "Use Ctrl+Click to copy a non-public link.";
|
||||
"lng_public_post_private_hint_cmd" = "Use Cmd+Click to copy a non-public link.";
|
||||
|
||||
"lng_forwarded" = "Forwarded from {user}";
|
||||
"lng_forwarded_story" = "Story from {user}";
|
||||
@@ -2089,8 +2121,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_recommended_message_title" = "Recommended";
|
||||
"lng_edited" = "edited";
|
||||
"lng_commented" = "commented";
|
||||
"lng_approximate" = "appx.";
|
||||
"lng_edited_date" = "Edited: {date}";
|
||||
"lng_sent_date" = "Sent: {date}";
|
||||
"lng_approximate_about" = "Estimated date of video publishing.";
|
||||
"lng_views_tooltip#one" = "Views: {count}";
|
||||
"lng_views_tooltip#other" = "Views: {count}";
|
||||
"lng_forwards_tooltip#one" = "Shares: {count}";
|
||||
@@ -2125,6 +2159,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_media_cancel" = "Cancel";
|
||||
"lng_media_video" = "Video";
|
||||
"lng_media_audio" = "Voice message";
|
||||
"lng_media_round" = "Video message";
|
||||
|
||||
"lng_media_auto_settings" = "Automatic media download";
|
||||
"lng_media_auto_in_private" = "In private chats";
|
||||
@@ -2415,18 +2450,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_credits_box_history_entry_gift_name" = "Received Gift";
|
||||
"lng_credits_box_history_entry_giveaway_name" = "Received Prize";
|
||||
"lng_credits_box_history_entry_gift_sent" = "Sent Gift";
|
||||
"lng_credits_box_history_entry_gift_converted" = "Converted Gift";
|
||||
"lng_credits_box_history_entry_gift_unavailable" = "Unavailable";
|
||||
"lng_credits_box_history_entry_gift_sold_out" = "This gift has sold out";
|
||||
"lng_credits_box_history_entry_gift_out_about" = "With Stars, **{user}** will be able to unlock content and services on Telegram.\n{link}";
|
||||
"lng_credits_box_history_entry_gift_in_about" = "Use Stars to unlock content and services on Telegram. {link}";
|
||||
"lng_credits_box_history_entry_gift_about_link" = "See Examples {emoji}";
|
||||
"lng_credits_box_history_entry_gift_examples" = "Examples";
|
||||
"lng_credits_box_history_entry_ads" = "Ads Platform";
|
||||
"lng_credits_box_history_entry_premium_bot" = "Stars Top-Up";
|
||||
"lng_credits_box_history_entry_api" = "Paid Broadcast";
|
||||
"lng_credits_box_history_entry_floodskip_about#one" = "{count} Message";
|
||||
"lng_credits_box_history_entry_floodskip_about#other" = "{count} Messages";
|
||||
"lng_credits_box_history_entry_floodskip_row" = "Messages";
|
||||
"lng_credits_box_history_entry_via_premium_bot" = "Premium Bot";
|
||||
"lng_credits_box_history_entry_id" = "Transaction ID";
|
||||
"lng_credits_box_history_entry_id_copied" = "Transaction ID copied to clipboard.";
|
||||
"lng_credits_box_history_entry_success_date" = "Transaction date";
|
||||
"lng_credits_box_history_entry_success_url" = "Transaction link";
|
||||
"lng_credits_box_history_entry_media" = "Media";
|
||||
"lng_credits_box_history_entry_message" = "Message";
|
||||
"lng_credits_box_history_entry_about" = "You can dispute this transaction {link}.";
|
||||
"lng_credits_box_history_entry_about_link" = "here";
|
||||
"lng_credits_box_history_entry_reaction_name" = "Star Reaction";
|
||||
@@ -2461,6 +2504,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_credits_small_balance_about" = "Buy **Stars** and use them on **{bot}** and other miniapps.";
|
||||
"lng_credits_small_balance_reaction" = "Buy **Stars** and send them to {channel} to support their posts.";
|
||||
"lng_credits_small_balance_subscribe" = "Buy **Stars** and subscribe to **{channel}** and other channels.";
|
||||
"lng_credits_small_balance_star_gift" = "Buy **Stars** to send gifts to {user} and other contacts.";
|
||||
"lng_credits_small_balance_fallback" = "Buy **Stars** to unlock content and services on Telegram.";
|
||||
"lng_credits_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars.";
|
||||
"lng_credits_enough" = "You have enough stars at the moment. {link}";
|
||||
@@ -2960,6 +3004,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_gift_link_reason_unclaimed" = "Incomplete Giveaway";
|
||||
"lng_gift_link_reason_chosen" = "You were selected by the channel";
|
||||
"lng_gift_link_label_date" = "Date";
|
||||
"lng_gift_link_label_first_sale" = "First Sale";
|
||||
"lng_gift_link_label_last_sale" = "Last Sale";
|
||||
"lng_gift_link_label_value" = "Value";
|
||||
"lng_gift_link_also_send" = "You can also {link} to a friend as a gift.";
|
||||
"lng_gift_link_also_send_link" = "send this link";
|
||||
"lng_gift_link_use" = "Use Link";
|
||||
@@ -2980,6 +3027,59 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_gift_stars_incoming" = "Use Stars to unlock content and services on Telegram.";
|
||||
"lng_gift_until" = "Until";
|
||||
|
||||
"lng_gift_premium_or_stars" = "Gift Premium or Stars";
|
||||
"lng_gift_premium_subtitle" = "Gift Premium";
|
||||
"lng_gift_premium_about" = "Give {name} access to exclusive features with Telegram Premium. {features}";
|
||||
"lng_gift_premium_features" = "See Features >";
|
||||
"lng_gift_premium_label" = "Premium";
|
||||
"lng_gift_stars_subtitle" = "Gift Stars";
|
||||
"lng_gift_stars_about" = "Give {name} gifts that can be kept on your profile or converted to Stars. {link}";
|
||||
"lng_gift_stars_link" = "What are Stars >";
|
||||
"lng_gift_stars_limited" = "limited";
|
||||
"lng_gift_stars_sold_out" = "sold out";
|
||||
"lng_gift_stars_tabs_all" = "All Gifts";
|
||||
"lng_gift_stars_tabs_limited" = "Limited";
|
||||
"lng_gift_send_title" = "Send a Gift";
|
||||
"lng_gift_send_message" = "Enter Message";
|
||||
"lng_gift_send_anonymous" = "Hide My Name";
|
||||
"lng_gift_send_anonymous_about" = "You can hide your name and message from visitors to {user}'s profile. {recipient} will still see your name and message.";
|
||||
"lng_gift_send_premium_about" = "Only {user} will see your message.";
|
||||
"lng_gift_send_button" = "Send a Gift for {cost}";
|
||||
"lng_gift_sent_title" = "Gift Sent!";
|
||||
"lng_gift_sent_about#one" = "You spent **{count}** Star from your balance.";
|
||||
"lng_gift_sent_about#other" = "You spent **{count}** Stars from your balance.";
|
||||
"lng_gift_limited_of_one" = "unique";
|
||||
"lng_gift_limited_of_count" = "1 of {amount}";
|
||||
"lng_gift_anonymous_hint" = "Only you can see the sender's name.";
|
||||
"lng_gift_hidden_hint" = "This gift is hidden. Only you can see it.";
|
||||
"lng_gift_visible_hint" = "This gift is visible to visitors of your page.";
|
||||
"lng_gift_availability" = "Availability";
|
||||
"lng_gift_from_hidden" = "Hidden User";
|
||||
"lng_gift_availability_left#one" = "{count} of {amount} left";
|
||||
"lng_gift_availability_left#other" = "{count} of {amount} left";
|
||||
"lng_gift_availability_none" = "None of {amount} left";
|
||||
"lng_gift_display_on_page" = "Display on my Page";
|
||||
"lng_gift_display_on_page_hide" = "Hide from my Page";
|
||||
"lng_gift_convert_to_stars#one" = "Convert to {count} Star";
|
||||
"lng_gift_convert_to_stars#other" = "Convert to {count} Stars";
|
||||
"lng_gift_convert_sure_title" = "Convert Gift to Stars";
|
||||
"lng_gift_convert_sure_confirm#one" = "Do you want to convert this gift from {user} to **{count} Star**?";
|
||||
"lng_gift_convert_sure_confirm#other" = "Do you want to convert this gift from {user} to **{count} Stars**?";
|
||||
"lng_gift_convert_sure_limit#one" = "Conversion is available for the next **{count} day**.";
|
||||
"lng_gift_convert_sure_limit#other" = "Conversion is available for the next **{count} days**.";
|
||||
"lng_gift_convert_sure_caution" = "This action cannot be undone. This will permanently destroy the gift.";
|
||||
"lng_gift_convert_sure" = "Convert";
|
||||
"lng_gift_display_done" = "The gift is now shown on your profile page.";
|
||||
"lng_gift_display_done_hide" = "The gift is now hidden from your profile page.";
|
||||
"lng_gift_got_stars#one" = "You got **{count} Star** for this gift.";
|
||||
"lng_gift_got_stars#other" = "You got **{count} Stars** for this gift.";
|
||||
"lng_gift_sold_out_title" = "Sold Out!";
|
||||
"lng_gift_sold_out_text#one" = "All {count} gift was already sold.";
|
||||
"lng_gift_sold_out_text#other" = "All {count} gifts were already sold.";
|
||||
"lng_gift_send_small" = "send a gift";
|
||||
"lng_gift_sell_small#one" = "sell for {count} Star";
|
||||
"lng_gift_sell_small#other" = "sell for {count} Stars";
|
||||
|
||||
"lng_accounts_limit_title" = "Limit Reached";
|
||||
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account.";
|
||||
"lng_accounts_limit1#other" = "You have reached the limit of **{count}** connected accounts.";
|
||||
@@ -3168,11 +3268,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_record_cancel" = "Release outside this field to cancel";
|
||||
"lng_record_cancel_stories" = "Release outside to cancel";
|
||||
"lng_record_lock_cancel_sure" = "Do you want to stop recording and discard your voice message?";
|
||||
"lng_record_lock_cancel_sure_round" = "Do you want to stop recording and discard your video message?";
|
||||
"lng_record_listen_cancel_sure" = "Do you want to discard your recorded voice message?";
|
||||
"lng_record_listen_cancel_sure_round" = "Do you want to discard your recorded video message?";
|
||||
"lng_record_lock_discard" = "Discard";
|
||||
"lng_record_hold_tip" = "Please hold the mouse button pressed to record a voice message.";
|
||||
"lng_record_voice_tip" = "Hold to record audio. Click to switch to video.";
|
||||
"lng_record_video_tip" = "Hold to record video. Click to switch to audio.";
|
||||
"lng_record_audio_problem" = "Could not start audio recording. Please check your microphone.";
|
||||
"lng_record_video_problem" = "Could not start video recording. Please check your camera.";
|
||||
"lng_record_once_first_tooltip" = "Click to set this message to **Play Once**.";
|
||||
"lng_record_once_active_tooltip" = "The recipient will be able to listen only once.";
|
||||
"lng_record_once_active_video" = "The recipient will be able to watch only once.";
|
||||
"lng_will_be_notified" = "Subscribers will be notified when you post.";
|
||||
"lng_wont_be_notified" = "Subscribers will receive a silent notification.";
|
||||
"lng_willbe_history" = "Select a chat to start messaging";
|
||||
@@ -3201,6 +3308,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_scheduled_send_now" = "Send message now?";
|
||||
"lng_scheduled_send_now_many#one" = "Send {count} message now?";
|
||||
"lng_scheduled_send_now_many#other" = "Send {count} messages now?";
|
||||
"lng_scheduled_video_tip_title" = "Improving video...";
|
||||
"lng_scheduled_video_tip_text" = "The video will be published after it's optimized for the best viewing experience.";
|
||||
"lng_scheduled_video_tip" = "Processing video may take a few minutes.";
|
||||
"lng_scheduled_video_published" = "Video Published.";
|
||||
"lng_scheduled_video_view" = "View";
|
||||
|
||||
"lng_replies_view#one" = "View {count} Reply";
|
||||
"lng_replies_view#other" = "View {count} Replies";
|
||||
@@ -3222,6 +3334,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_replies_discussion_started" = "Discussion started";
|
||||
"lng_replies_no_comments" = "No comments here yet...";
|
||||
|
||||
"lng_verification_codes" = "Verification Codes";
|
||||
|
||||
"lng_archived_name" = "Archived chats";
|
||||
"lng_archived_add" = "Archive";
|
||||
"lng_archived_remove" = "Unarchive";
|
||||
@@ -3246,7 +3360,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_open_link" = "Open";
|
||||
"lng_allow_bot_pass" = "Allow {bot_name} to pass your Telegram name and ID (not your phone number) to the web pages you open via this bot?";
|
||||
"lng_allow_bot" = "Allow";
|
||||
"lng_allow_bot_webview" = "{bot_name} would like to open its web app to proceed.\n\nIt will be able to access your **IP address** and basic device info.";
|
||||
"lng_allow_bot_webview_details" = "More about this bot {emoji}";
|
||||
"lng_allow_bot_webview_details_about" = "To launch this web app, you will connect to its website.\n\nIt will be able to access your **IP address** and basic device info.";
|
||||
"lng_url_auth_open_confirm" = "Do you want to open {link}?";
|
||||
"lng_url_auth_login_option" = "Log in to {domain} as {user}";
|
||||
"lng_url_auth_allow_messages" = "Allow {bot} to send me messages";
|
||||
@@ -3281,6 +3396,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_bot_settings" = "Settings";
|
||||
"lng_bot_open" = "Open Bot";
|
||||
"lng_bot_terms" = "Terms of Use";
|
||||
"lng_bot_privacy" = "Privacy Policy";
|
||||
"lng_bot_reload_page" = "Reload Page";
|
||||
"lng_bot_add_to_menu" = "{bot} asks your permission to be added as an option to your attachment menu so you can access it from any chat.";
|
||||
"lng_bot_add_to_menu_done" = "Bot added to the menu.";
|
||||
@@ -3498,11 +3614,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_context_animated_reactions_many#one" = "Reactions contain emoji from **{count} pack**.";
|
||||
"lng_context_animated_reactions_many#other" = "Reactions contain emoji from **{count} packs**.";
|
||||
|
||||
"lng_context_noforwards_info_channel" = "Copying and forwarding is not allowed in this channel.";
|
||||
"lng_context_noforwards_info_group" = "Copying and forwarding is not allowed in this group.";
|
||||
"lng_context_noforwards_info_bot" = "Copying and forwarding is not allowed from this bot.";
|
||||
|
||||
"lng_context_spoiler_effect" = "Hide with Spoiler";
|
||||
"lng_context_disable_spoiler" = "Remove Spoiler";
|
||||
"lng_context_make_paid" = "Make This Content Paid";
|
||||
"lng_context_change_price" = "Change Price";
|
||||
|
||||
"lng_context_mention" = "Mention";
|
||||
"lng_context_search_from" = "Search messages";
|
||||
|
||||
"lng_factcheck_title" = "Fact Check";
|
||||
"lng_factcheck_placeholder" = "Add Facts or Context";
|
||||
"lng_factcheck_whats_this" = "what's this?";
|
||||
@@ -3600,6 +3723,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
"lng_reply_in_another_title" = "Reply in...";
|
||||
"lng_reply_in_another_chat" = "Reply in Another Chat";
|
||||
"lng_reply_in_author" = "Message author";
|
||||
"lng_reply_in_chats_list" = "Your chats";
|
||||
"lng_reply_show_in_chat" = "Show in Chat";
|
||||
"lng_reply_remove" = "Do Not Reply";
|
||||
"lng_reply_about_quote" = "You can select a specific part to quote.";
|
||||
@@ -3608,6 +3733,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_reply_header_short" = "Reply";
|
||||
"lng_reply_quote_selected" = "Quote Selected";
|
||||
"lng_reply_from_private_chat" = "This reply is from a private chat.";
|
||||
"lng_reply_quote_long_title" = "Quote too long!";
|
||||
"lng_reply_quote_long_text" = "The selected text is too long to quote.";
|
||||
"lng_link_options_header" = "Link Preview Settings";
|
||||
"lng_link_header_short" = "Link";
|
||||
"lng_link_move_up" = "Move Up";
|
||||
@@ -3785,6 +3912,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_mediaview_downloads" = "Downloads";
|
||||
"lng_mediaview_playback_speed" = "Playback speed: {speed}";
|
||||
"lng_mediaview_rotate_video" = "Rotate video";
|
||||
"lng_mediaview_quality_auto" = "Auto";
|
||||
|
||||
"lng_theme_preview_title" = "Theme Preview";
|
||||
"lng_theme_preview_generating" = "Generating color theme preview...";
|
||||
@@ -3866,7 +3994,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
"lng_payments_webview_no_use" = "Unfortunately, you can't use payments with current system configuration.";
|
||||
"lng_payments_webview_install_edge" = "Please install {link}.";
|
||||
"lng_payments_webview_install_webkit" = "Please install WebKitGTK (webkitgtk-6.0/webkit2gtk-4.1/webkit2gtk-4.0) using your package manager.";
|
||||
"lng_payments_webview_install_webkit" = "Please install WebKitGTK (webkit2gtk-4.1/webkit2gtk-4.0) using your package manager.";
|
||||
"lng_payments_webview_enable_opengl" = "Please enable OpenGL in application settings.";
|
||||
"lng_payments_webview_switch_x11" = "Unsupported display server. Please switch to X11.";
|
||||
"lng_payments_webview_update_windows" = "Please update your system to Windows 8.1 or later.";
|
||||
"lng_payments_sure_close" = "Are you sure you want to close this payment form? The changes you made will be lost.";
|
||||
"lng_payments_receipt_label" = "Receipt";
|
||||
@@ -4233,6 +4363,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_rights_restriction_for_all" = "This option is disabled for all members in Group Permissions.";
|
||||
"lng_rights_permission_for_all" = "This option is enabled for all members in Group Permissions.";
|
||||
"lng_rights_permission_unavailable" = "This permission is not available in public groups.";
|
||||
"lng_rights_permission_in_discuss" = "This permission is not available in discussion groups.";
|
||||
"lng_rights_permission_cant_edit" = "You cannot change this permission.";
|
||||
"lng_rights_user_restrictions" = "User permissions";
|
||||
"lng_rights_user_restrictions_header" = "What can this member do?";
|
||||
@@ -5087,13 +5218,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_sponsored_revenued_subtitle" = "Telegram Ads are very different from ads on other platforms. Ads such as this one:";
|
||||
"lng_sponsored_revenued_info1_title" = "Respect Your Privacy";
|
||||
"lng_sponsored_revenued_info1_description" = "Ads on Telegram do not use your personal information and are based on the channel in which you see them.";
|
||||
"lng_sponsored_revenued_info1_bot_description" = "Ads on Telegram do not use your personal information and are based on the mini app in which you see them.";
|
||||
"lng_sponsored_revenued_info2_title" = "Help the Channel Creator";
|
||||
"lng_sponsored_revenued_info2_bot_title" = "Help the Bot Developer";
|
||||
"lng_sponsored_revenued_info2_description" = "50% of the revenue from Telegram Ads goes to the owner of the channel where they are displayed.";
|
||||
"lng_sponsored_revenued_info2_bot_description" = "50% of the revenue from Telegram Ads goes to the developer of the mini app where they are displayed.";
|
||||
"lng_sponsored_revenued_info3_title" = "Can Be Removed";
|
||||
"lng_sponsored_revenued_info3_description#one" = "You can turn off ads by subscribing to {link}, and Level {count} channels can remove them for their subscribers.";
|
||||
"lng_sponsored_revenued_info3_description#other" = "You can turn off ads by subscribing to {link}, and Level {count} channels can remove them for their subscribers.";
|
||||
"lng_sponsored_revenued_info3_bot_description" = "You can turn off ads in mini apps by subscribing to {link}.";
|
||||
"lng_sponsored_revenued_footer_title" = "Can I Launch an Ad?";
|
||||
"lng_sponsored_revenued_footer_description" = "Anyone can create an ad to display in this channel — with minimal budgets. Check out the **Telegram Ad Platform** for details. {link}";
|
||||
"lng_sponsored_revenued_footer_bot_description" = "Anyone can create an ad to display in this bot — with minimal budgets. Check out the **Telegram Ad Platform** for details. {link}";
|
||||
"lng_sponsored_top_bar_hide" = "remove";
|
||||
|
||||
"lng_telegram_features_url" = "https://t.me/TelegramTips";
|
||||
|
||||
@@ -5397,6 +5534,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
"lng_channel_earn_title" = "Monetization";
|
||||
"lng_channel_earn_about" = "Telegram shares 50% of the revenue from ads displayed in your channel as rewards. {link}";
|
||||
"lng_channel_earn_about_bot" = "Telegram shares 50% of the revenue from ads displayed in your bot. {link}";
|
||||
"lng_channel_earn_about_link" = "Learn more {emoji}";
|
||||
"lng_channel_earn_overview_title" = "Rewards overview";
|
||||
"lng_channel_earn_available" = "Rewards available for collection";
|
||||
@@ -5429,8 +5567,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_channel_earn_cpm#one" = "{emoji} {count} CPM";
|
||||
"lng_channel_earn_cpm#other" = "{emoji} {count} CPM";
|
||||
"lng_channel_earn_learn_title" = "Earn From Your Channel";
|
||||
"lng_channel_earn_bot_learn_title" = "Earn From Your Bot";
|
||||
"lng_channel_earn_learn_in_subtitle" = "Telegram Ads";
|
||||
"lng_channel_earn_learn_in_about" = "Telegram can display ads in your channel.";
|
||||
"lng_channel_earn_learn_bot_in_about" = "Telegram can display ads in your bot.";
|
||||
"lng_channel_earn_learn_split_subtitle" = "50:50 revenue split";
|
||||
"lng_channel_earn_learn_split_about" = "You can receive 50% of the ad revenue as rewards in TON.";
|
||||
"lng_channel_earn_learn_out_subtitle" = "Flexible withdrawals";
|
||||
@@ -5467,6 +5607,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_bot_earn_credits_out_minimal" = "You cannot withdraw less than {link}.";
|
||||
"lng_bot_earn_credits_out_minimal_link#one" = "{count} star";
|
||||
"lng_bot_earn_credits_out_minimal_link#other" = "{count} stars";
|
||||
"lng_bot_copy_text_tooltip" = "Copy to Clipboard: {text}";
|
||||
|
||||
"lng_contact_add" = "Add";
|
||||
"lng_contact_send_message" = "Message";
|
||||
@@ -5477,6 +5618,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_iv_window_title" = "Instant View";
|
||||
"lng_iv_wrong_layout" = "Wrong layout?";
|
||||
"lng_iv_not_supported" = "This link appears to be invalid.";
|
||||
"lng_iv_zoom_tooltip_ctrl" = "Hold Ctrl to zoom by 5%.\nHold Alt to zoom by 1%.";
|
||||
"lng_iv_zoom_tooltip_cmd" = "Hold Cmd to zoom by 5%.\nHold Alt to zoom by 1%.";
|
||||
|
||||
"lng_limit_download_title" = "Download speed limited";
|
||||
"lng_limit_download_subscribe" = "Subscribe to {link} to increase download speed {increase}.";
|
||||
@@ -5514,6 +5657,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_channels_recommended" = "Similar channels";
|
||||
"lng_bot_apps_your" = "Apps you use";
|
||||
"lng_bot_apps_popular" = "Grossing apps";
|
||||
"lng_bot_apps_which" = "Which apps are included here? {link}";
|
||||
"lng_bot_apps_which_link" = "Learn >";
|
||||
|
||||
"lng_popular_apps_info_title" = "Top Mini Apps";
|
||||
"lng_popular_apps_info_text" = "This catalogue ranks mini apps based on their daily revenue, measured in Stars. To be listed, developers must set their main mini apps in {bot} (as described {link}), have over **1,000** daily users, and earn a daily revenue above **1,000** Stars, based on the weekly average.";
|
||||
"lng_popular_apps_info_bot" = "@botfather";
|
||||
"lng_popular_apps_info_here" = "here";
|
||||
"lng_popular_apps_info_url" = "https://core.telegram.org/bots/webapps#launching-the-main-mini-app";
|
||||
"lng_popular_apps_info_confirm" = "Understood";
|
||||
|
||||
"lng_font_box_title" = "Choose font family";
|
||||
"lng_font_default" = "Default";
|
||||
@@ -5550,6 +5702,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_qr_box_quality1" = "Normal";
|
||||
"lng_qr_box_quality2" = "High";
|
||||
"lng_qr_box_quality3" = "Very High";
|
||||
"lng_qr_box_transparent_background" = "Transparent Background";
|
||||
"lng_qr_box_font_size" = "Font size";
|
||||
|
||||
// Wnd specific
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<file alias="art/logo_256.png">../../art/logo_256.png</file>
|
||||
<file alias="art/logo_256_no_margin.png">../../art/logo_256_no_margin.png</file>
|
||||
<file alias="art/themeimage.jpg">../../art/themeimage.jpg</file>
|
||||
<file alias="art/round_placeholder.jpg">../../art/round_placeholder.jpg</file>
|
||||
<file alias="day-blue.tdesktop-theme">../../day-blue.tdesktop-theme</file>
|
||||
<file alias="night.tdesktop-theme">../../night.tdesktop-theme</file>
|
||||
<file alias="night-green.tdesktop-theme">../../night-green.tdesktop-theme</file>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="5.5.3.0" />
|
||||
Version="5.7.2.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
</compatibility>
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitor</dpiAwareness>
|
||||
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,5,3,0
|
||||
PRODUCTVERSION 5,5,3,0
|
||||
FILEVERSION 5,7,2,0
|
||||
PRODUCTVERSION 5,7,2,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.5.3.0"
|
||||
VALUE "FileVersion", "5.7.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.5.3.0"
|
||||
VALUE "ProductVersion", "5.7.2.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,5,3,0
|
||||
PRODUCTVERSION 5,5,3,0
|
||||
FILEVERSION 5,7,2,0
|
||||
PRODUCTVERSION 5,7,2,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.5.3.0"
|
||||
VALUE "FileVersion", "5.7.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.5.3.0"
|
||||
VALUE "ProductVersion", "5.7.2.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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "settings/settings_credits_graphics.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/controls/userpic_button.h"
|
||||
#include "ui/effects/credits_graphics.h"
|
||||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/effects/premium_stars_colored.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
@@ -129,6 +130,7 @@ void ConfirmSubscriptionBox(
|
||||
struct State final {
|
||||
std::shared_ptr<Data::PhotoMedia> photoMedia;
|
||||
std::unique_ptr<Ui::EmptyUserpic> photoEmpty;
|
||||
QImage frame;
|
||||
|
||||
std::optional<MTP::Sender> api;
|
||||
Ui::RpWidget* saveButton = nullptr;
|
||||
@@ -146,25 +148,45 @@ void ConfirmSubscriptionBox(
|
||||
const auto userpic = userpicWrap->entity();
|
||||
const auto photoSize = st::confirmInvitePhotoSize;
|
||||
userpic->resize(Size(photoSize));
|
||||
const auto creditsIconSize = photoSize / 3;
|
||||
const auto creditsIconCallback =
|
||||
Ui::PaintOutlinedColoredCreditsIconCallback(
|
||||
creditsIconSize,
|
||||
1.5);
|
||||
state->frame = QImage(
|
||||
Size(photoSize * style::DevicePixelRatio()),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
state->frame.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
const auto options = Images::Option::RoundCircle;
|
||||
userpic->paintRequest(
|
||||
) | rpl::start_with_next([=, small = Data::PhotoSize::Small] {
|
||||
auto p = QPainter(userpic);
|
||||
if (state->photoMedia) {
|
||||
if (const auto image = state->photoMedia->image(small)) {
|
||||
p.drawPixmap(
|
||||
state->frame.fill(Qt::transparent);
|
||||
{
|
||||
auto p = QPainter(&state->frame);
|
||||
if (state->photoMedia) {
|
||||
if (const auto image = state->photoMedia->image(small)) {
|
||||
p.drawPixmap(
|
||||
0,
|
||||
0,
|
||||
image->pix(Size(photoSize), { .options = options }));
|
||||
}
|
||||
} else if (state->photoEmpty) {
|
||||
state->photoEmpty->paintCircle(
|
||||
p,
|
||||
0,
|
||||
0,
|
||||
image->pix(Size(photoSize), { .options = options }));
|
||||
userpic->width(),
|
||||
photoSize);
|
||||
}
|
||||
if (creditsIconCallback) {
|
||||
p.translate(
|
||||
photoSize - creditsIconSize,
|
||||
photoSize - creditsIconSize);
|
||||
creditsIconCallback(p);
|
||||
}
|
||||
} else if (state->photoEmpty) {
|
||||
state->photoEmpty->paintCircle(
|
||||
p,
|
||||
0,
|
||||
0,
|
||||
userpic->width(),
|
||||
photoSize);
|
||||
}
|
||||
auto p = QPainter(userpic);
|
||||
p.drawImage(0, 0, state->frame);
|
||||
}, userpicWrap->lifetime());
|
||||
userpicWrap->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
if (photo) {
|
||||
@@ -275,7 +297,6 @@ void ConfirmSubscriptionBox(
|
||||
: 0;
|
||||
state->api->request(
|
||||
MTPpayments_SendStarsForm(
|
||||
MTP_flags(0),
|
||||
MTP_long(formId),
|
||||
MTP_inputInvoiceChatInviteSubscription(MTP_string(hash)))
|
||||
).done([=](const MTPpayments_PaymentResult &result) {
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_updates.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "data/components/credits.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_peer.h"
|
||||
@@ -38,8 +39,8 @@ constexpr auto kTransactionsLimit = 100;
|
||||
if (const auto list = tl.data().vextended_media()) {
|
||||
extended.reserve(list->v.size());
|
||||
for (const auto &media : list->v) {
|
||||
media.match([&](const MTPDmessageMediaPhoto &photo) {
|
||||
if (const auto inner = photo.vphoto()) {
|
||||
media.match([&](const MTPDmessageMediaPhoto &data) {
|
||||
if (const auto inner = data.vphoto()) {
|
||||
const auto photo = owner->processPhoto(*inner);
|
||||
if (!photo->isNull()) {
|
||||
extended.push_back(CreditsHistoryMedia{
|
||||
@@ -48,9 +49,11 @@ constexpr auto kTransactionsLimit = 100;
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [&](const MTPDmessageMediaDocument &document) {
|
||||
if (const auto inner = document.vdocument()) {
|
||||
const auto document = owner->processDocument(*inner);
|
||||
}, [&](const MTPDmessageMediaDocument &data) {
|
||||
if (const auto inner = data.vdocument()) {
|
||||
const auto document = owner->processDocument(
|
||||
*inner,
|
||||
data.valt_documents());
|
||||
if (document->isAnimation()
|
||||
|| document->isVideoFile()
|
||||
|| document->isGifv()) {
|
||||
@@ -69,18 +72,26 @@ constexpr auto kTransactionsLimit = 100;
|
||||
}, [](const auto &) {
|
||||
return PeerId(0);
|
||||
}).value;
|
||||
const auto stargift = tl.data().vstargift();
|
||||
const auto reaction = tl.data().is_reaction();
|
||||
const auto incoming = (int64(tl.data().vstars().v) >= 0);
|
||||
const auto saveActorId = (reaction || !extended.empty()) && incoming;
|
||||
return Data::CreditsHistoryEntry{
|
||||
.id = qs(tl.data().vid()),
|
||||
.title = qs(tl.data().vtitle().value_or_empty()),
|
||||
.description = qs(tl.data().vdescription().value_or_empty()),
|
||||
.description = { qs(tl.data().vdescription().value_or_empty()) },
|
||||
.date = base::unixtime::parse(tl.data().vdate().v),
|
||||
.photoId = photo ? photo->id : 0,
|
||||
.extended = std::move(extended),
|
||||
.credits = tl.data().vstars().v,
|
||||
.bareMsgId = uint64(tl.data().vmsg_id().value_or_empty()),
|
||||
.barePeerId = barePeerId,
|
||||
.barePeerId = saveActorId ? peer->id.value : barePeerId,
|
||||
.bareGiveawayMsgId = uint64(
|
||||
tl.data().vgiveaway_post_id().value_or_empty()),
|
||||
.bareGiftStickerId = (stargift
|
||||
? owner->processDocument(stargift->data().vsticker())->id
|
||||
: 0),
|
||||
.bareActorId = saveActorId ? barePeerId : uint64(0),
|
||||
.peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::Peer;
|
||||
}, [](const MTPDstarsTransactionPeerPlayMarket &) {
|
||||
@@ -95,6 +106,8 @@ constexpr auto kTransactionsLimit = 100;
|
||||
return Data::CreditsHistoryEntry::PeerType::PremiumBot;
|
||||
}, [](const MTPDstarsTransactionPeerAds &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::Ads;
|
||||
}, [](const MTPDstarsTransactionPeerAPI &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::API;
|
||||
}),
|
||||
.subscriptionUntil = tl.data().vsubscription_period()
|
||||
? base::unixtime::parse(base::unixtime::now()
|
||||
@@ -104,12 +117,17 @@ constexpr auto kTransactionsLimit = 100;
|
||||
? base::unixtime::parse(tl.data().vtransaction_date()->v)
|
||||
: QDateTime(),
|
||||
.successLink = qs(tl.data().vtransaction_url().value_or_empty()),
|
||||
.convertStars = int(stargift
|
||||
? stargift->data().vconvert_stars().v
|
||||
: 0),
|
||||
.floodSkip = int(tl.data().vfloodskip_number().value_or(0)),
|
||||
.converted = stargift && incoming,
|
||||
.reaction = tl.data().is_reaction(),
|
||||
.refunded = tl.data().is_refund(),
|
||||
.pending = tl.data().is_pending(),
|
||||
.failed = tl.data().is_failed(),
|
||||
.in = (int64(tl.data().vstars().v) >= 0),
|
||||
.gift = tl.data().is_gift(),
|
||||
.in = incoming,
|
||||
.gift = tl.data().is_gift() || stargift.has_value(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -239,6 +257,8 @@ void CreditsStatus::request(
|
||||
_peer->isSelf() ? MTP_inputPeerSelf() : _peer->input
|
||||
)).done([=](const TLResult &result) {
|
||||
_requestId = 0;
|
||||
const auto balance = result.data().vbalance().v;
|
||||
_peer->session().credits().apply(_peer->id, balance);
|
||||
if (const auto onstack = done) {
|
||||
onstack(StatusFromTL(result, _peer));
|
||||
}
|
||||
|
||||
@@ -99,8 +99,8 @@ public:
|
||||
[[nodiscard]] Data::CreditsEarnStatistics data() const;
|
||||
|
||||
private:
|
||||
const bool _isUser = false;
|
||||
Data::CreditsEarnStatistics _data;
|
||||
bool _isUser = false;
|
||||
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
|
||||
@@ -89,12 +89,15 @@ void HandleWithdrawalButton(
|
||||
}
|
||||
};
|
||||
const auto fail = [=](const MTP::Error &error) {
|
||||
show->showToast(error.type());
|
||||
const auto message = error.type();
|
||||
if (box && !box->handleCustomCheckError(message)) {
|
||||
show->showToast(message);
|
||||
}
|
||||
};
|
||||
if (channel) {
|
||||
session->api().request(
|
||||
MTPstats_GetBroadcastRevenueWithdrawalUrl(
|
||||
channel->inputChannel,
|
||||
channel->input,
|
||||
result.result
|
||||
)).done([=](const ChannelOutUrl &r) {
|
||||
done(qs(r.data().vurl()));
|
||||
@@ -134,7 +137,7 @@ void HandleWithdrawalButton(
|
||||
if (channel) {
|
||||
session->api().request(
|
||||
MTPstats_GetBroadcastRevenueWithdrawalUrl(
|
||||
channel->inputChannel,
|
||||
channel->input,
|
||||
MTP_inputCheckPasswordEmpty()
|
||||
)).fail(fail).send();
|
||||
} else if (peer) {
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -550,6 +550,24 @@ Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice(
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<GiftOptionData> PremiumGiftCodeOptions::optionsForPeer() const {
|
||||
auto result = std::vector<GiftOptionData>();
|
||||
|
||||
if (!_optionsForOnePerson.currency.isEmpty()) {
|
||||
const auto count = int(_optionsForOnePerson.months.size());
|
||||
result.reserve(count);
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
Assert(i < _optionsForOnePerson.totalCosts.size());
|
||||
result.push_back({
|
||||
.cost = _optionsForOnePerson.totalCosts[i],
|
||||
.currency = _optionsForOnePerson.currency,
|
||||
.months = _optionsForOnePerson.months[i],
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Data::PremiumSubscriptionOptions PremiumGiftCodeOptions::options(int amount) {
|
||||
const auto it = _subscriptionOptions.find(amount);
|
||||
if (it != end(_subscriptionOptions)) {
|
||||
@@ -571,6 +589,41 @@ Data::PremiumSubscriptionOptions PremiumGiftCodeOptions::options(int amount) {
|
||||
}
|
||||
}
|
||||
|
||||
auto PremiumGiftCodeOptions::requestStarGifts()
|
||||
-> rpl::producer<rpl::no_value, QString> {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
_api.request(MTPpayments_GetStarGifts(
|
||||
MTP_int(0)
|
||||
)).done([=](const MTPpayments_StarGifts &result) {
|
||||
result.match([&](const MTPDpayments_starGifts &data) {
|
||||
_giftsHash = data.vhash().v;
|
||||
const auto &list = data.vgifts().v;
|
||||
const auto session = &_peer->session();
|
||||
auto gifts = std::vector<StarGift>();
|
||||
gifts.reserve(list.size());
|
||||
for (const auto &gift : list) {
|
||||
if (auto parsed = FromTL(session, gift)) {
|
||||
gifts.push_back(std::move(*parsed));
|
||||
}
|
||||
}
|
||||
_gifts = std::move(gifts);
|
||||
}, [&](const MTPDpayments_starGiftsNotModified &) {
|
||||
});
|
||||
consumer.put_done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
const std::vector<StarGift> &PremiumGiftCodeOptions::starGifts() const {
|
||||
return _gifts;
|
||||
}
|
||||
|
||||
int PremiumGiftCodeOptions::giveawayBoostsPerPremium() const {
|
||||
constexpr auto kFallbackCount = 4;
|
||||
return _peer->session().appConfig().get<int>(
|
||||
@@ -705,4 +758,58 @@ rpl::producer<DocumentData*> RandomHelloStickerValue(
|
||||
}) | rpl::take(1) | rpl::map(random));
|
||||
}
|
||||
|
||||
std::optional<StarGift> FromTL(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPstarGift &gift) {
|
||||
const auto &data = gift.data();
|
||||
const auto document = session->data().processDocument(
|
||||
data.vsticker());
|
||||
const auto remaining = data.vavailability_remains();
|
||||
const auto total = data.vavailability_total();
|
||||
if (!document->sticker()) {
|
||||
return {};
|
||||
}
|
||||
return StarGift{
|
||||
.id = uint64(data.vid().v),
|
||||
.stars = int64(data.vstars().v),
|
||||
.convertStars = int64(data.vconvert_stars().v),
|
||||
.document = document,
|
||||
.limitedLeft = remaining.value_or_empty(),
|
||||
.limitedCount = total.value_or_empty(),
|
||||
.firstSaleDate = data.vfirst_sale_date().value_or_empty(),
|
||||
.lastSaleDate = data.vlast_sale_date().value_or_empty(),
|
||||
};
|
||||
}
|
||||
|
||||
std::optional<UserStarGift> FromTL(
|
||||
not_null<UserData*> to,
|
||||
const MTPuserStarGift &gift) {
|
||||
const auto session = &to->session();
|
||||
const auto &data = gift.data();
|
||||
auto parsed = FromTL(session, data.vgift());
|
||||
if (!parsed) {
|
||||
return {};
|
||||
}
|
||||
return UserStarGift{
|
||||
.info = std::move(*parsed),
|
||||
.message = (data.vmessage()
|
||||
? TextWithEntities{
|
||||
.text = qs(data.vmessage()->data().vtext()),
|
||||
.entities = Api::EntitiesFromMTP(
|
||||
session,
|
||||
data.vmessage()->data().ventities().v),
|
||||
}
|
||||
: TextWithEntities()),
|
||||
.convertStars = int64(data.vconvert_stars().value_or_empty()),
|
||||
.fromId = (data.vfrom_id()
|
||||
? peerFromUser(data.vfrom_id()->v)
|
||||
: PeerId()),
|
||||
.messageId = data.vmsg_id().value_or_empty(),
|
||||
.date = data.vdate().v,
|
||||
.anonymous = data.is_name_hidden(),
|
||||
.hidden = data.is_unsaved(),
|
||||
.mine = to->isSelf(),
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -67,6 +67,39 @@ struct GiveawayInfo {
|
||||
}
|
||||
};
|
||||
|
||||
struct GiftOptionData {
|
||||
int64 cost = 0;
|
||||
QString currency;
|
||||
int months = 0;
|
||||
};
|
||||
|
||||
struct StarGift {
|
||||
uint64 id = 0;
|
||||
int64 stars = 0;
|
||||
int64 convertStars = 0;
|
||||
not_null<DocumentData*> document;
|
||||
int limitedLeft = 0;
|
||||
int limitedCount = 0;
|
||||
TimeId firstSaleDate = 0;
|
||||
TimeId lastSaleDate = 0;
|
||||
|
||||
friend inline bool operator==(
|
||||
const StarGift &,
|
||||
const StarGift &) = default;
|
||||
};
|
||||
|
||||
struct UserStarGift {
|
||||
StarGift info;
|
||||
TextWithEntities message;
|
||||
int64 convertStars = 0;
|
||||
PeerId fromId = 0;
|
||||
MsgId messageId = 0;
|
||||
TimeId date = 0;
|
||||
bool anonymous = false;
|
||||
bool hidden = false;
|
||||
bool mine = false;
|
||||
};
|
||||
|
||||
class Premium final {
|
||||
public:
|
||||
explicit Premium(not_null<ApiWrap*> api);
|
||||
@@ -171,6 +204,7 @@ public:
|
||||
PremiumGiftCodeOptions(not_null<PeerData*> peer);
|
||||
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> request();
|
||||
[[nodiscard]] std::vector<GiftOptionData> optionsForPeer() const;
|
||||
[[nodiscard]] Data::PremiumSubscriptionOptions options(int amount);
|
||||
[[nodiscard]] const std::vector<int> &availablePresets() const;
|
||||
[[nodiscard]] int monthsFromPreset(int monthsIndex);
|
||||
@@ -187,6 +221,9 @@ public:
|
||||
[[nodiscard]] int giveawayPeriodMax() const;
|
||||
[[nodiscard]] bool giveawayGiftsPurchaseAvailable() const;
|
||||
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> requestStarGifts();
|
||||
[[nodiscard]] const std::vector<StarGift> &starGifts() const;
|
||||
|
||||
private:
|
||||
struct Token final {
|
||||
int users = 0;
|
||||
@@ -206,7 +243,7 @@ private:
|
||||
base::flat_map<Amount, PremiumSubscriptionOptions> _subscriptionOptions;
|
||||
struct {
|
||||
std::vector<int> months;
|
||||
std::vector<float64> totalCosts;
|
||||
std::vector<int64> totalCosts;
|
||||
QString currency;
|
||||
} _optionsForOnePerson;
|
||||
|
||||
@@ -214,6 +251,9 @@ private:
|
||||
|
||||
base::flat_map<Token, Store> _stores;
|
||||
|
||||
int32 _giftsHash = 0;
|
||||
std::vector<StarGift> _gifts;
|
||||
|
||||
MTP::Sender _api;
|
||||
|
||||
};
|
||||
@@ -242,4 +282,11 @@ enum class RequirePremiumState {
|
||||
[[nodiscard]] rpl::producer<DocumentData*> RandomHelloStickerValue(
|
||||
not_null<Main::Session*> session);
|
||||
|
||||
[[nodiscard]] std::optional<StarGift> FromTL(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPstarGift &gift);
|
||||
[[nodiscard]] std::optional<UserStarGift> FromTL(
|
||||
not_null<UserData*> to,
|
||||
const MTPuserStarGift &gift);
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -10,10 +10,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_report.h"
|
||||
#include "data/data_user.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/boxes/report_box.h"
|
||||
#include "ui/boxes/report_box_graphics.h"
|
||||
#include "ui/layers/show.h"
|
||||
|
||||
namespace Api {
|
||||
@@ -39,52 +40,106 @@ MTPreportReason ReasonToTL(const Ui::ReportReason &reason) {
|
||||
|
||||
} // namespace
|
||||
|
||||
void SendReport(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
Ui::ReportReason reason,
|
||||
const QString &comment,
|
||||
std::variant<
|
||||
v::null_t,
|
||||
MessageIdsList,
|
||||
not_null<PhotoData*>,
|
||||
StoryId> data) {
|
||||
auto done = [=] {
|
||||
void SendPhotoReport(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
Ui::ReportReason reason,
|
||||
const QString &comment,
|
||||
not_null<PhotoData*> photo) {
|
||||
peer->session().api().request(MTPaccount_ReportProfilePhoto(
|
||||
peer->input,
|
||||
photo->mtpInput(),
|
||||
ReasonToTL(reason),
|
||||
MTP_string(comment)
|
||||
)).done([=] {
|
||||
show->showToast(tr::lng_report_thanks(tr::now));
|
||||
};
|
||||
v::match(data, [&](v::null_t) {
|
||||
peer->session().api().request(MTPaccount_ReportPeer(
|
||||
peer->input,
|
||||
ReasonToTL(reason),
|
||||
MTP_string(comment)
|
||||
)).done(std::move(done)).send();
|
||||
}, [&](const MessageIdsList &ids) {
|
||||
auto apiIds = QVector<MTPint>();
|
||||
apiIds.reserve(ids.size());
|
||||
for (const auto &fullId : ids) {
|
||||
apiIds.push_back(MTP_int(fullId.msg));
|
||||
}).send();
|
||||
}
|
||||
|
||||
auto CreateReportMessagesOrStoriesCallback(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<PeerData*> peer)
|
||||
-> Fn<void(Data::ReportInput, Fn<void(ReportResult)>)> {
|
||||
using TLChoose = MTPDreportResultChooseOption;
|
||||
using TLAddComment = MTPDreportResultAddComment;
|
||||
using TLReported = MTPDreportResultReported;
|
||||
using Result = ReportResult;
|
||||
|
||||
struct State final {
|
||||
#ifdef _DEBUG
|
||||
~State() {
|
||||
qDebug() << "Messages or Stories Report ~State().";
|
||||
}
|
||||
peer->session().api().request(MTPmessages_Report(
|
||||
peer->input,
|
||||
MTP_vector<MTPint>(apiIds),
|
||||
ReasonToTL(reason),
|
||||
MTP_string(comment)
|
||||
)).done(std::move(done)).send();
|
||||
}, [&](not_null<PhotoData*> photo) {
|
||||
peer->session().api().request(MTPaccount_ReportProfilePhoto(
|
||||
peer->input,
|
||||
photo->mtpInput(),
|
||||
ReasonToTL(reason),
|
||||
MTP_string(comment)
|
||||
)).done(std::move(done)).send();
|
||||
}, [&](StoryId id) {
|
||||
peer->session().api().request(MTPstories_Report(
|
||||
peer->input,
|
||||
MTP_vector<MTPint>(1, MTP_int(id)),
|
||||
ReasonToTL(reason),
|
||||
MTP_string(comment)
|
||||
)).done(std::move(done)).send();
|
||||
});
|
||||
#endif
|
||||
mtpRequestId requestId = 0;
|
||||
};
|
||||
const auto state = std::make_shared<State>();
|
||||
|
||||
return [=](
|
||||
Data::ReportInput reportInput,
|
||||
Fn<void(Result)> done) {
|
||||
auto apiIds = QVector<MTPint>();
|
||||
apiIds.reserve(reportInput.ids.size() + reportInput.stories.size());
|
||||
for (const auto &id : reportInput.ids) {
|
||||
apiIds.push_back(MTP_int(id));
|
||||
}
|
||||
for (const auto &story : reportInput.stories) {
|
||||
apiIds.push_back(MTP_int(story));
|
||||
}
|
||||
|
||||
const auto received = [=](
|
||||
const MTPReportResult &result,
|
||||
mtpRequestId requestId) {
|
||||
if (state->requestId != requestId) {
|
||||
return;
|
||||
}
|
||||
state->requestId = 0;
|
||||
done(result.match([&](const TLChoose &data) {
|
||||
const auto t = qs(data.vtitle());
|
||||
auto list = Result::Options();
|
||||
list.reserve(data.voptions().v.size());
|
||||
for (const auto &tl : data.voptions().v) {
|
||||
list.emplace_back(Result::Option{
|
||||
.id = tl.data().voption().v,
|
||||
.text = qs(tl.data().vtext()),
|
||||
});
|
||||
}
|
||||
return Result{ .options = std::move(list), .title = t };
|
||||
}, [&](const TLAddComment &data) -> Result {
|
||||
return {
|
||||
.commentOption = ReportResult::CommentOption{
|
||||
.optional = data.is_optional(),
|
||||
.id = data.voption().v,
|
||||
}
|
||||
};
|
||||
}, [&](const TLReported &data) -> Result {
|
||||
return { .successful = true };
|
||||
}));
|
||||
};
|
||||
|
||||
const auto fail = [=](const MTP::Error &error) {
|
||||
state->requestId = 0;
|
||||
done({ .error = error.type() });
|
||||
};
|
||||
|
||||
if (!reportInput.stories.empty()) {
|
||||
state->requestId = peer->session().api().request(
|
||||
MTPstories_Report(
|
||||
peer->input,
|
||||
MTP_vector<MTPint>(apiIds),
|
||||
MTP_bytes(reportInput.optionId),
|
||||
MTP_string(reportInput.comment))
|
||||
).done(received).fail(fail).send();
|
||||
} else {
|
||||
state->requestId = peer->session().api().request(
|
||||
MTPmessages_Report(
|
||||
peer->input,
|
||||
MTP_vector<MTPint>(apiIds),
|
||||
MTP_bytes(reportInput.optionId),
|
||||
MTP_string(reportInput.comment))
|
||||
).done(received).fail(fail).send();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
class HistoryItem;
|
||||
class PeerData;
|
||||
class PhotoData;
|
||||
|
||||
@@ -15,17 +16,41 @@ class Show;
|
||||
enum class ReportReason;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Data {
|
||||
struct ReportInput;
|
||||
} // namespace Data
|
||||
|
||||
namespace Api {
|
||||
|
||||
void SendReport(
|
||||
struct ReportResult final {
|
||||
using Id = QByteArray;
|
||||
struct Option final {
|
||||
Id id = 0;
|
||||
QString text;
|
||||
};
|
||||
using Options = std::vector<Option>;
|
||||
Options options;
|
||||
QString title;
|
||||
QString error;
|
||||
QString comment;
|
||||
struct CommentOption {
|
||||
bool optional = false;
|
||||
Id id = 0;
|
||||
};
|
||||
std::optional<CommentOption> commentOption;
|
||||
bool successful = false;
|
||||
};
|
||||
|
||||
void SendPhotoReport(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
Ui::ReportReason reason,
|
||||
const QString &comment,
|
||||
std::variant<
|
||||
v::null_t,
|
||||
MessageIdsList,
|
||||
not_null<PhotoData*>,
|
||||
StoryId> data);
|
||||
not_null<PhotoData*> photo);
|
||||
|
||||
[[nodiscard]] auto CreateReportMessagesOrStoriesCallback(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<PeerData*> peer)
|
||||
-> Fn<void(Data::ReportInput, Fn<void(ReportResult)>)>;
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -456,6 +456,7 @@ void SendConfirmedFile(
|
||||
not_null<Main::Session*> session,
|
||||
const std::shared_ptr<FilePrepareResult> &file) {
|
||||
const auto isEditing = (file->type != SendMediaType::Audio)
|
||||
&& (file->type != SendMediaType::Round)
|
||||
&& (file->to.replaceMediaOf != 0);
|
||||
const auto newId = FullMsgId(
|
||||
file->to.peer,
|
||||
@@ -525,7 +526,8 @@ void SendConfirmedFile(
|
||||
// Shortcut messages have no 'edited' badge.
|
||||
flags |= MessageFlag::HideEdited;
|
||||
}
|
||||
if (file->type == SendMediaType::Audio) {
|
||||
if (file->type == SendMediaType::Audio
|
||||
|| file->type == SendMediaType::Round) {
|
||||
if (!peer->isChannel() || peer->isMegagroup()) {
|
||||
flags |= MessageFlag::MediaIsUnread;
|
||||
}
|
||||
@@ -547,32 +549,28 @@ 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;
|
||||
const auto isVoice = [&] {
|
||||
return file->document.match([](const MTPDdocumentEmpty &d) {
|
||||
return false;
|
||||
}, [](const MTPDdocument &d) {
|
||||
return ranges::any_of(d.vattributes().v, [&](
|
||||
const MTPDocumentAttribute &attribute) {
|
||||
using Att = MTPDdocumentAttributeAudio;
|
||||
return attribute.match([](const Att &data) -> bool {
|
||||
return data.vflags().v & Att::Flag::f_voice;
|
||||
}, [](const auto &) {
|
||||
return false;
|
||||
});
|
||||
});
|
||||
});
|
||||
}();
|
||||
using Flag = MTPDmessageMediaDocument::Flag;
|
||||
return MTP_messageMediaDocument(
|
||||
MTP_flags(Flag::f_document
|
||||
| (isVoice ? Flag::f_voice : Flag())
|
||||
| Flag::f_voice
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())),
|
||||
file->document,
|
||||
MTPDocument(), // alt_document
|
||||
MTPVector<MTPDocument>(), // alt_documents
|
||||
MTP_int(ttlSeconds));
|
||||
} else if (file->type == SendMediaType::Round) {
|
||||
using Flag = MTPDmessageMediaDocument::Flag;
|
||||
const auto ttlSeconds = file->to.options.ttlSeconds;
|
||||
return MTP_messageMediaDocument(
|
||||
MTP_flags(Flag::f_document
|
||||
| Flag::f_round
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())
|
||||
| (file->spoiler ? Flag::f_spoiler : Flag())),
|
||||
file->document,
|
||||
MTPVector<MTPDocument>(), // alt_documents
|
||||
MTP_int(ttlSeconds));
|
||||
} else {
|
||||
Unexpected("Type in sendFilesConfirmed.");
|
||||
|
||||
@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_stories.h"
|
||||
#include "data/data_story.h"
|
||||
#include "data/data_user.h"
|
||||
#include "history/history.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
@@ -341,6 +342,10 @@ void PublicForwards::request(
|
||||
.token = nextToken,
|
||||
});
|
||||
};
|
||||
const auto processFail = [=] {
|
||||
_requestId = 0;
|
||||
done({});
|
||||
};
|
||||
|
||||
constexpr auto kLimit = tl::make_int(100);
|
||||
if (_fullId.messageId) {
|
||||
@@ -349,14 +354,14 @@ void PublicForwards::request(
|
||||
MTP_int(_fullId.messageId.msg),
|
||||
MTP_string(token),
|
||||
kLimit
|
||||
)).done(processResult).fail([=] { _requestId = 0; }).send();
|
||||
)).done(processResult).fail(processFail).send();
|
||||
} else if (_fullId.storyId) {
|
||||
_requestId = makeRequest(MTPstats_GetStoryPublicForwards(
|
||||
channel->input,
|
||||
MTP_int(_fullId.storyId.story),
|
||||
MTP_string(token),
|
||||
kLimit
|
||||
)).done(processResult).fail([=] { _requestId = 0; }).send();
|
||||
)).done(processResult).fail(processFail).send();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,7 +386,7 @@ Data::PublicForwardsSlice MessageStatistics::firstSlice() const {
|
||||
}
|
||||
|
||||
void MessageStatistics::request(Fn<void(Data::MessageStatistics)> done) {
|
||||
if (channel()->isMegagroup()) {
|
||||
if (channel()->isMegagroup() && !_storyId) {
|
||||
return;
|
||||
}
|
||||
const auto requestFirstPublicForwards = [=](
|
||||
@@ -681,17 +686,18 @@ Data::BoostStatus Boosts::boostStatus() const {
|
||||
return _boostStatus;
|
||||
}
|
||||
|
||||
ChannelEarnStatistics::ChannelEarnStatistics(not_null<ChannelData*> channel)
|
||||
: StatisticsRequestSender(channel) {
|
||||
EarnStatistics::EarnStatistics(not_null<PeerData*> peer)
|
||||
: StatisticsRequestSender(peer)
|
||||
, _isUser(peer->isUser()) {
|
||||
}
|
||||
|
||||
rpl::producer<rpl::no_value, QString> ChannelEarnStatistics::request() {
|
||||
rpl::producer<rpl::no_value, QString> EarnStatistics::request() {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
makeRequest(MTPstats_GetBroadcastRevenueStats(
|
||||
MTP_flags(0),
|
||||
channel()->inputChannel
|
||||
(_isUser ? user()->input : channel()->input)
|
||||
)).done([=](const MTPstats_BroadcastRevenueStats &result) {
|
||||
const auto &data = result.data();
|
||||
const auto &balances = data.vbalances().data();
|
||||
@@ -708,18 +714,22 @@ rpl::producer<rpl::no_value, QString> ChannelEarnStatistics::request() {
|
||||
requestHistory({}, [=](Data::EarnHistorySlice &&slice) {
|
||||
_data.firstHistorySlice = std::move(slice);
|
||||
|
||||
api().request(
|
||||
MTPchannels_GetFullChannel(channel()->inputChannel)
|
||||
).done([=](const MTPmessages_ChatFull &result) {
|
||||
result.data().vfull_chat().match([&](
|
||||
const MTPDchannelFull &d) {
|
||||
_data.switchedOff = d.is_restricted_sponsored();
|
||||
}, [](const auto &) {
|
||||
});
|
||||
if (!_isUser) {
|
||||
api().request(
|
||||
MTPchannels_GetFullChannel(channel()->inputChannel)
|
||||
).done([=](const MTPmessages_ChatFull &result) {
|
||||
result.data().vfull_chat().match([&](
|
||||
const MTPDchannelFull &d) {
|
||||
_data.switchedOff = d.is_restricted_sponsored();
|
||||
}, [](const auto &) {
|
||||
});
|
||||
consumer.put_done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
} else {
|
||||
consumer.put_done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
}
|
||||
});
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
@@ -729,7 +739,7 @@ rpl::producer<rpl::no_value, QString> ChannelEarnStatistics::request() {
|
||||
};
|
||||
}
|
||||
|
||||
void ChannelEarnStatistics::requestHistory(
|
||||
void EarnStatistics::requestHistory(
|
||||
const Data::EarnHistorySlice::OffsetToken &token,
|
||||
Fn<void(Data::EarnHistorySlice)> done) {
|
||||
if (_requestId) {
|
||||
@@ -738,7 +748,7 @@ void ChannelEarnStatistics::requestHistory(
|
||||
constexpr auto kTlFirstSlice = tl::make_int(kFirstSlice);
|
||||
constexpr auto kTlLimit = tl::make_int(kLimit);
|
||||
_requestId = api().request(MTPstats_GetBroadcastRevenueTransactions(
|
||||
channel()->inputChannel,
|
||||
(_isUser ? user()->input : channel()->input),
|
||||
MTP_int(token),
|
||||
(!token) ? kTlFirstSlice : kTlLimit
|
||||
)).done([=](const MTPstats_BroadcastRevenueTransactions &result) {
|
||||
@@ -799,7 +809,7 @@ void ChannelEarnStatistics::requestHistory(
|
||||
}).send();
|
||||
}
|
||||
|
||||
Data::EarnStatistics ChannelEarnStatistics::data() const {
|
||||
Data::EarnStatistics EarnStatistics::data() const {
|
||||
return _data;
|
||||
}
|
||||
|
||||
|
||||
@@ -79,9 +79,9 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class ChannelEarnStatistics final : public StatisticsRequestSender {
|
||||
class EarnStatistics final : public StatisticsRequestSender {
|
||||
public:
|
||||
explicit ChannelEarnStatistics(not_null<ChannelData*> channel);
|
||||
explicit EarnStatistics(not_null<PeerData*> peer);
|
||||
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> request();
|
||||
void requestHistory(
|
||||
@@ -94,6 +94,7 @@ public:
|
||||
static constexpr auto kLimit = int(10);
|
||||
|
||||
private:
|
||||
const bool _isUser = false;
|
||||
Data::EarnStatistics _data;
|
||||
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
@@ -316,6 +316,9 @@ void Updates::feedUpdateVector(
|
||||
} else if (policy == SkipUpdatePolicy::SkipExceptGroupCallParticipants) {
|
||||
return;
|
||||
}
|
||||
if (policy == SkipUpdatePolicy::SkipNone) {
|
||||
applyConvertToScheduledOnSend(updates);
|
||||
}
|
||||
for (const auto &entry : std::as_const(list)) {
|
||||
const auto type = entry.type();
|
||||
if ((policy == SkipUpdatePolicy::SkipMessageIds
|
||||
@@ -329,6 +332,15 @@ void Updates::feedUpdateVector(
|
||||
session().data().sendHistoryChangeNotifications();
|
||||
}
|
||||
|
||||
void Updates::checkForSentToScheduled(const MTPUpdates &updates) {
|
||||
updates.match([&](const MTPDupdates &data) {
|
||||
applyConvertToScheduledOnSend(data.vupdates(), true);
|
||||
}, [&](const MTPDupdatesCombined &data) {
|
||||
applyConvertToScheduledOnSend(data.vupdates(), true);
|
||||
}, [](const auto &) {
|
||||
});
|
||||
}
|
||||
|
||||
void Updates::feedMessageIds(const MTPVector<MTPUpdate> &updates) {
|
||||
for (const auto &update : updates.v) {
|
||||
if (update.type() == mtpc_updateMessageID) {
|
||||
@@ -417,13 +429,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,6 +444,7 @@ void Updates::feedChannelDifference(
|
||||
session().data().processChats(data.vchats());
|
||||
|
||||
_handlingChannelDifference = true;
|
||||
applyConvertToScheduledOnSend(data.vother_updates());
|
||||
feedMessageIds(data.vother_updates());
|
||||
session().data().processMessages(
|
||||
data.vnew_messages(),
|
||||
@@ -597,6 +609,7 @@ void Updates::feedDifference(
|
||||
Core::App().checkAutoLock();
|
||||
session().data().processUsers(users);
|
||||
session().data().processChats(chats);
|
||||
applyConvertToScheduledOnSend(other);
|
||||
feedMessageIds(other);
|
||||
session().data().processMessages(msgs, NewMessageType::Unread);
|
||||
feedUpdateVector(other, SkipUpdatePolicy::SkipMessageIds);
|
||||
@@ -655,6 +668,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);
|
||||
}
|
||||
@@ -703,7 +717,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);
|
||||
@@ -740,16 +756,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());
|
||||
|
||||
@@ -863,6 +895,51 @@ void Updates::mtpUpdateReceived(const MTPUpdates &updates) {
|
||||
}
|
||||
}
|
||||
|
||||
void Updates::applyConvertToScheduledOnSend(
|
||||
const MTPVector<MTPUpdate> &other,
|
||||
bool skipScheduledCheck) {
|
||||
for (const auto &update : other.v) {
|
||||
update.match([&](const MTPDupdateNewScheduledMessage &data) {
|
||||
const auto &message = data.vmessage();
|
||||
const auto id = IdFromMessage(message);
|
||||
const auto scheduledMessages = &_session->scheduledMessages();
|
||||
const auto scheduledId = scheduledMessages->localMessageId(id);
|
||||
for (const auto &updateId : other.v) {
|
||||
updateId.match([&](const MTPDupdateMessageID &dataId) {
|
||||
if (dataId.vid().v == id) {
|
||||
auto &owner = session().data();
|
||||
if (skipScheduledCheck) {
|
||||
const auto peerId = PeerFromMessage(message);
|
||||
const auto history = owner.historyLoaded(peerId);
|
||||
if (history) {
|
||||
_session->data().sentToScheduled({
|
||||
.history = history,
|
||||
.scheduledId = scheduledId,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
const auto rand = dataId.vrandom_id().v;
|
||||
const auto localId = owner.messageIdByRandomId(rand);
|
||||
if (const auto local = owner.message(localId)) {
|
||||
if (!local->isScheduled()) {
|
||||
_session->data().sentToScheduled({
|
||||
.history = local->history(),
|
||||
.scheduledId = scheduledId,
|
||||
});
|
||||
|
||||
// We've sent a non-scheduled message,
|
||||
// but it was converted to a scheduled.
|
||||
local->destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [](const auto &) {});
|
||||
}
|
||||
}, [](const auto &) {});
|
||||
}
|
||||
}
|
||||
|
||||
void Updates::applyGroupCallParticipantUpdates(const MTPUpdates &updates) {
|
||||
updates.match([&](const MTPDupdates &data) {
|
||||
session().data().processUsers(data.vusers());
|
||||
@@ -1554,6 +1631,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);
|
||||
@@ -1646,6 +1724,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);
|
||||
@@ -1661,6 +1740,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);
|
||||
@@ -1775,6 +1855,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);
|
||||
@@ -1838,6 +1919,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);
|
||||
|
||||
@@ -40,6 +40,8 @@ public:
|
||||
void applyUpdatesNoPtsCheck(const MTPUpdates &updates);
|
||||
void applyUpdateNoPtsCheck(const MTPUpdate &update);
|
||||
|
||||
void checkForSentToScheduled(const MTPUpdates &updates);
|
||||
|
||||
[[nodiscard]] int32 pts() const;
|
||||
|
||||
void updateOnline(crl::time lastNonIdleTime = 0);
|
||||
@@ -63,6 +65,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 {
|
||||
@@ -130,6 +133,9 @@ private:
|
||||
// Doesn't call sendHistoryChangeNotifications itself.
|
||||
void feedUpdate(const MTPUpdate &update);
|
||||
|
||||
void applyConvertToScheduledOnSend(
|
||||
const MTPVector<MTPUpdate> &other,
|
||||
bool skipScheduledCheck = false);
|
||||
void applyGroupCallParticipantUpdates(const MTPUpdates &updates);
|
||||
|
||||
bool whenGetDiffChanged(
|
||||
|
||||
@@ -756,5 +756,19 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
const style::WhoRead &st) {
|
||||
return WhoReacted(item, reaction, context, st, nullptr);
|
||||
}
|
||||
rpl::producer<Ui::WhoReadContent> WhenEdited(
|
||||
not_null<PeerData*> author,
|
||||
TimeId date) {
|
||||
return rpl::single(Ui::WhoReadContent{
|
||||
.participants = { Ui::WhoReadParticipant{
|
||||
.name = author->name(),
|
||||
.date = FormatReadDate(date, QDateTime::currentDateTime()),
|
||||
.id = author->id.value,
|
||||
} },
|
||||
.type = Ui::WhoReadType::Edited,
|
||||
.fullReadCount = 1,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -61,5 +61,8 @@ struct WhoReadList {
|
||||
const Data::ReactionId &reaction,
|
||||
not_null<QWidget*> context, // Cache results for this lifetime.
|
||||
const style::WhoRead &st);
|
||||
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhenEdited(
|
||||
not_null<PeerData*> author,
|
||||
TimeId date);
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -708,7 +708,8 @@ void ApiWrap::finalizeMessageDataRequest(
|
||||
|
||||
QString ApiWrap::exportDirectMessageLink(
|
||||
not_null<HistoryItem*> item,
|
||||
bool inRepliesContext) {
|
||||
bool inRepliesContext,
|
||||
bool forceNonPublicLink) {
|
||||
Expects(item->history()->peer->isChannel());
|
||||
|
||||
const auto itemId = item->fullId();
|
||||
@@ -731,7 +732,7 @@ QString ApiWrap::exportDirectMessageLink(
|
||||
const auto sender = root
|
||||
? root->discussionPostOriginalSender()
|
||||
: nullptr;
|
||||
if (sender && sender->hasUsername()) {
|
||||
if (sender && sender->hasUsername() && !forceNonPublicLink) {
|
||||
// Comment to a public channel.
|
||||
const auto forwarded = root->Get<HistoryMessageForwarded>();
|
||||
linkItemId = forwarded->savedFromMsgId;
|
||||
@@ -747,7 +748,7 @@ QString ApiWrap::exportDirectMessageLink(
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto base = linkChannel->hasUsername()
|
||||
const auto base = (linkChannel->hasUsername() && !forceNonPublicLink)
|
||||
? linkChannel->username()
|
||||
: "c/" + QString::number(peerToChannel(linkChannel->id).bare);
|
||||
const auto post = QString::number(linkItemId.bare);
|
||||
@@ -761,6 +762,7 @@ QString ApiWrap::exportDirectMessageLink(
|
||||
? (QString::number(linkThreadId.bare) + '/' + post)
|
||||
: post);
|
||||
if (linkChannel->hasUsername()
|
||||
&& !forceNonPublicLink
|
||||
&& !linkChannel->isMegagroup()
|
||||
&& !linkCommentId
|
||||
&& !linkThreadId) {
|
||||
@@ -774,6 +776,9 @@ QString ApiWrap::exportDirectMessageLink(
|
||||
}
|
||||
return session().createInternalLinkFull(query);
|
||||
};
|
||||
if (forceNonPublicLink) {
|
||||
return fallback();
|
||||
}
|
||||
const auto i = _unlikelyMessageLinks.find(itemId);
|
||||
const auto current = (i != end(_unlikelyMessageLinks))
|
||||
? i->second
|
||||
@@ -2028,7 +2033,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()) {
|
||||
@@ -3329,6 +3334,7 @@ void ApiWrap::forwardMessages(
|
||||
}
|
||||
const auto requestType = Data::Histories::RequestType::Send;
|
||||
const auto idsCopy = localIds;
|
||||
const auto scheduled = action.options.scheduled;
|
||||
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
||||
history->sendRequestId = request(MTPmessages_ForwardMessages(
|
||||
MTP_flags(sendFlags),
|
||||
@@ -3341,6 +3347,9 @@ void ApiWrap::forwardMessages(
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, action.options.shortcutId)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
if (!scheduled) {
|
||||
this->updates().checkForSentToScheduled(result);
|
||||
}
|
||||
applyUpdates(result);
|
||||
if (shared && !--shared->requestsLeft) {
|
||||
shared->callback();
|
||||
@@ -3502,6 +3511,7 @@ void ApiWrap::sendVoiceMessage(
|
||||
QByteArray result,
|
||||
VoiceWaveform waveform,
|
||||
crl::time duration,
|
||||
bool video,
|
||||
const SendAction &action) {
|
||||
const auto caption = TextWithTags();
|
||||
const auto to = FileLoadTaskOptions(action);
|
||||
@@ -3510,6 +3520,7 @@ void ApiWrap::sendVoiceMessage(
|
||||
result,
|
||||
duration,
|
||||
waveform,
|
||||
video,
|
||||
to,
|
||||
caption));
|
||||
}
|
||||
@@ -4225,6 +4236,7 @@ void ApiWrap::sendMultiPaidMedia(
|
||||
auto &histories = history->owner().histories();
|
||||
const auto peer = history->peer;
|
||||
const auto itemId = item->fullId();
|
||||
album->sent = true;
|
||||
histories.sendPreparedMessage(
|
||||
history,
|
||||
replyTo,
|
||||
@@ -4296,6 +4308,9 @@ void ApiWrap::sendAlbumWithCancelled(
|
||||
}
|
||||
|
||||
void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
||||
if (album->sent) {
|
||||
return;
|
||||
}
|
||||
const auto groupId = album->groupId;
|
||||
if (album->items.empty()) {
|
||||
_sendingAlbums.remove(groupId);
|
||||
@@ -4320,6 +4335,7 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
||||
return;
|
||||
} else if (medias.size() < 2) {
|
||||
const auto &single = medias.front().data();
|
||||
album->sent = true;
|
||||
sendMediaWithRandomId(
|
||||
sample,
|
||||
single.vmedia(),
|
||||
@@ -4346,6 +4362,7 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
||||
| (album->options.invertCaption ? Flag::f_invert_media : Flag(0));
|
||||
auto &histories = history->owner().histories();
|
||||
const auto peer = history->peer;
|
||||
album->sent = true;
|
||||
histories.sendPreparedMessage(
|
||||
history,
|
||||
replyTo,
|
||||
|
||||
@@ -164,7 +164,8 @@ public:
|
||||
void requestMessageData(PeerData *peer, MsgId msgId, Fn<void()> done);
|
||||
QString exportDirectMessageLink(
|
||||
not_null<HistoryItem*> item,
|
||||
bool inRepliesContext);
|
||||
bool inRepliesContext,
|
||||
bool forceNonPublicLink = false);
|
||||
QString exportDirectStoryLink(not_null<Data::Story*> item);
|
||||
|
||||
void requestContacts();
|
||||
@@ -317,6 +318,7 @@ public:
|
||||
QByteArray result,
|
||||
VoiceWaveform waveform,
|
||||
crl::time duration,
|
||||
bool video,
|
||||
const SendAction &action);
|
||||
void sendFiles(
|
||||
Ui::PreparedList &&list,
|
||||
|
||||
@@ -154,9 +154,7 @@ contactsSortButton: IconButton(defaultIconButton) {
|
||||
iconPosition: point(10px, -1px);
|
||||
rippleAreaPosition: point(1px, 6px);
|
||||
rippleAreaSize: 42px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
contactsSortOnlineIcon: icon{{ "contacts_online", boxTitleCloseFg }};
|
||||
contactsSortOnlineIconOver: icon{{ "contacts_online", boxTitleCloseFgOver }};
|
||||
@@ -416,9 +414,7 @@ calendarPrevious: IconButton {
|
||||
|
||||
rippleAreaPosition: point(2px, 2px);
|
||||
rippleAreaSize: 44px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
calendarPreviousDisabled: icon {{ "calendar_down-flip_vertical", menuIconFg }};
|
||||
calendarNext: IconButton(calendarPrevious) {
|
||||
@@ -616,9 +612,7 @@ proxyTryIPv6Padding: margins(22px, 8px, 22px, 5px);
|
||||
proxyRowPadding: margins(22px, 8px, 8px, 8px);
|
||||
proxyRowIconSkip: 32px;
|
||||
proxyRowSkip: 2px;
|
||||
proxyRowRipple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
proxyRowRipple: defaultRippleAnimationBgOver;
|
||||
proxyRowTitleFg: windowFg;
|
||||
proxyRowTitlePalette: TextPalette(defaultTextPalette) {
|
||||
linkFg: windowSubTextFg;
|
||||
@@ -683,9 +677,7 @@ themesMenuToggle: IconButton(defaultIconButton) {
|
||||
|
||||
rippleAreaPosition: point(4px, 4px);
|
||||
rippleAreaSize: 36px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
themesMenuPosition: point(-2px, 25px);
|
||||
|
||||
@@ -738,9 +730,7 @@ createPollOptionRemove: CrossButton {
|
||||
|
||||
duration: 150;
|
||||
loadingPeriod: 1000;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
createPollOptionRemovePosition: point(11px, 9px);
|
||||
createPollOptionEmojiPositionSkip: 4px;
|
||||
@@ -888,6 +878,13 @@ peerListWithInviteViaLink: PeerList(peerListBox) {
|
||||
peerListSingleRow: PeerList(peerListBox) {
|
||||
padding: margins(0px, 0px, 0px, 0px);
|
||||
}
|
||||
peerListSmallSkips: PeerList(peerListBox) {
|
||||
padding: margins(
|
||||
0px,
|
||||
defaultVerticalListSkip,
|
||||
0px,
|
||||
defaultVerticalListSkip);
|
||||
}
|
||||
|
||||
scheduleHeight: 95px;
|
||||
scheduleDateTop: 38px;
|
||||
@@ -951,9 +948,7 @@ sponsoredUrlButton: RoundButton(defaultActiveButton) {
|
||||
textTop: 7px;
|
||||
style: defaultTextStyle;
|
||||
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
|
||||
requestPeerRestriction: FlatLabel(defaultFlatLabel) {
|
||||
|
||||
@@ -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"
|
||||
@@ -237,7 +238,7 @@ EditCaptionBox::EditCaptionBox(
|
||||
Fn<void()> saved)
|
||||
: _controller(controller)
|
||||
, _historyItem(item)
|
||||
, _isAllowedEditMedia(item->media() && item->media()->allowsEditMedia())
|
||||
, _isAllowedEditMedia(item->allowsEditMedia())
|
||||
, _albumType(ComputeAlbumType(item))
|
||||
, _controls(base::make_unique_q<Ui::VerticalLayout>(this))
|
||||
, _scroll(base::make_unique_q<Ui::ScrollArea>(this, st::boxScroll))
|
||||
@@ -252,8 +253,8 @@ EditCaptionBox::EditCaptionBox(
|
||||
, _initialText(std::move(text))
|
||||
, _initialList(std::move(list))
|
||||
, _saved(std::move(saved)) {
|
||||
Expects(item->media() != nullptr);
|
||||
Expects(item->media()->allowsEditCaption());
|
||||
Expects(!_initialList.files.empty());
|
||||
Expects(!item->media() || item->media()->allowsEditCaption());
|
||||
|
||||
_mediaEditManager.start(item, spoilered, invertCaption);
|
||||
|
||||
@@ -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(); });
|
||||
@@ -413,7 +422,8 @@ void EditCaptionBox::prepare() {
|
||||
setInitialText();
|
||||
|
||||
if (!setPreparedList(std::move(_initialList))) {
|
||||
rebuildPreview();
|
||||
crl::on_main(this, [=] { closeBox(); });
|
||||
return;
|
||||
}
|
||||
setupEditEventHandler();
|
||||
SetupShadowsToScrollContent(this, _scroll, _contentHeight.events());
|
||||
@@ -525,6 +535,7 @@ void EditCaptionBox::setupField() {
|
||||
_field.get(),
|
||||
Window::GifPauseReason::Layer,
|
||||
allow);
|
||||
setupFieldAutocomplete();
|
||||
Ui::Emoji::SuggestionsController::Init(
|
||||
getDelegate()->outerContainer(),
|
||||
_field,
|
||||
@@ -562,6 +573,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;
|
||||
|
||||
@@ -356,11 +356,12 @@ void PrivacyExceptionsBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||
|
||||
auto PrivacyExceptionsBoxController::createRow(not_null<History*> history)
|
||||
-> std::unique_ptr<Row> {
|
||||
if (history->peer->isSelf() || history->peer->isRepliesChat()) {
|
||||
const auto peer = history->peer;
|
||||
if (peer->isSelf() || peer->isRepliesChat() || peer->isVerifyCodes()) {
|
||||
return nullptr;
|
||||
} else if (!history->peer->isUser()
|
||||
&& !history->peer->isChat()
|
||||
&& !history->peer->isMegagroup()) {
|
||||
} else if (!peer->isUser()
|
||||
&& !peer->isChat()
|
||||
&& !peer->isMegagroup()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto result = std::make_unique<Row>(history);
|
||||
|
||||
@@ -131,10 +131,13 @@ ExceptionRow::ExceptionRow(not_null<History*> history) : Row(history) {
|
||||
}
|
||||
|
||||
QString ExceptionRow::generateName() {
|
||||
return peer()->isSelf()
|
||||
const auto peer = this->peer();
|
||||
return peer->isSelf()
|
||||
? tr::lng_saved_messages(tr::now)
|
||||
: peer()->isRepliesChat()
|
||||
: peer->isRepliesChat()
|
||||
? tr::lng_replies_messages(tr::now)
|
||||
: peer->isVerifyCodes()
|
||||
? tr::lng_verification_codes(tr::now)
|
||||
: Row::generateName();
|
||||
}
|
||||
|
||||
@@ -152,10 +155,11 @@ PaintRoundImageCallback ExceptionRow::generatePaintUserpicCallback(
|
||||
return ForceRoundUserpicCallback(peer);
|
||||
}
|
||||
return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
|
||||
using namespace Ui;
|
||||
if (saved) {
|
||||
Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
|
||||
EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
|
||||
} else if (replies) {
|
||||
Ui::EmptyUserpic::PaintRepliesMessages(p, x, y, outerWidth, size);
|
||||
EmptyUserpic::PaintRepliesMessages(p, x, y, outerWidth, size);
|
||||
} else {
|
||||
peer->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
|
||||
}
|
||||
|
||||
@@ -122,9 +122,11 @@ void FilterChatsPreview::paintEvent(QPaintEvent *e) {
|
||||
top += st.height;
|
||||
}
|
||||
for (auto &[history, userpic, name, button] : _removePeer) {
|
||||
const auto savedMessages = history->peer->isSelf();
|
||||
const auto repliesMessages = history->peer->isRepliesChat();
|
||||
if (savedMessages || repliesMessages) {
|
||||
const auto peer = history->peer;
|
||||
const auto savedMessages = peer->isSelf();
|
||||
const auto repliesMessages = peer->isRepliesChat();
|
||||
const auto verifyCodes = peer->isVerifyCodes();
|
||||
if (savedMessages || repliesMessages || verifyCodes) {
|
||||
if (savedMessages) {
|
||||
Ui::EmptyUserpic::PaintSavedMessages(
|
||||
p,
|
||||
@@ -132,13 +134,21 @@ void FilterChatsPreview::paintEvent(QPaintEvent *e) {
|
||||
top + iconTop,
|
||||
width(),
|
||||
st.photoSize);
|
||||
} else {
|
||||
} else if (repliesMessages) {
|
||||
Ui::EmptyUserpic::PaintRepliesMessages(
|
||||
p,
|
||||
iconLeft,
|
||||
top + iconTop,
|
||||
width(),
|
||||
st.photoSize);
|
||||
} else {
|
||||
history->peer->paintUserpicLeft(
|
||||
p,
|
||||
userpic,
|
||||
iconLeft,
|
||||
top + iconTop,
|
||||
width(),
|
||||
st.photoSize);
|
||||
}
|
||||
p.setPen(st::contactsNameFg);
|
||||
p.drawTextLeft(
|
||||
@@ -147,7 +157,9 @@ void FilterChatsPreview::paintEvent(QPaintEvent *e) {
|
||||
width(),
|
||||
(savedMessages
|
||||
? tr::lng_saved_messages(tr::now)
|
||||
: tr::lng_replies_messages(tr::now)));
|
||||
: repliesMessages
|
||||
? tr::lng_replies_messages(tr::now)
|
||||
: tr::lng_verification_codes(tr::now)));
|
||||
} else {
|
||||
history->peer->paintUserpicLeft(
|
||||
p,
|
||||
|
||||
@@ -337,12 +337,13 @@ PaintRoundImageCallback ChatRow::generatePaintUserpicCallback(
|
||||
int y,
|
||||
int outerWidth,
|
||||
int size) mutable {
|
||||
using namespace Ui;
|
||||
if (forceRound && peer->isForum()) {
|
||||
ForceRoundUserpicCallback(peer)(p, x, y, outerWidth, size);
|
||||
} else if (saved) {
|
||||
Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
|
||||
EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
|
||||
} else if (replies) {
|
||||
Ui::EmptyUserpic::PaintRepliesMessages(p, x, y, outerWidth, size);
|
||||
EmptyUserpic::PaintRepliesMessages(p, x, y, outerWidth, size);
|
||||
} else {
|
||||
peer->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
|
||||
}
|
||||
|
||||
@@ -9,8 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
class UserData;
|
||||
|
||||
namespace Api {
|
||||
struct GiftCode;
|
||||
} // namespace Api
|
||||
@@ -29,29 +27,9 @@ class VerticalLayout;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
class SessionNavigation;
|
||||
} // namespace Window
|
||||
|
||||
class GiftPremiumValidator final {
|
||||
public:
|
||||
GiftPremiumValidator(not_null<Window::SessionController*> controller);
|
||||
|
||||
void showBox(not_null<UserData*> user);
|
||||
void showChoosePeerBox(const QString &ref);
|
||||
void showChosenPeerBox(not_null<UserData*> user, const QString &ref);
|
||||
void cancel();
|
||||
|
||||
private:
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
MTP::Sender _api;
|
||||
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
rpl::lifetime _manyGiftsLifetime;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] rpl::producer<QString> GiftDurationValue(int months);
|
||||
[[nodiscard]] QString GiftDuration(int months);
|
||||
|
||||
@@ -76,6 +54,11 @@ void ResolveGiveawayInfo(
|
||||
std::optional<Data::GiveawayStart> start,
|
||||
std::optional<Data::GiveawayResults> results);
|
||||
|
||||
void AddStarGiftTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
const Data::CreditsHistoryEntry &entry,
|
||||
Fn<void()> convertToStars);
|
||||
void AddCreditsHistoryEntryTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
|
||||
@@ -447,6 +447,8 @@ void PeerListBox::addSelectItem(
|
||||
? tr::lng_saved_short(tr::now)
|
||||
: (respect && peer->isRepliesChat())
|
||||
? tr::lng_replies_messages(tr::now)
|
||||
: (respect && peer->isVerifyCodes())
|
||||
? tr::lng_verification_codes(tr::now)
|
||||
: peer->shortName();
|
||||
addSelectItem(
|
||||
peer->id.value,
|
||||
@@ -625,6 +627,8 @@ void PeerListRow::refreshName(const style::PeerListItem &st) {
|
||||
? tr::lng_saved_messages(tr::now)
|
||||
: _isRepliesMessagesChat
|
||||
? tr::lng_replies_messages(tr::now)
|
||||
: _isVerifyCodesChat
|
||||
? tr::lng_verification_codes(tr::now)
|
||||
: generateName();
|
||||
_name.setText(st.nameStyle, text, Ui::NameTextOptions());
|
||||
}
|
||||
@@ -695,6 +699,8 @@ QString PeerListRow::generateShortName() {
|
||||
? tr::lng_saved_short(tr::now)
|
||||
: _isRepliesMessagesChat
|
||||
? tr::lng_replies_messages(tr::now)
|
||||
: _isVerifyCodesChat
|
||||
? tr::lng_verification_codes(tr::now)
|
||||
: peer()->shortName();
|
||||
}
|
||||
|
||||
@@ -715,10 +721,11 @@ PaintRoundImageCallback PeerListRow::generatePaintUserpicCallback(
|
||||
return ForceRoundUserpicCallback(peer);
|
||||
}
|
||||
return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
|
||||
using namespace Ui;
|
||||
if (saved) {
|
||||
Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
|
||||
EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
|
||||
} else if (replies) {
|
||||
Ui::EmptyUserpic::PaintRepliesMessages(p, x, y, outerWidth, size);
|
||||
EmptyUserpic::PaintRepliesMessages(p, x, y, outerWidth, size);
|
||||
} else {
|
||||
peer->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
|
||||
}
|
||||
@@ -757,12 +764,14 @@ int PeerListRow::paintNameIconGetWidth(
|
||||
int availableWidth,
|
||||
int outerWidth,
|
||||
bool selected) {
|
||||
if (special()
|
||||
if (_skipPeerBadge
|
||||
|| special()
|
||||
|| !_savedMessagesStatus.isEmpty()
|
||||
|| _isRepliesMessagesChat) {
|
||||
|| _isRepliesMessagesChat
|
||||
|| _isVerifyCodesChat) {
|
||||
return 0;
|
||||
}
|
||||
return _bagde.drawGetWidth(
|
||||
return _badge.drawGetWidth(
|
||||
p,
|
||||
QRect(
|
||||
nameLeft,
|
||||
@@ -874,12 +883,13 @@ void PeerListRow::paintDisabledCheckUserpic(
|
||||
auto iconBorderPen = st.checkbox.check.border->p;
|
||||
iconBorderPen.setWidth(st.checkbox.selectWidth);
|
||||
|
||||
const auto size = userpicRadius * 2;
|
||||
if (!_savedMessagesStatus.isEmpty()) {
|
||||
Ui::EmptyUserpic::PaintSavedMessages(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
|
||||
Ui::EmptyUserpic::PaintSavedMessages(p, userpicLeft, userpicTop, outerWidth, size);
|
||||
} else if (_isRepliesMessagesChat) {
|
||||
Ui::EmptyUserpic::PaintRepliesMessages(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
|
||||
Ui::EmptyUserpic::PaintRepliesMessages(p, userpicLeft, userpicTop, outerWidth, size);
|
||||
} else {
|
||||
peer()->paintUserpicLeft(p, _userpic, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
|
||||
peer()->paintUserpicLeft(p, _userpic, userpicLeft, userpicTop, outerWidth, size);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1069,10 +1079,13 @@ void PeerListContent::setRowHidden(not_null<PeerListRow*> row, bool hidden) {
|
||||
void PeerListContent::addRowEntry(not_null<PeerListRow*> row) {
|
||||
const auto savedMessagesStatus = _controller->savedMessagesChatStatus();
|
||||
if (!savedMessagesStatus.isEmpty() && !row->special()) {
|
||||
if (row->peer()->isSelf()) {
|
||||
const auto peer = row->peer();
|
||||
if (peer->isSelf()) {
|
||||
row->setSavedMessagesChatStatus(savedMessagesStatus);
|
||||
} else if (row->peer()->isRepliesChat()) {
|
||||
} else if (peer->isRepliesChat()) {
|
||||
row->setIsRepliesMessagesChat(true);
|
||||
} else if (peer->isVerifyCodes()) {
|
||||
row->setIsVerifyCodesChat(true);
|
||||
}
|
||||
}
|
||||
_rowsById.emplace(row->id(), row);
|
||||
@@ -1931,6 +1944,13 @@ PeerListContent::SkipResult PeerListContent::selectSkip(int direction) {
|
||||
}
|
||||
}
|
||||
|
||||
if (_controller->overrideKeyboardNavigation(
|
||||
direction,
|
||||
_selected.index.value,
|
||||
newSelectedIndex)) {
|
||||
return { _selected.index.value, _selected.index.value };
|
||||
}
|
||||
|
||||
_selected.index.value = newSelectedIndex;
|
||||
_selected.element = 0;
|
||||
if (newSelectedIndex >= 0) {
|
||||
|
||||
@@ -200,6 +200,9 @@ public:
|
||||
void setIsRepliesMessagesChat(bool isRepliesMessagesChat) {
|
||||
_isRepliesMessagesChat = isRepliesMessagesChat;
|
||||
}
|
||||
void setIsVerifyCodesChat(bool isVerifyCodesChat) {
|
||||
_isVerifyCodesChat = isVerifyCodesChat;
|
||||
}
|
||||
|
||||
template <typename UpdateCallback>
|
||||
void setChecked(
|
||||
@@ -251,6 +254,10 @@ public:
|
||||
return _nameFirstLetters;
|
||||
}
|
||||
|
||||
void setSkipPeerBadge(bool skip) {
|
||||
_skipPeerBadge = skip;
|
||||
}
|
||||
|
||||
virtual void lazyInitialize(const style::PeerListItem &st);
|
||||
virtual void paintStatusText(
|
||||
Painter &p,
|
||||
@@ -288,7 +295,7 @@ private:
|
||||
std::unique_ptr<Ui::RoundImageCheckbox> _checkbox;
|
||||
Ui::Text::String _name;
|
||||
Ui::Text::String _status;
|
||||
Ui::PeerBadge _bagde;
|
||||
Ui::PeerBadge _badge;
|
||||
StatusType _statusType = StatusType::Online;
|
||||
crl::time _statusValidTill = 0;
|
||||
base::flat_set<QChar> _nameFirstLetters;
|
||||
@@ -299,6 +306,8 @@ private:
|
||||
bool _initialized : 1 = false;
|
||||
bool _isSearchResult : 1 = false;
|
||||
bool _isRepliesMessagesChat : 1 = false;
|
||||
bool _isVerifyCodesChat : 1 = false;
|
||||
bool _skipPeerBadge : 1 = false;
|
||||
|
||||
};
|
||||
|
||||
@@ -348,6 +357,8 @@ public:
|
||||
virtual int peerListPartitionRows(Fn<bool(const PeerListRow &a)> border) = 0;
|
||||
virtual std::shared_ptr<Main::SessionShow> peerListUiShow() = 0;
|
||||
|
||||
virtual void peerListSelectSkip(int direction) = 0;
|
||||
|
||||
virtual void peerListPressLeftToContextMenu(bool shown) = 0;
|
||||
virtual bool peerListTrackRowPressFromGlobal(QPoint globalPosition) = 0;
|
||||
|
||||
@@ -564,6 +575,13 @@ public:
|
||||
Unexpected("PeerListController::customRowRippleMaskGenerator.");
|
||||
}
|
||||
|
||||
virtual bool overrideKeyboardNavigation(
|
||||
int direction,
|
||||
int fromIndex,
|
||||
int toIndex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime() {
|
||||
return _lifetime;
|
||||
}
|
||||
@@ -1007,6 +1025,10 @@ public:
|
||||
bool highlightRow,
|
||||
Fn<void(not_null<Ui::PopupMenu*>)> destroyed = nullptr) override;
|
||||
|
||||
void peerListSelectSkip(int direction) override {
|
||||
_content->selectSkip(direction);
|
||||
}
|
||||
|
||||
void peerListPressLeftToContextMenu(bool shown) override {
|
||||
_content->pressLeftToContextMenu(shown);
|
||||
}
|
||||
|
||||
@@ -842,6 +842,7 @@ auto ChooseRecipientBoxController::createRow(
|
||||
? !_filter(history)
|
||||
: ((peer->isBroadcast() && !Data::CanSendAnything(peer))
|
||||
|| peer->isRepliesChat()
|
||||
|| peer->isVerifyCodes()
|
||||
|| (peer->isUser() && (_premiumRequiredError
|
||||
? !peer->asUser()->canSendIgnoreRequirePremium()
|
||||
: !Data::CanSendAnything(peer))));
|
||||
|
||||
@@ -269,7 +269,8 @@ PaintRoundImageCallback ForbiddenRow::generatePaintUserpicCallback(
|
||||
const auto peer = this->peer();
|
||||
const auto saved = peer->isSelf();
|
||||
const auto replies = peer->isRepliesChat();
|
||||
auto userpic = (saved || replies)
|
||||
const auto verifyCodes = peer->isVerifyCodes();
|
||||
auto userpic = (saved || replies || verifyCodes)
|
||||
? Ui::PeerUserpicView()
|
||||
: ensureUserpicView();
|
||||
auto paint = [=](
|
||||
@@ -302,6 +303,7 @@ PaintRoundImageCallback ForbiddenRow::generatePaintUserpicCallback(
|
||||
repaint = (_paletteVersion != style::PaletteVersion())
|
||||
|| (!saved
|
||||
&& !replies
|
||||
&& !verifyCodes
|
||||
&& (_userpicKey != peer->userpicUniqueKey(userpic)));
|
||||
}
|
||||
if (repaint) {
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_credits.h"
|
||||
#include "api/api_peer_photo.h"
|
||||
#include "api/api_statistics.h"
|
||||
#include "api/api_user_names.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
@@ -33,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "data/components/credits.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_peer.h"
|
||||
@@ -45,6 +47,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/admin_log/history_admin_log_section.h"
|
||||
#include "info/bot/earn/info_bot_earn_widget.h"
|
||||
#include "info/channel_statistics/boosts/info_boosts_widget.h"
|
||||
#include "info/channel_statistics/earn/earn_format.h"
|
||||
#include "info/channel_statistics/earn/earn_icons.h"
|
||||
#include "info/channel_statistics/earn/info_channel_earn_widget.h"
|
||||
#include "info/profile/info_profile_values.h"
|
||||
#include "info/info_memento.h"
|
||||
#include "lang/lang_keys.h"
|
||||
@@ -351,7 +356,8 @@ private:
|
||||
void fillPendingRequestsButton();
|
||||
|
||||
void fillBotUsernamesButton();
|
||||
void fillBotBalanceButton();
|
||||
void fillBotCurrencyButton();
|
||||
void fillBotCreditsButton();
|
||||
void fillBotEditIntroButton();
|
||||
void fillBotEditCommandsButton();
|
||||
void fillBotEditSettingsButton();
|
||||
@@ -1173,7 +1179,8 @@ void Controller::fillManageSection() {
|
||||
|
||||
::AddSkip(container, 0);
|
||||
fillBotUsernamesButton();
|
||||
fillBotBalanceButton();
|
||||
fillBotCurrencyButton();
|
||||
fillBotCreditsButton();
|
||||
fillBotEditIntroButton();
|
||||
fillBotEditCommandsButton();
|
||||
fillBotEditSettingsButton();
|
||||
@@ -1582,7 +1589,7 @@ void Controller::fillBotUsernamesButton() {
|
||||
{ &st::menuIconLinks });
|
||||
}
|
||||
|
||||
void Controller::fillBotBalanceButton() {
|
||||
void Controller::fillBotCurrencyButton() {
|
||||
Expects(_isBot);
|
||||
|
||||
struct State final {
|
||||
@@ -1591,20 +1598,88 @@ void Controller::fillBotBalanceButton() {
|
||||
|
||||
auto &lifetime = _controls.buttonsLayout->lifetime();
|
||||
const auto state = lifetime.make_state<State>();
|
||||
const auto format = [=](uint64 balance) {
|
||||
return Info::ChannelEarn::MajorPart(balance)
|
||||
+ Info::ChannelEarn::MinorPart(balance);
|
||||
};
|
||||
const auto was = _peer->session().credits().balanceCurrency(
|
||||
_peer->id);
|
||||
if (was) {
|
||||
state->balance = format(was);
|
||||
}
|
||||
|
||||
const auto wrap = _controls.buttonsLayout->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
|
||||
_controls.buttonsLayout,
|
||||
EditPeerInfoBox::CreateButton(
|
||||
_controls.buttonsLayout,
|
||||
tr::lng_manage_peer_bot_balance(),
|
||||
tr::lng_manage_peer_bot_balance_currency(),
|
||||
state->balance.value(),
|
||||
[controller = _navigation->parentController(), peer = _peer] {
|
||||
controller->showSection(Info::ChannelEarn::Make(peer));
|
||||
},
|
||||
st::manageGroupButton,
|
||||
{})));
|
||||
wrap->toggle(!state->balance.current().isEmpty(), anim::type::instant);
|
||||
|
||||
const auto button = wrap->entity();
|
||||
{
|
||||
const auto currencyLoad
|
||||
= button->lifetime().make_state<Api::EarnStatistics>(_peer);
|
||||
currencyLoad->request(
|
||||
) | rpl::start_with_error_done([=](const QString &error) {
|
||||
}, [=] {
|
||||
const auto balance = currencyLoad->data().currentBalance;
|
||||
if (balance) {
|
||||
wrap->toggle(true, anim::type::normal);
|
||||
}
|
||||
state->balance = format(balance);
|
||||
}, button->lifetime());
|
||||
}
|
||||
{
|
||||
const auto icon = Ui::CreateChild<Ui::RpWidget>(button);
|
||||
icon->resize(st::menuIconLinks.size());
|
||||
const auto image = Ui::Earn::MenuIconCurrency(icon->size());
|
||||
icon->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = QPainter(icon);
|
||||
p.drawImage(0, 0, image);
|
||||
}, icon->lifetime());
|
||||
|
||||
button->sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &size) {
|
||||
icon->moveToLeft(
|
||||
button->st().iconLeft,
|
||||
(size.height() - icon->height()) / 2);
|
||||
}, icon->lifetime());
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::fillBotCreditsButton() {
|
||||
Expects(_isBot);
|
||||
|
||||
struct State final {
|
||||
rpl::variable<QString> balance;
|
||||
};
|
||||
|
||||
auto &lifetime = _controls.buttonsLayout->lifetime();
|
||||
const auto state = lifetime.make_state<State>();
|
||||
if (const auto balance = _peer->session().credits().balance(_peer->id)) {
|
||||
state->balance = Lang::FormatCountDecimal(balance);
|
||||
}
|
||||
|
||||
const auto wrap = _controls.buttonsLayout->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
|
||||
_controls.buttonsLayout,
|
||||
EditPeerInfoBox::CreateButton(
|
||||
_controls.buttonsLayout,
|
||||
tr::lng_manage_peer_bot_balance_credits(),
|
||||
state->balance.value(),
|
||||
[controller = _navigation->parentController(), peer = _peer] {
|
||||
controller->showSection(Info::BotEarn::Make(peer));
|
||||
},
|
||||
st::manageGroupButton,
|
||||
{})));
|
||||
wrap->toggle(false, anim::type::instant);
|
||||
wrap->toggle(!state->balance.current().isEmpty(), anim::type::instant);
|
||||
|
||||
const auto button = wrap->entity();
|
||||
{
|
||||
@@ -1614,46 +1689,22 @@ void Controller::fillBotBalanceButton() {
|
||||
if (data.balance) {
|
||||
wrap->toggle(true, anim::type::normal);
|
||||
}
|
||||
state->balance = QString::number(data.balance);
|
||||
state->balance = Lang::FormatCountDecimal(data.balance);
|
||||
});
|
||||
}
|
||||
{
|
||||
constexpr auto kSizeShift = 3;
|
||||
constexpr auto kStrokeWidth = 5;
|
||||
|
||||
const auto icon = Ui::CreateChild<Ui::RpWidget>(button);
|
||||
icon->resize(Size(st::menuIconLinks.width() - kSizeShift));
|
||||
|
||||
auto colorized = [&] {
|
||||
auto f = QFile(Ui::Premium::Svg());
|
||||
if (!f.open(QIODevice::ReadOnly)) {
|
||||
return QString();
|
||||
}
|
||||
return QString::fromUtf8(
|
||||
f.readAll()).replace(u"#fff"_q, u"#ffffff00"_q);
|
||||
}();
|
||||
colorized.replace(
|
||||
u"stroke=\"none\""_q,
|
||||
u"stroke=\"%1\""_q.arg(st::menuIconColor->c.name()));
|
||||
colorized.replace(
|
||||
u"stroke-width=\"1\""_q,
|
||||
u"stroke-width=\"%1\""_q.arg(kStrokeWidth));
|
||||
const auto svg = icon->lifetime().make_state<QSvgRenderer>(
|
||||
colorized.toUtf8());
|
||||
svg->setViewBox(svg->viewBox() + Margins(kStrokeWidth));
|
||||
|
||||
const auto starSize = Size(icon->height());
|
||||
|
||||
icon->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto image = Ui::Earn::MenuIconCredits();
|
||||
icon->resize(image.size() / style::DevicePixelRatio());
|
||||
icon->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = QPainter(icon);
|
||||
svg->render(&p, Rect(starSize));
|
||||
p.drawImage(0, 0, image);
|
||||
}, icon->lifetime());
|
||||
|
||||
button->sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &size) {
|
||||
icon->moveToLeft(
|
||||
button->st().iconLeft + kSizeShift / 2.,
|
||||
button->st().iconLeft,
|
||||
(size.height() - icon->height()) / 2);
|
||||
}, icon->lifetime());
|
||||
}
|
||||
|
||||
@@ -956,12 +956,11 @@ void Controller::rowClicked(not_null<PeerListRow*> row) {
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
|
||||
const auto &stUser = st::boostReplaceUserpic;
|
||||
const auto photoSize = st::boostReplaceUserpic.photoSize;
|
||||
const auto session = &row->peer()->session();
|
||||
content->add(object_ptr<Ui::CenterWrap<>>(
|
||||
content,
|
||||
object_ptr<Ui::UserpicButton>(content, channel, stUser))
|
||||
)->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
Settings::SubscriptionUserpic(content, channel, photoSize)));
|
||||
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
|
||||
@@ -1129,11 +1129,14 @@ 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;
|
||||
|
||||
@@ -534,15 +534,16 @@ void PeerShortInfoCover::handleStreamingUpdate(
|
||||
|
||||
v::match(update.data, [&](Information &update) {
|
||||
streamingReady(std::move(update));
|
||||
}, [&](const PreloadedVideo &update) {
|
||||
}, [&](const UpdateVideo &update) {
|
||||
}, [](PreloadedVideo) {
|
||||
}, [&](UpdateVideo update) {
|
||||
_videoPosition = update.position;
|
||||
_widget->update();
|
||||
}, [&](const PreloadedAudio &update) {
|
||||
}, [&](const UpdateAudio &update) {
|
||||
}, [&](const WaitingForData &update) {
|
||||
}, [&](MutedByOther) {
|
||||
}, [&](Finished) {
|
||||
}, [](PreloadedAudio) {
|
||||
}, [](UpdateAudio) {
|
||||
}, [](WaitingForData) {
|
||||
}, [](SpeedEstimate) {
|
||||
}, [](MutedByOther) {
|
||||
}, [](Finished) {
|
||||
});
|
||||
}
|
||||
|
||||
@@ -774,6 +775,10 @@ void PeerShortInfoBox::prepareRows() {
|
||||
result->setContextCopyText(contextCopyText);
|
||||
return result;
|
||||
};
|
||||
addInfoOneLine(
|
||||
tr::lng_settings_channel_label(),
|
||||
channelValue(),
|
||||
tr::lng_context_copy_link(tr::now));
|
||||
addInfoOneLine(
|
||||
tr::lng_info_link_label(),
|
||||
linkValue(),
|
||||
@@ -835,6 +840,13 @@ rpl::producer<QString> PeerShortInfoBox::nameValue() const {
|
||||
}) | rpl::distinct_until_changed();
|
||||
}
|
||||
|
||||
rpl::producer<TextWithEntities> PeerShortInfoBox::channelValue() const {
|
||||
return _fields.value(
|
||||
) | rpl::map([](const PeerShortInfoFields &fields) {
|
||||
return Ui::Text::Link(fields.channelName, fields.channelLink);
|
||||
}) | rpl::distinct_until_changed();
|
||||
}
|
||||
|
||||
rpl::producer<TextWithEntities> PeerShortInfoBox::linkValue() const {
|
||||
return _fields.value(
|
||||
) | rpl::map([](const PeerShortInfoFields &fields) {
|
||||
|
||||
@@ -37,6 +37,8 @@ enum class PeerShortInfoType {
|
||||
|
||||
struct PeerShortInfoFields {
|
||||
QString name;
|
||||
QString channelName;
|
||||
QString channelLink;
|
||||
QString phone;
|
||||
QString link;
|
||||
TextWithEntities about;
|
||||
@@ -169,6 +171,7 @@ private:
|
||||
int fillRoundedTopHeight();
|
||||
|
||||
[[nodiscard]] rpl::producer<QString> nameValue() const;
|
||||
[[nodiscard]] rpl::producer<TextWithEntities> channelValue() const;
|
||||
[[nodiscard]] rpl::producer<TextWithEntities> linkValue() const;
|
||||
[[nodiscard]] rpl::producer<QString> phoneValue() const;
|
||||
[[nodiscard]] rpl::producer<QString> usernameValue() const;
|
||||
|
||||
@@ -202,6 +202,7 @@ void ProcessFullPhoto(
|
||||
return peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
(UpdateFlag::Name
|
||||
| UpdateFlag::PersonalChannel
|
||||
| UpdateFlag::PhoneNumber
|
||||
| UpdateFlag::Username
|
||||
| UpdateFlag::About
|
||||
@@ -209,8 +210,20 @@ void ProcessFullPhoto(
|
||||
) | rpl::map([=] {
|
||||
const auto user = peer->asUser();
|
||||
const auto username = peer->username();
|
||||
const auto channelId = user->personalChannelId();
|
||||
const auto channel = channelId
|
||||
? user->owner().channel(channelId).get()
|
||||
: nullptr;
|
||||
const auto channelUsername = channel
|
||||
? channel->username()
|
||||
: QString();
|
||||
const auto hasChannel = !channelUsername.isEmpty();
|
||||
return PeerShortInfoFields{
|
||||
.name = peer->name(),
|
||||
.channelName = hasChannel ? channel->name() : QString(),
|
||||
.channelLink = (hasChannel
|
||||
? channel->session().createInternalLinkFull(channelUsername)
|
||||
: QString()),
|
||||
.phone = user ? Ui::FormatPhone(user->phone()) : QString(),
|
||||
.link = ((user || username.isEmpty())
|
||||
? QString()
|
||||
|
||||
@@ -8,27 +8,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/report_messages_box.h"
|
||||
|
||||
#include "api/api_report.h"
|
||||
#include "core/application.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/boxes/report_box.h"
|
||||
#include "ui/boxes/report_box_graphics.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_settings.h"
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> Report(
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> ReportPhoto(
|
||||
not_null<PeerData*> peer,
|
||||
std::variant<
|
||||
v::null_t,
|
||||
MessageIdsList,
|
||||
not_null<PhotoData*>,
|
||||
StoryId> data,
|
||||
not_null<PhotoData*> photo,
|
||||
const style::ReportBox *stOverride) {
|
||||
const auto source = v::match(data, [](const MessageIdsList &ids) {
|
||||
return Ui::ReportSource::Message;
|
||||
}, [&](not_null<PhotoData*> photo) {
|
||||
const auto source = [&] {
|
||||
return peer->isUser()
|
||||
? (photo->hasVideo()
|
||||
? Ui::ReportSource::ProfileVideo
|
||||
@@ -40,19 +43,14 @@ namespace {
|
||||
: (photo->hasVideo()
|
||||
? Ui::ReportSource::ChannelVideo
|
||||
: Ui::ReportSource::ChannelPhoto);
|
||||
}, [&](StoryId id) {
|
||||
return Ui::ReportSource::Story;
|
||||
}, [](v::null_t) {
|
||||
Unexpected("Bad source report.");
|
||||
return Ui::ReportSource::Bot;
|
||||
});
|
||||
}();
|
||||
const auto st = stOverride ? stOverride : &st::defaultReportBox;
|
||||
return Box([=](not_null<Ui::GenericBox*> box) {
|
||||
const auto show = box->uiShow();
|
||||
Ui::ReportReasonBox(box, *st, source, [=](Ui::ReportReason reason) {
|
||||
show->showBox(Box([=](not_null<Ui::GenericBox*> box) {
|
||||
Ui::ReportDetailsBox(box, *st, [=](const QString &text) {
|
||||
Api::SendReport(show, peer, reason, text, data);
|
||||
Api::SendPhotoReport(show, peer, reason, text, photo);
|
||||
show->hideLayer();
|
||||
});
|
||||
}));
|
||||
@@ -62,64 +60,151 @@ namespace {
|
||||
|
||||
} // namespace
|
||||
|
||||
object_ptr<Ui::BoxContent> ReportItemsBox(
|
||||
not_null<PeerData*> peer,
|
||||
MessageIdsList ids) {
|
||||
return Report(peer, ids, nullptr);
|
||||
}
|
||||
|
||||
object_ptr<Ui::BoxContent> ReportProfilePhotoBox(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<PhotoData*> photo) {
|
||||
return Report(peer, photo, nullptr);
|
||||
return ReportPhoto(peer, photo, nullptr);
|
||||
}
|
||||
|
||||
void ShowReportPeerBox(
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<PeerData*> peer) {
|
||||
struct State {
|
||||
QPointer<Ui::BoxContent> reasonBox;
|
||||
QPointer<Ui::BoxContent> detailsBox;
|
||||
MessageIdsList ids;
|
||||
};
|
||||
const auto state = std::make_shared<State>();
|
||||
const auto chosen = [=](Ui::ReportReason reason) {
|
||||
const auto send = [=](const QString &text) {
|
||||
window->clearChooseReportMessages();
|
||||
Api::SendReport(
|
||||
window->uiShow(),
|
||||
peer,
|
||||
reason,
|
||||
text,
|
||||
std::move(state->ids));
|
||||
if (const auto strong = state->reasonBox.data()) {
|
||||
strong->closeBox();
|
||||
void ShowReportMessageBox(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
const std::vector<MsgId> &ids,
|
||||
const std::vector<StoryId> &stories,
|
||||
const style::ReportBox *stOverride) {
|
||||
const auto report = Api::CreateReportMessagesOrStoriesCallback(
|
||||
show,
|
||||
peer);
|
||||
|
||||
auto performRequest = [=](
|
||||
const auto &repeatRequest,
|
||||
Data::ReportInput reportInput) -> void {
|
||||
constexpr auto kToastDuration = crl::time(4000);
|
||||
report(reportInput, [=](const Api::ReportResult &result) {
|
||||
if (!result.error.isEmpty()) {
|
||||
if (result.error == u"MESSAGE_ID_REQUIRED"_q) {
|
||||
const auto widget = show->toastParent();
|
||||
const auto window = Core::App().findWindow(widget);
|
||||
const auto controller = window
|
||||
? window->sessionController()
|
||||
: nullptr;
|
||||
if (controller) {
|
||||
const auto callback = [=](std::vector<MsgId> ids) {
|
||||
auto copy = reportInput;
|
||||
copy.ids = std::move(ids);
|
||||
repeatRequest(repeatRequest, std::move(copy));
|
||||
};
|
||||
controller->showChooseReportMessages(
|
||||
peer,
|
||||
reportInput,
|
||||
std::move(callback));
|
||||
}
|
||||
} else {
|
||||
show->showToast(result.error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (const auto strong = state->detailsBox.data()) {
|
||||
strong->closeBox();
|
||||
if (!result.options.empty() || result.commentOption) {
|
||||
show->show(Box([=](not_null<Ui::GenericBox*> box) {
|
||||
box->setTitle(
|
||||
rpl::single(
|
||||
result.title.isEmpty()
|
||||
? reportInput.optionText
|
||||
: result.title));
|
||||
|
||||
for (const auto &option : result.options) {
|
||||
const auto button = Ui::AddReportOptionButton(
|
||||
box->verticalLayout(),
|
||||
option.text,
|
||||
stOverride);
|
||||
button->setClickedCallback([=] {
|
||||
auto copy = reportInput;
|
||||
copy.optionId = option.id;
|
||||
copy.optionText = option.text;
|
||||
repeatRequest(repeatRequest, std::move(copy));
|
||||
});
|
||||
}
|
||||
if (const auto commentOption = result.commentOption) {
|
||||
constexpr auto kReportReasonLengthMax = 512;
|
||||
const auto &st = stOverride
|
||||
? stOverride
|
||||
: &st::defaultReportBox;
|
||||
Ui::AddReportDetailsIconButton(box);
|
||||
Ui::AddSkip(box->verticalLayout());
|
||||
Ui::AddSkip(box->verticalLayout());
|
||||
const auto details = box->addRow(
|
||||
object_ptr<Ui::InputField>(
|
||||
box,
|
||||
st->field,
|
||||
Ui::InputField::Mode::MultiLine,
|
||||
commentOption->optional
|
||||
? tr::lng_report_details_optional()
|
||||
: tr::lng_report_details_non_optional(),
|
||||
QString()));
|
||||
Ui::AddSkip(box->verticalLayout());
|
||||
Ui::AddSkip(box->verticalLayout());
|
||||
{
|
||||
const auto container = box->verticalLayout();
|
||||
auto label = object_ptr<Ui::FlatLabel>(
|
||||
container,
|
||||
tr::lng_report_details_message_about(),
|
||||
st::boxDividerLabel);
|
||||
label->setTextColorOverride(st->dividerFg->c);
|
||||
using namespace Ui;
|
||||
const auto widget = container->add(
|
||||
object_ptr<PaddingWrap<>>(
|
||||
container,
|
||||
std::move(label),
|
||||
st::defaultBoxDividerLabelPadding));
|
||||
const auto background
|
||||
= CreateChild<BoxContentDivider>(
|
||||
widget,
|
||||
st::boxDividerHeight,
|
||||
st->dividerBg,
|
||||
RectPart::Top | RectPart::Bottom);
|
||||
background->lower();
|
||||
widget->sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &s) {
|
||||
background->resize(s);
|
||||
}, background->lifetime());
|
||||
}
|
||||
details->setMaxLength(kReportReasonLengthMax);
|
||||
box->setFocusCallback([=] {
|
||||
details->setFocusFast();
|
||||
});
|
||||
const auto submit = [=] {
|
||||
if (!commentOption->optional
|
||||
&& details->empty()) {
|
||||
details->showError();
|
||||
details->setFocus();
|
||||
return;
|
||||
}
|
||||
auto copy = reportInput;
|
||||
copy.optionId = commentOption->id;
|
||||
copy.comment = details->getLastText();
|
||||
repeatRequest(repeatRequest, std::move(copy));
|
||||
};
|
||||
details->submits(
|
||||
) | rpl::start_with_next(submit, details->lifetime());
|
||||
box->addButton(tr::lng_report_button(), submit);
|
||||
} else {
|
||||
box->addButton(
|
||||
tr::lng_close(),
|
||||
[=] { show->hideLayer(); });
|
||||
}
|
||||
if (!reportInput.optionId.isNull()) {
|
||||
box->addLeftButton(
|
||||
tr::lng_create_group_back(),
|
||||
[=] { box->closeBox(); });
|
||||
}
|
||||
}));
|
||||
} else if (result.successful) {
|
||||
show->showToast(
|
||||
tr::lng_report_thanks(tr::now),
|
||||
kToastDuration);
|
||||
show->hideLayer();
|
||||
}
|
||||
};
|
||||
if (reason == Ui::ReportReason::Fake
|
||||
|| reason == Ui::ReportReason::Other) {
|
||||
state->ids = {};
|
||||
state->detailsBox = window->show(
|
||||
Box(Ui::ReportDetailsBox, st::defaultReportBox, send));
|
||||
return;
|
||||
}
|
||||
window->showChooseReportMessages(peer, reason, [=](
|
||||
MessageIdsList ids) {
|
||||
state->ids = std::move(ids);
|
||||
state->detailsBox = window->show(
|
||||
Box(Ui::ReportDetailsBox, st::defaultReportBox, send));
|
||||
});
|
||||
};
|
||||
state->reasonBox = window->show(Box(
|
||||
Ui::ReportReasonBox,
|
||||
st::defaultReportBox,
|
||||
(peer->isBroadcast()
|
||||
? Ui::ReportSource::Channel
|
||||
: peer->isUser()
|
||||
? Ui::ReportSource::Bot
|
||||
: Ui::ReportSource::Group),
|
||||
chosen));
|
||||
performRequest(performRequest, { .ids = ids, .stories = stories });
|
||||
}
|
||||
|
||||
@@ -12,20 +12,22 @@ class object_ptr;
|
||||
|
||||
namespace Ui {
|
||||
class BoxContent;
|
||||
class Show;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Main
|
||||
namespace style {
|
||||
struct ReportBox;
|
||||
} // namespace style
|
||||
|
||||
class PeerData;
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> ReportItemsBox(
|
||||
not_null<PeerData*> peer,
|
||||
MessageIdsList ids);
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> ReportProfilePhotoBox(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<PhotoData*> photo);
|
||||
void ShowReportPeerBox(
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<PeerData*> peer);
|
||||
|
||||
void ShowReportMessageBox(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
const std::vector<MsgId> &ids,
|
||||
const std::vector<StoryId> &stories,
|
||||
const style::ReportBox *stOverride = nullptr);
|
||||
|
||||
@@ -272,10 +272,13 @@ void SendCreditsBox(
|
||||
state->confirmButtonBusy = true;
|
||||
session->api().request(
|
||||
MTPpayments_SendStarsForm(
|
||||
MTP_flags(0),
|
||||
MTP_long(form->formId),
|
||||
form->inputInvoice)
|
||||
).done([=](auto result) {
|
||||
).done([=](const MTPpayments_PaymentResult &result) {
|
||||
result.match([&](const MTPDpayments_paymentResult &data) {
|
||||
session->api().applyUpdates(data.vupdates());
|
||||
}, [](const MTPDpayments_paymentVerificationNeeded &data) {
|
||||
});
|
||||
if (weak) {
|
||||
state->confirmButtonBusy = false;
|
||||
box->closeBox();
|
||||
@@ -311,41 +314,22 @@ void SendCreditsBox(
|
||||
AddChildToWidgetCenter(button.data(), loadingAnimation);
|
||||
loadingAnimation->showOn(state->confirmButtonBusy.value());
|
||||
}
|
||||
{
|
||||
auto buttonText = tr::lng_credits_box_out_confirm(
|
||||
lt_count,
|
||||
rpl::single(form->invoice.amount) | tr::to_count(),
|
||||
lt_emoji,
|
||||
rpl::single(CreditsEmojiSmall(session)),
|
||||
Ui::Text::RichLangValue);
|
||||
const auto buttonLabel = Ui::CreateChild<Ui::FlatLabel>(
|
||||
button,
|
||||
rpl::single(QString()),
|
||||
st::creditsBoxButtonLabel);
|
||||
std::move(
|
||||
buttonText
|
||||
) | rpl::start_with_next([=](const TextWithEntities &text) {
|
||||
buttonLabel->setMarkedText(
|
||||
text,
|
||||
Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = [=] { buttonLabel->update(); },
|
||||
});
|
||||
}, buttonLabel->lifetime());
|
||||
buttonLabel->setTextColorOverride(
|
||||
box->getDelegate()->style().button.textFg->c);
|
||||
button->sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &size) {
|
||||
buttonLabel->moveToLeft(
|
||||
(size.width() - buttonLabel->width()) / 2,
|
||||
(size.height() - buttonLabel->height()) / 2);
|
||||
}, buttonLabel->lifetime());
|
||||
buttonLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
state->confirmButtonBusy.value(
|
||||
) | rpl::start_with_next([=](bool busy) {
|
||||
buttonLabel->setVisible(!busy);
|
||||
}, buttonLabel->lifetime());
|
||||
}
|
||||
SetButtonMarkedLabel(
|
||||
button,
|
||||
rpl::combine(
|
||||
tr::lng_credits_box_out_confirm(
|
||||
lt_count,
|
||||
rpl::single(form->invoice.amount) | tr::to_count(),
|
||||
lt_emoji,
|
||||
rpl::single(CreditsEmojiSmall(session)),
|
||||
Ui::Text::RichLangValue),
|
||||
state->confirmButtonBusy.value()
|
||||
) | rpl::map([](TextWithEntities &&text, bool busy) {
|
||||
return busy ? TextWithEntities() : std::move(text);
|
||||
}),
|
||||
session,
|
||||
st::creditsBoxButtonLabel,
|
||||
box->getDelegate()->style().button.textFg->c);
|
||||
|
||||
const auto buttonWidth = st::boxWidth
|
||||
- rect::m::sum::h(st::giveawayGiftCodeBox.buttonPadding);
|
||||
@@ -405,4 +389,73 @@ TextWithEntities CreditsEmojiSmall(not_null<Main::Session*> session) {
|
||||
QString(QChar(0x2B50)));
|
||||
}
|
||||
|
||||
not_null<FlatLabel*> SetButtonMarkedLabel(
|
||||
not_null<RpWidget*> button,
|
||||
rpl::producer<TextWithEntities> text,
|
||||
Fn<std::any(Fn<void()> update)> context,
|
||||
const style::FlatLabel &st,
|
||||
std::optional<QColor> textFg) {
|
||||
const auto buttonLabel = Ui::CreateChild<Ui::FlatLabel>(
|
||||
button,
|
||||
rpl::single(QString()),
|
||||
st);
|
||||
rpl::duplicate(
|
||||
text
|
||||
) | rpl::filter([=](const TextWithEntities &text) {
|
||||
return !text.text.isEmpty();
|
||||
}) | rpl::start_with_next([=](const TextWithEntities &text) {
|
||||
buttonLabel->setMarkedText(
|
||||
text,
|
||||
context([=] { buttonLabel->update(); }));
|
||||
}, buttonLabel->lifetime());
|
||||
if (textFg) {
|
||||
buttonLabel->setTextColorOverride(textFg);
|
||||
}
|
||||
button->sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &size) {
|
||||
buttonLabel->moveToLeft(
|
||||
(size.width() - buttonLabel->width()) / 2,
|
||||
(size.height() - buttonLabel->height()) / 2);
|
||||
}, buttonLabel->lifetime());
|
||||
buttonLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
buttonLabel->showOn(std::move(
|
||||
text
|
||||
) | rpl::map([=](const TextWithEntities &text) {
|
||||
return !text.text.isEmpty();
|
||||
}));
|
||||
return buttonLabel;
|
||||
}
|
||||
|
||||
not_null<FlatLabel*> SetButtonMarkedLabel(
|
||||
not_null<RpWidget*> button,
|
||||
rpl::producer<TextWithEntities> text,
|
||||
not_null<Main::Session*> session,
|
||||
const style::FlatLabel &st,
|
||||
std::optional<QColor> textFg) {
|
||||
return SetButtonMarkedLabel(button, text, [=](Fn<void()> update) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = update,
|
||||
};
|
||||
}, st, textFg);
|
||||
}
|
||||
|
||||
void SendStarGift(
|
||||
not_null<Main::Session*> session,
|
||||
std::shared_ptr<Payments::CreditsFormData> data,
|
||||
Fn<void(std::optional<QString>)> done) {
|
||||
session->api().request(MTPpayments_SendStarsForm(
|
||||
MTP_long(data->formId),
|
||||
data->inputInvoice
|
||||
)).done([=](const MTPpayments_PaymentResult &result) {
|
||||
result.match([&](const MTPDpayments_paymentResult &data) {
|
||||
session->api().applyUpdates(data.vupdates());
|
||||
}, [](const MTPDpayments_paymentVerificationNeeded &data) {
|
||||
});
|
||||
done(std::nullopt);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
done(error.type());
|
||||
}).send();
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
@@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
class HistoryItem;
|
||||
|
||||
namespace style {
|
||||
struct FlatLabel;
|
||||
} // namespace style
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
@@ -19,7 +23,9 @@ struct CreditsFormData;
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class RpWidget;
|
||||
class GenericBox;
|
||||
class FlatLabel;
|
||||
|
||||
void SendCreditsBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
@@ -32,4 +38,23 @@ void SendCreditsBox(
|
||||
[[nodiscard]] TextWithEntities CreditsEmojiSmall(
|
||||
not_null<Main::Session*> session);
|
||||
|
||||
not_null<FlatLabel*> SetButtonMarkedLabel(
|
||||
not_null<RpWidget*> button,
|
||||
rpl::producer<TextWithEntities> text,
|
||||
Fn<std::any(Fn<void()> update)> context,
|
||||
const style::FlatLabel &st,
|
||||
std::optional<QColor> textFg = {});
|
||||
|
||||
not_null<FlatLabel*> SetButtonMarkedLabel(
|
||||
not_null<RpWidget*> button,
|
||||
rpl::producer<TextWithEntities> text,
|
||||
not_null<Main::Session*> session,
|
||||
const style::FlatLabel &st,
|
||||
std::optional<QColor> textFg = {});
|
||||
|
||||
void SendStarGift(
|
||||
not_null<Main::Session*> session,
|
||||
std::shared_ptr<Payments::CreditsFormData> data,
|
||||
Fn<void(std::optional<QString>)> done);
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
@@ -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"
|
||||
@@ -1266,13 +1267,17 @@ void SendFilesBox::setupCaption() {
|
||||
: (_limits & SendFilesAllow::EmojiWithoutPremium);
|
||||
};
|
||||
const auto show = _show;
|
||||
InitMessageFieldHandlers(
|
||||
&show->session(),
|
||||
show,
|
||||
_caption.data(),
|
||||
[=] { return show->paused(Window::GifPauseReason::Layer); },
|
||||
allow,
|
||||
&_st.files.caption);
|
||||
InitMessageFieldHandlers({
|
||||
.session = &show->session(),
|
||||
.show = show,
|
||||
.field = _caption.data(),
|
||||
.customEmojiPaused = [=] {
|
||||
return show->paused(Window::GifPauseReason::Layer);
|
||||
},
|
||||
.allowPremiumEmoji = allow,
|
||||
.fieldStyle = &_st.files.caption,
|
||||
});
|
||||
setupCaptionAutocomplete();
|
||||
Ui::Emoji::SuggestionsController::Init(
|
||||
getDelegate()->outerContainer(),
|
||||
_caption,
|
||||
@@ -1332,6 +1337,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())
|
||||
@@ -1647,6 +1705,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"
|
||||
@@ -226,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);
|
||||
@@ -255,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());
|
||||
};
|
||||
@@ -264,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);
|
||||
|
||||
|
||||
@@ -240,13 +240,12 @@ void ShareBox::prepareCommentField() {
|
||||
}, field->lifetime());
|
||||
|
||||
if (const auto show = uiShow(); show->valid()) {
|
||||
InitMessageFieldHandlers(
|
||||
_descriptor.session,
|
||||
Main::MakeSessionShow(show, _descriptor.session),
|
||||
field,
|
||||
nullptr,
|
||||
nullptr,
|
||||
_descriptor.stLabel);
|
||||
InitMessageFieldHandlers({
|
||||
.session = _descriptor.session,
|
||||
.show = Main::MakeSessionShow(show, _descriptor.session),
|
||||
.field = field,
|
||||
.fieldStyle = _descriptor.stLabel,
|
||||
});
|
||||
}
|
||||
field->setSubmitSettings(Core::App().settings().sendSubmitWay());
|
||||
|
||||
@@ -838,6 +837,8 @@ void ShareBox::Inner::updateChatName(not_null<Chat*> chat) {
|
||||
? tr::lng_saved_messages(tr::now)
|
||||
: peer->isRepliesChat()
|
||||
? tr::lng_replies_messages(tr::now)
|
||||
: peer->isVerifyCodes()
|
||||
? tr::lng_verification_codes(tr::now)
|
||||
: peer->name();
|
||||
chat->name.setText(_st.item.nameStyle, text, Ui::NameTextOptions());
|
||||
}
|
||||
|
||||
1358
Telegram/SourceFiles/boxes/star_gift_box.cpp
Normal file
23
Telegram/SourceFiles/boxes/star_gift_box.h
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace Ui {
|
||||
|
||||
void ChooseStarGiftRecipient(
|
||||
not_null<Window::SessionController*> controller);
|
||||
|
||||
void ShowStarGiftBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<PeerData*> peer);
|
||||
|
||||
} // namespace Ui
|
||||
@@ -82,7 +82,8 @@ using TLStickerSet = MTPmessages_StickerSet;
|
||||
|
||||
[[nodiscard]] std::optional<QColor> ComputeImageColor(
|
||||
const style::icon &lockIcon,
|
||||
const QImage &frame) {
|
||||
const QImage &frame,
|
||||
RectPart part) {
|
||||
if (frame.isNull()
|
||||
|| frame.format() != QImage::Format_ARGB32_Premultiplied) {
|
||||
return {};
|
||||
@@ -91,13 +92,29 @@ using TLStickerSet = MTPmessages_StickerSet;
|
||||
auto sg = int64();
|
||||
auto sb = int64();
|
||||
auto sa = int64();
|
||||
const auto factor = frame.devicePixelRatio();
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto size = lockIcon.size() * factor;
|
||||
const auto width = std::min(frame.width(), size.width());
|
||||
const auto height = std::min(frame.height(), size.height());
|
||||
const auto skipx = (frame.width() - width) / 2;
|
||||
const auto radius = st::roundRadiusSmall;
|
||||
const auto skipy = std::max(frame.height() - height - radius, 0);
|
||||
const auto skipx = (part == RectPart::TopLeft
|
||||
|| part == RectPart::Left
|
||||
|| part == RectPart::BottomLeft)
|
||||
? 0
|
||||
: (part == RectPart::Top
|
||||
|| part == RectPart::Center
|
||||
|| part == RectPart::Bottom)
|
||||
? (frame.width() - width) / 2
|
||||
: std::max(frame.width() - width - radius, 0);
|
||||
const auto skipy = (part == RectPart::TopLeft
|
||||
|| part == RectPart::Top
|
||||
|| part == RectPart::TopRight)
|
||||
? 0
|
||||
: (part == RectPart::Left
|
||||
|| part == RectPart::Center
|
||||
|| part == RectPart::Right)
|
||||
? (frame.height() - height) / 2
|
||||
: std::max(frame.height() - height - radius, 0);
|
||||
const auto perline = frame.bytesPerLine();
|
||||
const auto addperline = perline - (width * 4);
|
||||
auto bits = static_cast<const uchar*>(frame.bits())
|
||||
@@ -121,17 +138,20 @@ using TLStickerSet = MTPmessages_StickerSet;
|
||||
|
||||
[[nodiscard]] QColor ComputeLockColor(
|
||||
const style::icon &lockIcon,
|
||||
const QImage &frame) {
|
||||
const QImage &frame,
|
||||
RectPart part) {
|
||||
return ComputeImageColor(
|
||||
lockIcon,
|
||||
frame
|
||||
frame,
|
||||
part
|
||||
).value_or(st::windowSubTextFg->c);
|
||||
}
|
||||
|
||||
void ValidatePremiumLockBg(
|
||||
const style::icon &lockIcon,
|
||||
QImage &image,
|
||||
const QImage &frame) {
|
||||
const QImage &frame,
|
||||
RectPart part) {
|
||||
if (!image.isNull()) {
|
||||
return;
|
||||
}
|
||||
@@ -142,7 +162,7 @@ void ValidatePremiumLockBg(
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
image.setDevicePixelRatio(factor);
|
||||
auto p = QPainter(&image);
|
||||
const auto color = ComputeLockColor(lockIcon, frame);
|
||||
const auto color = ComputeLockColor(lockIcon, frame, part);
|
||||
p.fillRect(
|
||||
QRect(QPoint(), size),
|
||||
anim::color(color, st::windowSubTextFg, kGrayLockOpacity));
|
||||
@@ -195,8 +215,10 @@ void ValidatePremiumStarFg(const style::icon &lockIcon, QImage &image) {
|
||||
|
||||
StickerPremiumMark::StickerPremiumMark(
|
||||
not_null<Main::Session*> session,
|
||||
const style::icon &lockIcon)
|
||||
: _lockIcon(lockIcon) {
|
||||
const style::icon &lockIcon,
|
||||
RectPart part)
|
||||
: _lockIcon(lockIcon)
|
||||
, _part(part) {
|
||||
style::PaletteChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
_lockGray = QImage();
|
||||
@@ -221,11 +243,15 @@ void StickerPremiumMark::paint(
|
||||
const auto &bg = frame.isNull() ? _lockGray : backCache;
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto radius = st::roundRadiusSmall;
|
||||
const auto point = position + QPoint(
|
||||
(singleSize.width() - (bg.width() / factor) - radius),
|
||||
singleSize.height() - (bg.height() / factor) - radius);
|
||||
const auto shiftx = (_part == RectPart::Center)
|
||||
? (singleSize.width() - (bg.width() / factor)) / 2
|
||||
: (singleSize.width() - (bg.width() / factor) - radius);
|
||||
const auto shifty = (_part == RectPart::Center)
|
||||
? (singleSize.height() - (bg.height() / factor)) / 2
|
||||
: (singleSize.height() - (bg.height() / factor) - radius);
|
||||
const auto point = position + QPoint(shiftx, shifty);
|
||||
p.drawImage(point, bg);
|
||||
if (_premium) {
|
||||
if (_premium && _part != RectPart::Center) {
|
||||
validateStar();
|
||||
p.drawImage(point, _star);
|
||||
} else {
|
||||
@@ -237,7 +263,7 @@ void StickerPremiumMark::validateLock(
|
||||
const QImage &frame,
|
||||
QImage &backCache) {
|
||||
auto &image = frame.isNull() ? _lockGray : backCache;
|
||||
ValidatePremiumLockBg(_lockIcon, image, frame);
|
||||
ValidatePremiumLockBg(_lockIcon, image, frame, _part);
|
||||
}
|
||||
|
||||
void StickerPremiumMark::validateStar() {
|
||||
@@ -1402,9 +1428,13 @@ void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) {
|
||||
const auto send = crl::guard(this, [=](Api::SendOptions options) {
|
||||
chosen(index, document, options);
|
||||
});
|
||||
|
||||
// In case we're adding items after FillSendMenu we have
|
||||
// to pass nullptr for showForEffect and attach selector later.
|
||||
// Otherwise added items widths won't be respected in menu geometry.
|
||||
SendMenu::FillSendMenu(
|
||||
_menu.get(),
|
||||
_show,
|
||||
nullptr, // showForEffect
|
||||
details,
|
||||
SendMenu::DefaultCallback(_show, send));
|
||||
|
||||
@@ -1438,6 +1468,12 @@ void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) {
|
||||
.isAttention = true,
|
||||
});
|
||||
}
|
||||
|
||||
SendMenu::AttachSendMenuEffect(
|
||||
_menu.get(),
|
||||
_show,
|
||||
details,
|
||||
SendMenu::DefaultCallback(_show, send));
|
||||
}
|
||||
if (_menu->empty()) {
|
||||
_menu = nullptr;
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/layers/box_content.h"
|
||||
#include "base/timer.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "ui/rect_part.h"
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
@@ -32,7 +33,8 @@ class StickerPremiumMark final {
|
||||
public:
|
||||
StickerPremiumMark(
|
||||
not_null<Main::Session*> session,
|
||||
const style::icon &lockIcon);
|
||||
const style::icon &lockIcon,
|
||||
RectPart part = RectPart::Bottom);
|
||||
|
||||
void paint(
|
||||
QPainter &p,
|
||||
@@ -49,6 +51,7 @@ private:
|
||||
const style::icon &_lockIcon;
|
||||
QImage _lockGray;
|
||||
QImage _star;
|
||||
RectPart _part = RectPart::Bottom;
|
||||
bool _premium = false;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
@@ -409,9 +409,7 @@ callRatingStar: IconButton {
|
||||
icon: icon {{ "calls/call_rating", windowSubTextFg }};
|
||||
iconPosition: point(-1px, -1px);
|
||||
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
rippleAreaPosition: point(0px, 0px);
|
||||
rippleAreaSize: 36px;
|
||||
}
|
||||
@@ -1139,6 +1137,7 @@ groupCallTitle: WindowTitle(defaultWindowTitle) {
|
||||
bgActive: groupCallBg;
|
||||
fg: transparent;
|
||||
fgActive: transparent;
|
||||
oneSideControls: true;
|
||||
minimize: IconButton(groupCallTitleButton) {
|
||||
icon: groupCallTitleMinimizeIcon;
|
||||
iconOver: groupCallTitleMinimizeIconOver;
|
||||
@@ -1409,9 +1408,7 @@ groupCallRtmpShowButton: IconButton(defaultIconButton) {
|
||||
|
||||
rippleAreaPosition: point(0px, 0px);
|
||||
rippleAreaSize: 32px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
groupCallSettingsRtmpShowButton: IconButton(groupCallRtmpShowButton) {
|
||||
ripple: groupCallRipple;
|
||||
|
||||
@@ -198,7 +198,9 @@ void Panel::initWindow() {
|
||||
return Flag::None | Flag(0);
|
||||
}
|
||||
#ifndef Q_OS_MAC
|
||||
if (_controls->controls.geometry().contains(widgetPoint)) {
|
||||
using Result = Ui::Platform::HitTestResult;
|
||||
const auto windowPoint = widget()->mapTo(window(), widgetPoint);
|
||||
if (_controls->controls.hitTest(windowPoint) != Result::None) {
|
||||
return Flag::None | Flag(0);
|
||||
}
|
||||
#endif // !Q_OS_MAC
|
||||
|
||||
@@ -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 {
|
||||
@@ -74,8 +73,8 @@ private:
|
||||
|
||||
SourceButton _widget;
|
||||
FlatLabel _label;
|
||||
RoundRect _selectedRect;
|
||||
RoundRect _activeRect;
|
||||
Ui::RoundRect _selectedRect;
|
||||
Ui::RoundRect _activeRect;
|
||||
tgcalls::DesktopCaptureSource _source;
|
||||
std::unique_ptr<Preview> _preview;
|
||||
rpl::event_stream<> _activations;
|
||||
@@ -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)
|
||||
|
||||
@@ -150,6 +150,8 @@ SendButton {
|
||||
inner: IconButton;
|
||||
record: icon;
|
||||
recordOver: icon;
|
||||
round: icon;
|
||||
roundOver: icon;
|
||||
sendDisabledFg: color;
|
||||
}
|
||||
|
||||
@@ -218,8 +220,11 @@ ComposeControls {
|
||||
|
||||
ReportBox {
|
||||
button: SettingsButton;
|
||||
noIconButton: SettingsButton;
|
||||
label: FlatLabel;
|
||||
field: InputField;
|
||||
dividerBg: color;
|
||||
dividerFg: color;
|
||||
spam: icon;
|
||||
fake: icon;
|
||||
violence: icon;
|
||||
@@ -328,9 +333,7 @@ stickersRemove: IconButton(defaultIconButton) {
|
||||
|
||||
rippleAreaSize: 40px;
|
||||
rippleAreaPosition: point(0px, 0px);
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
stickersUndoRemove: RoundButton(defaultLightButton) {
|
||||
width: -16px;
|
||||
@@ -491,9 +494,7 @@ hashtagClose: IconButton {
|
||||
|
||||
rippleAreaPosition: point(5px, 5px);
|
||||
rippleAreaSize: 20px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
|
||||
stickerPanWidthMin: 64px;
|
||||
@@ -895,9 +896,7 @@ historyBusinessBotSettings: IconButton(defaultIconButton) {
|
||||
iconPosition: point(-1px, -1px);
|
||||
rippleAreaSize: 40px;
|
||||
rippleAreaPosition: point(4px, 9px);
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
height: 58px;
|
||||
width: 48px;
|
||||
}
|
||||
@@ -924,9 +923,7 @@ historyReplyCancel: IconButton {
|
||||
|
||||
rippleAreaPosition: point(4px, 4px);
|
||||
rippleAreaSize: 40px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
historyPinnedShowAll: IconButton(historyReplyCancel) {
|
||||
icon: icon {{ "pinned_show_all", historyReplyCancelFg }};
|
||||
@@ -1055,9 +1052,7 @@ historyAttach: IconButton(defaultIconButton) {
|
||||
|
||||
rippleAreaPosition: point(2px, 3px);
|
||||
rippleAreaSize: 40px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
|
||||
historyMessagesTTL: IconButtonWithText {
|
||||
@@ -1079,6 +1074,13 @@ historyReplaceMedia: IconButton(historyAttach) {
|
||||
color: lightButtonBgOver;
|
||||
}
|
||||
}
|
||||
historyAddMedia: IconButton(historyAttach) {
|
||||
icon: icon {{ "chat/input_attach", windowBgActive }};
|
||||
iconOver: icon {{ "chat/input_attach", windowBgActive }};
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: lightButtonBgOver;
|
||||
}
|
||||
}
|
||||
|
||||
historyAttachEmojiActive: icon {{ "chat/input_smile_face", windowBgActive }};
|
||||
historyEmojiCircle: size(20px, 20px);
|
||||
@@ -1166,6 +1168,10 @@ historyRecordVoiceOnceFg: icon {{ "voice_lock/audio_once_number", windowFgActive
|
||||
historyRecordVoiceOnceFgOver: icon {{ "voice_lock/audio_once_number", windowFgActive }};
|
||||
historyRecordVoiceOnceInactive: icon {{ "chat/audio_once", windowSubTextFg }};
|
||||
historyRecordVoiceActive: icon {{ "chat/input_record_filled", historyRecordVoiceFgActiveIcon }};
|
||||
historyRecordRound: icon {{ "chat/input_video", historyRecordVoiceFg }};
|
||||
historyRecordRoundOver: icon {{ "chat/input_video", historyRecordVoiceFgOver }};
|
||||
historyRecordRoundActive: icon {{ "chat/input_video", historyRecordVoiceFgActiveIcon }};
|
||||
historyRecordRoundIconPosition: point(0px, 0px);
|
||||
historyRecordSendIconPosition: point(2px, 0px);
|
||||
historyRecordVoiceRippleBgActive: lightButtonBgOver;
|
||||
historyRecordSignalRadius: 5px;
|
||||
@@ -1211,6 +1217,7 @@ historyRecordLockBody: icon {{ "voice_lock/record_lock_body", historyToDownBg }}
|
||||
historyRecordLockMargin: margins(4px, 4px, 4px, 4px);
|
||||
historyRecordLockArrow: icon {{ "voice_lock/voice_arrow", historyToDownFg }};
|
||||
historyRecordLockInput: icon {{ "voice_lock/input_mic_s", historyToDownFg }};
|
||||
historyRecordLockRound: icon {{ "voice_lock/input_round_s", historyToDownFg }};
|
||||
historyRecordLockRippleMargin: margins(6px, 6px, 6px, 6px);
|
||||
|
||||
historyRecordDelete: IconButton(historyAttach) {
|
||||
@@ -1271,6 +1278,8 @@ historySend: SendButton {
|
||||
}
|
||||
record: historyRecordVoice;
|
||||
recordOver: historyRecordVoiceOver;
|
||||
round: historyRecordRound;
|
||||
roundOver: historyRecordRoundOver;
|
||||
sendDisabledFg: historyComposeIconFg;
|
||||
}
|
||||
|
||||
@@ -1284,9 +1293,7 @@ defaultComposeFilesMenu: IconButton(defaultIconButton) {
|
||||
|
||||
rippleAreaPosition: point(1px, 6px);
|
||||
rippleAreaSize: 42px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
defaultComposeFilesField: InputField(defaultInputField) {
|
||||
textMargins: margins(1px, 26px, 31px, 4px);
|
||||
@@ -1346,9 +1353,7 @@ moreChatsBarClose: IconButton(defaultIconButton) {
|
||||
|
||||
rippleAreaPosition: point(0px, 4px);
|
||||
rippleAreaSize: 40px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
|
||||
reportReasonTopSkip: 8px;
|
||||
@@ -1360,8 +1365,13 @@ reportReasonButton: SettingsButton(defaultSettingsButton) {
|
||||
|
||||
defaultReportBox: ReportBox {
|
||||
button: reportReasonButton;
|
||||
noIconButton: SettingsButton(reportReasonButton) {
|
||||
padding: margins(22px, 7px, 8px, 7px);
|
||||
}
|
||||
label: boxLabel;
|
||||
field: newGroupDescription;
|
||||
dividerBg: boxDividerBg;
|
||||
dividerFg: windowSubTextFg;
|
||||
spam: menuIconDelete;
|
||||
fake: menuIconFake;
|
||||
violence: menuIconViolence;
|
||||
@@ -1509,3 +1519,22 @@ pickLocationChooseOnMap: RoundButton(defaultActiveButton) {
|
||||
sendGifBox: Box(defaultBox) {
|
||||
shadowIgnoreBottomSkip: true;
|
||||
}
|
||||
|
||||
processingVideoTipMaxWidth: 364px;
|
||||
processingVideoTipShift: 8px;
|
||||
processingVideoToast: Toast(defaultToast) {
|
||||
minWidth: 32px;
|
||||
maxWidth: 380px;
|
||||
padding: margins(19px, 17px, 19px, 17px);
|
||||
}
|
||||
processingVideoPreviewSkip: 8px;
|
||||
processingVideoView: RoundButton(defaultActiveButton) {
|
||||
width: -24px;
|
||||
height: 52px;
|
||||
textTop: 17px;
|
||||
textFg: mediaviewTextLinkFg;
|
||||
textFgOver: mediaviewTextLinkFg;
|
||||
textBg: transparent;
|
||||
textBgOver: transparent;
|
||||
ripple: emptyRippleAnimation;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||