Compare commits
400 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d031046edb | ||
|
|
7808cc6d41 | ||
|
|
a7b60c43b5 | ||
|
|
a8b959826c | ||
|
|
42f96f3c43 | ||
|
|
cc97093c5a | ||
|
|
7d5d086ade | ||
|
|
3da44eb5dd | ||
|
|
4955cdcdce | ||
|
|
597195a2e2 | ||
|
|
5966c74a27 | ||
|
|
caa1ae4436 | ||
|
|
af5ad84e72 | ||
|
|
728ed02a1c | ||
|
|
8e369a4aa5 | ||
|
|
35e457c924 | ||
|
|
097c3c4a5a | ||
|
|
10022a3c6d | ||
|
|
0dbb195106 | ||
|
|
6493cb9ed8 | ||
|
|
1cfda38345 | ||
|
|
bf20dbe3bc | ||
|
|
46d3f232af | ||
|
|
ade97fd2d2 | ||
|
|
39614aab3f | ||
|
|
6bab2b4df6 | ||
|
|
b4c7272351 | ||
|
|
d831775e2f | ||
|
|
bde39970a0 | ||
|
|
076291b98f | ||
|
|
1341907cfd | ||
|
|
b793c06759 | ||
|
|
475b2ac739 | ||
|
|
d1c310de00 | ||
|
|
8615a25cd1 | ||
|
|
de4eb1e59b | ||
|
|
8e8f6f905f | ||
|
|
a57eecd420 | ||
|
|
f306b11676 | ||
|
|
745ad45d47 | ||
|
|
5fee0a7a73 | ||
|
|
096ddcad6d | ||
|
|
3b7448ccab | ||
|
|
478c6c4d36 | ||
|
|
e2ea27cbef | ||
|
|
625ae87eea | ||
|
|
597816db09 | ||
|
|
ec3fc8c749 | ||
|
|
e64a096dca | ||
|
|
eaf30d58be | ||
|
|
94ac9f93fa | ||
|
|
04f040c9c5 | ||
|
|
af0e87c569 | ||
|
|
1a503e5f1d | ||
|
|
c46b659aa5 | ||
|
|
7a754f8b00 | ||
|
|
29fb263495 | ||
|
|
c8660b5385 | ||
|
|
d47b99b0b5 | ||
|
|
88e3c87cd9 | ||
|
|
fe0e526b79 | ||
|
|
c480ab1a3b | ||
|
|
49bc8ccd6a | ||
|
|
b180070ba3 | ||
|
|
b40f30ca98 | ||
|
|
c2a1817400 | ||
|
|
cb6698cf4a | ||
|
|
bcdb1bdfd2 | ||
|
|
effc9873c9 | ||
|
|
9561026cd4 | ||
|
|
5d335341ab | ||
|
|
cc8408d11c | ||
|
|
a197ed9e95 | ||
|
|
3b91e2dee4 | ||
|
|
c035ec6917 | ||
|
|
041ec1157f | ||
|
|
17578be4b9 | ||
|
|
1409d38ac3 | ||
|
|
b463c76eca | ||
|
|
d62fb5786d | ||
|
|
aad157cf56 | ||
|
|
2d3e2b1ef8 | ||
|
|
91ab82c9da | ||
|
|
9c23de7f1a | ||
|
|
60fb5fdaf0 | ||
|
|
4709e11e46 | ||
|
|
8c28ce4c99 | ||
|
|
20c63b98c7 | ||
|
|
8b42161898 | ||
|
|
b1823d981b | ||
|
|
b2e8e0431e | ||
|
|
486d5b63d3 | ||
|
|
84a1fec7b1 | ||
|
|
3a84c6afdd | ||
|
|
16d18b437d | ||
|
|
12fab565a4 | ||
|
|
f8d5a8a203 | ||
|
|
b9af4f3cb0 | ||
|
|
4b6107fa56 | ||
|
|
f90a010b84 | ||
|
|
ef0539c9fc | ||
|
|
00db325e91 | ||
|
|
d5429e769f | ||
|
|
714dae054a | ||
|
|
981babf302 | ||
|
|
0926bb1288 | ||
|
|
e13768ea50 | ||
|
|
6c19274eac | ||
|
|
394883b986 | ||
|
|
4240568ea5 | ||
|
|
a77131dfd6 | ||
|
|
caca679336 | ||
|
|
b08869abdb | ||
|
|
d5147c9d28 | ||
|
|
859e41f95a | ||
|
|
0e45f3ebd9 | ||
|
|
7f9461820b | ||
|
|
e59a60b3b5 | ||
|
|
926aae6847 | ||
|
|
744c1b925e | ||
|
|
3fc9ed0ccb | ||
|
|
0b7d544615 | ||
|
|
d19baeace2 | ||
|
|
cd0f58fa65 | ||
|
|
1f25301283 | ||
|
|
0788f3d7b0 | ||
|
|
f93b7a60f8 | ||
|
|
d0875a1178 | ||
|
|
29f8493a82 | ||
|
|
d4db838d43 | ||
|
|
0be2e8b672 | ||
|
|
0aa1031402 | ||
|
|
53c73accd0 | ||
|
|
be38800a70 | ||
|
|
f2fa1cd70d | ||
|
|
5dfce5f7b2 | ||
|
|
2b10e1e595 | ||
|
|
eb1ef6d2a7 | ||
|
|
fde63ccb21 | ||
|
|
b209683c8e | ||
|
|
b6be799938 | ||
|
|
ec8c634e9c | ||
|
|
b7a9aa9a0e | ||
|
|
2ca489b2fb | ||
|
|
daf76c1bc2 | ||
|
|
d3aa0664a7 | ||
|
|
f61c22b065 | ||
|
|
8041941565 | ||
|
|
0aa1cd0b52 | ||
|
|
1bbac5784b | ||
|
|
f925a9e961 | ||
|
|
8b6d475882 | ||
|
|
d82c422ea1 | ||
|
|
d7e57e42d8 | ||
|
|
7ee2ec13f0 | ||
|
|
a6e13a9f9e | ||
|
|
66f73a5a64 | ||
|
|
575684670c | ||
|
|
9661bac876 | ||
|
|
4b618a3578 | ||
|
|
0e79bd3d12 | ||
|
|
dd692f2d26 | ||
|
|
cba8387589 | ||
|
|
01c2ade501 | ||
|
|
b9fa14139a | ||
|
|
a8cb5419d6 | ||
|
|
f775670938 | ||
|
|
c035a25aaa | ||
|
|
6cae088d1f | ||
|
|
bee0534052 | ||
|
|
3e11d44cac | ||
|
|
4d269f6e97 | ||
|
|
e9496fb612 | ||
|
|
c9c82446cb | ||
|
|
0dec803177 | ||
|
|
2dc45ac907 | ||
|
|
a3d8db4ac0 | ||
|
|
aee6b6e224 | ||
|
|
736efd4692 | ||
|
|
ec5e846374 | ||
|
|
caf32cccd3 | ||
|
|
594b2bc8f2 | ||
|
|
d1ba270a8c | ||
|
|
2c1abd32bf | ||
|
|
79662dffa4 | ||
|
|
a79e025151 | ||
|
|
3fa168cee0 | ||
|
|
9c1ef76e49 | ||
|
|
8497b83f7c | ||
|
|
393c23ad12 | ||
|
|
01821cd779 | ||
|
|
3da733520d | ||
|
|
a605275157 | ||
|
|
8564e4d727 | ||
|
|
fc3acff5d6 | ||
|
|
515850ec9b | ||
|
|
837b256778 | ||
|
|
d16cab30d4 | ||
|
|
fcdd7ecd61 | ||
|
|
da9720530a | ||
|
|
4a10d86a29 | ||
|
|
3b5a007db5 | ||
|
|
2479b56c3b | ||
|
|
0909e8bd08 | ||
|
|
6109ec70b8 | ||
|
|
c20bd17029 | ||
|
|
10e3115c39 | ||
|
|
3425b40746 | ||
|
|
42fc4fbb31 | ||
|
|
f081917cd0 | ||
|
|
bdfb0ffe04 | ||
|
|
2b282c8d7d | ||
|
|
77d23ad182 | ||
|
|
79442fde97 | ||
|
|
f8e80bc266 | ||
|
|
cb4c629178 | ||
|
|
af0e11a1aa | ||
|
|
8ac6aca315 | ||
|
|
2638e54181 | ||
|
|
db97db4aba | ||
|
|
ded3f135bb | ||
|
|
be82df72e6 | ||
|
|
3fa6335b24 | ||
|
|
23868bf9cc | ||
|
|
5b67f4ac9b | ||
|
|
17fdef7d9e | ||
|
|
cf82e12bf4 | ||
|
|
6f27aeef10 | ||
|
|
df53ddf837 | ||
|
|
a3fd4f3fac | ||
|
|
24c0624704 | ||
|
|
33724be6ea | ||
|
|
4624d34f68 | ||
|
|
aeee016dc9 | ||
|
|
8ded88baf5 | ||
|
|
bdd35a6e2b | ||
|
|
d9a08bb6a6 | ||
|
|
cee833f102 | ||
|
|
c19a527872 | ||
|
|
6995fcafb5 | ||
|
|
b261d23645 | ||
|
|
21c1ba7607 | ||
|
|
5a2b8d06e3 | ||
|
|
9046daa1a6 | ||
|
|
65ccb4059e | ||
|
|
d2578e9e47 | ||
|
|
be17e2b919 | ||
|
|
26c2e7f245 | ||
|
|
9051716172 | ||
|
|
71b6a58683 | ||
|
|
32cd454554 | ||
|
|
6ffe555f6a | ||
|
|
7ac9ab3a51 | ||
|
|
a9b0464726 | ||
|
|
e4e85e5a39 | ||
|
|
c5f294a1ac | ||
|
|
5dc078a3f8 | ||
|
|
83753343cb | ||
|
|
42215343cf | ||
|
|
bedefee1d1 | ||
|
|
788eb014d4 | ||
|
|
13b7a07d2e | ||
|
|
f026271436 | ||
|
|
d13fe39629 | ||
|
|
62b3b60c45 | ||
|
|
74313d23f3 | ||
|
|
646390141a | ||
|
|
83cf12b475 | ||
|
|
a0226f9789 | ||
|
|
54fecd497e | ||
|
|
2106747496 | ||
|
|
ecce9dbaaa | ||
|
|
b606a7b21d | ||
|
|
35ff45971f | ||
|
|
7a436f32dd | ||
|
|
54d5358b75 | ||
|
|
20c2250abb | ||
|
|
2ddc1ee2e1 | ||
|
|
b55d2008c0 | ||
|
|
11b932707c | ||
|
|
d50aca0d33 | ||
|
|
671e81033c | ||
|
|
361d269bf3 | ||
|
|
ae81373cff | ||
|
|
160794b26c | ||
|
|
1dc57afbe1 | ||
|
|
d9f397ea3f | ||
|
|
c9a976bf87 | ||
|
|
7b921dea3b | ||
|
|
fcc6aaed91 | ||
|
|
eb0ab9609f | ||
|
|
bb359f6493 | ||
|
|
b24be50afe | ||
|
|
3e55380eed | ||
|
|
25c97a3ee8 | ||
|
|
2055cc70d1 | ||
|
|
788a81df6c | ||
|
|
1209bd35c5 | ||
|
|
f473a1a804 | ||
|
|
c8e95f7297 | ||
|
|
b1ed8cd1b1 | ||
|
|
64bb818fe9 | ||
|
|
ee172d951d | ||
|
|
41bc47eb6f | ||
|
|
423d2293f9 | ||
|
|
e6559276c0 | ||
|
|
13959ca36c | ||
|
|
520989a7e6 | ||
|
|
4c02d19a51 | ||
|
|
734e1166ad | ||
|
|
94fd3e32dd | ||
|
|
367adaa44d | ||
|
|
73b4621121 | ||
|
|
8256a4c686 | ||
|
|
b6b6673214 | ||
|
|
8ba2e95e6c | ||
|
|
40ab042fb5 | ||
|
|
25f401c22e | ||
|
|
d8566f770f | ||
|
|
f76f69b5cd | ||
|
|
487dd27ca1 | ||
|
|
32df03f08d | ||
|
|
658db59aaf | ||
|
|
695542cfd2 | ||
|
|
07cd35b1a8 | ||
|
|
74aae29b64 | ||
|
|
70713d5f62 | ||
|
|
dd1b006d8a | ||
|
|
ec8d604db7 | ||
|
|
9e8d60065b | ||
|
|
d603f4de51 | ||
|
|
77695091b3 | ||
|
|
20e81177a6 | ||
|
|
ce3ad95950 | ||
|
|
c5684e768a | ||
|
|
c8d5a60c74 | ||
|
|
4dad0a215a | ||
|
|
15698fd6f0 | ||
|
|
cd4654dfd2 | ||
|
|
3a3d4480cc | ||
|
|
5c3748db56 | ||
|
|
7dfdcc7be0 | ||
|
|
d1f2950167 | ||
|
|
59f61586a9 | ||
|
|
629dd6f9de | ||
|
|
f4fc8ec2c4 | ||
|
|
e8aa55d4d8 | ||
|
|
26b17325aa | ||
|
|
c71f35778d | ||
|
|
c9eb9a3ee0 | ||
|
|
06948ad15e | ||
|
|
029e0c9488 | ||
|
|
c0219cb95d | ||
|
|
78e553b724 | ||
|
|
b5b70beea0 | ||
|
|
177a7eaf43 | ||
|
|
ca863bfb5b | ||
|
|
c45025c6e5 | ||
|
|
10968d0da2 | ||
|
|
b0a65885c9 | ||
|
|
ad8f8513c3 | ||
|
|
f457a9d109 | ||
|
|
830fb3ccc2 | ||
|
|
9116328f29 | ||
|
|
aa7575dec4 | ||
|
|
bfe272e39f | ||
|
|
a7ca15657b | ||
|
|
41dada2c06 | ||
|
|
501784cd15 | ||
|
|
ac699ccf80 | ||
|
|
aadaf47569 | ||
|
|
6bc0179919 | ||
|
|
93fbad50bc | ||
|
|
a5ec616382 | ||
|
|
bf3f474195 | ||
|
|
14f68c2f33 | ||
|
|
92fadd2652 | ||
|
|
b68a7c7f04 | ||
|
|
50097e1a81 | ||
|
|
2414e927bd | ||
|
|
da768ac1d1 | ||
|
|
396c229a4d | ||
|
|
2b3f17e982 | ||
|
|
5ef48cac9c | ||
|
|
6ba922d7b0 | ||
|
|
f881192dd0 | ||
|
|
ef2a0bb05e | ||
|
|
54efa2353e | ||
|
|
2878533078 | ||
|
|
ac520b314d | ||
|
|
871fef2c4a | ||
|
|
99f4b93745 | ||
|
|
a757e07c3a | ||
|
|
5c4f006550 | ||
|
|
9ad38b9638 | ||
|
|
fe8ebc1659 | ||
|
|
a732d8f5e7 | ||
|
|
7ddcc47fcd | ||
|
|
70f22293cf | ||
|
|
90fb59348c |
2
.github/workflows/docker.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
curl -sSL https://install.python-poetry.org | python3 -
|
||||
|
||||
- name: Free up some disk space.
|
||||
uses: jlumbroso/free-disk-space@76866dbe54312617f00798d1762df7f43def6e5c
|
||||
uses: jlumbroso/free-disk-space@f68fdb76e2ea636224182cfb7377ff9a1708f9b8
|
||||
|
||||
- name: Docker image build.
|
||||
run: |
|
||||
|
||||
4
.github/workflows/master_updater.yml
vendored
@@ -11,7 +11,9 @@ jobs:
|
||||
SKIP: "0"
|
||||
to_branch: "master"
|
||||
steps:
|
||||
- uses: actions/checkout@v3.1.0
|
||||
- uses: actions/checkout@v4.1.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
if: env.SKIP == '0'
|
||||
- name: Push the code to the master branch.
|
||||
if: env.SKIP == '0'
|
||||
|
||||
3
.gitmodules
vendored
@@ -100,3 +100,6 @@
|
||||
[submodule "Telegram/ThirdParty/wayland"]
|
||||
path = Telegram/ThirdParty/wayland
|
||||
url = https://github.com/gitlab-freedesktop-mirrors/wayland.git
|
||||
[submodule "Telegram/ThirdParty/libprisma"]
|
||||
path = Telegram/ThirdParty/libprisma
|
||||
url = https://github.com/desktop-app/libprisma.git
|
||||
|
||||
@@ -28,6 +28,7 @@ include(cmake/lib_ffmpeg.cmake)
|
||||
include(cmake/lib_stripe.cmake)
|
||||
include(cmake/lib_tgvoip.cmake)
|
||||
include(cmake/lib_tgcalls.cmake)
|
||||
include(cmake/lib_prisma.cmake)
|
||||
include(cmake/td_export.cmake)
|
||||
include(cmake/td_mtproto.cmake)
|
||||
include(cmake/td_lang.cmake)
|
||||
@@ -151,6 +152,8 @@ PRIVATE
|
||||
api/api_sensitive_content.h
|
||||
api/api_single_message_search.cpp
|
||||
api/api_single_message_search.h
|
||||
api/api_statistics.cpp
|
||||
api/api_statistics.h
|
||||
api/api_text_entities.cpp
|
||||
api/api_text_entities.h
|
||||
api/api_toggling_media.cpp
|
||||
@@ -195,6 +198,8 @@ PRIVATE
|
||||
boxes/peers/edit_participant_box.h
|
||||
boxes/peers/edit_participants_box.cpp
|
||||
boxes/peers/edit_participants_box.h
|
||||
boxes/peers/edit_peer_color_box.cpp
|
||||
boxes/peers/edit_peer_color_box.h
|
||||
boxes/peers/edit_peer_common.h
|
||||
boxes/peers/edit_peer_info_box.cpp
|
||||
boxes/peers/edit_peer_info_box.h
|
||||
@@ -450,6 +455,7 @@ PRIVATE
|
||||
data/data_audio_msg_id.h
|
||||
data/data_auto_download.cpp
|
||||
data/data_auto_download.h
|
||||
data/data_boosts.h
|
||||
data/data_bot_app.cpp
|
||||
data/data_bot_app.h
|
||||
data/data_chat.cpp
|
||||
@@ -552,6 +558,7 @@ PRIVATE
|
||||
data/data_sparse_ids.h
|
||||
data/data_sponsored_messages.cpp
|
||||
data/data_sponsored_messages.h
|
||||
data/data_statistics.h
|
||||
data/data_stories.cpp
|
||||
data/data_stories.h
|
||||
data/data_stories_ids.cpp
|
||||
@@ -646,6 +653,8 @@ PRIVATE
|
||||
history/view/controls/history_view_compose_controls.h
|
||||
history/view/controls/history_view_compose_search.cpp
|
||||
history/view/controls/history_view_compose_search.h
|
||||
history/view/controls/history_view_draft_options.cpp
|
||||
history/view/controls/history_view_draft_options.h
|
||||
history/view/controls/history_view_forward_panel.cpp
|
||||
history/view/controls/history_view_forward_panel.h
|
||||
history/view/controls/history_view_ttl_button.cpp
|
||||
@@ -654,6 +663,8 @@ 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/controls/history_view_webpage_processor.cpp
|
||||
history/view/controls/history_view_webpage_processor.h
|
||||
history/view/media/history_view_call.cpp
|
||||
history/view/media/history_view_call.h
|
||||
history/view/media/history_view_contact.cpp
|
||||
@@ -672,6 +683,8 @@ PRIVATE
|
||||
history/view/media/history_view_game.h
|
||||
history/view/media/history_view_gif.cpp
|
||||
history/view/media/history_view_gif.h
|
||||
history/view/media/history_view_giveaway.cpp
|
||||
history/view/media/history_view_giveaway.h
|
||||
history/view/media/history_view_invoice.cpp
|
||||
history/view/media/history_view_invoice.h
|
||||
history/view/media/history_view_large_emoji.cpp
|
||||
@@ -811,20 +824,10 @@ PRIVATE
|
||||
history/history_view_highlight_manager.h
|
||||
history/history_widget.cpp
|
||||
history/history_widget.h
|
||||
info/info_content_widget.cpp
|
||||
info/info_content_widget.h
|
||||
info/info_controller.cpp
|
||||
info/info_controller.h
|
||||
info/info_layer_widget.cpp
|
||||
info/info_layer_widget.h
|
||||
info/info_memento.cpp
|
||||
info/info_memento.h
|
||||
info/info_section_widget.cpp
|
||||
info/info_section_widget.h
|
||||
info/info_top_bar.cpp
|
||||
info/info_top_bar.h
|
||||
info/info_wrap_widget.cpp
|
||||
info/info_wrap_widget.h
|
||||
info/boosts/info_boosts_inner_widget.cpp
|
||||
info/boosts/info_boosts_inner_widget.h
|
||||
info/boosts/info_boosts_widget.cpp
|
||||
info/boosts/info_boosts_widget.h
|
||||
info/common_groups/info_common_groups_inner_widget.cpp
|
||||
info/common_groups/info_common_groups_inner_widget.h
|
||||
info/common_groups/info_common_groups_widget.cpp
|
||||
@@ -880,6 +883,15 @@ PRIVATE
|
||||
info/profile/info_profile_widget.h
|
||||
info/settings/info_settings_widget.cpp
|
||||
info/settings/info_settings_widget.h
|
||||
info/statistics/info_statistics_common.h
|
||||
info/statistics/info_statistics_inner_widget.cpp
|
||||
info/statistics/info_statistics_inner_widget.h
|
||||
info/statistics/info_statistics_list_controllers.cpp
|
||||
info/statistics/info_statistics_list_controllers.h
|
||||
info/statistics/info_statistics_recent_message.cpp
|
||||
info/statistics/info_statistics_recent_message.h
|
||||
info/statistics/info_statistics_widget.cpp
|
||||
info/statistics/info_statistics_widget.h
|
||||
info/stories/info_stories_inner_widget.cpp
|
||||
info/stories/info_stories_inner_widget.h
|
||||
info/stories/info_stories_provider.cpp
|
||||
@@ -898,6 +910,20 @@ PRIVATE
|
||||
info/userpic/info_userpic_emoji_builder_preview.h
|
||||
info/userpic/info_userpic_emoji_builder_widget.cpp
|
||||
info/userpic/info_userpic_emoji_builder_widget.h
|
||||
info/info_content_widget.cpp
|
||||
info/info_content_widget.h
|
||||
info/info_controller.cpp
|
||||
info/info_controller.h
|
||||
info/info_layer_widget.cpp
|
||||
info/info_layer_widget.h
|
||||
info/info_memento.cpp
|
||||
info/info_memento.h
|
||||
info/info_section_widget.cpp
|
||||
info/info_section_widget.h
|
||||
info/info_top_bar.cpp
|
||||
info/info_top_bar.h
|
||||
info/info_wrap_widget.cpp
|
||||
info/info_wrap_widget.h
|
||||
inline_bots/bot_attach_web_view.cpp
|
||||
inline_bots/bot_attach_web_view.h
|
||||
inline_bots/inline_bot_layout_internal.cpp
|
||||
@@ -1539,6 +1565,7 @@ elseif (APPLE)
|
||||
PRE_LINK
|
||||
COMMAND mkdir -p $<TARGET_FILE_DIR:Telegram>/../Resources
|
||||
COMMAND cp ${CMAKE_BINARY_DIR}/lib_ui.rcc $<TARGET_FILE_DIR:Telegram>/../Resources
|
||||
COMMAND cp ${CMAKE_BINARY_DIR}/lib_spellcheck.rcc $<TARGET_FILE_DIR:Telegram>/../Resources
|
||||
)
|
||||
if (NOT build_macstore)
|
||||
add_custom_command(TARGET Telegram
|
||||
|
||||
BIN
Telegram/Resources/animations/stats.tgs
Normal file
BIN
Telegram/Resources/icons/chat/mini_copy.png
Normal file
|
After Width: | Height: | Size: 324 B |
BIN
Telegram/Resources/icons/chat/mini_copy@2x.png
Normal file
|
After Width: | Height: | Size: 484 B |
BIN
Telegram/Resources/icons/chat/mini_copy@3x.png
Normal file
|
After Width: | Height: | Size: 623 B |
BIN
Telegram/Resources/icons/chat/mini_quote.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
Telegram/Resources/icons/chat/mini_quote@2x.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
Telegram/Resources/icons/chat/mini_quote@3x.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
Telegram/Resources/icons/menu/boosts.png
Normal file
|
After Width: | Height: | Size: 589 B |
BIN
Telegram/Resources/icons/menu/boosts@2x.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/menu/boosts@3x.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
Telegram/Resources/icons/menu/link_above.png
Normal file
|
After Width: | Height: | Size: 450 B |
BIN
Telegram/Resources/icons/menu/link_above@2x.png
Normal file
|
After Width: | Height: | Size: 725 B |
BIN
Telegram/Resources/icons/menu/link_above@3x.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
Telegram/Resources/icons/menu/link_below.png
Normal file
|
After Width: | Height: | Size: 454 B |
BIN
Telegram/Resources/icons/menu/link_below@2x.png
Normal file
|
After Width: | Height: | Size: 761 B |
BIN
Telegram/Resources/icons/menu/link_below@3x.png
Normal file
|
After Width: | Height: | Size: 1017 B |
BIN
Telegram/Resources/icons/menu/link_enlarge.png
Normal file
|
After Width: | Height: | Size: 582 B |
BIN
Telegram/Resources/icons/menu/link_enlarge@2x.png
Normal file
|
After Width: | Height: | Size: 965 B |
BIN
Telegram/Resources/icons/menu/link_enlarge@3x.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Telegram/Resources/icons/menu/link_shrink.png
Normal file
|
After Width: | Height: | Size: 542 B |
BIN
Telegram/Resources/icons/menu/link_shrink@2x.png
Normal file
|
After Width: | Height: | Size: 991 B |
BIN
Telegram/Resources/icons/menu/link_shrink@3x.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Telegram/Resources/icons/menu/stats.png
Normal file
|
After Width: | Height: | Size: 548 B |
BIN
Telegram/Resources/icons/menu/stats@2x.png
Normal file
|
After Width: | Height: | Size: 938 B |
BIN
Telegram/Resources/icons/menu/stats@3x.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
@@ -129,6 +129,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_reconnecting#other" = "Reconnect in {count} s...";
|
||||
"lng_reconnecting_try_now" = "Try now";
|
||||
|
||||
"lng_code_block_header_copy" = "copy";
|
||||
|
||||
"lng_status_service_notifications" = "service notifications";
|
||||
"lng_status_support" = "support";
|
||||
"lng_status_bot" = "bot";
|
||||
@@ -402,6 +404,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_dlg_search_from" = "From: {user}";
|
||||
|
||||
"lng_settings_save" = "Save";
|
||||
"lng_settings_apply" = "Apply";
|
||||
|
||||
"lng_username_title" = "Username";
|
||||
"lng_username_description1" = "You can choose a username on Telegram. If you do, other people will be able to find you by this username and contact you without knowing your phone number.";
|
||||
@@ -543,6 +546,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_workmode_tray" = "Show tray icon";
|
||||
"lng_settings_workmode_window" = "Show taskbar icon";
|
||||
"lng_settings_close_to_taskbar" = "Close to taskbar";
|
||||
"lng_settings_monochrome_icon" = "Use monochrome icon";
|
||||
"lng_settings_window_system" = "Window title";
|
||||
"lng_settings_title_chat_name" = "Show chat name";
|
||||
"lng_settings_title_account_name" = "Show active account";
|
||||
@@ -770,11 +774,33 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_clear_payment_info_clear" = "Clear";
|
||||
"lng_clear_payment_info_confirm" = "Delete your shipping info and instruct all payment providers to remove your saved credit cards? Note that Telegram never stores your credit card data.";
|
||||
|
||||
"lng_settings_theme_settings" = "Theme settings";
|
||||
"lng_settings_theme_name_color" = "Your name color";
|
||||
"lng_settings_auto_night_mode" = "Auto-Night mode";
|
||||
"lng_settings_auto_night_enabled" = "Match the system settings";
|
||||
"lng_settings_auto_night_mode_off" = "Off";
|
||||
"lng_settings_auto_night_mode_on" = "System";
|
||||
"lng_settings_auto_night_warning" = "You have enabled auto-night mode. If you want to change the dark mode settings, you'll need to disable it first.";
|
||||
"lng_settings_auto_night_disable" = "Disable";
|
||||
|
||||
"lng_settings_color_title" = "Color preview";
|
||||
"lng_settings_color_reply" = "Reply to your message";
|
||||
"lng_settings_color_reply_channel" = "Reply to your channel message";
|
||||
"lng_settings_color_text" = "Your name and replies to your messages will be shown in the selected color.";
|
||||
"lng_settings_color_text_channel" = "The name of the channel and replies to its messages will be shown in the selected color.";
|
||||
"lng_settings_color_link_name" = "Telegram";
|
||||
"lng_settings_color_link_title" = "Link Preview";
|
||||
"lng_settings_color_link_description" = "Your selected color will also tint the link preview.";
|
||||
"lng_settings_color_about" = "You can choose a color to tint your name, the links you send, and replies to your messages.";
|
||||
"lng_settings_color_about_channel" = "You can choose a color to tint your channel's name, the links it sends, and replies to its messages.";
|
||||
"lng_settings_color_emoji" = "Add icons to replies";
|
||||
"lng_settings_color_emoji_remove" = "Remove icon";
|
||||
"lng_settings_color_emoji_off" = "Off";
|
||||
"lng_settings_color_emoji_about" = "Make replies to your messages stand out by adding custom patterns to them.";
|
||||
"lng_settings_color_emoji_about_channel" = "Make replies to your channel's messages stand out by adding custom patterns to them.";
|
||||
"lng_settings_color_subscribe" = "Subscribe to {link} to choose a custom color for your name.";
|
||||
"lng_settings_color_changed" = "Your name color has been updated!";
|
||||
"lng_settings_color_changed_channel" = "Your channel color has been updated!";
|
||||
|
||||
"lng_suggest_hide_new_title" = "Hide new chats?";
|
||||
"lng_suggest_hide_new_about" = "You are receiving lots of new chats from users who are not in your Contact List.\n\nDo you want to have such chats **automatically muted** and **archived**?";
|
||||
"lng_suggest_hide_new_to_settings" = "Go to Settings";
|
||||
@@ -802,7 +828,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_background_text1" = "Ah, you kids today with techno music! You should enjoy the classics, like Hasselhoff!";
|
||||
"lng_background_text2" = "I can't even take you seriously right now.";
|
||||
"lng_background_bad_link" = "This background link appears to be invalid.";
|
||||
"lng_background_apply" = "Apply";
|
||||
"lng_background_share" = "Share";
|
||||
"lng_background_link_copied" = "Link copied to clipboard";
|
||||
"lng_background_blur" = "Blurred";
|
||||
@@ -1142,6 +1167,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_mute_box_title" = "Mute notifications for...";
|
||||
|
||||
"lng_preview_loading" = "Getting Link Info...";
|
||||
"lng_preview_cant" = "Could not generate preview for this link.";
|
||||
|
||||
"lng_profile_settings_section" = "Settings";
|
||||
"lng_profile_bot_settings" = "Bot Settings";
|
||||
@@ -1640,6 +1666,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_action_story_mention_button" = "View Story";
|
||||
"lng_action_story_mention_me_unavailable" = "The story where you mentioned {user} is no longer available.";
|
||||
"lng_action_story_mention_unavailable" = "The story where {user} mentioned you is no longer available.";
|
||||
"lng_action_giveaway_started" = "{from} just started a giveaway of Telegram Premium subscriptions to its followers.";
|
||||
|
||||
"lng_premium_gift_duration_months#one" = "for {count} month";
|
||||
"lng_premium_gift_duration_months#other" = "for {count} months";
|
||||
@@ -2034,6 +2061,129 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_boost_now_instead" = "You currently boost {channel}. Do you want to boost {other} instead?";
|
||||
"lng_boost_now_replace" = "Replace";
|
||||
|
||||
"lng_boost_channel_title_color" = "Enable colors";
|
||||
"lng_boost_channel_needs_level_color#one" = "Your channel needs to reach **Level {count}** to change channel color.";
|
||||
"lng_boost_channel_needs_level_color#other" = "Your channel needs to reach **Level {count}** to change channel color.";
|
||||
"lng_boost_channel_ask" = "Ask your **Premium** subscribers to boost your channel with this link:";
|
||||
"lng_boost_channel_ask_button" = "Copy Link";
|
||||
"lng_boost_channel_or" = "or";
|
||||
"lng_boost_channel_gifting" = "Boost your channel by gifting your subscribers Telegram Premium. {link}";
|
||||
"lng_boost_channel_gifting_link" = "Get boosts >";
|
||||
|
||||
"lng_giveaway_new_title" = "Boosts via Gifts";
|
||||
"lng_giveaway_new_about" = "Get more boosts for your channel by gifting Premium to your subscribers.";
|
||||
"lng_giveaway_create_option" = "Create Giveaway";
|
||||
"lng_giveaway_create_subtitle" = "winners are chosen randomly";
|
||||
"lng_giveaway_award_option" = "Award Specific Users";
|
||||
"lng_giveaway_award_subtitle" = "Select recipients >";
|
||||
"lng_giveaway_award_chosen#one" = "{count} recipient >";
|
||||
"lng_giveaway_award_chosen#other" = "{count} recipients >";
|
||||
"lng_giveaway_quantity_title" = "Quantity of prizes / boosts";
|
||||
"lng_giveaway_quantity#one" = "{count} Subscription / Boost";
|
||||
"lng_giveaway_quantity#other" = "{count} Subscriptions / Boosts";
|
||||
"lng_giveaway_quantity_about" = "Choose how many Premium subscriptions to give away and boosts to receive.";
|
||||
"lng_giveaway_channels_title" = "Channels included in the giveaway";
|
||||
"lng_giveaway_channels_this#one" = "this channel will receive {count} boost";
|
||||
"lng_giveaway_channels_this#other" = "this channel will receive {count} boosts";
|
||||
"lng_giveaway_channels_add" = "Add Channel";
|
||||
"lng_giveaway_channels_about" = "Choose the channels the users need to join to take part in the giveaway.";
|
||||
"lng_giveaway_users_title" = "Users eligible for the giveaway";
|
||||
"lng_giveaway_users_all" = "All subscribers";
|
||||
"lng_giveaway_users_new" = "Only new subscribers";
|
||||
"lng_giveaway_users_about" = "Choose if you want to limit the giveaway only to the newly joined subscribers.";
|
||||
"lng_giveaway_start" = "Start Giveaway";
|
||||
"lng_giveaway_award" = "Gift Premium";
|
||||
"lng_giveaway_date_title" = "Date when giveaway ends";
|
||||
"lng_giveaway_date" = "Date and Time";
|
||||
"lng_giveaway_date_about#one" = "Choose when {count} subscriber of your channel will be randomly selected to receive Telegram Premium.";
|
||||
"lng_giveaway_date_about#other" = "Choose when {count} subscribers of your channel will be randomly selected to receive Telegram Premium.";
|
||||
"lng_giveaway_duration_title#one" = "Duration of Premium subscription";
|
||||
"lng_giveaway_duration_title#other" = "Duration of Premium subscriptions";
|
||||
"lng_giveaway_duration_price" = "{price} x {amount}";
|
||||
"lng_giveaway_duration_about" = "You can review the list of features and terms of use for Telegram Premium {link}.";
|
||||
"lng_giveaway_duration_about_link" = "here";
|
||||
"lng_giveaway_date_select" = "Select Date and Time";
|
||||
"lng_giveaway_date_confirm" = "Confirm";
|
||||
"lng_giveaway_channels_select#one" = "Select up to {count} channel";
|
||||
"lng_giveaway_channels_select#other" = "Select up to {count} channels";
|
||||
"lng_giveaway_recipients_save" = "Save Recipients";
|
||||
"lng_giveaway_recipients_deselect" = "Deselect All";
|
||||
|
||||
"lng_prize_title" = "Congratulations!";
|
||||
"lng_prize_about" = "You won a prize in a giveaway organized by {channel}.";
|
||||
"lng_prize_duration" = "Your prize is a **Telegram Premium** subscription {duration}.";
|
||||
"lng_prize_gift_about" = "You've received a gift from {channel}.";
|
||||
"lng_prize_gift_duration" = "Your gift is a **Telegram Premium** subscription {duration}.";
|
||||
"lng_prize_open" = "Open Gift Link";
|
||||
"lng_prize_unclaimed_title" = "Unclaimed Prize";
|
||||
"lng_prize_unclaimed_about" = "You have an unclaimed prize from a giveaway by {channel}.";
|
||||
"lng_prize_unclaimed_duration" = "This prize is a **Telegram Premium** subscription {duration}.";
|
||||
|
||||
"lng_prizes_title#one" = "Giveaway Prize";
|
||||
"lng_prizes_title#other" = "Giveaway Prizes";
|
||||
"lng_prizes_about#one" = "**{count}** Telegram Premium Subscription {duration}.";
|
||||
"lng_prizes_about#other" = "**{count}** Telegram Premium Subscriptions {duration}.";
|
||||
"lng_prizes_participants" = "Participants";
|
||||
"lng_prizes_participants_all#one" = "All subscribers of the channel:";
|
||||
"lng_prizes_participants_all#other" = "All subscribers of the channels:";
|
||||
"lng_prizes_participants_new#one" = "All users who joined the channel below after this date:";
|
||||
"lng_prizes_participants_new#other" = "All users who joined the channels below after this date:";
|
||||
"lng_prizes_countries" = "from {countries}";
|
||||
"lng_prizes_countries_and_one" = "{countries}, {country}";
|
||||
"lng_prizes_countries_and_last" = "{countries} and {country}";
|
||||
"lng_prizes_date" = "Winners Selection Date";
|
||||
"lng_prizes_how_works" = "Learn more";
|
||||
"lng_prizes_how_title" = "About this giveaway";
|
||||
"lng_prizes_end_title" = "Giveaway ended";
|
||||
"lng_prizes_how_text" = "This giveaway is sponsored by {admins}.";
|
||||
"lng_prizes_end_text" = "This giveaway was sponsored by {admins}.";
|
||||
"lng_prizes_admins#one" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscription {duration} for its followers";
|
||||
"lng_prizes_admins#other" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscriptions {duration} for its followers.";
|
||||
"lng_prizes_how_when_finish" = "On {date}, Telegram will automatically select {winners}.";
|
||||
"lng_prizes_end_when_finish" = "On {date}, Telegram automatically selected {winners}.";
|
||||
"lng_prizes_end_activated#one" = "**{count}** of the winners already used their gift link.";
|
||||
"lng_prizes_end_activated#other" = "**{count}** of the winners already used their gift links.";
|
||||
"lng_prizes_winners_all_of_one#one" = "{count} random subscribers of {channel}.";
|
||||
"lng_prizes_winners_all_of_one#other" = "{count} random subscribers of {channel}.";
|
||||
"lng_prizes_winners_all_of_many#one" = "{count} random subscribers of {channel} and other listed channels.";
|
||||
"lng_prizes_winners_all_of_many#other" = "{count} random subscribers of {channel} and other listed channels.";
|
||||
"lng_prizes_winners_new_of_one#one" = "{count} random user that joined {channel} after {start_date}";
|
||||
"lng_prizes_winners_new_of_one#other" = "{count} random users that joined {channel} after {start_date}";
|
||||
"lng_prizes_winners_new_of_many#one" = "{count} random user that joined {channel} and other listed channels after {start_date}";
|
||||
"lng_prizes_winners_new_of_many#other" = "{count} random users that joined {channel} and other listed channels after {start_date}";
|
||||
"lng_prizes_how_participate_one" = "To take part in this giveaway please join channel {channel} before {date}.";
|
||||
"lng_prizes_how_participate_many" = "To take part in this giveaway please join channel {channel} and other listed channels before {date}.";
|
||||
"lng_prizes_how_no_admin" = "You are not eligible to participate in this giveaway, because you are an admin of participating channel ({channel}).";
|
||||
"lng_prizes_how_no_joined" = "You are not eligible to participate in this giveaway, because you joined this channel on {date}, which is before the contest started.";
|
||||
"lng_prizes_how_no_country" = "You are not eligible to participate in this giveaway, because your country is not included in the terms of the giveaway.";
|
||||
"lng_prizes_how_yes_joined_one" = "You are participating in this giveaway, because you have joined channel {channel}.";
|
||||
"lng_prizes_how_yes_joined_many" = "You are participating in this giveaway, because you have joined channel {channel} (and other listed channels).";
|
||||
"lng_prizes_you_won" = "You won a prize in this giveaway {cup}";
|
||||
"lng_prizes_view_prize" = "View my prize";
|
||||
"lng_prizes_you_didnt" = "You didn't win a prize in this giveaway.";
|
||||
"lng_prizes_cancelled" = "The channel cancelled the prizes by reversing the payment for them.";
|
||||
"lng_prizes_badge" = "x{amount}";
|
||||
|
||||
"lng_gift_link_title" = "Gift Link";
|
||||
"lng_gift_link_about" = "This link allows you to activate\na **Telegram Premium** subscription.";
|
||||
"lng_gift_link_label_from" = "From";
|
||||
"lng_gift_link_label_to" = "To";
|
||||
"lng_gift_link_label_to_unclaimed" = "No recipient";
|
||||
"lng_gift_link_label_gift" = "Gift";
|
||||
"lng_gift_link_gift_premium" = "Telegram Premium {duration}";
|
||||
"lng_gift_link_label_reason" = "Reason";
|
||||
"lng_gift_link_reason_giveaway" = "Giveaway";
|
||||
"lng_gift_link_reason_unclaimed" = "Incomplete Giveaway";
|
||||
"lng_gift_link_reason_chosen" = "You were selected by the channel";
|
||||
"lng_gift_link_label_date" = "Date";
|
||||
"lng_gift_link_also_send" = "You can also {link} to a friend as a gift.";
|
||||
"lng_gift_link_also_send_link" = "send this link";
|
||||
"lng_gift_link_use" = "Use Link";
|
||||
"lng_gift_link_used_title" = "Used Gift Link";
|
||||
"lng_gift_link_used_about" = "This link was used to activate\na **Telegram Premium** subscription.";
|
||||
"lng_gift_link_used_footer" = "This link was used on {date}.";
|
||||
"lng_gift_link_expired" = "Gift code link expired";
|
||||
|
||||
"lng_accounts_limit_title" = "Limit Reached";
|
||||
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected accounts.";
|
||||
"lng_accounts_limit1#other" = "You have reached the limit of **{count}** connected accounts.";
|
||||
@@ -2213,6 +2363,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_saved_messages" = "Saved Messages";
|
||||
"lng_saved_short" = "Save";
|
||||
"lng_saved_forward_here" = "Forward messages here for quick access";
|
||||
"lng_saved_quote_here" = "Quote here to save";
|
||||
|
||||
"lng_scheduled_messages" = "Scheduled Messages";
|
||||
"lng_scheduled_messages_empty" = "No scheduled messages here yet...";
|
||||
@@ -2420,6 +2571,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_context_attached_stickers" = "Attached Stickers";
|
||||
"lng_context_to_msg" = "Go To Message";
|
||||
"lng_context_reply_msg" = "Reply";
|
||||
"lng_context_quote_and_reply" = "Quote & Reply";
|
||||
"lng_context_edit_msg" = "Edit";
|
||||
"lng_context_forward_msg" = "Forward Message";
|
||||
"lng_context_send_now_msg" = "Send now";
|
||||
@@ -2524,6 +2676,25 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_inline_switch_choose" = "Choose conversation...";
|
||||
"lng_inline_switch_cant" = "Sorry, no way to write here :(";
|
||||
|
||||
"lng_reply_in_another_title" = "Reply in...";
|
||||
"lng_reply_in_another_chat" = "Reply in Another Chat";
|
||||
"lng_reply_show_in_chat" = "Show in Chat";
|
||||
"lng_reply_remove" = "Do Not Reply";
|
||||
"lng_reply_about_quote" = "You can select specific part to quote.";
|
||||
"lng_reply_options_header" = "Reply to Message";
|
||||
"lng_reply_options_quote" = "Update Quote";
|
||||
"lng_reply_header_short" = "Reply";
|
||||
"lng_reply_quote_selected" = "Quote Selected";
|
||||
"lng_reply_from_private_chat" = "This reply is from a private chat.";
|
||||
"lng_link_options_header" = "Link Preview Settings";
|
||||
"lng_link_header_short" = "Link";
|
||||
"lng_link_move_up" = "Move Up";
|
||||
"lng_link_move_down" = "Move Down";
|
||||
"lng_link_shrink_photo" = "Shrink Photo";
|
||||
"lng_link_enlarge_photo" = "Enlarge Photo";
|
||||
"lng_link_remove" = "Do Not Preview";
|
||||
"lng_link_about_choose" = "Click on a link to generate its preview.";
|
||||
|
||||
"lng_share_cant" = "Sorry, no way to share here :(";
|
||||
"lng_reply_cant" = "Sorry, no way to reply to an old message in supergroup :(";
|
||||
"lng_reply_cant_forward" = "Sorry, you can't reply to a message that was sent before the group was upgraded to a supergroup. Do you wish to forward it and add your comment?";
|
||||
@@ -2546,6 +2717,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_edit_bot_title" = "Edit bot";
|
||||
"lng_edit_sign_messages" = "Sign messages";
|
||||
"lng_edit_group" = "Edit group";
|
||||
"lng_edit_channel_color" = "Change name color";
|
||||
"lng_edit_self_title" = "Edit your name";
|
||||
"lng_confirm_contact_data" = "New Contact";
|
||||
"lng_add_contact" = "Create";
|
||||
@@ -2674,6 +2846,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_menu_formatting_italic" = "Italic";
|
||||
"lng_menu_formatting_underline" = "Underline";
|
||||
"lng_menu_formatting_strike_out" = "Strike-through";
|
||||
"lng_menu_formatting_blockquote" = "Quote";
|
||||
"lng_menu_formatting_monospace" = "Monospace";
|
||||
"lng_menu_formatting_spoiler" = "Spoiler";
|
||||
"lng_menu_formatting_link_create" = "Create link";
|
||||
@@ -2686,6 +2859,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_formatting_link_create" = "Create";
|
||||
|
||||
"lng_text_copied" = "Text copied to clipboard.";
|
||||
"lng_code_copied" = "Block copied to clipboard.";
|
||||
|
||||
"lng_spellchecker_submenu" = "Spelling";
|
||||
"lng_spellchecker_add" = "Add to Dictionary";
|
||||
@@ -3351,6 +3525,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_admin_log_participant_volume_channel" = "{from} changed live stream volume for {user} to {percent}";
|
||||
"lng_admin_log_antispam_enabled" = "{from} enabled aggressive anti-spam";
|
||||
"lng_admin_log_antispam_disabled" = "{from} disabled aggressive anti-spam";
|
||||
"lng_admin_log_change_color" = "{from} changed channel color from {previous} to {color}";
|
||||
"lng_admin_log_set_background_emoji" = "{from} set channel background emoji to {emoji}";
|
||||
"lng_admin_log_change_background_emoji" = "{from} changed channel background emoji from {previous} to {emoji}";
|
||||
"lng_admin_log_removed_background_emoji" = "{from} removed channel background emoji {emoji}";
|
||||
"lng_admin_log_user_with_username" = "{name} ({mention})";
|
||||
"lng_admin_log_messages_ttl_set" = "{from} enabled messages auto-delete after {duration}";
|
||||
"lng_admin_log_messages_ttl_changed" = "{from} changed messages auto-delete period from {previous} to {duration}";
|
||||
@@ -4065,6 +4243,80 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
"lng_stories_link_invalid" = "This link is broken or has expired.";
|
||||
|
||||
"lng_stats_title" = "Statistics";
|
||||
"lng_stats_message_title" = "Message Statistic";
|
||||
"lng_stats_zoom_out" = "Zoom Out";
|
||||
|
||||
"lng_stats_overview_title" = "Overview";
|
||||
"lng_stats_overview_member_count" = "Followers";
|
||||
"lng_stats_overview_mean_view_count" = "Views Per Post";
|
||||
"lng_stats_overview_mean_share_count" = "Shared Per Post";
|
||||
"lng_stats_overview_enabled_notifications" = "Enabled Notifications";
|
||||
"lng_stats_overview_messages" = "Messages";
|
||||
"lng_stats_overview_group_mean_view_count" = "Viewing Members";
|
||||
"lng_stats_overview_group_mean_post_count" = "Posting Members";
|
||||
"lng_stats_overview_message_private_shares" = "Private Shares";
|
||||
"lng_stats_overview_message_public_shares" = "Public Shares";
|
||||
"lng_stats_overview_message_views" = "Views";
|
||||
"lng_stats_overview_message_public_share#one" = "{count} public share";
|
||||
"lng_stats_overview_message_public_share#other" = "{count} public shares";
|
||||
|
||||
"lng_stats_members_title" = "Top members";
|
||||
"lng_stats_admins_title" = "Top admins";
|
||||
"lng_stats_inviters_title" = "Top inviters";
|
||||
"lng_stats_member_messages#one" = "{count} message";
|
||||
"lng_stats_member_messages#other" = "{count} messages";
|
||||
"lng_stats_member_characters#one" = "{count} symbol per message";
|
||||
"lng_stats_member_characters#other" = "{count} symbols per message";
|
||||
"lng_stats_member_deletions#one" = "{count} deletions";
|
||||
"lng_stats_member_deletions#other" = "{count} deletions";
|
||||
"lng_stats_member_bans#one" = "{count} ban";
|
||||
"lng_stats_member_bans#other" = "{count} bans";
|
||||
"lng_stats_member_restrictions#one" = "{count} restriction";
|
||||
"lng_stats_member_restrictions#other" = "{count} restrictions";
|
||||
"lng_stats_member_invitations#one" = "{count} invitation";
|
||||
"lng_stats_member_invitations#other" = "{count} invitations";
|
||||
|
||||
"lng_stats_recent_messages_title" = "Recent posts";
|
||||
"lng_stats_recent_messages_views#one" = "{count} view";
|
||||
"lng_stats_recent_messages_views#other" = "{count} views";
|
||||
"lng_stats_recent_messages_shares#one" = "{count} share";
|
||||
"lng_stats_recent_messages_shares#other" = "{count} shares";
|
||||
|
||||
"lng_stats_loading" = "Loading stats...";
|
||||
"lng_stats_loading_subtext" = "Please wait a few moments while we generate your stats.";
|
||||
|
||||
"lng_chart_title_member_count" = "Growth";
|
||||
"lng_chart_title_join" = "Followers";
|
||||
"lng_chart_title_mute" = "Notifications";
|
||||
"lng_chart_title_view_count_by_hour" = "Views by hours";
|
||||
"lng_chart_title_view_count_by_source" = "Views by source";
|
||||
"lng_chart_title_join_by_source" = "New followers by source";
|
||||
"lng_chart_title_language" = "Languages";
|
||||
"lng_chart_title_message_interaction" = "Interactions";
|
||||
"lng_chart_title_instant_view_interaction" = "IV Interactions";
|
||||
|
||||
"lng_chart_title_group_join" = "Group members";
|
||||
"lng_chart_title_group_join_by_source" = "New members by source";
|
||||
"lng_chart_title_group_language" = "Members's primary language";
|
||||
"lng_chart_title_group_message_content" = "Messages";
|
||||
"lng_chart_title_group_action" = "Actions";
|
||||
"lng_chart_title_group_day" = "Views by hours";
|
||||
"lng_chart_title_group_week" = "Top days of week";
|
||||
|
||||
"lng_boosts_title" = "Boosts";
|
||||
"lng_boosts_level" = "Level";
|
||||
"lng_boosts_existing" = "Existing boosts";
|
||||
"lng_boosts_premium_audience" = "Premium subscribers";
|
||||
"lng_boosts_next_level" = "Boosts to level up";
|
||||
"lng_boosts_list_title#one" = "{count} booster";
|
||||
"lng_boosts_list_title#other" = "{count} boosters";
|
||||
"lng_boosts_list_subtext" = "Your channel is currently boosted by these users.";
|
||||
"lng_boosts_show_more" = "Show More Boosts";
|
||||
"lng_boosts_list_status" = "boost expires on {date}";
|
||||
"lng_boosts_link_title" = "Link for boosting";
|
||||
"lng_boosts_link_subtext" = "Share this link with your subscribers to get more boosts.";
|
||||
|
||||
// Wnd specific
|
||||
|
||||
"lng_wnd_choose_program_menu" = "Choose Default Program...";
|
||||
|
||||
@@ -10,5 +10,6 @@
|
||||
<file alias="cloud_password/email.tgs">../../animations/cloud_password/email.tgs</file>
|
||||
<file alias="ttl.tgs">../../animations/ttl.tgs</file>
|
||||
<file alias="discussion.tgs">../../animations/discussion.tgs</file>
|
||||
<file alias="stats.tgs">../../animations/stats.tgs</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="4.10.2.0" />
|
||||
Version="4.11.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,10,2,0
|
||||
PRODUCTVERSION 4,10,2,0
|
||||
FILEVERSION 4,11,2,0
|
||||
PRODUCTVERSION 4,11,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.10.2.0"
|
||||
VALUE "FileVersion", "4.11.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "4.10.2.0"
|
||||
VALUE "ProductVersion", "4.11.2.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 4,10,2,0
|
||||
PRODUCTVERSION 4,10,2,0
|
||||
FILEVERSION 4,11,2,0
|
||||
PRODUCTVERSION 4,11,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.10.2.0"
|
||||
VALUE "FileVersion", "4.11.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "4.10.2.0"
|
||||
VALUE "ProductVersion", "4.11.2.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -169,9 +169,7 @@ void SendBotCallbackData(
|
||||
void HideSingleUseKeyboard(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<HistoryItem*> item) {
|
||||
controller->content()->hideSingleUseKeyboard(
|
||||
item->history()->peer,
|
||||
item->id);
|
||||
controller->content()->hideSingleUseKeyboard(item->fullId());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -312,12 +310,14 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
|
||||
case ButtonType::Default: {
|
||||
// Copy string before passing it to the sending method
|
||||
// because the original button can be destroyed inside.
|
||||
const auto replyTo = item->isRegular() ? item->id : 0;
|
||||
const auto replyTo = item->isRegular()
|
||||
? item->fullId()
|
||||
: FullMsgId();
|
||||
controller->content()->sendBotCommand({
|
||||
.peer = item->history()->peer,
|
||||
.command = QString(button->text),
|
||||
.context = item->fullId(),
|
||||
.replyTo = replyTo,
|
||||
.replyTo = { replyTo },
|
||||
});
|
||||
} break;
|
||||
|
||||
@@ -363,7 +363,7 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
|
||||
|
||||
case ButtonType::RequestPhone: {
|
||||
HideSingleUseKeyboard(controller, item);
|
||||
const auto itemId = item->id;
|
||||
const auto itemId = item->fullId();
|
||||
const auto topicRootId = item->topicRootId();
|
||||
const auto history = item->history();
|
||||
controller->show(Ui::MakeConfirmBox({
|
||||
@@ -376,7 +376,7 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
|
||||
auto action = Api::SendAction(history);
|
||||
action.clearDraft = false;
|
||||
action.replyTo = {
|
||||
.msgId = itemId,
|
||||
.messageId = itemId,
|
||||
.topicRootId = topicRootId,
|
||||
};
|
||||
history->session().api().shareContact(
|
||||
@@ -397,13 +397,11 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
|
||||
chosen |= PollData::Flag::Quiz;
|
||||
}
|
||||
}
|
||||
const auto replyToId = MsgId(0);
|
||||
const auto topicRootId = MsgId(0);
|
||||
const auto replyTo = FullReplyTo();
|
||||
Window::PeerMenuCreatePoll(
|
||||
controller,
|
||||
item->history()->peer,
|
||||
replyToId,
|
||||
topicRootId,
|
||||
replyTo,
|
||||
chosen,
|
||||
disabled);
|
||||
} break;
|
||||
|
||||
@@ -19,8 +19,8 @@ SendAction::SendAction(
|
||||
SendOptions options)
|
||||
: history(thread->owningHistory())
|
||||
, options(options)
|
||||
, replyTo({ .msgId = thread->topicRootId() }) {
|
||||
replyTo.topicRootId = replyTo.msgId;
|
||||
, replyTo({ .messageId = { history->peer->id, thread->topicRootId() } }) {
|
||||
replyTo.topicRootId = replyTo.messageId.msg;
|
||||
}
|
||||
|
||||
SendOptions DefaultSendWhenOnlineOptions() {
|
||||
@@ -31,7 +31,7 @@ SendOptions DefaultSendWhenOnlineOptions() {
|
||||
}
|
||||
|
||||
MTPInputReplyTo SendAction::mtpReplyTo() const {
|
||||
return Data::ReplyToForMTP(&history->owner(), replyTo);
|
||||
return Data::ReplyToForMTP(history, replyTo);
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "data/data_drafts.h"
|
||||
|
||||
class History;
|
||||
|
||||
namespace Data {
|
||||
@@ -22,7 +24,6 @@ struct SendOptions {
|
||||
TimeId scheduled = 0;
|
||||
bool silent = false;
|
||||
bool handleSupportSwitch = false;
|
||||
bool removeWebPageId = false;
|
||||
bool hideViaBot = false;
|
||||
};
|
||||
[[nodiscard]] SendOptions DefaultSendWhenOnlineOptions();
|
||||
@@ -54,7 +55,7 @@ struct MessageToSend {
|
||||
|
||||
SendAction action;
|
||||
TextWithTags textWithTags;
|
||||
WebPageId webPageId = 0;
|
||||
Data::WebPageDraft webPage;
|
||||
};
|
||||
|
||||
struct RemoteFileInfo {
|
||||
|
||||
@@ -11,8 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_media.h"
|
||||
#include "api/api_text_entities.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_scheduled_messages.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_web_page.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "lang/lang_keys.h"
|
||||
@@ -45,6 +47,7 @@ template <typename DoneCallback, typename FailCallback>
|
||||
mtpRequestId EditMessage(
|
||||
not_null<HistoryItem*> item,
|
||||
const TextWithEntities &textWithEntities,
|
||||
Data::WebPageDraft webpage,
|
||||
SendOptions options,
|
||||
DoneCallback &&done,
|
||||
FailCallback &&fail,
|
||||
@@ -65,15 +68,21 @@ mtpRequestId EditMessage(
|
||||
|
||||
const auto emptyFlag = MTPmessages_EditMessage::Flag(0);
|
||||
const auto flags = emptyFlag
|
||||
| (!text.isEmpty() || media
|
||||
| ((!text.isEmpty() || media)
|
||||
? MTPmessages_EditMessage::Flag::f_message
|
||||
: emptyFlag)
|
||||
| ((media && inputMedia.has_value())
|
||||
? MTPmessages_EditMessage::Flag::f_media
|
||||
: emptyFlag)
|
||||
| (options.removeWebPageId
|
||||
| (webpage.removed
|
||||
? MTPmessages_EditMessage::Flag::f_no_webpage
|
||||
: emptyFlag)
|
||||
| ((!webpage.removed && !webpage.url.isEmpty())
|
||||
? MTPmessages_EditMessage::Flag::f_media
|
||||
: emptyFlag)
|
||||
| ((!webpage.removed && !webpage.url.isEmpty() && webpage.invert)
|
||||
? MTPmessages_EditMessage::Flag::f_invert_media
|
||||
: emptyFlag)
|
||||
| (!sentEntities.v.isEmpty()
|
||||
? MTPmessages_EditMessage::Flag::f_entities
|
||||
: emptyFlag)
|
||||
@@ -89,7 +98,7 @@ mtpRequestId EditMessage(
|
||||
item->history()->peer->input,
|
||||
MTP_int(id),
|
||||
MTP_string(text),
|
||||
inputMedia.value_or(MTPInputMedia()),
|
||||
inputMedia.value_or(Data::WebPageForMTP(webpage, text.isEmpty())),
|
||||
MTPReplyMarkup(),
|
||||
sentEntities,
|
||||
MTP_int(options.scheduled)
|
||||
@@ -133,9 +142,15 @@ mtpRequestId EditMessage(
|
||||
FailCallback &&fail,
|
||||
std::optional<MTPInputMedia> inputMedia = std::nullopt) {
|
||||
const auto &text = item->originalText();
|
||||
const auto webpage = (!item->media() || !item->media()->webpage())
|
||||
? Data::WebPageDraft{ .removed = true }
|
||||
: Data::WebPageDraft{
|
||||
.id = item->media()->webpage()->id,
|
||||
};
|
||||
return EditMessage(
|
||||
item,
|
||||
text,
|
||||
webpage,
|
||||
options,
|
||||
std::forward<DoneCallback>(done),
|
||||
std::forward<FailCallback>(fail),
|
||||
@@ -216,12 +231,19 @@ mtpRequestId EditCaption(
|
||||
SendOptions options,
|
||||
Fn<void()> done,
|
||||
Fn<void(const QString &)> fail) {
|
||||
return EditMessage(item, caption, options, done, fail);
|
||||
return EditMessage(
|
||||
item,
|
||||
caption,
|
||||
Data::WebPageDraft(),
|
||||
options,
|
||||
done,
|
||||
fail);
|
||||
}
|
||||
|
||||
mtpRequestId EditTextMessage(
|
||||
not_null<HistoryItem*> item,
|
||||
const TextWithEntities &caption,
|
||||
Data::WebPageDraft webpage,
|
||||
SendOptions options,
|
||||
Fn<void(mtpRequestId requestId)> done,
|
||||
Fn<void(const QString &, mtpRequestId requestId)> fail) {
|
||||
@@ -229,7 +251,7 @@ mtpRequestId EditTextMessage(
|
||||
applyUpdates();
|
||||
done(id);
|
||||
};
|
||||
return EditMessage(item, caption, options, callback, fail);
|
||||
return EditMessage(item, caption, webpage, options, callback, fail);
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
class HistoryItem;
|
||||
|
||||
namespace Data {
|
||||
struct WebPageDraft;
|
||||
} // namespace Data
|
||||
|
||||
namespace MTP {
|
||||
class Error;
|
||||
} // namespace MTP
|
||||
@@ -48,6 +52,7 @@ mtpRequestId EditCaption(
|
||||
mtpRequestId EditTextMessage(
|
||||
not_null<HistoryItem*> item,
|
||||
const TextWithEntities &caption,
|
||||
Data::WebPageDraft webpage,
|
||||
SendOptions options,
|
||||
Fn<void(mtpRequestId requestId)> done,
|
||||
Fn<void(const QString &error, mtpRequestId requestId)> fail);
|
||||
|
||||
@@ -510,40 +510,57 @@ void PeerPhoto::requestUserPhotos(
|
||||
_userPhotosRequests.emplace(user, requestId);
|
||||
}
|
||||
|
||||
auto PeerPhoto::emojiList(EmojiListType type) -> EmojiListData & {
|
||||
switch (type) {
|
||||
case EmojiListType::Profile: return _profileEmojiList;
|
||||
case EmojiListType::Group: return _groupEmojiList;
|
||||
case EmojiListType::Background: return _backgroundEmojiList;
|
||||
}
|
||||
Unexpected("Type in PeerPhoto::emojiList.");
|
||||
}
|
||||
|
||||
auto PeerPhoto::emojiList(EmojiListType type) const
|
||||
-> const EmojiListData & {
|
||||
return const_cast<PeerPhoto*>(this)->emojiList(type);
|
||||
}
|
||||
|
||||
void PeerPhoto::requestEmojiList(EmojiListType type) {
|
||||
if (_requestIdEmojiList) {
|
||||
auto &list = emojiList(type);
|
||||
if (list.requestId) {
|
||||
return;
|
||||
}
|
||||
const auto isGroup = (type == EmojiListType::Group);
|
||||
const auto d = [=](const MTPEmojiList &result) {
|
||||
_requestIdEmojiList = 0;
|
||||
result.match([](const MTPDemojiListNotModified &data) {
|
||||
}, [&](const MTPDemojiList &data) {
|
||||
auto &list = isGroup ? _profileEmojiList : _groupEmojiList;
|
||||
list = ranges::views::all(
|
||||
data.vdocument_id().v
|
||||
) | ranges::views::transform(&MTPlong::v) | ranges::to_vector;
|
||||
});
|
||||
const auto send = [&](auto &&request) {
|
||||
return _api.request(
|
||||
std::move(request)
|
||||
).done([=](const MTPEmojiList &result) {
|
||||
auto &list = emojiList(type);
|
||||
list.requestId = 0;
|
||||
result.match([](const MTPDemojiListNotModified &data) {
|
||||
}, [&](const MTPDemojiList &data) {
|
||||
list.list = ranges::views::all(
|
||||
data.vdocument_id().v
|
||||
) | ranges::views::transform(
|
||||
&MTPlong::v
|
||||
) | ranges::to_vector;
|
||||
});
|
||||
}).fail([=] {
|
||||
emojiList(type).requestId = 0;
|
||||
}).send();
|
||||
};
|
||||
const auto f = [=] { _requestIdEmojiList = 0; };
|
||||
_requestIdEmojiList = isGroup
|
||||
? _api.request(
|
||||
MTPaccount_GetDefaultGroupPhotoEmojis()
|
||||
).done(d).fail(f).send()
|
||||
: _api.request(
|
||||
MTPaccount_GetDefaultProfilePhotoEmojis()
|
||||
).done(d).fail(f).send();
|
||||
list.requestId = (type == EmojiListType::Profile)
|
||||
? send(MTPaccount_GetDefaultProfilePhotoEmojis())
|
||||
: (type == EmojiListType::Group)
|
||||
? send(MTPaccount_GetDefaultGroupPhotoEmojis())
|
||||
: send(MTPaccount_GetDefaultBackgroundEmojis());
|
||||
}
|
||||
|
||||
rpl::producer<PeerPhoto::EmojiList> PeerPhoto::emojiListValue(
|
||||
EmojiListType type) {
|
||||
auto &list = (type == EmojiListType::Group)
|
||||
? _profileEmojiList
|
||||
: _groupEmojiList;
|
||||
if (list.current().empty() && !_requestIdEmojiList) {
|
||||
auto &list = emojiList(type);
|
||||
if (list.list.current().empty() && !list.requestId) {
|
||||
requestEmojiList(type);
|
||||
}
|
||||
return list.value();
|
||||
return list.list.value();
|
||||
}
|
||||
|
||||
// Non-personal photo in case a personal photo is set.
|
||||
|
||||
@@ -31,6 +31,7 @@ public:
|
||||
enum class EmojiListType {
|
||||
Profile,
|
||||
Group,
|
||||
Background,
|
||||
};
|
||||
|
||||
struct UserPhoto {
|
||||
@@ -73,6 +74,10 @@ private:
|
||||
Suggestion,
|
||||
Fallback,
|
||||
};
|
||||
struct EmojiListData {
|
||||
rpl::variable<EmojiList> list;
|
||||
mtpRequestId requestId = 0;
|
||||
};
|
||||
|
||||
void ready(
|
||||
const FullMsgId &msgId,
|
||||
@@ -84,6 +89,9 @@ private:
|
||||
UploadType type,
|
||||
Fn<void()> done);
|
||||
|
||||
[[nodiscard]] EmojiListData &emojiList(EmojiListType type);
|
||||
[[nodiscard]] const EmojiListData &emojiList(EmojiListType type) const;
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
MTP::Sender _api;
|
||||
|
||||
@@ -101,9 +109,9 @@ private:
|
||||
not_null<UserData*>,
|
||||
not_null<PhotoData*>> _nonPersonalPhotos;
|
||||
|
||||
mtpRequestId _requestIdEmojiList = 0;
|
||||
rpl::variable<EmojiList> _profileEmojiList;
|
||||
rpl::variable<EmojiList> _groupEmojiList;
|
||||
EmojiListData _profileEmojiList;
|
||||
EmojiListData _groupEmojiList;
|
||||
EmojiListData _backgroundEmojiList;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ void Polls::create(
|
||||
|
||||
const auto history = action.history;
|
||||
const auto peer = history->peer;
|
||||
const auto topicRootId = action.replyTo.msgId
|
||||
const auto topicRootId = action.replyTo.messageId
|
||||
? action.replyTo.topicRootId
|
||||
: 0;
|
||||
auto sendFlags = MTPmessages_SendMedia::Flags(0);
|
||||
|
||||
@@ -17,6 +17,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "apiwrap.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] GiftCode Parse(const MTPDpayments_checkedGiftCode &data) {
|
||||
return {
|
||||
.from = peerFromMTP(data.vfrom_id()),
|
||||
.to = data.vto_id() ? peerFromUser(*data.vto_id()) : PeerId(),
|
||||
.giveawayId = data.vgiveaway_msg_id().value_or_empty(),
|
||||
.date = data.vdate().v,
|
||||
.used = data.vused_date().value_or_empty(),
|
||||
.months = data.vmonths().v,
|
||||
.giveaway = data.is_via_giveaway(),
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Premium::Premium(not_null<ApiWrap*> api)
|
||||
: _session(&api->session())
|
||||
@@ -183,6 +198,115 @@ void Premium::reloadCloudSet() {
|
||||
}).send();
|
||||
}
|
||||
|
||||
void Premium::checkGiftCode(
|
||||
const QString &slug,
|
||||
Fn<void(GiftCode)> done) {
|
||||
if (_giftCodeRequestId) {
|
||||
if (_giftCodeSlug == slug) {
|
||||
return;
|
||||
}
|
||||
_api.request(_giftCodeRequestId).cancel();
|
||||
}
|
||||
_giftCodeSlug = slug;
|
||||
_giftCodeRequestId = _api.request(MTPpayments_CheckGiftCode(
|
||||
MTP_string(slug)
|
||||
)).done([=](const MTPpayments_CheckedGiftCode &result) {
|
||||
_giftCodeRequestId = 0;
|
||||
|
||||
const auto &data = result.data();
|
||||
_session->data().processUsers(data.vusers());
|
||||
_session->data().processChats(data.vchats());
|
||||
done(updateGiftCode(slug, Parse(data)));
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_giftCodeRequestId = 0;
|
||||
|
||||
done(updateGiftCode(slug, {}));
|
||||
}).send();
|
||||
}
|
||||
|
||||
GiftCode Premium::updateGiftCode(
|
||||
const QString &slug,
|
||||
const GiftCode &code) {
|
||||
auto &now = _giftCodes[slug];
|
||||
if (now != code) {
|
||||
now = code;
|
||||
_giftCodeUpdated.fire_copy(slug);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
rpl::producer<GiftCode> Premium::giftCodeValue(const QString &slug) const {
|
||||
return _giftCodeUpdated.events_starting_with_copy(
|
||||
slug
|
||||
) | rpl::filter(rpl::mappers::_1 == slug) | rpl::map([=] {
|
||||
const auto i = _giftCodes.find(slug);
|
||||
return (i != end(_giftCodes)) ? i->second : GiftCode();
|
||||
});
|
||||
}
|
||||
|
||||
void Premium::applyGiftCode(const QString &slug, Fn<void(QString)> done) {
|
||||
_api.request(MTPpayments_ApplyGiftCode(
|
||||
MTP_string(slug)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_session->api().applyUpdates(result);
|
||||
done({});
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
done(error.type());
|
||||
}).send();
|
||||
}
|
||||
|
||||
void Premium::resolveGiveawayInfo(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId messageId,
|
||||
Fn<void(GiveawayInfo)> done) {
|
||||
Expects(done != nullptr);
|
||||
|
||||
_giveawayInfoDone = std::move(done);
|
||||
if (_giveawayInfoRequestId) {
|
||||
if (_giveawayInfoPeer == peer
|
||||
&& _giveawayInfoMessageId == messageId) {
|
||||
return;
|
||||
}
|
||||
_api.request(_giveawayInfoRequestId).cancel();
|
||||
}
|
||||
_giveawayInfoPeer = peer;
|
||||
_giveawayInfoMessageId = messageId;
|
||||
_giveawayInfoRequestId = _api.request(MTPpayments_GetGiveawayInfo(
|
||||
_giveawayInfoPeer->input,
|
||||
MTP_int(_giveawayInfoMessageId.bare)
|
||||
)).done([=](const MTPpayments_GiveawayInfo &result) {
|
||||
_giveawayInfoRequestId = 0;
|
||||
|
||||
auto info = GiveawayInfo();
|
||||
result.match([&](const MTPDpayments_giveawayInfo &data) {
|
||||
info.participating = data.is_participating();
|
||||
info.state = data.is_preparing_results()
|
||||
? GiveawayState::Preparing
|
||||
: GiveawayState::Running;
|
||||
info.adminChannelId = data.vadmin_disallowed_chat_id()
|
||||
? ChannelId(*data.vadmin_disallowed_chat_id())
|
||||
: ChannelId();
|
||||
info.disallowedCountry = qs(
|
||||
data.vdisallowed_country().value_or_empty());
|
||||
info.tooEarlyDate
|
||||
= data.vjoined_too_early_date().value_or_empty();
|
||||
info.startDate = data.vstart_date().v;
|
||||
}, [&](const MTPDpayments_giveawayInfoResults &data) {
|
||||
info.state = data.is_refunded()
|
||||
? GiveawayState::Refunded
|
||||
: GiveawayState::Finished;
|
||||
info.giftCode = qs(data.vgift_code_slug().value_or_empty());
|
||||
info.activatedCount = data.vactivated_count().v;
|
||||
info.finishDate = data.vfinish_date().v;
|
||||
info.startDate = data.vstart_date().v;
|
||||
});
|
||||
_giveawayInfoDone(std::move(info));
|
||||
}).fail([=] {
|
||||
_giveawayInfoRequestId = 0;
|
||||
_giveawayInfoDone({});
|
||||
}).send();
|
||||
}
|
||||
|
||||
const Data::SubscriptionOptions &Premium::subscriptionOptions() const {
|
||||
return _subscriptionOptions;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,49 @@ class Session;
|
||||
|
||||
namespace Api {
|
||||
|
||||
struct GiftCode {
|
||||
PeerId from = 0;
|
||||
PeerId to = 0;
|
||||
MsgId giveawayId = 0;
|
||||
TimeId date = 0;
|
||||
TimeId used = 0; // 0 if not used.
|
||||
int months = 0;
|
||||
bool giveaway = false;
|
||||
|
||||
explicit operator bool() const {
|
||||
return months != 0;
|
||||
}
|
||||
|
||||
friend inline bool operator==(
|
||||
const GiftCode&,
|
||||
const GiftCode&) = default;
|
||||
};
|
||||
|
||||
enum class GiveawayState {
|
||||
Invalid,
|
||||
Running,
|
||||
Preparing,
|
||||
Finished,
|
||||
Refunded,
|
||||
};
|
||||
|
||||
struct GiveawayInfo {
|
||||
QString giftCode;
|
||||
QString disallowedCountry;
|
||||
ChannelId adminChannelId = 0;
|
||||
GiveawayState state = GiveawayState::Invalid;
|
||||
TimeId tooEarlyDate = 0;
|
||||
TimeId finishDate = 0;
|
||||
TimeId startDate = 0;
|
||||
int winnersCount = 0;
|
||||
int activatedCount = 0;
|
||||
bool participating = false;
|
||||
|
||||
explicit operator bool() const {
|
||||
return state != GiveawayState::Invalid;
|
||||
}
|
||||
};
|
||||
|
||||
class Premium final {
|
||||
public:
|
||||
explicit Premium(not_null<ApiWrap*> api);
|
||||
@@ -40,6 +83,19 @@ public:
|
||||
[[nodiscard]] int64 monthlyAmount() const;
|
||||
[[nodiscard]] QString monthlyCurrency() const;
|
||||
|
||||
void checkGiftCode(
|
||||
const QString &slug,
|
||||
Fn<void(GiftCode)> done);
|
||||
GiftCode updateGiftCode(const QString &slug, const GiftCode &code);
|
||||
[[nodiscard]] rpl::producer<GiftCode> giftCodeValue(
|
||||
const QString &slug) const;
|
||||
void applyGiftCode(const QString &slug, Fn<void(QString)> done);
|
||||
|
||||
void resolveGiveawayInfo(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId messageId,
|
||||
Fn<void(GiveawayInfo)> done);
|
||||
|
||||
[[nodiscard]] auto subscriptionOptions() const
|
||||
-> const Data::SubscriptionOptions &;
|
||||
|
||||
@@ -71,6 +127,16 @@ private:
|
||||
int64 _monthlyAmount = 0;
|
||||
QString _monthlyCurrency;
|
||||
|
||||
mtpRequestId _giftCodeRequestId = 0;
|
||||
QString _giftCodeSlug;
|
||||
base::flat_map<QString, GiftCode> _giftCodes;
|
||||
rpl::event_stream<QString> _giftCodeUpdated;
|
||||
|
||||
mtpRequestId _giveawayInfoRequestId = 0;
|
||||
PeerData *_giveawayInfoPeer = nullptr;
|
||||
MsgId _giveawayInfoMessageId = 0;
|
||||
Fn<void(GiveawayInfo)> _giveawayInfoDone;
|
||||
|
||||
Data::SubscriptionOptions _subscriptionOptions;
|
||||
|
||||
};
|
||||
|
||||
@@ -270,7 +270,6 @@ bool SendDice(MessageToSend &message) {
|
||||
flags |= MessageFlag::HasReplyInfo;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to;
|
||||
}
|
||||
const auto replyHeader = NewMessageReplyHeader(message.action);
|
||||
const auto anonymousPost = peer->amAnonymous();
|
||||
const auto silentPost = ShouldSendSilent(peer, message.action.options);
|
||||
InnerFillMessagePostFlags(message.action.options, peer, flags);
|
||||
@@ -368,9 +367,9 @@ void SendConfirmedFile(
|
||||
|
||||
if (!isEditing) {
|
||||
const auto histories = &session->data().histories();
|
||||
file->to.replyTo.msgId = histories->convertTopicReplyToId(
|
||||
file->to.replyTo.messageId = histories->convertTopicReplyToId(
|
||||
history,
|
||||
file->to.replyTo.msgId);
|
||||
file->to.replyTo.messageId);
|
||||
file->to.replyTo.topicRootId = histories->convertTopicReplyToId(
|
||||
history,
|
||||
file->to.replyTo.topicRootId);
|
||||
@@ -399,7 +398,6 @@ void SendConfirmedFile(
|
||||
if (file->to.replyTo) {
|
||||
flags |= MessageFlag::HasReplyInfo;
|
||||
}
|
||||
const auto replyHeader = NewMessageReplyHeader(action);
|
||||
const auto anonymousPost = peer->amAnonymous();
|
||||
const auto silentPost = ShouldSendSilent(peer, file->to.options);
|
||||
FillMessagePostFlags(action, peer, flags);
|
||||
|
||||
594
Telegram/SourceFiles/api/api_statistics.cpp
Normal file
@@ -0,0 +1,594 @@
|
||||
/*
|
||||
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_statistics.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "history/history.h"
|
||||
#include "main/main_session.h"
|
||||
#include "statistics/statistics_data_deserialize.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] Data::StatisticalGraph StatisticalGraphFromTL(
|
||||
const MTPStatsGraph &tl) {
|
||||
return tl.match([&](const MTPDstatsGraph &d) {
|
||||
using namespace Statistic;
|
||||
const auto zoomToken = d.vzoom_token().has_value()
|
||||
? qs(*d.vzoom_token()).toUtf8()
|
||||
: QByteArray();
|
||||
return Data::StatisticalGraph{
|
||||
StatisticalChartFromJSON(qs(d.vjson().data().vdata()).toUtf8()),
|
||||
zoomToken,
|
||||
};
|
||||
}, [&](const MTPDstatsGraphAsync &data) {
|
||||
return Data::StatisticalGraph{
|
||||
.zoomToken = qs(data.vtoken()).toUtf8(),
|
||||
};
|
||||
}, [&](const MTPDstatsGraphError &data) {
|
||||
return Data::StatisticalGraph{ .error = qs(data.verror()) };
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] Data::StatisticalValue StatisticalValueFromTL(
|
||||
const MTPStatsAbsValueAndPrev &tl) {
|
||||
const auto current = tl.data().vcurrent().v;
|
||||
const auto previous = tl.data().vprevious().v;
|
||||
return Data::StatisticalValue{
|
||||
.value = current,
|
||||
.previousValue = previous,
|
||||
.growthRatePercentage = previous
|
||||
? std::abs((current - previous) / float64(previous) * 100.)
|
||||
: 0,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] Data::ChannelStatistics ChannelStatisticsFromTL(
|
||||
const MTPDstats_broadcastStats &data) {
|
||||
const auto &tlUnmuted = data.venabled_notifications().data();
|
||||
const auto unmuted = (!tlUnmuted.vtotal().v)
|
||||
? 0.
|
||||
: std::clamp(
|
||||
tlUnmuted.vpart().v / tlUnmuted.vtotal().v * 100.,
|
||||
0.,
|
||||
100.);
|
||||
using Recent = MTPMessageInteractionCounters;
|
||||
auto recentMessages = ranges::views::all(
|
||||
data.vrecent_message_interactions().v
|
||||
) | ranges::views::transform([&](const Recent &tl) {
|
||||
return Data::StatisticsMessageInteractionInfo{
|
||||
.messageId = tl.data().vmsg_id().v,
|
||||
.viewsCount = tl.data().vviews().v,
|
||||
.forwardsCount = tl.data().vforwards().v,
|
||||
};
|
||||
}) | ranges::to_vector;
|
||||
|
||||
return {
|
||||
.startDate = data.vperiod().data().vmin_date().v,
|
||||
.endDate = data.vperiod().data().vmax_date().v,
|
||||
|
||||
.memberCount = StatisticalValueFromTL(data.vfollowers()),
|
||||
.meanViewCount = StatisticalValueFromTL(data.vviews_per_post()),
|
||||
.meanShareCount = StatisticalValueFromTL(data.vshares_per_post()),
|
||||
|
||||
.enabledNotificationsPercentage = unmuted,
|
||||
|
||||
.memberCountGraph = StatisticalGraphFromTL(
|
||||
data.vgrowth_graph()),
|
||||
|
||||
.joinGraph = StatisticalGraphFromTL(
|
||||
data.vfollowers_graph()),
|
||||
|
||||
.muteGraph = StatisticalGraphFromTL(
|
||||
data.vmute_graph()),
|
||||
|
||||
.viewCountByHourGraph = StatisticalGraphFromTL(
|
||||
data.vtop_hours_graph()),
|
||||
|
||||
.viewCountBySourceGraph = StatisticalGraphFromTL(
|
||||
data.vviews_by_source_graph()),
|
||||
|
||||
.joinBySourceGraph = StatisticalGraphFromTL(
|
||||
data.vnew_followers_by_source_graph()),
|
||||
|
||||
.languageGraph = StatisticalGraphFromTL(
|
||||
data.vlanguages_graph()),
|
||||
|
||||
.messageInteractionGraph = StatisticalGraphFromTL(
|
||||
data.vinteractions_graph()),
|
||||
|
||||
.instantViewInteractionGraph = StatisticalGraphFromTL(
|
||||
data.viv_interactions_graph()),
|
||||
|
||||
.recentMessageInteractions = std::move(recentMessages),
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] Data::SupergroupStatistics SupergroupStatisticsFromTL(
|
||||
const MTPDstats_megagroupStats &data) {
|
||||
using Senders = MTPStatsGroupTopPoster;
|
||||
using Administrators = MTPStatsGroupTopAdmin;
|
||||
using Inviters = MTPStatsGroupTopInviter;
|
||||
|
||||
auto topSenders = ranges::views::all(
|
||||
data.vtop_posters().v
|
||||
) | ranges::views::transform([&](const Senders &tl) {
|
||||
return Data::StatisticsMessageSenderInfo{
|
||||
.userId = UserId(tl.data().vuser_id().v),
|
||||
.sentMessageCount = tl.data().vmessages().v,
|
||||
.averageCharacterCount = tl.data().vavg_chars().v,
|
||||
};
|
||||
}) | ranges::to_vector;
|
||||
auto topAdministrators = ranges::views::all(
|
||||
data.vtop_admins().v
|
||||
) | ranges::views::transform([&](const Administrators &tl) {
|
||||
return Data::StatisticsAdministratorActionsInfo{
|
||||
.userId = UserId(tl.data().vuser_id().v),
|
||||
.deletedMessageCount = tl.data().vdeleted().v,
|
||||
.bannedUserCount = tl.data().vkicked().v,
|
||||
.restrictedUserCount = tl.data().vbanned().v,
|
||||
};
|
||||
}) | ranges::to_vector;
|
||||
auto topInviters = ranges::views::all(
|
||||
data.vtop_inviters().v
|
||||
) | ranges::views::transform([&](const Inviters &tl) {
|
||||
return Data::StatisticsInviterInfo{
|
||||
.userId = UserId(tl.data().vuser_id().v),
|
||||
.addedMemberCount = tl.data().vinvitations().v,
|
||||
};
|
||||
}) | ranges::to_vector;
|
||||
|
||||
return {
|
||||
.startDate = data.vperiod().data().vmin_date().v,
|
||||
.endDate = data.vperiod().data().vmax_date().v,
|
||||
|
||||
.memberCount = StatisticalValueFromTL(data.vmembers()),
|
||||
.messageCount = StatisticalValueFromTL(data.vmessages()),
|
||||
.viewerCount = StatisticalValueFromTL(data.vviewers()),
|
||||
.senderCount = StatisticalValueFromTL(data.vposters()),
|
||||
|
||||
.memberCountGraph = StatisticalGraphFromTL(
|
||||
data.vgrowth_graph()),
|
||||
|
||||
.joinGraph = StatisticalGraphFromTL(
|
||||
data.vmembers_graph()),
|
||||
|
||||
.joinBySourceGraph = StatisticalGraphFromTL(
|
||||
data.vnew_members_by_source_graph()),
|
||||
|
||||
.languageGraph = StatisticalGraphFromTL(
|
||||
data.vlanguages_graph()),
|
||||
|
||||
.messageContentGraph = StatisticalGraphFromTL(
|
||||
data.vmessages_graph()),
|
||||
|
||||
.actionGraph = StatisticalGraphFromTL(
|
||||
data.vactions_graph()),
|
||||
|
||||
.dayGraph = StatisticalGraphFromTL(
|
||||
data.vtop_hours_graph()),
|
||||
|
||||
.weekGraph = StatisticalGraphFromTL(
|
||||
data.vweekdays_graph()),
|
||||
|
||||
.topSenders = std::move(topSenders),
|
||||
.topAdministrators = std::move(topAdministrators),
|
||||
.topInviters = std::move(topInviters),
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Statistics::Statistics(not_null<ApiWrap*> api)
|
||||
: _api(&api->instance()) {
|
||||
}
|
||||
|
||||
rpl::producer<rpl::no_value, QString> Statistics::request(
|
||||
not_null<PeerData*> peer) {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
const auto channel = peer->asChannel();
|
||||
if (!channel) {
|
||||
return lifetime;
|
||||
}
|
||||
|
||||
if (!channel->isMegagroup()) {
|
||||
_api.request(MTPstats_GetBroadcastStats(
|
||||
MTP_flags(MTPstats_GetBroadcastStats::Flags(0)),
|
||||
channel->inputChannel
|
||||
)).done([=](const MTPstats_BroadcastStats &result) {
|
||||
_channelStats = ChannelStatisticsFromTL(result.data());
|
||||
consumer.put_done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
} else {
|
||||
_api.request(MTPstats_GetMegagroupStats(
|
||||
MTP_flags(MTPstats_GetMegagroupStats::Flags(0)),
|
||||
channel->inputChannel
|
||||
)).done([=](const MTPstats_MegagroupStats &result) {
|
||||
_supergroupStats = SupergroupStatisticsFromTL(result.data());
|
||||
peer->owner().processUsers(result.data().vusers());
|
||||
consumer.put_done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
}
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
Statistics::GraphResult Statistics::requestZoom(
|
||||
not_null<PeerData*> peer,
|
||||
const QString &token,
|
||||
float64 x) {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
const auto channel = peer->asChannel();
|
||||
if (!channel) {
|
||||
return lifetime;
|
||||
}
|
||||
const auto wasEmpty = _zoomDeque.empty();
|
||||
_zoomDeque.push_back([=] {
|
||||
_api.request(MTPstats_LoadAsyncGraph(
|
||||
MTP_flags(x
|
||||
? MTPstats_LoadAsyncGraph::Flag::f_x
|
||||
: MTPstats_LoadAsyncGraph::Flag(0)),
|
||||
MTP_string(token),
|
||||
MTP_long(x)
|
||||
)).done([=](const MTPStatsGraph &result) {
|
||||
consumer.put_next(StatisticalGraphFromTL(result));
|
||||
consumer.put_done();
|
||||
if (!_zoomDeque.empty()) {
|
||||
_zoomDeque.pop_front();
|
||||
if (!_zoomDeque.empty()) {
|
||||
_zoomDeque.front()();
|
||||
}
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
});
|
||||
if (wasEmpty) {
|
||||
_zoomDeque.front()();
|
||||
}
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
Statistics::GraphResult Statistics::requestMessage(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId msgId) {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
const auto channel = peer->asChannel();
|
||||
if (!channel) {
|
||||
return lifetime;
|
||||
}
|
||||
|
||||
_api.request(MTPstats_GetMessageStats(
|
||||
MTP_flags(MTPstats_GetMessageStats::Flags(0)),
|
||||
channel->inputChannel,
|
||||
MTP_int(msgId.bare)
|
||||
)).done([=](const MTPstats_MessageStats &result) {
|
||||
consumer.put_next(
|
||||
StatisticalGraphFromTL(result.data().vviews_graph()));
|
||||
consumer.put_done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
Data::ChannelStatistics Statistics::channelStats() const {
|
||||
return _channelStats;
|
||||
}
|
||||
|
||||
Data::SupergroupStatistics Statistics::supergroupStats() const {
|
||||
return _supergroupStats;
|
||||
}
|
||||
|
||||
PublicForwards::PublicForwards(
|
||||
not_null<ChannelData*> channel,
|
||||
FullMsgId fullId)
|
||||
: _channel(channel)
|
||||
, _fullId(fullId)
|
||||
, _api(&channel->session().api().instance()) {
|
||||
}
|
||||
|
||||
void PublicForwards::request(
|
||||
const Data::PublicForwardsSlice::OffsetToken &token,
|
||||
Fn<void(Data::PublicForwardsSlice)> done) {
|
||||
if (_requestId) {
|
||||
return;
|
||||
}
|
||||
const auto offsetPeer = _channel->owner().peer(token.fullId.peer);
|
||||
const auto tlOffsetPeer = offsetPeer
|
||||
? offsetPeer->input
|
||||
: MTP_inputPeerEmpty();
|
||||
constexpr auto kLimit = tl::make_int(100);
|
||||
_requestId = _api.request(MTPstats_GetMessagePublicForwards(
|
||||
_channel->inputChannel,
|
||||
MTP_int(_fullId.msg),
|
||||
MTP_int(token.rate),
|
||||
tlOffsetPeer,
|
||||
MTP_int(token.fullId.msg),
|
||||
kLimit
|
||||
)).done([=, channel = _channel](const MTPmessages_Messages &result) {
|
||||
using Messages = QVector<FullMsgId>;
|
||||
_requestId = 0;
|
||||
|
||||
auto nextToken = Data::PublicForwardsSlice::OffsetToken();
|
||||
const auto process = [&](const MTPVector<MTPMessage> &messages) {
|
||||
auto result = Messages();
|
||||
for (const auto &message : messages.v) {
|
||||
const auto msgId = IdFromMessage(message);
|
||||
const auto peerId = PeerFromMessage(message);
|
||||
const auto lastDate = DateFromMessage(message);
|
||||
if (const auto peer = channel->owner().peerLoaded(peerId)) {
|
||||
if (lastDate) {
|
||||
channel->owner().addNewMessage(
|
||||
message,
|
||||
MessageFlags(),
|
||||
NewMessageType::Existing);
|
||||
nextToken.fullId = { peerId, msgId };
|
||||
result.push_back(nextToken.fullId);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
auto allLoaded = false;
|
||||
auto fullCount = 0;
|
||||
auto messages = result.match([&](const MTPDmessages_messages &data) {
|
||||
channel->owner().processUsers(data.vusers());
|
||||
channel->owner().processChats(data.vchats());
|
||||
auto list = process(data.vmessages());
|
||||
allLoaded = true;
|
||||
fullCount = list.size();
|
||||
return list;
|
||||
}, [&](const MTPDmessages_messagesSlice &data) {
|
||||
channel->owner().processUsers(data.vusers());
|
||||
channel->owner().processChats(data.vchats());
|
||||
auto list = process(data.vmessages());
|
||||
|
||||
if (const auto nextRate = data.vnext_rate()) {
|
||||
const auto rateUpdated = (nextRate->v != token.rate);
|
||||
if (rateUpdated) {
|
||||
nextToken.rate = nextRate->v;
|
||||
} else {
|
||||
allLoaded = true;
|
||||
}
|
||||
}
|
||||
fullCount = data.vcount().v;
|
||||
return list;
|
||||
}, [&](const MTPDmessages_channelMessages &data) {
|
||||
channel->owner().processUsers(data.vusers());
|
||||
channel->owner().processChats(data.vchats());
|
||||
auto list = process(data.vmessages());
|
||||
allLoaded = true;
|
||||
fullCount = data.vcount().v;
|
||||
return list;
|
||||
}, [&](const MTPDmessages_messagesNotModified &) {
|
||||
allLoaded = true;
|
||||
return Messages();
|
||||
});
|
||||
|
||||
_lastTotal = std::max(_lastTotal, fullCount);
|
||||
done({
|
||||
.list = std::move(messages),
|
||||
.total = _lastTotal,
|
||||
.allLoaded = allLoaded,
|
||||
.token = nextToken,
|
||||
});
|
||||
}).fail([=] {
|
||||
_requestId = 0;
|
||||
}).send();
|
||||
}
|
||||
|
||||
MessageStatistics::MessageStatistics(
|
||||
not_null<ChannelData*> channel,
|
||||
FullMsgId fullId)
|
||||
: _publicForwards(channel, fullId)
|
||||
, _channel(channel)
|
||||
, _fullId(fullId)
|
||||
, _api(&channel->session().api().instance()) {
|
||||
}
|
||||
|
||||
Data::PublicForwardsSlice MessageStatistics::firstSlice() const {
|
||||
return _firstSlice;
|
||||
}
|
||||
|
||||
void MessageStatistics::request(Fn<void(Data::MessageStatistics)> done) {
|
||||
if (_channel->isMegagroup()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto requestFirstPublicForwards = [=](
|
||||
const Data::StatisticalGraph &messageGraph,
|
||||
const Data::StatisticsMessageInteractionInfo &info) {
|
||||
_publicForwards.request({}, [=](Data::PublicForwardsSlice slice) {
|
||||
const auto total = slice.total;
|
||||
_firstSlice = std::move(slice);
|
||||
done({
|
||||
.messageInteractionGraph = messageGraph,
|
||||
.publicForwards = total,
|
||||
.privateForwards = info.forwardsCount - total,
|
||||
.views = info.viewsCount,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const auto requestPrivateForwards = [=](
|
||||
const Data::StatisticalGraph &messageGraph) {
|
||||
_api.request(MTPchannels_GetMessages(
|
||||
_channel->inputChannel,
|
||||
MTP_vector<MTPInputMessage>(
|
||||
1,
|
||||
MTP_inputMessageID(MTP_int(_fullId.msg))))
|
||||
).done([=](const MTPmessages_Messages &result) {
|
||||
const auto process = [&](const MTPVector<MTPMessage> &messages) {
|
||||
const auto &message = messages.v.front();
|
||||
return message.match([&](const MTPDmessage &data) {
|
||||
return Data::StatisticsMessageInteractionInfo{
|
||||
.messageId = IdFromMessage(message),
|
||||
.viewsCount = data.vviews()
|
||||
? data.vviews()->v
|
||||
: 0,
|
||||
.forwardsCount = data.vforwards()
|
||||
? data.vforwards()->v
|
||||
: 0,
|
||||
};
|
||||
}, [](const MTPDmessageEmpty &) {
|
||||
return Data::StatisticsMessageInteractionInfo();
|
||||
}, [](const MTPDmessageService &) {
|
||||
return Data::StatisticsMessageInteractionInfo();
|
||||
});
|
||||
};
|
||||
|
||||
auto info = result.match([&](const MTPDmessages_messages &data) {
|
||||
return process(data.vmessages());
|
||||
}, [&](const MTPDmessages_messagesSlice &data) {
|
||||
return process(data.vmessages());
|
||||
}, [&](const MTPDmessages_channelMessages &data) {
|
||||
return process(data.vmessages());
|
||||
}, [](const MTPDmessages_messagesNotModified &) {
|
||||
return Data::StatisticsMessageInteractionInfo();
|
||||
});
|
||||
|
||||
requestFirstPublicForwards(messageGraph, std::move(info));
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
requestFirstPublicForwards(messageGraph, {});
|
||||
}).send();
|
||||
};
|
||||
|
||||
_api.request(MTPstats_GetMessageStats(
|
||||
MTP_flags(MTPstats_GetMessageStats::Flags(0)),
|
||||
_channel->inputChannel,
|
||||
MTP_int(_fullId.msg.bare)
|
||||
)).done([=](const MTPstats_MessageStats &result) {
|
||||
requestPrivateForwards(
|
||||
StatisticalGraphFromTL(result.data().vviews_graph()));
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
requestPrivateForwards({});
|
||||
}).send();
|
||||
|
||||
}
|
||||
|
||||
Boosts::Boosts(not_null<PeerData*> peer)
|
||||
: _peer(peer)
|
||||
, _api(&peer->session().api().instance()) {
|
||||
}
|
||||
|
||||
rpl::producer<rpl::no_value, QString> Boosts::request() {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
const auto channel = _peer->asChannel();
|
||||
if (!channel || channel->isMegagroup()) {
|
||||
return lifetime;
|
||||
}
|
||||
|
||||
_api.request(MTPpremium_GetBoostsStatus(
|
||||
_peer->input
|
||||
)).done([=](const MTPpremium_BoostsStatus &result) {
|
||||
const auto &data = result.data();
|
||||
const auto hasPremium = !!data.vpremium_audience();
|
||||
const auto premiumMemberCount = hasPremium
|
||||
? std::max(0, int(data.vpremium_audience()->data().vpart().v))
|
||||
: 0;
|
||||
const auto participantCount = hasPremium
|
||||
? std::max(
|
||||
int(data.vpremium_audience()->data().vtotal().v),
|
||||
premiumMemberCount)
|
||||
: 0;
|
||||
const auto premiumMemberPercentage = (participantCount > 0)
|
||||
? (100. * premiumMemberCount / participantCount)
|
||||
: 0;
|
||||
|
||||
_boostStatus.overview = Data::BoostsOverview{
|
||||
.isBoosted = data.is_my_boost(),
|
||||
.level = std::max(data.vlevel().v, 0),
|
||||
.boostCount = std::max(
|
||||
data.vboosts().v,
|
||||
data.vcurrent_level_boosts().v),
|
||||
.currentLevelBoostCount = data.vcurrent_level_boosts().v,
|
||||
.nextLevelBoostCount = data.vnext_level_boosts()
|
||||
? data.vnext_level_boosts()->v
|
||||
: 0,
|
||||
.premiumMemberCount = premiumMemberCount,
|
||||
.premiumMemberPercentage = premiumMemberPercentage,
|
||||
};
|
||||
_boostStatus.link = qs(data.vboost_url());
|
||||
|
||||
requestBoosts({}, [=](Data::BoostsListSlice &&slice) {
|
||||
_boostStatus.firstSlice = std::move(slice);
|
||||
consumer.put_done();
|
||||
});
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
void Boosts::requestBoosts(
|
||||
const Data::BoostsListSlice::OffsetToken &token,
|
||||
Fn<void(Data::BoostsListSlice)> done) {
|
||||
if (_requestId) {
|
||||
return;
|
||||
}
|
||||
constexpr auto kTlFirstSlice = tl::make_int(kFirstSlice);
|
||||
constexpr auto kTlLimit = tl::make_int(kLimit);
|
||||
_requestId = _api.request(MTPpremium_GetBoostsList(
|
||||
MTP_flags(0),
|
||||
_peer->input,
|
||||
MTP_string(token.next),
|
||||
token.next.isEmpty() ? kTlFirstSlice : kTlLimit
|
||||
)).done([=](const MTPpremium_BoostsList &result) {
|
||||
_requestId = 0;
|
||||
|
||||
const auto &data = result.data();
|
||||
_peer->owner().processUsers(data.vusers());
|
||||
|
||||
auto list = std::vector<Data::Boost>();
|
||||
list.reserve(data.vboosts().v.size());
|
||||
for (const auto &boost : data.vboosts().v) {
|
||||
list.push_back({
|
||||
boost.data().vuser_id().value_or_empty(),
|
||||
QDateTime::fromSecsSinceEpoch(boost.data().vexpires().v),
|
||||
});
|
||||
}
|
||||
done(Data::BoostsListSlice{
|
||||
.list = std::move(list),
|
||||
.total = data.vcount().v,
|
||||
.allLoaded = (data.vcount().v == data.vboosts().v.size()),
|
||||
.token = Data::BoostsListSlice::OffsetToken{
|
||||
data.vnext_offset()
|
||||
? qs(*data.vnext_offset())
|
||||
: QString()
|
||||
},
|
||||
});
|
||||
}).fail([=] {
|
||||
_requestId = 0;
|
||||
}).send();
|
||||
}
|
||||
|
||||
Data::BoostStatus Boosts::boostStatus() const {
|
||||
return _boostStatus;
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
110
Telegram/SourceFiles/api/api_statistics.h
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
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_boosts.h"
|
||||
#include "data/data_statistics.h"
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
class ApiWrap;
|
||||
class ChannelData;
|
||||
class PeerData;
|
||||
|
||||
namespace Api {
|
||||
|
||||
class Statistics final {
|
||||
public:
|
||||
explicit Statistics(not_null<ApiWrap*> api);
|
||||
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> request(
|
||||
not_null<PeerData*> peer);
|
||||
using GraphResult = rpl::producer<Data::StatisticalGraph, QString>;
|
||||
[[nodiscard]] GraphResult requestZoom(
|
||||
not_null<PeerData*> peer,
|
||||
const QString &token,
|
||||
float64 x);
|
||||
[[nodiscard]] GraphResult requestMessage(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId msgId);
|
||||
|
||||
[[nodiscard]] Data::ChannelStatistics channelStats() const;
|
||||
[[nodiscard]] Data::SupergroupStatistics supergroupStats() const;
|
||||
|
||||
private:
|
||||
Data::ChannelStatistics _channelStats;
|
||||
Data::SupergroupStatistics _supergroupStats;
|
||||
MTP::Sender _api;
|
||||
|
||||
std::deque<Fn<void()>> _zoomDeque;
|
||||
|
||||
};
|
||||
|
||||
class PublicForwards final {
|
||||
public:
|
||||
explicit PublicForwards(not_null<ChannelData*> channel, FullMsgId fullId);
|
||||
|
||||
void request(
|
||||
const Data::PublicForwardsSlice::OffsetToken &token,
|
||||
Fn<void(Data::PublicForwardsSlice)> done);
|
||||
|
||||
private:
|
||||
const not_null<ChannelData*> _channel;
|
||||
const FullMsgId _fullId;
|
||||
mtpRequestId _requestId = 0;
|
||||
int _lastTotal = 0;
|
||||
|
||||
MTP::Sender _api;
|
||||
|
||||
};
|
||||
|
||||
class MessageStatistics final {
|
||||
public:
|
||||
explicit MessageStatistics(
|
||||
not_null<ChannelData*> channel,
|
||||
FullMsgId fullId);
|
||||
|
||||
void request(Fn<void(Data::MessageStatistics)> done);
|
||||
|
||||
[[nodiscard]] Data::PublicForwardsSlice firstSlice() const;
|
||||
|
||||
private:
|
||||
PublicForwards _publicForwards;
|
||||
const not_null<ChannelData*> _channel;
|
||||
const FullMsgId _fullId;
|
||||
|
||||
Data::PublicForwardsSlice _firstSlice;
|
||||
|
||||
mtpRequestId _requestId = 0;
|
||||
MTP::Sender _api;
|
||||
|
||||
};
|
||||
|
||||
class Boosts final {
|
||||
public:
|
||||
explicit Boosts(not_null<PeerData*> peer);
|
||||
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> request();
|
||||
void requestBoosts(
|
||||
const Data::BoostsListSlice::OffsetToken &token,
|
||||
Fn<void(Data::BoostsListSlice)> done);
|
||||
|
||||
[[nodiscard]] Data::BoostStatus boostStatus() const;
|
||||
|
||||
static constexpr auto kFirstSlice = int(10);
|
||||
static constexpr auto kLimit = int(40);
|
||||
|
||||
private:
|
||||
const not_null<PeerData*> _peer;
|
||||
Data::BoostStatus _boostStatus;
|
||||
|
||||
MTP::Sender _api;
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Api
|
||||
@@ -114,6 +114,7 @@ EntitiesInText EntitiesFromMTP(
|
||||
case mtpc_messageEntityStrike: { auto &d = entity.c_messageEntityStrike(); result.push_back({ EntityType::StrikeOut, d.voffset().v, d.vlength().v }); } break;
|
||||
case mtpc_messageEntityCode: { auto &d = entity.c_messageEntityCode(); result.push_back({ EntityType::Code, d.voffset().v, d.vlength().v }); } break;
|
||||
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_messageEntityBlockquote: { auto &d = entity.c_messageEntityBlockquote(); result.push_back({ EntityType::Blockquote, d.voffset().v, d.vlength().v }); } 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: {
|
||||
@@ -142,6 +143,7 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(
|
||||
&& entity.type() != EntityType::StrikeOut
|
||||
&& entity.type() != EntityType::Code // #TODO entities
|
||||
&& entity.type() != EntityType::Pre
|
||||
&& entity.type() != EntityType::Blockquote
|
||||
&& entity.type() != EntityType::Spoiler
|
||||
&& entity.type() != EntityType::MentionName
|
||||
&& entity.type() != EntityType::CustomUrl
|
||||
@@ -170,6 +172,7 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(
|
||||
case EntityType::StrikeOut: v.push_back(MTP_messageEntityStrike(offset, length)); break;
|
||||
case EntityType::Code: v.push_back(MTP_messageEntityCode(offset, length)); break; // #TODO entities
|
||||
case EntityType::Pre: v.push_back(MTP_messageEntityPre(offset, length, MTP_string(entity.data()))); break;
|
||||
case EntityType::Blockquote: v.push_back(MTP_messageEntityBlockquote(offset, length)); break;
|
||||
case EntityType::Spoiler: v.push_back(MTP_messageEntitySpoiler(offset, length)); break;
|
||||
case EntityType::CustomEmoji: {
|
||||
if (const auto valid = CustomEmojiEntity(offset, length, entity.data())) {
|
||||
|
||||
@@ -2071,7 +2071,10 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
windows.front()->window().show(Ui::MakeInformBox(text));
|
||||
}
|
||||
} else {
|
||||
session().data().serviceNotification(text, d.vmedia());
|
||||
session().data().serviceNotification(
|
||||
text,
|
||||
d.vmedia(),
|
||||
d.is_invert_media());
|
||||
session().api().authorizations().reload();
|
||||
}
|
||||
} break;
|
||||
|
||||
@@ -2135,14 +2135,13 @@ void ApiWrap::saveDraftsToCloud() {
|
||||
|
||||
auto flags = MTPmessages_SaveDraft::Flags(0);
|
||||
auto &textWithTags = cloudDraft->textWithTags;
|
||||
if (cloudDraft->previewState != Data::PreviewState::Allowed) {
|
||||
if (cloudDraft->webpage.removed) {
|
||||
flags |= MTPmessages_SaveDraft::Flag::f_no_webpage;
|
||||
} else if (!cloudDraft->webpage.url.isEmpty()) {
|
||||
flags |= MTPmessages_SaveDraft::Flag::f_media;
|
||||
}
|
||||
if (cloudDraft->msgId) {
|
||||
flags |= MTPmessages_SaveDraft::Flag::f_reply_to_msg_id;
|
||||
}
|
||||
if (cloudDraft->topicRootId) {
|
||||
flags |= MTPmessages_SaveDraft::Flag::f_top_msg_id;
|
||||
if (cloudDraft->reply.messageId || cloudDraft->reply.topicRootId) {
|
||||
flags |= MTPmessages_SaveDraft::Flag::f_reply_to;
|
||||
}
|
||||
if (!textWithTags.tags.isEmpty()) {
|
||||
flags |= MTPmessages_SaveDraft::Flag::f_entities;
|
||||
@@ -2155,11 +2154,13 @@ void ApiWrap::saveDraftsToCloud() {
|
||||
history->startSavingCloudDraft(topicRootId);
|
||||
cloudDraft->saveRequestId = request(MTPmessages_SaveDraft(
|
||||
MTP_flags(flags),
|
||||
MTP_int(cloudDraft->msgId),
|
||||
MTP_int(cloudDraft->topicRootId),
|
||||
ReplyToForMTP(history, cloudDraft->reply),
|
||||
history->peer->input,
|
||||
MTP_string(textWithTags.text),
|
||||
entities
|
||||
entities,
|
||||
Data::WebPageForMTP(
|
||||
cloudDraft->webpage,
|
||||
textWithTags.text.isEmpty())
|
||||
)).done([=](const MTPBool &result, const MTP::Response &response) {
|
||||
const auto requestId = response.requestId;
|
||||
history->finishSavingCloudDraft(
|
||||
@@ -2246,7 +2247,7 @@ void ApiWrap::gotStickerSet(
|
||||
}
|
||||
|
||||
void ApiWrap::requestWebPageDelayed(not_null<WebPageData*> page) {
|
||||
if (page->pendingTill <= 0) {
|
||||
if (page->failed || !page->pendingTill) {
|
||||
return;
|
||||
}
|
||||
_webPagesPending.emplace(page, 0);
|
||||
@@ -2551,7 +2552,8 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &resu
|
||||
for (auto i = _webPagesPending.begin(); i != _webPagesPending.cend();) {
|
||||
if (i->second == req) {
|
||||
if (i->first->pendingTill > 0) {
|
||||
i->first->pendingTill = -1;
|
||||
i->first->pendingTill = 0;
|
||||
i->first->failed = 1;
|
||||
_session->data().notifyWebPageUpdateDelayed(i->first);
|
||||
}
|
||||
i = _webPagesPending.erase(i);
|
||||
@@ -3374,7 +3376,6 @@ void ApiWrap::sendSharedContact(
|
||||
if (action.replyTo) {
|
||||
flags |= MessageFlag::HasReplyInfo;
|
||||
}
|
||||
const auto replyHeader = NewMessageReplyHeader(action);
|
||||
FillMessagePostFlags(action, peer, flags);
|
||||
if (action.options.scheduled) {
|
||||
flags |= MessageFlag::IsOrWasScheduled;
|
||||
@@ -3578,15 +3579,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
action.generateLocal = true;
|
||||
sendAction(action);
|
||||
|
||||
const auto replyToId = action.replyTo.msgId;
|
||||
const auto replyTo = replyToId
|
||||
? peer->owner().message(peer, replyToId)
|
||||
: nullptr;
|
||||
const auto topicRootId = replyTo
|
||||
? replyTo->topicRootId()
|
||||
: action.replyTo.topicRootId
|
||||
? action.replyTo.topicRootId
|
||||
: Data::ForumTopic::kGeneralId;
|
||||
const auto clearCloudDraft = action.clearDraft;
|
||||
const auto topicRootId = action.replyTo.topicRootId;
|
||||
const auto topic = peer->forumTopicFor(topicRootId);
|
||||
if (!(topic ? Data::CanSendTexts(topic) : Data::CanSendTexts(peer))
|
||||
|| Api::SendDice(message)) {
|
||||
@@ -3608,7 +3602,13 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
|
||||
auto &histories = history->owner().histories();
|
||||
|
||||
while (TextUtilities::CutPart(sending, left, MaxMessageSize)) {
|
||||
const auto exactWebPage = !message.webPage.url.isEmpty();
|
||||
auto isFirst = true;
|
||||
while (TextUtilities::CutPart(sending, left, MaxMessageSize)
|
||||
|| (isFirst && exactWebPage)) {
|
||||
TextUtilities::Trim(left);
|
||||
const auto isLast = left.empty();
|
||||
|
||||
auto newId = FullMsgId(
|
||||
peer->id,
|
||||
_session->data().nextLocalMessageId());
|
||||
@@ -3622,26 +3622,51 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
MTPstring msgText(MTP_string(sending.text));
|
||||
auto flags = NewMessageFlags(peer);
|
||||
auto sendFlags = MTPmessages_SendMessage::Flags(0);
|
||||
auto mediaFlags = MTPmessages_SendMedia::Flags(0);
|
||||
if (action.replyTo) {
|
||||
flags |= MessageFlag::HasReplyInfo;
|
||||
sendFlags |= MTPmessages_SendMessage::Flag::f_reply_to;
|
||||
mediaFlags |= MTPmessages_SendMedia::Flag::f_reply_to;
|
||||
}
|
||||
const auto replyHeader = NewMessageReplyHeader(action);
|
||||
const auto ignoreWebPage = message.webPage.removed
|
||||
|| (exactWebPage && !isLast);
|
||||
const auto manualWebPage = exactWebPage
|
||||
&& !ignoreWebPage
|
||||
&& (message.webPage.manual || (isLast && !isFirst));
|
||||
MTPMessageMedia media = MTP_messageMediaEmpty();
|
||||
if (message.webPageId == CancelledWebPageId) {
|
||||
if (ignoreWebPage) {
|
||||
sendFlags |= MTPmessages_SendMessage::Flag::f_no_webpage;
|
||||
} else if (message.webPageId) {
|
||||
auto page = _session->data().webpage(message.webPageId);
|
||||
} else if (exactWebPage) {
|
||||
using PageFlag = MTPDmessageMediaWebPage::Flag;
|
||||
using PendingFlag = MTPDwebPagePending::Flag;
|
||||
const auto &fields = message.webPage;
|
||||
const auto page = _session->data().webpage(fields.id);
|
||||
media = MTP_messageMediaWebPage(
|
||||
MTP_flags(PageFlag()
|
||||
| (manualWebPage ? PageFlag::f_manual : PageFlag())
|
||||
| (fields.forceLargeMedia
|
||||
? PageFlag::f_force_large_media
|
||||
: PageFlag())
|
||||
| (fields.forceSmallMedia
|
||||
? PageFlag::f_force_small_media
|
||||
: PageFlag())),
|
||||
MTP_webPagePending(
|
||||
MTP_long(page->id),
|
||||
MTP_flags(PendingFlag::f_url),
|
||||
MTP_long(fields.id),
|
||||
MTP_string(fields.url),
|
||||
MTP_int(page->pendingTill)));
|
||||
}
|
||||
const auto anonymousPost = peer->amAnonymous();
|
||||
const auto silentPost = ShouldSendSilent(peer, action.options);
|
||||
FillMessagePostFlags(action, peer, flags);
|
||||
if (exactWebPage && !ignoreWebPage && message.webPage.invert) {
|
||||
flags |= MessageFlag::InvertMedia;
|
||||
sendFlags |= MTPmessages_SendMessage::Flag::f_invert_media;
|
||||
mediaFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
|
||||
}
|
||||
if (silentPost) {
|
||||
sendFlags |= MTPmessages_SendMessage::Flag::f_silent;
|
||||
mediaFlags |= MTPmessages_SendMedia::Flag::f_silent;
|
||||
}
|
||||
const auto sentEntities = Api::EntitiesToMTP(
|
||||
_session,
|
||||
@@ -3649,11 +3674,11 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
Api::ConvertOption::SkipLocal);
|
||||
if (!sentEntities.v.isEmpty()) {
|
||||
sendFlags |= MTPmessages_SendMessage::Flag::f_entities;
|
||||
mediaFlags |= MTPmessages_SendMedia::Flag::f_entities;
|
||||
}
|
||||
const auto clearCloudDraft = action.clearDraft;
|
||||
const auto topicRootId = action.replyTo.topicRootId;
|
||||
if (clearCloudDraft) {
|
||||
sendFlags |= MTPmessages_SendMessage::Flag::f_clear_draft;
|
||||
mediaFlags |= MTPmessages_SendMedia::Flag::f_clear_draft;
|
||||
history->clearCloudDraft(topicRootId);
|
||||
history->startSavingCloudDraft(topicRootId);
|
||||
}
|
||||
@@ -3665,6 +3690,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
: _session->userPeerId();
|
||||
if (sendAs) {
|
||||
sendFlags |= MTPmessages_SendMessage::Flag::f_send_as;
|
||||
mediaFlags |= MTPmessages_SendMedia::Flag::f_send_as;
|
||||
}
|
||||
const auto messagePostAuthor = peer->isBroadcast()
|
||||
? _session->user()->name()
|
||||
@@ -3672,6 +3698,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
if (action.options.scheduled) {
|
||||
flags |= MessageFlag::IsOrWasScheduled;
|
||||
sendFlags |= MTPmessages_SendMessage::Flag::f_schedule_date;
|
||||
mediaFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
|
||||
}
|
||||
const auto viaBotId = UserId();
|
||||
lastMessage = history->addNewLocalMessage(
|
||||
@@ -3685,27 +3712,18 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
sending,
|
||||
media,
|
||||
HistoryMessageMarkupData());
|
||||
histories.sendPreparedMessage(
|
||||
history,
|
||||
action.replyTo,
|
||||
randomId,
|
||||
Data::Histories::PrepareMessage<MTPmessages_SendMessage>(
|
||||
MTP_flags(sendFlags),
|
||||
peer->input,
|
||||
Data::Histories::ReplyToPlaceholder(),
|
||||
msgText,
|
||||
MTP_long(randomId),
|
||||
MTPReplyMarkup(),
|
||||
sentEntities,
|
||||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty())
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
const auto done = [=](
|
||||
const MTPUpdates &result,
|
||||
const MTP::Response &response) {
|
||||
if (clearCloudDraft) {
|
||||
history->finishSavingCloudDraft(
|
||||
topicRootId,
|
||||
UnixtimeFromMsgId(response.outerMsgId));
|
||||
}
|
||||
}, [=](const MTP::Error &error, const MTP::Response &response) {
|
||||
};
|
||||
const auto fail = [=](
|
||||
const MTP::Error &error,
|
||||
const MTP::Response &response) {
|
||||
if (error.type() == u"MESSAGE_EMPTY"_q) {
|
||||
lastMessage->destroy();
|
||||
} else {
|
||||
@@ -3716,7 +3734,44 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
topicRootId,
|
||||
UnixtimeFromMsgId(response.outerMsgId));
|
||||
}
|
||||
});
|
||||
};
|
||||
if (exactWebPage
|
||||
&& !ignoreWebPage
|
||||
&& (manualWebPage || sending.empty())) {
|
||||
histories.sendPreparedMessage(
|
||||
history,
|
||||
action.replyTo,
|
||||
randomId,
|
||||
Data::Histories::PrepareMessage<MTPmessages_SendMedia>(
|
||||
MTP_flags(mediaFlags),
|
||||
peer->input,
|
||||
Data::Histories::ReplyToPlaceholder(),
|
||||
Data::WebPageForMTP(message.webPage, true),
|
||||
msgText,
|
||||
MTP_long(randomId),
|
||||
MTPReplyMarkup(),
|
||||
sentEntities,
|
||||
MTP_int(message.action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty())
|
||||
), done, fail);
|
||||
} else {
|
||||
histories.sendPreparedMessage(
|
||||
history,
|
||||
action.replyTo,
|
||||
randomId,
|
||||
Data::Histories::PrepareMessage<MTPmessages_SendMessage>(
|
||||
MTP_flags(sendFlags),
|
||||
peer->input,
|
||||
Data::Histories::ReplyToPlaceholder(),
|
||||
msgText,
|
||||
MTP_long(randomId),
|
||||
MTPReplyMarkup(),
|
||||
sentEntities,
|
||||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty())
|
||||
), done, fail);
|
||||
}
|
||||
isFirst = false;
|
||||
}
|
||||
|
||||
finishForwarding(action);
|
||||
@@ -3781,7 +3836,7 @@ void ApiWrap::sendInlineResult(
|
||||
? (*localMessageId)
|
||||
: _session->data().nextLocalMessageId());
|
||||
const auto randomId = base::RandomValue<uint64>();
|
||||
const auto topicRootId = action.replyTo.msgId
|
||||
const auto topicRootId = action.replyTo.messageId
|
||||
? action.replyTo.topicRootId
|
||||
: 0;
|
||||
|
||||
|
||||
@@ -175,7 +175,8 @@ BackgroundPreviewBox::BackgroundPreviewBox(
|
||||
, _controller(controller)
|
||||
, _forPeer(args.forPeer)
|
||||
, _fromMessageId(args.fromMessageId)
|
||||
, _chatStyle(std::make_unique<Ui::ChatStyle>())
|
||||
, _chatStyle(std::make_unique<Ui::ChatStyle>(
|
||||
controller->session().colorIndicesValue()))
|
||||
, _serviceHistory(_controller->session().data().history(
|
||||
PeerData::kServiceNotificationsId))
|
||||
, _service(nullptr)
|
||||
@@ -434,7 +435,7 @@ void BackgroundPreviewBox::rebuildButtons(bool dark) {
|
||||
clearButtons();
|
||||
addButton(_forPeer
|
||||
? tr::lng_background_apply_button()
|
||||
: tr::lng_background_apply(), [=] { apply(); });
|
||||
: tr::lng_settings_apply(), [=] { apply(); });
|
||||
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||
if (!_forPeer && _paper.hasShareUrl()) {
|
||||
addLeftButton(tr::lng_background_share(), [=] { share(); });
|
||||
|
||||
@@ -85,8 +85,6 @@ confirmInviteTitle: FlatLabel(defaultFlatLabel) {
|
||||
textFg: windowBoldFg;
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(18px semibold);
|
||||
linkFont: font(18px semibold);
|
||||
linkFontOver: font(18px semibold underline);
|
||||
}
|
||||
}
|
||||
confirmInviteAbout: FlatLabel(boxLabel) {
|
||||
@@ -143,8 +141,6 @@ contactsPadding: margins(16px, 7px, 16px, 7px);
|
||||
contactsNameTop: 2px;
|
||||
contactsNameStyle: TextStyle(defaultTextStyle) {
|
||||
font: semiboldFont;
|
||||
linkFont: semiboldFont;
|
||||
linkFontOver: semiboldFont;
|
||||
}
|
||||
contactsStatusTop: 23px;
|
||||
contactsStatusFont: font(fsize);
|
||||
@@ -199,8 +195,6 @@ localStorageRowTitle: FlatLabel(defaultFlatLabel) {
|
||||
maxHeight: 20px;
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(14px semibold);
|
||||
linkFont: font(14px semibold);
|
||||
linkFontOver: font(14px semibold);
|
||||
}
|
||||
}
|
||||
localStorageRowSize: FlatLabel(defaultFlatLabel) {
|
||||
@@ -208,8 +202,6 @@ localStorageRowSize: FlatLabel(defaultFlatLabel) {
|
||||
maxHeight: 20px;
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(14px);
|
||||
linkFont: font(14px);
|
||||
linkFontOver: font(14px);
|
||||
}
|
||||
}
|
||||
localStorageClear: defaultBoxButton;
|
||||
@@ -228,8 +220,6 @@ sharePhotoTop: 6px;
|
||||
shareBoxListItem: PeerListItem(defaultPeerListItem) {
|
||||
nameStyle: TextStyle(defaultTextStyle) {
|
||||
font: font(11px);
|
||||
linkFont: font(11px);
|
||||
linkFontOver: font(11px);
|
||||
}
|
||||
nameFg: windowFg;
|
||||
nameFgChecked: windowActiveTextFg;
|
||||
@@ -537,8 +527,6 @@ adminLogFilterLittleSkip: 16px;
|
||||
adminLogFilterCheckbox: Checkbox(defaultBoxCheckbox) {
|
||||
style: TextStyle(boxTextStyle) {
|
||||
font: font(boxFontSize semibold);
|
||||
linkFont: font(boxFontSize semibold);
|
||||
linkFontOver: font(boxFontSize semibold underline);
|
||||
}
|
||||
}
|
||||
adminLogFilterSkip: 32px;
|
||||
@@ -580,16 +568,12 @@ rightsPhotoButton: UserpicButton(defaultUserpicButton) {
|
||||
rightsPhotoMargin: margins(20px, 0px, 15px, 18px);
|
||||
rightsNameStyle: TextStyle(semiboldTextStyle) {
|
||||
font: font(15px semibold);
|
||||
linkFont: font(15px semibold);
|
||||
linkFontOver: font(15px semibold underline);
|
||||
}
|
||||
rightsNameTop: 8px;
|
||||
rightsStatusTop: 32px;
|
||||
rightsHeaderLabel: FlatLabel(boxLabel) {
|
||||
style: TextStyle(semiboldTextStyle) {
|
||||
font: font(boxFontSize semibold);
|
||||
linkFont: font(boxFontSize semibold);
|
||||
linkFontOver: font(boxFontSize semibold underline);
|
||||
}
|
||||
textFg: windowActiveTextFg;
|
||||
}
|
||||
@@ -623,8 +607,6 @@ proxyRowTitlePalette: TextPalette(defaultTextPalette) {
|
||||
}
|
||||
proxyRowTitleStyle: TextStyle(defaultTextStyle) {
|
||||
font: semiboldFont;
|
||||
linkFont: normalFont;
|
||||
linkFontOver: normalFont;
|
||||
}
|
||||
proxyRowStatusFg: windowSubTextFg;
|
||||
proxyRowStatusFgOnline: windowActiveTextFg;
|
||||
@@ -807,8 +789,6 @@ pollResultsQuestion: FlatLabel(defaultFlatLabel) {
|
||||
textFg: windowBoldFg;
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(16px semibold);
|
||||
linkFont: font(16px semibold);
|
||||
linkFontOver: font(16px semibold underline);
|
||||
}
|
||||
}
|
||||
pollResultsVotesCount: FlatLabel(defaultFlatLabel) {
|
||||
@@ -837,8 +817,6 @@ inviteViaLinkButton: SettingsButton(defaultSettingsButton) {
|
||||
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(14px semibold);
|
||||
linkFont: font(14px semibold);
|
||||
linkFontOver: font(14px semibold underline);
|
||||
}
|
||||
|
||||
height: 20px;
|
||||
|
||||
@@ -194,7 +194,7 @@ not_null<Ui::FlatLabel*> CreateWarningLabel(
|
||||
if (value >= 0) {
|
||||
result->setText(QString::number(value));
|
||||
} else {
|
||||
result->setMarkedText(Ui::Text::PlainLink(
|
||||
result->setMarkedText(Ui::Text::Colorized(
|
||||
QString::number(value)));
|
||||
}
|
||||
result->setVisible(shown);
|
||||
|
||||
@@ -8,26 +8,36 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/gift_premium_box.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_premium.h"
|
||||
#include "api/api_premium_option.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "base/weak_ptr.h"
|
||||
#include "boxes/peers/prepare_short_info_box.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_media_types.h" // Data::Giveaway
|
||||
#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 "mainwidget.h"
|
||||
#include "settings/settings_premium.h"
|
||||
#include "ui/basic_click_handlers.h" // UrlClickHandler::Open.
|
||||
#include "ui/boxes/boost_box.h" // StartFireworks.
|
||||
#include "ui/controls/userpic_button.h"
|
||||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/effects/premium_stars_colored.h"
|
||||
#include "ui/effects/premium_top_bar.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/format_values.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/gradient_round_button.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "ui/wrap/table_layout.h"
|
||||
#include "window/window_peer_menu.h" // ShowChooseRecipientBox.
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_layers.h"
|
||||
@@ -35,6 +45,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_info.h"
|
||||
#include "styles/style_premium.h"
|
||||
|
||||
#include <QtGui/QGuiApplication>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kDiscountDivider = 5.;
|
||||
@@ -225,6 +237,131 @@ void GiftBox(
|
||||
}, box->lifetime());
|
||||
}
|
||||
|
||||
struct GiftCodeLink {
|
||||
QString text;
|
||||
QString link;
|
||||
};
|
||||
[[nodiscard]] GiftCodeLink MakeGiftCodeLink(
|
||||
not_null<Main::Session*> session,
|
||||
const QString &slug) {
|
||||
const auto path = u"giftcode/"_q + slug;
|
||||
return {
|
||||
session->createInternalLink(path),
|
||||
session->createInternalLinkFull(path),
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> MakeLinkCopyIcon(
|
||||
not_null<QWidget*> parent) {
|
||||
auto result = object_ptr<Ui::RpWidget>(parent);
|
||||
const auto raw = result.data();
|
||||
|
||||
raw->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = QPainter(raw);
|
||||
const auto &icon = st::giveawayGiftCodeLinkCopy;
|
||||
const auto left = (raw->width() - icon.width()) / 2;
|
||||
const auto top = (raw->height() - icon.height()) / 2;
|
||||
icon.paint(p, left, top, raw->width());
|
||||
}, raw->lifetime());
|
||||
|
||||
raw->resize(
|
||||
st::giveawayGiftCodeLinkCopyWidth,
|
||||
st::giveawayGiftCodeLinkHeight);
|
||||
|
||||
raw->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] tr::phrase<lngtag_count> GiftDurationPhrase(int months) {
|
||||
return (months < 12)
|
||||
? tr::lng_premium_gift_duration_months
|
||||
: tr::lng_premium_gift_duration_years;
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> MakePeerTableValue(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
PeerId id) {
|
||||
auto result = object_ptr<Ui::AbstractButton>(parent);
|
||||
const auto raw = result.data();
|
||||
|
||||
const auto &st = st::giveawayGiftCodeUserpic;
|
||||
raw->resize(raw->width(), st.photoSize);
|
||||
|
||||
const auto peer = controller->session().data().peer(id);
|
||||
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(raw, peer, st);
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
raw,
|
||||
peer->name(),
|
||||
st::giveawayGiftCodeValue);
|
||||
raw->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
const auto position = st::giveawayGiftCodeNamePosition;
|
||||
label->resizeToNaturalWidth(width - position.x());
|
||||
label->moveToLeft(position.x(), position.y(), width);
|
||||
const auto top = (raw->height() - userpic->height()) / 2;
|
||||
userpic->moveToLeft(0, top, width);
|
||||
}, label->lifetime());
|
||||
|
||||
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
label->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
label->setTextColorOverride(st::windowActiveTextFg->c);
|
||||
|
||||
raw->setClickedCallback([=] {
|
||||
controller->uiShow()->showBox(PrepareShortInfoBox(peer, controller));
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void AddTableRow(
|
||||
not_null<Ui::TableLayout*> table,
|
||||
rpl::producer<QString> label,
|
||||
object_ptr<Ui::RpWidget> value,
|
||||
style::margins valueMargins) {
|
||||
table->addRow(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
table,
|
||||
std::move(label),
|
||||
st::giveawayGiftCodeLabel),
|
||||
std::move(value),
|
||||
st::giveawayGiftCodeLabelMargin,
|
||||
valueMargins);
|
||||
}
|
||||
|
||||
not_null<Ui::FlatLabel*> AddTableRow(
|
||||
not_null<Ui::TableLayout*> table,
|
||||
rpl::producer<QString> label,
|
||||
rpl::producer<TextWithEntities> value) {
|
||||
auto widget = object_ptr<Ui::FlatLabel>(
|
||||
table,
|
||||
std::move(value),
|
||||
st::giveawayGiftCodeValue);
|
||||
const auto result = widget.data();
|
||||
AddTableRow(
|
||||
table,
|
||||
std::move(label),
|
||||
std::move(widget),
|
||||
st::giveawayGiftCodeValueMargin);
|
||||
return result;
|
||||
}
|
||||
|
||||
void AddTableRow(
|
||||
not_null<Ui::TableLayout*> table,
|
||||
rpl::producer<QString> label,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
PeerId id) {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
AddTableRow(
|
||||
table,
|
||||
std::move(label),
|
||||
MakePeerTableValue(table, controller, id),
|
||||
st::giveawayGiftCodePeerMargin);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
GiftPremiumValidator::GiftPremiumValidator(
|
||||
@@ -263,3 +400,405 @@ void GiftPremiumValidator::showBox(not_null<UserData*> user) {
|
||||
_requestId = 0;
|
||||
}).send();
|
||||
}
|
||||
|
||||
rpl::producer<QString> GiftDurationValue(int months) {
|
||||
return GiftDurationPhrase(months)(
|
||||
lt_count,
|
||||
rpl::single(float64((months < 12) ? months : (months / 12))));
|
||||
}
|
||||
|
||||
QString GiftDuration(int months) {
|
||||
return GiftDurationPhrase(months)(
|
||||
tr::now,
|
||||
lt_count,
|
||||
(months < 12) ? months : (months / 12));
|
||||
}
|
||||
|
||||
void GiftCodeBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
const QString &slug) {
|
||||
struct State {
|
||||
rpl::variable<Api::GiftCode> data;
|
||||
rpl::variable<bool> used;
|
||||
bool sent = false;
|
||||
};
|
||||
const auto session = &controller->session();
|
||||
const auto state = box->lifetime().make_state<State>(State{});
|
||||
state->data = session->api().premium().giftCodeValue(slug);
|
||||
state->used = state->data.value(
|
||||
) | rpl::map([=](const Api::GiftCode &data) {
|
||||
return data.used;
|
||||
});
|
||||
|
||||
box->setWidth(st::boxWideWidth);
|
||||
box->setStyle(st::giveawayGiftCodeBox);
|
||||
box->setNoContentMargin(true);
|
||||
|
||||
const auto bar = box->setPinnedToTopContent(
|
||||
object_ptr<Ui::Premium::TopBar>(
|
||||
box,
|
||||
st::giveawayGiftCodeCover,
|
||||
nullptr,
|
||||
rpl::conditional(
|
||||
state->used.value(),
|
||||
tr::lng_gift_link_used_title(),
|
||||
tr::lng_gift_link_title()),
|
||||
rpl::conditional(
|
||||
state->used.value(),
|
||||
tr::lng_gift_link_used_about(Ui::Text::RichLangValue),
|
||||
tr::lng_gift_link_about(Ui::Text::RichLangValue)),
|
||||
true));
|
||||
|
||||
const auto max = st::giveawayGiftCodeTopHeight;
|
||||
bar->setMaximumHeight(max);
|
||||
bar->setMinimumHeight(st::infoLayerTopBarHeight);
|
||||
|
||||
bar->resize(bar->width(), bar->maximumHeight());
|
||||
|
||||
const auto link = MakeGiftCodeLink(&controller->session(), slug);
|
||||
box->addRow(
|
||||
Ui::MakeLinkLabel(
|
||||
box,
|
||||
rpl::single(link.text),
|
||||
rpl::single(link.link),
|
||||
box->uiShow(),
|
||||
MakeLinkCopyIcon(box)),
|
||||
st::giveawayGiftCodeLinkMargin);
|
||||
|
||||
auto table = box->addRow(
|
||||
object_ptr<Ui::TableLayout>(
|
||||
box,
|
||||
st::giveawayGiftCodeTable),
|
||||
st::giveawayGiftCodeTableMargin);
|
||||
const auto current = state->data.current();
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_from(),
|
||||
controller,
|
||||
current.from);
|
||||
if (current.to) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_to(),
|
||||
controller,
|
||||
current.to);
|
||||
} else {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_to(),
|
||||
tr::lng_gift_link_label_to_unclaimed(Ui::Text::WithEntities));
|
||||
}
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_gift(),
|
||||
tr::lng_gift_link_gift_premium(
|
||||
lt_duration,
|
||||
GiftDurationValue(current.months) | Ui::Text::ToWithEntities(),
|
||||
Ui::Text::WithEntities));
|
||||
const auto reason = AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_reason(),
|
||||
(current.giveawayId
|
||||
? ((current.to
|
||||
? tr::lng_gift_link_reason_giveaway
|
||||
: tr::lng_gift_link_reason_unclaimed)(
|
||||
) | Ui::Text::ToLink())
|
||||
: current.giveaway
|
||||
? ((current.to
|
||||
? tr::lng_gift_link_reason_giveaway
|
||||
: tr::lng_gift_link_reason_unclaimed)(
|
||||
Ui::Text::WithEntities
|
||||
) | rpl::type_erased())
|
||||
: tr::lng_gift_link_reason_chosen(Ui::Text::WithEntities)));
|
||||
reason->setClickHandlerFilter([=](const auto &...) {
|
||||
controller->showPeerHistory(
|
||||
current.from,
|
||||
Window::SectionShow::Way::Forward,
|
||||
current.giveawayId);
|
||||
return false;
|
||||
});
|
||||
if (current.date) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_date(),
|
||||
rpl::single(Ui::Text::WithEntities(
|
||||
langDateTime(base::unixtime::parse(current.date)))));
|
||||
}
|
||||
|
||||
auto shareLink = tr::lng_gift_link_also_send_link(
|
||||
) | rpl::map([](const QString &text) {
|
||||
return Ui::Text::Link(text);
|
||||
});
|
||||
auto richDate = [](const Api::GiftCode &data) {
|
||||
return TextWithEntities{
|
||||
langDateTime(base::unixtime::parse(data.used)),
|
||||
};
|
||||
};
|
||||
const auto footer = box->addRow(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
rpl::conditional(
|
||||
state->used.value(),
|
||||
tr::lng_gift_link_used_footer(
|
||||
lt_date,
|
||||
state->data.value() | rpl::map(richDate),
|
||||
Ui::Text::WithEntities),
|
||||
tr::lng_gift_link_also_send(
|
||||
lt_link,
|
||||
std::move(shareLink),
|
||||
Ui::Text::WithEntities)),
|
||||
st::giveawayGiftCodeFooter),
|
||||
st::giveawayGiftCodeFooterMargin);
|
||||
footer->setClickHandlerFilter([=](const auto &...) {
|
||||
const auto chosen = [=](not_null<Data::Thread*> thread) {
|
||||
const auto content = controller->parentController()->content();
|
||||
return content->shareUrl(
|
||||
thread,
|
||||
MakeGiftCodeLink(&controller->session(), slug).link,
|
||||
QString());
|
||||
};
|
||||
Window::ShowChooseRecipientBox(controller, chosen);
|
||||
return false;
|
||||
});
|
||||
|
||||
const auto close = Ui::CreateChild<Ui::IconButton>(
|
||||
box.get(),
|
||||
st::boxTitleClose);
|
||||
close->setClickedCallback([=] {
|
||||
box->closeBox();
|
||||
});
|
||||
box->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
close->moveToRight(0, 0);
|
||||
}, box->lifetime());
|
||||
|
||||
const auto button = box->addButton(rpl::conditional(
|
||||
state->used.value(),
|
||||
tr::lng_box_ok(),
|
||||
tr::lng_gift_link_use()
|
||||
), [=] {
|
||||
if (state->used.current()) {
|
||||
box->closeBox();
|
||||
} else if (!state->sent) {
|
||||
state->sent = true;
|
||||
const auto done = crl::guard(box, [=](const QString &error) {
|
||||
if (error.isEmpty()) {
|
||||
auto copy = state->data.current();
|
||||
copy.used = base::unixtime::now();
|
||||
state->data = std::move(copy);
|
||||
|
||||
Ui::StartFireworks(box->parentWidget());
|
||||
} else {
|
||||
box->uiShow()->showToast(error);
|
||||
}
|
||||
});
|
||||
controller->session().api().premium().applyGiftCode(slug, done);
|
||||
}
|
||||
});
|
||||
const auto buttonPadding = st::giveawayGiftCodeBox.buttonPadding;
|
||||
const auto buttonWidth = st::boxWideWidth
|
||||
- buttonPadding.left()
|
||||
- buttonPadding.right();
|
||||
button->widthValue() | rpl::filter([=] {
|
||||
return (button->widthNoMargins() != buttonWidth);
|
||||
}) | rpl::start_with_next([=] {
|
||||
button->resizeToWidth(buttonWidth);
|
||||
}, button->lifetime());
|
||||
}
|
||||
|
||||
void ResolveGiftCode(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
const QString &slug) {
|
||||
const auto done = [=](Api::GiftCode code) {
|
||||
if (!code) {
|
||||
controller->showToast(tr::lng_gift_link_expired(tr::now));
|
||||
} else {
|
||||
controller->uiShow()->showBox(Box(GiftCodeBox, controller, slug));
|
||||
}
|
||||
};
|
||||
controller->session().api().premium().checkGiftCode(
|
||||
slug,
|
||||
crl::guard(controller, done));
|
||||
}
|
||||
|
||||
void GiveawayInfoBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
Data::Giveaway giveaway,
|
||||
Api::GiveawayInfo info) {
|
||||
using State = Api::GiveawayState;
|
||||
const auto finished = (info.state == State::Finished)
|
||||
|| (info.state == State::Refunded);
|
||||
|
||||
box->setTitle((finished
|
||||
? tr::lng_prizes_end_title
|
||||
: tr::lng_prizes_how_title)());
|
||||
|
||||
const auto first = !giveaway.channels.empty()
|
||||
? giveaway.channels.front()->name()
|
||||
: u"channel"_q;
|
||||
auto text = (finished
|
||||
? tr::lng_prizes_end_text
|
||||
: tr::lng_prizes_how_text)(
|
||||
tr::now,
|
||||
lt_admins,
|
||||
tr::lng_prizes_admins(
|
||||
tr::now,
|
||||
lt_count,
|
||||
giveaway.quantity,
|
||||
lt_channel,
|
||||
Ui::Text::Bold(first),
|
||||
lt_duration,
|
||||
TextWithEntities{ GiftDuration(giveaway.months) },
|
||||
Ui::Text::RichLangValue),
|
||||
Ui::Text::RichLangValue);
|
||||
const auto many = (giveaway.channels.size() > 1);
|
||||
const auto count = info.winnersCount
|
||||
? info.winnersCount
|
||||
: giveaway.quantity;
|
||||
auto winners = giveaway.all
|
||||
? (many
|
||||
? tr::lng_prizes_winners_all_of_many
|
||||
: tr::lng_prizes_winners_all_of_one)(
|
||||
tr::now,
|
||||
lt_count,
|
||||
count,
|
||||
lt_channel,
|
||||
Ui::Text::Bold(first),
|
||||
Ui::Text::RichLangValue)
|
||||
: (many
|
||||
? tr::lng_prizes_winners_new_of_many
|
||||
: tr::lng_prizes_winners_new_of_one)(
|
||||
tr::now,
|
||||
lt_count,
|
||||
count,
|
||||
lt_channel,
|
||||
Ui::Text::Bold(first),
|
||||
lt_start_date,
|
||||
Ui::Text::Bold(
|
||||
langDateTime(base::unixtime::parse(info.startDate))),
|
||||
Ui::Text::RichLangValue);
|
||||
text.append("\n\n").append((finished
|
||||
? tr::lng_prizes_end_when_finish
|
||||
: tr::lng_prizes_how_when_finish)(
|
||||
tr::now,
|
||||
lt_date,
|
||||
Ui::Text::Bold(langDayOfMonthFull(
|
||||
base::unixtime::parse(giveaway.untilDate).date())),
|
||||
lt_winners,
|
||||
winners,
|
||||
Ui::Text::RichLangValue));
|
||||
if (info.activatedCount > 0) {
|
||||
text.append(' ').append(tr::lng_prizes_end_activated(
|
||||
tr::now,
|
||||
lt_count,
|
||||
info.activatedCount));
|
||||
}
|
||||
if (!info.giftCode.isEmpty()) {
|
||||
text.append("\n\n");
|
||||
text.append(tr::lng_prizes_you_won(
|
||||
tr::now,
|
||||
lt_cup,
|
||||
QString::fromUtf8("\xf0\x9f\x8f\x86")));
|
||||
} else if (info.state == State::Finished) {
|
||||
text.append("\n\n");
|
||||
text.append(tr::lng_prizes_you_didnt(tr::now));
|
||||
} else if (info.state == State::Preparing) {
|
||||
|
||||
} else if (info.state != State::Refunded) {
|
||||
if (info.adminChannelId) {
|
||||
const auto channel = controller->session().data().channel(
|
||||
info.adminChannelId);
|
||||
text.append("\n\n").append(tr::lng_prizes_how_no_admin(
|
||||
tr::now,
|
||||
lt_channel,
|
||||
Ui::Text::Bold(channel->name()),
|
||||
Ui::Text::RichLangValue));
|
||||
} else if (info.tooEarlyDate) {
|
||||
text.append("\n\n").append(tr::lng_prizes_how_no_joined(
|
||||
tr::now,
|
||||
lt_date,
|
||||
Ui::Text::Bold(
|
||||
langDateTime(base::unixtime::parse(info.tooEarlyDate))),
|
||||
Ui::Text::RichLangValue));
|
||||
} else if (!info.disallowedCountry.isEmpty()) {
|
||||
text.append("\n\n").append(tr::lng_prizes_how_no_country(
|
||||
tr::now,
|
||||
Ui::Text::RichLangValue));
|
||||
} else if (info.participating) {
|
||||
text.append("\n\n").append((many
|
||||
? tr::lng_prizes_how_yes_joined_many
|
||||
: tr::lng_prizes_how_yes_joined_one)(
|
||||
tr::now,
|
||||
lt_channel,
|
||||
Ui::Text::Bold(first),
|
||||
Ui::Text::RichLangValue));
|
||||
} else {
|
||||
text.append("\n\n").append((many
|
||||
? tr::lng_prizes_how_participate_many
|
||||
: tr::lng_prizes_how_participate_one)(
|
||||
tr::now,
|
||||
lt_channel,
|
||||
Ui::Text::Bold(first),
|
||||
lt_date,
|
||||
Ui::Text::Bold(langDayOfMonthFull(
|
||||
base::unixtime::parse(giveaway.untilDate).date())),
|
||||
Ui::Text::RichLangValue));
|
||||
}
|
||||
}
|
||||
const auto padding = st::boxPadding;
|
||||
box->addRow(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box.get(),
|
||||
rpl::single(std::move(text)),
|
||||
st::boxLabel),
|
||||
{ padding.left(), 0, padding.right(), padding.bottom() });
|
||||
|
||||
if (info.state == State::Refunded) {
|
||||
const auto wrap = box->addRow(
|
||||
object_ptr<Ui::PaddingWrap<Ui::FlatLabel>>(
|
||||
box.get(),
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box.get(),
|
||||
tr::lng_prizes_cancelled(),
|
||||
st::giveawayRefundedLabel),
|
||||
st::giveawayRefundedPadding),
|
||||
{ padding.left(), 0, padding.right(), padding.bottom() });
|
||||
const auto bg = wrap->lifetime().make_state<Ui::RoundRect>(
|
||||
st::boxRadius,
|
||||
st::attentionBoxButton.textBgOver);
|
||||
wrap->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = QPainter(wrap);
|
||||
bg->paint(p, wrap->rect());
|
||||
}, wrap->lifetime());
|
||||
}
|
||||
if (const auto slug = info.giftCode; !slug.isEmpty()) {
|
||||
box->addButton(tr::lng_prizes_view_prize(), [=] {
|
||||
ResolveGiftCode(controller, slug);
|
||||
});
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
} else {
|
||||
box->addButton(tr::lng_close(), [=] { box->closeBox(); });
|
||||
}
|
||||
}
|
||||
|
||||
void ResolveGiveawayInfo(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<PeerData*> peer,
|
||||
MsgId messageId,
|
||||
Data::Giveaway giveaway) {
|
||||
const auto show = [=](Api::GiveawayInfo info) {
|
||||
if (!info) {
|
||||
controller->showToast(
|
||||
tr::lng_confirm_phone_link_invalid(tr::now));
|
||||
} else {
|
||||
controller->uiShow()->showBox(
|
||||
Box(GiveawayInfoBox, controller, giveaway, info));
|
||||
}
|
||||
};
|
||||
controller->session().api().premium().resolveGiveawayInfo(
|
||||
peer,
|
||||
messageId,
|
||||
crl::guard(controller, show));
|
||||
}
|
||||
|
||||
@@ -11,8 +11,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
class UserData;
|
||||
|
||||
namespace Api {
|
||||
struct GiftCode;
|
||||
} // namespace Api
|
||||
|
||||
namespace Data {
|
||||
struct Giveaway;
|
||||
} // namespace Data
|
||||
|
||||
namespace Ui {
|
||||
class GenericBox;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
class SessionNavigation;
|
||||
} // namespace Window
|
||||
|
||||
class GiftPremiumValidator final {
|
||||
@@ -29,3 +42,20 @@ private:
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] rpl::producer<QString> GiftDurationValue(int months);
|
||||
[[nodiscard]] QString GiftDuration(int months);
|
||||
|
||||
void GiftCodeBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
const QString &slug);
|
||||
void ResolveGiftCode(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
const QString &slug);
|
||||
|
||||
void ResolveGiveawayInfo(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<PeerData*> peer,
|
||||
MsgId messageId,
|
||||
Data::Giveaway giveaway);
|
||||
|
||||
@@ -456,7 +456,7 @@ int PeerListController::descriptionTopSkipMin() const {
|
||||
void PeerListBox::addSelectItem(
|
||||
not_null<PeerData*> peer,
|
||||
anim::type animated) {
|
||||
const auto respect = _controller->respectSavedMessagesChat();
|
||||
const auto respect = !_controller->savedMessagesChatStatus().isEmpty();
|
||||
const auto text = (respect && peer->isSelf())
|
||||
? tr::lng_saved_short(tr::now)
|
||||
: (respect && peer->isRepliesChat())
|
||||
@@ -579,8 +579,8 @@ void PeerListRow::refreshStatus() {
|
||||
_statusType = StatusType::LastSeen;
|
||||
_statusValidTill = 0;
|
||||
if (auto user = peer()->asUser()) {
|
||||
if (_isSavedMessagesChat) {
|
||||
setStatusText(tr::lng_saved_forward_here(tr::now));
|
||||
if (!_savedMessagesStatus.isEmpty()) {
|
||||
setStatusText(_savedMessagesStatus);
|
||||
} else {
|
||||
auto time = base::unixtime::now();
|
||||
setStatusText(Data::OnlineText(user, time));
|
||||
@@ -613,7 +613,7 @@ void PeerListRow::refreshName(const style::PeerListItem &st) {
|
||||
if (!_initialized) {
|
||||
return;
|
||||
}
|
||||
const auto text = _isSavedMessagesChat
|
||||
const auto text = !_savedMessagesStatus.isEmpty()
|
||||
? tr::lng_saved_messages(tr::now)
|
||||
: _isRepliesMessagesChat
|
||||
? tr::lng_replies_messages(tr::now)
|
||||
@@ -683,7 +683,7 @@ QString PeerListRow::generateName() {
|
||||
}
|
||||
|
||||
QString PeerListRow::generateShortName() {
|
||||
return _isSavedMessagesChat
|
||||
return !_savedMessagesStatus.isEmpty()
|
||||
? tr::lng_saved_short(tr::now)
|
||||
: _isRepliesMessagesChat
|
||||
? tr::lng_replies_messages(tr::now)
|
||||
@@ -699,7 +699,7 @@ Ui::PeerUserpicView &PeerListRow::ensureUserpicView() {
|
||||
|
||||
PaintRoundImageCallback PeerListRow::generatePaintUserpicCallback(
|
||||
bool forceRound) {
|
||||
const auto saved = _isSavedMessagesChat;
|
||||
const auto saved = !_savedMessagesStatus.isEmpty();
|
||||
const auto replies = _isRepliesMessagesChat;
|
||||
const auto peer = this->peer();
|
||||
auto userpic = saved ? Ui::PeerUserpicView() : ensureUserpicView();
|
||||
@@ -745,7 +745,9 @@ int PeerListRow::paintNameIconGetWidth(
|
||||
int availableWidth,
|
||||
int outerWidth,
|
||||
bool selected) {
|
||||
if (special() || _isSavedMessagesChat || _isRepliesMessagesChat) {
|
||||
if (special()
|
||||
|| !_savedMessagesStatus.isEmpty()
|
||||
|| _isRepliesMessagesChat) {
|
||||
return 0;
|
||||
}
|
||||
return _bagde.drawGetWidth(
|
||||
@@ -758,7 +760,7 @@ int PeerListRow::paintNameIconGetWidth(
|
||||
nameWidth,
|
||||
outerWidth,
|
||||
{
|
||||
.peer = _peer,
|
||||
.peer = peer(),
|
||||
.verified = &(selected
|
||||
? st::dialogsVerifiedIconOver
|
||||
: st::dialogsVerifiedIcon),
|
||||
@@ -855,7 +857,7 @@ void PeerListRow::paintDisabledCheckUserpic(
|
||||
auto iconBorderPen = st.checkbox.check.border->p;
|
||||
iconBorderPen.setWidth(st.checkbox.selectWidth);
|
||||
|
||||
if (_isSavedMessagesChat) {
|
||||
if (!_savedMessagesStatus.isEmpty()) {
|
||||
Ui::EmptyUserpic::PaintSavedMessages(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
|
||||
} else if (_isRepliesMessagesChat) {
|
||||
Ui::EmptyUserpic::PaintRepliesMessages(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
|
||||
@@ -1046,9 +1048,10 @@ void PeerListContent::setRowHidden(not_null<PeerListRow*> row, bool hidden) {
|
||||
}
|
||||
|
||||
void PeerListContent::addRowEntry(not_null<PeerListRow*> row) {
|
||||
if (_controller->respectSavedMessagesChat() && !row->special()) {
|
||||
const auto savedMessagesStatus = _controller->savedMessagesChatStatus();
|
||||
if (!savedMessagesStatus.isEmpty() && !row->special()) {
|
||||
if (row->peer()->isSelf()) {
|
||||
row->setIsSavedMessagesChat(true);
|
||||
row->setSavedMessagesChatStatus(savedMessagesStatus);
|
||||
} else if (row->peer()->isRepliesChat()) {
|
||||
row->setIsRepliesMessagesChat(true);
|
||||
}
|
||||
|
||||
@@ -185,8 +185,8 @@ public:
|
||||
void setIsSearchResult(bool isSearchResult) {
|
||||
_isSearchResult = isSearchResult;
|
||||
}
|
||||
void setIsSavedMessagesChat(bool isSavedMessagesChat) {
|
||||
_isSavedMessagesChat = isSavedMessagesChat;
|
||||
void setSavedMessagesChatStatus(QString savedMessagesStatus) {
|
||||
_savedMessagesStatus = savedMessagesStatus;
|
||||
}
|
||||
void setIsRepliesMessagesChat(bool isRepliesMessagesChat) {
|
||||
_isRepliesMessagesChat = isRepliesMessagesChat;
|
||||
@@ -278,12 +278,12 @@ private:
|
||||
StatusType _statusType = StatusType::Online;
|
||||
crl::time _statusValidTill = 0;
|
||||
base::flat_set<QChar> _nameFirstLetters;
|
||||
QString _savedMessagesStatus;
|
||||
int _absoluteIndex = -1;
|
||||
State _disabledState = State::Active;
|
||||
bool _hidden : 1 = false;
|
||||
bool _initialized : 1 = false;
|
||||
bool _isSearchResult : 1 = false;
|
||||
bool _isSavedMessagesChat : 1 = false;
|
||||
bool _isRepliesMessagesChat : 1 = false;
|
||||
|
||||
};
|
||||
@@ -517,8 +517,8 @@ public:
|
||||
void peerListSearchAddRow(PeerListRowId id) override;
|
||||
void peerListSearchRefreshRows() override;
|
||||
|
||||
[[nodiscard]] virtual bool respectSavedMessagesChat() const {
|
||||
return false;
|
||||
[[nodiscard]] virtual QString savedMessagesChatStatus() const {
|
||||
return QString();
|
||||
}
|
||||
[[nodiscard]] virtual int customRowHeight() {
|
||||
Unexpected("PeerListController::customRowHeight.");
|
||||
|
||||
@@ -170,7 +170,7 @@ void PeerListRowWithLink::rightActionPaint(
|
||||
int outerWidth,
|
||||
bool selected,
|
||||
bool actionSelected) {
|
||||
p.setFont(actionSelected ? st::linkOverFont : st::linkFont);
|
||||
p.setFont(actionSelected ? st::linkFontOver : st::linkFont);
|
||||
p.setPen(actionSelected ? st::defaultLinkButton.overColor : st::defaultLinkButton.color);
|
||||
p.drawTextLeft(x, y, outerWidth, _action, _actionWidth);
|
||||
}
|
||||
@@ -313,7 +313,7 @@ void ChatsListBoxController::rebuildRows() {
|
||||
return count;
|
||||
};
|
||||
auto added = 0;
|
||||
if (respectSavedMessagesChat()) {
|
||||
if (!savedMessagesChatStatus().isEmpty()) {
|
||||
if (appendRow(session().data().history(session().user()))) {
|
||||
++added;
|
||||
}
|
||||
@@ -330,7 +330,7 @@ void ChatsListBoxController::rebuildRows() {
|
||||
const auto history = static_cast<const Row&>(a).history();
|
||||
return history->inChatList();
|
||||
});
|
||||
if (respectSavedMessagesChat()) {
|
||||
if (!savedMessagesChatStatus().isEmpty()) {
|
||||
delegate()->peerListPartitionRows([](const PeerListRow &a) {
|
||||
return a.peer()->isSelf();
|
||||
});
|
||||
@@ -696,6 +696,10 @@ void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||
}
|
||||
}
|
||||
|
||||
QString ChooseRecipientBoxController::savedMessagesChatStatus() const {
|
||||
return tr::lng_saved_forward_here(tr::now);
|
||||
}
|
||||
|
||||
auto ChooseRecipientBoxController::createRow(
|
||||
not_null<History*> history) -> std::unique_ptr<Row> {
|
||||
const auto peer = history->peer;
|
||||
|
||||
@@ -218,9 +218,7 @@ public:
|
||||
Main::Session &session() const override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
|
||||
bool respectSavedMessagesChat() const override {
|
||||
return true;
|
||||
}
|
||||
QString savedMessagesChatStatus() const override;
|
||||
|
||||
protected:
|
||||
void prepareViewHook() override;
|
||||
|
||||
@@ -45,8 +45,8 @@ public:
|
||||
Main::Session &session() const override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
|
||||
bool respectSavedMessagesChat() const override {
|
||||
return true;
|
||||
QString savedMessagesChatStatus() const override {
|
||||
return tr::lng_saved_forward_here(tr::now);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
936
Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp
Normal file
@@ -0,0 +1,936 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/peers/edit_peer_color_box.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "chat_helpers/compose/compose_show.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_web_page.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "info/boosts/info_boosts_widget.h"
|
||||
#include "info/profile/info_profile_emoji_status_panel.h"
|
||||
#include "info/info_memento.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_account.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "main/main_session.h"
|
||||
#include "settings/settings_common.h"
|
||||
#include "settings/settings_premium.h"
|
||||
#include "ui/boxes/boost_box.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/chat/chat_theme.h"
|
||||
#include "ui/effects/path_shift_gradient.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/painter.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "window/section_widget.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
#include "styles/style_settings.h"
|
||||
#include "styles/style_widgets.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace Settings;
|
||||
|
||||
constexpr auto kFakeChannelId = ChannelId(0xFFFFFFF000ULL);
|
||||
constexpr auto kFakeWebPageId = WebPageId(0xFFFFFFFF00000000ULL);
|
||||
constexpr auto kSelectAnimationDuration = crl::time(150);
|
||||
|
||||
class ColorSample final : public Ui::AbstractButton {
|
||||
public:
|
||||
ColorSample(
|
||||
not_null<QWidget*> parent,
|
||||
std::shared_ptr<Ui::ChatStyle> style,
|
||||
rpl::producer<uint8> colorIndex,
|
||||
const QString &name);
|
||||
ColorSample(
|
||||
not_null<QWidget*> parent,
|
||||
std::shared_ptr<Ui::ChatStyle> style,
|
||||
uint8 colorIndex,
|
||||
bool selected);
|
||||
|
||||
[[nodiscard]] uint8 index() const;
|
||||
int naturalWidth() const override;
|
||||
|
||||
void setSelected(bool selected);
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
std::shared_ptr<Ui::ChatStyle> _style;
|
||||
Ui::Text::String _name;
|
||||
uint8 _index = 0;
|
||||
Ui::Animations::Simple _selectAnimation;
|
||||
bool _selected = false;
|
||||
bool _simple = false;
|
||||
|
||||
};
|
||||
|
||||
class PreviewDelegate final : public HistoryView::DefaultElementDelegate {
|
||||
public:
|
||||
PreviewDelegate(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<Ui::ChatStyle*> st,
|
||||
Fn<void()> update);
|
||||
|
||||
bool elementAnimationsPaused() override;
|
||||
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
|
||||
HistoryView::Context elementContext() override;
|
||||
|
||||
private:
|
||||
const not_null<QWidget*> _parent;
|
||||
const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
|
||||
|
||||
};
|
||||
|
||||
class PreviewWrap final : public Ui::RpWidget {
|
||||
public:
|
||||
PreviewWrap(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
std::shared_ptr<Ui::ChatStyle> style,
|
||||
std::shared_ptr<Ui::ChatTheme> theme,
|
||||
not_null<PeerData*> peer,
|
||||
rpl::producer<uint8> colorIndexValue,
|
||||
rpl::producer<DocumentId> backgroundEmojiId);
|
||||
~PreviewWrap();
|
||||
|
||||
private:
|
||||
using Element = HistoryView::Element;
|
||||
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
void initElements();
|
||||
|
||||
const not_null<Ui::GenericBox*> _box;
|
||||
const not_null<PeerData*> _peer;
|
||||
const not_null<ChannelData*> _fake;
|
||||
const not_null<History*> _history;
|
||||
const not_null<WebPageData*> _webpage;
|
||||
const std::shared_ptr<Ui::ChatTheme> _theme;
|
||||
const std::shared_ptr<Ui::ChatStyle> _style;
|
||||
const std::unique_ptr<PreviewDelegate> _delegate;
|
||||
const not_null<HistoryItem*> _replyToItem;
|
||||
const not_null<HistoryItem*> _replyItem;
|
||||
std::unique_ptr<Element> _element;
|
||||
Ui::PeerUserpicView _userpic;
|
||||
QPoint _position;
|
||||
|
||||
};
|
||||
|
||||
ColorSample::ColorSample(
|
||||
not_null<QWidget*> parent,
|
||||
std::shared_ptr<Ui::ChatStyle> style,
|
||||
rpl::producer<uint8> colorIndex,
|
||||
const QString &name)
|
||||
: AbstractButton(parent)
|
||||
, _style(style)
|
||||
, _name(st::semiboldTextStyle, name) {
|
||||
std::move(
|
||||
colorIndex
|
||||
) | rpl::start_with_next([=](uint8 index) {
|
||||
_index = index;
|
||||
update();
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
ColorSample::ColorSample(
|
||||
not_null<QWidget*> parent,
|
||||
std::shared_ptr<Ui::ChatStyle> style,
|
||||
uint8 colorIndex,
|
||||
bool selected)
|
||||
: AbstractButton(parent)
|
||||
, _style(style)
|
||||
, _index(colorIndex)
|
||||
, _selected(selected)
|
||||
, _simple(true) {
|
||||
}
|
||||
|
||||
void ColorSample::setSelected(bool selected) {
|
||||
if (_selected == selected) {
|
||||
return;
|
||||
}
|
||||
_selected = selected;
|
||||
_selectAnimation.start(
|
||||
[=] { update(); },
|
||||
_selected ? 0. : 1.,
|
||||
_selected ? 1. : 0.,
|
||||
kSelectAnimationDuration);
|
||||
}
|
||||
|
||||
void ColorSample::paintEvent(QPaintEvent *e) {
|
||||
auto p = Painter(this);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
const auto colors = _style->coloredValues(false, _index);
|
||||
if (!_simple && !colors.outlines[1].alpha()) {
|
||||
const auto radius = height() / 2;
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(colors.bg);
|
||||
p.drawRoundedRect(rect(), radius, radius);
|
||||
|
||||
const auto padding = st::settingsColorSamplePadding;
|
||||
p.setPen(colors.name);
|
||||
p.setBrush(Qt::NoBrush);
|
||||
p.setFont(st::semiboldFont);
|
||||
_name.drawLeftElided(
|
||||
p,
|
||||
padding.left(),
|
||||
padding.top(),
|
||||
width() - padding.left() - padding.right(),
|
||||
width(),
|
||||
1,
|
||||
style::al_top);
|
||||
} else {
|
||||
const auto size = float64(width());
|
||||
const auto half = size / 2.;
|
||||
const auto full = QRectF(-half, -half, size, size);
|
||||
p.translate(size / 2., size / 2.);
|
||||
p.setPen(Qt::NoPen);
|
||||
if (colors.outlines[1].alpha()) {
|
||||
p.rotate(-45.);
|
||||
p.setClipRect(-size, 0, 3 * size, size);
|
||||
p.setBrush(colors.outlines[1]);
|
||||
p.drawEllipse(full);
|
||||
p.setClipRect(-size, -size, 3 * size, size);
|
||||
}
|
||||
p.setBrush(colors.outlines[0]);
|
||||
p.drawEllipse(full);
|
||||
p.setClipping(false);
|
||||
if (colors.outlines[2].alpha()) {
|
||||
const auto multiplier = size / st::settingsColorSampleSize;
|
||||
const auto center = st::settingsColorSampleCenter * multiplier;
|
||||
const auto radius = st::settingsColorSampleCenterRadius
|
||||
* multiplier;
|
||||
p.setBrush(colors.outlines[2]);
|
||||
p.drawRoundedRect(
|
||||
QRectF(-center / 2., -center / 2., center, center),
|
||||
radius,
|
||||
radius);
|
||||
}
|
||||
const auto selected = _selectAnimation.value(_selected ? 1. : 0.);
|
||||
if (selected > 0) {
|
||||
const auto line = st::settingsColorRadioStroke * 1.;
|
||||
const auto thickness = selected * line;
|
||||
auto pen = st::boxBg->p;
|
||||
pen.setWidthF(thickness);
|
||||
p.setBrush(Qt::NoBrush);
|
||||
p.setPen(pen);
|
||||
const auto skip = 1.5 * line;
|
||||
p.drawEllipse(full.marginsRemoved({ skip, skip, skip, skip }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8 ColorSample::index() const {
|
||||
return _index;
|
||||
}
|
||||
|
||||
int ColorSample::naturalWidth() const {
|
||||
if (_name.isEmpty() || _style->colorPatternIndex(_index)) {
|
||||
return st::settingsColorSampleSize;
|
||||
}
|
||||
const auto padding = st::settingsColorSamplePadding;
|
||||
return std::max(
|
||||
padding.left() + _name.maxWidth() + padding.right(),
|
||||
padding.top() + st::semiboldFont->height + padding.bottom());
|
||||
}
|
||||
|
||||
PreviewWrap::PreviewWrap(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
std::shared_ptr<Ui::ChatStyle> style,
|
||||
std::shared_ptr<Ui::ChatTheme> theme,
|
||||
not_null<PeerData*> peer,
|
||||
rpl::producer<uint8> colorIndexValue,
|
||||
rpl::producer<DocumentId> backgroundEmojiId)
|
||||
: RpWidget(box)
|
||||
, _box(box)
|
||||
, _peer(peer)
|
||||
, _fake(_peer->owner().channel(kFakeChannelId))
|
||||
, _history(_fake->owner().history(_fake))
|
||||
, _webpage(_peer->owner().webpage(
|
||||
kFakeWebPageId,
|
||||
WebPageType::Article,
|
||||
u"internal:peer-color-webpage-preview"_q,
|
||||
u"internal:peer-color-webpage-preview"_q,
|
||||
tr::lng_settings_color_link_name(tr::now),
|
||||
tr::lng_settings_color_link_title(tr::now),
|
||||
{ tr::lng_settings_color_link_description(tr::now) },
|
||||
nullptr, // photo
|
||||
nullptr, // document
|
||||
WebPageCollage(),
|
||||
0, // duration
|
||||
QString(), // author
|
||||
false, // hasLargeMedia
|
||||
0)) // pendingTill
|
||||
, _theme(theme)
|
||||
, _style(style)
|
||||
, _delegate(std::make_unique<PreviewDelegate>(box, _style.get(), [=] {
|
||||
update();
|
||||
}))
|
||||
, _replyToItem(_history->addNewLocalMessage(
|
||||
_history->nextNonHistoryEntryId(),
|
||||
(MessageFlag::FakeHistoryItem
|
||||
| MessageFlag::HasFromId
|
||||
| MessageFlag::Post),
|
||||
UserId(), // via
|
||||
FullReplyTo(),
|
||||
base::unixtime::now(), // date
|
||||
_fake->id,
|
||||
QString(), // postAuthor
|
||||
TextWithEntities{ _peer->isSelf()
|
||||
? tr::lng_settings_color_reply(tr::now)
|
||||
: tr::lng_settings_color_reply_channel(tr::now),
|
||||
},
|
||||
MTP_messageMediaEmpty(),
|
||||
HistoryMessageMarkupData(),
|
||||
uint64(0)))
|
||||
, _replyItem(_history->addNewLocalMessage(
|
||||
_history->nextNonHistoryEntryId(),
|
||||
(MessageFlag::FakeHistoryItem
|
||||
| MessageFlag::HasFromId
|
||||
| MessageFlag::HasReplyInfo
|
||||
| MessageFlag::Post),
|
||||
UserId(), // via
|
||||
FullReplyTo{ .messageId = _replyToItem->fullId() },
|
||||
base::unixtime::now(), // date
|
||||
_fake->id,
|
||||
QString(), // postAuthor
|
||||
TextWithEntities{ _peer->isSelf()
|
||||
? tr::lng_settings_color_text(tr::now)
|
||||
: tr::lng_settings_color_text_channel(tr::now),
|
||||
},
|
||||
MTP_messageMediaWebPage(
|
||||
MTP_flags(0),
|
||||
MTP_webPagePending(
|
||||
MTP_flags(0),
|
||||
MTP_long(_webpage->id),
|
||||
MTPstring(),
|
||||
MTP_int(0))),
|
||||
HistoryMessageMarkupData(),
|
||||
uint64(0)))
|
||||
, _element(_replyItem->createView(_delegate.get()))
|
||||
, _position(0, st::msgMargin.bottom()) {
|
||||
_style->apply(_theme.get());
|
||||
|
||||
_fake->setName(peer->name(), QString());
|
||||
std::move(colorIndexValue) | rpl::start_with_next([=](uint8 index) {
|
||||
_fake->changeColorIndex(index);
|
||||
update();
|
||||
}, lifetime());
|
||||
std::move(backgroundEmojiId) | rpl::start_with_next([=](DocumentId id) {
|
||||
_fake->changeBackgroundEmojiId(id);
|
||||
update();
|
||||
}, lifetime());
|
||||
|
||||
const auto session = &_history->session();
|
||||
session->data().viewRepaintRequest(
|
||||
) | rpl::start_with_next([=](not_null<const Element*> view) {
|
||||
if (view == _element.get()) {
|
||||
update();
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
initElements();
|
||||
}
|
||||
|
||||
PreviewWrap::~PreviewWrap() {
|
||||
_element = nullptr;
|
||||
_replyItem->destroy();
|
||||
_replyToItem->destroy();
|
||||
}
|
||||
|
||||
void PreviewWrap::paintEvent(QPaintEvent *e) {
|
||||
auto p = Painter(this);
|
||||
const auto clip = e->rect();
|
||||
|
||||
p.setClipRect(clip);
|
||||
Window::SectionWidget::PaintBackground(
|
||||
p,
|
||||
_theme.get(),
|
||||
QSize(_box->width(), _box->window()->height()),
|
||||
clip);
|
||||
|
||||
auto context = _theme->preparePaintContext(
|
||||
_style.get(),
|
||||
rect(),
|
||||
clip,
|
||||
!window()->isActiveWindow());
|
||||
|
||||
p.translate(_position);
|
||||
_element->draw(p, context);
|
||||
|
||||
if (_element->displayFromPhoto()) {
|
||||
auto userpicBottom = height()
|
||||
- _element->marginBottom()
|
||||
- _element->marginTop();
|
||||
const auto userpicTop = userpicBottom - st::msgPhotoSize;
|
||||
_peer->paintUserpicLeft(
|
||||
p,
|
||||
_userpic,
|
||||
st::historyPhotoLeft,
|
||||
userpicTop,
|
||||
width(),
|
||||
st::msgPhotoSize);
|
||||
}
|
||||
}
|
||||
|
||||
void PreviewWrap::initElements() {
|
||||
_element->initDimensions();
|
||||
|
||||
widthValue(
|
||||
) | rpl::filter([=](int width) {
|
||||
return width > st::msgMinWidth;
|
||||
}) | rpl::start_with_next([=](int width) {
|
||||
const auto height = _position.y()
|
||||
+ _element->resizeGetHeight(width)
|
||||
+ st::msgMargin.top();
|
||||
resize(width, height);
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
PreviewDelegate::PreviewDelegate(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<Ui::ChatStyle*> st,
|
||||
Fn<void()> update)
|
||||
: _parent(parent)
|
||||
, _pathGradient(HistoryView::MakePathShiftGradient(st, update)) {
|
||||
}
|
||||
|
||||
bool PreviewDelegate::elementAnimationsPaused() {
|
||||
return _parent->window()->isActiveWindow();
|
||||
}
|
||||
|
||||
auto PreviewDelegate::elementPathShiftGradient()
|
||||
-> not_null<Ui::PathShiftGradient*> {
|
||||
return _pathGradient.get();
|
||||
}
|
||||
|
||||
HistoryView::Context PreviewDelegate::elementContext() {
|
||||
return HistoryView::Context::AdminLog;
|
||||
}
|
||||
|
||||
void Set(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
uint8 colorIndex,
|
||||
DocumentId backgroundEmojiId) {
|
||||
const auto wasIndex = peer->colorIndex();
|
||||
const auto wasEmojiId = peer->backgroundEmojiId();
|
||||
|
||||
const auto setLocal = [=](uint8 index, DocumentId emojiId) {
|
||||
using UpdateFlag = Data::PeerUpdate::Flag;
|
||||
peer->changeColorIndex(index);
|
||||
peer->changeBackgroundEmojiId(emojiId);
|
||||
peer->session().changes().peerUpdated(
|
||||
peer,
|
||||
UpdateFlag::Color | UpdateFlag::BackgroundEmoji);
|
||||
};
|
||||
setLocal(colorIndex, backgroundEmojiId);
|
||||
|
||||
const auto done = [=] {
|
||||
show->showToast(peer->isSelf()
|
||||
? tr::lng_settings_color_changed(tr::now)
|
||||
: tr::lng_settings_color_changed_channel(tr::now));
|
||||
};
|
||||
const auto fail = [=](const MTP::Error &error) {
|
||||
setLocal(wasIndex, wasEmojiId);
|
||||
show->showToast(error.type());
|
||||
};
|
||||
const auto send = [&](auto &&request) {
|
||||
peer->session().api().request(
|
||||
std::move(request)
|
||||
).done(done).fail(fail).send();
|
||||
};
|
||||
if (peer->isSelf()) {
|
||||
send(MTPaccount_UpdateColor(
|
||||
MTP_flags(
|
||||
MTPaccount_UpdateColor::Flag::f_background_emoji_id),
|
||||
MTP_int(colorIndex),
|
||||
MTP_long(backgroundEmojiId)));
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
send(MTPchannels_UpdateColor(
|
||||
MTP_flags(
|
||||
MTPchannels_UpdateColor::Flag::f_background_emoji_id),
|
||||
channel->inputChannel,
|
||||
MTP_int(colorIndex),
|
||||
MTP_long(backgroundEmojiId)));
|
||||
} else {
|
||||
Unexpected("Invalid peer type in Set(colorIndex).");
|
||||
}
|
||||
}
|
||||
|
||||
void Apply(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
uint8 colorIndex,
|
||||
DocumentId backgroundEmojiId,
|
||||
Fn<void()> close,
|
||||
Fn<void()> cancel) {
|
||||
const auto session = &peer->session();
|
||||
if (peer->colorIndex() == colorIndex
|
||||
&& peer->backgroundEmojiId() == backgroundEmojiId) {
|
||||
close();
|
||||
} else if (peer->isSelf() && !session->premium()) {
|
||||
Settings::ShowPremiumPromoToast(
|
||||
show,
|
||||
tr::lng_settings_color_subscribe(
|
||||
tr::now,
|
||||
lt_link,
|
||||
Ui::Text::Link(
|
||||
Ui::Text::Bold(
|
||||
tr::lng_send_as_premium_required_link(tr::now))),
|
||||
Ui::Text::WithEntities),
|
||||
u"name_color"_q);
|
||||
cancel();
|
||||
} else if (peer->isSelf()) {
|
||||
Set(show, peer, colorIndex, backgroundEmojiId);
|
||||
close();
|
||||
} else {
|
||||
session->api().request(MTPpremium_GetBoostsStatus(
|
||||
peer->input
|
||||
)).done([=](const MTPpremium_BoostsStatus &result) {
|
||||
const auto &data = result.data();
|
||||
const auto required = session->account().appConfig().get<int>(
|
||||
"channel_color_level_min",
|
||||
5);
|
||||
if (data.vlevel().v >= required) {
|
||||
Set(show, peer, colorIndex, backgroundEmojiId);
|
||||
close();
|
||||
return;
|
||||
}
|
||||
const auto next = data.vnext_level_boosts().value_or_empty();
|
||||
const auto openStatistics = [=] {
|
||||
if (const auto controller = show->resolveWindow(
|
||||
ChatHelpers::WindowUsage::PremiumPromo)) {
|
||||
controller->showSection(Info::Boosts::Make(peer));
|
||||
}
|
||||
};
|
||||
show->show(Box(Ui::AskBoostBox, Ui::AskBoostBoxData{
|
||||
.link = qs(data.vboost_url()),
|
||||
.boost = {
|
||||
.level = data.vlevel().v,
|
||||
.boosts = data.vboosts().v,
|
||||
.thisLevelBoosts = data.vcurrent_level_boosts().v,
|
||||
.nextLevelBoosts = next,
|
||||
},
|
||||
.requiredLevel = required,
|
||||
}, openStatistics, nullptr));
|
||||
cancel();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
show->showToast(error.type());
|
||||
cancel();
|
||||
}).send();
|
||||
}
|
||||
}
|
||||
|
||||
class ColorSelector final : public Ui::RpWidget {
|
||||
public:
|
||||
ColorSelector(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
std::shared_ptr<Ui::ChatStyle> style,
|
||||
rpl::producer<std::vector<uint8>> indices,
|
||||
uint8 index,
|
||||
Fn<void(uint8)> callback);
|
||||
|
||||
private:
|
||||
void fillFrom(std::vector<uint8> indices);
|
||||
|
||||
int resizeGetHeight(int newWidth) override;
|
||||
|
||||
const std::shared_ptr<Ui::ChatStyle> _style;
|
||||
std::vector<std::unique_ptr<ColorSample>> _samples;
|
||||
const Fn<void(uint8)> _callback;
|
||||
uint8 _index = 0;
|
||||
|
||||
};
|
||||
|
||||
ColorSelector::ColorSelector(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
std::shared_ptr<Ui::ChatStyle> style,
|
||||
rpl::producer<std::vector<uint8>> indices,
|
||||
uint8 index,
|
||||
Fn<void(uint8)> callback)
|
||||
: RpWidget(box)
|
||||
, _style(style)
|
||||
, _callback(std::move(callback))
|
||||
, _index(index) {
|
||||
std::move(
|
||||
indices
|
||||
) | rpl::start_with_next([=](std::vector<uint8> indices) {
|
||||
fillFrom(std::move(indices));
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void ColorSelector::fillFrom(std::vector<uint8> indices) {
|
||||
auto samples = std::vector<std::unique_ptr<ColorSample>>();
|
||||
const auto add = [&](uint8 index) {
|
||||
auto i = ranges::find(_samples, index, &ColorSample::index);
|
||||
if (i != end(_samples)) {
|
||||
samples.push_back(std::move(*i));
|
||||
_samples.erase(i);
|
||||
} else {
|
||||
samples.push_back(std::make_unique<ColorSample>(
|
||||
this,
|
||||
_style,
|
||||
index,
|
||||
index == _index));
|
||||
samples.back()->show();
|
||||
samples.back()->setClickedCallback([=] {
|
||||
if (_index != index) {
|
||||
_callback(index);
|
||||
|
||||
ranges::find(
|
||||
_samples,
|
||||
_index,
|
||||
&ColorSample::index
|
||||
)->get()->setSelected(false);
|
||||
_index = index;
|
||||
ranges::find(
|
||||
_samples,
|
||||
_index,
|
||||
&ColorSample::index
|
||||
)->get()->setSelected(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
for (const auto index : indices) {
|
||||
add(index);
|
||||
}
|
||||
if (!ranges::contains(indices, _index)) {
|
||||
add(_index);
|
||||
}
|
||||
_samples = std::move(samples);
|
||||
if (width() > 0) {
|
||||
resizeToWidth(width());
|
||||
}
|
||||
}
|
||||
|
||||
int ColorSelector::resizeGetHeight(int newWidth) {
|
||||
if (newWidth <= 0) {
|
||||
return 0;
|
||||
}
|
||||
const auto count = int(_samples.size());
|
||||
const auto columns = Ui::kSimpleColorIndexCount;
|
||||
const auto skip = st::settingsColorRadioSkip;
|
||||
const auto size = (newWidth - skip * (columns - 1)) / float64(columns);
|
||||
const auto isize = int(base::SafeRound(size));
|
||||
auto top = 0;
|
||||
auto left = 0.;
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
_samples[i]->resize(isize, isize);
|
||||
_samples[i]->move(int(base::SafeRound(left)), top);
|
||||
left += size + skip;
|
||||
if (!((i + 1) % columns)) {
|
||||
top += isize + skip;
|
||||
left = 0.;
|
||||
}
|
||||
}
|
||||
return (top - skip) + ((count % columns) ? (isize + skip) : 0);
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::SettingsButton> CreateEmojiIconButton(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
std::shared_ptr<Ui::ChatStyle> style,
|
||||
rpl::producer<uint8> colorIndexValue,
|
||||
rpl::producer<DocumentId> emojiIdValue,
|
||||
Fn<void(DocumentId)> emojiIdChosen) {
|
||||
const auto &basicSt = st::settingsButtonNoIcon;
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
const auto added = st::normalFont->spacew;
|
||||
const auto emojiSize = Data::FrameSizeFromTag({}) / ratio;
|
||||
const auto noneWidth = added
|
||||
+ st::normalFont->width(tr::lng_settings_color_emoji_off(tr::now));
|
||||
const auto emojiWidth = added + emojiSize;
|
||||
const auto rightPadding = std::max(noneWidth, emojiWidth)
|
||||
+ basicSt.padding.right();
|
||||
const auto st = parent->lifetime().make_state<style::SettingsButton>(
|
||||
basicSt);
|
||||
st->padding.setRight(rightPadding);
|
||||
auto result = CreateButton(
|
||||
parent,
|
||||
tr::lng_settings_color_emoji(),
|
||||
*st,
|
||||
{});
|
||||
const auto raw = result.data();
|
||||
|
||||
const auto right = Ui::CreateChild<Ui::RpWidget>(raw);
|
||||
right->show();
|
||||
|
||||
struct State {
|
||||
Info::Profile::EmojiStatusPanel panel;
|
||||
std::unique_ptr<Ui::Text::CustomEmoji> emoji;
|
||||
DocumentId emojiId = 0;
|
||||
uint8 index = 0;
|
||||
};
|
||||
const auto state = right->lifetime().make_state<State>();
|
||||
state->panel.backgroundEmojiChosen(
|
||||
) | rpl::start_with_next(emojiIdChosen, raw->lifetime());
|
||||
|
||||
std::move(colorIndexValue) | rpl::start_with_next([=](uint8 index) {
|
||||
state->index = index;
|
||||
if (state->emoji) {
|
||||
right->update();
|
||||
}
|
||||
}, right->lifetime());
|
||||
|
||||
const auto session = &show->session();
|
||||
std::move(emojiIdValue) | rpl::start_with_next([=](DocumentId emojiId) {
|
||||
state->emojiId = emojiId;
|
||||
state->emoji = emojiId
|
||||
? session->data().customEmojiManager().create(
|
||||
emojiId,
|
||||
[=] { right->update(); })
|
||||
: nullptr;
|
||||
right->resize(
|
||||
(emojiId ? emojiWidth : noneWidth) + added,
|
||||
right->height());
|
||||
right->update();
|
||||
}, right->lifetime());
|
||||
|
||||
rpl::combine(
|
||||
raw->sizeValue(),
|
||||
right->widthValue()
|
||||
) | rpl::start_with_next([=](QSize outer, int width) {
|
||||
right->resize(width, outer.height());
|
||||
const auto skip = st::settingsButton.padding.right();
|
||||
right->moveToRight(skip - added, 0, outer.width());
|
||||
}, right->lifetime());
|
||||
|
||||
right->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (state->panel.paintBadgeFrame(right)) {
|
||||
return;
|
||||
}
|
||||
auto p = QPainter(right);
|
||||
const auto height = right->height();
|
||||
if (state->emoji) {
|
||||
const auto colors = style->coloredValues(false, state->index);
|
||||
state->emoji->paint(p, {
|
||||
.textColor = colors.name,
|
||||
.position = QPoint(added, (height - emojiSize) / 2),
|
||||
.internal = {
|
||||
.forceFirstFrame = true,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
const auto &font = st::normalFont;
|
||||
p.setFont(font);
|
||||
p.setPen(style->windowActiveTextFg());
|
||||
p.drawText(
|
||||
QPoint(added, (height - font->height) / 2 + font->ascent),
|
||||
tr::lng_settings_color_emoji_off(tr::now));
|
||||
}
|
||||
}, right->lifetime());
|
||||
|
||||
raw->setClickedCallback([=] {
|
||||
const auto customTextColor = [=] {
|
||||
return style->coloredValues(false, state->index).name;
|
||||
};
|
||||
const auto controller = show->resolveWindow(
|
||||
ChatHelpers::WindowUsage::PremiumPromo);
|
||||
if (controller) {
|
||||
state->panel.show({
|
||||
.controller = controller,
|
||||
.button = right,
|
||||
.currentBackgroundEmojiId = state->emojiId,
|
||||
.customTextColor = customTextColor,
|
||||
.backgroundEmojiMode = true,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void EditPeerColorBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
std::shared_ptr<Ui::ChatStyle> style,
|
||||
std::shared_ptr<Ui::ChatTheme> theme) {
|
||||
box->setTitle(tr::lng_settings_color_title());
|
||||
box->setWidth(st::boxWideWidth);
|
||||
|
||||
struct State {
|
||||
rpl::variable<uint8> index;
|
||||
rpl::variable<DocumentId> emojiId;
|
||||
bool changing = false;
|
||||
bool applying = false;
|
||||
};
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
state->index = peer->colorIndex();
|
||||
state->emojiId = peer->backgroundEmojiId();
|
||||
|
||||
box->addRow(object_ptr<PreviewWrap>(
|
||||
box,
|
||||
style,
|
||||
theme,
|
||||
peer,
|
||||
state->index.value(),
|
||||
state->emojiId.value()
|
||||
), {});
|
||||
|
||||
const auto appConfig = &peer->session().account().appConfig();
|
||||
auto indices = rpl::single(
|
||||
rpl::empty
|
||||
) | rpl::then(
|
||||
appConfig->refreshed()
|
||||
) | rpl::map([=] {
|
||||
const auto list = appConfig->get<std::vector<int>>(
|
||||
"peer_colors_available",
|
||||
{ 0, 1, 2, 3, 4, 5, 6 });
|
||||
return list | ranges::views::transform([](int i) {
|
||||
return uint8(i);
|
||||
}) | ranges::to_vector;
|
||||
});
|
||||
const auto margin = st::settingsColorRadioMargin;
|
||||
const auto skip = st::settingsColorRadioSkip;
|
||||
box->addRow(
|
||||
object_ptr<ColorSelector>(
|
||||
box,
|
||||
style,
|
||||
std::move(indices),
|
||||
state->index.current(),
|
||||
[=](uint8 index) { state->index = index; }),
|
||||
{ margin, skip, margin, skip });
|
||||
|
||||
const auto container = box->verticalLayout();
|
||||
AddDividerText(container, peer->isSelf()
|
||||
? tr::lng_settings_color_about()
|
||||
: tr::lng_settings_color_about_channel());
|
||||
|
||||
AddSkip(container, st::settingsColorSampleSkip);
|
||||
|
||||
container->add(CreateEmojiIconButton(
|
||||
container,
|
||||
show,
|
||||
style,
|
||||
state->index.value(),
|
||||
state->emojiId.value(),
|
||||
[=](DocumentId id) { state->emojiId = id; }));
|
||||
|
||||
AddSkip(container, st::settingsColorSampleSkip);
|
||||
AddDividerText(container, peer->isSelf()
|
||||
? tr::lng_settings_color_emoji_about()
|
||||
: tr::lng_settings_color_emoji_about_channel());
|
||||
|
||||
box->addButton(tr::lng_settings_apply(), [=] {
|
||||
if (state->applying) {
|
||||
return;
|
||||
}
|
||||
state->applying = true;
|
||||
const auto index = state->index.current();
|
||||
const auto emojiId = state->emojiId.current();
|
||||
Apply(show, peer, index, emojiId, crl::guard(box, [=] {
|
||||
box->closeBox();
|
||||
}), crl::guard(box, [=] {
|
||||
state->applying = false;
|
||||
}));
|
||||
});
|
||||
box->addButton(tr::lng_cancel(), [=] {
|
||||
box->closeBox();
|
||||
});
|
||||
}
|
||||
|
||||
void AddPeerColorButton(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> peer) {
|
||||
const auto button = AddButton(
|
||||
container,
|
||||
(peer->isSelf()
|
||||
? tr::lng_settings_theme_name_color()
|
||||
: tr::lng_edit_channel_color()),
|
||||
st::settingsColorButton,
|
||||
{ &st::menuIconChangeColors });
|
||||
|
||||
auto colorIndexValue = peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
Data::PeerUpdate::Flag::Color
|
||||
) | rpl::map([=] {
|
||||
return peer->colorIndex();
|
||||
});
|
||||
const auto name = peer->shortName();
|
||||
|
||||
const auto style = std::make_shared<Ui::ChatStyle>(
|
||||
peer->session().colorIndicesValue());
|
||||
const auto theme = std::shared_ptr<Ui::ChatTheme>(
|
||||
Window::Theme::DefaultChatThemeOn(button->lifetime()));
|
||||
style->apply(theme.get());
|
||||
|
||||
const auto sample = Ui::CreateChild<ColorSample>(
|
||||
button.get(),
|
||||
style,
|
||||
rpl::duplicate(colorIndexValue),
|
||||
name);
|
||||
sample->show();
|
||||
|
||||
rpl::combine(
|
||||
button->widthValue(),
|
||||
tr::lng_settings_theme_name_color(),
|
||||
rpl::duplicate(colorIndexValue)
|
||||
) | rpl::start_with_next([=](
|
||||
int width,
|
||||
const QString &button,
|
||||
int colorIndex) {
|
||||
const auto sampleSize = st::settingsColorSampleSize;
|
||||
const auto available = width
|
||||
- st::settingsButton.padding.left()
|
||||
- (st::settingsColorButton.padding.right() - sampleSize)
|
||||
- st::settingsButton.style.font->width(button)
|
||||
- st::settingsButtonRightSkip;
|
||||
if (style->colorPatternIndex(colorIndex)) {
|
||||
sample->resize(sampleSize, sampleSize);
|
||||
} else {
|
||||
const auto padding = st::settingsColorSamplePadding;
|
||||
const auto wantedHeight = padding.top()
|
||||
+ st::semiboldFont->height
|
||||
+ padding.bottom();
|
||||
const auto wantedWidth = sample->naturalWidth();
|
||||
sample->resize(std::min(wantedWidth, available), wantedHeight);
|
||||
}
|
||||
sample->update();
|
||||
}, sample->lifetime());
|
||||
|
||||
rpl::combine(
|
||||
button->sizeValue(),
|
||||
sample->sizeValue(),
|
||||
std::move(colorIndexValue)
|
||||
) | rpl::start_with_next([=](QSize outer, QSize inner, int colorIndex) {
|
||||
const auto right = st::settingsColorButton.padding.right()
|
||||
- st::settingsColorSampleSkip
|
||||
- st::settingsColorSampleSize
|
||||
- (style->colorPatternIndex(colorIndex)
|
||||
? 0
|
||||
: st::settingsColorSamplePadding.right());
|
||||
sample->move(
|
||||
outer.width() - right - inner.width(),
|
||||
(outer.height() - inner.height()) / 2);
|
||||
}, sample->lifetime());
|
||||
|
||||
sample->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
button->setClickedCallback([=] {
|
||||
show->show(Box(EditPeerColorBox, show, peer, style, theme));
|
||||
});
|
||||
}
|
||||
31
Telegram/SourceFiles/boxes/peers/edit_peer_color_box.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace ChatHelpers {
|
||||
class Show;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace Ui {
|
||||
class GenericBox;
|
||||
class ChatStyle;
|
||||
class ChatTheme;
|
||||
class VerticalLayout;
|
||||
} // namespace Ui
|
||||
|
||||
void EditPeerColorBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
std::shared_ptr<Ui::ChatStyle> style = nullptr,
|
||||
std::shared_ptr<Ui::ChatTheme> theme = nullptr);
|
||||
|
||||
void AddPeerColorButton(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> peer);
|
||||
@@ -13,8 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "main/main_session.h"
|
||||
#include "boxes/add_contact_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "boxes/peers/edit_participants_box.h"
|
||||
#include "boxes/peers/edit_peer_color_box.h"
|
||||
#include "boxes/peers/edit_peer_common.h"
|
||||
#include "boxes/peers/edit_peer_type_box.h"
|
||||
#include "boxes/peers/edit_peer_history_visibility_box.h"
|
||||
@@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/peers/edit_linked_chat_box.h"
|
||||
#include "boxes/peers/edit_peer_requests_box.h"
|
||||
#include "boxes/peers/edit_peer_reactions.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "boxes/stickers_box.h"
|
||||
#include "boxes/username_box.h"
|
||||
#include "ui/boxes/single_choice_box.h"
|
||||
@@ -302,6 +303,7 @@ private:
|
||||
void fillLinkedChatButton();
|
||||
//void fillInviteLinkButton();
|
||||
void fillForumButton();
|
||||
void fillColorIndexButton();
|
||||
void fillSignaturesButton();
|
||||
void fillHistoryVisibilityButton();
|
||||
void fillManageSection();
|
||||
@@ -905,6 +907,13 @@ void Controller::refreshForumToggleLocked() {
|
||||
_controls.forumToggle->setToggleLocked(locked);
|
||||
}
|
||||
|
||||
void Controller::fillColorIndexButton() {
|
||||
Expects(_controls.buttonsLayout != nullptr);
|
||||
|
||||
const auto show = _navigation->uiShow();
|
||||
AddPeerColorButton(_controls.buttonsLayout, show, _peer);
|
||||
}
|
||||
|
||||
void Controller::fillSignaturesButton() {
|
||||
Expects(_controls.buttonsLayout != nullptr);
|
||||
|
||||
@@ -1024,74 +1033,42 @@ void Controller::fillManageSection() {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto canEditType = [&] {
|
||||
return isChannel
|
||||
? channel->amCreator()
|
||||
: chat->amCreator();
|
||||
}();
|
||||
const auto canEditSignatures = [&] {
|
||||
return isChannel
|
||||
? (channel->canEditSignatures() && !channel->isMegagroup())
|
||||
: false;
|
||||
}();
|
||||
const auto canEditPreHistoryHidden = [&] {
|
||||
return isChannel
|
||||
? channel->canEditPreHistoryHidden()
|
||||
: chat->canEditPreHistoryHidden();
|
||||
}();
|
||||
const auto canEditType = isChannel
|
||||
? channel->amCreator()
|
||||
: chat->amCreator();
|
||||
const auto canEditSignatures = isChannel
|
||||
&& channel->canEditSignatures()
|
||||
&& !channel->isMegagroup();
|
||||
const auto canEditPreHistoryHidden = isChannel
|
||||
? channel->canEditPreHistoryHidden()
|
||||
: chat->canEditPreHistoryHidden();
|
||||
const auto canEditForum = isChannel
|
||||
? (channel->isMegagroup() && channel->amCreator())
|
||||
: chat->amCreator();
|
||||
|
||||
const auto canEditPermissions = [&] {
|
||||
return isChannel
|
||||
? channel->canEditPermissions()
|
||||
: chat->canEditPermissions();
|
||||
}();
|
||||
const auto canEditInviteLinks = [&] {
|
||||
return isChannel
|
||||
? channel->canHaveInviteLink()
|
||||
: chat->canHaveInviteLink();
|
||||
}();
|
||||
const auto canViewAdmins = [&] {
|
||||
return isChannel
|
||||
? channel->canViewAdmins()
|
||||
: chat->amIn();
|
||||
}();
|
||||
const auto canViewMembers = [&] {
|
||||
return isChannel
|
||||
? channel->canViewMembers()
|
||||
: chat->amIn();
|
||||
}();
|
||||
const auto canViewKicked = [&] {
|
||||
return isChannel
|
||||
? (channel->isBroadcast() || channel->isGigagroup())
|
||||
: false;
|
||||
}();
|
||||
const auto hasRecentActions = [&] {
|
||||
return isChannel
|
||||
? (channel->hasAdminRights() || channel->amCreator())
|
||||
: false;
|
||||
}();
|
||||
|
||||
const auto canEditStickers = [&] {
|
||||
return isChannel
|
||||
? channel->canEditStickers()
|
||||
: false;
|
||||
}();
|
||||
const auto canDeleteChannel = [&] {
|
||||
return isChannel
|
||||
? channel->canDelete()
|
||||
: false;
|
||||
}();
|
||||
|
||||
const auto canViewOrEditLinkedChat = [&] {
|
||||
return !isChannel
|
||||
? false
|
||||
: channel->linkedChat()
|
||||
? true
|
||||
: (channel->isBroadcast() && channel->canEditInformation());
|
||||
}();
|
||||
const auto canEditPermissions = isChannel
|
||||
? channel->canEditPermissions()
|
||||
: chat->canEditPermissions();
|
||||
const auto canEditInviteLinks = isChannel
|
||||
? channel->canHaveInviteLink()
|
||||
: chat->canHaveInviteLink();
|
||||
const auto canViewAdmins = isChannel
|
||||
? channel->canViewAdmins()
|
||||
: chat->amIn();
|
||||
const auto canViewMembers = isChannel
|
||||
? channel->canViewMembers()
|
||||
: chat->amIn();
|
||||
const auto canViewKicked = isChannel
|
||||
&& (channel->isBroadcast() || channel->isGigagroup());
|
||||
const auto hasRecentActions = isChannel
|
||||
&& (channel->hasAdminRights() || channel->amCreator());
|
||||
const auto canEditStickers = isChannel && channel->canEditStickers();
|
||||
const auto canDeleteChannel = isChannel && channel->canDelete();
|
||||
const auto canEditColorIndex = isChannel
|
||||
&& !channel->isMegagroup()
|
||||
&& channel->canEditInformation();
|
||||
const auto canViewOrEditLinkedChat = isChannel
|
||||
&& (channel->linkedChat()
|
||||
|| (channel->isBroadcast() && channel->canEditInformation()));
|
||||
|
||||
AddSkip(_controls.buttonsLayout, 0);
|
||||
|
||||
@@ -1109,11 +1086,15 @@ void Controller::fillManageSection() {
|
||||
if (canEditForum) {
|
||||
fillForumButton();
|
||||
}
|
||||
if (canEditColorIndex) {
|
||||
fillColorIndexButton();
|
||||
}
|
||||
if (canEditSignatures) {
|
||||
fillSignaturesButton();
|
||||
}
|
||||
if (canEditPreHistoryHidden
|
||||
|| canEditForum
|
||||
|| canEditColorIndex
|
||||
|| canEditSignatures
|
||||
//|| canEditInviteLinks
|
||||
|| canViewOrEditLinkedChat
|
||||
|
||||
@@ -63,7 +63,9 @@ PeerId GenerateUser(not_null<History*> history, const QString &name) {
|
||||
MTPstring(), // lang code
|
||||
MTPEmojiStatus(),
|
||||
MTPVector<MTPUsername>(),
|
||||
MTPint())); // stories_max_id
|
||||
MTPint(), // stories_max_id
|
||||
MTP_int(0), // color
|
||||
MTPlong())); // background_emoji_id
|
||||
return peerId;
|
||||
}
|
||||
|
||||
@@ -71,7 +73,7 @@ AdminLog::OwnedItem GenerateItem(
|
||||
not_null<HistoryView::ElementDelegate*> delegate,
|
||||
not_null<History*> history,
|
||||
PeerId from,
|
||||
MsgId replyTo,
|
||||
FullMsgId replyTo,
|
||||
const QString &text) {
|
||||
Expects(history->peer->isUser());
|
||||
|
||||
@@ -81,7 +83,7 @@ AdminLog::OwnedItem GenerateItem(
|
||||
| MessageFlag::HasFromId
|
||||
| MessageFlag::HasReplyInfo),
|
||||
UserId(), // via
|
||||
FullReplyTo{ .msgId = replyTo },
|
||||
FullReplyTo{ .messageId = replyTo },
|
||||
base::unixtime::now(), // date
|
||||
from,
|
||||
QString(), // postAuthor
|
||||
@@ -131,7 +133,8 @@ void AddMessage(
|
||||
state->delegate = std::make_unique<Delegate>(
|
||||
controller,
|
||||
crl::guard(widget, [=] { widget->update(); }));
|
||||
state->style = std::make_unique<Ui::ChatStyle>();
|
||||
state->style = std::make_unique<Ui::ChatStyle>(
|
||||
controller->session().colorIndicesValue());
|
||||
state->style->apply(controller->defaultChatTheme().get());
|
||||
state->icons.lifetimes = std::vector<rpl::lifetime>(2);
|
||||
|
||||
@@ -143,13 +146,13 @@ void AddMessage(
|
||||
GenerateUser(
|
||||
history,
|
||||
tr::lng_settings_chat_message_reply_from(tr::now)),
|
||||
0,
|
||||
FullMsgId(),
|
||||
tr::lng_settings_chat_message_reply(tr::now));
|
||||
auto message = GenerateItem(
|
||||
state->delegate.get(),
|
||||
history,
|
||||
history->peer->id,
|
||||
state->reply->data()->fullId().msg,
|
||||
state->reply->data()->fullId(),
|
||||
tr::lng_settings_chat_message(tr::now));
|
||||
const auto view = message.get();
|
||||
state->item = std::move(message);
|
||||
|
||||
@@ -97,8 +97,6 @@ callButtonLabel: FlatLabel(defaultFlatLabel) {
|
||||
textFg: callNameFg;
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(11px);
|
||||
linkFont: font(11px);
|
||||
linkFontOver: font(11px underline);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,8 +216,6 @@ callMuteButtonLabel: FlatLabel(defaultFlatLabel) {
|
||||
textFg: groupCallMembersFg;
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(14px);
|
||||
linkFont: font(14px);
|
||||
linkFontOver: font(14px underline);
|
||||
}
|
||||
}
|
||||
callMuteButtonActiveInner: IconButton {
|
||||
@@ -294,8 +290,6 @@ callName: FlatLabel(defaultFlatLabel) {
|
||||
align: align(top);
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(21px semibold);
|
||||
linkFont: font(21px semibold);
|
||||
linkFontOver: font(21px semibold underline);
|
||||
}
|
||||
}
|
||||
callStatus: FlatLabel(defaultFlatLabel) {
|
||||
@@ -305,8 +299,6 @@ callStatus: FlatLabel(defaultFlatLabel) {
|
||||
align: align(top);
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(14px);
|
||||
linkFont: font(14px);
|
||||
linkFontOver: font(14px underline);
|
||||
}
|
||||
}
|
||||
callRemoteAudioMute: FlatLabel(callStatus) {
|
||||
@@ -314,8 +306,6 @@ callRemoteAudioMute: FlatLabel(callStatus) {
|
||||
textFg: videoPlayIconFg;
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(12px);
|
||||
linkFont: font(12px);
|
||||
linkFontOver: font(12px underline);
|
||||
}
|
||||
}
|
||||
callRemoteAudioMuteSkip: 12px;
|
||||
@@ -746,8 +736,6 @@ groupCallShareBoxList: PeerList(groupCallMembersList) {
|
||||
item: PeerListItem(groupCallMembersListItem) {
|
||||
nameStyle: TextStyle(defaultTextStyle) {
|
||||
font: font(11px);
|
||||
linkFont: font(11px);
|
||||
linkFontOver: font(11px);
|
||||
}
|
||||
checkbox: RoundImageCheckbox(groupCallMembersListCheckbox) {
|
||||
imageRadius: 28px;
|
||||
@@ -784,8 +772,6 @@ groupCallTitleLabel: FlatLabel(groupCallSubtitleLabel) {
|
||||
textFg: groupCallMembersFg;
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(semibold 14px);
|
||||
linkFont: font(semibold 14px);
|
||||
linkFontOver: font(semibold 14px);
|
||||
}
|
||||
}
|
||||
groupCallTitleSeparator: 4px;
|
||||
@@ -1203,8 +1189,6 @@ callTopBarMuteCrossLine: CrossLineAnimation {
|
||||
groupCallStartsIn: FlatLabel(defaultFlatLabel) {
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(20px semibold);
|
||||
linkFont: font(20px semibold);
|
||||
linkFontOver: font(20px semibold underline);
|
||||
}
|
||||
textFg: groupCallMembersFg;
|
||||
}
|
||||
|
||||
@@ -712,13 +712,24 @@ void Instance::destroyCurrentCall() {
|
||||
}
|
||||
}
|
||||
|
||||
bool Instance::hasActivePanel(not_null<Main::Session*> session) const {
|
||||
bool Instance::hasVisiblePanel(Main::Session *session) const {
|
||||
if (inCall()) {
|
||||
return (&_currentCall->user()->session() == session)
|
||||
&& _currentCallPanel->isActive();
|
||||
return _currentCallPanel->isVisible()
|
||||
&& (!session || (&_currentCall->user()->session() == session));
|
||||
} else if (inGroupCall()) {
|
||||
return (&_currentGroupCall->peer()->session() == session)
|
||||
&& _currentGroupCallPanel->isActive();
|
||||
return _currentGroupCallPanel->isVisible()
|
||||
&& (!session || (&_currentGroupCall->peer()->session() == session));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Instance::hasActivePanel(Main::Session *session) const {
|
||||
if (inCall()) {
|
||||
return _currentCallPanel->isActive()
|
||||
&& (!session || (&_currentCall->user()->session() == session));
|
||||
} else if (inGroupCall()) {
|
||||
return _currentGroupCallPanel->isActive()
|
||||
&& (!session || (&_currentGroupCall->peer()->session() == session));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -89,8 +89,10 @@ public:
|
||||
[[nodiscard]] rpl::producer<GroupCall*> currentGroupCallValue() const;
|
||||
[[nodiscard]] bool inCall() const;
|
||||
[[nodiscard]] bool inGroupCall() const;
|
||||
[[nodiscard]] bool hasVisiblePanel(
|
||||
Main::Session *session = nullptr) const;
|
||||
[[nodiscard]] bool hasActivePanel(
|
||||
not_null<Main::Session*> session) const;
|
||||
Main::Session *session = nullptr) const;
|
||||
bool activateCurrentCall(const QString &joinHash = QString());
|
||||
bool minimizeCurrentActiveCall();
|
||||
bool toggleFullScreenCurrentActiveCall();
|
||||
|
||||
@@ -106,12 +106,15 @@ Panel::Panel(not_null<Call*> call)
|
||||
|
||||
Panel::~Panel() = default;
|
||||
|
||||
bool Panel::isActive() const {
|
||||
return window()->isActiveWindow()
|
||||
&& window()->isVisible()
|
||||
bool Panel::isVisible() const {
|
||||
return window()->isVisible()
|
||||
&& !(window()->windowState() & Qt::WindowMinimized);
|
||||
}
|
||||
|
||||
bool Panel::isActive() const {
|
||||
return window()->isActiveWindow() && isVisible();
|
||||
}
|
||||
|
||||
void Panel::showAndActivate() {
|
||||
if (window()->isHidden()) {
|
||||
window()->show();
|
||||
|
||||
@@ -61,6 +61,7 @@ public:
|
||||
Panel(not_null<Call*> call);
|
||||
~Panel();
|
||||
|
||||
[[nodiscard]] bool isVisible() const;
|
||||
[[nodiscard]] bool isActive() const;
|
||||
void showAndActivate();
|
||||
void minimize();
|
||||
|
||||
@@ -202,8 +202,7 @@ void Userpic::createCache(Image *image) {
|
||||
{
|
||||
auto p = QPainter(&filled);
|
||||
Ui::EmptyUserpic(
|
||||
Ui::EmptyUserpic::UserpicColor(
|
||||
Data::PeerColorIndex(_peer->id)),
|
||||
Ui::EmptyUserpic::UserpicColor(_peer->colorIndex()),
|
||||
_peer->name()
|
||||
).paintCircle(p, 0, 0, size, size);
|
||||
}
|
||||
|
||||
@@ -258,12 +258,15 @@ not_null<GroupCall*> Panel::call() const {
|
||||
return _call;
|
||||
}
|
||||
|
||||
bool Panel::isActive() const {
|
||||
return window()->isActiveWindow()
|
||||
&& window()->isVisible()
|
||||
bool Panel::isVisible() const {
|
||||
return window()->isVisible()
|
||||
&& !(window()->windowState() & Qt::WindowMinimized);
|
||||
}
|
||||
|
||||
bool Panel::isActive() const {
|
||||
return window()->isActiveWindow() && isVisible();
|
||||
}
|
||||
|
||||
base::weak_ptr<Ui::Toast::Instance> Panel::showToast(
|
||||
const QString &text,
|
||||
crl::time duration) {
|
||||
|
||||
@@ -91,6 +91,7 @@ public:
|
||||
|
||||
[[nodiscard]] not_null<Ui::RpWidget*> widget() const;
|
||||
[[nodiscard]] not_null<GroupCall*> call() const;
|
||||
[[nodiscard]] bool isVisible() const;
|
||||
[[nodiscard]] bool isActive() const;
|
||||
|
||||
base::weak_ptr<Ui::Toast::Instance> showToast(
|
||||
|
||||
@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_calls.h"
|
||||
|
||||
#include <QtCore/QDateTime>
|
||||
#include <QtCore/QLocale>
|
||||
|
||||
namespace Calls::Group::Ui {
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ struct SendCommandRequest {
|
||||
not_null<PeerData*> peer;
|
||||
QString command;
|
||||
FullMsgId context;
|
||||
MsgId replyTo = 0;
|
||||
FullReplyTo replyTo;
|
||||
};
|
||||
|
||||
[[nodiscard]] QString WrapCommandInChat(
|
||||
|
||||
@@ -509,8 +509,6 @@ emojiPanColorAllLabel: FlatLabel(defaultFlatLabel) {
|
||||
minWidth: 40px;
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(12px);
|
||||
linkFont: font(12px);
|
||||
linkFontOver: font(12px);
|
||||
}
|
||||
}
|
||||
emojiPanColorAllPadding: margins(10px, 6px, 10px, -1px);
|
||||
@@ -664,6 +662,9 @@ statusEmojiPan: EmojiPan(defaultEmojiPan) {
|
||||
fadeLeft: icon {{ "fade_horizontal-flip_horizontal", windowBg }};
|
||||
fadeRight: icon {{ "fade_horizontal", windowBg }};
|
||||
}
|
||||
backgroundEmojiPan: EmojiPan(defaultEmojiPan) {
|
||||
padding: margins(7px, 7px, 4px, 0px);
|
||||
}
|
||||
|
||||
inlineBotsScroll: ScrollArea(defaultSolidScroll) {
|
||||
deltat: stickerPanPadding;
|
||||
|
||||
@@ -467,6 +467,7 @@ EmojiListWidget::EmojiListWidget(
|
||||
, _localSetsManager(
|
||||
std::make_unique<LocalStickersManager>(&session()))
|
||||
, _customRecentFactory(std::move(descriptor.customRecentFactory))
|
||||
, _customTextColor(std::move(descriptor.customTextColor))
|
||||
, _overBg(st::emojiPanRadius, st().overBg)
|
||||
, _collapsedBg(st::emojiPanExpand.height / 2, st().headerFg)
|
||||
, _picker(this, st())
|
||||
@@ -476,7 +477,7 @@ EmojiListWidget::EmojiListWidget(
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
}
|
||||
|
||||
if (_mode != Mode::RecentReactions) {
|
||||
if (_mode != Mode::RecentReactions && _mode != Mode::BackgroundEmoji) {
|
||||
setupSearch();
|
||||
}
|
||||
|
||||
@@ -791,10 +792,12 @@ object_ptr<TabbedSelector::InnerFooter> EmojiListWidget::createFooter() {
|
||||
};
|
||||
auto result = object_ptr<StickersListFooter>(FooterDescriptor{
|
||||
.session = &session(),
|
||||
.customTextColor = _customTextColor,
|
||||
.paused = footerPaused,
|
||||
.parent = this,
|
||||
.st = &st(),
|
||||
.features = { .stickersSettings = false },
|
||||
.forceFirstFrame = (_mode == Mode::BackgroundEmoji),
|
||||
});
|
||||
_footer = result;
|
||||
|
||||
@@ -1027,6 +1030,14 @@ void EmojiListWidget::fillRecentFrom(const std::vector<DocumentId> &list) {
|
||||
if (!id && _mode == Mode::EmojiStatus) {
|
||||
const auto star = QString::fromUtf8("\xe2\xad\x90\xef\xb8\x8f");
|
||||
_recent.push_back({ .id = { Ui::Emoji::Find(star) } });
|
||||
} else if (!id && _mode == Mode::BackgroundEmoji) {
|
||||
const auto fakeId = DocumentId(5246772116543512028ULL);
|
||||
const auto no = QString::fromUtf8("\xe2\x9b\x94\xef\xb8\x8f");
|
||||
_recent.push_back({
|
||||
.custom = resolveCustomRecent(fakeId),
|
||||
.id = { Ui::Emoji::Find(no) },
|
||||
});
|
||||
_recentCustomIds.emplace(fakeId);
|
||||
} else {
|
||||
_recent.push_back({
|
||||
.custom = resolveCustomRecent(id),
|
||||
@@ -1188,7 +1199,9 @@ void EmojiListWidget::paintEvent(QPaintEvent *e) {
|
||||
void EmojiListWidget::validateEmojiPaintContext(
|
||||
const ExpandingContext &context) {
|
||||
auto value = Ui::Text::CustomEmojiPaintContext{
|
||||
.textColor = (_mode == Mode::EmojiStatus
|
||||
.textColor = (_customTextColor
|
||||
? _customTextColor()
|
||||
: (_mode == Mode::EmojiStatus)
|
||||
? anim::color(
|
||||
st::stickerPanPremium1,
|
||||
st::stickerPanPremium2,
|
||||
@@ -1199,6 +1212,7 @@ void EmojiListWidget::validateEmojiPaintContext(
|
||||
.scale = context.progress,
|
||||
.paused = On(powerSavingFlag()) || paused(),
|
||||
.scaled = context.expanding,
|
||||
.internal = { .forceFirstFrame = (_mode == Mode::BackgroundEmoji) },
|
||||
};
|
||||
if (!_emojiPaintContext) {
|
||||
_emojiPaintContext = std::make_unique<
|
||||
@@ -1629,6 +1643,9 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
|
||||
case Mode::TopicIcon:
|
||||
Settings::ShowPremium(resolved, u"forum_topic_icon"_q);
|
||||
break;
|
||||
case Mode::BackgroundEmoji:
|
||||
Settings::ShowPremium(resolved, u"name_color"_q);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1995,7 +2012,10 @@ void EmojiListWidget::refreshCustom() {
|
||||
const auto &sets = owner->stickers().sets();
|
||||
const auto push = [&](uint64 setId, bool installed) {
|
||||
auto it = sets.find(setId);
|
||||
if (it == sets.cend() || it->second->stickers.isEmpty()) {
|
||||
if (it == sets.cend()
|
||||
|| it->second->stickers.isEmpty()
|
||||
|| (_mode == Mode::BackgroundEmoji
|
||||
&& !it->second->textColor())) {
|
||||
return;
|
||||
}
|
||||
const auto canRemove = !!(it->second->flags
|
||||
|
||||
@@ -74,11 +74,13 @@ enum class EmojiListMode {
|
||||
FullReactions,
|
||||
RecentReactions,
|
||||
UserpicBuilder,
|
||||
BackgroundEmoji,
|
||||
};
|
||||
|
||||
struct EmojiListDescriptor {
|
||||
std::shared_ptr<Show> show;
|
||||
EmojiListMode mode = EmojiListMode::Full;
|
||||
Fn<QColor()> customTextColor;
|
||||
Fn<bool()> paused;
|
||||
std::vector<DocumentId> customRecentList;
|
||||
Fn<std::unique_ptr<Ui::Text::CustomEmoji>(
|
||||
@@ -386,6 +388,7 @@ private:
|
||||
base::flat_map<
|
||||
DocumentId,
|
||||
std::unique_ptr<Ui::Text::CustomEmoji>> _customRecent;
|
||||
Fn<QColor()> _customTextColor;
|
||||
int _customSingleSize = 0;
|
||||
bool _allowWithoutPremium = false;
|
||||
Ui::RoundRect _overBg;
|
||||
|
||||
@@ -493,10 +493,7 @@ InlineBotQuery ParseInlineBotQuery(
|
||||
result.lookingUpBot = true;
|
||||
}
|
||||
}
|
||||
if (result.lookingUpBot) {
|
||||
result.query = QString();
|
||||
return result;
|
||||
} else if (result.bot
|
||||
if (result.bot
|
||||
&& (!result.bot->isBot()
|
||||
|| result.bot->botInfo->inlinePlaceholder.isEmpty())) {
|
||||
result.bot = nullptr;
|
||||
@@ -601,6 +598,12 @@ MessageLinksParser::MessageLinksParser(not_null<Ui::InputField*> field)
|
||||
_lifetime = _field->changes(
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto length = _field->getTextWithTags().text.size();
|
||||
if (!length) {
|
||||
_lastLength = 0;
|
||||
_timer.cancel();
|
||||
parse();
|
||||
return;
|
||||
}
|
||||
const auto timeout = (std::abs(length - _lastLength) > 2)
|
||||
? 0
|
||||
: kParseLinksTimeout;
|
||||
@@ -642,16 +645,13 @@ bool MessageLinksParser::eventFilter(QObject *object, QEvent *event) {
|
||||
return QObject::eventFilter(object, event);
|
||||
}
|
||||
|
||||
const rpl::variable<QStringList> &MessageLinksParser::list() const {
|
||||
return _list;
|
||||
}
|
||||
|
||||
void MessageLinksParser::parse() {
|
||||
const auto &textWithTags = _field->getTextWithTags();
|
||||
const auto &text = textWithTags.text;
|
||||
const auto &tags = textWithTags.tags;
|
||||
const auto &markdownTags = _field->getMarkdownTags();
|
||||
if (_disabled || text.isEmpty()) {
|
||||
_ranges = {};
|
||||
_list = QStringList();
|
||||
return;
|
||||
}
|
||||
@@ -663,7 +663,7 @@ void MessageLinksParser::parse() {
|
||||
|| (tag == Ui::InputField::kTagSpoiler);
|
||||
};
|
||||
|
||||
auto ranges = QVector<LinkRange>();
|
||||
_ranges.clear();
|
||||
|
||||
auto tag = tags.begin();
|
||||
const auto tagsEnd = tags.end();
|
||||
@@ -672,7 +672,7 @@ void MessageLinksParser::parse() {
|
||||
|
||||
if (Ui::InputField::IsValidMarkdownLink(tag->id)
|
||||
&& !TextUtilities::IsMentionLink(tag->id)) {
|
||||
ranges.push_back({ tag->offset, tag->length, tag->id });
|
||||
_ranges.push_back({ tag->offset, tag->length, tag->id });
|
||||
}
|
||||
++tag;
|
||||
};
|
||||
@@ -774,7 +774,7 @@ void MessageLinksParser::parse() {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const auto range = LinkRange {
|
||||
const auto range = MessageLinkRange{
|
||||
int(domainOffset),
|
||||
static_cast<int>(p - start - domainOffset),
|
||||
QString()
|
||||
@@ -782,22 +782,20 @@ void MessageLinksParser::parse() {
|
||||
processTagsBefore(domainOffset);
|
||||
if (!hasTagsIntersection(range.start + range.length)) {
|
||||
if (markdownTagsAllow(range.start, range.length)) {
|
||||
ranges.push_back(range);
|
||||
_ranges.push_back(range);
|
||||
}
|
||||
}
|
||||
offset = matchOffset = p - start;
|
||||
}
|
||||
processTagsBefore(QFIXED_MAX);
|
||||
processTagsBefore(Ui::kQFixedMax);
|
||||
|
||||
apply(text, ranges);
|
||||
applyRanges(text);
|
||||
}
|
||||
|
||||
void MessageLinksParser::apply(
|
||||
const QString &text,
|
||||
const QVector<LinkRange> &ranges) {
|
||||
const auto count = int(ranges.size());
|
||||
void MessageLinksParser::applyRanges(const QString &text) {
|
||||
const auto count = int(_ranges.size());
|
||||
const auto current = _list.current();
|
||||
const auto computeLink = [&](const LinkRange &range) {
|
||||
const auto computeLink = [&](const MessageLinkRange &range) {
|
||||
return range.custom.isEmpty()
|
||||
? base::StringViewMid(text, range.start, range.length)
|
||||
: QStringView(range.custom);
|
||||
@@ -807,7 +805,7 @@ void MessageLinksParser::apply(
|
||||
return true;
|
||||
}
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
if (computeLink(ranges[i]) != current[i]) {
|
||||
if (computeLink(_ranges[i]) != current[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -818,7 +816,7 @@ void MessageLinksParser::apply(
|
||||
}
|
||||
auto parsed = QStringList();
|
||||
parsed.reserve(count);
|
||||
for (const auto &range : ranges) {
|
||||
for (const auto &range : _ranges) {
|
||||
parsed.push_back(computeLink(range).toString());
|
||||
}
|
||||
_list = std::move(parsed);
|
||||
|
||||
@@ -7,9 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "base/qt/qt_compare.h"
|
||||
#include "base/timer.h"
|
||||
#include "chat_helpers/compose/compose_features.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_SPELLCHECK
|
||||
#include "boxes/dictionaries_manager.h"
|
||||
@@ -96,38 +97,42 @@ AutocompleteQuery ParseMentionHashtagBotCommandQuery(
|
||||
not_null<const Ui::InputField*> field,
|
||||
ChatHelpers::ComposeFeatures features);
|
||||
|
||||
class MessageLinksParser : private QObject {
|
||||
struct MessageLinkRange {
|
||||
int start = 0;
|
||||
int length = 0;
|
||||
QString custom;
|
||||
|
||||
friend inline auto operator<=>(
|
||||
const MessageLinkRange&,
|
||||
const MessageLinkRange&) = default;
|
||||
friend inline bool operator==(
|
||||
const MessageLinkRange&,
|
||||
const MessageLinkRange&) = default;
|
||||
};
|
||||
|
||||
class MessageLinksParser final : private QObject {
|
||||
public:
|
||||
MessageLinksParser(not_null<Ui::InputField*> field);
|
||||
|
||||
void parseNow();
|
||||
void setDisabled(bool disabled);
|
||||
|
||||
[[nodiscard]] const rpl::variable<QStringList> &list() const;
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *object, QEvent *event) override;
|
||||
[[nodiscard]] const rpl::variable<QStringList> &list() const {
|
||||
return _list;
|
||||
}
|
||||
[[nodiscard]] const std::vector<MessageLinkRange> &ranges() const {
|
||||
return _ranges;
|
||||
}
|
||||
|
||||
private:
|
||||
struct LinkRange {
|
||||
int start;
|
||||
int length;
|
||||
QString custom;
|
||||
};
|
||||
friend inline bool operator==(const LinkRange &a, const LinkRange &b) {
|
||||
return (a.start == b.start)
|
||||
&& (a.length == b.length)
|
||||
&& (a.custom == b.custom);
|
||||
}
|
||||
friend inline bool operator!=(const LinkRange &a, const LinkRange &b) {
|
||||
return !(a == b);
|
||||
}
|
||||
bool eventFilter(QObject *object, QEvent *event) override;
|
||||
|
||||
void parse();
|
||||
void apply(const QString &text, const QVector<LinkRange> &ranges);
|
||||
void applyRanges(const QString &text);
|
||||
|
||||
not_null<Ui::InputField*> _field;
|
||||
rpl::variable<QStringList> _list;
|
||||
std::vector<MessageLinkRange> _ranges;
|
||||
int _lastLength = 0;
|
||||
bool _disabled = false;
|
||||
base::Timer _timer;
|
||||
|
||||
@@ -291,12 +291,14 @@ StickersListFooter::StickersListFooter(Descriptor &&descriptor)
|
||||
descriptor.parent,
|
||||
descriptor.st ? *descriptor.st : st::defaultEmojiPan)
|
||||
, _session(descriptor.session)
|
||||
, _paused(descriptor.paused)
|
||||
, _customTextColor(std::move(descriptor.customTextColor))
|
||||
, _paused(std::move(descriptor.paused))
|
||||
, _features(descriptor.features)
|
||||
, _iconState([=] { update(); })
|
||||
, _subiconState([=] { update(); })
|
||||
, _selectionBg(st::emojiPanRadius, st().categoriesBgOver)
|
||||
, _subselectionBg(st().iconArea / 2, st().categoriesBgOver) {
|
||||
, _subselectionBg(st().iconArea / 2, st().categoriesBgOver)
|
||||
, _forceFirstFrame(descriptor.forceFirstFrame) {
|
||||
setMouseTracking(true);
|
||||
|
||||
_iconsLeft = st().iconSkip
|
||||
@@ -1345,13 +1347,16 @@ void StickersListFooter::paintSetIconToCache(
|
||||
const auto y = (st().footer - icon.pixh) / 2;
|
||||
if (icon.custom) {
|
||||
icon.custom->paint(p, Ui::Text::CustomEmoji::Context{
|
||||
.textColor = st().textFg->c,
|
||||
.textColor = (_customTextColor
|
||||
? _customTextColor()
|
||||
: st().textFg->c),
|
||||
.size = QSize(icon.pixw, icon.pixh),
|
||||
.now = now,
|
||||
.scale = context.progress,
|
||||
.position = { x, y },
|
||||
.paused = paused,
|
||||
.scaled = context.expanding,
|
||||
.internal = { .forceFirstFrame = _forceFirstFrame },
|
||||
});
|
||||
} else if (icon.lottie && icon.lottie->ready()) {
|
||||
const auto frame = icon.lottie->frame();
|
||||
@@ -1428,11 +1433,13 @@ void StickersListFooter::paintSetIconToCache(
|
||||
return icons[index];
|
||||
};
|
||||
const auto paintOne = [&](int left, const style::icon *icon) {
|
||||
icon->paint(
|
||||
p,
|
||||
left + (_singleWidth - icon->width()) / 2,
|
||||
(st().footer - icon->height()) / 2,
|
||||
width());
|
||||
left += (_singleWidth - icon->width()) / 2;
|
||||
const auto top = (st().footer - icon->height()) / 2;
|
||||
if (_customTextColor) {
|
||||
icon->paint(p, left, top, width(), _customTextColor());
|
||||
} else {
|
||||
icon->paint(p, left, top, width());
|
||||
}
|
||||
};
|
||||
if (_icons[info.index].setId == AllEmojiSectionSetId()
|
||||
&& info.width > _singleWidth) {
|
||||
|
||||
@@ -115,10 +115,12 @@ class StickersListFooter final : public TabbedSelector::InnerFooter {
|
||||
public:
|
||||
struct Descriptor {
|
||||
not_null<Main::Session*> session;
|
||||
Fn<QColor()> customTextColor;
|
||||
Fn<bool()> paused;
|
||||
not_null<RpWidget*> parent;
|
||||
const style::EmojiPan *st = nullptr;
|
||||
ComposeFeatures features;
|
||||
bool forceFirstFrame = false;
|
||||
};
|
||||
explicit StickersListFooter(Descriptor &&descriptor);
|
||||
|
||||
@@ -269,6 +271,7 @@ private:
|
||||
void clipCallback(Media::Clip::Notification notification, uint64 setId);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
const Fn<QColor()> _customTextColor;
|
||||
const Fn<bool()> _paused;
|
||||
const ComposeFeatures _features;
|
||||
|
||||
@@ -303,6 +306,7 @@ private:
|
||||
int _subiconsWidth = 0;
|
||||
bool _subiconsExpanded = false;
|
||||
bool _repaintScheduled = false;
|
||||
bool _forceFirstFrame = false;
|
||||
|
||||
rpl::event_stream<> _openSettingsRequests;
|
||||
rpl::event_stream<uint64> _setChosen;
|
||||
|
||||
@@ -331,7 +331,7 @@ TabbedSelector::TabbedSelector(
|
||||
Mode mode)
|
||||
: TabbedSelector(parent, {
|
||||
.show = std::move(show),
|
||||
.st = (mode == Mode::EmojiStatus
|
||||
.st = ((mode == Mode::EmojiStatus || mode == Mode::BackgroundEmoji)
|
||||
? st::statusEmojiPan
|
||||
: st::defaultEmojiPan),
|
||||
.level = level,
|
||||
@@ -347,6 +347,7 @@ TabbedSelector::TabbedSelector(
|
||||
, _features(descriptor.features)
|
||||
, _show(std::move(descriptor.show))
|
||||
, _level(descriptor.level)
|
||||
, _customTextColor(std::move(descriptor.customTextColor))
|
||||
, _mode(descriptor.mode)
|
||||
, _panelRounding(Ui::PrepareCornerPixmaps(st::emojiPanRadius, _st.bg))
|
||||
, _categoriesRounding(
|
||||
@@ -512,7 +513,10 @@ TabbedSelector::Tab TabbedSelector::createTab(SelectorTab type, int index) {
|
||||
.show = _show,
|
||||
.mode = (_mode == Mode::EmojiStatus
|
||||
? EmojiMode::EmojiStatus
|
||||
: _mode == Mode::BackgroundEmoji
|
||||
? EmojiMode::BackgroundEmoji
|
||||
: EmojiMode::Full),
|
||||
.customTextColor = _customTextColor,
|
||||
.paused = paused,
|
||||
.st = &_st,
|
||||
.features = _features,
|
||||
|
||||
@@ -81,6 +81,7 @@ enum class TabbedSelectorMode {
|
||||
EmojiOnly,
|
||||
MediaEditor,
|
||||
EmojiStatus,
|
||||
BackgroundEmoji,
|
||||
};
|
||||
|
||||
struct TabbedSelectorDescriptor {
|
||||
@@ -88,6 +89,7 @@ struct TabbedSelectorDescriptor {
|
||||
const style::EmojiPan &st;
|
||||
PauseReason level = {};
|
||||
TabbedSelectorMode mode = TabbedSelectorMode::Full;
|
||||
Fn<QColor()> customTextColor;
|
||||
ComposeFeatures features;
|
||||
};
|
||||
|
||||
@@ -272,6 +274,7 @@ private:
|
||||
const ComposeFeatures _features;
|
||||
const std::shared_ptr<Show> _show;
|
||||
const PauseReason _level = {};
|
||||
const Fn<QColor()> _customTextColor;
|
||||
|
||||
Mode _mode = Mode::Full;
|
||||
int _roundRadius = 0;
|
||||
|
||||
@@ -512,14 +512,16 @@ void Application::startMediaView() {
|
||||
InvokeQueued(this, [=] {
|
||||
_mediaView = std::make_unique<Media::View::OverlayWidget>();
|
||||
});
|
||||
#else // Q_OS_MAC
|
||||
#elif defined Q_OS_WIN // Q_OS_MAC || Q_OS_WIN
|
||||
// On Windows we needed such hack for the main window, otherwise
|
||||
// somewhere inside the media viewer creating code its geometry
|
||||
// was broken / lost to some invalid values.
|
||||
const auto current = _lastActivePrimaryWindow->widget()->geometry();
|
||||
_mediaView = std::make_unique<Media::View::OverlayWidget>();
|
||||
_lastActivePrimaryWindow->widget()->Ui::RpWidget::setGeometry(current);
|
||||
#endif // Q_OS_MAC
|
||||
#else
|
||||
_mediaView = std::make_unique<Media::View::OverlayWidget>();
|
||||
#endif // Q_OS_MAC || Q_OS_WIN
|
||||
}
|
||||
|
||||
void Application::startTray() {
|
||||
|
||||
@@ -30,6 +30,16 @@ std::map<int, const char*> BetaLogs() {
|
||||
|
||||
"- Fix memory leak in Direct3D 11 media viewer on Windows.\n"
|
||||
},
|
||||
{
|
||||
4010004,
|
||||
"- Statistics in channels and group chats.\n"
|
||||
|
||||
"- Nice looking code blocks with syntax highlight.\n"
|
||||
|
||||
"- Copy full code block by click on its header.\n"
|
||||
|
||||
"- Send a highlighted code block using ```language syntax.\n"
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -308,7 +308,6 @@ void BotCommandClickHandler::onClick(ClickContext context) const {
|
||||
.peer = peer,
|
||||
.command = _cmd,
|
||||
.context = my.itemId,
|
||||
.replyTo = 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,6 +344,8 @@ QByteArray Settings::serialize() const {
|
||||
for (const auto &id : _recentEmojiSkip) {
|
||||
stream << id;
|
||||
}
|
||||
stream
|
||||
<< qint32(_trayIconMonochrome.current() ? 1 : 0);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -451,6 +453,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
quint64 macRoundIconDigest = _macRoundIconDigest.value_or(0);
|
||||
qint32 storiesClickTooltipHidden = _storiesClickTooltipHidden.current() ? 1 : 0;
|
||||
base::flat_set<QString> recentEmojiSkip;
|
||||
qint32 trayIconMonochrome = (_trayIconMonochrome.current() ? 1 : 0);
|
||||
|
||||
stream >> themesAccentColors;
|
||||
if (!stream.atEnd()) {
|
||||
@@ -701,6 +704,12 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> trayIconMonochrome;
|
||||
} else {
|
||||
// Let existing clients use the old value.
|
||||
trayIconMonochrome = 0;
|
||||
}
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("App Error: "
|
||||
"Bad data for Core::Settings::constructFromSerialized()"));
|
||||
@@ -894,6 +903,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||
_macRoundIconDigest = macRoundIconDigest ? macRoundIconDigest : std::optional<uint64>();
|
||||
_storiesClickTooltipHidden = (storiesClickTooltipHidden == 1);
|
||||
_recentEmojiSkip = std::move(recentEmojiSkip);
|
||||
_trayIconMonochrome = (trayIconMonochrome == 1);
|
||||
}
|
||||
|
||||
QString Settings::getSoundPath(const QString &key) const {
|
||||
|
||||
@@ -702,6 +702,15 @@ public:
|
||||
[[nodiscard]] rpl::producer<bool> closeToTaskbarChanges() const {
|
||||
return _closeToTaskbar.changes();
|
||||
}
|
||||
void setTrayIconMonochrome(bool value) {
|
||||
_trayIconMonochrome = value;
|
||||
}
|
||||
[[nodiscard]] bool trayIconMonochrome() const {
|
||||
return _trayIconMonochrome.current();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<bool> trayIconMonochromeChanges() const {
|
||||
return _trayIconMonochrome.changes();
|
||||
}
|
||||
|
||||
void setCustomDeviceModel(const QString &model) {
|
||||
_customDeviceModel = model;
|
||||
@@ -918,6 +927,7 @@ private:
|
||||
rpl::variable<WorkMode> _workMode = WorkMode::WindowAndTray;
|
||||
base::flags<Calls::Group::StickedTooltip> _hiddenGroupCallTooltips;
|
||||
rpl::variable<bool> _closeToTaskbar = false;
|
||||
rpl::variable<bool> _trayIconMonochrome = true;
|
||||
rpl::variable<QString> _customDeviceModel;
|
||||
rpl::variable<Media::RepeatMode> _playerRepeatMode;
|
||||
rpl::variable<Media::OrderMode> _playerOrderMode;
|
||||
|
||||
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_text_entities.h"
|
||||
#include "api/api_chat_filters.h"
|
||||
#include "api/api_chat_invite.h"
|
||||
#include "api/api_premium.h"
|
||||
#include "base/qthelp_regex.h"
|
||||
#include "base/qthelp_url.h"
|
||||
#include "lang/lang_cloud_manager.h"
|
||||
@@ -23,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "boxes/share_box.h"
|
||||
#include "boxes/connection_box.h"
|
||||
#include "boxes/gift_premium_box.h"
|
||||
#include "boxes/sticker_set_box.h"
|
||||
#include "boxes/sessions_box.h"
|
||||
#include "boxes/language_box.h"
|
||||
@@ -348,6 +350,11 @@ bool ResolveUsernameOrPhone(
|
||||
const auto domainParam = params.value(u"domain"_q);
|
||||
const auto appnameParam = params.value(u"appname"_q);
|
||||
|
||||
if (domainParam == u"giftcode"_q && !appnameParam.isEmpty()) {
|
||||
ResolveGiftCode(controller, appnameParam);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fix t.me/s/username links.
|
||||
const auto webChannelPreviewLink = (domainParam == u"s"_q)
|
||||
&& !appnameParam.isEmpty();
|
||||
@@ -637,6 +644,17 @@ bool OpenExternalLink(
|
||||
context);
|
||||
}
|
||||
|
||||
bool CopyPeerId(
|
||||
Window::SessionController *controller,
|
||||
const Match &match,
|
||||
const QVariant &context) {
|
||||
TextUtilities::SetClipboardText(TextForMimeData{ match->captured(1) });
|
||||
if (controller) {
|
||||
controller->showToast(tr::lng_text_copied(tr::now));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ExportTestChatTheme(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<const Data::CloudTheme*> theme) {
|
||||
@@ -978,6 +996,10 @@ const std::vector<LocalUrlHandler> &InternalUrlHandlers() {
|
||||
u"^url:(.+)$"_q,
|
||||
OpenExternalLink
|
||||
},
|
||||
{
|
||||
u"^copy:(.+)$"_q,
|
||||
CopyPeerId
|
||||
}
|
||||
};
|
||||
return Result;
|
||||
}
|
||||
@@ -1078,7 +1100,7 @@ QString TryConvertUrlToLocal(QString url) {
|
||||
"("
|
||||
"/?\\?|"
|
||||
"/?$|"
|
||||
"/[a-zA-Z0-9\\.\\_]+/?(\\?|$)|"
|
||||
"/[a-zA-Z0-9\\.\\_\\-]+/?(\\?|$)|"
|
||||
"/\\d+/?(\\?|$)|"
|
||||
"/s/\\d+/?(\\?|$)|"
|
||||
"/\\d+/\\d+/?(\\?|$)"
|
||||
@@ -1103,7 +1125,7 @@ QString TryConvertUrlToLocal(QString url) {
|
||||
added = u"&post="_q + postMatch->captured(1);
|
||||
} else if (const auto storyMatch = regex_match(u"^/s/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
|
||||
added = u"&story="_q + storyMatch->captured(1);
|
||||
} else if (const auto appNameMatch = regex_match(u"^/([a-zA-Z0-9\\.\\_]+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
|
||||
} else if (const auto appNameMatch = regex_match(u"^/([a-zA-Z0-9\\.\\_\\-]+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
|
||||
added = u"&appname="_q + appNameMatch->captured(1);
|
||||
}
|
||||
return base + added + (params.isEmpty() ? QString() : '&' + params);
|
||||
|
||||
@@ -94,9 +94,10 @@ const auto CommandByName = base::flat_map<QString, Command>{
|
||||
{ u"read_chat"_q , Command::ReadChat },
|
||||
|
||||
// Shortcuts that have no default values.
|
||||
{ u"message"_q , Command::JustSendMessage },
|
||||
{ u"message_silently"_q , Command::SendSilentMessage },
|
||||
{ u"message_scheduled"_q , Command::ScheduleMessage },
|
||||
{ u"message"_q , Command::JustSendMessage },
|
||||
{ u"message_silently"_q , Command::SendSilentMessage },
|
||||
{ u"message_scheduled"_q , Command::ScheduleMessage },
|
||||
{ u"media_viewer_video_fullscreen"_q , Command::MediaViewerFullscreen },
|
||||
//
|
||||
};
|
||||
|
||||
|
||||
@@ -59,6 +59,8 @@ enum class Command {
|
||||
|
||||
ReadChat,
|
||||
|
||||
MediaViewerFullscreen,
|
||||
|
||||
SupportReloadTemplates,
|
||||
SupportToggleMuted,
|
||||
SupportScrollToCurrent,
|
||||
|
||||
@@ -243,6 +243,16 @@ bool UiIntegration::handleUrlClick(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UiIntegration::copyPreOnClick(const QVariant &context) {
|
||||
const auto my = context.value<ClickHandlerContext>();
|
||||
if (const auto window = my.sessionWindow.get()) {
|
||||
window->showToast(tr::lng_code_copied(tr::now));
|
||||
} else if (my.show) {
|
||||
my.show->showToast(tr::lng_code_copied(tr::now));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<Ui::Text::CustomEmoji> UiIntegration::createCustomEmoji(
|
||||
const QString &data,
|
||||
const std::any &context) {
|
||||
@@ -338,6 +348,10 @@ QString UiIntegration::phraseFormattingStrikeOut() {
|
||||
return tr::lng_menu_formatting_strike_out(tr::now);
|
||||
}
|
||||
|
||||
QString UiIntegration::phraseFormattingBlockquote() {
|
||||
return tr::lng_menu_formatting_blockquote(tr::now);
|
||||
}
|
||||
|
||||
QString UiIntegration::phraseFormattingMonospace() {
|
||||
return tr::lng_menu_formatting_monospace(tr::now);
|
||||
}
|
||||
@@ -394,6 +408,10 @@ QString UiIntegration::phraseBotAllowWriteConfirm() {
|
||||
return tr::lng_bot_allow_write_confirm(tr::now);
|
||||
}
|
||||
|
||||
QString UiIntegration::phraseQuoteHeaderCopy() {
|
||||
return tr::lng_code_block_header_copy(tr::now);
|
||||
}
|
||||
|
||||
bool OpenGLLastCheckFailed() {
|
||||
return QFile::exists(OpenGLCheckFilePath());
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ public:
|
||||
bool handleUrlClick(
|
||||
const QString &url,
|
||||
const QVariant &context) override;
|
||||
bool copyPreOnClick(const QVariant &context) override;
|
||||
rpl::producer<> forcePopupMenuHideRequests() override;
|
||||
const Ui::Emoji::One *defaultEmojiVariant(
|
||||
const Ui::Emoji::One *emoji) override;
|
||||
@@ -76,6 +77,7 @@ public:
|
||||
QString phraseFormattingItalic() override;
|
||||
QString phraseFormattingUnderline() override;
|
||||
QString phraseFormattingStrikeOut() override;
|
||||
QString phraseFormattingBlockquote() override;
|
||||
QString phraseFormattingMonospace() override;
|
||||
QString phraseFormattingSpoiler() override;
|
||||
QString phraseButtonOk() override;
|
||||
@@ -90,6 +92,7 @@ public:
|
||||
QString phraseBotAllowWrite() override;
|
||||
QString phraseBotAllowWriteTitle() override;
|
||||
QString phraseBotAllowWriteConfirm() override;
|
||||
QString phraseQuoteHeaderCopy() override;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
|
||||
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
|
||||
constexpr auto AppName = "Telegram Desktop"_cs;
|
||||
constexpr auto AppFile = "Telegram"_cs;
|
||||
constexpr auto AppVersion = 4010002;
|
||||
constexpr auto AppVersionStr = "4.10.2";
|
||||
constexpr auto AppVersion = 4011002;
|
||||
constexpr auto AppVersionStr = "4.11.2";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "countries/countries_instance.h"
|
||||
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
#include "base/qt/qt_string_view.h"
|
||||
|
||||
namespace Countries {
|
||||
namespace {
|
||||
@@ -253,7 +254,7 @@ const std::array<Info, 231> FallbackList = { {
|
||||
CountriesInstance::CountriesInstance() {
|
||||
}
|
||||
|
||||
const std::vector<Info> &CountriesInstance::list() {
|
||||
const std::vector<Info> &CountriesInstance::list() const {
|
||||
if (_list.empty()) {
|
||||
_list = (FallbackList | ranges::to_vector);
|
||||
}
|
||||
@@ -267,7 +268,7 @@ void CountriesInstance::setList(std::vector<Info> &&infos) {
|
||||
_updated.fire({});
|
||||
}
|
||||
|
||||
const CountriesInstance::Map &CountriesInstance::byCode() {
|
||||
const CountriesInstance::Map &CountriesInstance::byCode() const {
|
||||
if (_byCode.empty()) {
|
||||
_byCode.reserve(list().size());
|
||||
for (const auto &entry : list()) {
|
||||
@@ -279,7 +280,7 @@ const CountriesInstance::Map &CountriesInstance::byCode() {
|
||||
return _byCode;
|
||||
}
|
||||
|
||||
const CountriesInstance::Map &CountriesInstance::byISO2() {
|
||||
const CountriesInstance::Map &CountriesInstance::byISO2() const {
|
||||
if (_byISO2.empty()) {
|
||||
_byISO2.reserve(list().size());
|
||||
for (const auto &entry : list()) {
|
||||
@@ -289,7 +290,7 @@ const CountriesInstance::Map &CountriesInstance::byISO2() {
|
||||
return _byISO2;
|
||||
}
|
||||
|
||||
QString CountriesInstance::validPhoneCode(QString fullCode) {
|
||||
QString CountriesInstance::validPhoneCode(QString fullCode) const {
|
||||
const auto &listByCode = byCode();
|
||||
while (fullCode.length()) {
|
||||
const auto i = listByCode.constFind(fullCode);
|
||||
@@ -301,20 +302,34 @@ QString CountriesInstance::validPhoneCode(QString fullCode) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString CountriesInstance::countryNameByISO2(const QString &iso) {
|
||||
QString CountriesInstance::countryNameByISO2(const QString &iso) const {
|
||||
const auto &listByISO2 = byISO2();
|
||||
const auto i = listByISO2.constFind(iso);
|
||||
return (i != listByISO2.cend()) ? (*i)->name : QString();
|
||||
}
|
||||
|
||||
QString CountriesInstance::countryISO2ByPhone(const QString &phone) {
|
||||
QString CountriesInstance::countryISO2ByPhone(const QString &phone) const {
|
||||
const auto &listByCode = byCode();
|
||||
const auto code = validPhoneCode(phone);
|
||||
const auto i = listByCode.find(code);
|
||||
return (i != listByCode.cend()) ? (*i)->iso2 : QString();
|
||||
}
|
||||
|
||||
FormatResult CountriesInstance::format(FormatArgs args) {
|
||||
QString CountriesInstance::flagEmojiByISO2(const QString &iso) const {
|
||||
if (iso.size() != 2
|
||||
|| iso.front() < 'A'
|
||||
|| iso.front() > 'Z'
|
||||
|| iso.back() < 'A'
|
||||
|| iso.back() > 'Z') {
|
||||
return QString();
|
||||
}
|
||||
auto result = QString(4, QChar(0xD83C));
|
||||
result[1] = QChar(iso.front().unicode() - 'A' + 0xDDE6);
|
||||
result[3] = QChar(iso.back().unicode() - 'A' + 0xDDE6);
|
||||
return result;
|
||||
}
|
||||
|
||||
FormatResult CountriesInstance::format(FormatArgs args) const {
|
||||
// Ported from TDLib.
|
||||
if (args.phone.isEmpty()) {
|
||||
return FormatResult();
|
||||
|
||||
@@ -43,25 +43,26 @@ public:
|
||||
using Map = QHash<QString, const Info *>;
|
||||
|
||||
CountriesInstance();
|
||||
[[nodiscard]] const std::vector<Info> &list();
|
||||
[[nodiscard]] const std::vector<Info> &list() const;
|
||||
void setList(std::vector<Info> &&infos);
|
||||
|
||||
[[nodiscard]] const Map &byCode();
|
||||
[[nodiscard]] const Map &byISO2();
|
||||
[[nodiscard]] const Map &byCode() const;
|
||||
[[nodiscard]] const Map &byISO2() const;
|
||||
|
||||
[[nodiscard]] QString validPhoneCode(QString fullCode);
|
||||
[[nodiscard]] QString countryNameByISO2(const QString &iso);
|
||||
[[nodiscard]] QString countryISO2ByPhone(const QString &phone);
|
||||
[[nodiscard]] QString validPhoneCode(QString fullCode) const;
|
||||
[[nodiscard]] QString countryNameByISO2(const QString &iso) const;
|
||||
[[nodiscard]] QString countryISO2ByPhone(const QString &phone) const;
|
||||
[[nodiscard]] QString flagEmojiByISO2(const QString &iso) const;
|
||||
|
||||
[[nodiscard]] FormatResult format(FormatArgs args);
|
||||
[[nodiscard]] FormatResult format(FormatArgs args) const;
|
||||
|
||||
[[nodiscard]] rpl::producer<> updated() const;
|
||||
|
||||
private:
|
||||
std::vector<Info> _list;
|
||||
mutable std::vector<Info> _list;
|
||||
|
||||
Map _byCode;
|
||||
Map _byISO2;
|
||||
mutable Map _byCode;
|
||||
mutable Map _byISO2;
|
||||
|
||||
rpl::event_stream<> _updated;
|
||||
|
||||
|
||||
43
Telegram/SourceFiles/data/data_boosts.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Data {
|
||||
|
||||
struct BoostsOverview final {
|
||||
bool isBoosted = false;
|
||||
int level = 0;
|
||||
int boostCount = 0;
|
||||
int currentLevelBoostCount = 0;
|
||||
int nextLevelBoostCount = 0;
|
||||
int premiumMemberCount = 0;
|
||||
float64 premiumMemberPercentage = 0;
|
||||
};
|
||||
|
||||
struct Boost final {
|
||||
UserId userId = UserId(0);
|
||||
QDateTime expirationDate;
|
||||
};
|
||||
|
||||
struct BoostsListSlice final {
|
||||
struct OffsetToken final {
|
||||
QString next;
|
||||
};
|
||||
std::vector<Boost> list;
|
||||
int total = 0;
|
||||
bool allLoaded = false;
|
||||
OffsetToken token;
|
||||
};
|
||||
|
||||
struct BoostStatus final {
|
||||
BoostsOverview overview;
|
||||
BoostsListSlice firstSlice;
|
||||
QString link;
|
||||
};
|
||||
|
||||
} // namespace Data
|
||||