Compare commits
210 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9923cc9b31 | ||
|
|
47b32898f5 | ||
|
|
557a2e400e | ||
|
|
84400f5912 | ||
|
|
b28140c4b0 | ||
|
|
08321b8d8b | ||
|
|
bc340d75c4 | ||
|
|
cae18b3320 | ||
|
|
d81b2fbb69 | ||
|
|
02daa2a04b | ||
|
|
1363faddbf | ||
|
|
20a5950f99 | ||
|
|
40bdcd7ebc | ||
|
|
379736a7d1 | ||
|
|
d2234d88b6 | ||
|
|
c2fd4ccd59 | ||
|
|
26c79939e0 | ||
|
|
ffaeb239f0 | ||
|
|
7c8d3452b5 | ||
|
|
e2bca0a7ff | ||
|
|
2e8a03dfd1 | ||
|
|
a919978a37 | ||
|
|
20b5138e00 | ||
|
|
2523d6e8d8 | ||
|
|
03e90840de | ||
|
|
4ab34e3727 | ||
|
|
3129d9f0df | ||
|
|
2b6b1d7611 | ||
|
|
6e9493c725 | ||
|
|
746141a363 | ||
|
|
4942154a9c | ||
|
|
d511f2bb90 | ||
|
|
460b4b2de5 | ||
|
|
ab25cf214c | ||
|
|
b0203af398 | ||
|
|
c00f2f96ec | ||
|
|
2215500c9a | ||
|
|
5cc6275fc3 | ||
|
|
d4810713cb | ||
|
|
1877786707 | ||
|
|
06ec574543 | ||
|
|
f0955f2021 | ||
|
|
400d4b793a | ||
|
|
d6ba092697 | ||
|
|
9ef2f370ac | ||
|
|
234c74a439 | ||
|
|
88f1f8ff22 | ||
|
|
00ee31ce2d | ||
|
|
a5bd4ef6f7 | ||
|
|
c6c2a44e9d | ||
|
|
21b0454461 | ||
|
|
ca4b5edf21 | ||
|
|
9ac739c423 | ||
|
|
bd7a880468 | ||
|
|
807e63d9f2 | ||
|
|
4cedf89e51 | ||
|
|
a736ddb24e | ||
|
|
b48674d302 | ||
|
|
a6a9b16358 | ||
|
|
dfb40dd216 | ||
|
|
85acf051c1 | ||
|
|
86059f2b5e | ||
|
|
4f261ced8e | ||
|
|
feb6107ce6 | ||
|
|
eab41d272b | ||
|
|
38da0e086d | ||
|
|
f874876b00 | ||
|
|
2065616592 | ||
|
|
898edad09b | ||
|
|
49773dde72 | ||
|
|
fa6b4f9b52 | ||
|
|
fa4801ac94 | ||
|
|
9bb2bb09b9 | ||
|
|
e32031963b | ||
|
|
c13221a984 | ||
|
|
688cd70c91 | ||
|
|
a256eb4bc8 | ||
|
|
7d77e8a203 | ||
|
|
47709884dd | ||
|
|
7658d1da3c | ||
|
|
0a3077b9a5 | ||
|
|
e0513f7b7c | ||
|
|
7e7562fdad | ||
|
|
15aefddab4 | ||
|
|
e34b61d56b | ||
|
|
010b5e3949 | ||
|
|
5530df8f2d | ||
|
|
742b819c7e | ||
|
|
2618ee3d75 | ||
|
|
da281c4d3d | ||
|
|
2d07539892 | ||
|
|
923e725e18 | ||
|
|
b2d72e2541 | ||
|
|
7ed10eaacc | ||
|
|
5505a566be | ||
|
|
35c59ad35a | ||
|
|
5e81c65ea6 | ||
|
|
021e275336 | ||
|
|
e540b8cbdc | ||
|
|
ebf6cea2f5 | ||
|
|
a3c110dafa | ||
|
|
ad0c9ebb79 | ||
|
|
d5008fe7ac | ||
|
|
7c3814cdcd | ||
|
|
ed3f246510 | ||
|
|
d9a6d5f508 | ||
|
|
da6d580348 | ||
|
|
0ffa88d0f3 | ||
|
|
149d92d224 | ||
|
|
6f3d19914d | ||
|
|
fc759ac688 | ||
|
|
e03eaaaa98 | ||
|
|
857f56d5b4 | ||
|
|
638cf237c4 | ||
|
|
01b50a8460 | ||
|
|
b519b6bf4c | ||
|
|
b10bf0e12c | ||
|
|
4d43830c3b | ||
|
|
65ad8e6ac1 | ||
|
|
56cbde93da | ||
|
|
90ef0e4969 | ||
|
|
668a3308be | ||
|
|
ba83836922 | ||
|
|
14f937cb02 | ||
|
|
733cad798b | ||
|
|
8a6b3027f5 | ||
|
|
31db1804c8 | ||
|
|
f8c962712b | ||
|
|
a202174d12 | ||
|
|
3399397a76 | ||
|
|
c655f78780 | ||
|
|
73d8530c13 | ||
|
|
610c46e26f | ||
|
|
96805b62b2 | ||
|
|
4762c7a4fd | ||
|
|
0277d765bb | ||
|
|
20d4d00634 | ||
|
|
c5fa4aae62 | ||
|
|
f72092a261 | ||
|
|
33b266175d | ||
|
|
c9a98ae723 | ||
|
|
bd42c23999 | ||
|
|
f658cb7e99 | ||
|
|
fa26afaf9a | ||
|
|
b648387e96 | ||
|
|
8d2ebdbb99 | ||
|
|
a0d5456a4d | ||
|
|
c20ed7c7a8 | ||
|
|
ef543d040e | ||
|
|
27063167ae | ||
|
|
8a1118d9bb | ||
|
|
1e2e007d38 | ||
|
|
09124f6424 | ||
|
|
cece9cf09b | ||
|
|
60cc232884 | ||
|
|
ba8673af5e | ||
|
|
77772caabb | ||
|
|
e22bb40dd1 | ||
|
|
bd089f20a8 | ||
|
|
64bd4f0926 | ||
|
|
165d3143de | ||
|
|
21fd381778 | ||
|
|
285ce81b7b | ||
|
|
e7f85f7255 | ||
|
|
12d8d193a1 | ||
|
|
c18313614b | ||
|
|
f1092753fc | ||
|
|
5fc7caeec9 | ||
|
|
e20840b4d4 | ||
|
|
5f53dfda0e | ||
|
|
630e73fa23 | ||
|
|
92a9ba2ba0 | ||
|
|
4d4d75addf | ||
|
|
42fe80b5e2 | ||
|
|
96793179a3 | ||
|
|
fc19ce5a9b | ||
|
|
31fa6d24f4 | ||
|
|
7b005c64e0 | ||
|
|
82d68f5b98 | ||
|
|
2259f747f1 | ||
|
|
c793537d96 | ||
|
|
e568f7ab01 | ||
|
|
11b711c43f | ||
|
|
d1a81a83b4 | ||
|
|
42ca06d33c | ||
|
|
184ebc865c | ||
|
|
c8c3f43853 | ||
|
|
9105677325 | ||
|
|
7c2d3a8855 | ||
|
|
888306c017 | ||
|
|
6ff30c643a | ||
|
|
5f8fca0355 | ||
|
|
4933fbb74a | ||
|
|
51010e864c | ||
|
|
24ee944689 | ||
|
|
2c0a38d356 | ||
|
|
e05f570e1a | ||
|
|
2846b2f7a5 | ||
|
|
c126a1e56e | ||
|
|
f0f7318978 | ||
|
|
fede1ff173 | ||
|
|
94e4a8981f | ||
|
|
1e7117dd67 | ||
|
|
65ddbec794 | ||
|
|
8adec5fcfd | ||
|
|
f82089cbfe | ||
|
|
23d9ca896e | ||
|
|
5f3c957b1d | ||
|
|
b9181db407 | ||
|
|
be8d91055a |
21
.github/stale.yml
vendored
@@ -1,21 +0,0 @@
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 180
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 30
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels: [ "enhancement" ]
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: stale
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: |
|
||||
Hey there!
|
||||
|
||||
This issue was inactive for a long time and will be automatically closed in 30 days if there isn't any further activity. We therefore assume that the user has lost interest or resolved the problem on their own.
|
||||
|
||||
Don't worry though; if this is an error, let us know with a comment and we'll be happy to reopen the issue.
|
||||
|
||||
Thanks!
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
||||
# Process only issues
|
||||
only: issues
|
||||
8
.github/workflows/docker.yml
vendored
@@ -25,16 +25,14 @@ jobs:
|
||||
submodules: recursive
|
||||
|
||||
- name: First set up.
|
||||
run: |
|
||||
curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -
|
||||
echo $HOME/.poetry/bin >> $GITHUB_PATH
|
||||
run: curl -sSL https://install.python-poetry.org | python3 -
|
||||
|
||||
- name: Docker image build.
|
||||
run: |
|
||||
cd Telegram/build/docker/centos_env
|
||||
poetry install
|
||||
DEBUG= poetry run gen_dockerfile | docker build -t $IMAGE_TAG -
|
||||
|
||||
DEBUG= poetry run gen_dockerfile | docker buildx build -t $IMAGE_TAG -
|
||||
|
||||
- name: Push the Docker image.
|
||||
if: ${{ github.ref_name == github.event.repository.default_branch }}
|
||||
run: |
|
||||
|
||||
25
.github/workflows/stale.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: 'Close stale issues and PRs'
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 1 * * *'
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v5
|
||||
with:
|
||||
stale-issue-message: |
|
||||
Hey there!
|
||||
|
||||
This issue was inactive for a long time and will be automatically closed in 30 days if there isn't any further activity. We therefore assume that the user has lost interest or resolved the problem on their own.
|
||||
|
||||
Don't worry though; if this is an error, let us know with a comment and we'll be happy to reopen the issue.
|
||||
|
||||
Thanks!
|
||||
stale-issue-label: 'stale'
|
||||
exempt-issue-labels: 'enhancement'
|
||||
days-before-stale: 180
|
||||
days-before-close: 30
|
||||
days-before-pr-stale: -1
|
||||
operations-per-run: 1000
|
||||
15
.github/workflows/win.yml
vendored
@@ -79,23 +79,12 @@ jobs:
|
||||
submodules: recursive
|
||||
path: ${{ env.TBUILD }}\${{ env.REPO_NAME }}
|
||||
|
||||
- name: Choco installs.
|
||||
run: |
|
||||
choco install --no-progress -y nasm strawberryperl yasm jom ninja
|
||||
py -m pip install pywin32
|
||||
|
||||
- name: Install msys64.
|
||||
run: |
|
||||
mkdir %TBUILD%\ThirdParty
|
||||
xcopy /E /I C:\msys64 %TBUILD%\ThirdParty\msys64
|
||||
- name: Python installs.
|
||||
run: py -m pip install pywin32
|
||||
|
||||
- name: Set up environment paths.
|
||||
shell: bash
|
||||
run: |
|
||||
echo "C:\\Strawberry\\perl\\bin\\" >> $GITHUB_PATH
|
||||
echo "C:\\Program Files\\NASM\\" >> $GITHUB_PATH
|
||||
echo "C:\\ProgramData\\chocolatey\\lib\\ninja\\tools\\" >> $GITHUB_PATH
|
||||
|
||||
echo "CACHE_KEY=$(sha256sum $TBUILD/$REPO_NAME/$PREPARE_PATH | awk '{ print $1 }')" >> $GITHUB_ENV
|
||||
|
||||
echo "Configurate git for cherry-picks."
|
||||
|
||||
3
.gitmodules
vendored
@@ -79,9 +79,6 @@
|
||||
[submodule "Telegram/lib_webview"]
|
||||
path = Telegram/lib_webview
|
||||
url = https://github.com/desktop-app/lib_webview.git
|
||||
[submodule "Telegram/lib_waylandshells"]
|
||||
path = Telegram/lib_waylandshells
|
||||
url = https://github.com/desktop-app/lib_waylandshells.git
|
||||
[submodule "Telegram/ThirdParty/jemalloc"]
|
||||
path = Telegram/ThirdParty/jemalloc
|
||||
url = https://github.com/jemalloc/jemalloc
|
||||
|
||||
@@ -48,6 +48,15 @@ include(cmake/target_prepare_qrc.cmake)
|
||||
|
||||
include(cmake/options.cmake)
|
||||
|
||||
if (NOT DESKTOP_APP_USE_PACKAGED)
|
||||
if (WIN32)
|
||||
set(qt_version 5.15.4)
|
||||
elseif (APPLE)
|
||||
set(qt_version 6.3.1)
|
||||
else()
|
||||
set(qt_version 6.4.0)
|
||||
endif()
|
||||
endif()
|
||||
include(cmake/external/qt/package.cmake)
|
||||
|
||||
set(desktop_app_skip_libs
|
||||
|
||||
@@ -18,9 +18,6 @@ endif()
|
||||
add_subdirectory(lib_storage)
|
||||
add_subdirectory(lib_lottie)
|
||||
add_subdirectory(lib_qr)
|
||||
if (LINUX AND NOT DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION)
|
||||
add_subdirectory(lib_waylandshells)
|
||||
endif()
|
||||
add_subdirectory(lib_webrtc)
|
||||
add_subdirectory(lib_webview)
|
||||
add_subdirectory(codegen)
|
||||
@@ -82,9 +79,9 @@ PRIVATE
|
||||
desktop-app::lib_webview
|
||||
desktop-app::lib_ffmpeg
|
||||
desktop-app::lib_stripe
|
||||
desktop-app::external_lz4
|
||||
desktop-app::external_rlottie
|
||||
desktop-app::external_zlib
|
||||
desktop-app::external_kcoreaddons
|
||||
desktop-app::external_qt_static_plugins
|
||||
desktop-app::external_qt
|
||||
desktop-app::external_qr_code_generator
|
||||
@@ -139,6 +136,8 @@ PRIVATE
|
||||
api/api_polls.h
|
||||
api/api_premium.cpp
|
||||
api/api_premium.h
|
||||
api/api_premium_option.cpp
|
||||
api/api_premium_option.h
|
||||
api/api_report.cpp
|
||||
api/api_report.h
|
||||
api/api_ringtones.cpp
|
||||
@@ -464,6 +463,8 @@ PRIVATE
|
||||
data/data_download_manager.h
|
||||
data/data_drafts.cpp
|
||||
data/data_drafts.h
|
||||
data/data_emoji_statuses.cpp
|
||||
data/data_emoji_statuses.h
|
||||
data/data_folder.cpp
|
||||
data/data_folder.h
|
||||
data/data_file_click_handler.cpp
|
||||
@@ -487,6 +488,8 @@ PRIVATE
|
||||
data/data_media_types.h
|
||||
# data/data_messages.cpp
|
||||
# data/data_messages.h
|
||||
data/data_message_reaction_id.cpp
|
||||
data/data_message_reaction_id.h
|
||||
data/data_message_reactions.cpp
|
||||
data/data_message_reactions.h
|
||||
data/data_msg_id.h
|
||||
@@ -614,54 +617,69 @@ PRIVATE
|
||||
history/view/controls/history_view_voice_record_bar.h
|
||||
history/view/controls/history_view_voice_record_button.cpp
|
||||
history/view/controls/history_view_voice_record_button.h
|
||||
history/view/media/history_view_call.h
|
||||
history/view/media/history_view_call.cpp
|
||||
history/view/media/history_view_contact.h
|
||||
history/view/media/history_view_call.h
|
||||
history/view/media/history_view_contact.cpp
|
||||
history/view/media/history_view_custom_emoji.h
|
||||
history/view/media/history_view_contact.h
|
||||
history/view/media/history_view_custom_emoji.cpp
|
||||
history/view/media/history_view_dice.h
|
||||
history/view/media/history_view_custom_emoji.h
|
||||
history/view/media/history_view_dice.cpp
|
||||
history/view/media/history_view_document.h
|
||||
history/view/media/history_view_dice.h
|
||||
history/view/media/history_view_document.cpp
|
||||
history/view/media/history_view_file.h
|
||||
history/view/media/history_view_document.h
|
||||
history/view/media/history_view_extended_preview.cpp
|
||||
history/view/media/history_view_extended_preview.h
|
||||
history/view/media/history_view_file.cpp
|
||||
history/view/media/history_view_game.h
|
||||
history/view/media/history_view_file.h
|
||||
history/view/media/history_view_game.cpp
|
||||
history/view/media/history_view_gif.h
|
||||
history/view/media/history_view_game.h
|
||||
history/view/media/history_view_gif.cpp
|
||||
history/view/media/history_view_invoice.h
|
||||
history/view/media/history_view_gif.h
|
||||
history/view/media/history_view_invoice.cpp
|
||||
history/view/media/history_view_large_emoji.h
|
||||
history/view/media/history_view_invoice.h
|
||||
history/view/media/history_view_large_emoji.cpp
|
||||
history/view/media/history_view_location.h
|
||||
history/view/media/history_view_large_emoji.h
|
||||
history/view/media/history_view_location.cpp
|
||||
history/view/media/history_view_media.h
|
||||
history/view/media/history_view_location.h
|
||||
history/view/media/history_view_media.cpp
|
||||
history/view/media/history_view_media_common.h
|
||||
history/view/media/history_view_media.h
|
||||
history/view/media/history_view_media_common.cpp
|
||||
history/view/media/history_view_media_grouped.h
|
||||
history/view/media/history_view_media_common.h
|
||||
history/view/media/history_view_media_grouped.cpp
|
||||
history/view/media/history_view_media_unwrapped.h
|
||||
history/view/media/history_view_media_grouped.h
|
||||
history/view/media/history_view_media_unwrapped.cpp
|
||||
history/view/media/history_view_photo.h
|
||||
history/view/media/history_view_media_unwrapped.h
|
||||
history/view/media/history_view_photo.cpp
|
||||
history/view/media/history_view_poll.h
|
||||
history/view/media/history_view_photo.h
|
||||
history/view/media/history_view_poll.cpp
|
||||
history/view/media/history_view_service_media_gift.h
|
||||
history/view/media/history_view_poll.h
|
||||
history/view/media/history_view_service_media_gift.cpp
|
||||
history/view/media/history_view_slot_machine.h
|
||||
history/view/media/history_view_service_media_gift.h
|
||||
history/view/media/history_view_slot_machine.cpp
|
||||
history/view/media/history_view_sticker.h
|
||||
history/view/media/history_view_slot_machine.h
|
||||
history/view/media/history_view_sticker.cpp
|
||||
history/view/media/history_view_theme_document.h
|
||||
history/view/media/history_view_sticker.h
|
||||
history/view/media/history_view_sticker_player.cpp
|
||||
history/view/media/history_view_sticker_player.h
|
||||
history/view/media/history_view_sticker_player_abstract.h
|
||||
history/view/media/history_view_theme_document.cpp
|
||||
history/view/media/history_view_web_page.h
|
||||
history/view/media/history_view_theme_document.h
|
||||
history/view/media/history_view_web_page.cpp
|
||||
history/view/reactions/message_reactions_list.cpp
|
||||
history/view/reactions/message_reactions_list.h
|
||||
history/view/reactions/message_reactions_selector.cpp
|
||||
history/view/reactions/message_reactions_selector.h
|
||||
history/view/media/history_view_web_page.h
|
||||
history/view/reactions/history_view_reactions.cpp
|
||||
history/view/reactions/history_view_reactions.h
|
||||
history/view/reactions/history_view_reactions_animation.cpp
|
||||
history/view/reactions/history_view_reactions_animation.h
|
||||
history/view/reactions/history_view_reactions_button.cpp
|
||||
history/view/reactions/history_view_reactions_button.h
|
||||
history/view/reactions/history_view_reactions_list.cpp
|
||||
history/view/reactions/history_view_reactions_list.h
|
||||
history/view/reactions/history_view_reactions_selector.cpp
|
||||
history/view/reactions/history_view_reactions_selector.h
|
||||
history/view/reactions/history_view_reactions_strip.cpp
|
||||
history/view/reactions/history_view_reactions_strip.h
|
||||
history/view/reactions/history_view_reactions_tabs.cpp
|
||||
history/view/reactions/history_view_reactions_tabs.h
|
||||
history/view/history_view_bottom_info.cpp
|
||||
history/view/history_view_bottom_info.h
|
||||
history/view/history_view_contact_status.cpp
|
||||
@@ -692,12 +710,6 @@ PRIVATE
|
||||
history/view/history_view_pinned_tracker.h
|
||||
history/view/history_view_quick_action.cpp
|
||||
history/view/history_view_quick_action.h
|
||||
history/view/history_view_react_animation.cpp
|
||||
history/view/history_view_react_animation.h
|
||||
history/view/history_view_react_button.cpp
|
||||
history/view/history_view_react_button.h
|
||||
history/view/history_view_reactions.cpp
|
||||
history/view/history_view_reactions.h
|
||||
history/view/history_view_replies_section.cpp
|
||||
history/view/history_view_replies_section.h
|
||||
history/view/history_view_requests_bar.cpp
|
||||
@@ -797,8 +809,12 @@ PRIVATE
|
||||
info/polls/info_polls_results_widget.h
|
||||
info/profile/info_profile_actions.cpp
|
||||
info/profile/info_profile_actions.h
|
||||
info/profile/info_profile_badge.cpp
|
||||
info/profile/info_profile_badge.h
|
||||
info/profile/info_profile_cover.cpp
|
||||
info/profile/info_profile_cover.h
|
||||
info/profile/info_profile_emoji_status_panel.cpp
|
||||
info/profile/info_profile_emoji_status_panel.h
|
||||
info/profile/info_profile_icon.cpp
|
||||
info/profile/info_profile_icon.h
|
||||
info/profile/info_profile_inner_widget.cpp
|
||||
@@ -1459,7 +1475,6 @@ else()
|
||||
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
desktop-app::lib_waylandshells
|
||||
desktop-app::external_wayland_client
|
||||
)
|
||||
endif()
|
||||
|
||||
BIN
Telegram/Resources/icons/chat/reactions_bubble.png
Normal file
|
After Width: | Height: | Size: 331 B |
BIN
Telegram/Resources/icons/chat/reactions_bubble@2x.png
Normal file
|
After Width: | Height: | Size: 531 B |
BIN
Telegram/Resources/icons/chat/reactions_bubble@3x.png
Normal file
|
After Width: | Height: | Size: 772 B |
BIN
Telegram/Resources/icons/chat/reactions_bubble_shadow.png
Normal file
|
After Width: | Height: | Size: 496 B |
BIN
Telegram/Resources/icons/chat/reactions_bubble_shadow@2x.png
Normal file
|
After Width: | Height: | Size: 967 B |
BIN
Telegram/Resources/icons/chat/reactions_bubble_shadow@3x.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
Telegram/Resources/icons/chat/reactions_expand_panel.png
Normal file
|
After Width: | Height: | Size: 244 B |
BIN
Telegram/Resources/icons/chat/reactions_expand_panel@2x.png
Normal file
|
After Width: | Height: | Size: 337 B |
BIN
Telegram/Resources/icons/chat/reactions_expand_panel@3x.png
Normal file
|
After Width: | Height: | Size: 465 B |
BIN
Telegram/Resources/icons/chat/reactions_round_big.png
Normal file
|
After Width: | Height: | Size: 398 B |
BIN
Telegram/Resources/icons/chat/reactions_round_big@2x.png
Normal file
|
After Width: | Height: | Size: 691 B |
BIN
Telegram/Resources/icons/chat/reactions_round_big@3x.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
Telegram/Resources/icons/chat/reactions_round_small.png
Normal file
|
After Width: | Height: | Size: 359 B |
BIN
Telegram/Resources/icons/chat/reactions_round_small@2x.png
Normal file
|
After Width: | Height: | Size: 670 B |
BIN
Telegram/Resources/icons/chat/reactions_round_small@3x.png
Normal file
|
After Width: | Height: | Size: 947 B |
BIN
Telegram/Resources/icons/settings/premium/arrow.png
Normal file
|
After Width: | Height: | Size: 268 B |
BIN
Telegram/Resources/icons/settings/premium/arrow@2x.png
Normal file
|
After Width: | Height: | Size: 470 B |
BIN
Telegram/Resources/icons/settings/premium/arrow@3x.png
Normal file
|
After Width: | Height: | Size: 610 B |
BIN
Telegram/Resources/icons/settings/premium/status.png
Normal file
|
After Width: | Height: | Size: 497 B |
BIN
Telegram/Resources/icons/settings/premium/status@2x.png
Normal file
|
After Width: | Height: | Size: 787 B |
BIN
Telegram/Resources/icons/settings/premium/status@3x.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
@@ -1,9 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg version="1.1" viewBox="0 0 76 73" xmlns="http://www.w3.org/2000/svg">
|
||||
<title>Star</title>
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<g transform="translate(-5 -5)" fill="#fff">
|
||||
<path d="m40.806 66.037-17.389 10.627c-1.5943 0.97436-3.6766 0.4718-4.651-1.1225-0.47558-0.77817-0.61685-1.7154-0.39178-2.5992l2.8114-11.039c0.91458-3.5913 3.3384-6.6111 6.6467-8.281l15.265-7.7056c0.84279-0.42542 1.1811-1.4535 0.7557-2.2963-0.3335-0.66067-1.0543-1.0317-1.7857-0.91925l-17.288 2.6578c-4.0448 0.62183-8.1617-0.52596-11.301-3.1508l-7.112-5.9459c-1.4335-1.1985-1.624-3.3321-0.42557-4.7656 0.58391-0.69842 1.4246-1.1321 2.3322-1.2029l20.357-1.5894c1.3848-0.10811 2.5919-0.98375 3.1246-2.2665l7.8535-18.913c0.71656-1.7256 2.6963-2.5436 4.4219-1.8271 0.82676 0.34331 1.4838 1.0003 1.8271 1.8271l7.8535 18.913c0.53267 1.2828 1.7398 2.1584 3.1246 2.2665l20.456 1.5971c1.8628 0.14544 3.255 1.7734 3.1096 3.6363-0.070088 0.89771-0.49515 1.7304-1.1811 2.3138l-15.589 13.259c-1.0616 0.90287-1.5248 2.3263-1.1979 3.681l4.7945 19.87c0.43827 1.8163-0.67888 3.6441-2.4952 4.0824-0.87218 0.21045-1.7922 0.065833-2.5578-0.40204l-17.515-10.704c-1.1827-0.72283-2.6706-0.72283-3.8533 0z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<svg width="84" height="81" viewBox="0 0 84 81" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g transform="translate(-190.000000, -73.000000)" fill="#fff"><defs>gradientPlaceholder</defs><path d="M217.584175 95.7198452l10.102137-20.1302773C228.851735 73.2672578 231.669488 72.3342655 233.979937 73.5056713 234.886993 73.9655512 235.620771 74.7092927 236.070698 75.6248193l9.540513 19.41335C246.390574 96.6240434 247.914247 97.7068442 249.661599 97.9165844L269.640156 100.314675C272.352386 100.640233 274.288515 103.114133 273.96462 105.840286 273.831224 106.963049 273.321003 108.006556 272.518147 108.798619l-15.7911 15.578821C256.089452 125.006464 255.780497 125.898568 255.89167 126.789576L258.515919 147.821772C258.891602 150.832711 256.767771 153.579675 253.772209 153.957287 252.636962 154.100393 251.485656 153.881854 250.480518 153.332463L233.796907 144.21349C232.589892 143.553757 231.136827 143.534799 229.913183 144.162822l-17.2854 8.871545C210.195073 154.282929 207.215982 153.312865 205.973797 150.867667 205.509027 149.952785 205.337643 148.915649 205.483212 147.898872L206.864713 138.249309C207.540411 133.529666 210.458736 129.435705 214.68762 127.274973L233.885915 117.465678C234.398537 117.203755 234.602854 116.57373 234.342269 116.058476 234.140381 115.659283 233.708181 115.433429 233.267119 115.496635l-23.479483 3.364749C206.198378 119.375746 202.558336 118.361996 199.744254 116.064306L191.92377 109.678904C189.692048 107.856709 189.352516 104.561073 191.165405 102.317895 192.008458 101.274743 193.218498 100.597614 194.544752 100.426837L214.594132 97.8451554C215.880032 97.679575 217.000319 96.8832795 217.584175 95.7198452z"/></g></g></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.7 KiB |
@@ -17,6 +17,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_menu_night_mode" = "Night Mode";
|
||||
"lng_menu_add_account" = "Add Account";
|
||||
"lng_menu_activate" = "Activate";
|
||||
"lng_menu_set_status" = "Set Emoji Status";
|
||||
"lng_menu_change_status" = "Change Emoji Status";
|
||||
|
||||
"lng_disable_notifications_from_tray" = "Disable notifications";
|
||||
"lng_enable_notifications_from_tray" = "Enable notifications";
|
||||
@@ -267,7 +269,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_proxy_unsupported" = "Your Telegram Desktop version doesn't support this proxy type or the proxy link is invalid. Please update Telegram Desktop to the latest version.";
|
||||
|
||||
"lng_edit_deleted" = "This message was deleted";
|
||||
"lng_edit_too_long" = "Your message text is too long";
|
||||
"lng_edit_limit_reached#one" = "You've reached the message text limit. Please make the text shorter by {count} character.";
|
||||
"lng_edit_limit_reached#other" = "You've reached the message text limit. Please make the text shorter by {count} characters.";
|
||||
"lng_edit_message" = "Edit message";
|
||||
"lng_edit_message_text" = "New message text...";
|
||||
"lng_deleted" = "Deleted Account";
|
||||
@@ -511,6 +514,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_send_cmdenter" = "Send by Cmd+Enter";
|
||||
"lng_settings_chat_quick_action_reply" = "Reply with double click";
|
||||
"lng_settings_chat_quick_action_react" = "Send reaction with double click";
|
||||
"lng_settings_chat_corner_reaction" = "Reaction button on messages";
|
||||
|
||||
"lng_settings_chat_reactions_title" = "Quick Reaction";
|
||||
"lng_settings_chat_reactions_subtitle" = "Choose your favorite reaction";
|
||||
@@ -1194,13 +1198,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_manage_peer_permissions" = "Permissions";
|
||||
"lng_manage_peer_invite_links" = "Invite links";
|
||||
"lng_manage_peer_reactions" = "Reactions";
|
||||
"lng_manage_peer_reactions_on" = "All";
|
||||
"lng_manage_peer_reactions_off" = "Off";
|
||||
"lng_manage_peer_requests" = "Member Requests";
|
||||
"lng_manage_peer_requests_channel" = "Subscriber Requests";
|
||||
|
||||
"lng_manage_peer_reactions_enable" = "Enable Reactions";
|
||||
"lng_manage_peer_reactions_about" = "Allow members to react to group messages.";
|
||||
"lng_manage_peer_reactions_about_channel" = "Allow subscribers to react to channel posts.";
|
||||
"lng_manage_peer_reactions_all" = "All reactions";
|
||||
"lng_manage_peer_reactions_all_about" = "Members of the group can use any emoji as reactions to messages.";
|
||||
"lng_manage_peer_reactions_some" = "Some reactions";
|
||||
"lng_manage_peer_reactions_some_about" = "You can select emoji that will allow members of your group to react to messages.";
|
||||
"lng_manage_peer_reactions_none" = "No reactions";
|
||||
"lng_manage_peer_reactions_none_about" = "Members of the group can't add any reactions to messages.";
|
||||
"lng_manage_peer_reactions_some_title" = "Only allow these reactions";
|
||||
"lng_manage_peer_reactions_available" = "Available reactions";
|
||||
|
||||
"lng_manage_peer_group_type" = "Group type";
|
||||
@@ -1700,6 +1711,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_premium_unlock_reactions" = "Unlock Premium Reactions";
|
||||
"lng_premium_unlock_stickers" = "Unlock Premium Stickers";
|
||||
"lng_premium_unlock_emoji" = "Unlock Animated Emoji";
|
||||
"lng_premium_unlock_status" = "Unlock Emoji Status";
|
||||
|
||||
"lng_premium_subscribe_months_12" = "Annual";
|
||||
"lng_premium_subscribe_months_6" = "Semiannual";
|
||||
"lng_premium_subscribe_months_1" = "Monthly";
|
||||
"lng_premium_subscribe_total" = "{cost} per year";
|
||||
"lng_premium_subscribe_button" = "Subscribe for {cost} per month";
|
||||
|
||||
"lng_premium_emoji_status_title" = "{user} set this emoji from {link} as their current status.";
|
||||
"lng_premium_emoji_status_title_colored" = "{user} set this emoji as their current status.";
|
||||
"lng_premium_emoji_status_about" = "Emoji status is a premium feature. Other features included in **Telegram Premium**:";
|
||||
"lng_premium_emoji_status_button" = "Unlock Emoji Status";
|
||||
|
||||
"lng_premium_summary_user_title" = "{user} is a subscriber of Telegram Premium.";
|
||||
"lng_premium_summary_user_about" = "Owners of Telegram Premium accounts have exclusive access to multiple additional features.";
|
||||
"lng_premium_summary_user_button" = "Learn More";
|
||||
|
||||
"lng_premium_summary_title" = "Telegram Premium";
|
||||
"lng_premium_summary_top_about" = "Go **beyond the limits**, get **exclusive features** and support us by subscribing to **Telegram Premium**.";
|
||||
@@ -1718,8 +1745,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_premium_summary_about_voice_to_text" = "Ability to read the transcript of any incoming voice message.";
|
||||
"lng_premium_summary_subtitle_no_ads" = "No Ads";
|
||||
"lng_premium_summary_about_no_ads" = "No more ads in public channels where Telegram sometimes shows ads.";
|
||||
"lng_premium_summary_subtitle_unique_reactions" = "Unique Reactions";
|
||||
"lng_premium_summary_about_unique_reactions" = "Additional animated reactions on messages available only to the Premium subscribers.";
|
||||
"lng_premium_summary_subtitle_emoji_status" = "Emoji Status";
|
||||
"lng_premium_summary_about_emoji_status" = "Add any of thousands emoji next to your name to display current activity.";
|
||||
"lng_premium_summary_subtitle_infinite_reactions" = "Infinite Reactions";
|
||||
"lng_premium_summary_about_infinite_reactions" = "React with thousands of emoji — with multiple reactions per message.";
|
||||
"lng_premium_summary_subtitle_premium_stickers" = "Premium Stickers";
|
||||
"lng_premium_summary_about_premium_stickers" = "Exclusive enlarged stickers featuring additional effects, updated monthly.";
|
||||
"lng_premium_summary_subtitle_animated_emoji" = "Animated Emoji";
|
||||
@@ -1789,6 +1818,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_accounts_limit1#other" = "You have reached the limit of **{count}** connected accounts.";
|
||||
"lng_accounts_limit2" = "You can free one space by subscribing to **Telegram Premium** with one of these connected accounts:";
|
||||
|
||||
"lng_emoji_status_for_title" = "Set Status for...";
|
||||
"lng_emoji_status_for_submit" = "Set Status";
|
||||
"lng_emoji_status_menu_duration_any" = "Set Status for {duration}";
|
||||
|
||||
"lng_group_about_header" = "You have created a group.";
|
||||
"lng_group_about_text" = "Groups can have:";
|
||||
"lng_group_about1" = "Up to 100,000 members";
|
||||
@@ -1884,6 +1917,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_new_contact_unarchive" = "Unarchive";
|
||||
"lng_new_contact_from_request_channel" = "{user} is an admin of {name}, a channel you requested to join.";
|
||||
"lng_new_contact_from_request_group" = "{user} is an admin of {name}, a group you requested to join.";
|
||||
"lng_new_contact_about_status" = "This account uses {emoji} as a custom status next to its\nname. Such emoji statuses are available to all\nsubscribers of {link}.";
|
||||
"lng_new_contact_about_status_link" = "Telegram Premium";
|
||||
"lng_from_request_title_channel" = "Chat with channel's admin";
|
||||
"lng_from_request_title_group" = "Chat with group's admin";
|
||||
"lng_from_request_body" = "You received this message because you requested to join {name} on {date}.";
|
||||
@@ -1913,6 +1948,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_send_anonymous_ph" = "Send anonymously...";
|
||||
"lng_send_as_title" = "Send message as...";
|
||||
"lng_send_as_anonymous_admin" = "Anonymous admin";
|
||||
"lng_send_as_premium_required" = "Subscribe to {link} to be able to comment on behalf of your channels in group chats.";
|
||||
"lng_send_as_premium_required_link" = "Telegram Premium";
|
||||
"lng_record_cancel" = "Release outside this field to cancel";
|
||||
"lng_record_lock_cancel_sure" = "Are you sure you want to stop recording and discard your voice message?";
|
||||
"lng_record_listen_cancel_sure" = "Are you sure you want to discard your recorded voice message?";
|
||||
@@ -2168,6 +2205,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_context_animated_emoji" = "This message contains emoji from **{name} pack**.";
|
||||
"lng_context_animated_emoji_many#one" = "This message contains emoji from **{count} pack**.";
|
||||
"lng_context_animated_emoji_many#other" = "This message contains emoji from **{count} packs**.";
|
||||
"lng_context_animated_reaction" = "This reaction is from **{name} pack**.";
|
||||
"lng_context_animated_reactions" = "Reactions contain emoji from **{name} pack**.";
|
||||
"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_downloads_section" = "Downloads";
|
||||
"lng_downloads_view_in_chat" = "View in chat";
|
||||
@@ -2783,6 +2824,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_slowmode_enabled"= "Slow mode is enabled. You can send your next message in {left}.";
|
||||
"lng_slowmode_no_many" = "Slow mode is enabled. You can't send more than one message at a time.";
|
||||
"lng_slowmode_too_long" = "Sorry, this text is too long to send as one message.\n\nSlow mode is enabled. You can't send more than one message at a time.";
|
||||
"lng_slowmode_seconds#one" = "{count} second";
|
||||
"lng_slowmode_seconds#other" = "{count} seconds";
|
||||
|
||||
"lng_rights_gigagroup_title" = "Broadcast group";
|
||||
"lng_rights_gigagroup_convert" = "Convert to Broadcast Group";
|
||||
@@ -2991,6 +3034,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_admin_log_messages_ttl_removed" = "{from} disabled messages auto-deletion after {duration}";
|
||||
"lng_admin_log_reactions_disabled" = "{from} disabled reactions";
|
||||
"lng_admin_log_reactions_updated" = "{from} updated the list of allowed reactions to: {emoji}";
|
||||
"lng_admin_log_reactions_allowed_all" = "{from} allowed all reactions";
|
||||
"lng_admin_log_reactions_allowed_official" = "{from} allowed all official reactions";
|
||||
"lng_admin_log_edited_invite_link" = "edited invite link {link}";
|
||||
"lng_admin_log_invite_link_expire_date" = "Expire date: {previous} -> {limit}";
|
||||
"lng_admin_log_invite_link_usage_limit" = "Usage limit: {previous} -> {limit}";
|
||||
|
||||
@@ -68,7 +68,7 @@ inputMediaVenue#c13d1c11 geo_point:InputGeoPoint title:string address:string pro
|
||||
inputMediaPhotoExternal#e5bbfe1a flags:# url:string ttl_seconds:flags.0?int = InputMedia;
|
||||
inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int = InputMedia;
|
||||
inputMediaGame#d33f43f3 id:InputGame = InputMedia;
|
||||
inputMediaInvoice#d9799874 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:flags.1?string = InputMedia;
|
||||
inputMediaInvoice#8eb5a6d5 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:flags.1?string extended_media:flags.2?InputMedia = InputMedia;
|
||||
inputMediaGeoLive#971fa843 flags:# stopped:flags.0?true geo_point:InputGeoPoint heading:flags.2?int period:flags.1?int proximity_notification_radius:flags.3?int = InputMedia;
|
||||
inputMediaPoll#f94e5f1 flags:# poll:Poll correct_answers:flags.0?Vector<bytes> solution:flags.1?string solution_entities:flags.1?Vector<MessageEntity> = InputMedia;
|
||||
inputMediaDice#e66fbf7b emoticon:string = InputMedia;
|
||||
@@ -110,7 +110,7 @@ storage.fileMp4#b3cea0e4 = storage.FileType;
|
||||
storage.fileWebp#1081464c = storage.FileType;
|
||||
|
||||
userEmpty#d3bc4b7a id:long = User;
|
||||
user#3ff6ecb0 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true premium:flags.28?true attach_menu_enabled:flags.29?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector<RestrictionReason> bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User;
|
||||
user#5d99adee flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true premium:flags.28?true attach_menu_enabled:flags.29?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector<RestrictionReason> bot_inline_placeholder:flags.19?string lang_code:flags.22?string emoji_status:flags.30?EmojiStatus = User;
|
||||
|
||||
userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto;
|
||||
userProfilePhoto#82d1f706 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = UserProfilePhoto;
|
||||
@@ -128,8 +128,8 @@ chatForbidden#6592a1a7 id:long title:string = Chat;
|
||||
channel#8261ac61 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true join_to_send:flags.28?true join_request:flags.29?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector<RestrictionReason> admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat;
|
||||
channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat;
|
||||
|
||||
chatFull#d18ee226 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector<BotInfo> pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector<long> available_reactions:flags.18?Vector<string> = ChatFull;
|
||||
channelFull#ea68a619 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector<string> groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector<long> default_send_as:flags.29?Peer available_reactions:flags.30?Vector<string> = ChatFull;
|
||||
chatFull#c9d31138 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector<BotInfo> pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector<long> available_reactions:flags.18?ChatReactions = ChatFull;
|
||||
channelFull#f2355507 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector<string> groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector<long> default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions = ChatFull;
|
||||
|
||||
chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant;
|
||||
chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant;
|
||||
@@ -154,7 +154,7 @@ messageMediaDocument#9cb070d7 flags:# nopremium:flags.3?true document:flags.0?Do
|
||||
messageMediaWebPage#a32dd600 webpage:WebPage = MessageMedia;
|
||||
messageMediaVenue#2ec0533f geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string = MessageMedia;
|
||||
messageMediaGame#fdb19008 game:Game = MessageMedia;
|
||||
messageMediaInvoice#84551347 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string = MessageMedia;
|
||||
messageMediaInvoice#f6a548d3 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string extended_media:flags.4?MessageExtendedMedia = MessageMedia;
|
||||
messageMediaGeoLive#b940c666 flags:# geo:GeoPoint heading:flags.0?int period:int proximity_notification_radius:flags.1?int = MessageMedia;
|
||||
messageMediaPoll#4bd6e798 poll:Poll results:PollResults = MessageMedia;
|
||||
messageMediaDice#3f7ee58b value:int emoticon:string = MessageMedia;
|
||||
@@ -324,7 +324,7 @@ updateChannelMessageViews#f226ac08 channel_id:long id:int views:int = Update;
|
||||
updateChatParticipantAdmin#d7ca61a2 chat_id:long user_id:long is_admin:Bool version:int = Update;
|
||||
updateNewStickerSet#688a30aa stickerset:messages.StickerSet = Update;
|
||||
updateStickerSetsOrder#bb2d201 flags:# masks:flags.0?true emojis:flags.1?true order:Vector<long> = Update;
|
||||
updateStickerSets#43ae3dec = Update;
|
||||
updateStickerSets#31c24808 flags:# masks:flags.0?true emojis:flags.1?true = Update;
|
||||
updateSavedGifs#9375341e = Update;
|
||||
updateBotInlineQuery#496f379c flags:# query_id:long user_id:long query:string geo:flags.0?GeoPoint peer_type:flags.1?InlineQueryPeerType offset:string = Update;
|
||||
updateBotInlineSend#12f12a07 flags:# user_id:long query:string geo:flags.0?GeoPoint id:string msg_id:flags.1?InputBotInlineMessageID = Update;
|
||||
@@ -393,6 +393,11 @@ updateBotMenuButton#14b85813 bot_id:long button:BotMenuButton = Update;
|
||||
updateSavedRingtones#74d8be99 = Update;
|
||||
updateTranscribedAudio#84cd5a flags:# pending:flags.0?true peer:Peer msg_id:int transcription_id:long text:string = Update;
|
||||
updateReadFeaturedEmojiStickers#fb4c496c = Update;
|
||||
updateUserEmojiStatus#28373599 user_id:long emoji_status:EmojiStatus = Update;
|
||||
updateRecentEmojiStatuses#30f443db = Update;
|
||||
updateRecentReactions#6f7863f4 = Update;
|
||||
updateMoveStickerSetToTop#86fccf85 flags:# masks:flags.0?true emojis:flags.1?true stickerset:long = Update;
|
||||
updateMessageExtendedMedia#5a73a98c peer:Peer msg_id:int extended_media:MessageExtendedMedia = Update;
|
||||
|
||||
updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
|
||||
|
||||
@@ -419,7 +424,7 @@ upload.fileCdnRedirect#f18cda44 dc_id:int file_token:bytes encryption_key:bytes
|
||||
|
||||
dcOption#18b7a10d flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true cdn:flags.3?true static:flags.4?true this_port_only:flags.5?true id:int ip_address:string port:int secret:flags.10?bytes = DcOption;
|
||||
|
||||
config#330b4067 flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true blocked_mode:flags.8?true pfs_enabled:flags.13?true force_try_ipv6:flags.14?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector<DcOption> dc_txt_domain_name:string chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int pinned_infolder_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string autoupdate_url_prefix:flags.7?string gif_search_username:flags.9?string venue_search_username:flags.10?string img_search_username:flags.11?string static_maps_provider:flags.12?string caption_length_max:int message_length_max:int webfile_dc_id:int suggested_lang_code:flags.2?string lang_pack_version:flags.2?int base_lang_pack_version:flags.2?int = Config;
|
||||
config#232566ac flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true blocked_mode:flags.8?true pfs_enabled:flags.13?true force_try_ipv6:flags.14?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector<DcOption> dc_txt_domain_name:string chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int pinned_infolder_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string autoupdate_url_prefix:flags.7?string gif_search_username:flags.9?string venue_search_username:flags.10?string img_search_username:flags.11?string static_maps_provider:flags.12?string caption_length_max:int message_length_max:int webfile_dc_id:int suggested_lang_code:flags.2?string lang_pack_version:flags.2?int base_lang_pack_version:flags.2?int reactions_default:flags.15?Reaction = Config;
|
||||
|
||||
nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc;
|
||||
|
||||
@@ -557,7 +562,7 @@ authorization#ad01d61d flags:# current:flags.0?true official_app:flags.1?true pa
|
||||
|
||||
account.authorizations#4bff8ea0 authorization_ttl_days:int authorizations:Vector<Authorization> = account.Authorizations;
|
||||
|
||||
account.password#185b184f flags:# has_recovery:flags.0?true has_secure_values:flags.1?true has_password:flags.2?true current_algo:flags.2?PasswordKdfAlgo srp_B:flags.2?bytes srp_id:flags.2?long hint:flags.3?string email_unconfirmed_pattern:flags.4?string new_algo:PasswordKdfAlgo new_secure_algo:SecurePasswordKdfAlgo secure_random:bytes pending_reset_date:flags.5?int = account.Password;
|
||||
account.password#957b50fb flags:# has_recovery:flags.0?true has_secure_values:flags.1?true has_password:flags.2?true current_algo:flags.2?PasswordKdfAlgo srp_B:flags.2?bytes srp_id:flags.2?long hint:flags.3?string email_unconfirmed_pattern:flags.4?string new_algo:PasswordKdfAlgo new_secure_algo:SecurePasswordKdfAlgo secure_random:bytes pending_reset_date:flags.5?int login_email_pattern:flags.6?string = account.Password;
|
||||
|
||||
account.passwordSettings#9a5c33e5 flags:# email:flags.0?string secure_settings:flags.1?SecureSecretSettings = account.PasswordSettings;
|
||||
|
||||
@@ -581,6 +586,8 @@ inputStickerSetAnimatedEmoji#28703c8 = InputStickerSet;
|
||||
inputStickerSetDice#e67f520e emoticon:string = InputStickerSet;
|
||||
inputStickerSetAnimatedEmojiAnimations#cde3739 = InputStickerSet;
|
||||
inputStickerSetPremiumGifts#c88b3b02 = InputStickerSet;
|
||||
inputStickerSetEmojiGenericAnimations#4c4d4ce = InputStickerSet;
|
||||
inputStickerSetEmojiDefaultStatuses#29d0f5ee = InputStickerSet;
|
||||
|
||||
stickerSet#2dd14edc flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true videos:flags.6?true emojis:flags.7?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector<PhotoSize> thumb_dc_id:flags.4?int thumb_version:flags.4?int thumb_document_id:flags.8?long count:int hash:int = StickerSet;
|
||||
|
||||
@@ -716,6 +723,8 @@ auth.sentCodeTypeSms#c000bba2 length:int = auth.SentCodeType;
|
||||
auth.sentCodeTypeCall#5353e5a7 length:int = auth.SentCodeType;
|
||||
auth.sentCodeTypeFlashCall#ab03c6d9 pattern:string = auth.SentCodeType;
|
||||
auth.sentCodeTypeMissedCall#82006484 prefix:string length:int = auth.SentCodeType;
|
||||
auth.sentCodeTypeEmailCode#5a159841 flags:# apple_signin_allowed:flags.0?true google_signin_allowed:flags.1?true email_pattern:string length:int next_phone_login_date:flags.2?int = auth.SentCodeType;
|
||||
auth.sentCodeTypeSetUpEmailRequired#a5491dea flags:# apple_signin_allowed:flags.0?true google_signin_allowed:flags.1?true = auth.SentCodeType;
|
||||
|
||||
messages.botCallbackAnswer#36585ea4 flags:# alert:flags.1?true has_url:flags.3?true native_ui:flags.4?true message:flags.0?string url:flags.2?string cache_time:int = messages.BotCallbackAnswer;
|
||||
|
||||
@@ -942,7 +951,7 @@ channelAdminLogEventActionChangeHistoryTTL#6e941a38 prev_value:int new_value:int
|
||||
channelAdminLogEventActionParticipantJoinByRequest#afb6144a invite:ExportedChatInvite approved_by:long = ChannelAdminLogEventAction;
|
||||
channelAdminLogEventActionToggleNoForwards#cb2ac766 new_value:Bool = ChannelAdminLogEventAction;
|
||||
channelAdminLogEventActionSendMessage#278f2868 message:Message = ChannelAdminLogEventAction;
|
||||
channelAdminLogEventActionChangeAvailableReactions#9cf7f76a prev_value:Vector<string> new_value:Vector<string> = ChannelAdminLogEventAction;
|
||||
channelAdminLogEventActionChangeAvailableReactions#be4e0ef8 prev_value:ChatReactions new_value:ChatReactions = ChannelAdminLogEventAction;
|
||||
|
||||
channelAdminLogEvent#1fad68cd id:long date:int user_id:long action:ChannelAdminLogEventAction = ChannelAdminLogEvent;
|
||||
|
||||
@@ -1319,7 +1328,7 @@ searchResultPosition#7f648b67 msg_id:int date:int offset:int = SearchResultsPosi
|
||||
|
||||
messages.searchResultsPositions#53b22baf count:int positions:Vector<SearchResultsPosition> = messages.SearchResultsPositions;
|
||||
|
||||
channels.sendAsPeers#8356cda9 peers:Vector<Peer> chats:Vector<Chat> users:Vector<User> = channels.SendAsPeers;
|
||||
channels.sendAsPeers#f496b0c6 peers:Vector<SendAsPeer> chats:Vector<Chat> users:Vector<User> = channels.SendAsPeers;
|
||||
|
||||
users.userFull#3b6d152e full_user:UserFull chats:Vector<Chat> users:Vector<User> = users.UserFull;
|
||||
|
||||
@@ -1327,7 +1336,7 @@ messages.peerSettings#6880b94d settings:PeerSettings chats:Vector<Chat> users:Ve
|
||||
|
||||
auth.loggedOut#c3a2835f flags:# future_auth_token:flags.0?bytes = auth.LoggedOut;
|
||||
|
||||
reactionCount#6fb250d1 flags:# chosen:flags.0?true reaction:string count:int = ReactionCount;
|
||||
reactionCount#a3d1cb80 flags:# chosen_order:flags.0?int reaction:Reaction count:int = ReactionCount;
|
||||
|
||||
messageReactions#4f2b9479 flags:# min:flags.0?true can_see_list:flags.2?true results:Vector<ReactionCount> recent_reactions:flags.1?Vector<MessagePeerReaction> = MessageReactions;
|
||||
|
||||
@@ -1341,7 +1350,7 @@ messages.availableReactions#768e3aad hash:int reactions:Vector<AvailableReaction
|
||||
messages.translateNoResult#67ca4737 = messages.TranslatedText;
|
||||
messages.translateResultText#a214f7d0 text:string = messages.TranslatedText;
|
||||
|
||||
messagePeerReaction#51b67eff flags:# big:flags.0?true unread:flags.1?true peer_id:Peer reaction:string = MessagePeerReaction;
|
||||
messagePeerReaction#b156fe9c flags:# big:flags.0?true unread:flags.1?true peer_id:Peer reaction:Reaction = MessagePeerReaction;
|
||||
|
||||
groupCallStreamChannel#80eb48af channel:int scale:int last_timestamp_ms:long = GroupCallStreamChannel;
|
||||
|
||||
@@ -1394,7 +1403,7 @@ payments.exportedInvoice#aed0cbd9 url:string = payments.ExportedInvoice;
|
||||
|
||||
messages.transcribedAudio#93752c52 flags:# pending:flags.0?true transcription_id:long text:string = messages.TranscribedAudio;
|
||||
|
||||
help.premiumPromo#8a4f3c29 status_text:string status_entities:Vector<MessageEntity> video_sections:Vector<string> videos:Vector<Document> currency:string monthly_amount:long users:Vector<User> = help.PremiumPromo;
|
||||
help.premiumPromo#5334759c status_text:string status_entities:Vector<MessageEntity> video_sections:Vector<string> videos:Vector<Document> period_options:Vector<PremiumSubscriptionOption> users:Vector<User> = help.PremiumPromo;
|
||||
|
||||
inputStorePaymentPremiumSubscription#a6751e66 flags:# restore:flags.0?true = InputStorePaymentPurpose;
|
||||
inputStorePaymentGiftPremium#616f7fe8 user_id:InputUser currency:string amount:long = InputStorePaymentPurpose;
|
||||
@@ -1403,6 +1412,42 @@ premiumGiftOption#74c34319 flags:# months:int currency:string amount:long bot_ur
|
||||
|
||||
paymentFormMethod#88f8f21b url:string title:string = PaymentFormMethod;
|
||||
|
||||
emojiStatusEmpty#2de11aae = EmojiStatus;
|
||||
emojiStatus#929b619d document_id:long = EmojiStatus;
|
||||
emojiStatusUntil#fa30a8c7 document_id:long until:int = EmojiStatus;
|
||||
|
||||
account.emojiStatusesNotModified#d08ce645 = account.EmojiStatuses;
|
||||
account.emojiStatuses#90c467d1 hash:long statuses:Vector<EmojiStatus> = account.EmojiStatuses;
|
||||
|
||||
reactionEmpty#79f5d419 = Reaction;
|
||||
reactionEmoji#1b2286b8 emoticon:string = Reaction;
|
||||
reactionCustomEmoji#8935fc73 document_id:long = Reaction;
|
||||
|
||||
chatReactionsNone#eafc32bc = ChatReactions;
|
||||
chatReactionsAll#52928bca flags:# allow_custom:flags.0?true = ChatReactions;
|
||||
chatReactionsSome#661d4037 reactions:Vector<Reaction> = ChatReactions;
|
||||
|
||||
messages.reactionsNotModified#b06fdbdf = messages.Reactions;
|
||||
messages.reactions#eafdf716 hash:long reactions:Vector<Reaction> = messages.Reactions;
|
||||
|
||||
emailVerifyPurposeLoginSetup#4345be73 phone_number:string phone_code_hash:string = EmailVerifyPurpose;
|
||||
emailVerifyPurposeLoginChange#527d22eb = EmailVerifyPurpose;
|
||||
emailVerifyPurposePassport#bbf51685 = EmailVerifyPurpose;
|
||||
|
||||
emailVerificationCode#922e55a9 code:string = EmailVerification;
|
||||
emailVerificationGoogle#db909ec2 token:string = EmailVerification;
|
||||
emailVerificationApple#96d074fd token:string = EmailVerification;
|
||||
|
||||
account.emailVerified#2b96cd1b email:string = account.EmailVerified;
|
||||
account.emailVerifiedLogin#e1bb0d61 email:string sent_code:auth.SentCode = account.EmailVerified;
|
||||
|
||||
premiumSubscriptionOption#b6f11ebe flags:# current:flags.1?true can_purchase_upgrade:flags.2?true months:int currency:string amount:long bot_url:string store_product:flags.0?string = PremiumSubscriptionOption;
|
||||
|
||||
sendAsPeer#b81c7034 flags:# premium_required:flags.0?true peer:Peer = SendAsPeer;
|
||||
|
||||
messageExtendedMediaPreview#ad628cc8 flags:# w:flags.0?int h:flags.0?int thumb:flags.1?PhotoSize video_duration:flags.2?int = MessageExtendedMedia;
|
||||
messageExtendedMedia#ee479c64 media:MessageMedia = MessageExtendedMedia;
|
||||
|
||||
---functions---
|
||||
|
||||
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
|
||||
@@ -1415,7 +1460,7 @@ invokeWithTakeout#aca9fd2e {X:Type} takeout_id:long query:!X = X;
|
||||
|
||||
auth.sendCode#a677244f phone_number:string api_id:int api_hash:string settings:CodeSettings = auth.SentCode;
|
||||
auth.signUp#80eee427 phone_number:string phone_code_hash:string first_name:string last_name:string = auth.Authorization;
|
||||
auth.signIn#bcd51581 phone_number:string phone_code_hash:string phone_code:string = auth.Authorization;
|
||||
auth.signIn#8d52a951 flags:# phone_number:string phone_code_hash:string phone_code:flags.0?string email_verification:flags.1?EmailVerification = auth.Authorization;
|
||||
auth.logOut#3e72ba19 = auth.LoggedOut;
|
||||
auth.resetAuthorizations#9fab0d1a = Bool;
|
||||
auth.exportAuthorization#e5bfffcd dc_id:int = auth.ExportedAuthorization;
|
||||
@@ -1471,8 +1516,8 @@ account.getAuthorizationForm#a929597a bot_id:long scope:string public_key:string
|
||||
account.acceptAuthorization#f3ed4c73 bot_id:long scope:string public_key:string value_hashes:Vector<SecureValueHash> credentials:SecureCredentialsEncrypted = Bool;
|
||||
account.sendVerifyPhoneCode#a5a356f9 phone_number:string settings:CodeSettings = auth.SentCode;
|
||||
account.verifyPhone#4dd3a7f6 phone_number:string phone_code_hash:string phone_code:string = Bool;
|
||||
account.sendVerifyEmailCode#7011509f email:string = account.SentEmailCode;
|
||||
account.verifyEmail#ecba39db email:string code:string = Bool;
|
||||
account.sendVerifyEmailCode#98e037bb purpose:EmailVerifyPurpose email:string = account.SentEmailCode;
|
||||
account.verifyEmail#32da4cf purpose:EmailVerifyPurpose verification:EmailVerification = account.EmailVerified;
|
||||
account.initTakeoutSession#8ef3eab0 flags:# contacts:flags.0?true message_users:flags.1?true message_chats:flags.2?true message_megagroups:flags.3?true message_channels:flags.4?true files:flags.5?true file_max_size:flags.5?long = account.Takeout;
|
||||
account.finishTakeoutSession#1d2652ee flags:# success:flags.0?true = Bool;
|
||||
account.confirmPasswordEmail#8fdf1920 code:string = Bool;
|
||||
@@ -1509,6 +1554,10 @@ account.changeAuthorizationSettings#40f48462 flags:# hash:long encrypted_request
|
||||
account.getSavedRingtones#e1902288 hash:long = account.SavedRingtones;
|
||||
account.saveRingtone#3dea5b03 id:InputDocument unsave:Bool = account.SavedRingtone;
|
||||
account.uploadRingtone#831a83a2 file:InputFile file_name:string mime_type:string = Document;
|
||||
account.updateEmojiStatus#fbd3de6b emoji_status:EmojiStatus = Bool;
|
||||
account.getDefaultEmojiStatuses#d6753386 hash:long = account.EmojiStatuses;
|
||||
account.getRecentEmojiStatuses#f578105 hash:long = account.EmojiStatuses;
|
||||
account.clearRecentEmojiStatuses#18201aae = Bool;
|
||||
|
||||
users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
|
||||
users.getFullUser#b60f5918 id:InputUser = users.UserFull;
|
||||
@@ -1545,8 +1594,8 @@ messages.deleteHistory#b08f922a flags:# just_clear:flags.0?true revoke:flags.1?t
|
||||
messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector<int> = messages.AffectedMessages;
|
||||
messages.receivedMessages#5a954c0 max_id:int = Vector<ReceivedNotifyMessage>;
|
||||
messages.setTyping#58943ee2 flags:# peer:InputPeer top_msg_id:flags.0?int action:SendMessageAction = Bool;
|
||||
messages.sendMessage#d9d75a4 flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
|
||||
messages.sendMedia#e25ff8e0 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
|
||||
messages.sendMessage#d9d75a4 flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
|
||||
messages.sendMedia#e25ff8e0 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
|
||||
messages.forwardMessages#cc30290b flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true drop_author:flags.11?true drop_media_captions:flags.12?true noforwards:flags.14?true from_peer:InputPeer id:Vector<int> random_id:Vector<long> to_peer:InputPeer schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
|
||||
messages.reportSpam#cf1592db peer:InputPeer = Bool;
|
||||
messages.getPeerSettings#efd9a6a2 peer:InputPeer = messages.PeerSettings;
|
||||
@@ -1626,7 +1675,7 @@ messages.faveSticker#b9ffc55b id:InputDocument unfave:Bool = Bool;
|
||||
messages.getUnreadMentions#46578472 peer:InputPeer offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
|
||||
messages.readMentions#f0189d3 peer:InputPeer = messages.AffectedHistory;
|
||||
messages.getRecentLocations#702a40e0 peer:InputPeer limit:int hash:long = messages.Messages;
|
||||
messages.sendMultiMedia#f803138f flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true peer:InputPeer reply_to_msg_id:flags.0?int multi_media:Vector<InputSingleMedia> schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
|
||||
messages.sendMultiMedia#f803138f flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to_msg_id:flags.0?int multi_media:Vector<InputSingleMedia> schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
|
||||
messages.uploadEncryptedFile#5057c497 peer:InputEncryptedChat file:InputEncryptedFile = EncryptedFile;
|
||||
messages.searchStickerSets#35705b8a flags:# exclude_featured:flags.0?true q:string hash:long = messages.FoundStickerSets;
|
||||
messages.getSplitRanges#1cff7e08 = Vector<MessageRange>;
|
||||
@@ -1685,12 +1734,12 @@ messages.hideChatJoinRequest#7fe7e815 flags:# approved:flags.0?true peer:InputPe
|
||||
messages.hideAllChatJoinRequests#e085f4ea flags:# approved:flags.0?true peer:InputPeer link:flags.1?string = Updates;
|
||||
messages.toggleNoForwards#b11eafa2 peer:InputPeer enabled:Bool = Updates;
|
||||
messages.saveDefaultSendAs#ccfddf96 peer:InputPeer send_as:InputPeer = Bool;
|
||||
messages.sendReaction#25690ce4 flags:# big:flags.1?true peer:InputPeer msg_id:int reaction:flags.0?string = Updates;
|
||||
messages.sendReaction#d30d78d4 flags:# big:flags.1?true add_to_recent:flags.2?true peer:InputPeer msg_id:int reaction:flags.0?Vector<Reaction> = Updates;
|
||||
messages.getMessagesReactions#8bba90e6 peer:InputPeer id:Vector<int> = Updates;
|
||||
messages.getMessageReactionsList#e0ee6b77 flags:# peer:InputPeer id:int reaction:flags.0?string offset:flags.1?string limit:int = messages.MessageReactionsList;
|
||||
messages.setChatAvailableReactions#14050ea6 peer:InputPeer available_reactions:Vector<string> = Updates;
|
||||
messages.getMessageReactionsList#461b3f48 flags:# peer:InputPeer id:int reaction:flags.0?Reaction offset:flags.1?string limit:int = messages.MessageReactionsList;
|
||||
messages.setChatAvailableReactions#feb16771 peer:InputPeer available_reactions:ChatReactions = Updates;
|
||||
messages.getAvailableReactions#18dea0ac hash:int = messages.AvailableReactions;
|
||||
messages.setDefaultReaction#d960c4d4 reaction:string = Bool;
|
||||
messages.setDefaultReaction#4f47a016 reaction:Reaction = Bool;
|
||||
messages.translateText#24ce6dee flags:# peer:flags.0?InputPeer msg_id:flags.0?int text:flags.1?string from_lang:flags.2?string to_lang:string = messages.TranslatedText;
|
||||
messages.getUnreadReactions#e85bae1a peer:InputPeer offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
|
||||
messages.readReactions#82e251d7 peer:InputPeer = messages.AffectedHistory;
|
||||
@@ -1698,9 +1747,9 @@ messages.searchSentMedia#107e31a0 q:string filter:MessagesFilter limit:int = mes
|
||||
messages.getAttachMenuBots#16fcc2cb hash:long = AttachMenuBots;
|
||||
messages.getAttachMenuBot#77216192 bot:InputUser = AttachMenuBotsBot;
|
||||
messages.toggleBotInAttachMenu#1aee33af bot:InputUser enabled:Bool = Bool;
|
||||
messages.requestWebView#91b15831 flags:# from_bot_menu:flags.4?true silent:flags.5?true peer:InputPeer bot:InputUser url:flags.1?string start_param:flags.3?string theme_params:flags.2?DataJSON reply_to_msg_id:flags.0?int send_as:flags.13?InputPeer = WebViewResult;
|
||||
messages.requestWebView#fc87a53c flags:# from_bot_menu:flags.4?true silent:flags.5?true peer:InputPeer bot:InputUser url:flags.1?string start_param:flags.3?string theme_params:flags.2?DataJSON platform:string reply_to_msg_id:flags.0?int send_as:flags.13?InputPeer = WebViewResult;
|
||||
messages.prolongWebView#ea5fbcce flags:# silent:flags.5?true peer:InputPeer bot:InputUser query_id:long reply_to_msg_id:flags.0?int send_as:flags.13?InputPeer = Bool;
|
||||
messages.requestSimpleWebView#6abb2f73 flags:# bot:InputUser url:string theme_params:flags.0?DataJSON = SimpleWebViewResult;
|
||||
messages.requestSimpleWebView#299bec8e flags:# bot:InputUser url:string theme_params:flags.0?DataJSON platform:string = SimpleWebViewResult;
|
||||
messages.sendWebViewResultMessage#a4314f5 bot_query_id:string result:InputBotInlineResult = WebViewMessageSent;
|
||||
messages.sendWebViewData#dc0242c8 bot:InputUser random_id:long button_text:string data:string = Updates;
|
||||
messages.transcribeAudio#269e9a49 peer:InputPeer msg_id:int = messages.TranscribedAudio;
|
||||
@@ -1708,6 +1757,11 @@ messages.rateTranscribedAudio#7f1d072f peer:InputPeer msg_id:int transcription_i
|
||||
messages.getCustomEmojiDocuments#d9ab0f54 document_id:Vector<long> = Vector<Document>;
|
||||
messages.getEmojiStickers#fbfca18f hash:long = messages.AllStickers;
|
||||
messages.getFeaturedEmojiStickers#ecf6736 hash:long = messages.FeaturedStickers;
|
||||
messages.reportReaction#3f64c076 peer:InputPeer id:int reaction_peer:InputPeer = Bool;
|
||||
messages.getTopReactions#bb8125ba limit:int hash:long = messages.Reactions;
|
||||
messages.getRecentReactions#39461db2 limit:int hash:long = messages.Reactions;
|
||||
messages.clearRecentReactions#9dfeefb4 = Bool;
|
||||
messages.getExtendedMedia#84f80814 peer:InputPeer id:Vector<int> = Updates;
|
||||
|
||||
updates.getState#edd4882a = updates.State;
|
||||
updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
|
||||
@@ -1814,7 +1868,6 @@ payments.exportInvoice#f91b065 invoice_media:InputMedia = payments.ExportedInvoi
|
||||
payments.assignAppStoreTransaction#80ed747d receipt:bytes purpose:InputStorePaymentPurpose = Updates;
|
||||
payments.assignPlayMarketTransaction#dffd50d3 receipt:DataJSON purpose:InputStorePaymentPurpose = Updates;
|
||||
payments.canPurchasePremium#9fc19eb6 purpose:InputStorePaymentPurpose = Bool;
|
||||
payments.requestRecurringPayment#146e958d user_id:InputUser recurring_init_charge:string invoice_media:InputMedia = Updates;
|
||||
|
||||
stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true animated:flags.1?true videos:flags.4?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector<InputStickerSetItem> software:flags.3?string = messages.StickerSet;
|
||||
stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet;
|
||||
@@ -1871,4 +1924,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel
|
||||
stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
|
||||
stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;
|
||||
|
||||
// LAYER 144
|
||||
// LAYER 146
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="4.1.0.0" />
|
||||
Version="4.1.2.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 4,1,0,0
|
||||
PRODUCTVERSION 4,1,0,0
|
||||
FILEVERSION 4,1,2,0
|
||||
PRODUCTVERSION 4,1,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", "4.1.0.0"
|
||||
VALUE "FileVersion", "4.1.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "4.1.0.0"
|
||||
VALUE "ProductVersion", "4.1.2.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 4,1,0,0
|
||||
PRODUCTVERSION 4,1,0,0
|
||||
FILEVERSION 4,1,2,0
|
||||
PRODUCTVERSION 4,1,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", "4.1.0.0"
|
||||
VALUE "FileVersion", "4.1.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "4.1.0.0"
|
||||
VALUE "ProductVersion", "4.1.2.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -41,7 +41,7 @@ void AttachedStickers::request(
|
||||
return;
|
||||
} else if (result.v.size() > 1) {
|
||||
strongController->show(
|
||||
Box<StickersBox>(strongController, result));
|
||||
Box<StickersBox>(strongController, result.v));
|
||||
return;
|
||||
}
|
||||
// Single attached sticker pack.
|
||||
|
||||
@@ -35,6 +35,10 @@ void ConfirmPhone::resolve(
|
||||
_sendRequestId = 0;
|
||||
|
||||
result.match([&](const MTPDauth_sentCode &data) {
|
||||
const auto bad = [](const char *type) {
|
||||
LOG(("API Error: Should not be '%1'.").arg(type));
|
||||
return 0;
|
||||
};
|
||||
const auto sentCodeLength = data.vtype().match([&](
|
||||
const MTPDauth_sentCodeTypeApp &data) {
|
||||
LOG(("Error: should not be in-app code!"));
|
||||
@@ -43,12 +47,14 @@ void ConfirmPhone::resolve(
|
||||
return data.vlength().v;
|
||||
}, [&](const MTPDauth_sentCodeTypeCall &data) {
|
||||
return data.vlength().v;
|
||||
}, [&](const MTPDauth_sentCodeTypeFlashCall &data) {
|
||||
LOG(("Error: should not be flashcall!"));
|
||||
return 0;
|
||||
}, [&](const MTPDauth_sentCodeTypeMissedCall &data) {
|
||||
LOG(("Error: should not be missedcall!"));
|
||||
return 0;
|
||||
}, [&](const MTPDauth_sentCodeTypeFlashCall &) {
|
||||
return bad("FlashCall");
|
||||
}, [&](const MTPDauth_sentCodeTypeMissedCall &) {
|
||||
return bad("MissedCall");
|
||||
}, [&](const MTPDauth_sentCodeTypeEmailCode &) {
|
||||
return bad("EmailCode");
|
||||
}, [&](const MTPDauth_sentCodeTypeSetUpEmailRequired &) {
|
||||
return bad("SetUpEmailRequired");
|
||||
});
|
||||
const auto phoneHash = qs(data.vphone_code_hash());
|
||||
const auto timeout = [&]() -> std::optional<int> {
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "api/api_premium.h"
|
||||
|
||||
#include "api/api_premium_option.h"
|
||||
#include "api/api_text_entities.h"
|
||||
#include "main/main_session.h"
|
||||
#include "data/data_peer_values.h"
|
||||
@@ -86,37 +87,43 @@ void Premium::reloadPromo() {
|
||||
_promoRequestId = _api.request(MTPhelp_GetPremiumPromo(
|
||||
)).done([=](const MTPhelp_PremiumPromo &result) {
|
||||
_promoRequestId = 0;
|
||||
result.match([&](const MTPDhelp_premiumPromo &data) {
|
||||
_session->data().processUsers(data.vusers());
|
||||
_monthlyAmount = data.vmonthly_amount().v;
|
||||
_monthlyCurrency = qs(data.vcurrency());
|
||||
auto text = TextWithEntities{
|
||||
qs(data.vstatus_text()),
|
||||
EntitiesFromMTP(_session, data.vstatus_entities().v),
|
||||
};
|
||||
_statusText = text;
|
||||
_statusTextUpdates.fire(std::move(text));
|
||||
auto videos = base::flat_map<QString, not_null<DocumentData*>>();
|
||||
const auto count = int(std::min(
|
||||
data.vvideo_sections().v.size(),
|
||||
data.vvideos().v.size()));
|
||||
videos.reserve(count);
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
const auto document = _session->data().processDocument(
|
||||
data.vvideos().v[i]);
|
||||
if ((!document->isVideoFile() && !document->isGifv())
|
||||
|| !document->supportsStreaming()) {
|
||||
document->forceIsStreamedAnimation();
|
||||
}
|
||||
videos.emplace(
|
||||
qs(data.vvideo_sections().v[i]),
|
||||
document);
|
||||
const auto &data = result.data();
|
||||
_session->data().processUsers(data.vusers());
|
||||
|
||||
_subscriptionOptions = SubscriptionOptionsFromTL(
|
||||
data.vperiod_options().v);
|
||||
for (const auto &option : data.vperiod_options().v) {
|
||||
if (option.data().vmonths().v == 1) {
|
||||
_monthlyAmount = option.data().vamount().v;
|
||||
_monthlyCurrency = qs(option.data().vcurrency());
|
||||
}
|
||||
if (_videos != videos) {
|
||||
_videos = std::move(videos);
|
||||
_videosUpdated.fire({});
|
||||
}
|
||||
auto text = TextWithEntities{
|
||||
qs(data.vstatus_text()),
|
||||
EntitiesFromMTP(_session, data.vstatus_entities().v),
|
||||
};
|
||||
_statusText = text;
|
||||
_statusTextUpdates.fire(std::move(text));
|
||||
auto videos = base::flat_map<QString, not_null<DocumentData*>>();
|
||||
const auto count = int(std::min(
|
||||
data.vvideo_sections().v.size(),
|
||||
data.vvideos().v.size()));
|
||||
videos.reserve(count);
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
const auto document = _session->data().processDocument(
|
||||
data.vvideos().v[i]);
|
||||
if ((!document->isVideoFile() && !document->isGifv())
|
||||
|| !document->supportsStreaming()) {
|
||||
document->forceIsStreamedAnimation();
|
||||
}
|
||||
});
|
||||
videos.emplace(
|
||||
qs(data.vvideo_sections().v[i]),
|
||||
document);
|
||||
}
|
||||
if (_videos != videos) {
|
||||
_videos = std::move(videos);
|
||||
_videosUpdated.fire({});
|
||||
}
|
||||
}).fail([=] {
|
||||
_promoRequestId = 0;
|
||||
}).send();
|
||||
@@ -176,4 +183,8 @@ void Premium::reloadCloudSet() {
|
||||
}).send();
|
||||
}
|
||||
|
||||
const Data::SubscriptionOptions &Premium::subscriptionOptions() const {
|
||||
return _subscriptionOptions;
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "data/data_subscription_option.h"
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
class ApiWrap;
|
||||
@@ -39,6 +40,9 @@ public:
|
||||
[[nodiscard]] int64 monthlyAmount() const;
|
||||
[[nodiscard]] QString monthlyCurrency() const;
|
||||
|
||||
[[nodiscard]] auto subscriptionOptions() const
|
||||
-> const Data::SubscriptionOptions &;
|
||||
|
||||
private:
|
||||
void reloadPromo();
|
||||
void reloadStickers();
|
||||
@@ -67,6 +71,8 @@ private:
|
||||
int64 _monthlyAmount = 0;
|
||||
QString _monthlyCurrency;
|
||||
|
||||
Data::SubscriptionOptions _subscriptionOptions;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Api
|
||||
|
||||
40
Telegram/SourceFiles/api/api_premium_option.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "api/api_premium_option.h"
|
||||
|
||||
#include "ui/text/format_values.h"
|
||||
|
||||
namespace Api {
|
||||
|
||||
constexpr auto kDiscountDivider = 5.;
|
||||
|
||||
Data::SubscriptionOption CreateSubscriptionOption(
|
||||
int months,
|
||||
int monthlyAmount,
|
||||
int64 amount,
|
||||
const QString ¤cy,
|
||||
const QString &botUrl) {
|
||||
const auto discount = [&] {
|
||||
const auto percent = monthlyAmount * months / float64(amount) - 1.;
|
||||
return std::round(percent * 100. / kDiscountDivider)
|
||||
* kDiscountDivider;
|
||||
}();
|
||||
return {
|
||||
.duration = Ui::FormatTTL(months * 86400 * 31),
|
||||
.discount = discount
|
||||
? QString::fromUtf8("\xe2\x88\x92%1%").arg(discount)
|
||||
: QString(),
|
||||
.costPerMonth = Ui::FillAmountAndCurrency(
|
||||
amount / float64(months),
|
||||
currency),
|
||||
.costTotal = Ui::FillAmountAndCurrency(amount, currency),
|
||||
.botUrl = botUrl,
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
50
Telegram/SourceFiles/api/api_premium_option.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "data/data_subscription_option.h"
|
||||
|
||||
namespace Api {
|
||||
|
||||
[[nodiscard]] Data::SubscriptionOption CreateSubscriptionOption(
|
||||
int months,
|
||||
int monthlyAmount,
|
||||
int64 amount,
|
||||
const QString ¤cy,
|
||||
const QString &botUrl);
|
||||
|
||||
template<typename Option>
|
||||
[[nodiscard]] Data::SubscriptionOptions SubscriptionOptionsFromTL(
|
||||
const QVector<Option> &tlOptions) {
|
||||
auto result = Data::SubscriptionOptions();
|
||||
const auto monthlyAmount = [&] {
|
||||
const auto &min = ranges::min_element(
|
||||
tlOptions,
|
||||
ranges::less(),
|
||||
[](const Option &o) { return o.data().vamount().v; }
|
||||
)->data();
|
||||
return min.vamount().v / float64(min.vmonths().v);
|
||||
}();
|
||||
result.reserve(tlOptions.size());
|
||||
for (const auto &tlOption : tlOptions) {
|
||||
const auto &option = tlOption.data();
|
||||
const auto botUrl = qs(option.vbot_url());
|
||||
const auto months = option.vmonths().v;
|
||||
const auto amount = option.vamount().v;
|
||||
const auto currency = qs(option.vcurrency());
|
||||
result.push_back(CreateSubscriptionOption(
|
||||
months,
|
||||
monthlyAmount,
|
||||
amount,
|
||||
currency,
|
||||
botUrl));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
@@ -231,7 +231,8 @@ bool SendDice(MessageToSend &message) {
|
||||
const auto full = QStringView(message.textWithTags.text).trimmed();
|
||||
auto length = 0;
|
||||
if (!Ui::Emoji::Find(full.data(), full.data() + full.size(), &length)
|
||||
|| length != full.size()) {
|
||||
|| length != full.size()
|
||||
|| !message.textWithTags.tags.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
auto &account = message.action.history->session().account();
|
||||
|
||||
@@ -20,31 +20,24 @@ namespace {
|
||||
using namespace TextUtilities;
|
||||
|
||||
[[nodiscard]] QString CustomEmojiEntityData(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPDmessageEntityCustomEmoji &data) {
|
||||
return Data::SerializeCustomEmojiId({
|
||||
.selfId = session->userId().bare,
|
||||
.id = data.vdocument_id().v,
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<MTPMessageEntity> CustomEmojiEntity(
|
||||
not_null<Main::Session*> session,
|
||||
MTPint offset,
|
||||
MTPint length,
|
||||
const QString &data) {
|
||||
const auto parsed = Data::ParseCustomEmojiData(data);
|
||||
if (!parsed.id || parsed.selfId != session->userId().bare) {
|
||||
return {};
|
||||
}
|
||||
const auto document = session->data().document(parsed.id);
|
||||
if (!document->sticker()) {
|
||||
if (!parsed.id) {
|
||||
return {};
|
||||
}
|
||||
return MTP_messageEntityCustomEmoji(
|
||||
offset,
|
||||
length,
|
||||
MTP_long(document->id));
|
||||
MTP_long(parsed.id));
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<MTPMessageEntity> MentionNameEntity(
|
||||
@@ -125,9 +118,9 @@ EntitiesInText EntitiesFromMTP(
|
||||
case mtpc_messageEntityPre: { auto &d = entity.c_messageEntityPre(); result.push_back({ EntityType::Pre, d.voffset().v, d.vlength().v, qs(d.vlanguage()) }); } break;
|
||||
case mtpc_messageEntityBankCard: break; // Skipping cards. // #TODO entities
|
||||
case mtpc_messageEntitySpoiler: { auto &d = entity.c_messageEntitySpoiler(); result.push_back({ EntityType::Spoiler, d.voffset().v, d.vlength().v }); } break;
|
||||
case mtpc_messageEntityCustomEmoji: if (session) {
|
||||
case mtpc_messageEntityCustomEmoji: {
|
||||
const auto &d = entity.c_messageEntityCustomEmoji();
|
||||
result.push_back({ EntityType::CustomEmoji, d.voffset().v, d.vlength().v, CustomEmojiEntityData(session, d) });
|
||||
result.push_back({ EntityType::CustomEmoji, d.voffset().v, d.vlength().v, CustomEmojiEntityData(d) });
|
||||
} break;
|
||||
}
|
||||
}
|
||||
@@ -181,7 +174,7 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(
|
||||
case EntityType::Pre: v.push_back(MTP_messageEntityPre(offset, length, MTP_string(entity.data()))); break;
|
||||
case EntityType::Spoiler: v.push_back(MTP_messageEntitySpoiler(offset, length)); break;
|
||||
case EntityType::CustomEmoji: {
|
||||
if (const auto valid = CustomEmojiEntity(session, offset, length, entity.data())) {
|
||||
if (const auto valid = CustomEmojiEntity(offset, length, entity.data())) {
|
||||
v.push_back(*valid);
|
||||
}
|
||||
} break;
|
||||
|
||||
@@ -28,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_cloud_themes.h"
|
||||
#include "data/data_emoji_statuses.h"
|
||||
#include "data/data_group_call.h"
|
||||
#include "data/data_drafts.h"
|
||||
#include "data/data_histories.h"
|
||||
@@ -1649,6 +1650,15 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_updateMessageExtendedMedia: {
|
||||
const auto &d = update.c_updateMessageExtendedMedia();
|
||||
const auto peerId = peerFromMTP(d.vpeer());
|
||||
const auto msgId = d.vmsg_id().v;
|
||||
if (const auto item = session().data().message(peerId, msgId)) {
|
||||
item->applyEdition(d.vextended_media());
|
||||
}
|
||||
} break;
|
||||
|
||||
// Messages being read.
|
||||
case mtpc_updateReadHistoryInbox: {
|
||||
auto &d = update.c_updateReadHistoryInbox();
|
||||
@@ -2344,12 +2354,48 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_updateMoveStickerSetToTop: {
|
||||
const auto &d = update.c_updateMoveStickerSetToTop();
|
||||
auto &stickers = session().data().stickers();
|
||||
const auto isEmoji = d.is_emojis();
|
||||
const auto setId = d.vstickerset().v;
|
||||
auto &order = isEmoji
|
||||
? stickers.emojiSetsOrderRef()
|
||||
: stickers.setsOrderRef();
|
||||
const auto i = ranges::find(order, setId);
|
||||
if (i == order.end()) {
|
||||
if (isEmoji) {
|
||||
stickers.setLastEmojiUpdate(0);
|
||||
session().api().updateCustomEmoji();
|
||||
} else {
|
||||
stickers.setLastUpdate(0);
|
||||
session().api().updateStickers();
|
||||
}
|
||||
} else if (i != order.begin()) {
|
||||
std::rotate(order.begin(), i, i + 1);
|
||||
if (isEmoji) {
|
||||
session().local().writeInstalledCustomEmoji();
|
||||
} else {
|
||||
session().local().writeInstalledStickers();
|
||||
}
|
||||
stickers.notifyUpdated(isEmoji
|
||||
? Data::StickersType::Emoji
|
||||
: Data::StickersType::Stickers);
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_updateStickerSets: {
|
||||
// Can't determine is it masks or stickers, so update both.
|
||||
session().data().stickers().setLastUpdate(0);
|
||||
session().api().updateStickers();
|
||||
session().data().stickers().setLastMasksUpdate(0);
|
||||
session().api().updateMasks();
|
||||
const auto &d = update.c_updateStickerSets();
|
||||
if (d.is_emojis()) {
|
||||
session().data().stickers().setLastEmojiUpdate(0);
|
||||
session().api().updateCustomEmoji();
|
||||
} else if (d.is_masks()) {
|
||||
session().data().stickers().setLastMasksUpdate(0);
|
||||
session().api().updateMasks();
|
||||
} else {
|
||||
session().data().stickers().setLastUpdate(0);
|
||||
session().api().updateStickers();
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_updateRecentStickers: {
|
||||
@@ -2370,6 +2416,25 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
session().api().updateStickers();
|
||||
} break;
|
||||
|
||||
case mtpc_updateReadFeaturedEmojiStickers: {
|
||||
// We don't track read status of them for now.
|
||||
} break;
|
||||
|
||||
case mtpc_updateUserEmojiStatus: {
|
||||
const auto &d = update.c_updateUserEmojiStatus();
|
||||
if (const auto user = session().data().userLoaded(d.vuser_id())) {
|
||||
user->setEmojiStatus(d.vemoji_status());
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_updateRecentEmojiStatuses: {
|
||||
session().data().emojiStatuses().refreshRecentDelayed();
|
||||
} break;
|
||||
|
||||
case mtpc_updateRecentReactions: {
|
||||
session().data().reactions().refreshRecentDelayed();
|
||||
} break;
|
||||
|
||||
////// Cloud saved GIFs
|
||||
case mtpc_updateSavedGifs: {
|
||||
session().data().stickers().setLastSavedGifsUpdate(0);
|
||||
|
||||
@@ -20,13 +20,16 @@ namespace {
|
||||
|
||||
// Send channel views each second.
|
||||
constexpr auto kSendViewsTimeout = crl::time(1000);
|
||||
constexpr auto kPollExtendedMediaPeriod = 30 * crl::time(1000);
|
||||
constexpr auto kMaxPollPerRequest = 100;
|
||||
|
||||
} // namespace
|
||||
|
||||
ViewsManager::ViewsManager(not_null<ApiWrap*> api)
|
||||
: _session(&api->session())
|
||||
, _api(&api->instance())
|
||||
, _incrementTimer([=] { viewsIncrement(); }) {
|
||||
, _incrementTimer([=] { viewsIncrement(); })
|
||||
, _pollTimer([=] { sendPollRequests(); }) {
|
||||
}
|
||||
|
||||
void ViewsManager::scheduleIncrement(not_null<HistoryItem*> item) {
|
||||
@@ -52,6 +55,25 @@ void ViewsManager::removeIncremented(not_null<PeerData*> peer) {
|
||||
_incremented.remove(peer);
|
||||
}
|
||||
|
||||
void ViewsManager::pollExtendedMedia(not_null<HistoryItem*> item) {
|
||||
if (!item->isRegular()) {
|
||||
return;
|
||||
}
|
||||
const auto id = item->id;
|
||||
const auto peer = item->history()->peer;
|
||||
auto &request = _pollRequests[peer];
|
||||
if (request.ids.contains(id) || request.sent.contains(id)) {
|
||||
return;
|
||||
}
|
||||
request.ids.emplace(id);
|
||||
if (!request.id && !request.when) {
|
||||
request.when = crl::now() + kPollExtendedMediaPeriod;
|
||||
}
|
||||
if (!_pollTimer.isActive()) {
|
||||
_pollTimer.callOnce(kPollExtendedMediaPeriod);
|
||||
}
|
||||
}
|
||||
|
||||
void ViewsManager::viewsIncrement() {
|
||||
for (auto i = _toIncrement.begin(); i != _toIncrement.cend();) {
|
||||
if (_incrementRequests.contains(i->first)) {
|
||||
@@ -81,6 +103,88 @@ void ViewsManager::viewsIncrement() {
|
||||
}
|
||||
}
|
||||
|
||||
void ViewsManager::sendPollRequests() {
|
||||
const auto now = crl::now();
|
||||
auto toRequest = base::flat_map<not_null<PeerData*>, QVector<MTPint>>();
|
||||
auto nearest = crl::time();
|
||||
for (auto &[peer, request] : _pollRequests) {
|
||||
if (request.id) {
|
||||
continue;
|
||||
} else if (request.when <= now) {
|
||||
Assert(request.sent.empty());
|
||||
auto &list = toRequest[peer];
|
||||
const auto count = int(request.ids.size());
|
||||
if (count < kMaxPollPerRequest) {
|
||||
request.sent = base::take(request.ids);
|
||||
} else {
|
||||
const auto from = begin(request.ids);
|
||||
const auto end = from + kMaxPollPerRequest;
|
||||
request.sent = { from, end };
|
||||
request.ids.erase(from, end);
|
||||
}
|
||||
list.reserve(request.sent.size());
|
||||
for (const auto &id : request.sent) {
|
||||
list.push_back(MTP_int(id.bare));
|
||||
}
|
||||
if (!request.ids.empty()) {
|
||||
nearest = now;
|
||||
}
|
||||
} else if (!nearest || nearest > request.when) {
|
||||
nearest = request.when;
|
||||
}
|
||||
}
|
||||
sendPollRequests(toRequest);
|
||||
if (nearest) {
|
||||
_pollTimer.callOnce(std::max(nearest - now, crl::time(1)));
|
||||
}
|
||||
}
|
||||
|
||||
void ViewsManager::sendPollRequests(
|
||||
const base::flat_map<
|
||||
not_null<PeerData*>,
|
||||
QVector<MTPint>> &batched) {
|
||||
for (auto &[peer, list] : batched) {
|
||||
const auto finish = [=, list = list](mtpRequestId id) {
|
||||
const auto now = crl::now();
|
||||
const auto owner = &_session->data();
|
||||
for (auto i = begin(_pollRequests); i != end(_pollRequests);) {
|
||||
if (i->second.id == id) {
|
||||
const auto peer = i->first->id;
|
||||
for (const auto &itemId : i->second.sent) {
|
||||
if (const auto item = owner->message(peer, itemId)) {
|
||||
owner->requestItemRepaint(item);
|
||||
}
|
||||
}
|
||||
i->second.sent.clear();
|
||||
i->second.id = 0;
|
||||
if (i->second.ids.empty()) {
|
||||
i = _pollRequests.erase(i);
|
||||
} else {
|
||||
i->second.when = now + kPollExtendedMediaPeriod;
|
||||
if (!_pollTimer.isActive()) {
|
||||
_pollTimer.callOnce(kPollExtendedMediaPeriod);
|
||||
}
|
||||
++i;
|
||||
}
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
};
|
||||
const auto requestId = _api.request(MTPmessages_GetExtendedMedia(
|
||||
peer->input,
|
||||
MTP_vector<MTPint>(list)
|
||||
)).done([=](const MTPUpdates &result, mtpRequestId id) {
|
||||
_session->api().applyUpdates(result);
|
||||
finish(id);
|
||||
}).fail([=](const MTP::Error &error, mtpRequestId id) {
|
||||
finish(id);
|
||||
}).send();
|
||||
|
||||
_pollRequests[peer].id = requestId;
|
||||
}
|
||||
}
|
||||
|
||||
void ViewsManager::done(
|
||||
QVector<MTPint> ids,
|
||||
const MTPmessages_MessageViews &result,
|
||||
|
||||
@@ -26,8 +26,22 @@ public:
|
||||
void scheduleIncrement(not_null<HistoryItem*> item);
|
||||
void removeIncremented(not_null<PeerData*> peer);
|
||||
|
||||
void pollExtendedMedia(not_null<HistoryItem*> item);
|
||||
|
||||
private:
|
||||
struct PollExtendedMediaRequest {
|
||||
crl::time when = 0;
|
||||
mtpRequestId id = 0;
|
||||
base::flat_set<MsgId> ids;
|
||||
base::flat_set<MsgId> sent;
|
||||
};
|
||||
|
||||
void viewsIncrement();
|
||||
void sendPollRequests();
|
||||
void sendPollRequests(
|
||||
const base::flat_map<
|
||||
not_null<PeerData*>,
|
||||
QVector<MTPint>> &prepared);
|
||||
|
||||
void done(
|
||||
QVector<MTPint> ids,
|
||||
@@ -44,6 +58,11 @@ private:
|
||||
base::flat_map<mtpRequestId, not_null<PeerData*>> _incrementByRequest;
|
||||
base::Timer _incrementTimer;
|
||||
|
||||
base::flat_map<
|
||||
not_null<PeerData*>,
|
||||
PollExtendedMediaRequest> _pollRequests;
|
||||
base::Timer _pollTimer;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "history/history_item.h"
|
||||
#include "history/history.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_channel.h"
|
||||
@@ -17,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_media_types.h"
|
||||
#include "data/data_message_reaction_id.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_account.h"
|
||||
@@ -31,6 +33,8 @@ namespace {
|
||||
|
||||
constexpr auto kContextReactionsLimit = 50;
|
||||
|
||||
using Data::ReactionId;
|
||||
|
||||
struct Peers {
|
||||
std::vector<PeerId> list;
|
||||
bool unknown = false;
|
||||
@@ -41,7 +45,7 @@ inline bool operator==(const Peers &a, const Peers &b) noexcept {
|
||||
|
||||
struct PeerWithReaction {
|
||||
PeerId peer = 0;
|
||||
QString reaction;
|
||||
ReactionId reaction;
|
||||
};
|
||||
inline bool operator==(
|
||||
const PeerWithReaction &a,
|
||||
@@ -84,7 +88,7 @@ struct Context {
|
||||
base::flat_map<not_null<HistoryItem*>, CachedRead> cachedRead;
|
||||
base::flat_map<
|
||||
not_null<HistoryItem*>,
|
||||
base::flat_map<QString, CachedReacted>> cachedReacted;
|
||||
base::flat_map<ReactionId, CachedReacted>> cachedReacted;
|
||||
base::flat_map<not_null<Main::Session*>, rpl::lifetime> subscriptions;
|
||||
|
||||
[[nodiscard]] CachedRead &cacheRead(not_null<HistoryItem*> item) {
|
||||
@@ -97,7 +101,7 @@ struct Context {
|
||||
|
||||
[[nodiscard]] CachedReacted &cacheReacted(
|
||||
not_null<HistoryItem*> item,
|
||||
const QString &reaction) {
|
||||
const ReactionId &reaction) {
|
||||
auto &map = cachedReacted[item];
|
||||
const auto i = map.find(reaction);
|
||||
if (i != end(map)) {
|
||||
@@ -109,7 +113,7 @@ struct Context {
|
||||
|
||||
struct Userpic {
|
||||
not_null<PeerData*> peer;
|
||||
QString reaction;
|
||||
QString customEntityData;
|
||||
mutable std::shared_ptr<Data::CloudImageView> view;
|
||||
mutable InMemoryKey uniqueKey;
|
||||
};
|
||||
@@ -249,7 +253,7 @@ struct State {
|
||||
Peers &&peers) {
|
||||
auto result = PeersWithReactions{
|
||||
.list = peers.list | ranges::views::transform([](PeerId peer) {
|
||||
return PeerWithReaction{.peer = peer };
|
||||
return PeerWithReaction{ .peer = peer };
|
||||
}) | ranges::to_vector,
|
||||
.unknown = peers.unknown,
|
||||
};
|
||||
@@ -259,7 +263,7 @@ struct State {
|
||||
|
||||
[[nodiscard]] rpl::producer<PeersWithReactions> WhoReactedIds(
|
||||
not_null<HistoryItem*> item,
|
||||
const QString &reaction,
|
||||
const ReactionId &reaction,
|
||||
not_null<QWidget*> context) {
|
||||
auto weak = QPointer<QWidget>(context.get());
|
||||
const auto session = &item->history()->session();
|
||||
@@ -273,12 +277,12 @@ struct State {
|
||||
using Flag = MTPmessages_GetMessageReactionsList::Flag;
|
||||
entry.requestId = session->api().request(
|
||||
MTPmessages_GetMessageReactionsList(
|
||||
MTP_flags(reaction.isEmpty()
|
||||
MTP_flags(reaction.empty()
|
||||
? Flag(0)
|
||||
: Flag::f_reaction),
|
||||
item->history()->peer->input,
|
||||
MTP_int(item->id),
|
||||
MTP_string(reaction),
|
||||
ReactionToMTP(reaction),
|
||||
MTPstring(), // offset
|
||||
MTP_int(kContextReactionsLimit)
|
||||
)
|
||||
@@ -299,7 +303,8 @@ struct State {
|
||||
vote.match([&](const auto &data) {
|
||||
parsed.list.push_back(PeerWithReaction{
|
||||
.peer = peerFromMTP(data.vpeer_id()),
|
||||
.reaction = qs(data.vreaction()),
|
||||
.reaction = Data::ReactionFromMTP(
|
||||
data.vreaction()),
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -322,7 +327,7 @@ struct State {
|
||||
not_null<QWidget*> context)
|
||||
-> rpl::producer<PeersWithReactions> {
|
||||
return rpl::combine(
|
||||
WhoReactedIds(item, QString(), context),
|
||||
WhoReactedIds(item, {}, context),
|
||||
WhoReadIds(item, context)
|
||||
) | rpl::map([=](PeersWithReactions &&reacted, Peers &&read) {
|
||||
if (reacted.unknown || read.unknown) {
|
||||
@@ -347,7 +352,7 @@ bool UpdateUserpics(
|
||||
|
||||
struct ResolvedPeer {
|
||||
PeerData *peer = nullptr;
|
||||
QString reaction;
|
||||
ReactionId reaction;
|
||||
};
|
||||
const auto peers = ranges::views::all(
|
||||
ids
|
||||
@@ -373,17 +378,16 @@ bool UpdateUserpics(
|
||||
auto now = std::vector<Userpic>();
|
||||
for (const auto &resolved : peers) {
|
||||
const auto peer = not_null{ resolved.peer };
|
||||
if (ranges::contains(now, peer, &Userpic::peer)) {
|
||||
continue;
|
||||
}
|
||||
const auto &data = ReactionEntityData(resolved.reaction);
|
||||
const auto i = ranges::find(was, peer, &Userpic::peer);
|
||||
if (i != end(was)) {
|
||||
if (i != end(was) && i->view) {
|
||||
now.push_back(std::move(*i));
|
||||
now.back().customEntityData = data;
|
||||
continue;
|
||||
}
|
||||
now.push_back(Userpic{
|
||||
.peer = peer,
|
||||
.reaction = resolved.reaction,
|
||||
.customEntityData = data,
|
||||
});
|
||||
auto &userpic = now.back();
|
||||
userpic.uniqueKey = peer->userpicUniqueKey(userpic.view);
|
||||
@@ -432,7 +436,7 @@ void RegenerateParticipants(not_null<State*> state, int small, int large) {
|
||||
}
|
||||
now.push_back({
|
||||
.name = peer->name(),
|
||||
.reaction = userpic.reaction,
|
||||
.customEntityData = userpic.customEntityData,
|
||||
.userpicLarge = GenerateUserpic(userpic, large),
|
||||
.userpicKey = userpic.uniqueKey,
|
||||
.id = id,
|
||||
@@ -446,7 +450,7 @@ void RegenerateParticipants(not_null<State*> state, int small, int large) {
|
||||
|
||||
rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
not_null<HistoryItem*> item,
|
||||
const QString &reaction,
|
||||
const ReactionId &reaction,
|
||||
not_null<QWidget*> context,
|
||||
const style::WhoRead &st,
|
||||
std::shared_ptr<WhoReadList> whoReadIds) {
|
||||
@@ -455,7 +459,7 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
const auto resolveWhoRead = reaction.isEmpty()
|
||||
const auto resolveWhoRead = reaction.empty()
|
||||
&& WhoReadExists(item);
|
||||
|
||||
const auto state = lifetime.make_state<State>();
|
||||
@@ -463,7 +467,7 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
consumer.put_next_copy(state->current);
|
||||
};
|
||||
|
||||
const auto resolveWhoReacted = !reaction.isEmpty()
|
||||
const auto resolveWhoReacted = !reaction.empty()
|
||||
|| item->canViewReactions();
|
||||
auto idsWithReactions = (resolveWhoRead && resolveWhoReacted)
|
||||
? WhoReadOrReactedIds(item, context)
|
||||
@@ -475,22 +479,26 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
: Ui::WhoReadType::Reacted;
|
||||
if (resolveWhoReacted) {
|
||||
const auto &list = item->reactions();
|
||||
state->current.fullReactionsCount = reaction.isEmpty()
|
||||
? ranges::accumulate(
|
||||
state->current.fullReactionsCount = [&] {
|
||||
if (reaction.empty()) {
|
||||
return ranges::accumulate(
|
||||
list,
|
||||
0,
|
||||
ranges::plus{},
|
||||
&Data::MessageReaction::count);
|
||||
}
|
||||
const auto i = ranges::find(
|
||||
list,
|
||||
0,
|
||||
ranges::plus{},
|
||||
[](const auto &pair) { return pair.second; })
|
||||
: list.contains(reaction)
|
||||
? list.find(reaction)->second
|
||||
: 0;
|
||||
|
||||
// #TODO reactions
|
||||
state->current.singleReaction = !reaction.isEmpty()
|
||||
reaction,
|
||||
&Data::MessageReaction::id);
|
||||
return (i != end(list)) ? i->count : 0;
|
||||
}();
|
||||
state->current.singleCustomEntityData = ReactionEntityData(
|
||||
!reaction.empty()
|
||||
? reaction
|
||||
: (list.size() == 1)
|
||||
? list.front().first
|
||||
: QString();
|
||||
? list.front().id
|
||||
: ReactionId());
|
||||
}
|
||||
std::move(
|
||||
idsWithReactions
|
||||
@@ -510,7 +518,7 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
if (whoReadIds) {
|
||||
const auto reacted = peers.list.size() - ranges::count(
|
||||
peers.list,
|
||||
QString(),
|
||||
ReactionId(),
|
||||
&PeerWithReaction::reaction);
|
||||
whoReadIds->list = (peers.read.size() > reacted)
|
||||
? std::move(peers.read)
|
||||
@@ -583,8 +591,13 @@ bool WhoReadExists(not_null<HistoryItem*> item) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WhoReactedExists(not_null<HistoryItem*> item) {
|
||||
return item->canViewReactions() || WhoReadExists(item);
|
||||
bool WhoReactedExists(
|
||||
not_null<HistoryItem*> item,
|
||||
WhoReactedList list) {
|
||||
if (item->canViewReactions() || WhoReadExists(item)) {
|
||||
return true;
|
||||
}
|
||||
return (list == WhoReactedList::One) && item->history()->peer->isUser();
|
||||
}
|
||||
|
||||
rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
@@ -592,12 +605,12 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
not_null<QWidget*> context,
|
||||
const style::WhoRead &st,
|
||||
std::shared_ptr<WhoReadList> whoReadIds) {
|
||||
return WhoReacted(item, QString(), context, st, std::move(whoReadIds));
|
||||
return WhoReacted(item, {}, context, st, std::move(whoReadIds));
|
||||
}
|
||||
|
||||
rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
not_null<HistoryItem*> item,
|
||||
const QString &reaction,
|
||||
const Data::ReactionId &reaction,
|
||||
not_null<QWidget*> context,
|
||||
const style::WhoRead &st) {
|
||||
return WhoReacted(item, reaction, context, st, nullptr);
|
||||
|
||||
@@ -18,10 +18,21 @@ struct WhoReadContent;
|
||||
enum class WhoReadType;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Data {
|
||||
struct ReactionId;
|
||||
} // namespace Data
|
||||
|
||||
namespace Api {
|
||||
|
||||
enum class WhoReactedList {
|
||||
All,
|
||||
One,
|
||||
};
|
||||
|
||||
[[nodiscard]] bool WhoReadExists(not_null<HistoryItem*> item);
|
||||
[[nodiscard]] bool WhoReactedExists(not_null<HistoryItem*> item);
|
||||
[[nodiscard]] bool WhoReactedExists(
|
||||
not_null<HistoryItem*> item,
|
||||
WhoReactedList list);
|
||||
|
||||
struct WhoReadList {
|
||||
std::vector<PeerId> list;
|
||||
@@ -36,7 +47,7 @@ struct WhoReadList {
|
||||
std::shared_ptr<WhoReadList> whoReadIds = nullptr);
|
||||
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
not_null<HistoryItem*> item,
|
||||
const QString &reaction,
|
||||
const Data::ReactionId &reaction,
|
||||
not_null<QWidget*> context, // Cache results for this lifetime.
|
||||
const style::WhoRead &st);
|
||||
|
||||
|
||||
@@ -1490,6 +1490,8 @@ void ApiWrap::saveStickerSets(
|
||||
if (!archived) {
|
||||
const auto featured = !!(set->flags & Flag::Featured);
|
||||
const auto special = !!(set->flags & Flag::Special);
|
||||
const auto emoji = !!(set->flags & Flag::Emoji);
|
||||
const auto locked = (set->locked > 0);
|
||||
const auto setId = set->mtpInput();
|
||||
|
||||
auto requestId = request(MTPmessages_UninstallStickerSet(
|
||||
@@ -1506,7 +1508,7 @@ void ApiWrap::saveStickerSets(
|
||||
if (removeIndex >= 0) {
|
||||
orderRef.removeAt(removeIndex);
|
||||
}
|
||||
if (!featured && !special) {
|
||||
if (!featured && !special && !emoji && !locked) {
|
||||
sets.erase(it);
|
||||
} else {
|
||||
if (archived) {
|
||||
@@ -1573,7 +1575,9 @@ void ApiWrap::saveStickerSets(
|
||||
if ((set->flags & Flag::Featured)
|
||||
|| (set->flags & Flag::Installed)
|
||||
|| (set->flags & Flag::Archived)
|
||||
|| (set->flags & Flag::Special)) {
|
||||
|| (set->flags & Flag::Special)
|
||||
|| (set->flags & Flag::Emoji)
|
||||
|| (set->locked > 0)) {
|
||||
++it;
|
||||
} else {
|
||||
it = sets.erase(it);
|
||||
|
||||
@@ -897,10 +897,10 @@ void SetupChannelBox::prepare() {
|
||||
}
|
||||
|
||||
void SetupChannelBox::setInnerFocus() {
|
||||
if (_link->isHidden()) {
|
||||
setFocus();
|
||||
} else {
|
||||
if (!_link->isHidden()) {
|
||||
_link->setFocusFast();
|
||||
} else {
|
||||
BoxContent::setInnerFocus();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -357,7 +357,8 @@ void BackgroundPreviewBox::paintTexts(Painter &p, crl::time ms) {
|
||||
auto context = _controller->defaultChatTheme()->preparePaintContext(
|
||||
_chatStyle.get(),
|
||||
rect(),
|
||||
rect());
|
||||
rect(),
|
||||
_controller->isGifPausedAtLeastFor(Window::GifPauseReason::Layer));
|
||||
p.translate(0, textsTop());
|
||||
paintDate(p);
|
||||
|
||||
|
||||
@@ -215,6 +215,11 @@ void ChangePhone::EnterPhone::sendPhoneDone(
|
||||
return data;
|
||||
});
|
||||
|
||||
const auto bad = [&](const char *type) {
|
||||
LOG(("API Error: Should not be '%1'.").arg(type));
|
||||
showError(Lang::Hard::ServerError());
|
||||
return false;
|
||||
};
|
||||
auto codeLength = 0;
|
||||
const auto hasLength = data.vtype().match([&](
|
||||
const MTPDauth_sentCodeTypeApp &typeData) {
|
||||
@@ -227,14 +232,14 @@ void ChangePhone::EnterPhone::sendPhoneDone(
|
||||
}, [&](const MTPDauth_sentCodeTypeCall &typeData) {
|
||||
codeLength = typeData.vlength().v;
|
||||
return true;
|
||||
}, [&](const MTPDauth_sentCodeTypeFlashCall &typeData) {
|
||||
LOG(("Error: should not be flashcall!"));
|
||||
showError(Lang::Hard::ServerError());
|
||||
return false;
|
||||
}, [&](const MTPDauth_sentCodeTypeMissedCall &data) {
|
||||
LOG(("Error: should not be missedcall!"));
|
||||
showError(Lang::Hard::ServerError());
|
||||
return false;
|
||||
}, [&](const MTPDauth_sentCodeTypeFlashCall &) {
|
||||
return bad("FlashCall");
|
||||
}, [&](const MTPDauth_sentCodeTypeMissedCall &) {
|
||||
return bad("MissedCall");
|
||||
}, [&](const MTPDauth_sentCodeTypeEmailCode &) {
|
||||
return bad("EmailCode");
|
||||
}, [&](const MTPDauth_sentCodeTypeSetUpEmailRequired &) {
|
||||
return bad("SetUpEmailRequired");
|
||||
});
|
||||
if (!hasLength) {
|
||||
return;
|
||||
|
||||
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "apiwrap.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "boxes/premium_limits_box.h"
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "chat_helpers/emoji_suggestions_widget.h"
|
||||
#include "chat_helpers/message_field.h"
|
||||
#include "chat_helpers/tabbed_panel.h"
|
||||
@@ -25,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_premium_limits.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "editor/photo_editor_layer_widget.h"
|
||||
#include "history/history_drag_area.h"
|
||||
@@ -67,7 +69,7 @@ auto ListFromMimeData(not_null<const QMimeData*> data, bool premium) {
|
||||
auto result = data->hasUrls()
|
||||
? Storage::PrepareMediaList(
|
||||
// When we edit media, we need only 1 file.
|
||||
data->urls().mid(0, 1),
|
||||
base::GetMimeUrls(data).mid(0, 1),
|
||||
st::sendMediaPreviewSize,
|
||||
premium)
|
||||
: Ui::PreparedList(Error::EmptyFile, QString());
|
||||
@@ -128,8 +130,7 @@ EditCaptionBox::EditCaptionBox(
|
||||
this,
|
||||
st::confirmCaptionArea,
|
||||
Ui::InputField::Mode::MultiLine,
|
||||
tr::lng_photo_caption(),
|
||||
PrepareEditText(item)))
|
||||
tr::lng_photo_caption()))
|
||||
, _emojiToggle(base::make_unique_q<Ui::EmojiButton>(
|
||||
this,
|
||||
st::boxAttachEmoji)) {
|
||||
@@ -153,6 +154,7 @@ void EditCaptionBox::prepare() {
|
||||
|
||||
setupField();
|
||||
setupEmojiPanel();
|
||||
setInitialText();
|
||||
|
||||
rebuildPreview();
|
||||
setupEditEventHandler();
|
||||
@@ -280,7 +282,12 @@ void EditCaptionBox::setupField() {
|
||||
}
|
||||
Unexpected("Action in MimeData hook.");
|
||||
});
|
||||
}
|
||||
|
||||
void EditCaptionBox::setInitialText() {
|
||||
_field->setTextWithTags(
|
||||
PrepareEditText(_historyItem),
|
||||
Ui::InputField::HistoryAction::Clear);
|
||||
auto cursor = _field->textCursor();
|
||||
cursor.movePosition(QTextCursor::End);
|
||||
_field->setTextCursor(cursor);
|
||||
@@ -497,14 +504,22 @@ void EditCaptionBox::setupEmojiPanel() {
|
||||
_emojiPanel->hide();
|
||||
_emojiPanel->selector()->setCurrentPeer(_historyItem->history()->peer);
|
||||
_emojiPanel->selector()->emojiChosen(
|
||||
) | rpl::start_with_next([=](EmojiPtr emoji) {
|
||||
Ui::InsertEmojiAtCursor(_field->textCursor(), emoji);
|
||||
) | rpl::start_with_next([=](ChatHelpers::EmojiChosen data) {
|
||||
Ui::InsertEmojiAtCursor(_field->textCursor(), data.emoji);
|
||||
}, lifetime());
|
||||
_emojiPanel->selector()->customEmojiChosen(
|
||||
) | rpl::start_with_next([=](Selector::FileChosen data) {
|
||||
Data::InsertCustomEmoji(_field.get(), data.document);
|
||||
) | rpl::start_with_next([=](ChatHelpers::FileChosen data) {
|
||||
const auto info = data.document->sticker();
|
||||
if (info
|
||||
&& info->setType == Data::StickersType::Emoji
|
||||
&& !_controller->session().premium()) {
|
||||
ShowPremiumPreviewBox(
|
||||
_controller,
|
||||
PremiumPreview::AnimatedEmoji);
|
||||
} else {
|
||||
Data::InsertCustomEmoji(_field.get(), data.document);
|
||||
}
|
||||
}, lifetime());
|
||||
_emojiPanel->selector()->showPromoForPremiumEmoji();
|
||||
|
||||
const auto filterCallback = [=](not_null<QEvent*> event) {
|
||||
emojiFilterForGeometry(event);
|
||||
|
||||
@@ -52,6 +52,7 @@ private:
|
||||
void setupPhotoEditorEventHandler();
|
||||
void setupField();
|
||||
void setupControls();
|
||||
void setInitialText();
|
||||
|
||||
void updateBoxSize();
|
||||
void captionResized();
|
||||
|
||||
@@ -8,19 +8,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/gift_premium_box.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_premium_option.h"
|
||||
#include "base/weak_ptr.h"
|
||||
#include "core/click_handler_types.h" // ClickHandlerContext.
|
||||
#include "core/local_url_handlers.h" // TryConvertUrlToLocal.
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_peer_values.h" // Data::PeerPremiumValue.
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_subscription_option.h"
|
||||
#include "data/data_user.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "settings/settings_premium.h"
|
||||
#include "ui/basic_click_handlers.h" // UrlClickHandler::Open.
|
||||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/effects/premium_stars.h"
|
||||
#include "ui/effects/premium_stars_colored.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/special_buttons.h"
|
||||
#include "ui/text/format_values.h"
|
||||
@@ -39,11 +39,8 @@ namespace {
|
||||
|
||||
constexpr auto kDiscountDivider = 5.;
|
||||
|
||||
struct GiftOption final {
|
||||
QString url;
|
||||
Ui::Premium::GiftInfo info;
|
||||
};
|
||||
using GiftOptions = std::vector<GiftOption>;
|
||||
using GiftOption = Data::SubscriptionOption;
|
||||
using GiftOptions = Data::SubscriptionOptions;
|
||||
|
||||
GiftOptions GiftOptionFromTL(const MTPDuserFull &data) {
|
||||
auto result = GiftOptions();
|
||||
@@ -51,115 +48,16 @@ GiftOptions GiftOptionFromTL(const MTPDuserFull &data) {
|
||||
if (!gifts) {
|
||||
return result;
|
||||
}
|
||||
const auto monthlyAmount = [&] {
|
||||
const auto &min = ranges::min_element(
|
||||
gifts->v,
|
||||
ranges::less(),
|
||||
[](const MTPPremiumGiftOption &o) { return o.data().vamount().v; }
|
||||
)->data();
|
||||
return min.vamount().v / float64(min.vmonths().v);
|
||||
}();
|
||||
result.reserve(gifts->v.size());
|
||||
for (const auto &gift : gifts->v) {
|
||||
const auto &option = gift.data();
|
||||
const auto botUrl = qs(option.vbot_url());
|
||||
const auto months = option.vmonths().v;
|
||||
const auto amount = option.vamount().v;
|
||||
const auto currency = qs(option.vcurrency());
|
||||
const auto discount = [&] {
|
||||
const auto percent = monthlyAmount * months / float64(amount)
|
||||
- 1.;
|
||||
return std::round(percent * 100. / kDiscountDivider)
|
||||
* kDiscountDivider;
|
||||
}();
|
||||
auto info = Ui::Premium::GiftInfo{
|
||||
.duration = Ui::FormatTTL(months * 86400 * 31),
|
||||
.discount = discount
|
||||
? QString::fromUtf8("\xe2\x88\x92%1%").arg(discount)
|
||||
: QString(),
|
||||
.perMonth = tr::lng_premium_gift_per(
|
||||
tr::now,
|
||||
lt_cost,
|
||||
Ui::FillAmountAndCurrency(
|
||||
amount / float64(months),
|
||||
currency)),
|
||||
.total = Ui::FillAmountAndCurrency(amount, currency),
|
||||
};
|
||||
result.push_back({ .url = botUrl, .info = std::move(info) });
|
||||
result = Api::SubscriptionOptionsFromTL(gifts->v);
|
||||
for (auto &option : result) {
|
||||
option.costPerMonth = tr::lng_premium_gift_per(
|
||||
tr::now,
|
||||
lt_cost,
|
||||
option.costPerMonth);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
class ColoredMiniStars final {
|
||||
public:
|
||||
ColoredMiniStars(not_null<Ui::RpWidget*> parent);
|
||||
|
||||
void setSize(const QSize &size);
|
||||
void setPosition(QPoint position);
|
||||
void paint(Painter &p);
|
||||
|
||||
private:
|
||||
Ui::Premium::MiniStars _ministars;
|
||||
QRectF _ministarsRect;
|
||||
QImage _frame;
|
||||
QImage _mask;
|
||||
QSize _size;
|
||||
QPoint _position;
|
||||
|
||||
};
|
||||
|
||||
ColoredMiniStars::ColoredMiniStars(not_null<Ui::RpWidget*> parent)
|
||||
: _ministars([=](const QRect &r) {
|
||||
parent->update(r.translated(_position));
|
||||
}, true) {
|
||||
}
|
||||
|
||||
void ColoredMiniStars::setSize(const QSize &size) {
|
||||
_frame = QImage(
|
||||
size * style::DevicePixelRatio(),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
_frame.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
|
||||
_mask = _frame;
|
||||
_mask.fill(Qt::transparent);
|
||||
{
|
||||
Painter p(&_mask);
|
||||
auto gradient = QLinearGradient(0, 0, size.width(), 0);
|
||||
gradient.setStops(Ui::Premium::GiftGradientStops());
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(gradient);
|
||||
p.drawRect(0, 0, size.width(), size.height());
|
||||
}
|
||||
|
||||
_size = size;
|
||||
|
||||
{
|
||||
const auto s = _size / Ui::Premium::MiniStars::kSizeFactor;
|
||||
const auto margins = QMarginsF(
|
||||
s.width() / 2.,
|
||||
s.height() / 2.,
|
||||
s.width() / 2.,
|
||||
s.height() / 2.);
|
||||
_ministarsRect = QRectF(QPointF(), _size) - margins;
|
||||
}
|
||||
}
|
||||
|
||||
void ColoredMiniStars::setPosition(QPoint position) {
|
||||
_position = std::move(position);
|
||||
}
|
||||
|
||||
void ColoredMiniStars::paint(Painter &p) {
|
||||
_frame.fill(Qt::transparent);
|
||||
{
|
||||
Painter q(&_frame);
|
||||
_ministars.paint(q, _ministarsRect);
|
||||
q.setCompositionMode(QPainter::CompositionMode_SourceIn);
|
||||
q.drawImage(0, 0, _mask);
|
||||
}
|
||||
|
||||
p.drawImage(_position, _frame);
|
||||
}
|
||||
|
||||
void GiftBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionController*> controller,
|
||||
@@ -182,6 +80,7 @@ void GiftBox(
|
||||
+ userpicPadding.bottom()
|
||||
+ st::defaultUserpicButton.size.height()));
|
||||
|
||||
using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
|
||||
const auto stars = box->lifetime().make_state<ColoredMiniStars>(top);
|
||||
|
||||
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
|
||||
@@ -263,17 +162,14 @@ void GiftBox(
|
||||
auto text = tr::lng_premium_gift_button(
|
||||
tr::now,
|
||||
lt_cost,
|
||||
options[value].info.total);
|
||||
options[value].costTotal);
|
||||
state->buttonText.fire(std::move(text));
|
||||
});
|
||||
Ui::Premium::AddGiftOptions(
|
||||
buttonsParent,
|
||||
group,
|
||||
ranges::views::all(
|
||||
options
|
||||
) | ranges::views::transform([](const GiftOption &option) {
|
||||
return option.info;
|
||||
}) | ranges::to_vector);
|
||||
options,
|
||||
st::premiumGiftOption);
|
||||
|
||||
// Footer.
|
||||
auto terms = object_ptr<Ui::FlatLabel>(
|
||||
@@ -304,26 +200,17 @@ void GiftBox(
|
||||
[] { return QString("gift"); },
|
||||
state->buttonText.events(),
|
||||
Ui::Premium::GiftGradientStops(),
|
||||
[=] {
|
||||
const auto value = group->value();
|
||||
return (value < options.size() && value >= 0)
|
||||
? options[value].botUrl
|
||||
: QString();
|
||||
},
|
||||
});
|
||||
auto button = object_ptr<Ui::GradientButton>::fromRaw(raw);
|
||||
button->resizeToWidth(boxWidth
|
||||
- stButton.buttonPadding.left()
|
||||
- stButton.buttonPadding.right());
|
||||
button->setClickedCallback([=] {
|
||||
const auto value = group->value();
|
||||
Assert(value < options.size() && value >= 0);
|
||||
|
||||
const auto local = Core::TryConvertUrlToLocal(options[value].url);
|
||||
if (local.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
UrlClickHandler::Open(
|
||||
local,
|
||||
QVariant::fromValue(ClickHandlerContext{
|
||||
.sessionWindow = base::make_weak(controller.get()),
|
||||
.botStartAutoSubmit = true,
|
||||
}));
|
||||
});
|
||||
box->setShowFinishedCallback([raw = button.data()]{
|
||||
raw->startGlareAnimation();
|
||||
});
|
||||
|
||||
@@ -76,7 +76,9 @@ void MuteSettingsBox::prepare() {
|
||||
|
||||
_save = [=] {
|
||||
const auto muteForSeconds = group->value() * 3600;
|
||||
_peer->owner().notifySettings().update(_peer, muteForSeconds);
|
||||
_peer->owner().notifySettings().update(
|
||||
_peer,
|
||||
{ .period = muteForSeconds });
|
||||
closeBox();
|
||||
};
|
||||
addButton(tr::lng_box_ok(), _save);
|
||||
|
||||
@@ -1297,6 +1297,7 @@ void RecoverBox::proceedToChange(const QString &code) {
|
||||
// like if (_cloudFields.turningOff) { just RecoverPassword else Check }
|
||||
fields.mtp.curRequest = {};
|
||||
fields.hasPassword = false;
|
||||
fields.customCheckCallback = nullptr;
|
||||
auto box = Box<PasscodeBox>(_session, fields);
|
||||
|
||||
box->boxClosing(
|
||||
|
||||
@@ -653,27 +653,45 @@ void PeerListRow::invalidatePixmapsCache() {
|
||||
}
|
||||
}
|
||||
|
||||
int PeerListRow::nameIconWidth() const {
|
||||
return special()
|
||||
? 0
|
||||
: _peer->isVerified()
|
||||
? st::dialogsVerifiedIcon.width()
|
||||
: (_peer->isPremium() && !_peer->isSelf())
|
||||
? st::dialogsPremiumIcon.width()
|
||||
: 0;
|
||||
}
|
||||
|
||||
void PeerListRow::paintNameIcon(
|
||||
int PeerListRow::paintNameIconGetWidth(
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
Fn<void()> repaint,
|
||||
crl::time now,
|
||||
int nameLeft,
|
||||
int nameTop,
|
||||
int nameWidth,
|
||||
int availableWidth,
|
||||
int outerWidth,
|
||||
bool selected) {
|
||||
if (_peer->isVerified()) {
|
||||
st::dialogsVerifiedIcon.paint(p, x, y, outerWidth);
|
||||
} else if (_peer->isPremium() && !_peer->isSelf()) {
|
||||
st::dialogsPremiumIcon.paint(p, x, y, outerWidth);
|
||||
if (special() || _isSavedMessagesChat || _isRepliesMessagesChat) {
|
||||
return 0;
|
||||
}
|
||||
return _bagde.drawGetWidth(
|
||||
p,
|
||||
QRect(
|
||||
nameLeft,
|
||||
nameTop,
|
||||
availableWidth,
|
||||
st::msgNameStyle.font->height),
|
||||
nameWidth,
|
||||
outerWidth,
|
||||
{
|
||||
.peer = _peer,
|
||||
.verified = &(selected
|
||||
? st::dialogsVerifiedIconOver
|
||||
: st::dialogsVerifiedIcon),
|
||||
.premium = &(selected
|
||||
? st::dialogsPremiumIconOver
|
||||
: st::dialogsPremiumIcon),
|
||||
.scam = &(selected ? st::dialogsScamFgOver : st::dialogsScamFg),
|
||||
.premiumFg = &(selected
|
||||
? st::dialogsVerifiedIconBgOver
|
||||
: st::dialogsVerifiedIconBg),
|
||||
.preview = st::windowBgOver->c,
|
||||
.customEmojiRepaint = repaint,
|
||||
.now = now,
|
||||
.paused = false,
|
||||
});
|
||||
}
|
||||
|
||||
void PeerListRow::paintStatusText(
|
||||
@@ -829,7 +847,7 @@ PeerListContent::PeerListContent(
|
||||
|
||||
using UpdateFlag = Data::PeerUpdate::Flag;
|
||||
_controller->session().changes().peerUpdates(
|
||||
UpdateFlag::Name | UpdateFlag::Photo
|
||||
UpdateFlag::Name | UpdateFlag::Photo | UpdateFlag::EmojiStatus
|
||||
) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
|
||||
if (update.flags & UpdateFlag::Name) {
|
||||
handleNameChanged(update.peer);
|
||||
@@ -1549,33 +1567,49 @@ crl::time PeerListContent::paintRow(
|
||||
|
||||
p.setPen(st::contactsNameFg);
|
||||
|
||||
auto skipRight = _st.item.photoPosition.x();
|
||||
auto rightActionSize = row->rightActionSize();
|
||||
auto rightActionMargins = rightActionSize.isEmpty()
|
||||
const auto skipRight = _st.item.photoPosition.x();
|
||||
const auto rightActionSize = row->rightActionSize();
|
||||
const auto rightActionMargins = rightActionSize.isEmpty()
|
||||
? QMargins()
|
||||
: row->rightActionMargins();
|
||||
auto &name = row->name();
|
||||
auto namex = _st.item.namePosition.x();
|
||||
const auto &name = row->name();
|
||||
const auto namex = _st.item.namePosition.x();
|
||||
const auto namey = _st.item.namePosition.y();
|
||||
auto namew = outerWidth - namex - skipRight;
|
||||
if (!rightActionSize.isEmpty()) {
|
||||
if (!rightActionSize.isEmpty()
|
||||
&& (namey < rightActionMargins.top() + rightActionSize.height())
|
||||
&& (namey + _st.item.nameStyle.font->height
|
||||
> rightActionMargins.top())) {
|
||||
namew -= rightActionMargins.left()
|
||||
+ rightActionSize.width()
|
||||
+ rightActionMargins.right()
|
||||
- skipRight;
|
||||
}
|
||||
auto statusw = namew;
|
||||
if (auto iconWidth = row->nameIconWidth()) {
|
||||
namew -= iconWidth;
|
||||
row->paintNameIcon(
|
||||
p,
|
||||
namex + qMin(name.maxWidth(), namew),
|
||||
_st.item.namePosition.y(),
|
||||
width(),
|
||||
selected);
|
||||
const auto statusx = _st.item.statusPosition.x();
|
||||
const auto statusy = _st.item.statusPosition.y();
|
||||
auto statusw = outerWidth - statusx - skipRight;
|
||||
if (!rightActionSize.isEmpty()
|
||||
&& (statusy < rightActionMargins.top() + rightActionSize.height())
|
||||
&& (statusy + st::contactsStatusFont->height
|
||||
> rightActionMargins.top())) {
|
||||
statusw -= rightActionMargins.left()
|
||||
+ rightActionSize.width()
|
||||
+ rightActionMargins.right()
|
||||
- skipRight;
|
||||
}
|
||||
namew -= row->paintNameIconGetWidth(
|
||||
p,
|
||||
[=] { updateRow(row); },
|
||||
now,
|
||||
namex,
|
||||
namey,
|
||||
name.maxWidth(),
|
||||
namew,
|
||||
width(),
|
||||
selected);
|
||||
auto nameCheckedRatio = row->disabled() ? 0. : row->checkedRatio();
|
||||
p.setPen(anim::pen(_st.item.nameFg, _st.item.nameFgChecked, nameCheckedRatio));
|
||||
name.drawLeftElided(p, namex, _st.item.namePosition.y(), namew, width());
|
||||
name.drawLeftElided(p, namex, namey, namew, width());
|
||||
|
||||
p.setFont(st::contactsStatusFont);
|
||||
if (row->isSearchResult()
|
||||
@@ -1594,16 +1628,16 @@ crl::time PeerListContent::paintRow(
|
||||
highlightedPart = st::contactsStatusFont->elided(highlightedPart, availableWidth);
|
||||
}
|
||||
p.setPen(_st.item.statusFgActive);
|
||||
p.drawTextLeft(_st.item.statusPosition.x(), _st.item.statusPosition.y(), width(), highlightedPart);
|
||||
p.drawTextLeft(statusx, statusy, width(), highlightedPart);
|
||||
} else {
|
||||
grayedPart = st::contactsStatusFont->elided(grayedPart, availableWidth - highlightedWidth);
|
||||
p.setPen(_st.item.statusFgActive);
|
||||
p.drawTextLeft(_st.item.statusPosition.x(), _st.item.statusPosition.y(), width(), highlightedPart);
|
||||
p.drawTextLeft(statusx, statusy, width(), highlightedPart);
|
||||
p.setPen(selected ? _st.item.statusFgOver : _st.item.statusFg);
|
||||
p.drawTextLeft(_st.item.statusPosition.x() + highlightedWidth, _st.item.statusPosition.y(), width(), grayedPart);
|
||||
p.drawTextLeft(statusx + highlightedWidth, statusy, width(), grayedPart);
|
||||
}
|
||||
} else {
|
||||
row->paintStatusText(p, _st.item, _st.item.statusPosition.x(), _st.item.statusPosition.y(), statusw, width(), selected);
|
||||
row->paintStatusText(p, _st.item, statusx, statusy, statusw, width(), selected);
|
||||
}
|
||||
|
||||
row->elementsPaint(
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "ui/rp_widget.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/unread_badge.h"
|
||||
#include "boxes/abstract_box.h"
|
||||
#include "mtproto/sender.h"
|
||||
#include "data/data_cloud_file.h"
|
||||
@@ -94,11 +95,14 @@ public:
|
||||
void clearCustomStatus();
|
||||
|
||||
// Box interface.
|
||||
virtual int nameIconWidth() const;
|
||||
virtual void paintNameIcon(
|
||||
virtual int paintNameIconGetWidth(
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
Fn<void()> repaint,
|
||||
crl::time now,
|
||||
int nameLeft,
|
||||
int nameTop,
|
||||
int nameWidth,
|
||||
int availableWidth,
|
||||
int outerWidth,
|
||||
bool selected);
|
||||
|
||||
@@ -258,6 +262,7 @@ private:
|
||||
std::unique_ptr<Ui::RoundImageCheckbox> _checkbox;
|
||||
Ui::Text::String _name;
|
||||
Ui::Text::String _status;
|
||||
Ui::PeerBadge _bagde;
|
||||
StatusType _statusType = StatusType::Online;
|
||||
crl::time _statusValidTill = 0;
|
||||
base::flat_set<QChar> _nameFirstLetters;
|
||||
|
||||
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/peers/edit_peer_common.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/text/format_values.h" // Ui::FormatPhone
|
||||
#include "ui/text/text_utilities.h"
|
||||
|
||||
@@ -1932,18 +1932,6 @@ auto ParticipantsBoxController::computeType(
|
||||
? Rights::Admin
|
||||
: Rights::Normal;
|
||||
result.adminRank = user ? _additional.adminRank(user) : QString();
|
||||
using Badge = Info::Profile::Badge;
|
||||
result.badge = !user
|
||||
? Badge::None
|
||||
: user->isScam()
|
||||
? Badge::Scam
|
||||
: user->isFake()
|
||||
? Badge::Fake
|
||||
: user->isVerified()
|
||||
? Badge::Verified
|
||||
: (user->isPremium() && participant->session().premiumBadgesShown())
|
||||
? Badge::Premium
|
||||
: Badge::None;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1952,7 +1940,8 @@ void ParticipantsBoxController::recomputeTypeFor(
|
||||
if (_role != Role::Profile) {
|
||||
return;
|
||||
}
|
||||
if (const auto row = delegate()->peerListFindRow(participant->id.value)) {
|
||||
const auto row = delegate()->peerListFindRow(participant->id.value);
|
||||
if (row) {
|
||||
static_cast<Row*>(row)->setType(computeType(participant));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -988,21 +988,30 @@ void Controller::fillManageSection() {
|
||||
|
||||
if (canEditReactions()) {
|
||||
const auto session = &_peer->session();
|
||||
auto reactionsCount = Info::Profile::MigratedOrMeValue(
|
||||
auto allowedReactions = Info::Profile::MigratedOrMeValue(
|
||||
_peer
|
||||
) | rpl::map(
|
||||
Info::Profile::AllowedReactionsCountValue
|
||||
) | rpl::flatten_latest();
|
||||
auto fullCount = Info::Profile::FullReactionsCountValue(session);
|
||||
) | rpl::map([=](not_null<PeerData*> peer) {
|
||||
return peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
Data::PeerUpdate::Flag::Reactions
|
||||
) | rpl::map([=] {
|
||||
return Data::PeerAllowedReactions(peer);
|
||||
});
|
||||
}) | rpl::flatten_latest();
|
||||
auto label = rpl::combine(
|
||||
std::move(reactionsCount),
|
||||
std::move(fullCount)
|
||||
) | rpl::map([=](int allowed, int total) {
|
||||
return allowed
|
||||
? QString::number(allowed) + " / " + QString::number(total)
|
||||
std::move(allowedReactions),
|
||||
Info::Profile::FullReactionsCountValue(session)
|
||||
) | rpl::map([=](const Data::AllowedReactions &allowed, int total) {
|
||||
const auto some = int(allowed.some.size());
|
||||
return (allowed.type != Data::AllowedReactionsType::Some)
|
||||
? tr::lng_manage_peer_reactions_on(tr::now)
|
||||
: some
|
||||
? (QString::number(some)
|
||||
+ " / "
|
||||
+ QString::number(std::max(some, total)))
|
||||
: tr::lng_manage_peer_reactions_off(tr::now);
|
||||
});
|
||||
const auto done = [=](const std::vector<QString> &chosen) {
|
||||
const auto done = [=](const Data::AllowedReactions &chosen) {
|
||||
SaveAllowedReactions(_peer, chosen);
|
||||
};
|
||||
AddButtonWithCount(
|
||||
@@ -1012,10 +1021,11 @@ void Controller::fillManageSection() {
|
||||
[=] {
|
||||
_navigation->parentController()->show(Box(
|
||||
EditAllowedReactionsBox,
|
||||
_navigation,
|
||||
!_peer->isBroadcast(),
|
||||
session->data().reactions().list(
|
||||
Data::Reactions::Type::Active),
|
||||
*Data::PeerAllowedReactions(_peer),
|
||||
Data::PeerAllowedReactions(_peer),
|
||||
done));
|
||||
},
|
||||
{ &st::infoRoundedIconReactions, Settings::kIconRed });
|
||||
|
||||
@@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/peers/edit_peer_reactions.h"
|
||||
|
||||
#include "boxes/reactions_settings_box.h" // AddReactionLottieIcon
|
||||
#include "boxes/reactions_settings_box.h" // AddReactionAnimatedIcon
|
||||
#include "data/data_message_reactions.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_chat.h"
|
||||
@@ -18,47 +18,76 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "settings/settings_common.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_settings.h"
|
||||
#include "styles/style_info.h"
|
||||
|
||||
void EditAllowedReactionsBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
bool isGroup,
|
||||
const std::vector<Data::Reaction> &list,
|
||||
const base::flat_set<QString> &selected,
|
||||
Fn<void(const std::vector<QString> &)> callback) {
|
||||
const Data::AllowedReactions &allowed,
|
||||
Fn<void(const Data::AllowedReactions &)> callback) {
|
||||
using namespace Data;
|
||||
using namespace rpl::mappers;
|
||||
|
||||
const auto iconHeight = st::editPeerReactionsPreview;
|
||||
box->setTitle(tr::lng_manage_peer_reactions());
|
||||
|
||||
struct State {
|
||||
base::flat_map<QString, not_null<Ui::SettingsButton*>> toggles;
|
||||
rpl::variable<bool> anyToggled;
|
||||
rpl::event_stream<bool> forceToggleAll;
|
||||
enum class Option {
|
||||
All,
|
||||
Some,
|
||||
None,
|
||||
};
|
||||
struct State {
|
||||
base::flat_map<ReactionId, not_null<Ui::SettingsButton*>> toggles;
|
||||
rpl::variable<Option> option; // For groups.
|
||||
rpl::variable<bool> anyToggled; // For channels.
|
||||
rpl::event_stream<bool> forceToggleAll; // For channels.
|
||||
};
|
||||
const auto optionInitial = (allowed.type != AllowedReactionsType::Some)
|
||||
? Option::All
|
||||
: allowed.some.empty()
|
||||
? Option::None
|
||||
: Option::Some;
|
||||
const auto state = box->lifetime().make_state<State>(State{
|
||||
.anyToggled = !selected.empty(),
|
||||
.option = optionInitial,
|
||||
.anyToggled = (optionInitial != Option::None),
|
||||
});
|
||||
|
||||
const auto collect = [=] {
|
||||
auto result = std::vector<QString>();
|
||||
result.reserve(state->toggles.size());
|
||||
for (const auto &[emoji, button] : state->toggles) {
|
||||
if (button->toggled()) {
|
||||
result.push_back(emoji);
|
||||
auto result = AllowedReactions();
|
||||
if (!isGroup || state->option.current() == Option::Some) {
|
||||
result.some.reserve(state->toggles.size());
|
||||
for (const auto &[id, button] : state->toggles) {
|
||||
if (button->toggled()) {
|
||||
result.some.push_back(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
result.type = isGroup
|
||||
? (state->option.current() != Option::All
|
||||
? AllowedReactionsType::Some
|
||||
: AllowedReactionsType::All)
|
||||
: (result.some.size() == state->toggles.size())
|
||||
? AllowedReactionsType::Default
|
||||
: AllowedReactionsType::Some;
|
||||
return result;
|
||||
};
|
||||
|
||||
const auto container = box->verticalLayout();
|
||||
|
||||
const auto enabled = Settings::AddButton(
|
||||
const auto enabled = isGroup ? nullptr : Settings::AddButton(
|
||||
container,
|
||||
tr::lng_manage_peer_reactions_enable(),
|
||||
st::manageGroupButton.button);
|
||||
if (!list.empty()) {
|
||||
AddReactionLottieIcon(
|
||||
st::manageGroupButton.button
|
||||
).get();
|
||||
if (enabled && !list.empty()) {
|
||||
AddReactionAnimatedIcon(
|
||||
enabled,
|
||||
enabled->sizeValue(
|
||||
) | rpl::map([=](const QSize &size) {
|
||||
@@ -72,33 +101,88 @@ void EditAllowedReactionsBox(
|
||||
rpl::never<>(),
|
||||
&enabled->lifetime());
|
||||
}
|
||||
enabled->toggleOn(state->anyToggled.value());
|
||||
enabled->toggledChanges(
|
||||
) | rpl::filter([=](bool value) {
|
||||
return (value != state->anyToggled.current());
|
||||
}) | rpl::start_to_stream(state->forceToggleAll, enabled->lifetime());
|
||||
if (enabled) {
|
||||
enabled->toggleOn(state->anyToggled.value());
|
||||
enabled->toggledChanges(
|
||||
) | rpl::filter([=](bool value) {
|
||||
return (value != state->anyToggled.current());
|
||||
}) | rpl::start_to_stream(state->forceToggleAll, enabled->lifetime());
|
||||
}
|
||||
const auto group = std::make_shared<Ui::RadioenumGroup<Option>>(
|
||||
state->option.current());
|
||||
group->setChangedCallback([=](Option value) {
|
||||
state->option = value;
|
||||
});
|
||||
const auto addOption = [&](Option option, const QString &text) {
|
||||
if (!isGroup) {
|
||||
return;
|
||||
}
|
||||
container->add(
|
||||
object_ptr<Ui::Radioenum<Option>>(
|
||||
container,
|
||||
group,
|
||||
option,
|
||||
text,
|
||||
st::settingsSendType),
|
||||
st::settingsSendTypePadding);
|
||||
};
|
||||
addOption(Option::All, tr::lng_manage_peer_reactions_all(tr::now));
|
||||
addOption(Option::Some, tr::lng_manage_peer_reactions_some(tr::now));
|
||||
addOption(Option::None, tr::lng_manage_peer_reactions_none(tr::now));
|
||||
|
||||
const auto about = [](Option option) {
|
||||
switch (option) {
|
||||
case Option::All: return tr::lng_manage_peer_reactions_all_about();
|
||||
case Option::Some: return tr::lng_manage_peer_reactions_some_about();
|
||||
case Option::None: return tr::lng_manage_peer_reactions_none_about();
|
||||
}
|
||||
Unexpected("Option value in EditAllowedReactionsBox.");
|
||||
};
|
||||
Settings::AddSkip(container);
|
||||
Settings::AddDividerText(
|
||||
container,
|
||||
(isGroup
|
||||
? tr::lng_manage_peer_reactions_about
|
||||
: tr::lng_manage_peer_reactions_about_channel)());
|
||||
? (state->option.value()
|
||||
| rpl::map(about)
|
||||
| rpl::flatten_latest())
|
||||
: tr::lng_manage_peer_reactions_about_channel()));
|
||||
|
||||
Settings::AddSkip(container);
|
||||
Settings::AddSubsectionTitle(
|
||||
container,
|
||||
tr::lng_manage_peer_reactions_available());
|
||||
|
||||
const auto active = [&](const Data::Reaction &entry) {
|
||||
return selected.contains(entry.emoji);
|
||||
};
|
||||
const auto add = [&](const Data::Reaction &entry) {
|
||||
const auto button = Settings::AddButton(
|
||||
const auto wrap = enabled ? nullptr : container->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
container,
|
||||
object_ptr<Ui::VerticalLayout>(container)));
|
||||
if (wrap) {
|
||||
wrap->toggleOn(state->option.value() | rpl::map(_1 == Option::Some));
|
||||
wrap->finishAnimating();
|
||||
}
|
||||
const auto reactions = wrap ? wrap->entity() : container.get();
|
||||
|
||||
Settings::AddSkip(reactions);
|
||||
Settings::AddSubsectionTitle(
|
||||
reactions,
|
||||
(enabled
|
||||
? tr::lng_manage_peer_reactions_available()
|
||||
: tr::lng_manage_peer_reactions_some_title()));
|
||||
|
||||
const auto like = QString::fromUtf8("\xf0\x9f\x91\x8d");
|
||||
const auto dislike = QString::fromUtf8("\xf0\x9f\x91\x8e");
|
||||
const auto activeOnStart = [&](const ReactionId &id) {
|
||||
const auto inSome = ranges::contains(allowed.some, id);
|
||||
if (!isGroup) {
|
||||
return inSome || (allowed.type != AllowedReactionsType::Some);
|
||||
}
|
||||
const auto emoji = id.emoji();
|
||||
const auto isDefault = (emoji == like) || (emoji == dislike);
|
||||
return (allowed.type != AllowedReactionsType::Some)
|
||||
? isDefault
|
||||
: (inSome || (isDefault && allowed.some.empty()));
|
||||
};
|
||||
const auto add = [&](const Reaction &entry) {
|
||||
const auto button = Settings::AddButton(
|
||||
reactions,
|
||||
rpl::single(entry.title),
|
||||
st::manageGroupButton.button);
|
||||
AddReactionLottieIcon(
|
||||
AddReactionAnimatedIcon(
|
||||
button,
|
||||
button->sizeValue(
|
||||
) | rpl::map([=](const QSize &size) {
|
||||
@@ -114,29 +198,56 @@ void EditAllowedReactionsBox(
|
||||
}) | rpl::to_empty,
|
||||
rpl::never<>(),
|
||||
&button->lifetime());
|
||||
state->toggles.emplace(entry.emoji, button);
|
||||
state->toggles.emplace(entry.id, button);
|
||||
button->toggleOn(rpl::single(
|
||||
active(entry)
|
||||
) | rpl::then(
|
||||
state->forceToggleAll.events()
|
||||
activeOnStart(entry.id)
|
||||
) | rpl::then(enabled
|
||||
? (state->forceToggleAll.events() | rpl::type_erased())
|
||||
: rpl::never<bool>()
|
||||
));
|
||||
button->toggledChanges(
|
||||
) | rpl::start_with_next([=](bool toggled) {
|
||||
if (toggled) {
|
||||
state->anyToggled = true;
|
||||
} else if (collect().empty()) {
|
||||
state->anyToggled = false;
|
||||
}
|
||||
}, button->lifetime());
|
||||
if (enabled) {
|
||||
button->toggledChanges(
|
||||
) | rpl::start_with_next([=](bool toggled) {
|
||||
if (toggled) {
|
||||
state->anyToggled = true;
|
||||
} else if (collect().some.empty()) {
|
||||
state->anyToggled = false;
|
||||
}
|
||||
}, button->lifetime());
|
||||
}
|
||||
};
|
||||
for (const auto &entry : list) {
|
||||
add(entry);
|
||||
}
|
||||
for (const auto &id : allowed.some) {
|
||||
if (const auto customId = id.custom()) {
|
||||
// Some possible forward compatibility.
|
||||
const auto button = Settings::AddButton(
|
||||
reactions,
|
||||
rpl::single(u"Custom reaction"_q),
|
||||
st::manageGroupButton.button);
|
||||
AddReactionCustomIcon(
|
||||
button,
|
||||
button->sizeValue(
|
||||
) | rpl::map([=](const QSize &size) {
|
||||
return QPoint(
|
||||
st::editPeerReactionsIconLeft,
|
||||
(size.height() - iconHeight) / 2);
|
||||
}),
|
||||
iconHeight,
|
||||
navigation->parentController(),
|
||||
customId,
|
||||
rpl::never<>(),
|
||||
&button->lifetime());
|
||||
state->toggles.emplace(id, button);
|
||||
button->toggleOn(rpl::single(true));
|
||||
}
|
||||
}
|
||||
|
||||
box->addButton(tr::lng_settings_save(), [=] {
|
||||
const auto ids = collect();
|
||||
const auto result = collect();
|
||||
box->closeBox();
|
||||
callback(ids);
|
||||
callback(result);
|
||||
});
|
||||
box->addButton(tr::lng_cancel(), [=] {
|
||||
box->closeBox();
|
||||
@@ -145,27 +256,35 @@ void EditAllowedReactionsBox(
|
||||
|
||||
void SaveAllowedReactions(
|
||||
not_null<PeerData*> peer,
|
||||
const std::vector<QString> &allowed) {
|
||||
auto ids = allowed | ranges::views::transform([=](QString value) {
|
||||
return MTP_string(value);
|
||||
}) | ranges::to<QVector<MTPstring>>;
|
||||
const Data::AllowedReactions &allowed) {
|
||||
auto ids = allowed.some | ranges::views::transform(
|
||||
Data::ReactionToMTP
|
||||
) | ranges::to<QVector<MTPReaction>>;
|
||||
|
||||
using Type = Data::AllowedReactionsType;
|
||||
const auto updated = (allowed.type != Type::Some)
|
||||
? MTP_chatReactionsAll(MTP_flags((allowed.type == Type::Default)
|
||||
? MTPDchatReactionsAll::Flag(0)
|
||||
: MTPDchatReactionsAll::Flag::f_allow_custom))
|
||||
: allowed.some.empty()
|
||||
? MTP_chatReactionsNone()
|
||||
: MTP_chatReactionsSome(MTP_vector<MTPReaction>(ids));
|
||||
peer->session().api().request(MTPmessages_SetChatAvailableReactions(
|
||||
peer->input,
|
||||
MTP_vector<MTPstring>(ids)
|
||||
updated
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
peer->session().api().applyUpdates(result);
|
||||
if (const auto chat = peer->asChat()) {
|
||||
chat->setAllowedReactions({ begin(allowed), end(allowed) });
|
||||
chat->setAllowedReactions(Data::Parse(updated));
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
channel->setAllowedReactions({ begin(allowed), end(allowed) });
|
||||
channel->setAllowedReactions(Data::Parse(updated));
|
||||
} else {
|
||||
Unexpected("Invalid peer type in SaveAllowedReactions.");
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
if (error.type() == qstr("REACTION_INVALID")) {
|
||||
peer->updateFullForced();
|
||||
peer->owner().reactions().refresh();
|
||||
peer->owner().reactions().refreshDefault();
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
@@ -11,19 +11,25 @@ class PeerData;
|
||||
|
||||
namespace Data {
|
||||
struct Reaction;
|
||||
struct AllowedReactions;
|
||||
} // namespace Data
|
||||
|
||||
namespace Ui {
|
||||
class GenericBox;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Window {
|
||||
class SessionNavigation;
|
||||
} // namespace Window
|
||||
|
||||
void EditAllowedReactionsBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
bool isGroup,
|
||||
const std::vector<Data::Reaction> &list,
|
||||
const base::flat_set<QString> &selected,
|
||||
Fn<void(const std::vector<QString> &)> callback);
|
||||
const Data::AllowedReactions &allowed,
|
||||
Fn<void(const Data::AllowedReactions &)> callback);
|
||||
|
||||
void SaveAllowedReactions(
|
||||
not_null<PeerData*> peer,
|
||||
const std::vector<QString> &allowed);
|
||||
const Data::AllowedReactions &allowed);
|
||||
|
||||
@@ -241,6 +241,10 @@ void Controller::createContent() {
|
||||
) | rpl::start_with_next([=](bool toggled) {
|
||||
_dataSavedValue->joinToWrite = toggled;
|
||||
}, wrap->lifetime());
|
||||
} else {
|
||||
_controls.whoSendWrap->toggle(
|
||||
(_controls.privacy->value() == Privacy::HasUsername),
|
||||
anim::type::instant);
|
||||
}
|
||||
auto joinToWrite = _controls.joinToWrite
|
||||
? _controls.joinToWrite->toggledValue()
|
||||
|
||||
@@ -12,7 +12,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_message_reactions.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_streaming.h"
|
||||
#include "data/data_peer_values.h"
|
||||
@@ -58,10 +57,11 @@ constexpr auto kStarOpacityOff = 0.1;
|
||||
constexpr auto kStarOpacityOn = 1.;
|
||||
constexpr auto kStarPeriod = 3 * crl::time(1000);
|
||||
|
||||
using Data::ReactionId;
|
||||
|
||||
struct Descriptor {
|
||||
PremiumPreview section = PremiumPreview::Stickers;
|
||||
DocumentData *requestedSticker = nullptr;
|
||||
base::flat_map<QString, ReactionDisableType> disabled;
|
||||
bool fromSettings = false;
|
||||
Fn<void()> hiddenCallback;
|
||||
Fn<void(not_null<Ui::BoxContent*>)> shownCallback;
|
||||
@@ -70,27 +70,9 @@ struct Descriptor {
|
||||
bool operator==(const Descriptor &a, const Descriptor &b) {
|
||||
return (a.section == b.section)
|
||||
&& (a.requestedSticker == b.requestedSticker)
|
||||
&& (a.disabled == b.disabled)
|
||||
&& (a.fromSettings == b.fromSettings);
|
||||
}
|
||||
|
||||
[[nodiscard]] int ComputeX(int column, int columns) {
|
||||
const auto skip = st::premiumReactionWidthSkip;
|
||||
const auto fullWidth = columns * skip;
|
||||
const auto left = (st::boxWideWidth - fullWidth) / 2;
|
||||
return left + column * skip + (skip / 2);
|
||||
}
|
||||
|
||||
[[nodiscard]] int ComputeY(int row, int rows) {
|
||||
const auto middle = (rows > 3)
|
||||
? (st::premiumReactionInfoTop / 2)
|
||||
: st::premiumReactionsMiddle;
|
||||
const auto skip = st::premiumReactionHeightSkip;
|
||||
const auto fullHeight = rows * skip;
|
||||
const auto top = middle - (fullHeight / 2);
|
||||
return top + row * skip + (skip / 2);
|
||||
}
|
||||
|
||||
struct Preload {
|
||||
Descriptor descriptor;
|
||||
std::shared_ptr<Data::DocumentMedia> media;
|
||||
@@ -118,8 +100,10 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
|
||||
return tr::lng_premium_summary_subtitle_voice_to_text();
|
||||
case PremiumPreview::NoAds:
|
||||
return tr::lng_premium_summary_subtitle_no_ads();
|
||||
case PremiumPreview::Reactions:
|
||||
return tr::lng_premium_summary_subtitle_unique_reactions();
|
||||
case PremiumPreview::EmojiStatus:
|
||||
return tr::lng_premium_summary_subtitle_emoji_status();
|
||||
case PremiumPreview::InfiniteReactions:
|
||||
return tr::lng_premium_summary_subtitle_infinite_reactions();
|
||||
case PremiumPreview::Stickers:
|
||||
return tr::lng_premium_summary_subtitle_premium_stickers();
|
||||
case PremiumPreview::AnimatedEmoji:
|
||||
@@ -144,8 +128,10 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
|
||||
return tr::lng_premium_summary_about_voice_to_text();
|
||||
case PremiumPreview::NoAds:
|
||||
return tr::lng_premium_summary_about_no_ads();
|
||||
case PremiumPreview::Reactions:
|
||||
return tr::lng_premium_summary_about_unique_reactions();
|
||||
case PremiumPreview::EmojiStatus:
|
||||
return tr::lng_premium_summary_about_emoji_status();
|
||||
case PremiumPreview::InfiniteReactions:
|
||||
return tr::lng_premium_summary_about_infinite_reactions();
|
||||
case PremiumPreview::Stickers:
|
||||
return tr::lng_premium_summary_about_premium_stickers();
|
||||
case PremiumPreview::AnimatedEmoji:
|
||||
@@ -463,6 +449,8 @@ struct VideoPreviewDocument {
|
||||
case PremiumPreview::AnimatedEmoji: return "animated_emoji";
|
||||
case PremiumPreview::AdvancedChatManagement:
|
||||
return "advanced_chat_management";
|
||||
case PremiumPreview::EmojiStatus: return "emoji_status";
|
||||
case PremiumPreview::InfiniteReactions: return "infinite_reactions";
|
||||
case PremiumPreview::ProfileBadge: return "profile_badge";
|
||||
case PremiumPreview::AnimatedUserpics: return "animated_userpics";
|
||||
}
|
||||
@@ -724,423 +712,12 @@ struct VideoPreviewDocument {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
class ReactionPreview final {
|
||||
public:
|
||||
ReactionPreview(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const Data::Reaction &reaction,
|
||||
ReactionDisableType type,
|
||||
Fn<void()> update,
|
||||
QPoint position);
|
||||
|
||||
[[nodiscard]] bool playsEffect() const;
|
||||
void paint(Painter &p);
|
||||
void paintEffect(QPainter &p);
|
||||
|
||||
void setOver(bool over);
|
||||
void startAnimations();
|
||||
void cancelAnimations();
|
||||
[[nodiscard]] bool ready() const;
|
||||
[[nodiscard]] bool disabled() const;
|
||||
[[nodiscard]] QRect geometry() const;
|
||||
|
||||
private:
|
||||
void checkReady();
|
||||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
const Fn<void()> _update;
|
||||
const QPoint _position;
|
||||
Ui::Animations::Simple _scale;
|
||||
std::shared_ptr<Data::DocumentMedia> _centerMedia;
|
||||
std::shared_ptr<Data::DocumentMedia> _aroundMedia;
|
||||
std::unique_ptr<Lottie::SinglePlayer> _center;
|
||||
std::unique_ptr<Lottie::SinglePlayer> _around;
|
||||
std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
|
||||
QImage _cache1;
|
||||
QImage _cache2;
|
||||
bool _over = false;
|
||||
bool _disabled = false;
|
||||
bool _playRequested = false;
|
||||
bool _aroundPlaying = false;
|
||||
bool _centerPlaying = false;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
ReactionPreview::ReactionPreview(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const Data::Reaction &reaction,
|
||||
ReactionDisableType type,
|
||||
Fn<void()> update,
|
||||
QPoint position)
|
||||
: _controller(controller)
|
||||
, _update(std::move(update))
|
||||
, _position(position)
|
||||
, _centerMedia(reaction.centerIcon->createMediaView())
|
||||
, _aroundMedia(reaction.aroundAnimation->createMediaView())
|
||||
, _pathGradient(
|
||||
HistoryView::MakePathShiftGradient(
|
||||
controller->chatStyle(),
|
||||
_update))
|
||||
, _disabled(type != ReactionDisableType::None) {
|
||||
_centerMedia->checkStickerLarge();
|
||||
_aroundMedia->checkStickerLarge();
|
||||
checkReady();
|
||||
if (!_center || !_around) {
|
||||
_controller->session().downloaderTaskFinished(
|
||||
) | rpl::take_while([=] {
|
||||
checkReady();
|
||||
return !_center || !_around;
|
||||
}) | rpl::start(_lifetime);
|
||||
}
|
||||
}
|
||||
|
||||
QRect ReactionPreview::geometry() const {
|
||||
const auto xsize = st::premiumReactionWidthSkip;
|
||||
const auto ysize = st::premiumReactionHeightSkip;
|
||||
return { _position - QPoint(xsize / 2, ysize / 2), QSize(xsize, ysize) };
|
||||
}
|
||||
|
||||
void ReactionPreview::checkReady() {
|
||||
const auto make = [&](
|
||||
const std::shared_ptr<Data::DocumentMedia> &media,
|
||||
int size) {
|
||||
const auto bytes = media->bytes();
|
||||
const auto filepath = media->owner()->filepath();
|
||||
auto result = ChatHelpers::LottiePlayerFromDocument(
|
||||
media.get(),
|
||||
nullptr,
|
||||
ChatHelpers::StickerLottieSize::PremiumReactionPreview,
|
||||
QSize(size, size) * style::DevicePixelRatio(),
|
||||
Lottie::Quality::Default);
|
||||
result->updates() | rpl::start_with_next(_update, _lifetime);
|
||||
return result;
|
||||
};
|
||||
if (!_center && _centerMedia->loaded()) {
|
||||
_center = make(_centerMedia, st::premiumReactionSize);
|
||||
}
|
||||
if (!_around && _aroundMedia->loaded()) {
|
||||
_around = make(_aroundMedia, st::premiumReactionAround);
|
||||
}
|
||||
}
|
||||
|
||||
void ReactionPreview::setOver(bool over) {
|
||||
if (_over == over || _disabled) {
|
||||
return;
|
||||
}
|
||||
_over = over;
|
||||
const auto from = st::premiumReactionScale;
|
||||
_scale.start(
|
||||
_update,
|
||||
over ? from : 1.,
|
||||
over ? 1. : from,
|
||||
st::slideWrapDuration);
|
||||
}
|
||||
|
||||
void ReactionPreview::startAnimations() {
|
||||
if (_disabled) {
|
||||
return;
|
||||
}
|
||||
_playRequested = true;
|
||||
if (!_center || !_center->ready() || !_around || !_around->ready()) {
|
||||
return;
|
||||
}
|
||||
_update();
|
||||
}
|
||||
|
||||
void ReactionPreview::cancelAnimations() {
|
||||
_playRequested = false;
|
||||
}
|
||||
|
||||
bool ReactionPreview::ready() const {
|
||||
return _center && _center->ready();
|
||||
}
|
||||
|
||||
bool ReactionPreview::disabled() const {
|
||||
return _disabled;
|
||||
}
|
||||
|
||||
void ReactionPreview::paint(Painter &p) {
|
||||
const auto center = st::premiumReactionSize;
|
||||
const auto scale = _scale.value(_over ? 1. : st::premiumReactionScale);
|
||||
const auto inner = QRect(
|
||||
-center / 2,
|
||||
-center / 2,
|
||||
center,
|
||||
center
|
||||
).translated(_position);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
const auto centerReady = _center && _center->ready();
|
||||
const auto staticCenter = centerReady && !_centerPlaying;
|
||||
const auto use1 = staticCenter && scale == 1.;
|
||||
const auto use2 = staticCenter && scale == st::premiumReactionScale;
|
||||
const auto useScale = (!use1 && !use2 && scale != 1.);
|
||||
if (useScale) {
|
||||
p.save();
|
||||
p.translate(inner.center());
|
||||
p.scale(scale, scale);
|
||||
p.translate(-inner.center());
|
||||
}
|
||||
if (_disabled) {
|
||||
p.setOpacity(kDisabledOpacity);
|
||||
}
|
||||
checkReady();
|
||||
if (centerReady) {
|
||||
if (use1 || use2) {
|
||||
auto &cache = use1 ? _cache1 : _cache2;
|
||||
const auto use = int(std::round(center * scale));
|
||||
const auto rect = QRect(-use / 2, -use / 2, use, use).translated(
|
||||
_position);
|
||||
if (cache.isNull()) {
|
||||
cache = _center->frame().scaledToWidth(
|
||||
use * style::DevicePixelRatio(),
|
||||
Qt::SmoothTransformation);
|
||||
}
|
||||
p.drawImage(rect, cache);
|
||||
} else {
|
||||
p.drawImage(inner, _center->frame());
|
||||
}
|
||||
if (_centerPlaying) {
|
||||
const auto almost = (_center->frameIndex() + 1)
|
||||
== _center->framesCount();
|
||||
const auto marked = _center->markFrameShown();
|
||||
if (almost && marked) {
|
||||
_centerPlaying = false;
|
||||
}
|
||||
}
|
||||
if (_around
|
||||
&& _around->ready()
|
||||
&& !_aroundPlaying
|
||||
&& !_centerPlaying
|
||||
&& _playRequested) {
|
||||
_aroundPlaying = _centerPlaying = true;
|
||||
_playRequested = false;
|
||||
}
|
||||
} else {
|
||||
p.setBrush(_controller->chatStyle()->msgServiceBg());
|
||||
ChatHelpers::PaintStickerThumbnailPath(
|
||||
p,
|
||||
_centerMedia.get(),
|
||||
inner,
|
||||
_pathGradient.get());
|
||||
}
|
||||
if (useScale) {
|
||||
p.restore();
|
||||
} else if (_disabled) {
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
}
|
||||
|
||||
bool ReactionPreview::playsEffect() const {
|
||||
return _aroundPlaying;
|
||||
}
|
||||
|
||||
void ReactionPreview::paintEffect(QPainter &p) {
|
||||
if (!_aroundPlaying) {
|
||||
return;
|
||||
}
|
||||
const auto size = st::premiumReactionAround;
|
||||
const auto outer = QRect(-size/2, -size/2, size, size).translated(
|
||||
_position);
|
||||
const auto scale = _scale.value(_over ? 1. : st::premiumReactionScale);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
if (scale != 1.) {
|
||||
p.save();
|
||||
p.translate(outer.center());
|
||||
p.scale(scale, scale);
|
||||
p.translate(-outer.center());
|
||||
}
|
||||
p.drawImage(outer, _around->frame());
|
||||
if (scale != 1.) {
|
||||
p.restore();
|
||||
}
|
||||
if (_aroundPlaying) {
|
||||
const auto almost = (_around->frameIndex() + 1)
|
||||
== _around->framesCount();
|
||||
const auto marked = _around->markFrameShown();
|
||||
if (almost && marked) {
|
||||
_aroundPlaying = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] not_null<Ui::RpWidget*> ReactionsPreview(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
const base::flat_map<QString, ReactionDisableType> &disabled,
|
||||
Fn<void()> readyCallback) {
|
||||
struct State {
|
||||
std::vector<std::unique_ptr<ReactionPreview>> entries;
|
||||
Ui::Text::String bottom;
|
||||
int selected = -1;
|
||||
bool readyInvoked = false;
|
||||
};
|
||||
const auto result = Ui::CreateChild<Ui::RpWidget>(parent.get());
|
||||
result->show();
|
||||
|
||||
auto &lifetime = result->lifetime();
|
||||
const auto state = lifetime.make_state<State>();
|
||||
|
||||
result->setMouseTracking(true);
|
||||
|
||||
parent->sizeValue(
|
||||
) | rpl::start_with_next([=] {
|
||||
result->setGeometry(parent->rect());
|
||||
}, result->lifetime());
|
||||
|
||||
using namespace HistoryView;
|
||||
const auto list = controller->session().data().reactions().list(
|
||||
Data::Reactions::Type::Active);
|
||||
const auto count = ranges::count(list, true, &Data::Reaction::premium);
|
||||
const auto rows = (count + kReactionsPerRow - 1) / kReactionsPerRow;
|
||||
const auto inrowmax = (count + rows - 1) / rows;
|
||||
const auto inrowless = (inrowmax * rows - count);
|
||||
const auto inrowmore = rows - inrowless;
|
||||
const auto inmaxrows = inrowmore * inrowmax;
|
||||
auto index = 0;
|
||||
auto disableType = ReactionDisableType::None;
|
||||
for (const auto &reaction : list) {
|
||||
if (!reaction.premium) {
|
||||
continue;
|
||||
}
|
||||
const auto inrow = (index < inmaxrows) ? inrowmax : (inrowmax - 1);
|
||||
const auto row = (index < inmaxrows)
|
||||
? (index / inrow)
|
||||
: (inrowmore + ((index - inmaxrows) / inrow));
|
||||
const auto column = (index < inmaxrows)
|
||||
? (index % inrow)
|
||||
: ((index - inmaxrows) % inrow);
|
||||
++index;
|
||||
if (!reaction.centerIcon || !reaction.aroundAnimation) {
|
||||
continue;
|
||||
}
|
||||
const auto i = disabled.find(reaction.emoji);
|
||||
const auto disable = (i != end(disabled))
|
||||
? i->second
|
||||
: ReactionDisableType::None;
|
||||
if (disable != ReactionDisableType::None) {
|
||||
disableType = disable;
|
||||
}
|
||||
state->entries.push_back(std::make_unique<ReactionPreview>(
|
||||
controller,
|
||||
reaction,
|
||||
disable,
|
||||
[=] { result->update(); },
|
||||
QPoint(ComputeX(column, inrow), ComputeY(row, rows))));
|
||||
}
|
||||
|
||||
const auto bottom1 = tr::lng_reaction_premium_info(tr::now);
|
||||
const auto bottom2 = (disableType == ReactionDisableType::None)
|
||||
? QString()
|
||||
: (disableType == ReactionDisableType::Group)
|
||||
? tr::lng_reaction_premium_no_group(tr::now)
|
||||
: tr::lng_reaction_premium_no_channel(tr::now);
|
||||
state->bottom.setText(
|
||||
st::defaultTextStyle,
|
||||
(bottom1 + '\n' + bottom2).trimmed());
|
||||
|
||||
result->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
auto p = Painter(result);
|
||||
auto effects = std::vector<Fn<void()>>();
|
||||
auto ready = 0;
|
||||
for (const auto &entry : state->entries) {
|
||||
entry->paint(p);
|
||||
if (entry->ready()) {
|
||||
++ready;
|
||||
}
|
||||
if (entry->playsEffect()) {
|
||||
effects.push_back([&] {
|
||||
entry->paintEffect(p);
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!state->readyInvoked
|
||||
&& readyCallback
|
||||
&& ready > 0
|
||||
&& ready == state->entries.size()) {
|
||||
state->readyInvoked = true;
|
||||
readyCallback();
|
||||
|
||||
}
|
||||
const auto padding = st::boxRowPadding;
|
||||
const auto available = parent->width()
|
||||
- padding.left()
|
||||
- padding.right();
|
||||
const auto top = st::premiumReactionInfoTop
|
||||
+ ((state->bottom.maxWidth() > available)
|
||||
? st::normalFont->height
|
||||
: 0);
|
||||
p.setPen(st::premiumButtonFg);
|
||||
state->bottom.draw(
|
||||
p,
|
||||
padding.left(),
|
||||
top,
|
||||
available,
|
||||
style::al_top);
|
||||
for (const auto &paint : effects) {
|
||||
paint();
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
const auto lookup = [=](QPoint point) {
|
||||
auto index = 0;
|
||||
for (const auto &entry : state->entries) {
|
||||
if (entry->geometry().contains(point) && !entry->disabled()) {
|
||||
return index;
|
||||
}
|
||||
++index;
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
const auto select = [=](int index) {
|
||||
const auto wasInside = (state->selected >= 0);
|
||||
const auto nowInside = (index >= 0);
|
||||
if (state->selected != index) {
|
||||
if (wasInside) {
|
||||
state->entries[state->selected]->setOver(false);
|
||||
}
|
||||
if (nowInside) {
|
||||
state->entries[index]->setOver(true);
|
||||
}
|
||||
state->selected = index;
|
||||
}
|
||||
if (wasInside != nowInside) {
|
||||
result->setCursor(nowInside
|
||||
? style::cur_pointer
|
||||
: style::cur_default);
|
||||
}
|
||||
};
|
||||
result->events(
|
||||
) | rpl::start_with_next([=](not_null<QEvent*> event) {
|
||||
if (event->type() == QEvent::MouseButtonPress) {
|
||||
const auto point = static_cast<QMouseEvent*>(event.get())->pos();
|
||||
if (state->selected >= 0) {
|
||||
state->entries[state->selected]->cancelAnimations();
|
||||
}
|
||||
if (const auto index = lookup(point); index >= 0) {
|
||||
state->entries[index]->startAnimations();
|
||||
}
|
||||
} else if (event->type() == QEvent::MouseMove) {
|
||||
const auto point = static_cast<QMouseEvent*>(event.get())->pos();
|
||||
select(lookup(point));
|
||||
} else if (event->type() == QEvent::Leave) {
|
||||
select(-1);
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] not_null<Ui::RpWidget*> GenerateDefaultPreview(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
PremiumPreview section,
|
||||
Fn<void()> readyCallback) {
|
||||
switch (section) {
|
||||
case PremiumPreview::Reactions:
|
||||
return ReactionsPreview(parent, controller, {}, readyCallback);
|
||||
case PremiumPreview::Stickers:
|
||||
return StickersPreview(parent, controller, readyCallback);
|
||||
default:
|
||||
@@ -1226,8 +803,6 @@ void PreviewBox(
|
||||
Ui::RpWidget *content = nullptr;
|
||||
Ui::RpWidget *stickersPreload = nullptr;
|
||||
bool stickersPreloadReady = false;
|
||||
Ui::RpWidget *reactionsPreload = nullptr;
|
||||
bool reactionsPreloadReady = false;
|
||||
bool preloadScheduled = false;
|
||||
bool showFinished = false;
|
||||
Ui::Animations::Simple animation;
|
||||
@@ -1290,21 +865,6 @@ void PreviewBox(
|
||||
ready);
|
||||
state->stickersPreload->hide();
|
||||
}
|
||||
if (now != PremiumPreview::Reactions && !state->reactionsPreload) {
|
||||
const auto ready = [=] {
|
||||
if (state->reactionsPreload) {
|
||||
state->reactionsPreloadReady = true;
|
||||
} else {
|
||||
state->preload();
|
||||
}
|
||||
};
|
||||
state->reactionsPreload = GenerateDefaultPreview(
|
||||
outer,
|
||||
controller,
|
||||
PremiumPreview::Reactions,
|
||||
ready);
|
||||
state->reactionsPreload->hide();
|
||||
}
|
||||
};
|
||||
|
||||
switch (descriptor.section) {
|
||||
@@ -1313,13 +873,6 @@ void PreviewBox(
|
||||
? StickerPreview(outer, controller, media, state->preload)
|
||||
: StickersPreview(outer, controller, state->preload);
|
||||
break;
|
||||
case PremiumPreview::Reactions:
|
||||
state->content = ReactionsPreview(
|
||||
outer,
|
||||
controller,
|
||||
descriptor.disabled,
|
||||
state->preload);
|
||||
break;
|
||||
default:
|
||||
state->content = GenericPreview(
|
||||
outer,
|
||||
@@ -1379,13 +932,6 @@ void PreviewBox(
|
||||
if (base::take(state->stickersPreloadReady)) {
|
||||
state->preload();
|
||||
}
|
||||
} else if (now == PremiumPreview::Reactions
|
||||
&& state->reactionsPreload) {
|
||||
state->content = base::take(state->reactionsPreload);
|
||||
state->content->show();
|
||||
if (base::take(state->reactionsPreloadReady)) {
|
||||
state->preload();
|
||||
}
|
||||
} else {
|
||||
state->content = GenerateDefaultPreview(
|
||||
outer,
|
||||
@@ -1455,12 +1001,14 @@ void PreviewBox(
|
||||
};
|
||||
auto unlock = state->selected.value(
|
||||
) | rpl::map([=](PremiumPreview section) {
|
||||
return (section == PremiumPreview::Reactions)
|
||||
return (section == PremiumPreview::InfiniteReactions)
|
||||
? tr::lng_premium_unlock_reactions()
|
||||
: (section == PremiumPreview::Stickers)
|
||||
? tr::lng_premium_unlock_stickers()
|
||||
: (section == PremiumPreview::AnimatedEmoji)
|
||||
? tr::lng_premium_unlock_emoji()
|
||||
: (section == PremiumPreview::EmojiStatus)
|
||||
? tr::lng_premium_unlock_status()
|
||||
: tr::lng_premium_more_about();
|
||||
}) | rpl::flatten_latest();
|
||||
auto button = descriptor.fromSettings
|
||||
@@ -1623,11 +1171,9 @@ void ShowStickerPreviewBox(
|
||||
void ShowPremiumPreviewBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
PremiumPreview section,
|
||||
const base::flat_map<QString, ReactionDisableType> &disabled,
|
||||
Fn<void(not_null<Ui::BoxContent*>)> shown) {
|
||||
Show(controller, Descriptor{
|
||||
.section = section,
|
||||
.disabled = disabled,
|
||||
.shownCallback = std::move(shown),
|
||||
});
|
||||
}
|
||||
@@ -1670,14 +1216,14 @@ void DoubledLimitsPreviewBox(
|
||||
});
|
||||
}
|
||||
{
|
||||
const auto premium = limits.dialogsFolderPinnedPremium();
|
||||
const auto premium = limits.dialogsPinnedPremium();
|
||||
entries.push_back(Ui::Premium::ListEntry{
|
||||
tr::lng_premium_double_limits_subtitle_pins(),
|
||||
tr::lng_premium_double_limits_about_pins(
|
||||
lt_count,
|
||||
rpl::single(float64(premium)),
|
||||
Ui::Text::RichLangValue),
|
||||
limits.dialogsFolderPinnedDefault(),
|
||||
limits.dialogsPinnedDefault(),
|
||||
premium,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
class DocumentData;
|
||||
|
||||
namespace Data {
|
||||
struct ReactionId;
|
||||
} // namespace Data
|
||||
|
||||
namespace Ui {
|
||||
class BoxContent;
|
||||
class GenericBox;
|
||||
@@ -38,7 +42,8 @@ enum class PremiumPreview {
|
||||
FasterDownload,
|
||||
VoiceToText,
|
||||
NoAds,
|
||||
Reactions,
|
||||
EmojiStatus,
|
||||
InfiniteReactions,
|
||||
Stickers,
|
||||
AnimatedEmoji,
|
||||
AdvancedChatManagement,
|
||||
@@ -47,16 +52,10 @@ enum class PremiumPreview {
|
||||
|
||||
kCount,
|
||||
};
|
||||
enum class ReactionDisableType {
|
||||
None,
|
||||
Group,
|
||||
Channel,
|
||||
};
|
||||
|
||||
void ShowPremiumPreviewBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
PremiumPreview section,
|
||||
const base::flat_map<QString, ReactionDisableType> &disabled = {},
|
||||
Fn<void(not_null<Ui::BoxContent*>)> shown = nullptr);
|
||||
|
||||
void ShowPremiumPreviewToBuy(
|
||||
|
||||
@@ -17,9 +17,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/history.h"
|
||||
#include "history/history_message.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "history/view/history_view_react_button.h" // DefaultIconFactory
|
||||
#include "history/view/reactions/history_view_reactions_strip.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "lottie/lottie_icon.h"
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "main/main_session.h"
|
||||
#include "settings/settings_common.h"
|
||||
@@ -33,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/animated_icon.h"
|
||||
#include "window/section_widget.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_boxes.h"
|
||||
@@ -60,7 +60,8 @@ PeerId GenerateUser(not_null<History*> history, const QString &name) {
|
||||
MTP_int(0), // bot info version
|
||||
MTPVector<MTPRestrictionReason>(), // restrictions
|
||||
MTPstring(), // bot placeholder
|
||||
MTPstring())); // lang code
|
||||
MTPstring(), // lang code
|
||||
MTPEmojiStatus()));
|
||||
return peerId;
|
||||
}
|
||||
|
||||
@@ -193,7 +194,9 @@ void AddMessage(
|
||||
auto context = theme->preparePaintContext(
|
||||
state->style.get(),
|
||||
widget->rect(),
|
||||
widget->rect());
|
||||
widget->rect(),
|
||||
controller->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::Layer));
|
||||
context.outbg = view->hasOutLayout();
|
||||
|
||||
{
|
||||
@@ -221,14 +224,15 @@ void AddMessage(
|
||||
emojiValue = std::move(emojiValue),
|
||||
iconSize = st::settingsReactionMessageSize
|
||||
](const QString &emoji) {
|
||||
const auto id = Data::ReactionId{ emoji };
|
||||
const auto &reactions = controller->session().data().reactions();
|
||||
for (const auto &r : reactions.list(Data::Reactions::Type::Active)) {
|
||||
if (emoji != r.emoji) {
|
||||
if (r.id != id) {
|
||||
continue;
|
||||
}
|
||||
const auto index = state->icons.flag ? 1 : 0;
|
||||
state->icons.lifetimes[index] = rpl::lifetime();
|
||||
AddReactionLottieIcon(
|
||||
AddReactionAnimatedIcon(
|
||||
container,
|
||||
widget->geometryValue(
|
||||
) | rpl::map([=](const QRect &r) {
|
||||
@@ -249,9 +253,76 @@ void AddMessage(
|
||||
}, widget->lifetime());
|
||||
}
|
||||
|
||||
not_null<Ui::RpWidget*> AddReactionIconWrap(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
rpl::producer<QPoint> iconPositionValue,
|
||||
int iconSize,
|
||||
Fn<void(not_null<QWidget*>, QPainter&)> paintCallback,
|
||||
rpl::producer<> &&destroys,
|
||||
not_null<rpl::lifetime*> stateLifetime) {
|
||||
struct State {
|
||||
base::unique_qptr<Ui::RpWidget> widget;
|
||||
Ui::Animations::Simple finalAnimation;
|
||||
};
|
||||
|
||||
const auto state = stateLifetime->make_state<State>();
|
||||
state->widget = base::make_unique_q<Ui::RpWidget>(parent);
|
||||
|
||||
const auto widget = state->widget.get();
|
||||
widget->resize(iconSize, iconSize);
|
||||
widget->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
std::move(
|
||||
iconPositionValue
|
||||
) | rpl::start_with_next([=](const QPoint &point) {
|
||||
widget->moveToLeft(point.x(), point.y());
|
||||
}, widget->lifetime());
|
||||
|
||||
const auto update = crl::guard(widget, [=] { widget->update(); });
|
||||
|
||||
widget->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
auto p = QPainter(widget);
|
||||
|
||||
if (state->finalAnimation.animating()) {
|
||||
const auto progress = 1. - state->finalAnimation.value(0.);
|
||||
const auto size = widget->size();
|
||||
const auto scaledSize = size * progress;
|
||||
const auto scaledCenter = QPoint(
|
||||
(size.width() - scaledSize.width()) / 2.,
|
||||
(size.height() - scaledSize.height()) / 2.);
|
||||
p.setOpacity(progress);
|
||||
p.translate(scaledCenter);
|
||||
p.scale(progress, progress);
|
||||
}
|
||||
|
||||
paintCallback(widget, p);
|
||||
}, widget->lifetime());
|
||||
|
||||
std::move(
|
||||
destroys
|
||||
) | rpl::take(1) | rpl::start_with_next([=, from = 0., to = 1.] {
|
||||
state->finalAnimation.start(
|
||||
[=](float64 value) {
|
||||
update();
|
||||
if (value == to) {
|
||||
stateLifetime->destroy();
|
||||
}
|
||||
},
|
||||
from,
|
||||
to,
|
||||
st::defaultPopupMenu.showDuration);
|
||||
}, widget->lifetime());
|
||||
|
||||
widget->raise();
|
||||
widget->show();
|
||||
|
||||
return widget;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void AddReactionLottieIcon(
|
||||
void AddReactionAnimatedIcon(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
rpl::producer<QPoint> iconPositionValue,
|
||||
int iconSize,
|
||||
@@ -259,24 +330,17 @@ void AddReactionLottieIcon(
|
||||
rpl::producer<> &&selects,
|
||||
rpl::producer<> &&destroys,
|
||||
not_null<rpl::lifetime*> stateLifetime) {
|
||||
|
||||
struct State {
|
||||
struct Entry {
|
||||
std::shared_ptr<Data::DocumentMedia> media;
|
||||
std::shared_ptr<Lottie::Icon> icon;
|
||||
std::shared_ptr<Ui::AnimatedIcon> icon;
|
||||
};
|
||||
Entry appear;
|
||||
Entry select;
|
||||
bool appearAnimated = false;
|
||||
rpl::lifetime loadingLifetime;
|
||||
|
||||
base::unique_qptr<Ui::RpWidget> widget;
|
||||
|
||||
Ui::Animations::Simple finalAnimation;
|
||||
};
|
||||
|
||||
const auto state = stateLifetime->make_state<State>();
|
||||
state->widget = base::make_unique_q<Ui::RpWidget>(parent);
|
||||
|
||||
state->appear.media = reaction.appearAnimation->createMediaView();
|
||||
state->select.media = reaction.selectAnimation->createMediaView();
|
||||
@@ -302,35 +366,8 @@ void AddReactionLottieIcon(
|
||||
}
|
||||
}, state->loadingLifetime);
|
||||
|
||||
const auto widget = state->widget.get();
|
||||
widget->resize(iconSize, iconSize);
|
||||
widget->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
std::move(
|
||||
iconPositionValue
|
||||
) | rpl::start_with_next([=](const QPoint &point) {
|
||||
widget->moveToLeft(point.x(), point.y());
|
||||
}, widget->lifetime());
|
||||
|
||||
const auto update = crl::guard(widget, [=] { widget->update(); });
|
||||
|
||||
widget->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
Painter p(widget);
|
||||
|
||||
if (state->finalAnimation.animating()) {
|
||||
const auto progress = 1. - state->finalAnimation.value(0.);
|
||||
const auto size = widget->size();
|
||||
const auto scaledSize = size * progress;
|
||||
const auto scaledCenter = QPoint(
|
||||
(size.width() - scaledSize.width()) / 2.,
|
||||
(size.height() - scaledSize.height()) / 2.);
|
||||
p.setOpacity(progress);
|
||||
p.translate(scaledCenter);
|
||||
p.scale(progress, progress);
|
||||
}
|
||||
|
||||
const auto paintFrame = [&](not_null<Lottie::Icon*> animation) {
|
||||
const auto paintCallback = [=](not_null<QWidget*> widget, QPainter &p) {
|
||||
const auto paintFrame = [&](not_null<Ui::AnimatedIcon*> animation) {
|
||||
const auto frame = animation->frame();
|
||||
p.drawImage(
|
||||
QRect(
|
||||
@@ -344,41 +381,73 @@ void AddReactionLottieIcon(
|
||||
const auto appear = state->appear.icon.get();
|
||||
if (appear && !state->appearAnimated) {
|
||||
state->appearAnimated = true;
|
||||
appear->animate(update, 0, appear->framesCount() - 1);
|
||||
appear->animate(crl::guard(widget, [=] { widget->update(); }));
|
||||
}
|
||||
if (appear && appear->animating()) {
|
||||
paintFrame(appear);
|
||||
} else if (const auto select = state->select.icon.get()) {
|
||||
paintFrame(select);
|
||||
}
|
||||
}, widget->lifetime());
|
||||
|
||||
};
|
||||
const auto widget = AddReactionIconWrap(
|
||||
parent,
|
||||
std::move(iconPositionValue),
|
||||
iconSize,
|
||||
paintCallback,
|
||||
std::move(destroys),
|
||||
stateLifetime);
|
||||
|
||||
std::move(
|
||||
selects
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto select = state->select.icon.get();
|
||||
if (select && !select->animating()) {
|
||||
select->animate(update, 0, select->framesCount() - 1);
|
||||
select->animate(crl::guard(widget, [=] { widget->update(); }));
|
||||
}
|
||||
}, widget->lifetime());
|
||||
}
|
||||
|
||||
std::move(
|
||||
destroys
|
||||
) | rpl::take(1) | rpl::start_with_next([=, from = 0., to = 1.] {
|
||||
state->finalAnimation.start(
|
||||
[=](float64 value) {
|
||||
update();
|
||||
if (value == to) {
|
||||
stateLifetime->destroy();
|
||||
}
|
||||
},
|
||||
from,
|
||||
to,
|
||||
st::defaultPopupMenu.showDuration);
|
||||
}, widget->lifetime());
|
||||
void AddReactionCustomIcon(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
rpl::producer<QPoint> iconPositionValue,
|
||||
int iconSize,
|
||||
not_null<Window::SessionController*> controller,
|
||||
DocumentId customId,
|
||||
rpl::producer<> &&destroys,
|
||||
not_null<rpl::lifetime*> stateLifetime) {
|
||||
struct State {
|
||||
std::unique_ptr<Ui::Text::CustomEmoji> custom;
|
||||
Fn<void()> repaint;
|
||||
};
|
||||
const auto state = stateLifetime->make_state<State>();
|
||||
static constexpr auto tag = Data::CustomEmojiManager::SizeTag::Normal;
|
||||
state->custom = controller->session().data().customEmojiManager().create(
|
||||
customId,
|
||||
[=] { state->repaint(); },
|
||||
tag);
|
||||
|
||||
widget->raise();
|
||||
widget->show();
|
||||
const auto paintCallback = [=](not_null<QWidget*> widget, QPainter &p) {
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
const auto size = Data::FrameSizeFromTag(tag) / ratio;
|
||||
state->custom->paint(p, {
|
||||
.preview = st::windowBgRipple->c,
|
||||
.now = crl::now(),
|
||||
.position = QPoint(
|
||||
(widget->width() - size) / 2,
|
||||
(widget->height() - size) / 2),
|
||||
.paused = controller->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::Layer),
|
||||
});
|
||||
};
|
||||
const auto widget = AddReactionIconWrap(
|
||||
parent,
|
||||
std::move(iconPositionValue),
|
||||
iconSize,
|
||||
paintCallback,
|
||||
std::move(destroys),
|
||||
stateLifetime);
|
||||
state->repaint = crl::guard(widget, [=] { widget->update(); });
|
||||
}
|
||||
|
||||
void ReactionsSettingsBox(
|
||||
@@ -391,7 +460,9 @@ void ReactionsSettingsBox(
|
||||
|
||||
const auto &reactions = controller->session().data().reactions();
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
state->selectedEmoji = reactions.favorite();
|
||||
state->selectedEmoji = v::is<QString>(reactions.favoriteId().data)
|
||||
? v::get<QString>(reactions.favoriteId().data)
|
||||
: QString();
|
||||
|
||||
const auto pinnedToTop = box->setPinnedToTopContent(
|
||||
object_ptr<Ui::VerticalLayout>(box));
|
||||
@@ -433,7 +504,7 @@ void ReactionsSettingsBox(
|
||||
}
|
||||
|
||||
const auto iconSize = st::settingsReactionSize;
|
||||
AddReactionLottieIcon(
|
||||
AddReactionAnimatedIcon(
|
||||
button,
|
||||
button->sizeValue(
|
||||
) | rpl::map([=, left = button->st().iconLeft](const QSize &s) {
|
||||
@@ -450,17 +521,19 @@ void ReactionsSettingsBox(
|
||||
rpl::never<>(),
|
||||
&button->lifetime());
|
||||
|
||||
button->setClickedCallback([=, emoji = r.emoji] {
|
||||
button->setClickedCallback([=, id = r.id] {
|
||||
if (premium && !controller->session().premium()) {
|
||||
ShowPremiumPreviewBox(
|
||||
controller,
|
||||
PremiumPreview::Reactions);
|
||||
PremiumPreview::InfiniteReactions);
|
||||
return;
|
||||
}
|
||||
checkButton(button);
|
||||
state->selectedEmoji = emoji;
|
||||
state->selectedEmoji = v::is<QString>(id.data)
|
||||
? v::get<QString>(id.data)
|
||||
: QString();
|
||||
});
|
||||
if (r.emoji == state->selectedEmoji.current()) {
|
||||
if (r.id == Data::ReactionId{ state->selectedEmoji.current() }) {
|
||||
firstCheckedButton = button;
|
||||
}
|
||||
}
|
||||
@@ -478,9 +551,9 @@ void ReactionsSettingsBox(
|
||||
box->setWidth(st::boxWideWidth);
|
||||
box->addButton(tr::lng_settings_save(), [=] {
|
||||
const auto &data = controller->session().data();
|
||||
const auto selectedEmoji = state->selectedEmoji.current();
|
||||
if (data.reactions().favorite() != selectedEmoji) {
|
||||
data.reactions().setFavorite(selectedEmoji);
|
||||
const auto selected = state->selectedEmoji.current();
|
||||
if (data.reactions().favoriteId() != Data::ReactionId{ selected }) {
|
||||
data.reactions().setFavorite(Data::ReactionId{ selected });
|
||||
}
|
||||
box->closeBox();
|
||||
});
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Data {
|
||||
struct Reaction;
|
||||
} // namespace Data
|
||||
|
||||
void AddReactionLottieIcon(
|
||||
void AddReactionAnimatedIcon(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
rpl::producer<QPoint> iconPositionValue,
|
||||
int iconSize,
|
||||
@@ -28,6 +28,14 @@ void AddReactionLottieIcon(
|
||||
rpl::producer<> &&selects,
|
||||
rpl::producer<> &&destroys,
|
||||
not_null<rpl::lifetime*> stateLifetime);
|
||||
void AddReactionCustomIcon(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
rpl::producer<QPoint> iconPositionValue,
|
||||
int iconSize,
|
||||
not_null<Window::SessionController*> controller,
|
||||
DocumentId customId,
|
||||
rpl::producer<> &&destroys,
|
||||
not_null<rpl::lifetime*> stateLifetime);
|
||||
|
||||
void ReactionsSettingsBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
|
||||
@@ -24,7 +24,7 @@ using Type = SelfDestructionBox::Type;
|
||||
[[nodiscard]] std::vector<int> Values(Type type) {
|
||||
switch (type) {
|
||||
case Type::Account: return { 30, 90, 180, 365 };
|
||||
case Type::Sessions: return { 7, 30, 90, 180 };
|
||||
case Type::Sessions: return { 7, 30, 90, 180, 365 };
|
||||
}
|
||||
Unexpected("SelfDestructionBox::Type in Values.");
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/mime_type.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "boxes/premium_limits_box.h"
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/effects/scroll_content_shadow.h"
|
||||
@@ -48,6 +49,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_premium_limits.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "media/clip/media_clip_reader.h"
|
||||
#include "api/api_common.h"
|
||||
@@ -260,12 +262,8 @@ SendFilesBox::SendFilesBox(
|
||||
, _sendLimit(peer->slowmodeApplied() ? SendLimit::One : SendLimit::Many)
|
||||
, _sendMenuType(sendMenuType)
|
||||
, _allowEmojiWithoutPremium(Data::AllowEmojiWithoutPremium(peer))
|
||||
, _caption(
|
||||
this,
|
||||
st::confirmCaptionArea,
|
||||
Ui::InputField::Mode::MultiLine,
|
||||
nullptr,
|
||||
caption)
|
||||
, _caption(this, st::confirmCaptionArea, Ui::InputField::Mode::MultiLine)
|
||||
, _prefilledCaptionText(std::move(caption))
|
||||
, _scroll(this, st::boxScroll)
|
||||
, _inner(
|
||||
_scroll->setOwnedWidget(
|
||||
@@ -688,6 +686,15 @@ void SendFilesBox::setupCaption() {
|
||||
&_controller->session(),
|
||||
{ .suggestCustomEmoji = true, .allowCustomWithoutPremium = allow });
|
||||
|
||||
if (!_prefilledCaptionText.text.isEmpty()) {
|
||||
_caption->setTextWithTags(
|
||||
_prefilledCaptionText,
|
||||
Ui::InputField::HistoryAction::Clear);
|
||||
|
||||
auto cursor = _caption->textCursor();
|
||||
cursor.movePosition(QTextCursor::End);
|
||||
_caption->setTextCursor(cursor);
|
||||
}
|
||||
_caption->setSubmitSettings(
|
||||
Core::App().settings().sendSubmitWay());
|
||||
_caption->setMaxLength(kMaxMessageLength);
|
||||
@@ -739,14 +746,23 @@ void SendFilesBox::setupEmojiPanel() {
|
||||
_emojiPanel->selector()->setAllowEmojiWithoutPremium(
|
||||
_allowEmojiWithoutPremium);
|
||||
_emojiPanel->selector()->emojiChosen(
|
||||
) | rpl::start_with_next([=](EmojiPtr emoji) {
|
||||
Ui::InsertEmojiAtCursor(_caption->textCursor(), emoji);
|
||||
) | rpl::start_with_next([=](ChatHelpers::EmojiChosen data) {
|
||||
Ui::InsertEmojiAtCursor(_caption->textCursor(), data.emoji);
|
||||
}, lifetime());
|
||||
_emojiPanel->selector()->customEmojiChosen(
|
||||
) | rpl::start_with_next([=](Selector::FileChosen data) {
|
||||
Data::InsertCustomEmoji(_caption.data(), data.document);
|
||||
) | rpl::start_with_next([=](ChatHelpers::FileChosen data) {
|
||||
const auto info = data.document->sticker();
|
||||
if (info
|
||||
&& info->setType == Data::StickersType::Emoji
|
||||
&& !_controller->session().premium()
|
||||
&& !_allowEmojiWithoutPremium) {
|
||||
ShowPremiumPreviewBox(
|
||||
_controller,
|
||||
PremiumPreview::AnimatedEmoji);
|
||||
} else {
|
||||
Data::InsertCustomEmoji(_caption.data(), data.document);
|
||||
}
|
||||
}, lifetime());
|
||||
_emojiPanel->selector()->showPromoForPremiumEmoji();
|
||||
|
||||
const auto filterCallback = [=](not_null<QEvent*> event) {
|
||||
emojiFilterForGeometry(event);
|
||||
@@ -788,13 +804,13 @@ void SendFilesBox::captionResized() {
|
||||
}
|
||||
|
||||
bool SendFilesBox::canAddFiles(not_null<const QMimeData*> data) const {
|
||||
return (data->hasUrls() && CanAddUrls(data->urls())) || data->hasImage();
|
||||
return (data->hasUrls() && CanAddUrls(base::GetMimeUrls(data))) || data->hasImage();
|
||||
}
|
||||
|
||||
bool SendFilesBox::addFiles(not_null<const QMimeData*> data) {
|
||||
const auto premium = _controller->session().premium();
|
||||
auto list = [&] {
|
||||
const auto urls = data->hasUrls() ? data->urls() : QList<QUrl>();
|
||||
const auto urls = data->hasUrls() ? base::GetMimeUrls(data) : QList<QUrl>();
|
||||
auto result = CanAddUrls(urls)
|
||||
? Storage::PrepareMediaList(
|
||||
urls,
|
||||
@@ -972,10 +988,10 @@ void SendFilesBox::updateControlsGeometry() {
|
||||
}
|
||||
|
||||
void SendFilesBox::setInnerFocus() {
|
||||
if (!_caption || _caption->isHidden()) {
|
||||
setFocus();
|
||||
} else {
|
||||
if (_caption && !_caption->isHidden()) {
|
||||
_caption->setFocusFast();
|
||||
} else {
|
||||
BoxContent::setInnerFocus();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -180,6 +180,7 @@ private:
|
||||
bool _confirmed = false;
|
||||
|
||||
object_ptr<Ui::InputField> _caption = { nullptr };
|
||||
TextWithTags _prefilledCaptionText;
|
||||
object_ptr<Ui::EmojiButton> _emojiToggle = { nullptr };
|
||||
base::unique_qptr<ChatHelpers::TabbedPanel> _emojiPanel;
|
||||
base::unique_qptr<QObject> _emojiFilter;
|
||||
|
||||
@@ -154,6 +154,25 @@ void ValidatePremiumStarFg(QImage &image) {
|
||||
star.render(&p, outer);
|
||||
}
|
||||
|
||||
[[nodiscard]] TextForMimeData PrepareTextFromEmoji(
|
||||
not_null<DocumentData*> document) {
|
||||
const auto info = document->sticker();
|
||||
const auto text = info ? info->alt : QString();
|
||||
return {
|
||||
.expanded = text,
|
||||
.rich = {
|
||||
text,
|
||||
{
|
||||
EntityInText(
|
||||
EntityType::CustomEmoji,
|
||||
0,
|
||||
text.size(),
|
||||
Data::SerializeCustomEmojiId(document))
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
StickerPremiumMark::StickerPremiumMark(not_null<Main::Session*> session) {
|
||||
@@ -264,6 +283,9 @@ private:
|
||||
|
||||
void visibleTopBottomUpdated(int visibleTop, int visibleBottom) override;
|
||||
|
||||
[[nodiscard]] Ui::MessageSendingAnimationFrom messageSentAnimationInfo(
|
||||
int index,
|
||||
not_null<DocumentData*> document) const;
|
||||
[[nodiscard]] QSize boundingBoxSize() const;
|
||||
|
||||
void paintSticker(
|
||||
@@ -291,7 +313,10 @@ private:
|
||||
void gotSet(const MTPmessages_StickerSet &set);
|
||||
void installDone(const MTPmessages_StickerSetInstallResult &result);
|
||||
|
||||
void send(not_null<DocumentData*> sticker, Api::SendOptions options);
|
||||
void chosen(
|
||||
int index,
|
||||
not_null<DocumentData*> sticker,
|
||||
Api::SendOptions options);
|
||||
|
||||
not_null<Lottie::MultiPlayer*> getLottiePlayer();
|
||||
|
||||
@@ -305,6 +330,7 @@ private:
|
||||
std::vector<Element> _elements;
|
||||
std::unique_ptr<Lottie::MultiPlayer> _lottiePlayer;
|
||||
|
||||
mutable Ui::Text::CustomEmojiColored _colored;
|
||||
base::flat_map<
|
||||
not_null<DocumentData*>,
|
||||
std::unique_ptr<Ui::Text::CustomEmoji>> _customEmoji;
|
||||
@@ -919,74 +945,113 @@ void StickerSetBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
|
||||
}
|
||||
_previewTimer.cancel();
|
||||
const auto index = stickerFromGlobalPos(e->globalPos());
|
||||
if (index < 0
|
||||
|| index >= _pack.size()
|
||||
|| setType() != Data::StickersType::Stickers) {
|
||||
if (index < 0 || index >= _pack.size()) {
|
||||
return;
|
||||
}
|
||||
send(_pack[index], {});
|
||||
chosen(index, _pack[index], {});
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::send(
|
||||
void StickerSetBox::Inner::chosen(
|
||||
int index,
|
||||
not_null<DocumentData*> sticker,
|
||||
Api::SendOptions options) {
|
||||
const auto controller = _controller;
|
||||
const auto animation = options.scheduled
|
||||
? Ui::MessageSendingAnimationFrom()
|
||||
: messageSentAnimationInfo(index, sticker);
|
||||
Ui::PostponeCall(controller, [=] {
|
||||
if (controller->content()->sendExistingDocument(sticker, options)) {
|
||||
controller->window().hideSettingsAndLayer();
|
||||
}
|
||||
controller->stickerOrEmojiChosen({
|
||||
.document = sticker,
|
||||
.options = options,
|
||||
.messageSendingFrom = animation,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
auto StickerSetBox::Inner::messageSentAnimationInfo(
|
||||
int index,
|
||||
not_null<DocumentData*> document) const
|
||||
-> Ui::MessageSendingAnimationFrom {
|
||||
if (index < 0 || index >= _pack.size() || _pack[index] != document) {
|
||||
return {};
|
||||
}
|
||||
const auto row = index / _perRow;
|
||||
const auto column = index % _perRow;
|
||||
const auto left = _padding.left() + column * _singleSize.width();
|
||||
const auto top = _padding.top() + row * _singleSize.height();
|
||||
const auto rect = QRect(QPoint(left, top), _singleSize);
|
||||
const auto size = ChatHelpers::ComputeStickerSize(
|
||||
document,
|
||||
boundingBoxSize());
|
||||
const auto innerPos = QPoint(
|
||||
(rect.width() - size.width()) / 2,
|
||||
(rect.height() - size.height()) / 2);
|
||||
return {
|
||||
.type = Ui::MessageSendingAnimationFrom::Type::Sticker,
|
||||
.localId = _controller->session().data().nextLocalMessageId(),
|
||||
.globalStartGeometry = mapToGlobal(
|
||||
QRect(rect.topLeft() + innerPos, size)),
|
||||
};
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) {
|
||||
const auto index = stickerFromGlobalPos(e->globalPos());
|
||||
if (index < 0
|
||||
|| index >= _pack.size()
|
||||
|| setType() != Data::StickersType::Stickers) {
|
||||
return;
|
||||
}
|
||||
const auto type = _controller->content()->sendMenuType();
|
||||
if (type == SendMenu::Type::Disabled) {
|
||||
|| setType() == Data::StickersType::Masks) {
|
||||
return;
|
||||
}
|
||||
_previewTimer.cancel();
|
||||
_menu = base::make_unique_q<Ui::PopupMenu>(
|
||||
this,
|
||||
st::popupMenuWithIcons);
|
||||
const auto type = _controller->content()->sendMenuType();
|
||||
if (setType() == Data::StickersType::Emoji) {
|
||||
if (const auto t = PrepareTextFromEmoji(_pack[index]); !t.empty()) {
|
||||
_menu->addAction(tr::lng_mediaview_copy(tr::now), [=] {
|
||||
if (auto data = TextUtilities::MimeDataFromText(t)) {
|
||||
QGuiApplication::clipboard()->setMimeData(data.release());
|
||||
}
|
||||
}, &st::menuIconCopy);
|
||||
}
|
||||
} else if (type != SendMenu::Type::Disabled) {
|
||||
const auto document = _pack[index];
|
||||
const auto sendSelected = [=](Api::SendOptions options) {
|
||||
chosen(index, document, options);
|
||||
};
|
||||
SendMenu::FillSendMenu(
|
||||
_menu.get(),
|
||||
type,
|
||||
SendMenu::DefaultSilentCallback(sendSelected),
|
||||
SendMenu::DefaultScheduleCallback(this, type, sendSelected));
|
||||
|
||||
const auto document = _pack[index];
|
||||
const auto sendSelected = [=](Api::SendOptions options) {
|
||||
send(document, options);
|
||||
};
|
||||
SendMenu::FillSendMenu(
|
||||
_menu.get(),
|
||||
type,
|
||||
SendMenu::DefaultSilentCallback(sendSelected),
|
||||
SendMenu::DefaultScheduleCallback(this, type, sendSelected));
|
||||
|
||||
const auto controller = _controller;
|
||||
const auto toggleFavedSticker = [=] {
|
||||
Api::ToggleFavedSticker(
|
||||
controller,
|
||||
document,
|
||||
Data::FileOriginStickerSet(Data::Stickers::FavedSetId, 0));
|
||||
};
|
||||
const auto isFaved = document->owner().stickers().isFaved(document);
|
||||
_menu->addAction(
|
||||
(isFaved
|
||||
? tr::lng_faved_stickers_remove
|
||||
: tr::lng_faved_stickers_add)(tr::now),
|
||||
toggleFavedSticker,
|
||||
(isFaved
|
||||
? &st::menuIconUnfave
|
||||
: &st::menuIconFave));
|
||||
|
||||
_menu->popup(QCursor::pos());
|
||||
const auto controller = _controller;
|
||||
const auto toggleFavedSticker = [=] {
|
||||
Api::ToggleFavedSticker(
|
||||
controller,
|
||||
document,
|
||||
Data::FileOriginStickerSet(Data::Stickers::FavedSetId, 0));
|
||||
};
|
||||
const auto isFaved = document->owner().stickers().isFaved(document);
|
||||
_menu->addAction(
|
||||
(isFaved
|
||||
? tr::lng_faved_stickers_remove
|
||||
: tr::lng_faved_stickers_add)(tr::now),
|
||||
toggleFavedSticker,
|
||||
(isFaved
|
||||
? &st::menuIconUnfave
|
||||
: &st::menuIconFave));
|
||||
}
|
||||
if (_menu->empty()) {
|
||||
_menu = nullptr;
|
||||
} else {
|
||||
_menu->popup(QCursor::pos());
|
||||
}
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::updateSelected() {
|
||||
auto selected = stickerFromGlobalPos(QCursor::pos());
|
||||
setSelected(setType() != Data::StickersType::Stickers ? -1 : selected);
|
||||
setSelected(setType() == Data::StickersType::Masks ? -1 : selected);
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::setSelected(int selected) {
|
||||
@@ -1091,9 +1156,10 @@ uint64 StickerSetBox::Inner::setId() const {
|
||||
|
||||
QSize StickerSetBox::Inner::boundingBoxSize() const {
|
||||
if (isEmojiSet()) {
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto large = Ui::Emoji::GetSizeLarge() / factor;
|
||||
return QSize(large, large);
|
||||
using namespace Data;
|
||||
const auto size = FrameSizeFromTag(CustomEmojiSizeTag::Large)
|
||||
/ style::DevicePixelRatio();
|
||||
return { size, size };
|
||||
}
|
||||
return QSize(
|
||||
_singleSize.width() - st::roundRadiusSmall * 2,
|
||||
@@ -1268,13 +1334,14 @@ void StickerSetBox::Inner::paintSticker(
|
||||
(_singleSize.height() - size.height()) / 2);
|
||||
auto lottieFrame = QImage();
|
||||
if (element.emoji) {
|
||||
element.emoji->paint(
|
||||
p,
|
||||
ppos.x(),
|
||||
ppos.y(),
|
||||
now,
|
||||
st::windowBgOver->c,
|
||||
paused);
|
||||
_colored.color = st::profileVerifiedCheckBg->c;
|
||||
element.emoji->paint(p, {
|
||||
.preview = st::windowBgOver->c,
|
||||
.colored = &_colored,
|
||||
.now = now,
|
||||
.position = ppos,
|
||||
.paused = paused,
|
||||
});
|
||||
} else if (element.lottie && element.lottie->ready()) {
|
||||
lottieFrame = element.lottie->frame();
|
||||
p.drawImage(
|
||||
|
||||
@@ -416,7 +416,7 @@ StickersBox::StickersBox(
|
||||
StickersBox::StickersBox(
|
||||
QWidget*,
|
||||
not_null<Window::SessionController*> controller,
|
||||
const MTPVector<MTPStickerSetCovered> &attachedSets)
|
||||
const QVector<MTPStickerSetCovered> &attachedSets)
|
||||
: _controller(controller)
|
||||
, _api(&controller->session().mtp())
|
||||
, _section(Section::Attached)
|
||||
@@ -460,8 +460,8 @@ void StickersBox::showAttachedStickers() {
|
||||
}
|
||||
}
|
||||
};
|
||||
for (const auto &set : _attachedSets.v) {
|
||||
add(stickers->feedSetCovered(set));
|
||||
for (const auto &set : _attachedSets) {
|
||||
add(stickers->feedSet(set));
|
||||
}
|
||||
for (const auto &setId : _emojiSets) {
|
||||
const auto i = stickers->sets().find(setId.id);
|
||||
@@ -501,7 +501,7 @@ void StickersBox::getArchivedDone(
|
||||
auto addedSet = false;
|
||||
auto changedSets = false;
|
||||
for (const auto &data : stickers.vsets().v) {
|
||||
const auto set = session().data().stickers().feedSetCovered(data);
|
||||
const auto set = session().data().stickers().feedSet(data);
|
||||
const auto index = archived.indexOf(set->id);
|
||||
if (archived.isEmpty() || index != archived.size() - 1) {
|
||||
changedSets = true;
|
||||
@@ -1030,6 +1030,8 @@ void StickersBox::saveChanges() {
|
||||
void StickersBox::setInnerFocus() {
|
||||
if (_megagroupSet) {
|
||||
_installed.widget()->setInnerFocus();
|
||||
} else {
|
||||
BoxContent::setInnerFocus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1075,7 +1077,8 @@ StickersBox::Inner::Row::~Row() {
|
||||
const auto featured = !!(set->flags & SetFlag::Featured);
|
||||
const auto special = !!(set->flags & SetFlag::Special);
|
||||
const auto archived = !!(set->flags & SetFlag::Archived);
|
||||
if (!installed && !featured && !special && !archived) {
|
||||
const auto emoji = !!(set->flags & SetFlag::Emoji);
|
||||
if (!installed && !featured && !special && !archived && !emoji) {
|
||||
auto &sets = set->owner().stickers().setsRef();
|
||||
if (const auto i = sets.find(set->id); i != end(sets)) {
|
||||
sets.erase(i);
|
||||
|
||||
@@ -70,7 +70,7 @@ public:
|
||||
StickersBox(
|
||||
QWidget*,
|
||||
not_null<Window::SessionController*> controller,
|
||||
const MTPVector<MTPStickerSetCovered> &attachedSets);
|
||||
const QVector<MTPStickerSetCovered> &attachedSets);
|
||||
StickersBox(
|
||||
QWidget*,
|
||||
not_null<Window::SessionController*> controller,
|
||||
@@ -164,7 +164,7 @@ private:
|
||||
Tab *_tab = nullptr;
|
||||
|
||||
const Data::StickersType _attachedType = {};
|
||||
const MTPVector<MTPStickerSetCovered> _attachedSets;
|
||||
const QVector<MTPStickerSetCovered> _attachedSets;
|
||||
const std::vector<StickerSetIdentifier> _emojiSets;
|
||||
|
||||
ChannelData *_megagroupSet = nullptr;
|
||||
|
||||
@@ -512,9 +512,10 @@ groupCallMenu: Menu(defaultMenu) {
|
||||
itemFgShortcutOver: groupCallMemberNotJoinedStatus;
|
||||
itemFgShortcutDisabled: groupCallMemberNotJoinedStatus;
|
||||
|
||||
separatorFg: groupCallMenuBgOver;
|
||||
separatorPadding: margins(0px, 4px, 0px, 4px);
|
||||
|
||||
separator: MenuSeparator(defaultMenuSeparator) {
|
||||
padding: margins(0px, 4px, 0px, 4px);
|
||||
fg: groupCallMenuBgOver;
|
||||
}
|
||||
arrow: icon {{ "dropdown_submenu_arrow", groupCallMemberNotJoinedStatus }};
|
||||
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
|
||||
@@ -108,7 +108,16 @@ public:
|
||||
Fn<void()> updateCallback) override;
|
||||
void rightActionStopLastRipple() override;
|
||||
|
||||
int nameIconWidth() const override {
|
||||
int paintNameIconGetWidth(
|
||||
Painter &p,
|
||||
Fn<void()> repaint,
|
||||
crl::time now,
|
||||
int nameLeft,
|
||||
int nameTop,
|
||||
int nameWidth,
|
||||
int availableWidth,
|
||||
int outerWidth,
|
||||
bool selected) override {
|
||||
return 0;
|
||||
}
|
||||
QSize rightActionSize() const override {
|
||||
|
||||
@@ -45,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "platform/platform_specific.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/power_save_blocker.h"
|
||||
#include "media/streaming/media_streaming_utility.h"
|
||||
#include "window/main_window.h"
|
||||
#include "webrtc/webrtc_video_track.h"
|
||||
#include "webrtc/webrtc_media_devices.h"
|
||||
@@ -335,16 +336,10 @@ void Panel::refreshIncomingGeometry() {
|
||||
return;
|
||||
}
|
||||
const auto to = widget()->size();
|
||||
const auto small = _incomingFrameSize.scaled(to, Qt::KeepAspectRatio);
|
||||
const auto big = _incomingFrameSize.scaled(
|
||||
const auto use = ::Media::Streaming::DecideFrameResize(
|
||||
to,
|
||||
Qt::KeepAspectRatioByExpanding);
|
||||
|
||||
// If we cut out no more than 0.25 of the original, let's use expanding.
|
||||
const auto use = ((big.width() * 3 <= to.width() * 4)
|
||||
&& (big.height() * 3 <= to.height() * 4))
|
||||
? big
|
||||
: small;
|
||||
_incomingFrameSize
|
||||
).result;
|
||||
const auto pos = QPoint(
|
||||
(to.width() - use.width()) / 2,
|
||||
(to.height() - use.height()) / 2);
|
||||
|
||||
@@ -247,12 +247,11 @@ void ChooseJoinAsBox(
|
||||
: tr::lng_group_call_schedule)(makeLink),
|
||||
Ui::Text::WithEntities),
|
||||
labelSt));
|
||||
label->setClickHandlerFilter([=](const auto&...) {
|
||||
label->overrideLinkClickHandler([=] {
|
||||
auto withJoinAs = info;
|
||||
withJoinAs.joinAs = controller->selected();
|
||||
box->getDelegate()->show(
|
||||
Box(ScheduleGroupCallBox, withJoinAs, done));
|
||||
return false;
|
||||
});
|
||||
}
|
||||
auto next = (context == Context::Switch)
|
||||
|
||||
@@ -50,7 +50,7 @@ bool CoverItem::isEnabled() const {
|
||||
}
|
||||
|
||||
int CoverItem::contentHeight() const {
|
||||
return _st.size + st::groupCallMenu.separatorPadding.bottom();
|
||||
return _st.size + st::groupCallMenu.separator.padding.bottom();
|
||||
}
|
||||
|
||||
AboutItem::AboutItem(
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "calls/group/calls_group_viewport_tile.h"
|
||||
#include "webrtc/webrtc_video_track.h"
|
||||
#include "media/view/media_view_pip.h"
|
||||
#include "media/streaming/media_streaming_utility.h"
|
||||
#include "calls/group/calls_group_members_row.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/gl/gl_shader.h"
|
||||
@@ -31,7 +32,6 @@ constexpr auto kNoiseTextureSize = 256;
|
||||
constexpr auto kBlurTextureSizeFactor = 4.;
|
||||
constexpr auto kBlurOpacity = 0.65;
|
||||
constexpr auto kDitherNoiseAmount = 0.002;
|
||||
constexpr auto kMinCameraVisiblePart = 0.75;
|
||||
|
||||
constexpr auto kQuads = 9;
|
||||
constexpr auto kQuadVertices = kQuads * 4;
|
||||
@@ -224,13 +224,8 @@ vec4 background() {
|
||||
}
|
||||
|
||||
[[nodiscard]] bool UseExpandForCamera(QSize original, QSize viewport) {
|
||||
const auto big = original.scaled(
|
||||
viewport,
|
||||
Qt::KeepAspectRatioByExpanding);
|
||||
|
||||
// If we cut out no more than 0.25 of the original, let's use expanding.
|
||||
return (big.width() * kMinCameraVisiblePart <= viewport.width())
|
||||
&& (big.height() * kMinCameraVisiblePart <= viewport.height());
|
||||
using namespace ::Media::Streaming;
|
||||
return DecideFrameResize(viewport, original).expanding;
|
||||
}
|
||||
|
||||
[[nodiscard]] QSize NonEmpty(QSize size) {
|
||||
|
||||
@@ -10,6 +10,26 @@ using "ui/basic.style";
|
||||
using "boxes/boxes.style";
|
||||
using "ui/widgets/widgets.style";
|
||||
|
||||
EmojiPan {
|
||||
margin: margins;
|
||||
padding: margins;
|
||||
desiredSize: pixels;
|
||||
verticalSizeSub: pixels;
|
||||
header: pixels;
|
||||
headerLeft: pixels;
|
||||
headerLockLeft: pixels;
|
||||
headerLockedLeft: pixels;
|
||||
headerTop: pixels;
|
||||
footer: pixels;
|
||||
iconSkip: pixels;
|
||||
iconWidth: pixels;
|
||||
iconArea: pixels;
|
||||
bg: color;
|
||||
overBg: color;
|
||||
fadeLeft: icon;
|
||||
fadeRight: icon;
|
||||
}
|
||||
|
||||
switchPmButton: RoundButton(defaultBoxButton) {
|
||||
width: 320px;
|
||||
height: 34px;
|
||||
@@ -147,23 +167,7 @@ emojiObjectsActive: icon {{ "emoji/emoji_objects", emojiSubIconFgActive }};
|
||||
emojiSymbols: icon {{ "emoji/emoji_symbols", emojiIconFg }};
|
||||
emojiSymbolsActive: icon {{ "emoji/emoji_symbols", emojiSubIconFgActive }};
|
||||
|
||||
emojiFooterHeight: 46px;
|
||||
emojiCategorySkip: 4px;
|
||||
emojiCategory: IconButton {
|
||||
width: 42px;
|
||||
height: emojiFooterHeight;
|
||||
|
||||
iconPosition: point(-1px, 6px);
|
||||
}
|
||||
emojiCategoryRecent: IconButton(emojiCategory) { icon: emojiRecent; }
|
||||
emojiCategoryPeople: IconButton(emojiCategory) { icon: emojiPeople; }
|
||||
emojiCategoryNature: IconButton(emojiCategory) { icon: emojiNature; }
|
||||
emojiCategoryFood: IconButton(emojiCategory) { icon: emojiFood; }
|
||||
emojiCategoryActivity: IconButton(emojiCategory) { icon: emojiActivity; }
|
||||
emojiCategoryTravel: IconButton(emojiCategory) { icon: emojiTravel; }
|
||||
emojiCategoryObjects: IconButton(emojiCategory) { icon: emojiObjects; }
|
||||
emojiCategorySymbols: IconButton(emojiCategory) { icon: emojiSymbols; }
|
||||
|
||||
emojiCategoryIconTop: 6px;
|
||||
emojiPanAnimation: PanelAnimation(defaultPanelAnimation) {
|
||||
fadeBg: emojiPanBg;
|
||||
}
|
||||
@@ -176,21 +180,35 @@ emojiPanShowDuration: 200;
|
||||
emojiPanDuration: 200;
|
||||
emojiPanHover: windowBgOver;
|
||||
emojiPanSlideDuration: 200;
|
||||
emojiPanDesiredSize: 39px;
|
||||
emojiPanArea: size(34px, 32px);
|
||||
emojiPanLeft: 13px;
|
||||
emojiPanRight: 17px;
|
||||
emojiPanRadius: 8px;
|
||||
|
||||
defaultEmojiPan: EmojiPan {
|
||||
margin: margins(roundRadiusSmall, 0px, 14px, 0px);
|
||||
padding: margins(13px, 12px, 17px, 12px);
|
||||
desiredSize: 39px;
|
||||
verticalSizeSub: 2px;
|
||||
header: 40px;
|
||||
headerLeft: 23px;
|
||||
headerLockLeft: 17px;
|
||||
headerLockedLeft: 36px;
|
||||
headerTop: 12px;
|
||||
footer: 46px;
|
||||
iconSkip: 4px;
|
||||
iconWidth: 35px;
|
||||
iconArea: 32px;
|
||||
bg: emojiPanBg;
|
||||
overBg: emojiPanHover;
|
||||
fadeLeft: icon {{ "fade_horizontal-flip_horizontal", emojiPanCategories }};
|
||||
fadeRight: icon {{ "fade_horizontal", emojiPanCategories }};
|
||||
}
|
||||
|
||||
inlineResultsMinHeight: 278px;
|
||||
inlineResultsMaxHeight: 640px;
|
||||
|
||||
emojiPanHeader: 40px;
|
||||
emojiPanHeaderFont: semiboldFont;
|
||||
emojiPanHeaderLeft: 23px;
|
||||
emojiPanHeaderLockLeft: 17px;
|
||||
emojiPanHeaderLockedLeft: 36px;
|
||||
emojiPanHeaderTop: 12px;
|
||||
emojiPanRemoveSkip: 10px;
|
||||
|
||||
emojiColorsPadding: 5px;
|
||||
@@ -233,7 +251,6 @@ stickerPanDeleteOpacityFg: 0.8;
|
||||
stickerPanDeleteOpacityFgOver: 1.;
|
||||
stickerPanRemoveSet: hashtagClose;
|
||||
stickerIconWidth: 42px;
|
||||
stickerIconHeight: emojiFooterHeight;
|
||||
stickerIconPadding: 5px;
|
||||
stickerIconOpacity: 0.7;
|
||||
stickerIconSel: 2px;
|
||||
@@ -242,9 +259,6 @@ stickerIconMove: 400;
|
||||
stickerPreviewDuration: 150;
|
||||
stickerPreviewMin: 0.1;
|
||||
|
||||
emojiIconWidth: 35px;
|
||||
emojiIconArea: 32px;
|
||||
|
||||
stickerGroupCategorySize: 28px;
|
||||
stickerGroupCategoryAbout: defaultTextStyle;
|
||||
stickerGroupCategoryAddMargin: margins(0px, 10px, 0px, 5px);
|
||||
@@ -311,3 +325,43 @@ inlineRadialSize: 44px;
|
||||
inlineFileSize: 44px;
|
||||
|
||||
stickersPremiumLock: icon{{ "emoji/premium_lock", premiumButtonFg }};
|
||||
|
||||
reactStripExtend: margins(21px, 49px, 39px, 0px);
|
||||
reactStripHeight: 40px;
|
||||
reactStripSize: 32px;
|
||||
reactStripMinWidth: 60px;
|
||||
reactStripImage: 26px;
|
||||
reactStripSkip: 7px;
|
||||
reactStripBubble: icon{
|
||||
{ "chat/reactions_bubble_shadow", windowShadowFg },
|
||||
{ "chat/reactions_bubble", windowBg },
|
||||
};
|
||||
reactStripBubbleRight: 20px;
|
||||
reactPanelEmojiPan: EmojiPan(defaultEmojiPan) {
|
||||
margin: margins(reactStripSkip, 0px, reactStripSkip, 0px);
|
||||
padding: margins(reactStripSkip, 0px, reactStripSkip, reactStripSkip);
|
||||
desiredSize: reactStripSize;
|
||||
verticalSizeSub: 0px;
|
||||
headerLeft: 13px;
|
||||
headerLockLeft: 7px;
|
||||
headerLockedLeft: 26px;
|
||||
footer: 42px;
|
||||
iconSkip: 6px;
|
||||
iconWidth: 33px;
|
||||
iconArea: 30px;
|
||||
overBg: transparent;
|
||||
fadeLeft: icon {{ "fade_horizontal-flip_horizontal", windowBg }};
|
||||
fadeRight: icon {{ "fade_horizontal", windowBg }};
|
||||
}
|
||||
reactPanelScroll: ScrollArea(defaultSolidScroll) {
|
||||
deltat: 3px;
|
||||
deltab: 3px;
|
||||
round: 1px;
|
||||
width: 7px;
|
||||
deltax: 2px;
|
||||
hiding: 0;
|
||||
}
|
||||
|
||||
emojiSuggestionsFadeLeft: icon {{ "fade_horizontal-flip_horizontal", boxBg }};
|
||||
emojiSuggestionsFadeRight: icon {{ "fade_horizontal", boxBg }};
|
||||
|
||||
|
||||
@@ -12,6 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/round_rect.h"
|
||||
#include "base/timer.h"
|
||||
|
||||
namespace style {
|
||||
struct EmojiPan;
|
||||
} // namespace style
|
||||
|
||||
namespace Core {
|
||||
struct RecentEmojiId;
|
||||
} // namespace Core
|
||||
@@ -33,6 +37,10 @@ namespace Ui::Emoji {
|
||||
enum class Section;
|
||||
} // namespace Ui::Emoji
|
||||
|
||||
namespace Ui::Text {
|
||||
struct CustomEmojiColored;
|
||||
} // namespace Ui::Text
|
||||
|
||||
namespace Ui::CustomEmoji {
|
||||
class Loader;
|
||||
class Instance;
|
||||
@@ -50,16 +58,40 @@ inline constexpr auto kEmojiSectionCount = 8;
|
||||
struct StickerIcon;
|
||||
class EmojiColorPicker;
|
||||
class StickersListFooter;
|
||||
class GradientPremiumStar;
|
||||
class LocalStickersManager;
|
||||
|
||||
enum class EmojiListMode {
|
||||
Full,
|
||||
EmojiStatus,
|
||||
FullReactions,
|
||||
RecentReactions,
|
||||
};
|
||||
|
||||
struct EmojiListDescriptor {
|
||||
not_null<Main::Session*> session;
|
||||
EmojiListMode mode = EmojiListMode::Full;
|
||||
Window::SessionController *controller = nullptr;
|
||||
Fn<bool()> paused;
|
||||
std::vector<DocumentId> customRecentList;
|
||||
Fn<std::unique_ptr<Ui::Text::CustomEmoji>(
|
||||
DocumentId,
|
||||
Fn<void()>)> customRecentFactory;
|
||||
const style::EmojiPan *st = nullptr;
|
||||
};
|
||||
|
||||
class EmojiListWidget
|
||||
: public TabbedSelector::Inner
|
||||
, public Ui::AbstractTooltipShower {
|
||||
public:
|
||||
using Mode = EmojiListMode;
|
||||
|
||||
EmojiListWidget(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
Window::GifPauseReason level);
|
||||
Window::GifPauseReason level,
|
||||
Mode mode);
|
||||
EmojiListWidget(QWidget *parent, EmojiListDescriptor &&descriptor);
|
||||
~EmojiListWidget();
|
||||
|
||||
using Section = Ui::Emoji::Section;
|
||||
@@ -79,11 +111,21 @@ public:
|
||||
|
||||
void refreshEmoji();
|
||||
|
||||
[[nodiscard]] rpl::producer<EmojiPtr> chosen() const;
|
||||
[[nodiscard]] auto customChosen() const
|
||||
-> rpl::producer<TabbedSelector::FileChosen>;
|
||||
[[nodiscard]] auto premiumChosen() const
|
||||
-> rpl::producer<not_null<DocumentData*>>;
|
||||
[[nodiscard]] rpl::producer<EmojiChosen> chosen() const;
|
||||
[[nodiscard]] rpl::producer<FileChosen> customChosen() const;
|
||||
[[nodiscard]] rpl::producer<> jumpedToPremium() const;
|
||||
|
||||
void provideRecent(const std::vector<DocumentId> &customRecentList);
|
||||
|
||||
void paintExpanding(
|
||||
QPainter &p,
|
||||
QRect clip,
|
||||
int finalBottom,
|
||||
float64 progress,
|
||||
RectPart origin);
|
||||
|
||||
base::unique_qptr<Ui::PopupMenu> fillContextMenu(
|
||||
SendMenu::Type type) override;
|
||||
|
||||
protected:
|
||||
void visibleTopBottomUpdated(
|
||||
@@ -103,6 +145,7 @@ protected:
|
||||
void processHideFinished() override;
|
||||
void processPanelHideFinished() override;
|
||||
int countDesiredHeight(int newWidth) override;
|
||||
int defaultMinimalHeight() const override;
|
||||
|
||||
private:
|
||||
struct SectionInfo {
|
||||
@@ -177,6 +220,11 @@ private:
|
||||
OverEmoji,
|
||||
OverSet,
|
||||
OverButton>;
|
||||
struct ExpandingContext {
|
||||
float64 progress = 0.;
|
||||
int finalHeight = 0;
|
||||
bool expanding = false;
|
||||
};
|
||||
|
||||
template <typename Callback>
|
||||
bool enumerateSections(Callback callback) const;
|
||||
@@ -187,7 +235,7 @@ private:
|
||||
|
||||
void showPicker();
|
||||
void pickerHidden();
|
||||
void colorChosen(EmojiPtr emoji);
|
||||
void colorChosen(EmojiChosen data);
|
||||
bool checkPickerHide();
|
||||
void refreshCustom();
|
||||
void unloadNotSeenCustom(int visibleTop, int visibleBottom);
|
||||
@@ -200,21 +248,35 @@ private:
|
||||
void setPressed(OverState newPressed);
|
||||
|
||||
[[nodiscard]] EmojiPtr lookupOverEmoji(const OverEmoji *over) const;
|
||||
void selectEmoji(EmojiPtr emoji);
|
||||
void selectCustom(not_null<DocumentData*> document);
|
||||
[[nodiscard]] DocumentData *lookupCustomEmoji(
|
||||
int index,
|
||||
int section) const;
|
||||
[[nodiscard]] EmojiChosen lookupChosen(
|
||||
EmojiPtr emoji,
|
||||
not_null<const OverEmoji*> over);
|
||||
[[nodiscard]] FileChosen lookupChosen(
|
||||
not_null<DocumentData*> custom,
|
||||
const OverEmoji *over,
|
||||
Api::SendOptions options = Api::SendOptions());
|
||||
void selectEmoji(EmojiChosen data);
|
||||
void selectCustom(FileChosen data);
|
||||
void paint(QPainter &p, ExpandingContext context, QRect clip);
|
||||
void drawCollapsedBadge(QPainter &p, QPoint position, int count);
|
||||
void drawRecent(
|
||||
QPainter &p,
|
||||
const ExpandingContext &context,
|
||||
QPoint position,
|
||||
crl::time now,
|
||||
bool paused,
|
||||
int index);
|
||||
void drawEmoji(
|
||||
QPainter &p,
|
||||
const ExpandingContext &context,
|
||||
QPoint position,
|
||||
EmojiPtr emoji);
|
||||
void drawCustom(
|
||||
QPainter &p,
|
||||
const ExpandingContext &context,
|
||||
QPoint position,
|
||||
crl::time now,
|
||||
bool paused,
|
||||
@@ -247,6 +309,7 @@ private:
|
||||
void displaySet(uint64 setId);
|
||||
void removeSet(uint64 setId);
|
||||
|
||||
void refreshColoredStatuses();
|
||||
void initButton(RightButton &button, const QString &text, bool gradient);
|
||||
[[nodiscard]] std::unique_ptr<Ui::RippleAnimation> createButtonRipple(
|
||||
int section);
|
||||
@@ -255,35 +318,51 @@ private:
|
||||
void repaintCustom(uint64 setId);
|
||||
|
||||
void fillRecent();
|
||||
void fillRecentFrom(const std::vector<DocumentId> &list);
|
||||
[[nodiscard]] not_null<Ui::Text::CustomEmoji*> resolveCustomEmoji(
|
||||
not_null<DocumentData*> document,
|
||||
uint64 setId);
|
||||
[[nodiscard]] Ui::Text::CustomEmoji *resolveCustomEmoji(
|
||||
[[nodiscard]] Ui::Text::CustomEmoji *resolveCustomRecent(
|
||||
Core::RecentEmojiId customId);
|
||||
[[nodiscard]] not_null<Ui::Text::CustomEmoji*> resolveCustomEmoji(
|
||||
[[nodiscard]] not_null<Ui::Text::CustomEmoji*> resolveCustomRecent(
|
||||
DocumentId documentId);
|
||||
[[nodiscard]] Fn<void()> repaintCallback(
|
||||
DocumentId documentId,
|
||||
uint64 setId);
|
||||
|
||||
Window::SessionController *_controller = nullptr;
|
||||
Mode _mode = Mode::Full;
|
||||
const int _staticCount = 0;
|
||||
StickersListFooter *_footer = nullptr;
|
||||
std::unique_ptr<GradientPremiumStar> _premiumIcon;
|
||||
std::unique_ptr<LocalStickersManager> _localSetsManager;
|
||||
Fn<std::unique_ptr<Ui::Text::CustomEmoji>(
|
||||
DocumentId,
|
||||
Fn<void()>)> _customRecentFactory;
|
||||
|
||||
int _counts[kEmojiSectionCount];
|
||||
std::vector<RecentOne> _recent;
|
||||
base::flat_set<DocumentId> _recentCustomIds;
|
||||
base::flat_set<uint64> _repaintsScheduled;
|
||||
std::unique_ptr<Ui::Text::CustomEmojiColored> _emojiStatusColor;
|
||||
bool _recentPainted = false;
|
||||
bool _grabbingChosen = false;
|
||||
QVector<EmojiPtr> _emoji[kEmojiSectionCount];
|
||||
std::vector<CustomSet> _custom;
|
||||
base::flat_map<DocumentId, CustomEmojiInstance> _customEmoji;
|
||||
base::flat_map<
|
||||
DocumentId,
|
||||
std::unique_ptr<Ui::Text::CustomEmoji>> _customRecent;
|
||||
int _customSingleSize = 0;
|
||||
bool _allowWithoutPremium = false;
|
||||
Ui::RoundRect _overBg;
|
||||
|
||||
int _rowsLeft = 0;
|
||||
int _columnCount = 1;
|
||||
QSize _singleSize;
|
||||
QPoint _areaPosition;
|
||||
QPoint _innerPosition;
|
||||
QPoint _customPosition;
|
||||
|
||||
RightButton _add;
|
||||
RightButton _unlock;
|
||||
@@ -298,9 +377,9 @@ private:
|
||||
object_ptr<EmojiColorPicker> _picker;
|
||||
base::Timer _showPickerTimer;
|
||||
|
||||
rpl::event_stream<EmojiPtr> _chosen;
|
||||
rpl::event_stream<TabbedSelector::FileChosen> _customChosen;
|
||||
rpl::event_stream<not_null<DocumentData*>> _premiumChosen;
|
||||
rpl::event_stream<EmojiChosen> _chosen;
|
||||
rpl::event_stream<FileChosen> _customChosen;
|
||||
rpl::event_stream<> _jumpedToPremium;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -351,7 +351,11 @@ void SuggestionsWidget::paintEvent(QPaintEvent *e) {
|
||||
const auto x = i * _oneWidth + (_oneWidth - size) / 2;
|
||||
const auto y = (_oneWidth - size) / 2;
|
||||
if (row.custom) {
|
||||
row.custom->paint(p, x, y, now, preview, false);
|
||||
row.custom->paint(p, {
|
||||
.preview = preview,
|
||||
.now = now,
|
||||
.position = { x, y },
|
||||
});
|
||||
} else {
|
||||
Ui::Emoji::Draw(p, emoji, esize, x, y);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "menu/menu_send.h" // SendMenu::FillSendMenu
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
#include "chat_helpers/message_field.h" // PrepareMentionTag.
|
||||
#include "chat_helpers/tabbed_selector.h" // ChatHelpers::FileChosen.
|
||||
#include "mainwindow.h"
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_chat_participants.h"
|
||||
@@ -1127,7 +1128,7 @@ bool FieldAutocomplete::Inner::chooseAtIndex(
|
||||
};
|
||||
};
|
||||
|
||||
_stickerChosen.fire({ document, options, method, from() });
|
||||
_stickerChosen.fire({ document, options, from() });
|
||||
return true;
|
||||
}
|
||||
} else if (!_mrows->empty()) {
|
||||
|
||||
@@ -38,6 +38,9 @@ namespace SendMenu {
|
||||
enum class Type;
|
||||
} // namespace SendMenu
|
||||
|
||||
namespace ChatHelpers {
|
||||
struct FileChosen;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
class FieldAutocomplete final : public Ui::RpWidget {
|
||||
public:
|
||||
@@ -73,22 +76,17 @@ public:
|
||||
};
|
||||
struct MentionChosen {
|
||||
not_null<UserData*> user;
|
||||
ChooseMethod method;
|
||||
ChooseMethod method = ChooseMethod::ByEnter;
|
||||
};
|
||||
struct HashtagChosen {
|
||||
QString hashtag;
|
||||
ChooseMethod method;
|
||||
ChooseMethod method = ChooseMethod::ByEnter;
|
||||
};
|
||||
struct BotCommandChosen {
|
||||
QString command;
|
||||
ChooseMethod method;
|
||||
};
|
||||
struct StickerChosen {
|
||||
not_null<DocumentData*> sticker;
|
||||
Api::SendOptions options;
|
||||
ChooseMethod method;
|
||||
Ui::MessageSendingAnimationFrom messageSendingFrom;
|
||||
ChooseMethod method = ChooseMethod::ByEnter;
|
||||
};
|
||||
using StickerChosen = ChatHelpers::FileChosen;
|
||||
enum class Type {
|
||||
Mentions,
|
||||
Hashtags,
|
||||
|
||||
@@ -105,7 +105,8 @@ private:
|
||||
|
||||
};
|
||||
|
||||
GifsListWidget::Footer::Footer(not_null<GifsListWidget*> parent) : InnerFooter(parent)
|
||||
GifsListWidget::Footer::Footer(not_null<GifsListWidget*> parent)
|
||||
: InnerFooter(parent, st::defaultEmojiPan)
|
||||
, _pan(parent)
|
||||
, _field(this, st::gifsSearchField, tr::lng_gifs_search())
|
||||
, _cancel(this, st::gifsSearchCancel) {
|
||||
@@ -170,8 +171,13 @@ GifsListWidget::GifsListWidget(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
Window::GifPauseReason level)
|
||||
: Inner(parent, controller, level)
|
||||
, _api(&controller->session().mtp())
|
||||
: Inner(
|
||||
parent,
|
||||
st::defaultEmojiPan,
|
||||
&controller->session(),
|
||||
Window::PausedIn(controller, level))
|
||||
, _controller(controller)
|
||||
, _api(&session().mtp())
|
||||
, _section(Section::Gifs)
|
||||
, _updateInlineItems([=] { updateInlineItems(); })
|
||||
, _mosaic(st::emojiPanWidth - st::inlineResultsLeft)
|
||||
@@ -186,19 +192,19 @@ GifsListWidget::GifsListWidget(
|
||||
this,
|
||||
[=] { sendInlineRequest(); });
|
||||
|
||||
controller->session().data().stickers().savedGifsUpdated(
|
||||
session().data().stickers().savedGifsUpdated(
|
||||
) | rpl::start_with_next([=] {
|
||||
refreshSavedGifs();
|
||||
}, lifetime());
|
||||
|
||||
controller->session().downloaderTaskFinished(
|
||||
session().downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateInlineItems();
|
||||
}, lifetime());
|
||||
|
||||
controller->gifPauseLevelChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (!controller->isGifPausedAtLeastFor(level)) {
|
||||
if (!paused()) {
|
||||
updateInlineItems();
|
||||
}
|
||||
}, lifetime());
|
||||
@@ -214,12 +220,11 @@ GifsListWidget::GifsListWidget(
|
||||
_mosaic.setRightSkip(st::inlineResultsSkip);
|
||||
}
|
||||
|
||||
rpl::producer<TabbedSelector::FileChosen> GifsListWidget::fileChosen() const {
|
||||
rpl::producer<FileChosen> GifsListWidget::fileChosen() const {
|
||||
return _fileChosen.events();
|
||||
}
|
||||
|
||||
auto GifsListWidget::photoChosen() const
|
||||
-> rpl::producer<TabbedSelector::PhotoChosen> {
|
||||
rpl::producer<PhotoChosen> GifsListWidget::photoChosen() const {
|
||||
return _photoChosen.events();
|
||||
}
|
||||
|
||||
@@ -285,7 +290,7 @@ void GifsListWidget::inlineResultsDone(const MTPmessages_BotResults &result) {
|
||||
auto adding = (it != _inlineCache.cend());
|
||||
if (result.type() == mtpc_messages_botResults) {
|
||||
auto &d = result.c_messages_botResults();
|
||||
controller()->session().data().processUsers(d.vusers());
|
||||
session().data().processUsers(d.vusers());
|
||||
|
||||
auto &v = d.vresults().v;
|
||||
auto queryId = d.vquery_id().v;
|
||||
@@ -303,7 +308,7 @@ void GifsListWidget::inlineResultsDone(const MTPmessages_BotResults &result) {
|
||||
auto added = 0;
|
||||
for (const auto &res : v) {
|
||||
auto result = InlineBots::Result::Create(
|
||||
&controller()->session(),
|
||||
&session(),
|
||||
queryId,
|
||||
res);
|
||||
if (result) {
|
||||
@@ -343,7 +348,7 @@ void GifsListWidget::paintInlineItems(Painter &p, QRect clip) {
|
||||
p.drawText(QRect(0, 0, width(), (height() / 3) * 2 + st::normalFont->height), text, style::al_center);
|
||||
return;
|
||||
}
|
||||
const auto gifPaused = controller()->isGifPausedAtLeastFor(level());
|
||||
const auto gifPaused = paused();
|
||||
using namespace InlineBots::Layout;
|
||||
PaintContext context(crl::now(), false, gifPaused, false);
|
||||
|
||||
@@ -370,13 +375,15 @@ void GifsListWidget::mousePressEvent(QMouseEvent *e) {
|
||||
_previewTimer.callOnce(QApplication::startDragTime());
|
||||
}
|
||||
|
||||
void GifsListWidget::fillContextMenu(
|
||||
not_null<Ui::PopupMenu*> menu,
|
||||
base::unique_qptr<Ui::PopupMenu> GifsListWidget::fillContextMenu(
|
||||
SendMenu::Type type) {
|
||||
if (_selected < 0 || _pressed >= 0) {
|
||||
return;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto menu = base::make_unique_q<Ui::PopupMenu>(
|
||||
this,
|
||||
st::popupMenuWithIcons);
|
||||
const auto send = [=, selected = _selected](Api::SendOptions options) {
|
||||
selectInlineResult(selected, options, true);
|
||||
};
|
||||
@@ -397,9 +404,10 @@ void GifsListWidget::fillContextMenu(
|
||||
const style::icon *icon) {
|
||||
menu->addAction(text, std::move(done), icon);
|
||||
};
|
||||
AddGifAction(std::move(callback), controller(), document);
|
||||
AddGifAction(std::move(callback), _controller, document);
|
||||
}
|
||||
};
|
||||
}
|
||||
return menu;
|
||||
}
|
||||
|
||||
void GifsListWidget::mouseReleaseEvent(QMouseEvent *e) {
|
||||
@@ -426,7 +434,7 @@ void GifsListWidget::mouseReleaseEvent(QMouseEvent *e) {
|
||||
ActivateClickHandler(window(), activated, {
|
||||
e->button(),
|
||||
QVariant::fromValue(ClickHandlerContext{
|
||||
.sessionWindow = base::make_weak(controller().get()),
|
||||
.sessionWindow = base::make_weak(_controller.get()),
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -449,7 +457,7 @@ void GifsListWidget::selectInlineResult(
|
||||
_mosaic.findRect(index).topLeft());
|
||||
return Ui::MessageSendingAnimationFrom{
|
||||
.type = Ui::MessageSendingAnimationFrom::Type::Gif,
|
||||
.localId = controller()->session().data().nextLocalMessageId(),
|
||||
.localId = session().data().nextLocalMessageId(),
|
||||
.globalStartGeometry = mapToGlobal(rect),
|
||||
.crop = true,
|
||||
};
|
||||
@@ -554,7 +562,7 @@ void GifsListWidget::refreshSavedGifs() {
|
||||
if (_section == Section::Gifs) {
|
||||
clearInlineRows(false);
|
||||
|
||||
const auto &saved = controller()->session().data().stickers().savedGifs();
|
||||
const auto &saved = session().data().stickers().savedGifs();
|
||||
if (!saved.isEmpty()) {
|
||||
const auto layouts = ranges::views::all(
|
||||
saved
|
||||
@@ -827,9 +835,9 @@ void GifsListWidget::searchForGifs(const QString &query) {
|
||||
Expects(result.type() == mtpc_contacts_resolvedPeer);
|
||||
|
||||
auto &data = result.c_contacts_resolvedPeer();
|
||||
controller()->session().data().processUsers(data.vusers());
|
||||
controller()->session().data().processChats(data.vchats());
|
||||
const auto peer = controller()->session().data().peerLoaded(
|
||||
session().data().processUsers(data.vusers());
|
||||
session().data().processChats(data.vchats());
|
||||
const auto peer = session().data().peerLoaded(
|
||||
peerFromMTP(data.vpeer()));
|
||||
if (const auto user = peer ? peer->asUser() : nullptr) {
|
||||
_searchBot = user;
|
||||
@@ -918,11 +926,11 @@ void GifsListWidget::updateSelected() {
|
||||
_pressed = _selected;
|
||||
if (item) {
|
||||
if (const auto preview = item->getPreviewDocument()) {
|
||||
controller()->widget()->showMediaPreview(
|
||||
_controller->widget()->showMediaPreview(
|
||||
Data::FileOriginSavedGifs(),
|
||||
preview);
|
||||
} else if (const auto preview = item->getPreviewPhoto()) {
|
||||
controller()->widget()->showMediaPreview(
|
||||
_controller->widget()->showMediaPreview(
|
||||
Data::FileOrigin(),
|
||||
preview);
|
||||
}
|
||||
@@ -940,11 +948,11 @@ void GifsListWidget::showPreview() {
|
||||
}
|
||||
if (const auto layout = _mosaic.maybeItemAt(_pressed)) {
|
||||
if (const auto previewDocument = layout->getPreviewDocument()) {
|
||||
_previewShown = controller()->widget()->showMediaPreview(
|
||||
_previewShown = _controller->widget()->showMediaPreview(
|
||||
Data::FileOriginSavedGifs(),
|
||||
previewDocument);
|
||||
} else if (const auto previewPhoto = layout->getPreviewPhoto()) {
|
||||
_previewShown = controller()->widget()->showMediaPreview(
|
||||
_previewShown = _controller->widget()->showMediaPreview(
|
||||
Data::FileOrigin(),
|
||||
previewPhoto);
|
||||
}
|
||||
|
||||
@@ -49,15 +49,13 @@ class GifsListWidget
|
||||
: public TabbedSelector::Inner
|
||||
, public InlineBots::Layout::Context {
|
||||
public:
|
||||
using InlineChosen = TabbedSelector::InlineChosen;
|
||||
|
||||
GifsListWidget(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
Window::GifPauseReason level);
|
||||
|
||||
rpl::producer<TabbedSelector::FileChosen> fileChosen() const;
|
||||
rpl::producer<TabbedSelector::PhotoChosen> photoChosen() const;
|
||||
rpl::producer<FileChosen> fileChosen() const;
|
||||
rpl::producer<PhotoChosen> photoChosen() const;
|
||||
rpl::producer<InlineChosen> inlineResultChosen() const;
|
||||
|
||||
void refreshRecent() override;
|
||||
@@ -82,8 +80,7 @@ public:
|
||||
void cancelled();
|
||||
rpl::producer<> cancelRequests() const;
|
||||
|
||||
void fillContextMenu(
|
||||
not_null<Ui::PopupMenu*> menu,
|
||||
base::unique_qptr<Ui::PopupMenu> fillContextMenu(
|
||||
SendMenu::Type type) override;
|
||||
|
||||
~GifsListWidget();
|
||||
@@ -140,24 +137,8 @@ private:
|
||||
void repaintItems(crl::time now = 0);
|
||||
void showPreview();
|
||||
|
||||
MTP::Sender _api;
|
||||
|
||||
Section _section = Section::Gifs;
|
||||
crl::time _lastScrolledAt = 0;
|
||||
crl::time _lastUpdatedAt = 0;
|
||||
base::Timer _updateInlineItems;
|
||||
bool _inlineWithThumb = false;
|
||||
|
||||
void clearInlineRows(bool resultsDeleted);
|
||||
|
||||
std::map<
|
||||
not_null<DocumentData*>,
|
||||
std::unique_ptr<LayoutItem>> _gifLayouts;
|
||||
LayoutItem *layoutPrepareSavedGif(not_null<DocumentData*> document);
|
||||
|
||||
std::map<
|
||||
not_null<InlineResult*>,
|
||||
std::unique_ptr<LayoutItem>> _inlineLayouts;
|
||||
LayoutItem *layoutPrepareInlineResult(not_null<InlineResult*> result);
|
||||
|
||||
void deleteUnusedGifLayouts();
|
||||
@@ -170,6 +151,23 @@ private:
|
||||
Api::SendOptions options,
|
||||
bool forceSend = false);
|
||||
|
||||
not_null<Window::SessionController*> _controller;
|
||||
|
||||
MTP::Sender _api;
|
||||
|
||||
Section _section = Section::Gifs;
|
||||
crl::time _lastScrolledAt = 0;
|
||||
crl::time _lastUpdatedAt = 0;
|
||||
base::Timer _updateInlineItems;
|
||||
bool _inlineWithThumb = false;
|
||||
|
||||
std::map<
|
||||
not_null<DocumentData*>,
|
||||
std::unique_ptr<LayoutItem>> _gifLayouts;
|
||||
std::map<
|
||||
not_null<InlineResult*>,
|
||||
std::unique_ptr<LayoutItem>> _inlineLayouts;
|
||||
|
||||
Footer *_footer = nullptr;
|
||||
|
||||
Mosaic::Layout::MosaicLayout<LayoutItem> _mosaic;
|
||||
@@ -190,8 +188,8 @@ private:
|
||||
QString _inlineQuery, _inlineNextQuery, _inlineNextOffset;
|
||||
mtpRequestId _inlineRequestId = 0;
|
||||
|
||||
rpl::event_stream<TabbedSelector::FileChosen> _fileChosen;
|
||||
rpl::event_stream<TabbedSelector::PhotoChosen> _photoChosen;
|
||||
rpl::event_stream<FileChosen> _fileChosen;
|
||||
rpl::event_stream<PhotoChosen> _photoChosen;
|
||||
rpl::event_stream<InlineChosen> _inlineResultChosen;
|
||||
rpl::event_stream<> _cancelled;
|
||||
|
||||
|
||||
@@ -87,11 +87,10 @@ QString FieldTagMimeProcessor::operator()(QStringView mimeTag) {
|
||||
} else if (Ui::InputField::IsCustomEmojiLink(tag)) {
|
||||
const auto data = Ui::InputField::CustomEmojiEntityData(tag);
|
||||
const auto emoji = Data::ParseCustomEmojiData(data);
|
||||
if (emoji.selfId != id) {
|
||||
if (!emoji.id) {
|
||||
i = all.erase(i);
|
||||
continue;
|
||||
}
|
||||
if (!_session->premium()) {
|
||||
} else if (!_session->premium()) {
|
||||
const auto document = _session->data().document(emoji.id);
|
||||
if (document->isPremiumEmoji()) {
|
||||
if (!_allowPremiumEmoji
|
||||
@@ -624,8 +623,7 @@ void MessageLinksParser::parse() {
|
||||
Expects(tag != tagsEnd);
|
||||
|
||||
if (Ui::InputField::IsValidMarkdownLink(tag->id)
|
||||
&& !TextUtilities::IsMentionLink(tag->id)
|
||||
&& !Ui::InputField::IsCustomEmojiLink(tag->id)) {
|
||||
&& !TextUtilities::IsMentionLink(tag->id)) {
|
||||
ranges.push_back({ tag->offset, tag->length, tag->id });
|
||||
}
|
||||
++tag;
|
||||
|
||||
@@ -439,6 +439,7 @@ void EmojiPack::refreshAll() {
|
||||
for (const auto &[emoji, list] : _items) {
|
||||
refreshItems(list);
|
||||
}
|
||||
refreshItems(_onlyCustomItems);
|
||||
}
|
||||
|
||||
void EmojiPack::refreshItems(EmojiPtr emoji) {
|
||||
|
||||
@@ -96,10 +96,4 @@ void GiftBoxPack::applySet(const MTPDmessages_stickerSet &data) {
|
||||
}
|
||||
}
|
||||
|
||||
bool GiftBoxPack::isGiftSticker(not_null<DocumentData*> document) const {
|
||||
return _setId
|
||||
&& document->sticker()
|
||||
&& (document->sticker()->set.id == _setId);
|
||||
}
|
||||
|
||||
} // namespace Stickers
|
||||
|
||||
@@ -23,8 +23,6 @@ public:
|
||||
void load();
|
||||
[[nodiscard]] DocumentData *lookup(int months) const;
|
||||
|
||||
[[nodiscard]] bool isGiftSticker(not_null<DocumentData*> document) const;
|
||||
|
||||
private:
|
||||
using SetId = uint64;
|
||||
void applySet(const MTPDmessages_stickerSet &data);
|
||||
|
||||
@@ -21,7 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lottie/lottie_single_player.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "ui/rect_part.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
|
||||
#include <QtWidgets/QApplication>
|
||||
@@ -145,50 +145,30 @@ bool StickersListFooter::ScrollState::animationCallback(crl::time now) {
|
||||
return true;
|
||||
}
|
||||
|
||||
StickersListFooter::StickersListFooter(Descriptor &&descriptor)
|
||||
: InnerFooter(descriptor.parent)
|
||||
, _controller(descriptor.controller)
|
||||
, _level(descriptor.level)
|
||||
, _searchButtonVisible(descriptor.searchButtonVisible)
|
||||
, _settingsButtonVisible(descriptor.settingsButtonVisible)
|
||||
, _iconState([=] { update(); })
|
||||
, _subiconState([=] { update(); })
|
||||
, _selectionBg(st::roundRadiusLarge, st::windowBgRipple)
|
||||
, _subselectionBg(st::emojiIconArea / 2, st::windowBgRipple)
|
||||
, _barSelection(descriptor.barSelection) {
|
||||
setMouseTracking(true);
|
||||
|
||||
_iconsLeft = st::emojiCategorySkip + (_searchButtonVisible
|
||||
? st::stickerIconWidth
|
||||
: 0);
|
||||
_iconsRight = st::emojiCategorySkip + (_settingsButtonVisible
|
||||
? st::stickerIconWidth
|
||||
: 0);
|
||||
|
||||
_controller->session().downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
update();
|
||||
}, lifetime());
|
||||
|
||||
GradientPremiumStar::GradientPremiumStar() {
|
||||
style::PaletteChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
_premiumIcon = QImage();
|
||||
}, lifetime());
|
||||
_image = QImage();
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
void StickersListFooter::validatePremiumIcon() const {
|
||||
if (!_premiumIcon.isNull()) {
|
||||
return;
|
||||
QImage GradientPremiumStar::image() const {
|
||||
if (_image.isNull()) {
|
||||
renderOnDemand();
|
||||
}
|
||||
return _image;
|
||||
}
|
||||
|
||||
void GradientPremiumStar::renderOnDemand() const {
|
||||
const auto size = st::stickersPremium.size();
|
||||
const auto mask = st::stickersPremium.instance(Qt::white);
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
_premiumIcon = QImage(
|
||||
_image = QImage(
|
||||
size * factor,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
_premiumIcon.setDevicePixelRatio(factor);
|
||||
_image.setDevicePixelRatio(factor);
|
||||
|
||||
QPainter p(&_premiumIcon);
|
||||
QPainter p(&_image);
|
||||
auto gradient = QLinearGradient(
|
||||
QPoint(0, size.height()),
|
||||
QPoint(size.width(), 0));
|
||||
@@ -201,6 +181,34 @@ void StickersListFooter::validatePremiumIcon() const {
|
||||
p.drawImage(QRect(QPoint(), size), mask);
|
||||
}
|
||||
|
||||
StickersListFooter::StickersListFooter(Descriptor &&descriptor)
|
||||
: InnerFooter(
|
||||
descriptor.parent,
|
||||
descriptor.st ? *descriptor.st : st::defaultEmojiPan)
|
||||
, _session(descriptor.session)
|
||||
, _paused(descriptor.paused)
|
||||
, _searchButtonVisible(descriptor.searchButtonVisible)
|
||||
, _settingsButtonVisible(descriptor.settingsButtonVisible)
|
||||
, _iconState([=] { update(); })
|
||||
, _subiconState([=] { update(); })
|
||||
, _selectionBg(st::roundRadiusLarge, st::windowBgRipple)
|
||||
, _subselectionBg(st().iconArea / 2, st::windowBgRipple)
|
||||
, _barSelection(descriptor.barSelection) {
|
||||
setMouseTracking(true);
|
||||
|
||||
_iconsLeft = st().iconSkip + (_searchButtonVisible
|
||||
? st::stickerIconWidth
|
||||
: 0);
|
||||
_iconsRight = st().iconSkip + (_settingsButtonVisible
|
||||
? st::stickerIconWidth
|
||||
: 0);
|
||||
|
||||
_session->downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
update();
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void StickersListFooter::clearHeavyData() {
|
||||
enumerateIcons([&](const IconInfo &info) {
|
||||
auto &icon = _icons[info.index];
|
||||
@@ -215,6 +223,27 @@ void StickersListFooter::clearHeavyData() {
|
||||
});
|
||||
}
|
||||
|
||||
void StickersListFooter::paintExpanding(
|
||||
Painter &p,
|
||||
QRect clip,
|
||||
float64 radius,
|
||||
RectPart origin) {
|
||||
const auto delta = ((origin | RectPart::None) & RectPart::FullBottom)
|
||||
? (height() - clip.height())
|
||||
: 0;
|
||||
const auto shift = QPoint(clip.x(), clip.y() - delta);
|
||||
p.translate(shift);
|
||||
const auto context = ExpandingContext{
|
||||
.clip = clip.translated(-shift),
|
||||
.progress = clip.height() / float64(height()),
|
||||
.radius = int(std::ceil(radius)),
|
||||
.expanding = true,
|
||||
};
|
||||
paint(p, context);
|
||||
p.translate(-shift);
|
||||
p.setClipping(false);
|
||||
}
|
||||
|
||||
void StickersListFooter::initSearch() {
|
||||
_searchField.create(
|
||||
this,
|
||||
@@ -529,9 +558,15 @@ void StickersListFooter::setLoading(bool loading) {
|
||||
}
|
||||
|
||||
void StickersListFooter::paintEvent(QPaintEvent *e) {
|
||||
Painter p(this);
|
||||
auto p = Painter(this);
|
||||
|
||||
_repaintScheduled = false;
|
||||
paint(p, {});
|
||||
}
|
||||
|
||||
void StickersListFooter::paint(
|
||||
Painter &p,
|
||||
const ExpandingContext &context) const {
|
||||
if (_searchButtonVisible) {
|
||||
paintSearchIcon(p);
|
||||
}
|
||||
@@ -547,29 +582,41 @@ void StickersListFooter::paintEvent(QPaintEvent *e) {
|
||||
_iconsLeft,
|
||||
_iconsTop,
|
||||
width() - _iconsLeft - _iconsRight,
|
||||
st::emojiFooterHeight);
|
||||
st().footer);
|
||||
if (rtl()) {
|
||||
clip.moveLeft(width() - _iconsLeft - clip.width());
|
||||
}
|
||||
p.setClipRect(clip);
|
||||
if (context.expanding) {
|
||||
const auto both = clip.intersected(
|
||||
context.clip.marginsRemoved(
|
||||
{ context.radius, 0, context.radius, 0 }));
|
||||
if (both.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
p.setClipRect(both);
|
||||
} else {
|
||||
p.setClipRect(clip);
|
||||
}
|
||||
|
||||
if (!_barSelection) {
|
||||
paintSelectionBg(p);
|
||||
paintSelectionBg(p, context);
|
||||
}
|
||||
|
||||
const auto now = crl::now();
|
||||
const auto paused = _controller->isGifPausedAtLeastFor(_level);
|
||||
const auto paused = _paused();
|
||||
enumerateVisibleIcons([&](const IconInfo &info) {
|
||||
paintSetIcon(p, info, now, paused);
|
||||
paintSetIcon(p, context, info, now, paused);
|
||||
});
|
||||
|
||||
if (_barSelection) {
|
||||
paintSelectionBar(p);
|
||||
}
|
||||
paintLeftRightFading(p);
|
||||
paintLeftRightFading(p, context);
|
||||
}
|
||||
|
||||
void StickersListFooter::paintSelectionBg(Painter &p) const {
|
||||
void StickersListFooter::paintSelectionBg(
|
||||
QPainter &p,
|
||||
const ExpandingContext &context) const {
|
||||
auto selxrel = _iconsLeft + qRound(_iconState.selectionX.current());
|
||||
auto selx = selxrel - qRound(_iconState.x.current());
|
||||
const auto selw = qRound(_iconState.selectionWidth.current());
|
||||
@@ -577,10 +624,19 @@ void StickersListFooter::paintSelectionBg(Painter &p) const {
|
||||
selx = width() - selx - selw;
|
||||
}
|
||||
const auto sely = _iconsTop;
|
||||
const auto area = st::emojiIconArea;
|
||||
const auto rect = QRect(
|
||||
const auto area = st().iconArea;
|
||||
auto rect = QRect(
|
||||
QPoint(selx, sely) + _areaPosition,
|
||||
QSize(selw - 2 * _areaPosition.x(), area));
|
||||
if (context.expanding) {
|
||||
const auto recthalf = rect.height() / 2;
|
||||
const auto myhalf = height() / 2;
|
||||
const auto sub = anim::interpolate(recthalf, 0, context.progress);
|
||||
const auto shift = anim::interpolate(myhalf, 0, context.progress);
|
||||
rect = rect.marginsRemoved(
|
||||
{ sub, sub, sub, sub }
|
||||
).translated(0, shift);
|
||||
}
|
||||
if (rect.width() == rect.height() || _subiconsWidth <= _singleWidth) {
|
||||
_selectionBg.paint(p, rect);
|
||||
} else if (selw == _subiconsWidth) {
|
||||
@@ -599,7 +655,7 @@ void StickersListFooter::paintSelectionBg(Painter &p) const {
|
||||
}
|
||||
}
|
||||
|
||||
void StickersListFooter::paintSelectionBar(Painter &p) const {
|
||||
void StickersListFooter::paintSelectionBar(QPainter &p) const {
|
||||
auto selxrel = _iconsLeft + qRound(_iconState.selectionX.current());
|
||||
auto selx = selxrel - qRound(_iconState.x.current());
|
||||
const auto selw = qRound(_iconState.selectionWidth.current());
|
||||
@@ -608,35 +664,46 @@ void StickersListFooter::paintSelectionBar(Painter &p) const {
|
||||
}
|
||||
p.fillRect(
|
||||
selx,
|
||||
_iconsTop + st::emojiFooterHeight - st::stickerIconPadding,
|
||||
_iconsTop + st().footer - st::stickerIconPadding,
|
||||
selw,
|
||||
st::stickerIconSel,
|
||||
st::stickerIconSelColor);
|
||||
}
|
||||
|
||||
void StickersListFooter::paintLeftRightFading(Painter &p) const {
|
||||
auto o_left = std::clamp(
|
||||
_iconState.x.current() / st::stickerIconLeft.width(),
|
||||
void StickersListFooter::paintLeftRightFading(
|
||||
QPainter &p,
|
||||
const ExpandingContext &context) const {
|
||||
const auto o_left_normal = std::clamp(
|
||||
_iconState.x.current() / st().fadeLeft.width(),
|
||||
0.,
|
||||
1.);
|
||||
const auto o_left = context.expanding
|
||||
? (1. - context.progress * (1. - o_left_normal))
|
||||
: o_left_normal;
|
||||
const auto radiusSkip = context.expanding
|
||||
? std::max(context.radius - st::roundRadiusSmall, 0)
|
||||
: 0;
|
||||
if (o_left > 0) {
|
||||
p.setOpacity(o_left);
|
||||
st::stickerIconLeft.fill(p, style::rtlrect(_iconsLeft, _iconsTop, st::stickerIconLeft.width(), st::emojiFooterHeight, width()));
|
||||
st().fadeLeft.fill(p, style::rtlrect(std::max(_iconsLeft, radiusSkip), _iconsTop, st().fadeLeft.width(), st().footer, width()));
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
auto o_right = std::clamp(
|
||||
(_iconState.max - _iconState.x.current()) / st::stickerIconRight.width(),
|
||||
const auto o_right_normal = std::clamp(
|
||||
(_iconState.max - _iconState.x.current()) / st().fadeRight.width(),
|
||||
0.,
|
||||
1.);
|
||||
const auto o_right = context.expanding
|
||||
? (1. - context.progress * (1. - o_right_normal))
|
||||
: o_right_normal;
|
||||
if (o_right > 0) {
|
||||
p.setOpacity(o_right);
|
||||
st::stickerIconRight.fill(
|
||||
st().fadeRight.fill(
|
||||
p,
|
||||
style::rtlrect(
|
||||
width() - _iconsRight - st::stickerIconRight.width(),
|
||||
width() - std::max(_iconsRight, radiusSkip) - st().fadeRight.width(),
|
||||
_iconsTop,
|
||||
st::stickerIconRight.width(),
|
||||
st::emojiFooterHeight, width()));
|
||||
st().fadeRight.width(),
|
||||
st().footer, width()));
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
}
|
||||
@@ -875,19 +942,19 @@ void StickersListFooter::updateSelected() {
|
||||
&& x >= searchLeft
|
||||
&& x < searchLeft + _singleWidth
|
||||
&& y >= _iconsTop
|
||||
&& y < _iconsTop + st::emojiFooterHeight) {
|
||||
&& y < _iconsTop + st().footer) {
|
||||
newOver = SpecialOver::Search;
|
||||
} else if (_settingsButtonVisible
|
||||
&& x >= settingsLeft
|
||||
&& x < settingsLeft + _singleWidth
|
||||
&& y >= _iconsTop
|
||||
&& y < _iconsTop + st::emojiFooterHeight) {
|
||||
&& y < _iconsTop + st().footer) {
|
||||
if (!_icons.empty() && !hasOnlyFeaturedSets()) {
|
||||
newOver = SpecialOver::Settings;
|
||||
}
|
||||
} else if (!_icons.empty()) {
|
||||
if (y >= _iconsTop
|
||||
&& y < _iconsTop + st::emojiFooterHeight
|
||||
&& y < _iconsTop + st().footer
|
||||
&& x >= _iconsLeft
|
||||
&& x < width() - _iconsRight) {
|
||||
enumerateIcons([&](const IconInfo &info) {
|
||||
@@ -967,7 +1034,7 @@ void StickersListFooter::refreshIcons(
|
||||
void StickersListFooter::refreshScrollableDimensions() {
|
||||
const auto &last = iconInfo(_icons.size() - 1);
|
||||
_iconState.max = std::max(
|
||||
last.left + last.width + _iconsRight - width(),
|
||||
last.left + last.width + _iconsLeft + _iconsRight - width(),
|
||||
0);
|
||||
if (_iconState.x.current() > _iconState.max) {
|
||||
_iconState.x = anim::value(_iconState.max, _iconState.max);
|
||||
@@ -989,11 +1056,11 @@ void StickersListFooter::refreshIconsGeometry(
|
||||
&& _icons[1].setId == EmojiSectionSetId(EmojiSection::People)) {
|
||||
_singleWidth = (width() - _iconsLeft - _iconsRight) / _icons.size();
|
||||
} else {
|
||||
_singleWidth = st::emojiIconWidth;
|
||||
_singleWidth = st().iconWidth;
|
||||
}
|
||||
_areaPosition = QPoint(
|
||||
(_singleWidth - st::emojiIconArea) / 2,
|
||||
(st::emojiFooterHeight - st::emojiIconArea) / 2);
|
||||
(_singleWidth - st().iconArea) / 2,
|
||||
(st().footer - st().iconArea) / 2);
|
||||
refreshScrollableDimensions();
|
||||
refreshSubiconsGeometry();
|
||||
_iconState.selected = _subiconState.selected = -1;
|
||||
@@ -1036,22 +1103,22 @@ bool StickersListFooter::hasOnlyFeaturedSets() const {
|
||||
&& (_icons[0].setId == Data::Stickers::FeaturedSetId);
|
||||
}
|
||||
|
||||
void StickersListFooter::paintStickerSettingsIcon(Painter &p) const {
|
||||
void StickersListFooter::paintStickerSettingsIcon(QPainter &p) const {
|
||||
const auto settingsLeft = width() - _iconsRight;
|
||||
st::stickersSettings.paint(
|
||||
p,
|
||||
settingsLeft
|
||||
+ (_singleWidth - st::stickersSettings.width()) / 2,
|
||||
_iconsTop + st::emojiCategory.iconPosition.y(),
|
||||
_iconsTop + st::emojiCategoryIconTop,
|
||||
width());
|
||||
}
|
||||
|
||||
void StickersListFooter::paintSearchIcon(Painter &p) const {
|
||||
void StickersListFooter::paintSearchIcon(QPainter &p) const {
|
||||
const auto searchLeft = _iconsLeft - _singleWidth;
|
||||
st::stickersSearch.paint(
|
||||
p,
|
||||
searchLeft + (_singleWidth - st::stickersSearch.width()) / 2,
|
||||
_iconsTop + st::emojiCategory.iconPosition.y(),
|
||||
_iconsTop + st::emojiCategoryIconTop,
|
||||
width());
|
||||
}
|
||||
|
||||
@@ -1140,18 +1207,39 @@ void StickersListFooter::updateSetIcon(uint64 setId) {
|
||||
}
|
||||
|
||||
void StickersListFooter::updateSetIconAt(int left) {
|
||||
update(left, _iconsTop, _singleWidth, st::emojiFooterHeight);
|
||||
update(left, _iconsTop, _singleWidth, st().footer);
|
||||
}
|
||||
|
||||
void StickersListFooter::paintSetIcon(
|
||||
Painter &p,
|
||||
const ExpandingContext &context,
|
||||
const IconInfo &info,
|
||||
crl::time now,
|
||||
bool paused) const {
|
||||
const auto &icon = _icons[info.index];
|
||||
const auto expandingShift = context.expanding
|
||||
? QPoint(
|
||||
0,
|
||||
anim::interpolate(height() / 2, 0, context.progress))
|
||||
: QPoint();
|
||||
if (icon.sticker) {
|
||||
icon.ensureMediaCreated();
|
||||
const_cast<StickersListFooter*>(this)->validateIconAnimation(icon);
|
||||
}
|
||||
if (context.expanding) {
|
||||
if (icon.custom) {
|
||||
p.translate(expandingShift);
|
||||
} else {
|
||||
p.save();
|
||||
const auto center = QPoint(
|
||||
info.adjustedLeft + _singleWidth / 2,
|
||||
_iconsTop + st().footer / 2);
|
||||
p.translate(expandingShift + center);
|
||||
p.scale(context.progress, context.progress);
|
||||
p.translate(-center);
|
||||
}
|
||||
}
|
||||
if (icon.sticker) {
|
||||
const auto origin = icon.sticker->stickerSetOrigin();
|
||||
const auto thumb = icon.thumbnailMedia
|
||||
? icon.thumbnailMedia->image()
|
||||
@@ -1159,9 +1247,17 @@ void StickersListFooter::paintSetIcon(
|
||||
? icon.stickerMedia->thumbnail()
|
||||
: nullptr;
|
||||
const auto x = info.adjustedLeft + (_singleWidth - icon.pixw) / 2;
|
||||
const auto y = _iconsTop + (st::emojiFooterHeight - icon.pixh) / 2;
|
||||
const auto y = _iconsTop + (st().footer - icon.pixh) / 2;
|
||||
if (icon.custom) {
|
||||
icon.custom->paint(p, x, y, now, st::emojiIconFg->c, paused);
|
||||
icon.custom->paint(p, Ui::Text::CustomEmoji::Context{
|
||||
.preview = st::windowBgRipple->c,
|
||||
.size = QSize(icon.pixw, icon.pixh),
|
||||
.now = now,
|
||||
.scale = context.progress,
|
||||
.position = { x, y },
|
||||
.paused = paused,
|
||||
.scaled = context.expanding,
|
||||
});
|
||||
} else if (icon.lottie && icon.lottie->ready()) {
|
||||
const auto frame = icon.lottie->frame();
|
||||
const auto size = frame.size() / cIntRetinaFactor();
|
||||
@@ -1172,7 +1268,7 @@ void StickersListFooter::paintSetIcon(
|
||||
p.drawImage(
|
||||
QRect(
|
||||
(info.adjustedLeft + (_singleWidth - size.width()) / 2),
|
||||
_iconsTop + (st::emojiFooterHeight - size.height()) / 2,
|
||||
_iconsTop + (st().footer - size.height()) / 2,
|
||||
size.width(),
|
||||
size.height()),
|
||||
frame);
|
||||
@@ -1207,16 +1303,15 @@ void StickersListFooter::paintSetIcon(
|
||||
p,
|
||||
icon.megagroupUserpic,
|
||||
info.adjustedLeft + (_singleWidth - size) / 2,
|
||||
_iconsTop + (st::emojiFooterHeight - size) / 2,
|
||||
_iconsTop + (st().footer - size) / 2,
|
||||
width(),
|
||||
st::stickerGroupCategorySize);
|
||||
} else if (icon.setId == Data::Stickers::PremiumSetId) {
|
||||
validatePremiumIcon();
|
||||
const auto size = st::stickersPremium.size();
|
||||
p.drawImage(
|
||||
info.adjustedLeft + (_singleWidth - size.width()) / 2,
|
||||
_iconsTop + (st::emojiFooterHeight - size.height()) / 2,
|
||||
_premiumIcon);
|
||||
_iconsTop + (st().footer - size.height()) / 2,
|
||||
_premiumIcon.image());
|
||||
} else {
|
||||
using Section = Ui::Emoji::Section;
|
||||
const auto sectionIcon = [&](Section section, bool active) {
|
||||
@@ -1248,7 +1343,7 @@ void StickersListFooter::paintSetIcon(
|
||||
icon->paint(
|
||||
p,
|
||||
left + (_singleWidth - icon->width()) / 2,
|
||||
_iconsTop + (st::emojiFooterHeight - icon->height()) / 2,
|
||||
_iconsTop + (st().footer - icon->height()) / 2,
|
||||
width());
|
||||
};
|
||||
if (_icons[info.index].setId == AllEmojiSectionSetId()
|
||||
@@ -1259,7 +1354,7 @@ void StickersListFooter::paintSetIcon(
|
||||
left + skip,
|
||||
_iconsTop,
|
||||
info.width - 2 * skip,
|
||||
st::emojiFooterHeight,
|
||||
st().footer,
|
||||
Qt::IntersectClip);
|
||||
enumerateSubicons([&](const IconInfo &info) {
|
||||
if (info.visible) {
|
||||
@@ -1275,8 +1370,8 @@ void StickersListFooter::paintSetIcon(
|
||||
} else {
|
||||
paintOne(left, [&] {
|
||||
if (icon.setId == Data::Stickers::FeaturedSetId) {
|
||||
const auto session = &_controller->session();
|
||||
return session->data().stickers().featuredSetsUnreadCount()
|
||||
const auto &stickers = _session->data().stickers();
|
||||
return stickers.featuredSetsUnreadCount()
|
||||
? &st::stickersTrendingUnread
|
||||
: &st::stickersTrending;
|
||||
//} else if (setId == Stickers::FavedSetId) {
|
||||
@@ -1290,6 +1385,13 @@ void StickersListFooter::paintSetIcon(
|
||||
}());
|
||||
}
|
||||
}
|
||||
if (context.expanding) {
|
||||
if (icon.custom) {
|
||||
p.translate(-expandingShift);
|
||||
} else {
|
||||
p.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LocalStickersManager::LocalStickersManager(not_null<Main::Session*> session)
|
||||
|
||||
@@ -33,6 +33,10 @@ namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace style {
|
||||
struct EmojiPan;
|
||||
} // namespace style
|
||||
|
||||
namespace ChatHelpers {
|
||||
|
||||
enum class ValidateIconAnimations {
|
||||
@@ -75,15 +79,30 @@ struct StickerIcon {
|
||||
mutable rpl::lifetime lifetime;
|
||||
};
|
||||
|
||||
class GradientPremiumStar {
|
||||
public:
|
||||
GradientPremiumStar();
|
||||
|
||||
[[nodiscard]] QImage image() const;
|
||||
|
||||
private:
|
||||
void renderOnDemand() const;
|
||||
|
||||
mutable QImage _image;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
class StickersListFooter final : public TabbedSelector::InnerFooter {
|
||||
public:
|
||||
struct Descriptor {
|
||||
not_null<Window::SessionController*> controller;
|
||||
Window::GifPauseReason level = {};
|
||||
not_null<Main::Session*> session;
|
||||
Fn<bool()> paused;
|
||||
not_null<RpWidget*> parent;
|
||||
bool searchButtonVisible = false;
|
||||
bool settingsButtonVisible = false;
|
||||
bool barSelection = false;
|
||||
const style::EmojiPan *st = nullptr;
|
||||
};
|
||||
explicit StickersListFooter(Descriptor &&descriptor);
|
||||
|
||||
@@ -115,6 +134,12 @@ public:
|
||||
};
|
||||
[[nodiscard]] rpl::producer<SearchRequest> searchRequests() const;
|
||||
|
||||
void paintExpanding(
|
||||
Painter &p,
|
||||
QRect clip,
|
||||
float64 radius,
|
||||
RectPart origin);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
@@ -163,6 +188,12 @@ private:
|
||||
crl::time animationStart = 0;
|
||||
Ui::Animations::Basic animation;
|
||||
};
|
||||
struct ExpandingContext {
|
||||
QRect clip;
|
||||
float64 progress = 0.;
|
||||
int radius = 0;
|
||||
bool expanding = false;
|
||||
};
|
||||
|
||||
void enumerateVisibleIcons(Fn<void(const IconInfo &)> callback) const;
|
||||
void enumerateIcons(Fn<bool(const IconInfo &)> callback) const;
|
||||
@@ -193,17 +224,23 @@ private:
|
||||
void checkDragging(ScrollState &state);
|
||||
bool finishDragging(ScrollState &state);
|
||||
bool finishDragging();
|
||||
void paintStickerSettingsIcon(Painter &p) const;
|
||||
void paintSearchIcon(Painter &p) const;
|
||||
|
||||
void paint(Painter &p, const ExpandingContext &context) const;
|
||||
void paintStickerSettingsIcon(QPainter &p) const;
|
||||
void paintSearchIcon(QPainter &p) const;
|
||||
void paintSetIcon(
|
||||
Painter &p,
|
||||
const ExpandingContext &context,
|
||||
const IconInfo &info,
|
||||
crl::time now,
|
||||
bool paused) const;
|
||||
void paintSelectionBg(Painter &p) const;
|
||||
void paintSelectionBar(Painter &p) const;
|
||||
void paintLeftRightFading(Painter &p) const;
|
||||
void validatePremiumIcon() const;
|
||||
void paintSelectionBg(
|
||||
QPainter &p,
|
||||
const ExpandingContext &context) const;
|
||||
void paintSelectionBar(QPainter &p) const;
|
||||
void paintLeftRightFading(
|
||||
QPainter &p,
|
||||
const ExpandingContext &context) const;
|
||||
|
||||
void updateEmojiSectionWidth();
|
||||
void updateEmojiWidthCallback();
|
||||
@@ -215,8 +252,8 @@ private:
|
||||
|
||||
void clipCallback(Media::Clip::Notification notification, uint64 setId);
|
||||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
const Window::GifPauseReason _level = {};
|
||||
const not_null<Main::Session*> _session;
|
||||
const Fn<bool()> _paused;
|
||||
const bool _searchButtonVisible = false;
|
||||
const bool _settingsButtonVisible = false;
|
||||
|
||||
@@ -230,7 +267,7 @@ private:
|
||||
OverState _pressed = SpecialOver::None;
|
||||
|
||||
QPoint _iconsMousePos, _iconsMouseDown;
|
||||
mutable QImage _premiumIcon;
|
||||
GradientPremiumStar _premiumIcon;
|
||||
int _iconsLeft = 0;
|
||||
int _iconsRight = 0;
|
||||
int _iconsTop = 0;
|
||||
|
||||
@@ -164,10 +164,14 @@ StickersListWidget::StickersListWidget(
|
||||
not_null<Window::SessionController*> controller,
|
||||
Window::GifPauseReason level,
|
||||
bool masks)
|
||||
: Inner(parent, controller, level)
|
||||
, _api(&controller->session().mtp())
|
||||
, _localSetsManager(
|
||||
std::make_unique<LocalStickersManager>(&controller->session()))
|
||||
: Inner(
|
||||
parent,
|
||||
st::defaultEmojiPan,
|
||||
&controller->session(),
|
||||
Window::PausedIn(controller, level))
|
||||
, _controller(controller)
|
||||
, _api(&session().mtp())
|
||||
, _localSetsManager(std::make_unique<LocalStickersManager>(&session()))
|
||||
, _section(Section::Stickers)
|
||||
, _isMasks(masks)
|
||||
, _updateItemsTimer([=] { updateItems(); })
|
||||
@@ -176,12 +180,12 @@ StickersListWidget::StickersListWidget(
|
||||
st::windowBgRipple,
|
||||
st::windowBgOver,
|
||||
[=] { update(); }))
|
||||
, _megagroupSetAbout(st::columnMinimalWidthThird - st::emojiScroll.width - st::emojiPanHeaderLeft)
|
||||
, _megagroupSetAbout(st::columnMinimalWidthThird - st::emojiScroll.width - st().headerLeft)
|
||||
, _addText(tr::lng_stickers_featured_add(tr::now).toUpper())
|
||||
, _addWidth(st::stickersTrendingAdd.font->width(_addText))
|
||||
, _settings(this, tr::lng_stickers_you_have(tr::now))
|
||||
, _previewTimer([=] { showPreview(); })
|
||||
, _premiumMark(std::make_unique<StickerPremiumMark>(&controller->session()))
|
||||
, _premiumMark(std::make_unique<StickerPremiumMark>(&session()))
|
||||
, _searchRequestTimer([=] { sendSearchRequest(); }) {
|
||||
setMouseTracking(true);
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
@@ -228,7 +232,7 @@ StickersListWidget::StickersListWidget(
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
rpl::producer<TabbedSelector::FileChosen> StickersListWidget::chosen() const {
|
||||
rpl::producer<FileChosen> StickersListWidget::chosen() const {
|
||||
return _chosen.events();
|
||||
}
|
||||
|
||||
@@ -246,8 +250,8 @@ object_ptr<TabbedSelector::InnerFooter> StickersListWidget::createFooter() {
|
||||
|
||||
using FooterDescriptor = StickersListFooter::Descriptor;
|
||||
auto result = object_ptr<StickersListFooter>(FooterDescriptor{
|
||||
.controller = controller(),
|
||||
.level = level(),
|
||||
.session = &session(),
|
||||
.paused = pausedMethod(),
|
||||
.parent = this,
|
||||
.searchButtonVisible = !_isMasks,
|
||||
.settingsButtonVisible = true,
|
||||
@@ -263,8 +267,8 @@ object_ptr<TabbedSelector::InnerFooter> StickersListWidget::createFooter() {
|
||||
_footer->openSettingsRequests(
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto onlyFeatured = _footer->hasOnlyFeaturedSets();
|
||||
controller()->show(Box<StickersBox>(
|
||||
controller(),
|
||||
_controller->show(Box<StickersBox>(
|
||||
_controller,
|
||||
(onlyFeatured
|
||||
? StickersBox::Section::Featured
|
||||
: _isMasks
|
||||
@@ -346,7 +350,7 @@ void StickersListWidget::preloadMoreOfficial() {
|
||||
const auto &list = data.vsets().v;
|
||||
_officialOffset += list.size();
|
||||
for (int i = 0, l = list.size(); i != l; ++i) {
|
||||
const auto set = session().data().stickers().feedSetCovered(
|
||||
const auto set = session().data().stickers().feedSet(
|
||||
list[i]);
|
||||
if (set->stickers.empty() && set->covers.empty()) {
|
||||
continue;
|
||||
@@ -411,7 +415,7 @@ bool StickersListWidget::enumerateSections(Callback callback) const {
|
||||
const auto titleSkip = set.externalLayout
|
||||
? st::stickersTrendingHeader
|
||||
: setHasTitle(set)
|
||||
? st::emojiPanHeader
|
||||
? st().header
|
||||
: st::stickerPanPadding;
|
||||
info.rowsTop = info.top + titleSkip;
|
||||
if (set.externalLayout) {
|
||||
@@ -462,16 +466,16 @@ int StickersListWidget::countDesiredHeight(int newWidth) {
|
||||
if (newWidth <= st::stickerPanWidthMin) {
|
||||
return 0;
|
||||
}
|
||||
auto availableWidth = newWidth - (st::stickerPanPadding - st::roundRadiusSmall);
|
||||
auto availableWidth = newWidth - (st::stickerPanPadding - st().margin.left());
|
||||
auto columnCount = availableWidth / st::stickerPanWidthMin;
|
||||
auto singleWidth = availableWidth / columnCount;
|
||||
auto fullWidth = (st::roundRadiusSmall + newWidth + st::emojiScroll.width);
|
||||
auto fullWidth = (st().margin.left() + newWidth + st::emojiScroll.width);
|
||||
auto rowsRight = (fullWidth - columnCount * singleWidth) / 2;
|
||||
accumulate_max(rowsRight, st::emojiScroll.width);
|
||||
_rowsLeft = fullWidth
|
||||
- columnCount * singleWidth
|
||||
- rowsRight
|
||||
- st::roundRadiusSmall;
|
||||
- st().margin.left();
|
||||
_singleSize = QSize(singleWidth, singleWidth);
|
||||
setColumnCount(columnCount);
|
||||
|
||||
@@ -763,7 +767,7 @@ void StickersListWidget::searchResultsDone(
|
||||
}
|
||||
auto &d = result.c_messages_foundStickerSets();
|
||||
for (const auto &data : d.vsets().v) {
|
||||
const auto set = session().data().stickers().feedSetCovered(data);
|
||||
const auto set = session().data().stickers().feedSet(data);
|
||||
if (set->stickers.empty() && set->covers.empty()) {
|
||||
continue;
|
||||
}
|
||||
@@ -814,7 +818,7 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
|
||||
: &_selected);
|
||||
|
||||
const auto now = crl::now();
|
||||
const auto paused = controller()->isGifPausedAtLeastFor(level());
|
||||
const auto paused = this->paused();
|
||||
if (sets.empty() && _section == Section::Search) {
|
||||
paintEmptySearchResults(p);
|
||||
}
|
||||
@@ -831,7 +835,7 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
|
||||
? set.count
|
||||
: loadedCount;
|
||||
|
||||
auto widthForTitle = stickersRight() - (st::emojiPanHeaderLeft - st::roundRadiusSmall);
|
||||
auto widthForTitle = stickersRight() - (st().headerLeft - st().margin.left());
|
||||
if (featuredHasAddButton(info.section)) {
|
||||
auto add = featuredAddRect(info);
|
||||
auto selected = selectedButton ? (selectedButton->section == info.section) : false;
|
||||
@@ -867,7 +871,7 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
|
||||
}
|
||||
p.setFont(st::stickersTrendingHeaderFont);
|
||||
p.setPen(st::stickersTrendingHeaderFg);
|
||||
p.drawTextLeft(st::emojiPanHeaderLeft - st::roundRadiusSmall, info.top + st::stickersTrendingHeaderTop, width(), titleText, titleWidth);
|
||||
p.drawTextLeft(st().headerLeft - st().margin.left(), info.top + st::stickersTrendingHeaderTop, width(), titleText, titleWidth);
|
||||
|
||||
if (set.flags & SetFlag::Unread) {
|
||||
p.setPen(Qt::NoPen);
|
||||
@@ -875,14 +879,14 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
|
||||
|
||||
{
|
||||
PainterHighQualityEnabler hq(p);
|
||||
p.drawEllipse(style::rtlrect(st::emojiPanHeaderLeft - st::roundRadiusSmall + titleWidth + st::stickersFeaturedUnreadSkip, info.top + st::stickersTrendingHeaderTop + st::stickersFeaturedUnreadTop, st::stickersFeaturedUnreadSize, st::stickersFeaturedUnreadSize, width()));
|
||||
p.drawEllipse(style::rtlrect(st().headerLeft - st().margin.left() + titleWidth + st::stickersFeaturedUnreadSkip, info.top + st::stickersTrendingHeaderTop + st::stickersFeaturedUnreadTop, st::stickersFeaturedUnreadSize, st::stickersFeaturedUnreadSize, width()));
|
||||
}
|
||||
}
|
||||
|
||||
auto statusText = (count > 0) ? tr::lng_stickers_count(tr::now, lt_count, count) : tr::lng_contacts_loading(tr::now);
|
||||
p.setFont(st::stickersTrendingSubheaderFont);
|
||||
p.setPen(st::stickersTrendingSubheaderFg);
|
||||
p.drawTextLeft(st::emojiPanHeaderLeft - st::roundRadiusSmall, info.top + st::stickersTrendingSubheaderTop, width(), statusText);
|
||||
p.drawTextLeft(st().headerLeft - st().margin.left(), info.top + st::stickersTrendingSubheaderTop, width(), statusText);
|
||||
|
||||
if (info.rowsTop >= clip.y() + clip.height()) {
|
||||
return true;
|
||||
@@ -904,7 +908,7 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
|
||||
if (setHasTitle(set) && clip.top() < info.rowsTop) {
|
||||
auto titleText = set.title;
|
||||
auto titleWidth = st::stickersTrendingHeaderFont->width(titleText);
|
||||
auto widthForTitle = stickersRight() - (st::emojiPanHeaderLeft - st::roundRadiusSmall);
|
||||
auto widthForTitle = stickersRight() - (st().headerLeft - st().margin.left());
|
||||
if (hasRemoveButton(info.section)) {
|
||||
auto remove = removeButtonRect(info);
|
||||
auto selected = selectedButton ? (selectedButton->section == info.section) : false;
|
||||
@@ -924,7 +928,7 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
|
||||
}
|
||||
p.setFont(st::emojiPanHeaderFont);
|
||||
p.setPen(st::emojiPanHeaderFg);
|
||||
p.drawTextLeft(st::emojiPanHeaderLeft - st::roundRadiusSmall, info.top + st::emojiPanHeaderTop, width(), titleText, titleWidth);
|
||||
p.drawTextLeft(st().headerLeft - st().margin.left(), info.top + st().headerTop, width(), titleText, titleWidth);
|
||||
}
|
||||
if (clip.top() + clip.height() <= info.rowsTop) {
|
||||
return true;
|
||||
@@ -1054,7 +1058,7 @@ void StickersListWidget::paintEmptySearchResults(Painter &p) {
|
||||
}
|
||||
|
||||
int StickersListWidget::megagroupSetInfoLeft() const {
|
||||
return st::emojiPanHeaderLeft - st::roundRadiusSmall;
|
||||
return st().headerLeft - st().margin.left();
|
||||
}
|
||||
|
||||
void StickersListWidget::paintMegagroupEmptySet(Painter &p, int y, bool buttonSelected) {
|
||||
@@ -1440,7 +1444,7 @@ QRect StickersListWidget::removeButtonRect(const SectionInfo &info) const {
|
||||
auto buttonw = st::stickerPanRemoveSet.width;
|
||||
auto buttonh = st::stickerPanRemoveSet.height;
|
||||
auto buttonx = stickersRight() - buttonw;
|
||||
auto buttony = info.top + (st::emojiPanHeader - buttonh) / 2;
|
||||
auto buttony = info.top + (st().header - buttonh) / 2;
|
||||
return QRect(buttonx, buttony, buttonw, buttonh);
|
||||
}
|
||||
|
||||
@@ -1534,69 +1538,75 @@ QPoint StickersListWidget::buttonRippleTopLeft(int section) const {
|
||||
|
||||
void StickersListWidget::showStickerSetBox(not_null<DocumentData*> document) {
|
||||
if (document->sticker() && document->sticker()->set) {
|
||||
checkHideWithBox(StickerSetBox::Show(controller(), document));
|
||||
checkHideWithBox(StickerSetBox::Show(_controller, document));
|
||||
}
|
||||
}
|
||||
|
||||
void StickersListWidget::fillContextMenu(
|
||||
not_null<Ui::PopupMenu*> menu,
|
||||
base::unique_qptr<Ui::PopupMenu> StickersListWidget::fillContextMenu(
|
||||
SendMenu::Type type) {
|
||||
auto selected = _selected;
|
||||
auto &sets = shownSets();
|
||||
if (v::is_null(selected) || !v::is_null(_pressed)) {
|
||||
return;
|
||||
return nullptr;
|
||||
}
|
||||
if (auto sticker = std::get_if<OverSticker>(&selected)) {
|
||||
const auto section = sticker->section;
|
||||
const auto index = sticker->index;
|
||||
Assert(section >= 0 && section < sets.size());
|
||||
auto &set = sets[section];
|
||||
Assert(index >= 0 && index < set.stickers.size());
|
||||
const auto sticker = std::get_if<OverSticker>(&selected);
|
||||
if (!sticker) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto section = sticker->section;
|
||||
const auto index = sticker->index;
|
||||
Assert(section >= 0 && section < sets.size());
|
||||
auto &set = sets[section];
|
||||
Assert(index >= 0 && index < set.stickers.size());
|
||||
|
||||
const auto document = set.stickers[sticker->index].document;
|
||||
const auto send = [=](Api::SendOptions options) {
|
||||
_chosen.fire({
|
||||
.document = document,
|
||||
.options = options,
|
||||
.messageSendingFrom = options.scheduled
|
||||
? Ui::MessageSendingAnimationFrom()
|
||||
: messageSentAnimationInfo(section, index, document),
|
||||
});
|
||||
};
|
||||
SendMenu::FillSendMenu(
|
||||
menu,
|
||||
type,
|
||||
SendMenu::DefaultSilentCallback(send),
|
||||
SendMenu::DefaultScheduleCallback(this, type, send));
|
||||
auto menu = base::make_unique_q<Ui::PopupMenu>(
|
||||
this,
|
||||
st::popupMenuWithIcons);
|
||||
|
||||
const auto window = controller();
|
||||
const auto toggleFavedSticker = [=] {
|
||||
Api::ToggleFavedSticker(
|
||||
window,
|
||||
const auto document = set.stickers[sticker->index].document;
|
||||
const auto send = [=](Api::SendOptions options) {
|
||||
_chosen.fire({
|
||||
.document = document,
|
||||
.options = options,
|
||||
.messageSendingFrom = options.scheduled
|
||||
? Ui::MessageSendingAnimationFrom()
|
||||
: messageSentAnimationInfo(section, index, document),
|
||||
});
|
||||
};
|
||||
SendMenu::FillSendMenu(
|
||||
menu,
|
||||
type,
|
||||
SendMenu::DefaultSilentCallback(send),
|
||||
SendMenu::DefaultScheduleCallback(this, type, send));
|
||||
|
||||
const auto window = _controller;
|
||||
const auto toggleFavedSticker = [=] {
|
||||
Api::ToggleFavedSticker(
|
||||
window,
|
||||
document,
|
||||
Data::FileOriginStickerSet(Data::Stickers::FavedSetId, 0));
|
||||
};
|
||||
const auto isFaved = document->owner().stickers().isFaved(document);
|
||||
menu->addAction(
|
||||
(isFaved
|
||||
? tr::lng_faved_stickers_remove
|
||||
: tr::lng_faved_stickers_add)(tr::now),
|
||||
toggleFavedSticker,
|
||||
isFaved ? &st::menuIconUnfave : &st::menuIconFave);
|
||||
|
||||
menu->addAction(tr::lng_context_pack_info(tr::now), [=] {
|
||||
showStickerSetBox(document);
|
||||
}, &st::menuIconStickers);
|
||||
|
||||
if (const auto id = set.id; id == Data::Stickers::RecentSetId) {
|
||||
menu->addAction(tr::lng_recent_stickers_remove(tr::now), [=] {
|
||||
Api::ToggleRecentSticker(
|
||||
document,
|
||||
Data::FileOriginStickerSet(Data::Stickers::FavedSetId, 0));
|
||||
};
|
||||
const auto isFaved = document->owner().stickers().isFaved(document);
|
||||
menu->addAction(
|
||||
(isFaved
|
||||
? tr::lng_faved_stickers_remove
|
||||
: tr::lng_faved_stickers_add)(tr::now),
|
||||
toggleFavedSticker,
|
||||
isFaved ? &st::menuIconUnfave : &st::menuIconFave);
|
||||
|
||||
menu->addAction(tr::lng_context_pack_info(tr::now), [=] {
|
||||
showStickerSetBox(document);
|
||||
}, &st::menuIconStickers);
|
||||
|
||||
if (const auto id = set.id; id == Data::Stickers::RecentSetId) {
|
||||
menu->addAction(tr::lng_recent_stickers_remove(tr::now), [=] {
|
||||
Api::ToggleRecentSticker(
|
||||
document,
|
||||
Data::FileOriginStickerSet(id, 0),
|
||||
false);
|
||||
}, &st::menuIconDelete);
|
||||
}
|
||||
Data::FileOriginStickerSet(id, 0),
|
||||
false);
|
||||
}, &st::menuIconDelete);
|
||||
}
|
||||
return menu;
|
||||
}
|
||||
|
||||
Ui::MessageSendingAnimationFrom StickersListWidget::messageSentAnimationInfo(
|
||||
@@ -1674,7 +1684,7 @@ void StickersListWidget::mouseReleaseEvent(QMouseEvent *e) {
|
||||
removeSet(sets[button->section].id);
|
||||
}
|
||||
} else if (std::get_if<OverGroupAdd>(&pressed)) {
|
||||
controller()->show(Box<StickersBox>(controller(), _megagroupSet));
|
||||
_controller->show(Box<StickersBox>(_controller, _megagroupSet));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1732,9 +1742,9 @@ void StickersListWidget::removeFavedSticker(int section, int index) {
|
||||
clearSelection();
|
||||
const auto &sticker = _mySets[section].stickers[index];
|
||||
const auto document = sticker.document;
|
||||
session().data().stickers().setFaved(controller(), document, false);
|
||||
session().data().stickers().setFaved(_controller, document, false);
|
||||
Api::ToggleFavedSticker(
|
||||
controller(),
|
||||
_controller,
|
||||
document,
|
||||
Data::FileOriginStickerSet(Data::Stickers::FavedSetId, 0),
|
||||
false);
|
||||
@@ -2146,8 +2156,7 @@ void StickersListWidget::refreshRecentStickers(bool performResize) {
|
||||
}
|
||||
|
||||
void StickersListWidget::refreshPremiumStickers() {
|
||||
if (_isMasks
|
||||
|| controller()->session().settings().skipPremiumStickersSet()) {
|
||||
if (_isMasks || session().settings().skipPremiumStickersSet()) {
|
||||
return;
|
||||
}
|
||||
clearSelection();
|
||||
@@ -2323,7 +2332,7 @@ std::vector<StickerIcon> StickersListWidget::fillIcons() {
|
||||
Assert(set != nullptr);
|
||||
const auto s = _mySets[i].thumbnailDocument;
|
||||
const auto availw = st::stickerIconWidth - 2 * st::stickerIconPadding;
|
||||
const auto availh = st::emojiFooterHeight - 2 * st::stickerIconPadding;
|
||||
const auto availh = st().footer - 2 * st::stickerIconPadding;
|
||||
const auto size = set->hasThumbnail()
|
||||
? QSize(
|
||||
set->thumbnailLocation().width(),
|
||||
@@ -2457,7 +2466,7 @@ void StickersListWidget::setSelected(OverState newSelected) {
|
||||
const auto &set = sets[sticker->section];
|
||||
Assert(sticker->index >= 0 && sticker->index < set.stickers.size());
|
||||
const auto document = set.stickers[sticker->index].document;
|
||||
controller()->widget()->showMediaPreview(
|
||||
_controller->widget()->showMediaPreview(
|
||||
document->stickerSetOrigin(),
|
||||
document);
|
||||
}
|
||||
@@ -2472,7 +2481,7 @@ void StickersListWidget::showPreview() {
|
||||
const auto &set = sets[sticker->section];
|
||||
Assert(sticker->index >= 0 && sticker->index < set.stickers.size());
|
||||
const auto document = set.stickers[sticker->index].document;
|
||||
controller()->widget()->showMediaPreview(
|
||||
_controller->widget()->showMediaPreview(
|
||||
document->stickerSetOrigin(),
|
||||
document);
|
||||
_previewShown = true;
|
||||
@@ -2586,8 +2595,8 @@ void StickersListWidget::beforeHiding() {
|
||||
void StickersListWidget::displaySet(uint64 setId) {
|
||||
if (setId == Data::Stickers::MegagroupSetId) {
|
||||
if (_megagroupSet->canEditStickers()) {
|
||||
checkHideWithBox(controller()->show(
|
||||
Box<StickersBox>(controller(), _megagroupSet),
|
||||
checkHideWithBox(_controller->show(
|
||||
Box<StickersBox>(_controller, _megagroupSet),
|
||||
Ui::LayerOption::KeepOther).data());
|
||||
return;
|
||||
} else if (_megagroupSet->mgInfo->stickerSet.id) {
|
||||
@@ -2599,8 +2608,8 @@ void StickersListWidget::displaySet(uint64 setId) {
|
||||
const auto &sets = session().data().stickers().sets();
|
||||
auto it = sets.find(setId);
|
||||
if (it != sets.cend()) {
|
||||
checkHideWithBox(controller()->show(
|
||||
Box<StickerSetBox>(controller(), it->second.get()),
|
||||
checkHideWithBox(_controller->show(
|
||||
Box<StickerSetBox>(_controller, it->second.get()),
|
||||
Ui::LayerOption::KeepOther).data());
|
||||
}
|
||||
}
|
||||
@@ -2612,7 +2621,10 @@ void StickersListWidget::removeMegagroupSet(bool locally) {
|
||||
refreshStickers();
|
||||
return;
|
||||
}
|
||||
checkHideWithBox(controller()->show(Ui::MakeConfirmBox({
|
||||
const auto cancelled = [](Fn<void()> &&close) {
|
||||
close();
|
||||
};
|
||||
checkHideWithBox(_controller->show(Ui::MakeConfirmBox({
|
||||
.text = tr::lng_stickers_remove_group_set(),
|
||||
.confirmed = crl::guard(this, [this, group = _megagroupSet](
|
||||
Fn<void()> &&close) {
|
||||
@@ -2623,9 +2635,7 @@ void StickersListWidget::removeMegagroupSet(bool locally) {
|
||||
}
|
||||
close();
|
||||
}),
|
||||
.cancelled = [](Fn<void()> &&close) {
|
||||
close();
|
||||
},
|
||||
.cancelled = cancelled,
|
||||
})));
|
||||
}
|
||||
|
||||
@@ -2638,7 +2648,7 @@ void StickersListWidget::removeSet(uint64 setId) {
|
||||
|| !_megagroupSet->canEditStickers();
|
||||
removeMegagroupSet(removeLocally);
|
||||
} else if (auto box = MakeConfirmRemoveSetBox(&session(), setId)) {
|
||||
checkHideWithBox(controller()->show(
|
||||
checkHideWithBox(_controller->show(
|
||||
std::move(box),
|
||||
Ui::LayerOption::KeepOther));
|
||||
}
|
||||
@@ -2723,13 +2733,6 @@ object_ptr<Ui::BoxContent> MakeConfirmRemoveSetBox(
|
||||
}
|
||||
set->flags &= ~SetFlag::Installed;
|
||||
set->installDate = TimeId(0);
|
||||
//
|
||||
// Set can be in search results.
|
||||
//
|
||||
//if (!(set->flags & SetFlag::Featured)
|
||||
// && !(set->flags & SetFlag::Special)) {
|
||||
// sets.erase(it);
|
||||
//}
|
||||
auto &orderRef = (set->type() == Data::StickersType::Emoji)
|
||||
? session->data().stickers().emojiSetsOrderRef()
|
||||
: (set->type() == Data::StickersType::Masks)
|
||||
|
||||
@@ -46,6 +46,10 @@ class ReaderPointer;
|
||||
enum class Notification;
|
||||
} // namespace Media::Clip
|
||||
|
||||
namespace style {
|
||||
struct EmojiPan;
|
||||
} // namespace style
|
||||
|
||||
namespace ChatHelpers {
|
||||
|
||||
struct StickerIcon;
|
||||
@@ -61,7 +65,7 @@ public:
|
||||
Window::GifPauseReason level,
|
||||
bool masks = false);
|
||||
|
||||
rpl::producer<TabbedSelector::FileChosen> chosen() const;
|
||||
rpl::producer<FileChosen> chosen() const;
|
||||
rpl::producer<> scrollUpdated() const;
|
||||
rpl::producer<TabbedSelector::Action> choosingUpdated() const;
|
||||
|
||||
@@ -87,8 +91,7 @@ public:
|
||||
|
||||
std::shared_ptr<Lottie::FrameRenderer> getLottieRenderer();
|
||||
|
||||
void fillContextMenu(
|
||||
not_null<Ui::PopupMenu*> menu,
|
||||
base::unique_qptr<Ui::PopupMenu> fillContextMenu(
|
||||
SendMenu::Type type) override;
|
||||
|
||||
bool mySetsEmpty() const;
|
||||
@@ -320,14 +323,13 @@ private:
|
||||
void addSearchRow(not_null<Data::StickersSet*> set);
|
||||
|
||||
void showPreview();
|
||||
void validatePremiumLock(Set &set, int index, const QImage &frame);
|
||||
void validatePremiumStar();
|
||||
|
||||
Ui::MessageSendingAnimationFrom messageSentAnimationInfo(
|
||||
int section,
|
||||
int index,
|
||||
not_null<DocumentData*> document);
|
||||
|
||||
not_null<Window::SessionController*> _controller;
|
||||
MTP::Sender _api;
|
||||
std::unique_ptr<LocalStickersManager> _localSetsManager;
|
||||
ChannelData *_megagroupSet = nullptr;
|
||||
@@ -387,7 +389,7 @@ private:
|
||||
QString _searchQuery, _searchNextQuery;
|
||||
mtpRequestId _searchRequestId = 0;
|
||||
|
||||
rpl::event_stream<TabbedSelector::FileChosen> _chosen;
|
||||
rpl::event_stream<FileChosen> _chosen;
|
||||
rpl::event_stream<> _scrollUpdated;
|
||||
rpl::event_stream<TabbedSelector::Action> _choosingUpdated;
|
||||
|
||||
|
||||
@@ -132,7 +132,19 @@ void TabbedPanel::moveBottomRight(int bottom, int right) {
|
||||
_right = right;
|
||||
// If the panel is already shown, update the position.
|
||||
if (!isHidden() && isNew) {
|
||||
moveByBottom();
|
||||
moveHorizontally();
|
||||
} else {
|
||||
updateContentHeight();
|
||||
}
|
||||
}
|
||||
|
||||
void TabbedPanel::moveTopRight(int top, int right) {
|
||||
const auto isNew = (_top != top || _right != right);
|
||||
_top = top;
|
||||
_right = right;
|
||||
// If the panel is already shown, update the position.
|
||||
if (!isHidden() && isNew) {
|
||||
moveHorizontally();
|
||||
} else {
|
||||
updateContentHeight();
|
||||
}
|
||||
@@ -148,16 +160,26 @@ void TabbedPanel::setDesiredHeightValues(
|
||||
updateContentHeight();
|
||||
}
|
||||
|
||||
void TabbedPanel::setDropDown(bool dropDown) {
|
||||
selector()->setDropDown(dropDown);
|
||||
_dropDown = dropDown;
|
||||
}
|
||||
|
||||
void TabbedPanel::updateContentHeight() {
|
||||
auto addedHeight = innerPadding().top() + innerPadding().bottom();
|
||||
auto marginsHeight = _selector->marginTop() + _selector->marginBottom();
|
||||
auto availableHeight = _bottom - marginsHeight;
|
||||
auto wantedContentHeight = qRound(_heightRatio * availableHeight) - addedHeight;
|
||||
auto availableHeight = _dropDown
|
||||
? (parentWidget()->height() - _top - marginsHeight)
|
||||
: (_bottom - marginsHeight);
|
||||
auto wantedContentHeight = qRound(_heightRatio * availableHeight)
|
||||
- addedHeight;
|
||||
auto contentHeight = marginsHeight + std::clamp(
|
||||
wantedContentHeight,
|
||||
_minContentHeight,
|
||||
_maxContentHeight);
|
||||
auto resultTop = _bottom - addedHeight - contentHeight;
|
||||
auto resultTop = _dropDown
|
||||
? _top
|
||||
: (_bottom - addedHeight - contentHeight);
|
||||
if (contentHeight == _contentHeight) {
|
||||
move(x(), resultTop);
|
||||
return;
|
||||
@@ -204,8 +226,12 @@ void TabbedPanel::paintEvent(QPaintEvent *e) {
|
||||
}
|
||||
}
|
||||
|
||||
void TabbedPanel::moveByBottom() {
|
||||
const auto right = std::max(parentWidget()->width() - _right, 0);
|
||||
void TabbedPanel::moveHorizontally() {
|
||||
const auto padding = innerPadding();
|
||||
const auto width = innerRect().width() + padding.left() + padding.right();
|
||||
const auto right = std::max(
|
||||
parentWidget()->width() - std::max(_right, width),
|
||||
0);
|
||||
moveToRight(right, y());
|
||||
updateContentHeight();
|
||||
}
|
||||
@@ -318,7 +344,7 @@ void TabbedPanel::startShowAnimation() {
|
||||
if (!_a_show.animating()) {
|
||||
auto image = grabForAnimation();
|
||||
|
||||
_showAnimation = std::make_unique<Ui::PanelAnimation>(st::emojiPanAnimation, Ui::PanelAnimation::Origin::BottomRight);
|
||||
_showAnimation = std::make_unique<Ui::PanelAnimation>(st::emojiPanAnimation, _dropDown ? Ui::PanelAnimation::Origin::TopRight : Ui::PanelAnimation::Origin::BottomRight);
|
||||
auto inner = rect().marginsRemoved(st::emojiPanMargins);
|
||||
_showAnimation->setFinalImage(std::move(image), QRect(inner.topLeft() * cIntRetinaFactor(), inner.size() * cIntRetinaFactor()));
|
||||
_showAnimation->setCornerMasks(Images::CornersMask(ImageRoundRadius::Small));
|
||||
@@ -402,7 +428,7 @@ void TabbedPanel::showStarted() {
|
||||
}
|
||||
if (isHidden()) {
|
||||
_selector->showStarted();
|
||||
moveByBottom();
|
||||
moveHorizontally();
|
||||
raise();
|
||||
show();
|
||||
startShowAnimation();
|
||||
@@ -424,7 +450,7 @@ bool TabbedPanel::eventFilter(QObject *obj, QEvent *e) {
|
||||
|
||||
void TabbedPanel::showFromSelector() {
|
||||
if (isHidden()) {
|
||||
moveByBottom();
|
||||
moveHorizontally();
|
||||
startShowAnimation();
|
||||
show();
|
||||
}
|
||||
|
||||
@@ -41,10 +41,12 @@ public:
|
||||
[[nodiscard]] not_null<TabbedSelector*> selector() const;
|
||||
|
||||
void moveBottomRight(int bottom, int right);
|
||||
void moveTopRight(int top, int right);
|
||||
void setDesiredHeightValues(
|
||||
float64 ratio,
|
||||
int minHeight,
|
||||
int maxHeight);
|
||||
void setDropDown(bool dropDown);
|
||||
|
||||
void hideFast();
|
||||
bool hiding() const {
|
||||
@@ -76,7 +78,7 @@ private:
|
||||
TabbedSelector *nonOwnedSelector);
|
||||
|
||||
void hideByTimerOrLeave();
|
||||
void moveByBottom();
|
||||
void moveHorizontally();
|
||||
void showFromSelector();
|
||||
|
||||
style::margins innerPadding() const;
|
||||
@@ -103,6 +105,7 @@ private:
|
||||
|
||||
int _contentMaxHeight = 0;
|
||||
int _contentHeight = 0;
|
||||
int _top = 0;
|
||||
int _bottom = 0;
|
||||
int _right = 0;
|
||||
float64 _heightRatio = 1.;
|
||||
@@ -113,6 +116,7 @@ private:
|
||||
Ui::Animations::Simple _a_show;
|
||||
|
||||
bool _shouldFinishHide = false;
|
||||
bool _dropDown = false;
|
||||
|
||||
bool _hiding = false;
|
||||
bool _hideAfterSlide = false;
|
||||
|
||||
@@ -107,7 +107,7 @@ void TabbedSelector::SlideAnimation::setFinalImages(Direction direction, QImage
|
||||
_painterInnerBottom = _innerBottom / cIntRetinaFactor();
|
||||
_painterInnerWidth = _innerWidth / cIntRetinaFactor();
|
||||
_painterInnerHeight = _innerHeight / cIntRetinaFactor();
|
||||
_painterCategoriesTop = _painterInnerBottom - st::emojiFooterHeight;
|
||||
_painterCategoriesTop = _painterInnerBottom - st::defaultEmojiPan.footer;
|
||||
|
||||
_wasSectionIcons = wasSectionIcons;
|
||||
}
|
||||
@@ -294,6 +294,7 @@ TabbedSelector::TabbedSelector(
|
||||
Window::GifPauseReason level,
|
||||
Mode mode)
|
||||
: RpWidget(parent)
|
||||
, _st(st::defaultEmojiPan)
|
||||
, _controller(controller)
|
||||
, _level(level)
|
||||
, _mode(mode)
|
||||
@@ -439,7 +440,14 @@ TabbedSelector::Tab TabbedSelector::createTab(SelectorTab type, int index) {
|
||||
auto createWidget = [&]() -> object_ptr<Inner> {
|
||||
switch (type) {
|
||||
case SelectorTab::Emoji:
|
||||
return object_ptr<EmojiListWidget>(this, _controller, _level);
|
||||
using EmojiMode = EmojiListWidget::Mode;
|
||||
return object_ptr<EmojiListWidget>(
|
||||
this,
|
||||
_controller,
|
||||
_level,
|
||||
(_mode == Mode::EmojiStatus
|
||||
? EmojiMode::EmojiStatus
|
||||
: EmojiMode::Full));
|
||||
case SelectorTab::Stickers:
|
||||
return object_ptr<StickersListWidget>(this, _controller, _level);
|
||||
case SelectorTab::Gifs:
|
||||
@@ -484,21 +492,16 @@ bool TabbedSelector::hasMasksTab() const {
|
||||
return _hasMasksTab;
|
||||
}
|
||||
|
||||
rpl::producer<EmojiPtr> TabbedSelector::emojiChosen() const {
|
||||
rpl::producer<EmojiChosen> TabbedSelector::emojiChosen() const {
|
||||
return emoji()->chosen();
|
||||
}
|
||||
|
||||
auto TabbedSelector::customEmojiChosen() const -> rpl::producer<FileChosen> {
|
||||
rpl::producer<FileChosen> TabbedSelector::customEmojiChosen() const {
|
||||
return emoji()->customChosen();
|
||||
}
|
||||
|
||||
auto TabbedSelector::premiumEmojiChosen() const
|
||||
-> rpl::producer<not_null<DocumentData*>> {
|
||||
return emoji()->premiumChosen();
|
||||
}
|
||||
|
||||
auto TabbedSelector::fileChosen() const -> rpl::producer<FileChosen> {
|
||||
auto never = rpl::never<TabbedSelector::FileChosen>(
|
||||
rpl::producer<FileChosen> TabbedSelector::fileChosen() const {
|
||||
auto never = rpl::never<FileChosen>(
|
||||
) | rpl::type_erased();
|
||||
return rpl::merge(
|
||||
hasStickersTab() ? stickers()->chosen() : never,
|
||||
@@ -506,8 +509,7 @@ auto TabbedSelector::fileChosen() const -> rpl::producer<FileChosen> {
|
||||
hasMasksTab() ? masks()->chosen() : never);
|
||||
}
|
||||
|
||||
auto TabbedSelector::photoChosen() const
|
||||
-> rpl::producer<TabbedSelector::PhotoChosen>{
|
||||
rpl::producer<PhotoChosen> TabbedSelector::photoChosen() const {
|
||||
return hasGifsTab() ? gifs()->photoChosen() : nullptr;
|
||||
}
|
||||
|
||||
@@ -559,45 +561,50 @@ void TabbedSelector::resizeEvent(QResizeEvent *e) {
|
||||
_tabsSlider->width(),
|
||||
st::lineWidth);
|
||||
}
|
||||
updateScrollGeometry(e->oldSize());
|
||||
updateRestrictedLabelGeometry();
|
||||
updateFooterGeometry();
|
||||
update();
|
||||
}
|
||||
|
||||
void TabbedSelector::updateScrollGeometry(QSize oldSize) {
|
||||
auto scrollWidth = width() - st::roundRadiusSmall;
|
||||
auto scrollHeight = height() - scrollTop() - marginBottom();
|
||||
auto scrollHeight = height() - scrollTop() - scrollBottom();
|
||||
auto inner = currentTab()->widget();
|
||||
auto innerWidth = scrollWidth - st::emojiScroll.width;
|
||||
auto updateScrollGeometry = [&] {
|
||||
auto setScrollGeometry = [&] {
|
||||
_scroll->setGeometryToLeft(
|
||||
st::roundRadiusSmall,
|
||||
scrollTop(),
|
||||
scrollWidth,
|
||||
scrollHeight);
|
||||
};
|
||||
auto updateInnerGeometry = [&] {
|
||||
auto setInnerGeometry = [&] {
|
||||
auto scrollTop = _scroll->scrollTop();
|
||||
auto scrollBottom = scrollTop + scrollHeight;
|
||||
inner->setMinimalHeight(innerWidth, scrollHeight);
|
||||
inner->setVisibleTopBottom(scrollTop, scrollBottom);
|
||||
};
|
||||
if (e->oldSize().height() > height()) {
|
||||
updateScrollGeometry();
|
||||
updateInnerGeometry();
|
||||
if (oldSize.height() > height()) {
|
||||
setScrollGeometry();
|
||||
setInnerGeometry();
|
||||
} else {
|
||||
updateInnerGeometry();
|
||||
updateScrollGeometry();
|
||||
setInnerGeometry();
|
||||
setScrollGeometry();
|
||||
}
|
||||
_bottomShadow->setGeometry(
|
||||
0,
|
||||
_scroll->y() + _scroll->height() - st::lineWidth,
|
||||
width(),
|
||||
st::lineWidth);
|
||||
updateRestrictedLabelGeometry();
|
||||
}
|
||||
|
||||
_footerTop = height() - st::emojiFooterHeight;
|
||||
void TabbedSelector::updateFooterGeometry() {
|
||||
_footerTop = _dropDown ? 0 : (height() - _st.footer);
|
||||
for (auto &tab : _tabs) {
|
||||
tab.footer()->resizeToWidth(width());
|
||||
tab.footer()->moveToLeft(0, _footerTop);
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void TabbedSelector::updateRestrictedLabelGeometry() {
|
||||
@@ -630,21 +637,7 @@ void TabbedSelector::paintEvent(QPaintEvent *e) {
|
||||
|
||||
void TabbedSelector::paintSlideFrame(Painter &p) {
|
||||
if (_roundRadius > 0) {
|
||||
const auto topPart = QRect(
|
||||
0,
|
||||
0,
|
||||
width(),
|
||||
_tabsSlider
|
||||
? _tabsSlider->height() + _roundRadius
|
||||
: 3 * _roundRadius);
|
||||
Ui::FillRoundRect(
|
||||
p,
|
||||
topPart,
|
||||
st::emojiPanBg,
|
||||
ImageRoundRadius::Small,
|
||||
tabbed()
|
||||
? RectPart::FullTop | RectPart::NoTopBottom
|
||||
: RectPart::FullTop);
|
||||
paintBgRoundedPart(p);
|
||||
} else if (_tabsSlider) {
|
||||
p.fillRect(0, 0, width(), _tabsSlider->height(), st::emojiPanBg);
|
||||
}
|
||||
@@ -652,43 +645,53 @@ void TabbedSelector::paintSlideFrame(Painter &p) {
|
||||
_slideAnimation->paintFrame(p, slideDt, 1.);
|
||||
}
|
||||
|
||||
void TabbedSelector::paintBgRoundedPart(Painter &p) {
|
||||
const auto threeRadius = 3 * _roundRadius;
|
||||
const auto topOrBottomPart = _dropDown
|
||||
? QRect(0, height() - threeRadius, width(), threeRadius)
|
||||
: QRect(
|
||||
0,
|
||||
0,
|
||||
width(),
|
||||
(_tabsSlider
|
||||
? _tabsSlider->height() + _roundRadius
|
||||
: threeRadius));
|
||||
Ui::FillRoundRect(
|
||||
p,
|
||||
topOrBottomPart,
|
||||
st::emojiPanBg,
|
||||
ImageRoundRadius::Small,
|
||||
(_dropDown
|
||||
? RectPart::FullBottom
|
||||
: tabbed()
|
||||
? (RectPart::FullTop | RectPart::NoTopBottom)
|
||||
: RectPart::FullTop));
|
||||
}
|
||||
|
||||
void TabbedSelector::paintContent(Painter &p) {
|
||||
auto &bottomBg = hasSectionIcons()
|
||||
auto &footerBg = hasSectionIcons()
|
||||
? st::emojiPanCategories
|
||||
: st::emojiPanBg;
|
||||
if (_roundRadius > 0) {
|
||||
const auto topPart = QRect(
|
||||
0,
|
||||
0,
|
||||
width(),
|
||||
_tabsSlider
|
||||
? _tabsSlider->height() + _roundRadius
|
||||
: 3 * _roundRadius);
|
||||
Ui::FillRoundRect(
|
||||
p,
|
||||
topPart,
|
||||
st::emojiPanBg,
|
||||
ImageRoundRadius::Small,
|
||||
tabbed()
|
||||
? RectPart::FullTop | RectPart::NoTopBottom
|
||||
: RectPart::FullTop);
|
||||
paintBgRoundedPart(p);
|
||||
|
||||
const auto bottomPart = QRect(
|
||||
const auto footerPart = QRect(
|
||||
0,
|
||||
_footerTop - _roundRadius,
|
||||
_footerTop - (_dropDown ? 0 : _roundRadius),
|
||||
width(),
|
||||
st::emojiFooterHeight + _roundRadius);
|
||||
_st.footer + _roundRadius);
|
||||
Ui::FillRoundRect(
|
||||
p,
|
||||
bottomPart,
|
||||
bottomBg,
|
||||
footerPart,
|
||||
footerBg,
|
||||
ImageRoundRadius::Small,
|
||||
RectPart::NoTopBottom | RectPart::FullBottom);
|
||||
(RectPart::NoTopBottom
|
||||
| (_dropDown ? RectPart::FullTop : RectPart::FullBottom)));
|
||||
} else {
|
||||
if (_tabsSlider) {
|
||||
p.fillRect(0, 0, width(), _tabsSlider->height(), st::emojiPanBg);
|
||||
}
|
||||
p.fillRect(0, _footerTop, width(), st::emojiFooterHeight, bottomBg);
|
||||
p.fillRect(0, _footerTop, width(), _st.footer, footerBg);
|
||||
}
|
||||
|
||||
auto sidesTop = marginTop();
|
||||
@@ -710,17 +713,23 @@ void TabbedSelector::paintContent(Painter &p) {
|
||||
}
|
||||
|
||||
int TabbedSelector::marginTop() const {
|
||||
return _tabsSlider
|
||||
return _dropDown
|
||||
? _st.footer
|
||||
: _tabsSlider
|
||||
? (_tabsSlider->height() - st::lineWidth)
|
||||
: _roundRadius;
|
||||
}
|
||||
|
||||
int TabbedSelector::scrollTop() const {
|
||||
return tabbed() ? marginTop() : 0;
|
||||
return tabbed() ? marginTop() : _dropDown ? _st.footer : 0;
|
||||
}
|
||||
|
||||
int TabbedSelector::marginBottom() const {
|
||||
return st::emojiFooterHeight;
|
||||
return _dropDown ? _roundRadius : _st.footer;
|
||||
}
|
||||
|
||||
int TabbedSelector::scrollBottom() const {
|
||||
return _dropDown ? 0 : marginBottom();
|
||||
}
|
||||
|
||||
void TabbedSelector::refreshStickers() {
|
||||
@@ -850,14 +859,14 @@ void TabbedSelector::setCurrentPeer(PeerData *peer) {
|
||||
peer && Data::AllowEmojiWithoutPremium(peer));
|
||||
}
|
||||
|
||||
void TabbedSelector::showPromoForPremiumEmoji() {
|
||||
premiumEmojiChosen(
|
||||
) | rpl::start_with_next([=] {
|
||||
ShowPremiumPreviewBox(
|
||||
_controller,
|
||||
PremiumPreview::AnimatedEmoji,
|
||||
{});
|
||||
}, lifetime());
|
||||
void TabbedSelector::provideRecentEmoji(
|
||||
const std::vector<DocumentId> &customRecentList) {
|
||||
for (const auto &tab : _tabs) {
|
||||
if (tab.type() == SelectorTab::Emoji) {
|
||||
const auto emoji = static_cast<EmojiListWidget*>(tab.widget());
|
||||
emoji->provideRecent(customRecentList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TabbedSelector::checkRestrictedPeer() {
|
||||
@@ -1123,16 +1132,21 @@ void TabbedSelector::scrollToY(int y) {
|
||||
}
|
||||
|
||||
void TabbedSelector::showMenuWithType(SendMenu::Type type) {
|
||||
_menu = base::make_unique_q<Ui::PopupMenu>(
|
||||
this,
|
||||
st::popupMenuWithIcons);
|
||||
currentTab()->widget()->fillContextMenu(_menu, type);
|
||||
|
||||
if (!_menu->empty()) {
|
||||
_menu = currentTab()->widget()->fillContextMenu(type);
|
||||
if (_menu && !_menu->empty()) {
|
||||
_menu->popup(QCursor::pos());
|
||||
}
|
||||
}
|
||||
|
||||
void TabbedSelector::setDropDown(bool dropDown) {
|
||||
if (_dropDown == dropDown) {
|
||||
return;
|
||||
}
|
||||
_dropDown = dropDown;
|
||||
updateFooterGeometry();
|
||||
updateScrollGeometry(size());
|
||||
}
|
||||
|
||||
rpl::producer<> TabbedSelector::contextMenuRequested() const {
|
||||
return events(
|
||||
) | rpl::filter([=](not_null<QEvent*> e) {
|
||||
@@ -1178,13 +1192,22 @@ TabbedSelector::Inner::Inner(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
Window::GifPauseReason level)
|
||||
: RpWidget(parent)
|
||||
, _controller(controller)
|
||||
, _level(level) {
|
||||
: Inner(
|
||||
parent,
|
||||
st::defaultEmojiPan,
|
||||
&controller->session(),
|
||||
Window::PausedIn(controller, level)) {
|
||||
}
|
||||
|
||||
Main::Session &TabbedSelector::Inner::session() const {
|
||||
return controller()->session();
|
||||
TabbedSelector::Inner::Inner(
|
||||
QWidget *parent,
|
||||
const style::EmojiPan &st,
|
||||
not_null<Main::Session*> session,
|
||||
Fn<bool()> paused)
|
||||
: RpWidget(parent)
|
||||
, _st(st)
|
||||
, _session(session)
|
||||
, _paused(paused) {
|
||||
}
|
||||
|
||||
rpl::producer<int> TabbedSelector::Inner::scrollToRequests() const {
|
||||
@@ -1245,7 +1268,11 @@ int TabbedSelector::Inner::resizeGetHeight(int newWidth) {
|
||||
int TabbedSelector::Inner::minimalHeight() const {
|
||||
return (_minimalHeight > 0)
|
||||
? _minimalHeight
|
||||
: (st::emojiPanMaxHeight - st::emojiFooterHeight);
|
||||
: defaultMinimalHeight();
|
||||
}
|
||||
|
||||
int TabbedSelector::Inner::defaultMinimalHeight() const {
|
||||
return st::emojiPanMaxHeight - _st.footer;
|
||||
}
|
||||
|
||||
void TabbedSelector::Inner::hideFinished() {
|
||||
@@ -1263,9 +1290,16 @@ void TabbedSelector::Inner::panelHideFinished() {
|
||||
}
|
||||
}
|
||||
|
||||
TabbedSelector::InnerFooter::InnerFooter(QWidget *parent)
|
||||
: RpWidget(parent) {
|
||||
resize(st::emojiPanWidth, st::emojiFooterHeight);
|
||||
TabbedSelector::InnerFooter::InnerFooter(
|
||||
QWidget *parent,
|
||||
const style::EmojiPan &st)
|
||||
: RpWidget(parent)
|
||||
, _st(st) {
|
||||
resize(st::emojiPanWidth, _st.footer);
|
||||
}
|
||||
|
||||
const style::EmojiPan &TabbedSelector::InnerFooter::st() const {
|
||||
return _st;
|
||||
}
|
||||
|
||||
} // namespace ChatHelpers
|
||||
|
||||
@@ -41,8 +41,16 @@ namespace SendMenu {
|
||||
enum class Type;
|
||||
} // namespace SendMenu
|
||||
|
||||
namespace style {
|
||||
struct EmojiPan;
|
||||
} // namespace style
|
||||
|
||||
namespace ChatHelpers {
|
||||
|
||||
class EmojiListWidget;
|
||||
class StickersListWidget;
|
||||
class GifsListWidget;
|
||||
|
||||
enum class SelectorTab {
|
||||
Emoji,
|
||||
Stickers,
|
||||
@@ -50,26 +58,32 @@ enum class SelectorTab {
|
||||
Masks,
|
||||
};
|
||||
|
||||
class EmojiListWidget;
|
||||
class StickersListWidget;
|
||||
class GifsListWidget;
|
||||
struct FileChosen {
|
||||
not_null<DocumentData*> document;
|
||||
Api::SendOptions options;
|
||||
Ui::MessageSendingAnimationFrom messageSendingFrom;
|
||||
};
|
||||
|
||||
struct PhotoChosen {
|
||||
not_null<PhotoData*> photo;
|
||||
Api::SendOptions options;
|
||||
};
|
||||
|
||||
struct EmojiChosen {
|
||||
EmojiPtr emoji;
|
||||
Ui::MessageSendingAnimationFrom messageSendingFrom;
|
||||
};
|
||||
|
||||
using InlineChosen = InlineBots::ResultSelected;
|
||||
|
||||
class TabbedSelector : public Ui::RpWidget {
|
||||
public:
|
||||
struct FileChosen {
|
||||
not_null<DocumentData*> document;
|
||||
Api::SendOptions options;
|
||||
Ui::MessageSendingAnimationFrom messageSendingFrom;
|
||||
};
|
||||
struct PhotoChosen {
|
||||
not_null<PhotoData*> photo;
|
||||
Api::SendOptions options;
|
||||
};
|
||||
using InlineChosen = InlineBots::ResultSelected;
|
||||
static constexpr auto kPickCustomTimeId = -1;
|
||||
enum class Mode {
|
||||
Full,
|
||||
EmojiOnly,
|
||||
MediaEditor,
|
||||
EmojiStatus,
|
||||
};
|
||||
enum class Action {
|
||||
Update,
|
||||
@@ -86,9 +100,8 @@ public:
|
||||
Main::Session &session() const;
|
||||
Window::GifPauseReason level() const;
|
||||
|
||||
rpl::producer<EmojiPtr> emojiChosen() const;
|
||||
rpl::producer<EmojiChosen> emojiChosen() const;
|
||||
rpl::producer<FileChosen> customEmojiChosen() const;
|
||||
rpl::producer<not_null<DocumentData*>> premiumEmojiChosen() const;
|
||||
rpl::producer<FileChosen> fileChosen() const;
|
||||
rpl::producer<PhotoChosen> photoChosen() const;
|
||||
rpl::producer<InlineChosen> inlineResultChosen() const;
|
||||
@@ -103,16 +116,17 @@ public:
|
||||
void setRoundRadius(int radius);
|
||||
void refreshStickers();
|
||||
void setCurrentPeer(PeerData *peer);
|
||||
void showPromoForPremiumEmoji();
|
||||
void provideRecentEmoji(const std::vector<DocumentId> &customRecentList);
|
||||
|
||||
void hideFinished();
|
||||
void showStarted();
|
||||
void beforeHiding();
|
||||
void afterShown();
|
||||
|
||||
int marginTop() const;
|
||||
int marginBottom() const;
|
||||
int scrollTop() const;
|
||||
[[nodiscard]] int marginTop() const;
|
||||
[[nodiscard]] int marginBottom() const;
|
||||
[[nodiscard]] int scrollTop() const;
|
||||
[[nodiscard]] int scrollBottom() const;
|
||||
|
||||
bool preventAutoHide() const;
|
||||
bool isSliding() const {
|
||||
@@ -128,6 +142,7 @@ public:
|
||||
}
|
||||
|
||||
void showMenuWithType(SendMenu::Type type);
|
||||
void setDropDown(bool dropDown);
|
||||
|
||||
// Float player interface.
|
||||
bool floatPlayerHandleWheelEvent(QEvent *e);
|
||||
@@ -193,11 +208,14 @@ private:
|
||||
Tab createTab(SelectorTab type, int index);
|
||||
|
||||
void paintSlideFrame(Painter &p);
|
||||
void paintBgRoundedPart(Painter &p);
|
||||
void paintContent(Painter &p);
|
||||
|
||||
void checkRestrictedPeer();
|
||||
bool isRestrictedView();
|
||||
void updateRestrictedLabelGeometry();
|
||||
void updateScrollGeometry(QSize oldSize);
|
||||
void updateFooterGeometry();
|
||||
void handleScroll();
|
||||
|
||||
QImage grabForAnimation();
|
||||
@@ -227,6 +245,7 @@ private:
|
||||
not_null<GifsListWidget*> gifs() const;
|
||||
not_null<StickersListWidget*> masks() const;
|
||||
|
||||
const style::EmojiPan &_st;
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
const Window::GifPauseReason _level = {};
|
||||
|
||||
@@ -252,6 +271,7 @@ private:
|
||||
const bool _hasGifsTab;
|
||||
const bool _hasMasksTab;
|
||||
const bool _tabbed;
|
||||
bool _dropDown = false;
|
||||
|
||||
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||
|
||||
@@ -269,14 +289,24 @@ public:
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
Window::GifPauseReason level);
|
||||
Inner(
|
||||
QWidget *parent,
|
||||
const style::EmojiPan &st,
|
||||
not_null<Main::Session*> session,
|
||||
Fn<bool()> paused);
|
||||
|
||||
[[nodiscard]] not_null<Window::SessionController*> controller() const {
|
||||
return _controller;
|
||||
[[nodiscard]] Main::Session &session() const {
|
||||
return *_session;
|
||||
}
|
||||
[[nodiscard]] Window::GifPauseReason level() const {
|
||||
return _level;
|
||||
[[nodiscard]] const style::EmojiPan &st() const {
|
||||
return _st;
|
||||
}
|
||||
[[nodiscard]] Fn<bool()> pausedMethod() const {
|
||||
return _paused;
|
||||
}
|
||||
[[nodiscard]] bool paused() const {
|
||||
return _paused();
|
||||
}
|
||||
[[nodiscard]] Main::Session &session() const;
|
||||
|
||||
[[nodiscard]] int getVisibleTop() const {
|
||||
return _visibleTop;
|
||||
@@ -304,9 +334,9 @@ public:
|
||||
}
|
||||
virtual void beforeHiding() {
|
||||
}
|
||||
virtual void fillContextMenu(
|
||||
not_null<Ui::PopupMenu*> menu,
|
||||
SendMenu::Type type) {
|
||||
[[nodiscard]] virtual base::unique_qptr<Ui::PopupMenu> fillContextMenu(
|
||||
SendMenu::Type type) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
rpl::producer<int> scrollToRequests() const;
|
||||
@@ -319,6 +349,7 @@ protected:
|
||||
int visibleTop,
|
||||
int visibleBottom) override;
|
||||
int minimalHeight() const;
|
||||
virtual int defaultMinimalHeight() const;
|
||||
int resizeGetHeight(int newWidth) override final;
|
||||
|
||||
virtual int countDesiredHeight(int newWidth) = 0;
|
||||
@@ -334,8 +365,9 @@ protected:
|
||||
void checkHideWithBox(QPointer<Ui::BoxContent> box);
|
||||
|
||||
private:
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
const Window::GifPauseReason _level = {};
|
||||
const style::EmojiPan &_st;
|
||||
const not_null<Main::Session*> _session;
|
||||
const Fn<bool()> _paused;
|
||||
|
||||
int _visibleTop = 0;
|
||||
int _visibleBottom = 0;
|
||||
@@ -351,7 +383,9 @@ private:
|
||||
|
||||
class TabbedSelector::InnerFooter : public Ui::RpWidget {
|
||||
public:
|
||||
InnerFooter(QWidget *parent);
|
||||
InnerFooter(QWidget *parent, const style::EmojiPan &st);
|
||||
|
||||
[[nodiscard]] const style::EmojiPan &st() const;
|
||||
|
||||
protected:
|
||||
virtual void processHideFinished() {
|
||||
@@ -360,6 +394,9 @@ protected:
|
||||
}
|
||||
friend class Inner;
|
||||
|
||||
private:
|
||||
const style::EmojiPan &_st;
|
||||
|
||||
};
|
||||
|
||||
} // namespace ChatHelpers
|
||||
|
||||