Compare commits
654 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c04f68f25c | ||
|
|
e4902efefc | ||
|
|
1267bcd255 | ||
|
|
0659ccc3f0 | ||
|
|
bd2ae03ab4 | ||
|
|
244696ae24 | ||
|
|
1438046dd4 | ||
|
|
5c62ba0835 | ||
|
|
20fadfef7f | ||
|
|
eed9541f9f | ||
|
|
1594afa389 | ||
|
|
9d74d93ed7 | ||
|
|
e4e2f47f8e | ||
|
|
be53bec9b7 | ||
|
|
bb32c546d4 | ||
|
|
ecb4ceec7b | ||
|
|
c080bd4c4d | ||
|
|
39780f49bf | ||
|
|
73349c3c89 | ||
|
|
42a70ff7d0 | ||
|
|
fa8262cbe9 | ||
|
|
7552328cdd | ||
|
|
60f4587d95 | ||
|
|
572c074c42 | ||
|
|
3cfbd6a93b | ||
|
|
d0911b6a45 | ||
|
|
06b85442f8 | ||
|
|
762592daff | ||
|
|
338122793c | ||
|
|
ef521624a0 | ||
|
|
341ab781b2 | ||
|
|
2fed657940 | ||
|
|
7bf78b3317 | ||
|
|
2d1fb0562d | ||
|
|
3d77bff0c9 | ||
|
|
4198203a7f | ||
|
|
21487641c1 | ||
|
|
c987872be8 | ||
|
|
07e367e1a0 | ||
|
|
db2e45c56e | ||
|
|
67bbdbfc70 | ||
|
|
83df3cba66 | ||
|
|
f4e2b4bcbd | ||
|
|
6c64c22f83 | ||
|
|
702aa944dd | ||
|
|
df45edd816 | ||
|
|
3f3143514e | ||
|
|
3e89910749 | ||
|
|
721a642a2f | ||
|
|
d16ccc9dc5 | ||
|
|
77e7796b3f | ||
|
|
983c949e8c | ||
|
|
e3f4f60e2d | ||
|
|
33aa904cb7 | ||
|
|
0248be5543 | ||
|
|
f7ca8212aa | ||
|
|
5eb59a1a43 | ||
|
|
7dd1e9bfbe | ||
|
|
067fd25a34 | ||
|
|
7cb26ba104 | ||
|
|
a4212cc865 | ||
|
|
0445f7d6e8 | ||
|
|
efe99b3f62 | ||
|
|
7f2c98f17a | ||
|
|
8f1d215851 | ||
|
|
013b58f6f6 | ||
|
|
9d3b3476c2 | ||
|
|
715874a98f | ||
|
|
d2109dd2cb | ||
|
|
ddaf11ed6a | ||
|
|
2b5f68003d | ||
|
|
1a759cc4e7 | ||
|
|
9e83562bf4 | ||
|
|
c03c19d26f | ||
|
|
ad9ebdf8e6 | ||
|
|
9c1701c62a | ||
|
|
edf6c42e9d | ||
|
|
f0f2a71a87 | ||
|
|
cf270bd9ce | ||
|
|
49223a4688 | ||
|
|
074bb1e66e | ||
|
|
7e2e510d8a | ||
|
|
1ed34c6fa0 | ||
|
|
78a0fa55b5 | ||
|
|
d37c040b36 | ||
|
|
e56bbf557d | ||
|
|
5abecec478 | ||
|
|
ccb41f778e | ||
|
|
059a4cf0d8 | ||
|
|
7a535a4554 | ||
|
|
f89167ef94 | ||
|
|
a77777f509 | ||
|
|
4a327ba584 | ||
|
|
a41e9bf67e | ||
|
|
6716973ce0 | ||
|
|
7cc81393d6 | ||
|
|
3e413a036f | ||
|
|
63a8fe7ee8 | ||
|
|
146409844d | ||
|
|
ba0da9f59e | ||
|
|
81aef519d4 | ||
|
|
bcd84518d1 | ||
|
|
f205952ff2 | ||
|
|
1d7622e0b5 | ||
|
|
4d9112283d | ||
|
|
dc49c788a8 | ||
|
|
36741ab780 | ||
|
|
53dffc5e88 | ||
|
|
607c7e7777 | ||
|
|
6f09e1699f | ||
|
|
8c35de48f3 | ||
|
|
b83d943841 | ||
|
|
b11b5caeb3 | ||
|
|
36924da59a | ||
|
|
f0a2c47613 | ||
|
|
5a4449f1a2 | ||
|
|
de3d7a7774 | ||
|
|
b06dbd1c00 | ||
|
|
1fa5e424e9 | ||
|
|
d81c7abf1a | ||
|
|
932215c91d | ||
|
|
7aa1141ba5 | ||
|
|
3699439506 | ||
|
|
76b1288f77 | ||
|
|
8fd9ae4e59 | ||
|
|
53abd2fe38 | ||
|
|
da8a4ba8ab | ||
|
|
9c3990c0c1 | ||
|
|
1eeb46d5fc | ||
|
|
02c01e258c | ||
|
|
b2f912868d | ||
|
|
ecf0eba0a5 | ||
|
|
8e5fec2fa8 | ||
|
|
89c35e8512 | ||
|
|
3fa88fad79 | ||
|
|
5f037462ed | ||
|
|
93c01e5f1e | ||
|
|
52953626a7 | ||
|
|
f77fdc799d | ||
|
|
7bf1f9bd71 | ||
|
|
363ffc1c04 | ||
|
|
4001899bdf | ||
|
|
fabdd89c4a | ||
|
|
70f1675085 | ||
|
|
07e1e2d9d6 | ||
|
|
ebff6c6370 | ||
|
|
233eb6d916 | ||
|
|
d568cab2fc | ||
|
|
8447b95c50 | ||
|
|
e959d1e1b0 | ||
|
|
ead5dbe368 | ||
|
|
f091f2b344 | ||
|
|
267a51e800 | ||
|
|
939f6095ba | ||
|
|
a333615e53 | ||
|
|
e1b33fbc40 | ||
|
|
99c5d994b5 | ||
|
|
0971485367 | ||
|
|
3cfa963f69 | ||
|
|
7096a4231f | ||
|
|
07f0c182e6 | ||
|
|
535b223333 | ||
|
|
ffa8c2be79 | ||
|
|
2873e64ca2 | ||
|
|
8bd28bce69 | ||
|
|
b97ce43fca | ||
|
|
d34c4cc2f2 | ||
|
|
1ca1f0fa7d | ||
|
|
8c3c8f888d | ||
|
|
a75d7f0381 | ||
|
|
7684466acf | ||
|
|
0067245739 | ||
|
|
4a5d8aa217 | ||
|
|
2d786aa02c | ||
|
|
b0933b96ef | ||
|
|
20fb73b626 | ||
|
|
ae7bd7112b | ||
|
|
2365363dcc | ||
|
|
e0e4a7bec6 | ||
|
|
ebd0c3696a | ||
|
|
e0a0d9c039 | ||
|
|
dae9f2ab2b | ||
|
|
7168a00ee4 | ||
|
|
1e2d0ced20 | ||
|
|
d6ac883efa | ||
|
|
a386d70ae4 | ||
|
|
bc8bf672b4 | ||
|
|
e38998214f | ||
|
|
472a2fe802 | ||
|
|
2d6d89b1cf | ||
|
|
66be2ac6ca | ||
|
|
3137c9f3f7 | ||
|
|
b9ebb02e72 | ||
|
|
1e02c475d6 | ||
|
|
4d2cda0692 | ||
|
|
cbd2b8f428 | ||
|
|
93605db690 | ||
|
|
2567096de0 | ||
|
|
6ed25d012f | ||
|
|
c2afef2bde | ||
|
|
0991e7d8a4 | ||
|
|
cf52f2a743 | ||
|
|
37dddda1a0 | ||
|
|
3f2f3ebd51 | ||
|
|
7ba78540ac | ||
|
|
19afb49fce | ||
|
|
46ab553fa5 | ||
|
|
68cc42047e | ||
|
|
e25cf27ba5 | ||
|
|
3895e6d958 | ||
|
|
24cf3984c8 | ||
|
|
6237675744 | ||
|
|
30dae049ff | ||
|
|
1dc30caee9 | ||
|
|
b2d340cbfb | ||
|
|
7d52787e54 | ||
|
|
ae3f16ccbd | ||
|
|
057222757b | ||
|
|
119f109904 | ||
|
|
23a77b1ba4 | ||
|
|
c076daa91f | ||
|
|
b7ef5325ac | ||
|
|
8b535c58fa | ||
|
|
6bc8daaeda | ||
|
|
7a2562e5bb | ||
|
|
9e0c731b32 | ||
|
|
2e0e4006a1 | ||
|
|
187139473d | ||
|
|
dbb0a5ad28 | ||
|
|
24aaed44b9 | ||
|
|
32b8d83c04 | ||
|
|
bf55c325ce | ||
|
|
3af288c74e | ||
|
|
e306d9ba35 | ||
|
|
b23a877d7e | ||
|
|
08fda055fc | ||
|
|
84055ed74e | ||
|
|
2db30690ce | ||
|
|
304bcfd343 | ||
|
|
8a1cf2bb3a | ||
|
|
c857c24a64 | ||
|
|
bbdcb047d0 | ||
|
|
78f2e70956 | ||
|
|
75a75626ce | ||
|
|
cec9688d58 | ||
|
|
81492b7d3a | ||
|
|
9166acbbb9 | ||
|
|
36de2b6ca6 | ||
|
|
21f909dd4b | ||
|
|
f2a92c9122 | ||
|
|
7ee2e3d8bc | ||
|
|
f89aeb6ad4 | ||
|
|
0397006894 | ||
|
|
d6863074b2 | ||
|
|
9c185a30e0 | ||
|
|
a8f492a027 | ||
|
|
0a92b1dc68 | ||
|
|
e6d661f8ee | ||
|
|
f48dfb5d81 | ||
|
|
cd041e8366 | ||
|
|
6787ea883e | ||
|
|
78937d716f | ||
|
|
9713abc002 | ||
|
|
b44b45cca0 | ||
|
|
9e2cf0ed73 | ||
|
|
b01d7ea5b9 | ||
|
|
ae89b65a98 | ||
|
|
9b9c3d788d | ||
|
|
ccc6c6daa5 | ||
|
|
9ce6636c6a | ||
|
|
6287d306c2 | ||
|
|
6cfa053328 | ||
|
|
9514b6eecd | ||
|
|
c8d4818d22 | ||
|
|
4142ada729 | ||
|
|
d7ffdbd78d | ||
|
|
e8d87d37bb | ||
|
|
343ffc23eb | ||
|
|
95e0086eed | ||
|
|
c010ecfe38 | ||
|
|
302e9371c8 | ||
|
|
7060c0e6d7 | ||
|
|
20a4c7f9f4 | ||
|
|
e59e4afd3e | ||
|
|
f74dd3ca1e | ||
|
|
511cfc524f | ||
|
|
4cf6173d25 | ||
|
|
17996757fd | ||
|
|
6bc1049858 | ||
|
|
ff44f626ba | ||
|
|
552343fa37 | ||
|
|
4dc7fd8cd1 | ||
|
|
285c96fd2e | ||
|
|
e6af33367e | ||
|
|
7092fe2242 | ||
|
|
a32ff46579 | ||
|
|
7f20cf59d1 | ||
|
|
a8a1b08127 | ||
|
|
1a1e777b87 | ||
|
|
9e76e64064 | ||
|
|
975ae17ef9 | ||
|
|
ed9dcef66f | ||
|
|
b1e537e54e | ||
|
|
e5886862c3 | ||
|
|
d85b668d4f | ||
|
|
b363d8bfb5 | ||
|
|
754d467440 | ||
|
|
598f08d6c7 | ||
|
|
224fdc1864 | ||
|
|
e646b4dc9a | ||
|
|
9077db2e97 | ||
|
|
7e14277ead | ||
|
|
d351a7d697 | ||
|
|
70ed43b811 | ||
|
|
913083ebc6 | ||
|
|
588a95a7ae | ||
|
|
c0a0ad4ec5 | ||
|
|
5eafe96525 | ||
|
|
b41ac0fc2a | ||
|
|
cfe93530b8 | ||
|
|
4492e72ffa | ||
|
|
1a0fd35f36 | ||
|
|
db475ef0b4 | ||
|
|
b3dddc1dfe | ||
|
|
b64c610abb | ||
|
|
c5add2fca9 | ||
|
|
9ea78f7d28 | ||
|
|
54b0d965ae | ||
|
|
7b4bd5696b | ||
|
|
9064f3ba4b | ||
|
|
e00e562b5f | ||
|
|
73f38c896f | ||
|
|
5767cbd0e3 | ||
|
|
ba31756bf9 | ||
|
|
a88f48cd93 | ||
|
|
f2e0e481de | ||
|
|
566f279137 | ||
|
|
394ef13955 | ||
|
|
6812e17d07 | ||
|
|
fdf826b686 | ||
|
|
e57742e7de | ||
|
|
73b63aa414 | ||
|
|
44aa2aec5d | ||
|
|
8d9d7c4cea | ||
|
|
9557f0c844 | ||
|
|
a32a9aa3fc | ||
|
|
86fa98dfbb | ||
|
|
6a69447d90 | ||
|
|
2d20e7a9e2 | ||
|
|
ac7b2e0da0 | ||
|
|
388325a496 | ||
|
|
af728e82fc | ||
|
|
3cb33f0825 | ||
|
|
1038baf467 | ||
|
|
828ecabc78 | ||
|
|
88703ba1eb | ||
|
|
b0ecb2c535 | ||
|
|
c70482dbc4 | ||
|
|
51f1999412 | ||
|
|
dd4fbc256c | ||
|
|
cc2265583f | ||
|
|
1e7a4db57f | ||
|
|
9a9e30c88b | ||
|
|
3d98ebff42 | ||
|
|
a6f4b1ae8e | ||
|
|
3e6ea8109c | ||
|
|
ec407d57a5 | ||
|
|
838ad66166 | ||
|
|
c6bf905253 | ||
|
|
8310230582 | ||
|
|
1f21af0bdb | ||
|
|
127e4a5086 | ||
|
|
6a30967f23 | ||
|
|
df2b020b42 | ||
|
|
a74c5a89a6 | ||
|
|
3cb0be5be5 | ||
|
|
d56f3cfecf | ||
|
|
9716a901d1 | ||
|
|
649d242e9a | ||
|
|
8ac3c2157f | ||
|
|
7ca4ca21fa | ||
|
|
e565acba91 | ||
|
|
ede771e51b | ||
|
|
0282786b4c | ||
|
|
42dd08ace5 | ||
|
|
cf1d274b0d | ||
|
|
d361d5f3b2 | ||
|
|
3e63b40564 | ||
|
|
c5139ed06a | ||
|
|
3dc93526ff | ||
|
|
3edf8e10e2 | ||
|
|
00215622cc | ||
|
|
479b7c3140 | ||
|
|
a3ca8ddcfc | ||
|
|
9ace04d2c9 | ||
|
|
8b11d2d5e7 | ||
|
|
05fb0f81f9 | ||
|
|
da49efa1ed | ||
|
|
39fb0a5b66 | ||
|
|
962d4d29ee | ||
|
|
779e9b658b | ||
|
|
f9ee4dcb51 | ||
|
|
1acdbb69ae | ||
|
|
c022a1c838 | ||
|
|
52afd3d5a8 | ||
|
|
79b1c0edee | ||
|
|
4803bd4b3f | ||
|
|
215a262076 | ||
|
|
7a35577d3a | ||
|
|
efbd3ca8fa | ||
|
|
18904412cd | ||
|
|
0d0d0ab994 | ||
|
|
71deef61f5 | ||
|
|
46c8a55f56 | ||
|
|
37f837dcb7 | ||
|
|
a5be0a685a | ||
|
|
4cdd1fec95 | ||
|
|
761617c1ce | ||
|
|
e3bc4dab85 | ||
|
|
335095a332 | ||
|
|
15fcb73e19 | ||
|
|
a8b0f2934b | ||
|
|
7d67b3d00a | ||
|
|
b60c7e97ab | ||
|
|
440ebfcbf6 | ||
|
|
8d93eb919b | ||
|
|
d3df2dc1e5 | ||
|
|
677314dacb | ||
|
|
20f53c89ad | ||
|
|
1cb92ef69a | ||
|
|
b518f7c4c4 | ||
|
|
9fdc099c3a | ||
|
|
1db8ada2aa | ||
|
|
b753448052 | ||
|
|
e29c6d2f23 | ||
|
|
1ee53ee0c1 | ||
|
|
57438867b6 | ||
|
|
86c04424f6 | ||
|
|
632639d581 | ||
|
|
99c8edb3eb | ||
|
|
4582e61cfc | ||
|
|
a970fe93c1 | ||
|
|
91f5c72cf0 | ||
|
|
d2f3d04ad4 | ||
|
|
07b93e1e02 | ||
|
|
ab5e7b1588 | ||
|
|
0f4aadddfc | ||
|
|
a3c79feeba | ||
|
|
e6b76fefa1 | ||
|
|
a37f512949 | ||
|
|
3f34c0ce37 | ||
|
|
7cc9a0b9aa | ||
|
|
fbaa79f168 | ||
|
|
2b0aa8e418 | ||
|
|
1fa2e76c9b | ||
|
|
cb166e2591 | ||
|
|
187c2dda20 | ||
|
|
b962d2b550 | ||
|
|
d69bcfa138 | ||
|
|
0960a3b21d | ||
|
|
aec220fd2c | ||
|
|
666dacf73b | ||
|
|
67ce27afaa | ||
|
|
3f6d184435 | ||
|
|
a2fad84dae | ||
|
|
4a84f9fa00 | ||
|
|
7abc921d20 | ||
|
|
a307e7a798 | ||
|
|
3a9eb8463d | ||
|
|
4befb125e3 | ||
|
|
ca00a19736 | ||
|
|
296969bcd1 | ||
|
|
3218c30983 | ||
|
|
3658b32db5 | ||
|
|
592d46c8f2 | ||
|
|
8b86f12c23 | ||
|
|
2e2d8d2af3 | ||
|
|
3eec43cacd | ||
|
|
ba7cd25f21 | ||
|
|
969152e949 | ||
|
|
b78443cf2d | ||
|
|
4288ba2449 | ||
|
|
22a3093815 | ||
|
|
98ba2c7ce4 | ||
|
|
36962b8c62 | ||
|
|
d0a8bd1f03 | ||
|
|
c9e0f50f0f | ||
|
|
1dc310586d | ||
|
|
6f71d21bb7 | ||
|
|
c93f047056 | ||
|
|
ef98d4ece7 | ||
|
|
2383bf2c71 | ||
|
|
2c8c92c2a4 | ||
|
|
bad2dc30c3 | ||
|
|
fe7f4233b9 | ||
|
|
68454a9841 | ||
|
|
d3bcf63cf7 | ||
|
|
a5f1209f28 | ||
|
|
9a44ca2769 | ||
|
|
155305f0f7 | ||
|
|
6abce8d976 | ||
|
|
f16d1f034f | ||
|
|
dab107cf90 | ||
|
|
d1d1aa3d21 | ||
|
|
d0536cc31f | ||
|
|
c47f5e9995 | ||
|
|
0916836ff9 | ||
|
|
5dd9ff1062 | ||
|
|
d1e3b9f15d | ||
|
|
ca3c179b75 | ||
|
|
c2d5924508 | ||
|
|
016d0395c3 | ||
|
|
925c9239bd | ||
|
|
fdcbe3cf3a | ||
|
|
f6b9cc5ce1 | ||
|
|
8c915e6dc3 | ||
|
|
1db426da2e | ||
|
|
d9be363962 | ||
|
|
88daa37e34 | ||
|
|
931fa01337 | ||
|
|
8e1595eb29 | ||
|
|
29e97232d8 | ||
|
|
f05191e668 | ||
|
|
1ba189e59d | ||
|
|
c40ca70aa6 | ||
|
|
521f991167 | ||
|
|
edf1417bbb | ||
|
|
686e9643ad | ||
|
|
975460d268 | ||
|
|
6b0c606d25 | ||
|
|
93ff0bdcff | ||
|
|
bf9d90ca4e | ||
|
|
d6e5e1e8f7 | ||
|
|
d3ae2ef9ea | ||
|
|
40b0854704 | ||
|
|
9aec6b6496 | ||
|
|
d01f977960 | ||
|
|
1444009ee2 | ||
|
|
14ee9bee26 | ||
|
|
93587ddc3c | ||
|
|
0c4d03477e | ||
|
|
a35092f012 | ||
|
|
49ee7ee52b | ||
|
|
52c77a1970 | ||
|
|
e4269ae7fb | ||
|
|
bd6011c524 | ||
|
|
46cb3ec103 | ||
|
|
0241129948 | ||
|
|
e44aca06cb | ||
|
|
19d386f977 | ||
|
|
a67fdda913 | ||
|
|
5286c7b1c3 | ||
|
|
d9d96d0a6f | ||
|
|
13353bb615 | ||
|
|
d78348fd16 | ||
|
|
1e8e660133 | ||
|
|
5196982c98 | ||
|
|
99f857fbf6 | ||
|
|
0982aa166a | ||
|
|
3ae9f86097 | ||
|
|
9efd9b0d68 | ||
|
|
267a73f355 | ||
|
|
437d8ea890 | ||
|
|
7ed92ec402 | ||
|
|
678d9ffbf9 | ||
|
|
bc4b427ed1 | ||
|
|
36141a9df9 | ||
|
|
d68ba75457 | ||
|
|
00ad32c5f4 | ||
|
|
064bab60ff | ||
|
|
5b146217c0 | ||
|
|
202c81b2e5 | ||
|
|
4520480604 | ||
|
|
aea87bb5cb | ||
|
|
19492f7e7b | ||
|
|
51030e3c45 | ||
|
|
e6b8b4be18 | ||
|
|
6ac13b7f80 | ||
|
|
7e5e6003a9 | ||
|
|
f445440995 | ||
|
|
d81547f091 | ||
|
|
2b185d491b | ||
|
|
ca47440950 | ||
|
|
5e32602f4a | ||
|
|
1f4516028c | ||
|
|
7f29d269a3 | ||
|
|
62c3374911 | ||
|
|
2116e04af5 | ||
|
|
a97d5e80c7 | ||
|
|
b5098038d0 | ||
|
|
6fce718252 | ||
|
|
40cf96202d | ||
|
|
c18e59218e | ||
|
|
216865a20d | ||
|
|
6c5036ee8d | ||
|
|
dc2f59ca24 | ||
|
|
6f6457137e | ||
|
|
bc5cb6e2a2 | ||
|
|
16825fff41 | ||
|
|
5024f1db8c | ||
|
|
a60385fc3d | ||
|
|
b20e2c37c1 | ||
|
|
acd40cbeb6 | ||
|
|
6a45a862dd | ||
|
|
5bd45e9a20 | ||
|
|
52e42f23ab | ||
|
|
96b7755cde | ||
|
|
c589ee1ca5 | ||
|
|
18c9ee093b | ||
|
|
b82fa3112c | ||
|
|
8d0f66d562 | ||
|
|
20a5e0ba73 | ||
|
|
5a2667c71e | ||
|
|
1f4a8d7eb6 | ||
|
|
4430bd0328 | ||
|
|
ab2e7f4c03 | ||
|
|
54e5c06b4d | ||
|
|
add5a6a0be | ||
|
|
89c2ba4293 | ||
|
|
dd100fb709 | ||
|
|
e8dd2b9e7b | ||
|
|
71e3cd227c | ||
|
|
1648c31a22 | ||
|
|
f8c820f319 | ||
|
|
300f35e78f | ||
|
|
2aa5849997 | ||
|
|
fb1b845211 | ||
|
|
3d2af9db8e | ||
|
|
6129e5a1cf | ||
|
|
7f70ee1227 | ||
|
|
c9f7da6e82 | ||
|
|
f956c0f227 | ||
|
|
df935e0477 | ||
|
|
841f1afe1e | ||
|
|
fb8b88557e | ||
|
|
2b502b22b9 | ||
|
|
5ac80d2655 | ||
|
|
8aa7499e63 | ||
|
|
8cd5e51982 | ||
|
|
e5c2133446 | ||
|
|
3dccdf2f05 | ||
|
|
8a708c6655 | ||
|
|
9e1d9eee4b | ||
|
|
f30aabc365 | ||
|
|
a5546d016f | ||
|
|
f79d70d112 | ||
|
|
ec28f258fb | ||
|
|
08f3a6fb40 | ||
|
|
8823d5256f | ||
|
|
60e7aa90d2 | ||
|
|
71357a9546 | ||
|
|
a5b06e9c56 | ||
|
|
0f94419f6d | ||
|
|
94e7aabea5 |
17
.github/workflows/mac_packaged.yml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
|
||||
macos:
|
||||
name: MacOS
|
||||
runs-on: macos-13
|
||||
runs-on: macos-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
|
||||
env:
|
||||
GIT: "https://github.com"
|
||||
CMAKE_PREFIX_PATH: "/usr/local/opt/ffmpeg@6:/usr/local/opt/openal-soft"
|
||||
CMAKE_PREFIX_PATH: "/opt/homebrew/opt/ffmpeg@6:/opt/homebrew/opt/openal-soft"
|
||||
UPLOAD_ARTIFACT: "true"
|
||||
ONLY_CACHE: "false"
|
||||
MANUAL_CACHING: "1"
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
run: |
|
||||
brew update
|
||||
brew upgrade || true
|
||||
brew install ada-url autoconf automake boost cmake ffmpeg@6 openal-soft openh264 openssl opus ninja pkg-config python qt yasm xz
|
||||
brew install ada-url autoconf automake boost cmake ffmpeg@6 libtool openal-soft openh264 openssl opus ninja pkg-config python qt yasm xz
|
||||
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
|
||||
|
||||
xcodebuild -version > CACHE_KEY.txt
|
||||
@@ -95,7 +95,7 @@ jobs:
|
||||
./autogen.sh
|
||||
./configure --disable-examples --disable-doc
|
||||
make -j$(sysctl -n hw.logicalcpu)
|
||||
make install
|
||||
sudo make install
|
||||
|
||||
- name: WebRTC cache.
|
||||
id: cache-webrtc
|
||||
@@ -111,7 +111,11 @@ jobs:
|
||||
git clone --depth=1 --recursive --shallow-submodules $GIT/desktop-app/tg_owt.git
|
||||
cd tg_owt
|
||||
|
||||
cmake -B build . -GNinja -DCMAKE_BUILD_TYPE=Debug
|
||||
cmake -Bbuild -GNinja . \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DCMAKE_C_FLAGS_DEBUG="" \
|
||||
-DCMAKE_CXX_FLAGS_DEBUG=""
|
||||
|
||||
cmake --build build --parallel
|
||||
|
||||
- name: Telegram Desktop build.
|
||||
@@ -132,6 +136,9 @@ jobs:
|
||||
|
||||
cmake -Bbuild -GNinja . \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DCMAKE_C_FLAGS_DEBUG="" \
|
||||
-DCMAKE_CXX_FLAGS_DEBUG="" \
|
||||
-DCMAKE_EXE_LINKER_FLAGS="-s" \
|
||||
-DCMAKE_FIND_FRAMEWORK=LAST \
|
||||
-DTDESKTOP_API_TEST=ON \
|
||||
-DDESKTOP_APP_USE_PACKAGED_LAZY=ON \
|
||||
|
||||
2
.github/workflows/snap.yml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
|
||||
snap:
|
||||
name: Ubuntu
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
UPLOAD_ARTIFACT: "true"
|
||||
|
||||
10
.github/workflows/win.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
|
||||
windows:
|
||||
name: Windows
|
||||
runs-on: windows-2022
|
||||
runs-on: windows-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -94,6 +94,14 @@ jobs:
|
||||
nuget sources Disable -Name "Microsoft Visual Studio Offline Packages"
|
||||
nuget sources Add -Source https://api.nuget.org/v3/index.json & exit 0
|
||||
|
||||
- name: ThirdParty cache.
|
||||
id: cache-third-party
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.TBUILD }}\ThirdParty
|
||||
key: ${{ runner.OS }}-${{ matrix.arch }}-third-party-${{ env.CACHE_KEY }}
|
||||
restore-keys: ${{ runner.OS }}-${{ matrix.arch }}-third-party-
|
||||
|
||||
- name: Libraries cache.
|
||||
id: cache-libs
|
||||
uses: actions/cache@v4
|
||||
|
||||
2
.gitmodules
vendored
@@ -3,7 +3,7 @@
|
||||
url = https://github.com/telegramdesktop/libtgvoip
|
||||
[submodule "Telegram/ThirdParty/GSL"]
|
||||
path = Telegram/ThirdParty/GSL
|
||||
url = https://github.com/desktop-app/GSL.git
|
||||
url = https://github.com/Microsoft/GSL.git
|
||||
[submodule "Telegram/ThirdParty/xxHash"]
|
||||
path = Telegram/ThirdParty/xxHash
|
||||
url = https://github.com/Cyan4973/xxHash.git
|
||||
|
||||
@@ -64,6 +64,7 @@ Version **1.8.15** was the last that supports older systems
|
||||
* QR Code generator ([MIT License](https://github.com/nayuki/QR-Code-generator#license))
|
||||
* CMake ([New BSD License](https://github.com/Kitware/CMake/blob/master/Copyright.txt))
|
||||
* Hunspell ([LGPL](https://github.com/hunspell/hunspell/blob/master/COPYING.LESSER))
|
||||
* Ada ([Apache License 2.0](https://github.com/ada-url/ada/blob/main/LICENSE-APACHE))
|
||||
|
||||
## Build instructions
|
||||
|
||||
|
||||
@@ -112,6 +112,8 @@ PRIVATE
|
||||
api/api_bot.h
|
||||
api/api_chat_filters.cpp
|
||||
api/api_chat_filters.h
|
||||
api/api_chat_filters_remove_manager.cpp
|
||||
api/api_chat_filters_remove_manager.h
|
||||
api/api_chat_invite.cpp
|
||||
api/api_chat_invite.h
|
||||
api/api_chat_links.cpp
|
||||
@@ -322,6 +324,8 @@ PRIVATE
|
||||
boxes/sessions_box.h
|
||||
boxes/share_box.cpp
|
||||
boxes/share_box.h
|
||||
boxes/star_gift_box.cpp
|
||||
boxes/star_gift_box.h
|
||||
boxes/sticker_set_box.cpp
|
||||
boxes/sticker_set_box.h
|
||||
boxes/stickers_box.cpp
|
||||
@@ -569,6 +573,8 @@ PRIVATE
|
||||
data/data_lastseen_status.h
|
||||
data/data_location.cpp
|
||||
data/data_location.h
|
||||
data/data_media_preload.cpp
|
||||
data/data_media_preload.h
|
||||
data/data_media_rotation.cpp
|
||||
data/data_media_rotation.h
|
||||
data/data_media_types.cpp
|
||||
@@ -604,6 +610,7 @@ PRIVATE
|
||||
data/data_replies_list.h
|
||||
data/data_reply_preview.cpp
|
||||
data/data_reply_preview.h
|
||||
data/data_report.h
|
||||
data/data_saved_messages.cpp
|
||||
data/data_saved_messages.h
|
||||
data/data_saved_sublist.cpp
|
||||
@@ -631,6 +638,8 @@ PRIVATE
|
||||
data/data_thread.h
|
||||
data/data_types.cpp
|
||||
data/data_types.h
|
||||
data/data_unread_value.cpp
|
||||
data/data_unread_value.h
|
||||
data/data_user.cpp
|
||||
data/data_user.h
|
||||
data/data_user_photos.cpp
|
||||
@@ -947,6 +956,10 @@ PRIVATE
|
||||
info/media/info_media_widget.h
|
||||
info/members/info_members_widget.cpp
|
||||
info/members/info_members_widget.h
|
||||
info/peer_gifts/info_peer_gifts_common.cpp
|
||||
info/peer_gifts/info_peer_gifts_common.h
|
||||
info/peer_gifts/info_peer_gifts_widget.cpp
|
||||
info/peer_gifts/info_peer_gifts_widget.h
|
||||
info/polls/info_polls_results_inner_widget.cpp
|
||||
info/polls/info_polls_results_inner_widget.h
|
||||
info/polls/info_polls_results_widget.cpp
|
||||
@@ -973,6 +986,10 @@ PRIVATE
|
||||
info/profile/info_profile_values.h
|
||||
info/profile/info_profile_widget.cpp
|
||||
info/profile/info_profile_widget.h
|
||||
info/reactions_list/info_reactions_list_widget.cpp
|
||||
info/reactions_list/info_reactions_list_widget.h
|
||||
info/requests_list/info_requests_list_widget.cpp
|
||||
info/requests_list/info_requests_list_widget.h
|
||||
info/saved/info_saved_sublists_widget.cpp
|
||||
info/saved/info_saved_sublists_widget.h
|
||||
info/settings/info_settings_widget.cpp
|
||||
@@ -986,6 +1003,7 @@ PRIVATE
|
||||
info/statistics/info_statistics_list_controllers.h
|
||||
info/statistics/info_statistics_recent_message.cpp
|
||||
info/statistics/info_statistics_recent_message.h
|
||||
info/statistics/info_statistics_tag.h
|
||||
info/statistics/info_statistics_widget.cpp
|
||||
info/statistics/info_statistics_widget.h
|
||||
info/stories/info_stories_inner_widget.cpp
|
||||
@@ -1022,6 +1040,10 @@ PRIVATE
|
||||
info/info_wrap_widget.h
|
||||
inline_bots/bot_attach_web_view.cpp
|
||||
inline_bots/bot_attach_web_view.h
|
||||
inline_bots/inline_bot_confirm_prepared.cpp
|
||||
inline_bots/inline_bot_confirm_prepared.h
|
||||
inline_bots/inline_bot_downloads.cpp
|
||||
inline_bots/inline_bot_downloads.h
|
||||
inline_bots/inline_bot_layout_internal.cpp
|
||||
inline_bots/inline_bot_layout_internal.h
|
||||
inline_bots/inline_bot_layout_item.cpp
|
||||
@@ -1153,6 +1175,8 @@ PRIVATE
|
||||
media/streaming/media_streaming_player.h
|
||||
media/streaming/media_streaming_reader.cpp
|
||||
media/streaming/media_streaming_reader.h
|
||||
media/streaming/media_streaming_round_preview.cpp
|
||||
media/streaming/media_streaming_round_preview.h
|
||||
media/streaming/media_streaming_utility.cpp
|
||||
media/streaming/media_streaming_utility.h
|
||||
media/streaming/media_streaming_video_track.cpp
|
||||
@@ -1482,6 +1506,8 @@ PRIVATE
|
||||
support/support_templates.h
|
||||
ui/boxes/edit_invite_link_session.cpp
|
||||
ui/boxes/edit_invite_link_session.h
|
||||
ui/boxes/peer_qr_box.cpp
|
||||
ui/boxes/peer_qr_box.h
|
||||
ui/chat/attach/attach_item_single_file_preview.cpp
|
||||
ui/chat/attach/attach_item_single_file_preview.h
|
||||
ui/chat/attach/attach_item_single_media_preview.cpp
|
||||
@@ -1490,6 +1516,8 @@ PRIVATE
|
||||
ui/chat/choose_send_as.h
|
||||
ui/chat/choose_theme_controller.cpp
|
||||
ui/chat/choose_theme_controller.h
|
||||
ui/chat/sponsored_message_bar.cpp
|
||||
ui/chat/sponsored_message_bar.h
|
||||
ui/controls/emoji_button_factory.cpp
|
||||
ui/controls/emoji_button_factory.h
|
||||
ui/controls/location_picker.cpp
|
||||
@@ -1521,6 +1549,8 @@ PRIVATE
|
||||
ui/widgets/expandable_peer_list.h
|
||||
ui/widgets/label_with_custom_emoji.cpp
|
||||
ui/widgets/label_with_custom_emoji.h
|
||||
ui/widgets/chat_filters_tabs_strip.cpp
|
||||
ui/widgets/chat_filters_tabs_strip.h
|
||||
ui/countryinput.cpp
|
||||
ui/countryinput.h
|
||||
ui/dynamic_thumbnails.cpp
|
||||
@@ -1838,7 +1868,7 @@ endif()
|
||||
|
||||
set_target_properties(Telegram PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_folder})
|
||||
|
||||
if (WIN32 AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
|
||||
if (MSVC)
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
delayimp
|
||||
@@ -1935,7 +1965,7 @@ if (NOT DESKTOP_APP_DISABLE_AUTOUPDATE AND NOT build_macstore AND NOT build_wins
|
||||
base/platform/win/base_windows_safe_library.h
|
||||
)
|
||||
target_include_directories(Updater PRIVATE ${lib_base_loc})
|
||||
if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
|
||||
if (MSVC)
|
||||
target_link_libraries(Updater
|
||||
PRIVATE
|
||||
delayimp
|
||||
|
||||
BIN
Telegram/Resources/animations/hello_status.tgs
Normal file
BIN
Telegram/Resources/art/round_placeholder.jpg
Normal file
|
After Width: | Height: | Size: 787 B |
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 832 KiB After Width: | Height: | Size: 935 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 234 KiB |
@@ -582,3 +582,55 @@ div.toast_shown {
|
||||
.bot_button_column_separator {
|
||||
width: 2px
|
||||
}
|
||||
|
||||
.reactions {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.reactions .reaction {
|
||||
display: inline-flex;
|
||||
height: 20px;
|
||||
border-radius: 15px;
|
||||
background-color: #e8f5fc;
|
||||
color: #168acd;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.reactions .reaction.active {
|
||||
background-color: #40a6e2;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.reactions .reaction.paid {
|
||||
background-color: #fdf6e1;
|
||||
color: #c58523;
|
||||
}
|
||||
|
||||
.reactions .reaction.active.paid {
|
||||
background-color: #ecae0a;
|
||||
color: #fdf6e1;
|
||||
}
|
||||
|
||||
.reactions .reaction .emoji {
|
||||
line-height: 20px;
|
||||
margin: 0 5px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.reactions .reaction .userpic:not(:first-child) {
|
||||
margin-left: -8px;
|
||||
}
|
||||
|
||||
.reactions .reaction .userpic {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.reactions .reaction .userpic .initials {
|
||||
font-size: 8px;
|
||||
}
|
||||
|
||||
.reactions .reaction .count {
|
||||
margin-right: 8px;
|
||||
line-height: 20px;
|
||||
}
|
||||
BIN
Telegram/Resources/icons/chat/input_video.png
Normal file
|
After Width: | Height: | Size: 621 B |
BIN
Telegram/Resources/icons/chat/input_video@2x.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Telegram/Resources/icons/chat/input_video@3x.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
Telegram/Resources/icons/inline_button_copy.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
Telegram/Resources/icons/inline_button_copy@2x.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
Telegram/Resources/icons/inline_button_copy@3x.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
Telegram/Resources/icons/menu/edited_status.png
Normal file
|
After Width: | Height: | Size: 586 B |
BIN
Telegram/Resources/icons/menu/edited_status@2x.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
Telegram/Resources/icons/menu/edited_status@3x.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
1
Telegram/Resources/icons/plane_white.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="1000px" height="1000px" viewBox="0 0 1000 1000" version="1.1" xmlns="http://www.w3.org/2000/svg"><g id="Artboard" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><path d="M226.328419,494.722069 C372.088573,431.216685 469.284839,389.350049 517.917216,369.122161 C656.772535,311.36743 685.625481,301.334815 704.431427,301.003532 C708.567621,300.93067 717.815839,301.955743 723.806446,306.816707 C728.864797,310.92121 730.256552,316.46581 730.922551,320.357329 C731.588551,324.248848 732.417879,333.113828 731.758626,340.040666 C724.234007,419.102486 691.675104,610.964674 675.110982,699.515267 C668.10208,736.984342 654.301336,749.547532 640.940618,750.777006 C611.904684,753.448938 589.856115,731.588035 561.733393,713.153237 C517.726886,684.306416 492.866009,666.349181 450.150074,638.200013 C400.78442,605.66878 432.786119,587.789048 460.919462,558.568563 C468.282091,550.921423 596.21508,434.556479 598.691227,424.000355 C599.00091,422.680135 599.288312,417.758981 596.36474,415.160431 C593.441168,412.561881 589.126229,413.450484 586.012448,414.157198 C581.598758,415.158943 511.297793,461.625274 375.109553,553.556189 C355.154858,567.258623 337.080515,573.934908 320.886524,573.585046 C303.033948,573.199351 268.692754,563.490928 243.163606,555.192408 C211.851067,545.013936 186.964484,539.632504 189.131547,522.346309 C190.260287,513.342589 202.659244,504.134509 226.328419,494.722069 Z" id="Path-3" fill="#FFFFFF"></path></g></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
BIN
Telegram/Resources/icons/player/player_settings.png
Normal file
|
After Width: | Height: | Size: 997 B |
BIN
Telegram/Resources/icons/player/player_settings@2x.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
Telegram/Resources/icons/player/player_settings@3x.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
Telegram/Resources/icons/qr_mini.png
Normal file
|
After Width: | Height: | Size: 641 B |
BIN
Telegram/Resources/icons/qr_mini@2x.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/qr_mini@3x.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/voice_lock/input_round_s.png
Normal file
|
After Width: | Height: | Size: 498 B |
BIN
Telegram/Resources/icons/voice_lock/input_round_s@2x.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/voice_lock/input_round_s@3x.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
@@ -12,6 +12,7 @@ body {
|
||||
margin: 0;
|
||||
background-color: var(--td-window-bg);
|
||||
color: var(--td-window-fg);
|
||||
zoom: var(--td-zoom-percentage);
|
||||
}
|
||||
|
||||
html.custom_scroll ::-webkit-scrollbar {
|
||||
|
||||
@@ -72,6 +72,9 @@ var IV = {
|
||||
}
|
||||
},
|
||||
frameKeyDown: function (e) {
|
||||
const key0 = (e.key === '0')
|
||||
|| (e.code === 'Key0')
|
||||
|| (e.keyCode === 48);
|
||||
const keyW = (e.key === 'w')
|
||||
|| (e.code === 'KeyW')
|
||||
|| (e.keyCode === 87);
|
||||
@@ -81,12 +84,12 @@ var IV = {
|
||||
const keyM = (e.key === 'm')
|
||||
|| (e.code === 'KeyM')
|
||||
|| (e.keyCode === 77);
|
||||
if ((e.metaKey || e.ctrlKey) && (keyW || keyQ || keyM)) {
|
||||
if ((e.metaKey || e.ctrlKey) && (keyW || keyQ || keyM || key0)) {
|
||||
e.preventDefault();
|
||||
IV.notify({
|
||||
event: 'keydown',
|
||||
modifier: e.ctrlKey ? 'ctrl' : 'cmd',
|
||||
key: keyW ? 'w' : keyQ ? 'q' : 'm',
|
||||
key: key0 ? '0' : keyW ? 'w' : keyQ ? 'q' : 'm',
|
||||
});
|
||||
} else if (e.key === 'Escape' || e.keyCode === 27) {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
<file alias="collectible_phone.tgs">../../animations/collectible_phone.tgs</file>
|
||||
<file alias="search.tgs">../../animations/search.tgs</file>
|
||||
<file alias="noresults.tgs">../../animations/noresults.tgs</file>
|
||||
<file alias="hello_status.tgs">../../animations/hello_status.tgs</file>
|
||||
|
||||
<file alias="dice_idle.tgs">../../animations/dice/dice_idle.tgs</file>
|
||||
<file alias="dart_idle.tgs">../../animations/dice/dart_idle.tgs</file>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<file alias="art/logo_256.png">../../art/logo_256.png</file>
|
||||
<file alias="art/logo_256_no_margin.png">../../art/logo_256_no_margin.png</file>
|
||||
<file alias="art/themeimage.jpg">../../art/themeimage.jpg</file>
|
||||
<file alias="art/round_placeholder.jpg">../../art/round_placeholder.jpg</file>
|
||||
<file alias="day-blue.tdesktop-theme">../../day-blue.tdesktop-theme</file>
|
||||
<file alias="night.tdesktop-theme">../../night.tdesktop-theme</file>
|
||||
<file alias="night-green.tdesktop-theme">../../night-green.tdesktop-theme</file>
|
||||
@@ -31,6 +32,7 @@
|
||||
<file alias="topic_icons/gray.svg">../../art/topic_icons/gray.svg</file>
|
||||
<file alias="topic_icons/general.svg">../../art/topic_icons/general.svg</file>
|
||||
<file alias="links_subscription.svg">../../icons/info/edit/links_subscription.svg</file>
|
||||
<file alias="plane_white.svg">../../icons/plane_white.svg</file>
|
||||
</qresource>
|
||||
<qresource prefix="/icons">
|
||||
<file alias="calls/hands.lottie">../../icons/calls/hands.lottie</file>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="5.4.4.0" />
|
||||
Version="5.8.2.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
</compatibility>
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitor</dpiAwareness>
|
||||
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,4,4,0
|
||||
PRODUCTVERSION 5,4,4,0
|
||||
FILEVERSION 5,8,2,0
|
||||
PRODUCTVERSION 5,8,2,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "5.4.4.0"
|
||||
VALUE "FileVersion", "5.8.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.4.4.0"
|
||||
VALUE "ProductVersion", "5.8.2.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,4,4,0
|
||||
PRODUCTVERSION 5,4,4,0
|
||||
FILEVERSION 5,8,2,0
|
||||
PRODUCTVERSION 5,8,2,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "5.4.4.0"
|
||||
VALUE "FileVersion", "5.8.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.4.4.0"
|
||||
VALUE "ProductVersion", "5.8.2.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -39,6 +39,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtGui/QClipboard>
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
@@ -503,11 +506,19 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
|
||||
bot->session().attachWebView().open({
|
||||
.bot = bot,
|
||||
.context = { .controller = controller },
|
||||
.button = {.text = button->text, .url = button->data },
|
||||
.button = { .text = button->text, .url = button->data },
|
||||
.source = InlineBots::WebViewSourceButton{ .simple = true },
|
||||
});
|
||||
}
|
||||
} break;
|
||||
|
||||
case ButtonType::CopyText: {
|
||||
const auto text = QString::fromUtf8(button->data);
|
||||
if (!text.isEmpty()) {
|
||||
QGuiApplication::clipboard()->setText(text);
|
||||
controller->showToast(tr::lng_text_copied(tr::now));
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/premium_limits_box.h"
|
||||
#include "boxes/filters/edit_filter_links.h" // FilterChatStatusText
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
@@ -152,6 +153,7 @@ void InitFilterLinkHeader(
|
||||
.badge = (type == Ui::FilterLinkHeaderType::AddingChats
|
||||
? std::move(count)
|
||||
: rpl::single(0)),
|
||||
.horizontalFilters = Core::App().settings().chatFiltersHorizontal(),
|
||||
});
|
||||
const auto widget = header.widget;
|
||||
widget->resizeToWidth(st::boxWideWidth);
|
||||
@@ -232,12 +234,12 @@ void ImportInvite(
|
||||
api->request(MTPchatlists_JoinChatlistInvite(
|
||||
MTP_string(slug),
|
||||
MTP_vector<MTPInputPeer>(std::move(inputs))
|
||||
)).done(callback).fail(error).send();
|
||||
)).done(callback).fail(error).handleFloodErrors().send();
|
||||
} else {
|
||||
api->request(MTPchatlists_JoinChatlistUpdates(
|
||||
MTP_inputChatlistDialogFilter(MTP_int(filterId)),
|
||||
MTP_vector<MTPInputPeer>(std::move(inputs))
|
||||
)).done(callback).fail(error).send();
|
||||
)).done(callback).fail(error).handleFloodErrors().send();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -517,6 +519,8 @@ void ShowImportError(
|
||||
} else {
|
||||
window->showToast((error == u"INVITE_SLUG_EXPIRED"_q)
|
||||
? tr::lng_group_invite_bad_link(tr::now)
|
||||
: error.startsWith(u"FLOOD_WAIT_"_q)
|
||||
? tr::lng_flood_error(tr::now)
|
||||
: error);
|
||||
}
|
||||
}
|
||||
|
||||
128
Telegram/SourceFiles/api/api_chat_filters_remove_manager.cpp
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
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_chat_filters_remove_manager.h"
|
||||
|
||||
#include "api/api_chat_filters.h"
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_layers.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
void RemoveChatFilter(
|
||||
not_null<Main::Session*> session,
|
||||
FilterId filterId,
|
||||
std::vector<not_null<PeerData*>> leave) {
|
||||
const auto api = &session->api();
|
||||
session->data().chatsFilters().apply(MTP_updateDialogFilter(
|
||||
MTP_flags(MTPDupdateDialogFilter::Flag(0)),
|
||||
MTP_int(filterId),
|
||||
MTPDialogFilter()));
|
||||
if (leave.empty()) {
|
||||
api->request(MTPmessages_UpdateDialogFilter(
|
||||
MTP_flags(MTPmessages_UpdateDialogFilter::Flag(0)),
|
||||
MTP_int(filterId),
|
||||
MTPDialogFilter()
|
||||
)).send();
|
||||
} else {
|
||||
api->request(MTPchatlists_LeaveChatlist(
|
||||
MTP_inputChatlistDialogFilter(MTP_int(filterId)),
|
||||
MTP_vector<MTPInputPeer>(ranges::views::all(
|
||||
leave
|
||||
) | ranges::views::transform([](not_null<PeerData*> peer) {
|
||||
return MTPInputPeer(peer->input);
|
||||
}) | ranges::to<QVector<MTPInputPeer>>())
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
api->applyUpdates(result);
|
||||
}).send();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RemoveComplexChatFilter::RemoveComplexChatFilter() = default;
|
||||
|
||||
void RemoveComplexChatFilter::request(
|
||||
QPointer<Ui::RpWidget> widget,
|
||||
base::weak_ptr<Window::SessionController> weak,
|
||||
FilterId id) {
|
||||
const auto session = &weak->session();
|
||||
const auto &list = session->data().chatsFilters().list();
|
||||
const auto i = ranges::find(list, id, &Data::ChatFilter::id);
|
||||
const auto filter = (i != end(list)) ? *i : Data::ChatFilter();
|
||||
const auto has = filter.hasMyLinks();
|
||||
const auto confirm = [=](Fn<void()> action, bool onlyWhenHas = false) {
|
||||
if (!has && onlyWhenHas) {
|
||||
action();
|
||||
return;
|
||||
}
|
||||
weak->window().show(Ui::MakeConfirmBox({
|
||||
.text = (has
|
||||
? tr::lng_filters_delete_sure()
|
||||
: tr::lng_filters_remove_sure()),
|
||||
.confirmed = [=](Fn<void()> &&close) { close(); action(); },
|
||||
.confirmText = (has
|
||||
? tr::lng_box_delete()
|
||||
: tr::lng_filters_remove_yes()),
|
||||
.confirmStyle = &st::attentionBoxButton,
|
||||
}));
|
||||
};
|
||||
const auto simple = [=] {
|
||||
confirm([=] { RemoveChatFilter(session, id, {}); });
|
||||
};
|
||||
const auto suggestRemoving = Api::ExtractSuggestRemoving(filter);
|
||||
if (suggestRemoving.empty()) {
|
||||
simple();
|
||||
return;
|
||||
} else if (_removingRequestId) {
|
||||
if (_removingId == id) {
|
||||
return;
|
||||
}
|
||||
session->api().request(_removingRequestId).cancel();
|
||||
}
|
||||
_removingId = id;
|
||||
_removingRequestId = session->api().request(
|
||||
MTPchatlists_GetLeaveChatlistSuggestions(
|
||||
MTP_inputChatlistDialogFilter(
|
||||
MTP_int(id)))
|
||||
).done(crl::guard(widget, [=, this](const MTPVector<MTPPeer> &result) {
|
||||
_removingRequestId = 0;
|
||||
const auto suggestRemovePeers = ranges::views::all(
|
||||
result.v
|
||||
) | ranges::views::transform([=](const MTPPeer &peer) {
|
||||
return session->data().peer(peerFromMTP(peer));
|
||||
}) | ranges::to_vector;
|
||||
const auto chosen = crl::guard(widget, [=](
|
||||
std::vector<not_null<PeerData*>> peers) {
|
||||
RemoveChatFilter(session, id, std::move(peers));
|
||||
});
|
||||
confirm(crl::guard(widget, [=] {
|
||||
Api::ProcessFilterRemove(
|
||||
weak,
|
||||
filter.title(),
|
||||
filter.iconEmoji(),
|
||||
suggestRemoving,
|
||||
suggestRemovePeers,
|
||||
chosen);
|
||||
}), true);
|
||||
})).fail(crl::guard(widget, [=, this] {
|
||||
_removingRequestId = 0;
|
||||
simple();
|
||||
})).send();
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
35
Telegram/SourceFiles/api/api_chat_filters_remove_manager.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace Ui {
|
||||
class RpWidget;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Api {
|
||||
|
||||
class RemoveComplexChatFilter final {
|
||||
public:
|
||||
RemoveComplexChatFilter();
|
||||
|
||||
void request(
|
||||
QPointer<Ui::RpWidget> widget,
|
||||
base::weak_ptr<Window::SessionController> weak,
|
||||
FilterId id);
|
||||
|
||||
private:
|
||||
FilterId _removingId = 0;
|
||||
mtpRequestId _removingRequestId = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Api
|
||||
@@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "settings/settings_credits_graphics.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/controls/userpic_button.h"
|
||||
#include "ui/effects/credits_graphics.h"
|
||||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/effects/premium_stars_colored.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
@@ -129,6 +130,7 @@ void ConfirmSubscriptionBox(
|
||||
struct State final {
|
||||
std::shared_ptr<Data::PhotoMedia> photoMedia;
|
||||
std::unique_ptr<Ui::EmptyUserpic> photoEmpty;
|
||||
QImage frame;
|
||||
|
||||
std::optional<MTP::Sender> api;
|
||||
Ui::RpWidget* saveButton = nullptr;
|
||||
@@ -146,25 +148,45 @@ void ConfirmSubscriptionBox(
|
||||
const auto userpic = userpicWrap->entity();
|
||||
const auto photoSize = st::confirmInvitePhotoSize;
|
||||
userpic->resize(Size(photoSize));
|
||||
const auto creditsIconSize = photoSize / 3;
|
||||
const auto creditsIconCallback =
|
||||
Ui::PaintOutlinedColoredCreditsIconCallback(
|
||||
creditsIconSize,
|
||||
1.5);
|
||||
state->frame = QImage(
|
||||
Size(photoSize * style::DevicePixelRatio()),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
state->frame.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
const auto options = Images::Option::RoundCircle;
|
||||
userpic->paintRequest(
|
||||
) | rpl::start_with_next([=, small = Data::PhotoSize::Small] {
|
||||
auto p = QPainter(userpic);
|
||||
if (state->photoMedia) {
|
||||
if (const auto image = state->photoMedia->image(small)) {
|
||||
p.drawPixmap(
|
||||
state->frame.fill(Qt::transparent);
|
||||
{
|
||||
auto p = QPainter(&state->frame);
|
||||
if (state->photoMedia) {
|
||||
if (const auto image = state->photoMedia->image(small)) {
|
||||
p.drawPixmap(
|
||||
0,
|
||||
0,
|
||||
image->pix(Size(photoSize), { .options = options }));
|
||||
}
|
||||
} else if (state->photoEmpty) {
|
||||
state->photoEmpty->paintCircle(
|
||||
p,
|
||||
0,
|
||||
0,
|
||||
image->pix(Size(photoSize), { .options = options }));
|
||||
userpic->width(),
|
||||
photoSize);
|
||||
}
|
||||
if (creditsIconCallback) {
|
||||
p.translate(
|
||||
photoSize - creditsIconSize,
|
||||
photoSize - creditsIconSize);
|
||||
creditsIconCallback(p);
|
||||
}
|
||||
} else if (state->photoEmpty) {
|
||||
state->photoEmpty->paintCircle(
|
||||
p,
|
||||
0,
|
||||
0,
|
||||
userpic->width(),
|
||||
photoSize);
|
||||
}
|
||||
auto p = QPainter(userpic);
|
||||
p.drawImage(0, 0, state->frame);
|
||||
}, userpicWrap->lifetime());
|
||||
userpicWrap->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
if (photo) {
|
||||
@@ -275,7 +297,6 @@ void ConfirmSubscriptionBox(
|
||||
: 0;
|
||||
state->api->request(
|
||||
MTPpayments_SendStarsForm(
|
||||
MTP_flags(0),
|
||||
MTP_long(formId),
|
||||
MTP_inputInvoiceChatInviteSubscription(MTP_string(hash)))
|
||||
).done([=](const MTPpayments_PaymentResult &result) {
|
||||
|
||||
@@ -264,14 +264,24 @@ ChatParticipant::ChatParticipant(
|
||||
_rank = qs(data.vrank().value_or_empty());
|
||||
_rights = ChatAdminRightsInfo(data.vadmin_rights());
|
||||
_by = peerToUser(peerFromUser(data.vpromoted_by()));
|
||||
_date = data.vdate().v;
|
||||
}, [&](const MTPDchannelParticipantSelf &data) {
|
||||
_type = Type::Member;
|
||||
_date = data.vdate().v;
|
||||
_by = peerToUser(peerFromUser(data.vinviter_id()));
|
||||
if (data.vsubscription_until_date()) {
|
||||
_subscriptionDate = data.vsubscription_until_date()->v;
|
||||
}
|
||||
}, [&](const MTPDchannelParticipant &data) {
|
||||
_type = Type::Member;
|
||||
_date = data.vdate().v;
|
||||
if (data.vsubscription_until_date()) {
|
||||
_subscriptionDate = data.vsubscription_until_date()->v;
|
||||
}
|
||||
}, [&](const MTPDchannelParticipantBanned &data) {
|
||||
_restrictions = ChatRestrictionsInfo(data.vbanned_rights());
|
||||
_by = peerToUser(peerFromUser(data.vkicked_by()));
|
||||
_date = data.vdate().v;
|
||||
|
||||
_type = (_restrictions.flags & ChatRestriction::ViewMessages)
|
||||
? Type::Banned
|
||||
@@ -348,6 +358,24 @@ ChatAdminRightsInfo ChatParticipant::rights() const {
|
||||
return _rights;
|
||||
}
|
||||
|
||||
TimeId ChatParticipant::subscriptionDate() const {
|
||||
return _subscriptionDate;
|
||||
}
|
||||
|
||||
TimeId ChatParticipant::promotedSince() const {
|
||||
return (_type == Type::Admin) ? _date : TimeId(0);
|
||||
}
|
||||
|
||||
TimeId ChatParticipant::restrictedSince() const {
|
||||
return (_type == Type::Restricted || _type == Type::Banned)
|
||||
? _date
|
||||
: TimeId(0);
|
||||
}
|
||||
|
||||
TimeId ChatParticipant::memberSince() const {
|
||||
return (_type == Type::Member) ? _date : TimeId(0);
|
||||
}
|
||||
|
||||
ChatParticipant::Type ChatParticipant::type() const {
|
||||
return _type;
|
||||
}
|
||||
|
||||
@@ -60,6 +60,11 @@ public:
|
||||
ChatRestrictionsInfo restrictions() const;
|
||||
ChatAdminRightsInfo rights() const;
|
||||
|
||||
TimeId subscriptionDate() const;
|
||||
TimeId promotedSince() const;
|
||||
TimeId restrictedSince() const;
|
||||
TimeId memberSince() const;
|
||||
|
||||
Type type() const;
|
||||
QString rank() const;
|
||||
|
||||
@@ -73,6 +78,8 @@ private:
|
||||
bool _canBeEdited = false;
|
||||
|
||||
QString _rank;
|
||||
TimeId _subscriptionDate = 0;
|
||||
TimeId _date = 0;
|
||||
|
||||
ChatRestrictionsInfo _restrictions;
|
||||
ChatAdminRightsInfo _rights;
|
||||
|
||||
@@ -12,6 +12,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/core_cloud_password.h"
|
||||
#include "passport/passport_encryption.h"
|
||||
|
||||
#include "base/unixtime.h"
|
||||
#include "base/call_delayed.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_updates.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "data/components/credits.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_peer.h"
|
||||
@@ -38,8 +39,8 @@ constexpr auto kTransactionsLimit = 100;
|
||||
if (const auto list = tl.data().vextended_media()) {
|
||||
extended.reserve(list->v.size());
|
||||
for (const auto &media : list->v) {
|
||||
media.match([&](const MTPDmessageMediaPhoto &photo) {
|
||||
if (const auto inner = photo.vphoto()) {
|
||||
media.match([&](const MTPDmessageMediaPhoto &data) {
|
||||
if (const auto inner = data.vphoto()) {
|
||||
const auto photo = owner->processPhoto(*inner);
|
||||
if (!photo->isNull()) {
|
||||
extended.push_back(CreditsHistoryMedia{
|
||||
@@ -48,9 +49,11 @@ constexpr auto kTransactionsLimit = 100;
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [&](const MTPDmessageMediaDocument &document) {
|
||||
if (const auto inner = document.vdocument()) {
|
||||
const auto document = owner->processDocument(*inner);
|
||||
}, [&](const MTPDmessageMediaDocument &data) {
|
||||
if (const auto inner = data.vdocument()) {
|
||||
const auto document = owner->processDocument(
|
||||
*inner,
|
||||
data.valt_documents());
|
||||
if (document->isAnimation()
|
||||
|| document->isVideoFile()
|
||||
|| document->isGifv()) {
|
||||
@@ -69,16 +72,26 @@ constexpr auto kTransactionsLimit = 100;
|
||||
}, [](const auto &) {
|
||||
return PeerId(0);
|
||||
}).value;
|
||||
const auto stargift = tl.data().vstargift();
|
||||
const auto reaction = tl.data().is_reaction();
|
||||
const auto incoming = (int64(tl.data().vstars().v) >= 0);
|
||||
const auto saveActorId = (reaction || !extended.empty()) && incoming;
|
||||
return Data::CreditsHistoryEntry{
|
||||
.id = qs(tl.data().vid()),
|
||||
.title = qs(tl.data().vtitle().value_or_empty()),
|
||||
.description = qs(tl.data().vdescription().value_or_empty()),
|
||||
.description = { qs(tl.data().vdescription().value_or_empty()) },
|
||||
.date = base::unixtime::parse(tl.data().vdate().v),
|
||||
.photoId = photo ? photo->id : 0,
|
||||
.extended = std::move(extended),
|
||||
.credits = tl.data().vstars().v,
|
||||
.bareMsgId = uint64(tl.data().vmsg_id().value_or_empty()),
|
||||
.barePeerId = barePeerId,
|
||||
.barePeerId = saveActorId ? peer->id.value : barePeerId,
|
||||
.bareGiveawayMsgId = uint64(
|
||||
tl.data().vgiveaway_post_id().value_or_empty()),
|
||||
.bareGiftStickerId = (stargift
|
||||
? owner->processDocument(stargift->data().vsticker())->id
|
||||
: 0),
|
||||
.bareActorId = saveActorId ? barePeerId : uint64(0),
|
||||
.peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::Peer;
|
||||
}, [](const MTPDstarsTransactionPeerPlayMarket &) {
|
||||
@@ -93,6 +106,8 @@ constexpr auto kTransactionsLimit = 100;
|
||||
return Data::CreditsHistoryEntry::PeerType::PremiumBot;
|
||||
}, [](const MTPDstarsTransactionPeerAds &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::Ads;
|
||||
}, [](const MTPDstarsTransactionPeerAPI &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::API;
|
||||
}),
|
||||
.subscriptionUntil = tl.data().vsubscription_period()
|
||||
? base::unixtime::parse(base::unixtime::now()
|
||||
@@ -102,12 +117,18 @@ constexpr auto kTransactionsLimit = 100;
|
||||
? base::unixtime::parse(tl.data().vtransaction_date()->v)
|
||||
: QDateTime(),
|
||||
.successLink = qs(tl.data().vtransaction_url().value_or_empty()),
|
||||
.starsConverted = int(stargift
|
||||
? stargift->data().vconvert_stars().v
|
||||
: 0),
|
||||
.floodSkip = int(tl.data().vfloodskip_number().value_or(0)),
|
||||
.converted = stargift && incoming,
|
||||
.stargift = stargift.has_value(),
|
||||
.reaction = tl.data().is_reaction(),
|
||||
.refunded = tl.data().is_refund(),
|
||||
.pending = tl.data().is_pending(),
|
||||
.failed = tl.data().is_failed(),
|
||||
.in = (int64(tl.data().vstars().v) >= 0),
|
||||
.gift = tl.data().is_gift(),
|
||||
.in = incoming,
|
||||
.gift = tl.data().is_gift() || stargift.has_value(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -154,7 +175,8 @@ constexpr auto kTransactionsLimit = 100;
|
||||
.balance = status.data().vbalance().v,
|
||||
.subscriptionsMissingBalance
|
||||
= status.data().vsubscriptions_missing_balance().value_or_empty(),
|
||||
.allLoaded = !status.data().vnext_offset().has_value(),
|
||||
.allLoaded = !status.data().vnext_offset().has_value()
|
||||
&& !status.data().vsubscriptions_next_offset().has_value(),
|
||||
.token = qs(status.data().vnext_offset().value_or_empty()),
|
||||
.tokenSubscriptions = qs(
|
||||
status.data().vsubscriptions_next_offset().value_or_empty()),
|
||||
@@ -215,6 +237,10 @@ rpl::producer<rpl::no_value, QString> CreditsTopupOptions::request() {
|
||||
};
|
||||
}
|
||||
|
||||
Data::CreditTopupOptions CreditsTopupOptions::options() const {
|
||||
return _options;
|
||||
}
|
||||
|
||||
CreditsStatus::CreditsStatus(not_null<PeerData*> peer)
|
||||
: _peer(peer)
|
||||
, _api(&peer->session().api().instance()) {
|
||||
@@ -233,6 +259,8 @@ void CreditsStatus::request(
|
||||
_peer->isSelf() ? MTP_inputPeerSelf() : _peer->input
|
||||
)).done([=](const TLResult &result) {
|
||||
_requestId = 0;
|
||||
const auto balance = result.data().vbalance().v;
|
||||
_peer->session().credits().apply(_peer->id, balance);
|
||||
if (const auto onstack = done) {
|
||||
onstack(StatusFromTL(result, _peer));
|
||||
}
|
||||
@@ -294,10 +322,6 @@ void CreditsHistory::requestSubscriptions(
|
||||
}).send();
|
||||
}
|
||||
|
||||
Data::CreditTopupOptions CreditsTopupOptions::options() const {
|
||||
return _options;
|
||||
}
|
||||
|
||||
rpl::producer<not_null<PeerData*>> PremiumPeerBot(
|
||||
not_null<Main::Session*> session) {
|
||||
const auto username = session->appConfig().get<QString>(
|
||||
@@ -385,4 +409,58 @@ Data::CreditsEarnStatistics CreditsEarnStatistics::data() const {
|
||||
return _data;
|
||||
}
|
||||
|
||||
CreditsGiveawayOptions::CreditsGiveawayOptions(not_null<PeerData*> peer)
|
||||
: _peer(peer)
|
||||
, _api(&peer->session().api().instance()) {
|
||||
}
|
||||
|
||||
rpl::producer<rpl::no_value, QString> CreditsGiveawayOptions::request() {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
using TLOption = MTPStarsGiveawayOption;
|
||||
|
||||
const auto optionsFromTL = [=](const auto &options) {
|
||||
return ranges::views::all(
|
||||
options
|
||||
) | ranges::views::transform([=](const auto &option) {
|
||||
return Data::CreditsGiveawayOption{
|
||||
.winners = ranges::views::all(
|
||||
option.data().vwinners().v
|
||||
) | ranges::views::transform([](const auto &winner) {
|
||||
return Data::CreditsGiveawayOption::Winner{
|
||||
.users = winner.data().vusers().v,
|
||||
.perUserStars = winner.data().vper_user_stars().v,
|
||||
.isDefault = winner.data().is_default(),
|
||||
};
|
||||
}) | ranges::to_vector,
|
||||
.storeProduct = qs(
|
||||
option.data().vstore_product().value_or_empty()),
|
||||
.currency = qs(option.data().vcurrency()),
|
||||
.amount = option.data().vamount().v,
|
||||
.credits = option.data().vstars().v,
|
||||
.yearlyBoosts = option.data().vyearly_boosts().v,
|
||||
.isExtended = option.data().is_extended(),
|
||||
.isDefault = option.data().is_default(),
|
||||
};
|
||||
}) | ranges::to_vector;
|
||||
};
|
||||
const auto fail = [=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
};
|
||||
|
||||
_api.request(MTPpayments_GetStarsGiveawayOptions(
|
||||
)).done([=](const MTPVector<TLOption> &result) {
|
||||
_options = optionsFromTL(result.v);
|
||||
consumer.put_done();
|
||||
}).fail(fail).send();
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
Data::CreditsGiveawayOptions CreditsGiveawayOptions::options() const {
|
||||
return _options;
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -36,6 +36,22 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class CreditsGiveawayOptions final {
|
||||
public:
|
||||
CreditsGiveawayOptions(not_null<PeerData*> peer);
|
||||
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> request();
|
||||
[[nodiscard]] Data::CreditsGiveawayOptions options() const;
|
||||
|
||||
private:
|
||||
const not_null<PeerData*> _peer;
|
||||
|
||||
Data::CreditsGiveawayOptions _options;
|
||||
|
||||
MTP::Sender _api;
|
||||
|
||||
};
|
||||
|
||||
class CreditsStatus final {
|
||||
public:
|
||||
CreditsStatus(not_null<PeerData*> peer);
|
||||
@@ -83,8 +99,8 @@ public:
|
||||
[[nodiscard]] Data::CreditsEarnStatistics data() const;
|
||||
|
||||
private:
|
||||
const bool _isUser = false;
|
||||
Data::CreditsEarnStatistics _data;
|
||||
bool _isUser = false;
|
||||
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ void HandleWithdrawalButton(
|
||||
std::shared_ptr<Ui::Show> show) {
|
||||
Expects(receiver.currencyReceiver
|
||||
|| (receiver.creditsReceiver && receiver.creditsAmount));
|
||||
|
||||
struct State {
|
||||
rpl::lifetime lifetime;
|
||||
bool loading = false;
|
||||
@@ -58,8 +59,7 @@ void HandleWithdrawalButton(
|
||||
const auto processOut = [=] {
|
||||
if (state->loading) {
|
||||
return;
|
||||
}
|
||||
if (peer && !receiver.creditsAmount()) {
|
||||
} else if (peer && !receiver.creditsAmount()) {
|
||||
return;
|
||||
}
|
||||
state->loading = true;
|
||||
@@ -89,12 +89,15 @@ void HandleWithdrawalButton(
|
||||
}
|
||||
};
|
||||
const auto fail = [=](const MTP::Error &error) {
|
||||
show->showToast(error.type());
|
||||
const auto message = error.type();
|
||||
if (box && !box->handleCustomCheckError(message)) {
|
||||
show->showToast(message);
|
||||
}
|
||||
};
|
||||
if (channel) {
|
||||
session->api().request(
|
||||
MTPstats_GetBroadcastRevenueWithdrawalUrl(
|
||||
channel->inputChannel,
|
||||
channel->input,
|
||||
result.result
|
||||
)).done([=](const ChannelOutUrl &r) {
|
||||
done(qs(r.data().vurl()));
|
||||
@@ -134,7 +137,7 @@ void HandleWithdrawalButton(
|
||||
if (channel) {
|
||||
session->api().request(
|
||||
MTPstats_GetBroadcastRevenueWithdrawalUrl(
|
||||
channel->inputChannel,
|
||||
channel->input,
|
||||
MTP_inputCheckPasswordEmpty()
|
||||
)).fail(fail).send();
|
||||
} else if (peer) {
|
||||
|
||||
@@ -115,6 +115,28 @@ rpl::producer<bool> GlobalPrivacy::newRequirePremium() const {
|
||||
return _newRequirePremium.value();
|
||||
}
|
||||
|
||||
void GlobalPrivacy::loadPaidReactionAnonymous() {
|
||||
if (_paidReactionAnonymousLoaded) {
|
||||
return;
|
||||
}
|
||||
_paidReactionAnonymousLoaded = true;
|
||||
_api.request(MTPmessages_GetPaidReactionPrivacy(
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_session->api().applyUpdates(result);
|
||||
}).send();
|
||||
}
|
||||
|
||||
void GlobalPrivacy::updatePaidReactionAnonymous(bool value) {
|
||||
_paidReactionAnonymous = value;
|
||||
}
|
||||
|
||||
bool GlobalPrivacy::paidReactionAnonymousCurrent() const {
|
||||
return _paidReactionAnonymous.current();
|
||||
}
|
||||
|
||||
rpl::producer<bool> GlobalPrivacy::paidReactionAnonymous() const {
|
||||
return _paidReactionAnonymous.value();
|
||||
}
|
||||
|
||||
void GlobalPrivacy::updateArchiveAndMute(bool value) {
|
||||
update(
|
||||
|
||||
@@ -49,6 +49,11 @@ public:
|
||||
[[nodiscard]] bool newRequirePremiumCurrent() const;
|
||||
[[nodiscard]] rpl::producer<bool> newRequirePremium() const;
|
||||
|
||||
void loadPaidReactionAnonymous();
|
||||
void updatePaidReactionAnonymous(bool value);
|
||||
[[nodiscard]] bool paidReactionAnonymousCurrent() const;
|
||||
[[nodiscard]] rpl::producer<bool> paidReactionAnonymous() const;
|
||||
|
||||
private:
|
||||
void apply(const MTPGlobalPrivacySettings &data);
|
||||
|
||||
@@ -67,7 +72,9 @@ private:
|
||||
rpl::variable<bool> _showArchiveAndMute = false;
|
||||
rpl::variable<bool> _hideReadTime = false;
|
||||
rpl::variable<bool> _newRequirePremium = false;
|
||||
rpl::variable<bool> _paidReactionAnonymous = false;
|
||||
std::vector<Fn<void()>> _callbacks;
|
||||
bool _paidReactionAnonymousLoaded = false;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -37,7 +37,8 @@ MTPVector<MTPDocumentAttribute> ComposeSendingDocumentAttributes(
|
||||
MTP_int(dimensions.width()),
|
||||
MTP_int(dimensions.height()),
|
||||
MTPint(), // preload_prefix_size
|
||||
MTPdouble())); // video_start_ts
|
||||
MTPdouble(), // video_start_ts
|
||||
MTPstring())); // video_codec
|
||||
} else {
|
||||
attributes.push_back(MTP_documentAttributeImageSize(
|
||||
MTP_int(dimensions.width()),
|
||||
|
||||
@@ -361,9 +361,10 @@ void Premium::resolveGiveawayInfo(
|
||||
? GiveawayState::Refunded
|
||||
: GiveawayState::Finished;
|
||||
info.giftCode = qs(data.vgift_code_slug().value_or_empty());
|
||||
info.activatedCount = data.vactivated_count().v;
|
||||
info.activatedCount = data.vactivated_count().value_or_empty();
|
||||
info.finishDate = data.vfinish_date().v;
|
||||
info.startDate = data.vstart_date().v;
|
||||
info.credits = data.vstars_prize().value_or_empty();
|
||||
});
|
||||
_giveawayInfoDone(std::move(info));
|
||||
}).fail([=] {
|
||||
@@ -508,7 +509,9 @@ rpl::producer<rpl::no_value, QString> PremiumGiftCodeOptions::applyPrepaid(
|
||||
_api.request(MTPpayments_LaunchPrepaidGiveaway(
|
||||
_peer->input,
|
||||
MTP_long(prepaidId),
|
||||
Payments::InvoicePremiumGiftCodeGiveawayToTL(invoice)
|
||||
invoice.creditsAmount
|
||||
? Payments::InvoiceCreditsGiveawayToTL(invoice)
|
||||
: Payments::InvoicePremiumGiftCodeGiveawayToTL(invoice)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_peer->session().api().applyUpdates(result);
|
||||
consumer.put_done();
|
||||
@@ -537,16 +540,34 @@ Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice(
|
||||
const auto token = Token{ users, months };
|
||||
const auto &store = _stores[token];
|
||||
return Payments::InvoicePremiumGiftCode{
|
||||
.randomId = randomId,
|
||||
.currency = _optionsForOnePerson.currency,
|
||||
.amount = store.amount,
|
||||
.storeProduct = store.product,
|
||||
.randomId = randomId,
|
||||
.amount = store.amount,
|
||||
.storeQuantity = store.quantity,
|
||||
.users = token.users,
|
||||
.months = token.months,
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<GiftOptionData> PremiumGiftCodeOptions::optionsForPeer() const {
|
||||
auto result = std::vector<GiftOptionData>();
|
||||
|
||||
if (!_optionsForOnePerson.currency.isEmpty()) {
|
||||
const auto count = int(_optionsForOnePerson.months.size());
|
||||
result.reserve(count);
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
Assert(i < _optionsForOnePerson.totalCosts.size());
|
||||
result.push_back({
|
||||
.cost = _optionsForOnePerson.totalCosts[i],
|
||||
.currency = _optionsForOnePerson.currency,
|
||||
.months = _optionsForOnePerson.months[i],
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Data::PremiumSubscriptionOptions PremiumGiftCodeOptions::options(int amount) {
|
||||
const auto it = _subscriptionOptions.find(amount);
|
||||
if (it != end(_subscriptionOptions)) {
|
||||
@@ -568,6 +589,41 @@ Data::PremiumSubscriptionOptions PremiumGiftCodeOptions::options(int amount) {
|
||||
}
|
||||
}
|
||||
|
||||
auto PremiumGiftCodeOptions::requestStarGifts()
|
||||
-> rpl::producer<rpl::no_value, QString> {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
_api.request(MTPpayments_GetStarGifts(
|
||||
MTP_int(0)
|
||||
)).done([=](const MTPpayments_StarGifts &result) {
|
||||
result.match([&](const MTPDpayments_starGifts &data) {
|
||||
_giftsHash = data.vhash().v;
|
||||
const auto &list = data.vgifts().v;
|
||||
const auto session = &_peer->session();
|
||||
auto gifts = std::vector<StarGift>();
|
||||
gifts.reserve(list.size());
|
||||
for (const auto &gift : list) {
|
||||
if (auto parsed = FromTL(session, gift)) {
|
||||
gifts.push_back(std::move(*parsed));
|
||||
}
|
||||
}
|
||||
_gifts = std::move(gifts);
|
||||
}, [&](const MTPDpayments_starGiftsNotModified &) {
|
||||
});
|
||||
consumer.put_done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
const std::vector<StarGift> &PremiumGiftCodeOptions::starGifts() const {
|
||||
return _gifts;
|
||||
}
|
||||
|
||||
int PremiumGiftCodeOptions::giveawayBoostsPerPremium() const {
|
||||
constexpr auto kFallbackCount = 4;
|
||||
return _peer->session().appConfig().get<int>(
|
||||
@@ -702,4 +758,59 @@ rpl::producer<DocumentData*> RandomHelloStickerValue(
|
||||
}) | rpl::take(1) | rpl::map(random));
|
||||
}
|
||||
|
||||
std::optional<StarGift> FromTL(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPstarGift &gift) {
|
||||
const auto &data = gift.data();
|
||||
const auto document = session->data().processDocument(
|
||||
data.vsticker());
|
||||
const auto remaining = data.vavailability_remains();
|
||||
const auto total = data.vavailability_total();
|
||||
if (!document->sticker()) {
|
||||
return {};
|
||||
}
|
||||
return StarGift{
|
||||
.id = uint64(data.vid().v),
|
||||
.stars = int64(data.vstars().v),
|
||||
.starsConverted = int64(data.vconvert_stars().v),
|
||||
.document = document,
|
||||
.limitedLeft = remaining.value_or_empty(),
|
||||
.limitedCount = total.value_or_empty(),
|
||||
.firstSaleDate = data.vfirst_sale_date().value_or_empty(),
|
||||
.lastSaleDate = data.vlast_sale_date().value_or_empty(),
|
||||
.birthday = data.is_birthday(),
|
||||
};
|
||||
}
|
||||
|
||||
std::optional<UserStarGift> FromTL(
|
||||
not_null<UserData*> to,
|
||||
const MTPuserStarGift &gift) {
|
||||
const auto session = &to->session();
|
||||
const auto &data = gift.data();
|
||||
auto parsed = FromTL(session, data.vgift());
|
||||
if (!parsed) {
|
||||
return {};
|
||||
}
|
||||
return UserStarGift{
|
||||
.info = std::move(*parsed),
|
||||
.message = (data.vmessage()
|
||||
? TextWithEntities{
|
||||
.text = qs(data.vmessage()->data().vtext()),
|
||||
.entities = Api::EntitiesFromMTP(
|
||||
session,
|
||||
data.vmessage()->data().ventities().v),
|
||||
}
|
||||
: TextWithEntities()),
|
||||
.starsConverted = int64(data.vconvert_stars().value_or_empty()),
|
||||
.fromId = (data.vfrom_id()
|
||||
? peerFromUser(data.vfrom_id()->v)
|
||||
: PeerId()),
|
||||
.messageId = data.vmsg_id().value_or_empty(),
|
||||
.date = data.vdate().v,
|
||||
.anonymous = data.is_name_hidden(),
|
||||
.hidden = data.is_unsaved(),
|
||||
.mine = to->isSelf(),
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -57,6 +57,7 @@ struct GiveawayInfo {
|
||||
TimeId tooEarlyDate = 0;
|
||||
TimeId finishDate = 0;
|
||||
TimeId startDate = 0;
|
||||
uint64 credits = 0;
|
||||
int winnersCount = 0;
|
||||
int activatedCount = 0;
|
||||
bool participating = false;
|
||||
@@ -66,6 +67,40 @@ struct GiveawayInfo {
|
||||
}
|
||||
};
|
||||
|
||||
struct GiftOptionData {
|
||||
int64 cost = 0;
|
||||
QString currency;
|
||||
int months = 0;
|
||||
};
|
||||
|
||||
struct StarGift {
|
||||
uint64 id = 0;
|
||||
int64 stars = 0;
|
||||
int64 starsConverted = 0;
|
||||
not_null<DocumentData*> document;
|
||||
int limitedLeft = 0;
|
||||
int limitedCount = 0;
|
||||
TimeId firstSaleDate = 0;
|
||||
TimeId lastSaleDate = 0;
|
||||
bool birthday = false;
|
||||
|
||||
friend inline bool operator==(
|
||||
const StarGift &,
|
||||
const StarGift &) = default;
|
||||
};
|
||||
|
||||
struct UserStarGift {
|
||||
StarGift info;
|
||||
TextWithEntities message;
|
||||
int64 starsConverted = 0;
|
||||
PeerId fromId = 0;
|
||||
MsgId messageId = 0;
|
||||
TimeId date = 0;
|
||||
bool anonymous = false;
|
||||
bool hidden = false;
|
||||
bool mine = false;
|
||||
};
|
||||
|
||||
class Premium final {
|
||||
public:
|
||||
explicit Premium(not_null<ApiWrap*> api);
|
||||
@@ -170,6 +205,7 @@ public:
|
||||
PremiumGiftCodeOptions(not_null<PeerData*> peer);
|
||||
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> request();
|
||||
[[nodiscard]] std::vector<GiftOptionData> optionsForPeer() const;
|
||||
[[nodiscard]] Data::PremiumSubscriptionOptions options(int amount);
|
||||
[[nodiscard]] const std::vector<int> &availablePresets() const;
|
||||
[[nodiscard]] int monthsFromPreset(int monthsIndex);
|
||||
@@ -186,6 +222,9 @@ public:
|
||||
[[nodiscard]] int giveawayPeriodMax() const;
|
||||
[[nodiscard]] bool giveawayGiftsPurchaseAvailable() const;
|
||||
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> requestStarGifts();
|
||||
[[nodiscard]] const std::vector<StarGift> &starGifts() const;
|
||||
|
||||
private:
|
||||
struct Token final {
|
||||
int users = 0;
|
||||
@@ -205,7 +244,7 @@ private:
|
||||
base::flat_map<Amount, PremiumSubscriptionOptions> _subscriptionOptions;
|
||||
struct {
|
||||
std::vector<int> months;
|
||||
std::vector<float64> totalCosts;
|
||||
std::vector<int64> totalCosts;
|
||||
QString currency;
|
||||
} _optionsForOnePerson;
|
||||
|
||||
@@ -213,6 +252,9 @@ private:
|
||||
|
||||
base::flat_map<Token, Store> _stores;
|
||||
|
||||
int32 _giftsHash = 0;
|
||||
std::vector<StarGift> _gifts;
|
||||
|
||||
MTP::Sender _api;
|
||||
|
||||
};
|
||||
@@ -241,4 +283,11 @@ enum class RequirePremiumState {
|
||||
[[nodiscard]] rpl::producer<DocumentData*> RandomHelloStickerValue(
|
||||
not_null<Main::Session*> session);
|
||||
|
||||
[[nodiscard]] std::optional<StarGift> FromTL(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPstarGift &gift);
|
||||
[[nodiscard]] std::optional<UserStarGift> FromTL(
|
||||
not_null<UserData*> to,
|
||||
const MTPuserStarGift &gift);
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -10,10 +10,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_report.h"
|
||||
#include "data/data_user.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/boxes/report_box.h"
|
||||
#include "ui/boxes/report_box_graphics.h"
|
||||
#include "ui/layers/show.h"
|
||||
|
||||
namespace Api {
|
||||
@@ -39,52 +40,106 @@ MTPreportReason ReasonToTL(const Ui::ReportReason &reason) {
|
||||
|
||||
} // namespace
|
||||
|
||||
void SendReport(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
Ui::ReportReason reason,
|
||||
const QString &comment,
|
||||
std::variant<
|
||||
v::null_t,
|
||||
MessageIdsList,
|
||||
not_null<PhotoData*>,
|
||||
StoryId> data) {
|
||||
auto done = [=] {
|
||||
void SendPhotoReport(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
Ui::ReportReason reason,
|
||||
const QString &comment,
|
||||
not_null<PhotoData*> photo) {
|
||||
peer->session().api().request(MTPaccount_ReportProfilePhoto(
|
||||
peer->input,
|
||||
photo->mtpInput(),
|
||||
ReasonToTL(reason),
|
||||
MTP_string(comment)
|
||||
)).done([=] {
|
||||
show->showToast(tr::lng_report_thanks(tr::now));
|
||||
};
|
||||
v::match(data, [&](v::null_t) {
|
||||
peer->session().api().request(MTPaccount_ReportPeer(
|
||||
peer->input,
|
||||
ReasonToTL(reason),
|
||||
MTP_string(comment)
|
||||
)).done(std::move(done)).send();
|
||||
}, [&](const MessageIdsList &ids) {
|
||||
auto apiIds = QVector<MTPint>();
|
||||
apiIds.reserve(ids.size());
|
||||
for (const auto &fullId : ids) {
|
||||
apiIds.push_back(MTP_int(fullId.msg));
|
||||
}).send();
|
||||
}
|
||||
|
||||
auto CreateReportMessagesOrStoriesCallback(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<PeerData*> peer)
|
||||
-> Fn<void(Data::ReportInput, Fn<void(ReportResult)>)> {
|
||||
using TLChoose = MTPDreportResultChooseOption;
|
||||
using TLAddComment = MTPDreportResultAddComment;
|
||||
using TLReported = MTPDreportResultReported;
|
||||
using Result = ReportResult;
|
||||
|
||||
struct State final {
|
||||
#ifdef _DEBUG
|
||||
~State() {
|
||||
qDebug() << "Messages or Stories Report ~State().";
|
||||
}
|
||||
peer->session().api().request(MTPmessages_Report(
|
||||
peer->input,
|
||||
MTP_vector<MTPint>(apiIds),
|
||||
ReasonToTL(reason),
|
||||
MTP_string(comment)
|
||||
)).done(std::move(done)).send();
|
||||
}, [&](not_null<PhotoData*> photo) {
|
||||
peer->session().api().request(MTPaccount_ReportProfilePhoto(
|
||||
peer->input,
|
||||
photo->mtpInput(),
|
||||
ReasonToTL(reason),
|
||||
MTP_string(comment)
|
||||
)).done(std::move(done)).send();
|
||||
}, [&](StoryId id) {
|
||||
peer->session().api().request(MTPstories_Report(
|
||||
peer->input,
|
||||
MTP_vector<MTPint>(1, MTP_int(id)),
|
||||
ReasonToTL(reason),
|
||||
MTP_string(comment)
|
||||
)).done(std::move(done)).send();
|
||||
});
|
||||
#endif
|
||||
mtpRequestId requestId = 0;
|
||||
};
|
||||
const auto state = std::make_shared<State>();
|
||||
|
||||
return [=](
|
||||
Data::ReportInput reportInput,
|
||||
Fn<void(Result)> done) {
|
||||
auto apiIds = QVector<MTPint>();
|
||||
apiIds.reserve(reportInput.ids.size() + reportInput.stories.size());
|
||||
for (const auto &id : reportInput.ids) {
|
||||
apiIds.push_back(MTP_int(id));
|
||||
}
|
||||
for (const auto &story : reportInput.stories) {
|
||||
apiIds.push_back(MTP_int(story));
|
||||
}
|
||||
|
||||
const auto received = [=](
|
||||
const MTPReportResult &result,
|
||||
mtpRequestId requestId) {
|
||||
if (state->requestId != requestId) {
|
||||
return;
|
||||
}
|
||||
state->requestId = 0;
|
||||
done(result.match([&](const TLChoose &data) {
|
||||
const auto t = qs(data.vtitle());
|
||||
auto list = Result::Options();
|
||||
list.reserve(data.voptions().v.size());
|
||||
for (const auto &tl : data.voptions().v) {
|
||||
list.emplace_back(Result::Option{
|
||||
.id = tl.data().voption().v,
|
||||
.text = qs(tl.data().vtext()),
|
||||
});
|
||||
}
|
||||
return Result{ .options = std::move(list), .title = t };
|
||||
}, [&](const TLAddComment &data) -> Result {
|
||||
return {
|
||||
.commentOption = ReportResult::CommentOption{
|
||||
.optional = data.is_optional(),
|
||||
.id = data.voption().v,
|
||||
}
|
||||
};
|
||||
}, [&](const TLReported &data) -> Result {
|
||||
return { .successful = true };
|
||||
}));
|
||||
};
|
||||
|
||||
const auto fail = [=](const MTP::Error &error) {
|
||||
state->requestId = 0;
|
||||
done({ .error = error.type() });
|
||||
};
|
||||
|
||||
if (!reportInput.stories.empty()) {
|
||||
state->requestId = peer->session().api().request(
|
||||
MTPstories_Report(
|
||||
peer->input,
|
||||
MTP_vector<MTPint>(apiIds),
|
||||
MTP_bytes(reportInput.optionId),
|
||||
MTP_string(reportInput.comment))
|
||||
).done(received).fail(fail).send();
|
||||
} else {
|
||||
state->requestId = peer->session().api().request(
|
||||
MTPmessages_Report(
|
||||
peer->input,
|
||||
MTP_vector<MTPint>(apiIds),
|
||||
MTP_bytes(reportInput.optionId),
|
||||
MTP_string(reportInput.comment))
|
||||
).done(received).fail(fail).send();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
class HistoryItem;
|
||||
class PeerData;
|
||||
class PhotoData;
|
||||
|
||||
@@ -15,17 +16,41 @@ class Show;
|
||||
enum class ReportReason;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Data {
|
||||
struct ReportInput;
|
||||
} // namespace Data
|
||||
|
||||
namespace Api {
|
||||
|
||||
void SendReport(
|
||||
struct ReportResult final {
|
||||
using Id = QByteArray;
|
||||
struct Option final {
|
||||
Id id = 0;
|
||||
QString text;
|
||||
};
|
||||
using Options = std::vector<Option>;
|
||||
Options options;
|
||||
QString title;
|
||||
QString error;
|
||||
QString comment;
|
||||
struct CommentOption {
|
||||
bool optional = false;
|
||||
Id id = 0;
|
||||
};
|
||||
std::optional<CommentOption> commentOption;
|
||||
bool successful = false;
|
||||
};
|
||||
|
||||
void SendPhotoReport(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
Ui::ReportReason reason,
|
||||
const QString &comment,
|
||||
std::variant<
|
||||
v::null_t,
|
||||
MessageIdsList,
|
||||
not_null<PhotoData*>,
|
||||
StoryId> data);
|
||||
not_null<PhotoData*> photo);
|
||||
|
||||
[[nodiscard]] auto CreateReportMessagesOrStoriesCallback(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<PeerData*> peer)
|
||||
-> Fn<void(Data::ReportInput, Fn<void(ReportResult)>)>;
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -44,7 +44,10 @@ void InnerFillMessagePostFlags(
|
||||
if (ShouldSendSilent(peer, options)) {
|
||||
flags |= MessageFlag::Silent;
|
||||
}
|
||||
if (!peer->amAnonymous()) {
|
||||
if (!peer->amAnonymous()
|
||||
|| (!peer->isBroadcast()
|
||||
&& options.sendAs
|
||||
&& options.sendAs != peer)) {
|
||||
flags |= MessageFlag::HasFromId;
|
||||
}
|
||||
const auto channel = peer->asBroadcast();
|
||||
@@ -453,6 +456,7 @@ void SendConfirmedFile(
|
||||
not_null<Main::Session*> session,
|
||||
const std::shared_ptr<FilePrepareResult> &file) {
|
||||
const auto isEditing = (file->type != SendMediaType::Audio)
|
||||
&& (file->type != SendMediaType::Round)
|
||||
&& (file->to.replaceMediaOf != 0);
|
||||
const auto newId = FullMsgId(
|
||||
file->to.peer,
|
||||
@@ -522,7 +526,8 @@ void SendConfirmedFile(
|
||||
// Shortcut messages have no 'edited' badge.
|
||||
flags |= MessageFlag::HideEdited;
|
||||
}
|
||||
if (file->type == SendMediaType::Audio) {
|
||||
if (file->type == SendMediaType::Audio
|
||||
|| file->type == SendMediaType::Round) {
|
||||
if (!peer->isChannel() || peer->isMegagroup()) {
|
||||
flags |= MessageFlag::MediaIsUnread;
|
||||
}
|
||||
@@ -544,32 +549,28 @@ void SendConfirmedFile(
|
||||
MTP_flags(Flag::f_document
|
||||
| (file->spoiler ? Flag::f_spoiler : Flag())),
|
||||
file->document,
|
||||
MTPDocument(), // alt_document
|
||||
MTPVector<MTPDocument>(), // alt_documents
|
||||
MTPint());
|
||||
} else if (file->type == SendMediaType::Audio) {
|
||||
const auto ttlSeconds = file->to.options.ttlSeconds;
|
||||
const auto isVoice = [&] {
|
||||
return file->document.match([](const MTPDdocumentEmpty &d) {
|
||||
return false;
|
||||
}, [](const MTPDdocument &d) {
|
||||
return ranges::any_of(d.vattributes().v, [&](
|
||||
const MTPDocumentAttribute &attribute) {
|
||||
using Att = MTPDdocumentAttributeAudio;
|
||||
return attribute.match([](const Att &data) -> bool {
|
||||
return data.vflags().v & Att::Flag::f_voice;
|
||||
}, [](const auto &) {
|
||||
return false;
|
||||
});
|
||||
});
|
||||
});
|
||||
}();
|
||||
using Flag = MTPDmessageMediaDocument::Flag;
|
||||
return MTP_messageMediaDocument(
|
||||
MTP_flags(Flag::f_document
|
||||
| (isVoice ? Flag::f_voice : Flag())
|
||||
| Flag::f_voice
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())),
|
||||
file->document,
|
||||
MTPDocument(), // alt_document
|
||||
MTPVector<MTPDocument>(), // alt_documents
|
||||
MTP_int(ttlSeconds));
|
||||
} else if (file->type == SendMediaType::Round) {
|
||||
using Flag = MTPDmessageMediaDocument::Flag;
|
||||
const auto ttlSeconds = file->to.options.ttlSeconds;
|
||||
return MTP_messageMediaDocument(
|
||||
MTP_flags(Flag::f_document
|
||||
| Flag::f_round
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())
|
||||
| (file->spoiler ? Flag::f_spoiler : Flag())),
|
||||
file->document,
|
||||
MTPVector<MTPDocument>(), // alt_documents
|
||||
MTP_int(ttlSeconds));
|
||||
} else {
|
||||
Unexpected("Type in sendFilesConfirmed.");
|
||||
|
||||
@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_stories.h"
|
||||
#include "data/data_story.h"
|
||||
#include "data/data_user.h"
|
||||
#include "history/history.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
@@ -341,6 +342,10 @@ void PublicForwards::request(
|
||||
.token = nextToken,
|
||||
});
|
||||
};
|
||||
const auto processFail = [=] {
|
||||
_requestId = 0;
|
||||
done({});
|
||||
};
|
||||
|
||||
constexpr auto kLimit = tl::make_int(100);
|
||||
if (_fullId.messageId) {
|
||||
@@ -349,14 +354,14 @@ void PublicForwards::request(
|
||||
MTP_int(_fullId.messageId.msg),
|
||||
MTP_string(token),
|
||||
kLimit
|
||||
)).done(processResult).fail([=] { _requestId = 0; }).send();
|
||||
)).done(processResult).fail(processFail).send();
|
||||
} else if (_fullId.storyId) {
|
||||
_requestId = makeRequest(MTPstats_GetStoryPublicForwards(
|
||||
channel->input,
|
||||
MTP_int(_fullId.storyId.story),
|
||||
MTP_string(token),
|
||||
kLimit
|
||||
)).done(processResult).fail([=] { _requestId = 0; }).send();
|
||||
)).done(processResult).fail(processFail).send();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,7 +386,7 @@ Data::PublicForwardsSlice MessageStatistics::firstSlice() const {
|
||||
}
|
||||
|
||||
void MessageStatistics::request(Fn<void(Data::MessageStatistics)> done) {
|
||||
if (channel()->isMegagroup()) {
|
||||
if (channel()->isMegagroup() && !_storyId) {
|
||||
return;
|
||||
}
|
||||
const auto requestFirstPublicForwards = [=](
|
||||
@@ -571,13 +576,22 @@ rpl::producer<rpl::no_value, QString> Boosts::request() {
|
||||
_boostStatus.prepaidGiveaway = ranges::views::all(
|
||||
data.vprepaid_giveaways()->v
|
||||
) | ranges::views::transform([](const MTPPrepaidGiveaway &r) {
|
||||
return Data::BoostPrepaidGiveaway{
|
||||
.months = r.data().vmonths().v,
|
||||
.id = r.data().vid().v,
|
||||
.quantity = r.data().vquantity().v,
|
||||
.date = QDateTime::fromSecsSinceEpoch(
|
||||
r.data().vdate().v),
|
||||
};
|
||||
return r.match([&](const MTPDprepaidGiveaway &data) {
|
||||
return Data::BoostPrepaidGiveaway{
|
||||
.date = base::unixtime::parse(data.vdate().v),
|
||||
.id = data.vid().v,
|
||||
.months = data.vmonths().v,
|
||||
.quantity = data.vquantity().v,
|
||||
};
|
||||
}, [&](const MTPDprepaidStarsGiveaway &data) {
|
||||
return Data::BoostPrepaidGiveaway{
|
||||
.date = base::unixtime::parse(data.vdate().v),
|
||||
.id = data.vid().v,
|
||||
.credits = data.vstars().v,
|
||||
.quantity = data.vquantity().v,
|
||||
.boosts = data.vboosts().v,
|
||||
};
|
||||
});
|
||||
}) | ranges::to_vector;
|
||||
}
|
||||
|
||||
@@ -635,19 +649,21 @@ void Boosts::requestBoosts(
|
||||
}
|
||||
: Data::GiftCodeLink();
|
||||
list.push_back({
|
||||
data.is_gift(),
|
||||
data.is_giveaway(),
|
||||
data.is_unclaimed(),
|
||||
qs(data.vid()),
|
||||
data.vuser_id().value_or_empty(),
|
||||
data.vgiveaway_msg_id()
|
||||
.id = qs(data.vid()),
|
||||
.userId = UserId(data.vuser_id().value_or_empty()),
|
||||
.giveawayMessage = data.vgiveaway_msg_id()
|
||||
? FullMsgId{ _peer->id, data.vgiveaway_msg_id()->v }
|
||||
: FullMsgId(),
|
||||
QDateTime::fromSecsSinceEpoch(data.vdate().v),
|
||||
QDateTime::fromSecsSinceEpoch(data.vexpires().v),
|
||||
(data.vexpires().v - data.vdate().v) / kMonthsDivider,
|
||||
std::move(giftCodeLink),
|
||||
data.vmultiplier().value_or_empty(),
|
||||
.date = base::unixtime::parse(data.vdate().v),
|
||||
.expiresAt = base::unixtime::parse(data.vexpires().v),
|
||||
.expiresAfterMonths = ((data.vexpires().v - data.vdate().v)
|
||||
/ kMonthsDivider),
|
||||
.giftCodeLink = std::move(giftCodeLink),
|
||||
.multiplier = data.vmultiplier().value_or_empty(),
|
||||
.credits = data.vstars().value_or_empty(),
|
||||
.isGift = data.is_gift(),
|
||||
.isGiveaway = data.is_giveaway(),
|
||||
.isUnclaimed = data.is_unclaimed(),
|
||||
});
|
||||
}
|
||||
done(Data::BoostsListSlice{
|
||||
@@ -670,17 +686,18 @@ Data::BoostStatus Boosts::boostStatus() const {
|
||||
return _boostStatus;
|
||||
}
|
||||
|
||||
ChannelEarnStatistics::ChannelEarnStatistics(not_null<ChannelData*> channel)
|
||||
: StatisticsRequestSender(channel) {
|
||||
EarnStatistics::EarnStatistics(not_null<PeerData*> peer)
|
||||
: StatisticsRequestSender(peer)
|
||||
, _isUser(peer->isUser()) {
|
||||
}
|
||||
|
||||
rpl::producer<rpl::no_value, QString> ChannelEarnStatistics::request() {
|
||||
rpl::producer<rpl::no_value, QString> EarnStatistics::request() {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
makeRequest(MTPstats_GetBroadcastRevenueStats(
|
||||
MTP_flags(0),
|
||||
channel()->inputChannel
|
||||
(_isUser ? user()->input : channel()->input)
|
||||
)).done([=](const MTPstats_BroadcastRevenueStats &result) {
|
||||
const auto &data = result.data();
|
||||
const auto &balances = data.vbalances().data();
|
||||
@@ -697,18 +714,22 @@ rpl::producer<rpl::no_value, QString> ChannelEarnStatistics::request() {
|
||||
requestHistory({}, [=](Data::EarnHistorySlice &&slice) {
|
||||
_data.firstHistorySlice = std::move(slice);
|
||||
|
||||
api().request(
|
||||
MTPchannels_GetFullChannel(channel()->inputChannel)
|
||||
).done([=](const MTPmessages_ChatFull &result) {
|
||||
result.data().vfull_chat().match([&](
|
||||
const MTPDchannelFull &d) {
|
||||
_data.switchedOff = d.is_restricted_sponsored();
|
||||
}, [](const auto &) {
|
||||
});
|
||||
if (!_isUser) {
|
||||
api().request(
|
||||
MTPchannels_GetFullChannel(channel()->inputChannel)
|
||||
).done([=](const MTPmessages_ChatFull &result) {
|
||||
result.data().vfull_chat().match([&](
|
||||
const MTPDchannelFull &d) {
|
||||
_data.switchedOff = d.is_restricted_sponsored();
|
||||
}, [](const auto &) {
|
||||
});
|
||||
consumer.put_done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
} else {
|
||||
consumer.put_done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
}
|
||||
});
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
@@ -718,7 +739,7 @@ rpl::producer<rpl::no_value, QString> ChannelEarnStatistics::request() {
|
||||
};
|
||||
}
|
||||
|
||||
void ChannelEarnStatistics::requestHistory(
|
||||
void EarnStatistics::requestHistory(
|
||||
const Data::EarnHistorySlice::OffsetToken &token,
|
||||
Fn<void(Data::EarnHistorySlice)> done) {
|
||||
if (_requestId) {
|
||||
@@ -727,7 +748,7 @@ void ChannelEarnStatistics::requestHistory(
|
||||
constexpr auto kTlFirstSlice = tl::make_int(kFirstSlice);
|
||||
constexpr auto kTlLimit = tl::make_int(kLimit);
|
||||
_requestId = api().request(MTPstats_GetBroadcastRevenueTransactions(
|
||||
channel()->inputChannel,
|
||||
(_isUser ? user()->input : channel()->input),
|
||||
MTP_int(token),
|
||||
(!token) ? kTlFirstSlice : kTlLimit
|
||||
)).done([=](const MTPstats_BroadcastRevenueTransactions &result) {
|
||||
@@ -788,7 +809,7 @@ void ChannelEarnStatistics::requestHistory(
|
||||
}).send();
|
||||
}
|
||||
|
||||
Data::EarnStatistics ChannelEarnStatistics::data() const {
|
||||
Data::EarnStatistics EarnStatistics::data() const {
|
||||
return _data;
|
||||
}
|
||||
|
||||
|
||||
@@ -79,9 +79,9 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class ChannelEarnStatistics final : public StatisticsRequestSender {
|
||||
class EarnStatistics final : public StatisticsRequestSender {
|
||||
public:
|
||||
explicit ChannelEarnStatistics(not_null<ChannelData*> channel);
|
||||
explicit EarnStatistics(not_null<PeerData*> peer);
|
||||
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> request();
|
||||
void requestHistory(
|
||||
@@ -94,6 +94,7 @@ public:
|
||||
static constexpr auto kLimit = int(10);
|
||||
|
||||
private:
|
||||
const bool _isUser = false;
|
||||
Data::EarnStatistics _data;
|
||||
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_authorizations.h"
|
||||
#include "api/api_user_names.h"
|
||||
#include "api/api_chat_participants.h"
|
||||
#include "api/api_global_privacy.h"
|
||||
#include "api/api_ringtones.h"
|
||||
#include "api/api_text_entities.h"
|
||||
#include "api/api_user_privacy.h"
|
||||
@@ -315,6 +316,9 @@ void Updates::feedUpdateVector(
|
||||
} else if (policy == SkipUpdatePolicy::SkipExceptGroupCallParticipants) {
|
||||
return;
|
||||
}
|
||||
if (policy == SkipUpdatePolicy::SkipNone) {
|
||||
applyConvertToScheduledOnSend(updates);
|
||||
}
|
||||
for (const auto &entry : std::as_const(list)) {
|
||||
const auto type = entry.type();
|
||||
if ((policy == SkipUpdatePolicy::SkipMessageIds
|
||||
@@ -328,6 +332,15 @@ void Updates::feedUpdateVector(
|
||||
session().data().sendHistoryChangeNotifications();
|
||||
}
|
||||
|
||||
void Updates::checkForSentToScheduled(const MTPUpdates &updates) {
|
||||
updates.match([&](const MTPDupdates &data) {
|
||||
applyConvertToScheduledOnSend(data.vupdates(), true);
|
||||
}, [&](const MTPDupdatesCombined &data) {
|
||||
applyConvertToScheduledOnSend(data.vupdates(), true);
|
||||
}, [](const auto &) {
|
||||
});
|
||||
}
|
||||
|
||||
void Updates::feedMessageIds(const MTPVector<MTPUpdate> &updates) {
|
||||
for (const auto &update : updates.v) {
|
||||
if (update.type() == mtpc_updateMessageID) {
|
||||
@@ -416,13 +429,12 @@ void Updates::channelDifferenceDone(
|
||||
"{ good - after not final channelDifference was received }%1"
|
||||
).arg(_session->mtp().isTestMode() ? " TESTMODE" : ""));
|
||||
getChannelDifference(channel);
|
||||
} else if (ranges::contains(
|
||||
_activeChats,
|
||||
channel,
|
||||
[](const auto &pair) { return pair.second.peer; })) {
|
||||
channel->ptsWaitingForShortPoll(timeout
|
||||
} else if (inActiveChats(channel)) {
|
||||
channel->ptsSetWaitingForShortPoll(timeout
|
||||
? (timeout * crl::time(1000))
|
||||
: kWaitForChannelGetDifference);
|
||||
} else {
|
||||
channel->ptsSetWaitingForShortPoll(-1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -432,6 +444,7 @@ void Updates::feedChannelDifference(
|
||||
session().data().processChats(data.vchats());
|
||||
|
||||
_handlingChannelDifference = true;
|
||||
applyConvertToScheduledOnSend(data.vother_updates());
|
||||
feedMessageIds(data.vother_updates());
|
||||
session().data().processMessages(
|
||||
data.vnew_messages(),
|
||||
@@ -596,6 +609,7 @@ void Updates::feedDifference(
|
||||
Core::App().checkAutoLock();
|
||||
session().data().processUsers(users);
|
||||
session().data().processChats(chats);
|
||||
applyConvertToScheduledOnSend(other);
|
||||
feedMessageIds(other);
|
||||
session().data().processMessages(msgs, NewMessageType::Unread);
|
||||
feedUpdateVector(other, SkipUpdatePolicy::SkipMessageIds);
|
||||
@@ -654,6 +668,7 @@ void Updates::getDifferenceAfterFail() {
|
||||
wait = wait ? std::min(wait, i->second - now) : (i->second - now);
|
||||
++i;
|
||||
} else {
|
||||
i->first->ptsSetRequesting(false);
|
||||
getChannelDifference(i->first, ChannelDifferenceRequest::AfterFail);
|
||||
i = _whenGetDiffAfterFail.erase(i);
|
||||
}
|
||||
@@ -702,7 +717,9 @@ void Updates::getChannelDifference(
|
||||
_whenGetDiffByPts.remove(channel);
|
||||
}
|
||||
|
||||
if (!channel->ptsInited() || channel->ptsRequesting()) return;
|
||||
if (!channel->ptsInited() || channel->ptsRequesting()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (from != ChannelDifferenceRequest::AfterFail) {
|
||||
_whenGetDiffAfterFail.remove(channel);
|
||||
@@ -739,16 +756,32 @@ void Updates::addActiveChat(rpl::producer<PeerData*> chat) {
|
||||
std::move(
|
||||
chat
|
||||
) | rpl::start_with_next_done([=](PeerData *peer) {
|
||||
_activeChats[key].peer = peer;
|
||||
if (const auto channel = peer ? peer->asChannel() : nullptr) {
|
||||
channel->ptsWaitingForShortPoll(
|
||||
kWaitForChannelGetDifference);
|
||||
auto &active = _activeChats[key];
|
||||
const auto was = active.peer;
|
||||
if (was != peer) {
|
||||
active.peer = peer;
|
||||
if (const auto channel = was ? was->asChannel() : nullptr) {
|
||||
if (!inActiveChats(channel)) {
|
||||
channel->ptsSetWaitingForShortPoll(-1);
|
||||
}
|
||||
}
|
||||
if (const auto channel = peer ? peer->asChannel() : nullptr) {
|
||||
channel->ptsSetWaitingForShortPoll(
|
||||
kWaitForChannelGetDifference);
|
||||
}
|
||||
}
|
||||
}, [=] {
|
||||
_activeChats.erase(key);
|
||||
}, _activeChats[key].lifetime);
|
||||
}
|
||||
|
||||
bool Updates::inActiveChats(not_null<PeerData*> peer) const {
|
||||
return ranges::contains(
|
||||
_activeChats,
|
||||
peer.get(),
|
||||
[](const auto &pair) { return pair.second.peer; });
|
||||
}
|
||||
|
||||
void Updates::requestChannelRangeDifference(not_null<History*> history) {
|
||||
Expects(history->peer->isChannel());
|
||||
|
||||
@@ -862,6 +895,51 @@ void Updates::mtpUpdateReceived(const MTPUpdates &updates) {
|
||||
}
|
||||
}
|
||||
|
||||
void Updates::applyConvertToScheduledOnSend(
|
||||
const MTPVector<MTPUpdate> &other,
|
||||
bool skipScheduledCheck) {
|
||||
for (const auto &update : other.v) {
|
||||
update.match([&](const MTPDupdateNewScheduledMessage &data) {
|
||||
const auto &message = data.vmessage();
|
||||
const auto id = IdFromMessage(message);
|
||||
const auto scheduledMessages = &_session->scheduledMessages();
|
||||
const auto scheduledId = scheduledMessages->localMessageId(id);
|
||||
for (const auto &updateId : other.v) {
|
||||
updateId.match([&](const MTPDupdateMessageID &dataId) {
|
||||
if (dataId.vid().v == id) {
|
||||
auto &owner = session().data();
|
||||
if (skipScheduledCheck) {
|
||||
const auto peerId = PeerFromMessage(message);
|
||||
const auto history = owner.historyLoaded(peerId);
|
||||
if (history) {
|
||||
_session->data().sentToScheduled({
|
||||
.history = history,
|
||||
.scheduledId = scheduledId,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
const auto rand = dataId.vrandom_id().v;
|
||||
const auto localId = owner.messageIdByRandomId(rand);
|
||||
if (const auto local = owner.message(localId)) {
|
||||
if (!local->isScheduled()) {
|
||||
_session->data().sentToScheduled({
|
||||
.history = local->history(),
|
||||
.scheduledId = scheduledId,
|
||||
});
|
||||
|
||||
// We've sent a non-scheduled message,
|
||||
// but it was converted to a scheduled.
|
||||
local->destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [](const auto &) {});
|
||||
}
|
||||
}, [](const auto &) {});
|
||||
}
|
||||
}
|
||||
|
||||
void Updates::applyGroupCallParticipantUpdates(const MTPUpdates &updates) {
|
||||
updates.match([&](const MTPDupdates &data) {
|
||||
session().data().processUsers(data.vusers());
|
||||
@@ -1553,6 +1631,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
}
|
||||
if (channel && !_handlingChannelDifference) {
|
||||
if (channel->ptsRequesting()) { // skip global updates while getting channel difference
|
||||
MTP_LOG(0, ("Skipping new channel message because getting the difference."));
|
||||
return;
|
||||
}
|
||||
channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
|
||||
@@ -1645,6 +1724,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
|
||||
if (channel && !_handlingChannelDifference) {
|
||||
if (channel->ptsRequesting()) { // skip global updates while getting channel difference
|
||||
MTP_LOG(0, ("Skipping channel message edit because getting the difference."));
|
||||
return;
|
||||
} else {
|
||||
channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
|
||||
@@ -1660,6 +1740,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
|
||||
if (channel && !_handlingChannelDifference) {
|
||||
if (channel->ptsRequesting()) { // skip global updates while getting channel difference
|
||||
MTP_LOG(0, ("Skipping pinned channel messages because getting the difference."));
|
||||
return;
|
||||
} else {
|
||||
channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
|
||||
@@ -1774,6 +1855,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
|
||||
if (channel && !_handlingChannelDifference) {
|
||||
if (channel->ptsRequesting()) { // skip global updates while getting channel difference
|
||||
MTP_LOG(0, ("Skipping delete channel messages because getting the difference."));
|
||||
return;
|
||||
}
|
||||
channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
|
||||
@@ -1837,6 +1919,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
auto channel = session().data().channelLoaded(d.vchannel_id());
|
||||
if (channel && !_handlingChannelDifference) {
|
||||
if (channel->ptsRequesting()) { // skip global updates while getting channel difference
|
||||
MTP_LOG(0, ("Skipping channel web page update because getting the difference."));
|
||||
return;
|
||||
} else {
|
||||
channel->ptsUpdateAndApply(d.vpts().v, d.vpts_count().v, update);
|
||||
@@ -2622,6 +2705,12 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
_session->credits().apply(data);
|
||||
} break;
|
||||
|
||||
case mtpc_updatePaidReactionPrivacy: {
|
||||
const auto &data = update.c_updatePaidReactionPrivacy();
|
||||
_session->api().globalPrivacy().updatePaidReactionAnonymous(
|
||||
mtpIsTrue(data.vprivate()));
|
||||
} break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,8 @@ public:
|
||||
void applyUpdatesNoPtsCheck(const MTPUpdates &updates);
|
||||
void applyUpdateNoPtsCheck(const MTPUpdate &update);
|
||||
|
||||
void checkForSentToScheduled(const MTPUpdates &updates);
|
||||
|
||||
[[nodiscard]] int32 pts() const;
|
||||
|
||||
void updateOnline(crl::time lastNonIdleTime = 0);
|
||||
@@ -63,6 +65,7 @@ public:
|
||||
void requestChannelRangeDifference(not_null<History*> history);
|
||||
|
||||
void addActiveChat(rpl::producer<PeerData*> chat);
|
||||
[[nodiscard]] bool inActiveChats(not_null<PeerData*> peer) const;
|
||||
|
||||
private:
|
||||
enum class ChannelDifferenceRequest {
|
||||
@@ -130,6 +133,9 @@ private:
|
||||
// Doesn't call sendHistoryChangeNotifications itself.
|
||||
void feedUpdate(const MTPUpdate &update);
|
||||
|
||||
void applyConvertToScheduledOnSend(
|
||||
const MTPVector<MTPUpdate> &other,
|
||||
bool skipScheduledCheck = false);
|
||||
void applyGroupCallParticipantUpdates(const MTPUpdates &updates);
|
||||
|
||||
bool whenGetDiffChanged(
|
||||
|
||||
@@ -69,6 +69,9 @@ TLInputRules RulesToTL(const UserPrivacy::Rule &rule) {
|
||||
if (rule.always.premiums && (rule.option != Option::Everyone)) {
|
||||
result.push_back(MTP_inputPrivacyValueAllowPremium());
|
||||
}
|
||||
if (rule.always.miniapps && (rule.option != Option::Everyone)) {
|
||||
result.push_back(MTP_inputPrivacyValueAllowBots());
|
||||
}
|
||||
}
|
||||
if (!rule.ignoreNever) {
|
||||
const auto users = collectInputUsers(rule.never);
|
||||
@@ -83,6 +86,9 @@ TLInputRules RulesToTL(const UserPrivacy::Rule &rule) {
|
||||
MTP_inputPrivacyValueDisallowChatParticipants(
|
||||
MTP_vector<MTPlong>(chats)));
|
||||
}
|
||||
if (rule.never.miniapps && (rule.option != Option::Nobody)) {
|
||||
result.push_back(MTP_inputPrivacyValueDisallowBots());
|
||||
}
|
||||
}
|
||||
result.push_back([&] {
|
||||
switch (rule.option) {
|
||||
@@ -124,6 +130,10 @@ UserPrivacy::Rule TLToRules(const TLRules &rules, Data::Session &owner) {
|
||||
setOption(Option::CloseFriends);
|
||||
}, [&](const MTPDprivacyValueAllowPremium &) {
|
||||
result.always.premiums = true;
|
||||
}, [&](const MTPDprivacyValueAllowBots &) {
|
||||
result.always.miniapps = true;
|
||||
}, [&](const MTPDprivacyValueDisallowBots &) {
|
||||
result.never.miniapps = true;
|
||||
}, [&](const MTPDprivacyValueAllowUsers &data) {
|
||||
const auto &users = data.vusers().v;
|
||||
always.reserve(always.size() + users.size());
|
||||
@@ -199,6 +209,7 @@ MTPInputPrivacyKey KeyToTL(UserPrivacy::Key key) {
|
||||
case Key::Voices: return MTP_inputPrivacyKeyVoiceMessages();
|
||||
case Key::About: return MTP_inputPrivacyKeyAbout();
|
||||
case Key::Birthday: return MTP_inputPrivacyKeyBirthday();
|
||||
case Key::GiftsAutoSave: return MTP_inputPrivacyKeyStarGiftsAutoSave();
|
||||
}
|
||||
Unexpected("Key in Api::UserPrivacy::KetToTL.");
|
||||
}
|
||||
@@ -228,6 +239,8 @@ std::optional<UserPrivacy::Key> TLToKey(mtpTypeId type) {
|
||||
case mtpc_inputPrivacyKeyAbout: return Key::About;
|
||||
case mtpc_privacyKeyBirthday:
|
||||
case mtpc_inputPrivacyKeyBirthday: return Key::Birthday;
|
||||
case mtpc_privacyKeyStarGiftsAutoSave:
|
||||
case mtpc_inputPrivacyKeyStarGiftsAutoSave: return Key::GiftsAutoSave;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ public:
|
||||
Voices,
|
||||
About,
|
||||
Birthday,
|
||||
GiftsAutoSave,
|
||||
};
|
||||
enum class Option {
|
||||
Everyone,
|
||||
@@ -41,6 +42,7 @@ public:
|
||||
struct Exceptions {
|
||||
std::vector<not_null<PeerData*>> peers;
|
||||
bool premiums = false;
|
||||
bool miniapps = false;
|
||||
};
|
||||
struct Rule {
|
||||
Option option = Option::Everyone;
|
||||
|
||||
@@ -214,7 +214,10 @@ struct State {
|
||||
|
||||
[[nodiscard]] QImage GenerateUserpic(Userpic &userpic, int size) {
|
||||
size *= style::DevicePixelRatio();
|
||||
auto result = userpic.peer->generateUserpicImage(userpic.view, size);
|
||||
auto result = PeerData::GenerateUserpicImage(
|
||||
userpic.peer,
|
||||
userpic.view,
|
||||
size);
|
||||
result.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
return result;
|
||||
}
|
||||
@@ -753,5 +756,19 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||
const style::WhoRead &st) {
|
||||
return WhoReacted(item, reaction, context, st, nullptr);
|
||||
}
|
||||
rpl::producer<Ui::WhoReadContent> WhenEdited(
|
||||
not_null<PeerData*> author,
|
||||
TimeId date) {
|
||||
return rpl::single(Ui::WhoReadContent{
|
||||
.participants = { Ui::WhoReadParticipant{
|
||||
.name = author->name(),
|
||||
.date = FormatReadDate(date, QDateTime::currentDateTime()),
|
||||
.id = author->id.value,
|
||||
} },
|
||||
.type = Ui::WhoReadType::Edited,
|
||||
.fullReadCount = 1,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -61,5 +61,8 @@ struct WhoReadList {
|
||||
const Data::ReactionId &reaction,
|
||||
not_null<QWidget*> context, // Cache results for this lifetime.
|
||||
const style::WhoRead &st);
|
||||
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhenEdited(
|
||||
not_null<PeerData*> author,
|
||||
TimeId date);
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -708,7 +708,8 @@ void ApiWrap::finalizeMessageDataRequest(
|
||||
|
||||
QString ApiWrap::exportDirectMessageLink(
|
||||
not_null<HistoryItem*> item,
|
||||
bool inRepliesContext) {
|
||||
bool inRepliesContext,
|
||||
bool forceNonPublicLink) {
|
||||
Expects(item->history()->peer->isChannel());
|
||||
|
||||
const auto itemId = item->fullId();
|
||||
@@ -731,7 +732,7 @@ QString ApiWrap::exportDirectMessageLink(
|
||||
const auto sender = root
|
||||
? root->discussionPostOriginalSender()
|
||||
: nullptr;
|
||||
if (sender && sender->hasUsername()) {
|
||||
if (sender && sender->hasUsername() && !forceNonPublicLink) {
|
||||
// Comment to a public channel.
|
||||
const auto forwarded = root->Get<HistoryMessageForwarded>();
|
||||
linkItemId = forwarded->savedFromMsgId;
|
||||
@@ -747,7 +748,7 @@ QString ApiWrap::exportDirectMessageLink(
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto base = linkChannel->hasUsername()
|
||||
const auto base = (linkChannel->hasUsername() && !forceNonPublicLink)
|
||||
? linkChannel->username()
|
||||
: "c/" + QString::number(peerToChannel(linkChannel->id).bare);
|
||||
const auto post = QString::number(linkItemId.bare);
|
||||
@@ -761,6 +762,7 @@ QString ApiWrap::exportDirectMessageLink(
|
||||
? (QString::number(linkThreadId.bare) + '/' + post)
|
||||
: post);
|
||||
if (linkChannel->hasUsername()
|
||||
&& !forceNonPublicLink
|
||||
&& !linkChannel->isMegagroup()
|
||||
&& !linkCommentId
|
||||
&& !linkThreadId) {
|
||||
@@ -774,6 +776,9 @@ QString ApiWrap::exportDirectMessageLink(
|
||||
}
|
||||
return session().createInternalLinkFull(query);
|
||||
};
|
||||
if (forceNonPublicLink) {
|
||||
return fallback();
|
||||
}
|
||||
const auto i = _unlikelyMessageLinks.find(itemId);
|
||||
const auto current = (i != end(_unlikelyMessageLinks))
|
||||
? i->second
|
||||
@@ -2028,7 +2033,7 @@ void ApiWrap::deleteHistory(
|
||||
}
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
if (!justClear && !revoke) {
|
||||
channel->ptsWaitingForShortPoll(-1);
|
||||
channel->ptsSetWaitingForShortPoll(-1);
|
||||
leaveChannel(channel);
|
||||
} else {
|
||||
if (const auto migrated = peer->migrateFrom()) {
|
||||
@@ -3329,6 +3334,7 @@ void ApiWrap::forwardMessages(
|
||||
}
|
||||
const auto requestType = Data::Histories::RequestType::Send;
|
||||
const auto idsCopy = localIds;
|
||||
const auto scheduled = action.options.scheduled;
|
||||
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
||||
history->sendRequestId = request(MTPmessages_ForwardMessages(
|
||||
MTP_flags(sendFlags),
|
||||
@@ -3341,6 +3347,9 @@ void ApiWrap::forwardMessages(
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, action.options.shortcutId)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
if (!scheduled) {
|
||||
this->updates().checkForSentToScheduled(result);
|
||||
}
|
||||
applyUpdates(result);
|
||||
if (shared && !--shared->requestsLeft) {
|
||||
shared->callback();
|
||||
@@ -3502,6 +3511,7 @@ void ApiWrap::sendVoiceMessage(
|
||||
QByteArray result,
|
||||
VoiceWaveform waveform,
|
||||
crl::time duration,
|
||||
bool video,
|
||||
const SendAction &action) {
|
||||
const auto caption = TextWithTags();
|
||||
const auto to = FileLoadTaskOptions(action);
|
||||
@@ -3510,6 +3520,7 @@ void ApiWrap::sendVoiceMessage(
|
||||
result,
|
||||
duration,
|
||||
waveform,
|
||||
video,
|
||||
to,
|
||||
caption));
|
||||
}
|
||||
@@ -3936,7 +3947,8 @@ void ApiWrap::sendInlineResult(
|
||||
not_null<UserData*> bot,
|
||||
not_null<InlineBots::Result*> data,
|
||||
const SendAction &action,
|
||||
std::optional<MsgId> localMessageId) {
|
||||
std::optional<MsgId> localMessageId,
|
||||
Fn<void(bool)> done) {
|
||||
sendAction(action);
|
||||
|
||||
const auto history = action.history;
|
||||
@@ -4016,11 +4028,17 @@ void ApiWrap::sendInlineResult(
|
||||
history->finishSavingCloudDraft(
|
||||
topicRootId,
|
||||
UnixtimeFromMsgId(response.outerMsgId));
|
||||
if (done) {
|
||||
done(true);
|
||||
}
|
||||
}, [=](const MTP::Error &error, const MTP::Response &response) {
|
||||
sendMessageFail(error, peer, randomId, newId);
|
||||
history->finishSavingCloudDraft(
|
||||
topicRootId,
|
||||
UnixtimeFromMsgId(response.outerMsgId));
|
||||
if (done) {
|
||||
done(false);
|
||||
}
|
||||
});
|
||||
finishForwarding(action);
|
||||
}
|
||||
@@ -4160,8 +4178,10 @@ void ApiWrap::sendMediaWithRandomId(
|
||||
Data::Histories::ReplyToPlaceholder(),
|
||||
(options.price
|
||||
? MTPInputMedia(MTP_inputMediaPaidMedia(
|
||||
MTP_flags(0),
|
||||
MTP_long(options.price),
|
||||
MTP_vector<MTPInputMedia>(1, media)))
|
||||
MTP_vector<MTPInputMedia>(1, media),
|
||||
MTPstring()))
|
||||
: media),
|
||||
MTP_string(caption.text),
|
||||
MTP_long(randomId),
|
||||
@@ -4223,6 +4243,7 @@ void ApiWrap::sendMultiPaidMedia(
|
||||
auto &histories = history->owner().histories();
|
||||
const auto peer = history->peer;
|
||||
const auto itemId = item->fullId();
|
||||
album->sent = true;
|
||||
histories.sendPreparedMessage(
|
||||
history,
|
||||
replyTo,
|
||||
@@ -4232,8 +4253,10 @@ void ApiWrap::sendMultiPaidMedia(
|
||||
peer->input,
|
||||
Data::Histories::ReplyToPlaceholder(),
|
||||
MTP_inputMediaPaidMedia(
|
||||
MTP_flags(0),
|
||||
MTP_long(options.price),
|
||||
MTP_vector<MTPInputMedia>(std::move(medias))),
|
||||
MTP_vector<MTPInputMedia>(std::move(medias)),
|
||||
MTPstring()),
|
||||
MTP_string(caption.text),
|
||||
MTP_long(randomId),
|
||||
MTPReplyMarkup(),
|
||||
@@ -4292,6 +4315,9 @@ void ApiWrap::sendAlbumWithCancelled(
|
||||
}
|
||||
|
||||
void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
||||
if (album->sent) {
|
||||
return;
|
||||
}
|
||||
const auto groupId = album->groupId;
|
||||
if (album->items.empty()) {
|
||||
_sendingAlbums.remove(groupId);
|
||||
@@ -4316,6 +4342,7 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
||||
return;
|
||||
} else if (medias.size() < 2) {
|
||||
const auto &single = medias.front().data();
|
||||
album->sent = true;
|
||||
sendMediaWithRandomId(
|
||||
sample,
|
||||
single.vmedia(),
|
||||
@@ -4342,6 +4369,7 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
||||
| (album->options.invertCaption ? Flag::f_invert_media : Flag(0));
|
||||
auto &histories = history->owner().histories();
|
||||
const auto peer = history->peer;
|
||||
album->sent = true;
|
||||
histories.sendPreparedMessage(
|
||||
history,
|
||||
replyTo,
|
||||
|
||||
@@ -164,7 +164,8 @@ public:
|
||||
void requestMessageData(PeerData *peer, MsgId msgId, Fn<void()> done);
|
||||
QString exportDirectMessageLink(
|
||||
not_null<HistoryItem*> item,
|
||||
bool inRepliesContext);
|
||||
bool inRepliesContext,
|
||||
bool forceNonPublicLink = false);
|
||||
QString exportDirectStoryLink(not_null<Data::Story*> item);
|
||||
|
||||
void requestContacts();
|
||||
@@ -317,6 +318,7 @@ public:
|
||||
QByteArray result,
|
||||
VoiceWaveform waveform,
|
||||
crl::time duration,
|
||||
bool video,
|
||||
const SendAction &action);
|
||||
void sendFiles(
|
||||
Ui::PreparedList &&list,
|
||||
@@ -359,7 +361,8 @@ public:
|
||||
not_null<UserData*> bot,
|
||||
not_null<InlineBots::Result*> data,
|
||||
const SendAction &action,
|
||||
std::optional<MsgId> localMessageId);
|
||||
std::optional<MsgId> localMessageId,
|
||||
Fn<void(bool)> done = nullptr);
|
||||
void sendMessageFail(
|
||||
const MTP::Error &error,
|
||||
not_null<PeerData*> peer,
|
||||
|
||||
@@ -217,7 +217,9 @@ void ShowAddParticipantsError(
|
||||
channel,
|
||||
user,
|
||||
ChatAdminRightsInfo(),
|
||||
QString());
|
||||
QString(),
|
||||
0,
|
||||
nullptr);
|
||||
box->setSaveCallback(saveCallback);
|
||||
*weak = box.data();
|
||||
show->showBox(std::move(box));
|
||||
|
||||
@@ -154,9 +154,7 @@ contactsSortButton: IconButton(defaultIconButton) {
|
||||
iconPosition: point(10px, -1px);
|
||||
rippleAreaPosition: point(1px, 6px);
|
||||
rippleAreaSize: 42px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
contactsSortOnlineIcon: icon{{ "contacts_online", boxTitleCloseFg }};
|
||||
contactsSortOnlineIconOver: icon{{ "contacts_online", boxTitleCloseFgOver }};
|
||||
@@ -416,9 +414,7 @@ calendarPrevious: IconButton {
|
||||
|
||||
rippleAreaPosition: point(2px, 2px);
|
||||
rippleAreaSize: 44px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
calendarPreviousDisabled: icon {{ "calendar_down-flip_vertical", menuIconFg }};
|
||||
calendarNext: IconButton(calendarPrevious) {
|
||||
@@ -597,8 +593,6 @@ rightsHeaderLabel: FlatLabel(boxLabel) {
|
||||
}
|
||||
textFg: windowActiveTextFg;
|
||||
}
|
||||
rightsUntilMargin: margins(0px, 8px, 0px, 20px);
|
||||
rightsRankMargin: margins(0px, 7px, 0px, 20px);
|
||||
|
||||
groupStickersRemove: defaultMultiSelectSearchCancel;
|
||||
groupStickersRemovePosition: point(6px, 6px);
|
||||
@@ -618,9 +612,7 @@ proxyTryIPv6Padding: margins(22px, 8px, 22px, 5px);
|
||||
proxyRowPadding: margins(22px, 8px, 8px, 8px);
|
||||
proxyRowIconSkip: 32px;
|
||||
proxyRowSkip: 2px;
|
||||
proxyRowRipple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
proxyRowRipple: defaultRippleAnimationBgOver;
|
||||
proxyRowTitleFg: windowFg;
|
||||
proxyRowTitlePalette: TextPalette(defaultTextPalette) {
|
||||
linkFg: windowSubTextFg;
|
||||
@@ -685,9 +677,7 @@ themesMenuToggle: IconButton(defaultIconButton) {
|
||||
|
||||
rippleAreaPosition: point(4px, 4px);
|
||||
rippleAreaSize: 36px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
themesMenuPosition: point(-2px, 25px);
|
||||
|
||||
@@ -740,9 +730,7 @@ createPollOptionRemove: CrossButton {
|
||||
|
||||
duration: 150;
|
||||
loadingPeriod: 1000;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
createPollOptionRemovePosition: point(11px, 9px);
|
||||
createPollOptionEmojiPositionSkip: 4px;
|
||||
@@ -787,7 +775,7 @@ backgroundConfirmPadding: margins(24px, 16px, 24px, 16px);
|
||||
backgroundConfirm: RoundButton(defaultActiveButton) {
|
||||
height: 44px;
|
||||
textTop: 12px;
|
||||
font: font(13px semibold);
|
||||
style: semiboldTextStyle;
|
||||
}
|
||||
backgroundConfirmCancel: RoundButton(backgroundConfirm) {
|
||||
textFg: mediaviewSaveMsgFg;
|
||||
@@ -799,7 +787,7 @@ backgroundConfirmCancel: RoundButton(backgroundConfirm) {
|
||||
|
||||
height: 44px;
|
||||
textTop: 12px;
|
||||
font: font(13px semibold);
|
||||
style: semiboldTextStyle;
|
||||
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: shadowFg;
|
||||
@@ -890,6 +878,13 @@ peerListWithInviteViaLink: PeerList(peerListBox) {
|
||||
peerListSingleRow: PeerList(peerListBox) {
|
||||
padding: margins(0px, 0px, 0px, 0px);
|
||||
}
|
||||
peerListSmallSkips: PeerList(peerListBox) {
|
||||
padding: margins(
|
||||
0px,
|
||||
defaultVerticalListSkip,
|
||||
0px,
|
||||
defaultVerticalListSkip);
|
||||
}
|
||||
|
||||
scheduleHeight: 95px;
|
||||
scheduleDateTop: 38px;
|
||||
@@ -951,11 +946,9 @@ sponsoredUrlButton: RoundButton(defaultActiveButton) {
|
||||
textFg: historyLinkInFg;
|
||||
textFgOver: historyLinkInFg;
|
||||
textTop: 7px;
|
||||
font: normalFont;
|
||||
style: defaultTextStyle;
|
||||
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
|
||||
requestPeerRestriction: FlatLabel(defaultFlatLabel) {
|
||||
@@ -1122,3 +1115,10 @@ moderateBoxDividerLabel: FlatLabel(boxDividerLabel) {
|
||||
selectLinkFg: windowActiveTextFg;
|
||||
}
|
||||
}
|
||||
|
||||
profileQrFont: font(fsize bold);
|
||||
profileQrCenterSize: 34px;
|
||||
profileQrBackgroundRadius: 12px;
|
||||
profileQrIcon: icon{{ "qr_mini", windowActiveTextFg }};
|
||||
profileQrBackgroundMargins: margins(36px, 12px, 36px, 12px);
|
||||
profileQrBackgroundPadding: margins(0px, 24px, 0px, 24px);
|
||||
|
||||
@@ -96,6 +96,9 @@ void ChangeFilterById(
|
||||
Ui::Text::WithEntities));
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
LOG(("API Error: failed to %1 a dialog to a folder. %2")
|
||||
.arg(add ? u"add"_q : u"remove"_q)
|
||||
.arg(error.type()));
|
||||
// Revert filter on fail.
|
||||
history->owner().chatsFilters().set(was);
|
||||
}).send();
|
||||
|
||||
@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/premium_limits_box.h"
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "chat_helpers/emoji_suggestions_widget.h"
|
||||
#include "chat_helpers/field_autocomplete.h"
|
||||
#include "chat_helpers/message_field.h"
|
||||
#include "chat_helpers/tabbed_panel.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
@@ -237,7 +238,7 @@ EditCaptionBox::EditCaptionBox(
|
||||
Fn<void()> saved)
|
||||
: _controller(controller)
|
||||
, _historyItem(item)
|
||||
, _isAllowedEditMedia(item->media() && item->media()->allowsEditMedia())
|
||||
, _isAllowedEditMedia(item->allowsEditMedia())
|
||||
, _albumType(ComputeAlbumType(item))
|
||||
, _controls(base::make_unique_q<Ui::VerticalLayout>(this))
|
||||
, _scroll(base::make_unique_q<Ui::ScrollArea>(this, st::boxScroll))
|
||||
@@ -252,8 +253,8 @@ EditCaptionBox::EditCaptionBox(
|
||||
, _initialText(std::move(text))
|
||||
, _initialList(std::move(list))
|
||||
, _saved(std::move(saved)) {
|
||||
Expects(item->media() != nullptr);
|
||||
Expects(item->media()->allowsEditCaption());
|
||||
Expects(!_initialList.files.empty());
|
||||
Expects(!item->media() || item->media()->allowsEditCaption());
|
||||
|
||||
_mediaEditManager.start(item, spoilered, invertCaption);
|
||||
|
||||
@@ -371,6 +372,14 @@ void EditCaptionBox::StartPhotoEdit(
|
||||
});
|
||||
}
|
||||
|
||||
void EditCaptionBox::showFinished() {
|
||||
if (const auto raw = _autocomplete.get()) {
|
||||
InvokeQueued(raw, [=] {
|
||||
raw->raise();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void EditCaptionBox::prepare() {
|
||||
const auto button = addButton(tr::lng_settings_save(), [=] { save(); });
|
||||
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||
@@ -413,7 +422,8 @@ void EditCaptionBox::prepare() {
|
||||
setInitialText();
|
||||
|
||||
if (!setPreparedList(std::move(_initialList))) {
|
||||
rebuildPreview();
|
||||
crl::on_main(this, [=] { closeBox(); });
|
||||
return;
|
||||
}
|
||||
setupEditEventHandler();
|
||||
SetupShadowsToScrollContent(this, _scroll, _contentHeight.events());
|
||||
@@ -525,6 +535,7 @@ void EditCaptionBox::setupField() {
|
||||
_field.get(),
|
||||
Window::GifPauseReason::Layer,
|
||||
allow);
|
||||
setupFieldAutocomplete();
|
||||
Ui::Emoji::SuggestionsController::Init(
|
||||
getDelegate()->outerContainer(),
|
||||
_field,
|
||||
@@ -562,6 +573,55 @@ void EditCaptionBox::setupField() {
|
||||
});
|
||||
}
|
||||
|
||||
void EditCaptionBox::setupFieldAutocomplete() {
|
||||
const auto parent = getDelegate()->outerContainer();
|
||||
ChatHelpers::InitFieldAutocomplete(_autocomplete, {
|
||||
.parent = parent,
|
||||
.show = _controller->uiShow(),
|
||||
.field = _field.get(),
|
||||
.peer = _historyItem->history()->peer,
|
||||
.features = [=] {
|
||||
auto result = ChatHelpers::ComposeFeatures();
|
||||
result.autocompleteCommands = false;
|
||||
result.suggestStickersByEmoji = false;
|
||||
return result;
|
||||
},
|
||||
});
|
||||
const auto raw = _autocomplete.get();
|
||||
const auto scheduled = std::make_shared<bool>();
|
||||
const auto recountPostponed = [=] {
|
||||
if (*scheduled) {
|
||||
return;
|
||||
}
|
||||
*scheduled = true;
|
||||
Ui::PostponeCall(raw, [=] {
|
||||
*scheduled = false;
|
||||
|
||||
auto field = Ui::MapFrom(parent, this, _field->geometry());
|
||||
_autocomplete->setBoundings(QRect(
|
||||
field.x() - _field->x(),
|
||||
st::defaultBox.margin.top(),
|
||||
width(),
|
||||
(field.y()
|
||||
+ st::defaultComposeFiles.caption.textMargins.top()
|
||||
+ st::defaultComposeFiles.caption.placeholderShift
|
||||
+ st::defaultComposeFiles.caption.placeholderFont->height
|
||||
- st::defaultBox.margin.top())));
|
||||
});
|
||||
};
|
||||
for (auto w = (QWidget*)_field.get(); w; w = w->parentWidget()) {
|
||||
base::install_event_filter(raw, w, [=](not_null<QEvent*> e) {
|
||||
if (e->type() == QEvent::Move || e->type() == QEvent::Resize) {
|
||||
recountPostponed();
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
if (w == parent) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditCaptionBox::setInitialText() {
|
||||
_field->setTextWithTags(
|
||||
_initialText,
|
||||
|
||||
@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace ChatHelpers {
|
||||
class TabbedPanel;
|
||||
class FieldAutocomplete;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace Window {
|
||||
@@ -68,6 +69,8 @@ public:
|
||||
bool invertCaption,
|
||||
Fn<void()> saved);
|
||||
|
||||
void showFinished() override;
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
void setInnerFocus() override;
|
||||
@@ -81,6 +84,7 @@ private:
|
||||
void setupEditEventHandler();
|
||||
void setupPhotoEditorEventHandler();
|
||||
void setupField();
|
||||
void setupFieldAutocomplete();
|
||||
void setupControls();
|
||||
void setInitialText();
|
||||
|
||||
@@ -115,6 +119,8 @@ private:
|
||||
const base::unique_qptr<Ui::InputField> _field;
|
||||
const base::unique_qptr<Ui::EmojiButton> _emojiToggle;
|
||||
|
||||
std::unique_ptr<ChatHelpers::FieldAutocomplete> _autocomplete;
|
||||
|
||||
base::unique_qptr<Ui::AbstractSinglePreview> _content;
|
||||
base::unique_qptr<ChatHelpers::TabbedPanel> _emojiPanel;
|
||||
base::unique_qptr<QObject> _emojiFilter;
|
||||
|
||||
@@ -36,13 +36,63 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_settings.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
#include "styles/style_window.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kPremiumsRowId = PeerId(FakeChatId(BareId(1))).value;
|
||||
constexpr auto kMiniAppsRowId = PeerId(FakeChatId(BareId(2))).value;
|
||||
|
||||
using Exceptions = Api::UserPrivacy::Exceptions;
|
||||
|
||||
enum class SpecialRowType {
|
||||
Premiums,
|
||||
MiniApps,
|
||||
};
|
||||
|
||||
[[nodiscard]] PaintRoundImageCallback GeneratePremiumsUserpicCallback(
|
||||
bool forceRound) {
|
||||
return [=](QPainter &p, int x, int y, int outerWidth, int size) {
|
||||
auto gradient = QLinearGradient(
|
||||
QPointF(x, y),
|
||||
QPointF(x + size, y + size));
|
||||
gradient.setStops(Ui::Premium::ButtonGradientStops());
|
||||
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(gradient);
|
||||
if (forceRound) {
|
||||
p.drawEllipse(x, y, size, size);
|
||||
} else {
|
||||
const auto radius = size * Ui::ForumUserpicRadiusMultiplier();
|
||||
p.drawRoundedRect(x, y, size, size, radius, radius);
|
||||
}
|
||||
st::settingsPrivacyPremium.paintInCenter(p, QRect(x, y, size, size));
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] PaintRoundImageCallback GenerateMiniAppsUserpicCallback(
|
||||
bool forceRound) {
|
||||
return [=](QPainter &p, int x, int y, int outerWidth, int size) {
|
||||
const auto &color1 = st::historyPeer6UserpicBg;
|
||||
const auto &color2 = st::historyPeer6UserpicBg2;
|
||||
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
auto gradient = QLinearGradient(x, y, x, y + size);
|
||||
gradient.setStops({ { 0., color1->c }, { 1., color2->c } });
|
||||
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(gradient);
|
||||
if (forceRound) {
|
||||
p.drawEllipse(x, y, size, size);
|
||||
} else {
|
||||
const auto radius = size * Ui::ForumUserpicRadiusMultiplier();
|
||||
p.drawRoundedRect(x, y, size, size, radius, radius);
|
||||
}
|
||||
st::windowFilterTypeBots.paintInCenter(p, QRect(x, y, size, size));
|
||||
};
|
||||
}
|
||||
|
||||
void CreateRadiobuttonLock(
|
||||
not_null<Ui::RpWidget*> widget,
|
||||
const style::Checkbox &st) {
|
||||
@@ -102,7 +152,7 @@ public:
|
||||
not_null<Main::Session*> session,
|
||||
rpl::producer<QString> title,
|
||||
const Exceptions &selected,
|
||||
bool allowChoosePremiums);
|
||||
std::optional<SpecialRowType> allowChooseSpecial);
|
||||
|
||||
Main::Session &session() const override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
@@ -110,18 +160,20 @@ public:
|
||||
bool handleDeselectForeignRow(PeerListRowId itemId) override;
|
||||
|
||||
[[nodiscard]] bool premiumsSelected() const;
|
||||
[[nodiscard]] bool miniAppsSelected() const;
|
||||
|
||||
protected:
|
||||
void prepareViewHook() override;
|
||||
std::unique_ptr<Row> createRow(not_null<History*> history) override;
|
||||
|
||||
private:
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> preparePremiumsRowList();
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> prepareSpecialRowList(
|
||||
SpecialRowType type);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
rpl::producer<QString> _title;
|
||||
Exceptions _selected;
|
||||
bool _allowChoosePremiums = false;
|
||||
std::optional<SpecialRowType> _allowChooseSpecial;
|
||||
|
||||
PeerListContentDelegate *_typesDelegate = nullptr;
|
||||
Fn<void(PeerListRowId)> _deselectOption;
|
||||
@@ -133,9 +185,9 @@ struct RowSelectionChange {
|
||||
bool checked = false;
|
||||
};
|
||||
|
||||
class PremiumsRow final : public PeerListRow {
|
||||
class SpecialRow final : public PeerListRow {
|
||||
public:
|
||||
PremiumsRow();
|
||||
explicit SpecialRow(SpecialRowType type);
|
||||
|
||||
QString generateName() override;
|
||||
QString generateShortName() override;
|
||||
@@ -147,66 +199,61 @@ public:
|
||||
|
||||
class TypesController final : public PeerListController {
|
||||
public:
|
||||
TypesController(not_null<Main::Session*> session, bool premiums);
|
||||
TypesController(not_null<Main::Session*> session, SpecialRowType type);
|
||||
|
||||
Main::Session &session() const override;
|
||||
void prepare() override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
|
||||
[[nodiscard]] bool premiumsSelected() const;
|
||||
[[nodiscard]] rpl::producer<bool> premiumsChanges() const;
|
||||
[[nodiscard]] bool specialSelected() const;
|
||||
[[nodiscard]] rpl::producer<bool> specialChanges() const;
|
||||
[[nodiscard]] auto rowSelectionChanges() const
|
||||
-> rpl::producer<RowSelectionChange>;
|
||||
|
||||
private:
|
||||
const not_null<Main::Session*> _session;
|
||||
const SpecialRowType _type;
|
||||
|
||||
rpl::event_stream<> _selectionChanged;
|
||||
rpl::event_stream<RowSelectionChange> _rowSelectionChanges;
|
||||
|
||||
};
|
||||
|
||||
PremiumsRow::PremiumsRow() : PeerListRow(kPremiumsRowId) {
|
||||
setCustomStatus(tr::lng_edit_privacy_premium_status(tr::now));
|
||||
SpecialRow::SpecialRow(SpecialRowType type)
|
||||
: PeerListRow((type == SpecialRowType::Premiums)
|
||||
? kPremiumsRowId
|
||||
: kMiniAppsRowId) {
|
||||
setCustomStatus((id() == kPremiumsRowId)
|
||||
? tr::lng_edit_privacy_premium_status(tr::now)
|
||||
: tr::lng_edit_privacy_miniapps_status(tr::now));
|
||||
}
|
||||
|
||||
QString PremiumsRow::generateName() {
|
||||
return tr::lng_edit_privacy_premium(tr::now);
|
||||
QString SpecialRow::generateName() {
|
||||
return (id() == kPremiumsRowId)
|
||||
? tr::lng_edit_privacy_premium(tr::now)
|
||||
: tr::lng_edit_privacy_miniapps(tr::now);
|
||||
}
|
||||
|
||||
QString PremiumsRow::generateShortName() {
|
||||
QString SpecialRow::generateShortName() {
|
||||
return generateName();
|
||||
}
|
||||
|
||||
PaintRoundImageCallback PremiumsRow::generatePaintUserpicCallback(
|
||||
PaintRoundImageCallback SpecialRow::generatePaintUserpicCallback(
|
||||
bool forceRound) {
|
||||
return [=](QPainter &p, int x, int y, int outerWidth, int size) {
|
||||
auto gradient = QLinearGradient(
|
||||
QPointF(x, y),
|
||||
QPointF(x + size, y + size));
|
||||
gradient.setStops(Ui::Premium::ButtonGradientStops());
|
||||
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(gradient);
|
||||
if (forceRound) {
|
||||
p.drawEllipse(x, y, size, size);
|
||||
} else {
|
||||
const auto radius = size * Ui::ForumUserpicRadiusMultiplier();
|
||||
p.drawRoundedRect(x, y, size, size, radius, radius);
|
||||
}
|
||||
st::settingsPrivacyPremium.paintInCenter(p, { x, y, size, size });
|
||||
};
|
||||
return (id() == kPremiumsRowId)
|
||||
? GeneratePremiumsUserpicCallback(forceRound)
|
||||
: GenerateMiniAppsUserpicCallback(forceRound);
|
||||
}
|
||||
|
||||
bool PremiumsRow::useForumLikeUserpic() const {
|
||||
bool SpecialRow::useForumLikeUserpic() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
TypesController::TypesController(
|
||||
not_null<Main::Session*> session,
|
||||
bool premiums)
|
||||
: _session(session) {
|
||||
SpecialRowType type)
|
||||
: _session(session)
|
||||
, _type(type) {
|
||||
}
|
||||
|
||||
Main::Session &TypesController::session() const {
|
||||
@@ -214,12 +261,15 @@ Main::Session &TypesController::session() const {
|
||||
}
|
||||
|
||||
void TypesController::prepare() {
|
||||
delegate()->peerListAppendRow(std::make_unique<PremiumsRow>());
|
||||
delegate()->peerListAppendRow(std::make_unique<SpecialRow>(_type));
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
|
||||
bool TypesController::premiumsSelected() const {
|
||||
const auto row = delegate()->peerListFindRow(kPremiumsRowId);
|
||||
bool TypesController::specialSelected() const {
|
||||
const auto premiums = (_type == SpecialRowType::Premiums);
|
||||
const auto row = delegate()->peerListFindRow(premiums
|
||||
? kPremiumsRowId
|
||||
: kMiniAppsRowId);
|
||||
Assert(row != nullptr);
|
||||
|
||||
return row->checked();
|
||||
@@ -231,10 +281,10 @@ void TypesController::rowClicked(not_null<PeerListRow*> row) {
|
||||
_rowSelectionChanges.fire({ row, checked });
|
||||
}
|
||||
|
||||
rpl::producer<bool> TypesController::premiumsChanges() const {
|
||||
rpl::producer<bool> TypesController::specialChanges() const {
|
||||
return _rowSelectionChanges.events(
|
||||
) | rpl::map([=] {
|
||||
return premiumsSelected();
|
||||
return specialSelected();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -247,12 +297,12 @@ PrivacyExceptionsBoxController::PrivacyExceptionsBoxController(
|
||||
not_null<Main::Session*> session,
|
||||
rpl::producer<QString> title,
|
||||
const Exceptions &selected,
|
||||
bool allowChoosePremiums)
|
||||
std::optional<SpecialRowType> allowChooseSpecial)
|
||||
: ChatsListBoxController(session)
|
||||
, _session(session)
|
||||
, _title(std::move(title))
|
||||
, _selected(selected)
|
||||
, _allowChoosePremiums(allowChoosePremiums) {
|
||||
, _allowChooseSpecial(allowChooseSpecial) {
|
||||
}
|
||||
|
||||
Main::Session &PrivacyExceptionsBoxController::session() const {
|
||||
@@ -261,14 +311,18 @@ Main::Session &PrivacyExceptionsBoxController::session() const {
|
||||
|
||||
void PrivacyExceptionsBoxController::prepareViewHook() {
|
||||
delegate()->peerListSetTitle(std::move(_title));
|
||||
if (_allowChoosePremiums || _selected.premiums) {
|
||||
delegate()->peerListSetAboveWidget(preparePremiumsRowList());
|
||||
if (_allowChooseSpecial || _selected.premiums || _selected.miniapps) {
|
||||
delegate()->peerListSetAboveWidget(prepareSpecialRowList(
|
||||
_allowChooseSpecial.value_or(_selected.premiums
|
||||
? SpecialRowType::Premiums
|
||||
: SpecialRowType::MiniApps)));
|
||||
}
|
||||
delegate()->peerListAddSelectedPeers(_selected.peers);
|
||||
}
|
||||
|
||||
bool PrivacyExceptionsBoxController::isForeignRow(PeerListRowId itemId) {
|
||||
return (itemId == kPremiumsRowId);
|
||||
return (itemId == kPremiumsRowId)
|
||||
|| (itemId == kMiniAppsRowId);
|
||||
}
|
||||
|
||||
bool PrivacyExceptionsBoxController::handleDeselectForeignRow(
|
||||
@@ -280,7 +334,8 @@ bool PrivacyExceptionsBoxController::handleDeselectForeignRow(
|
||||
return false;
|
||||
}
|
||||
|
||||
auto PrivacyExceptionsBoxController::preparePremiumsRowList()
|
||||
auto PrivacyExceptionsBoxController::prepareSpecialRowList(
|
||||
SpecialRowType type)
|
||||
-> object_ptr<Ui::RpWidget> {
|
||||
auto result = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
|
||||
const auto container = result.data();
|
||||
@@ -291,30 +346,39 @@ auto PrivacyExceptionsBoxController::preparePremiumsRowList()
|
||||
_typesDelegate = lifetime.make_state<PeerListContentDelegateSimple>();
|
||||
const auto controller = lifetime.make_state<TypesController>(
|
||||
&session(),
|
||||
_selected.premiums);
|
||||
type);
|
||||
const auto content = result->add(object_ptr<PeerListContent>(
|
||||
container,
|
||||
controller));
|
||||
_typesDelegate->setContent(content);
|
||||
controller->setDelegate(_typesDelegate);
|
||||
|
||||
const auto selectType = [&](PeerListRowId id) {
|
||||
const auto row = _typesDelegate->peerListFindRow(id);
|
||||
if (row) {
|
||||
content->changeCheckState(row, true, anim::type::instant);
|
||||
this->delegate()->peerListSetForeignRowChecked(
|
||||
row,
|
||||
true,
|
||||
anim::type::instant);
|
||||
}
|
||||
};
|
||||
if (_selected.premiums) {
|
||||
const auto row = _typesDelegate->peerListFindRow(kPremiumsRowId);
|
||||
Assert(row != nullptr);
|
||||
|
||||
content->changeCheckState(row, true, anim::type::instant);
|
||||
this->delegate()->peerListSetForeignRowChecked(
|
||||
row,
|
||||
true,
|
||||
anim::type::instant);
|
||||
selectType(kPremiumsRowId);
|
||||
} else if (_selected.miniapps) {
|
||||
selectType(kMiniAppsRowId);
|
||||
}
|
||||
container->add(CreatePeerListSectionSubtitle(
|
||||
container,
|
||||
tr::lng_edit_privacy_users_and_groups()));
|
||||
|
||||
controller->premiumsChanges(
|
||||
) | rpl::start_with_next([=](bool premiums) {
|
||||
_selected.premiums = premiums;
|
||||
controller->specialChanges(
|
||||
) | rpl::start_with_next([=](bool chosen) {
|
||||
if (type == SpecialRowType::Premiums) {
|
||||
_selected.premiums = chosen;
|
||||
} else {
|
||||
_selected.miniapps = chosen;
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
controller->rowSelectionChanges(
|
||||
@@ -329,6 +393,8 @@ auto PrivacyExceptionsBoxController::preparePremiumsRowList()
|
||||
if (const auto row = _typesDelegate->peerListFindRow(itemId)) {
|
||||
if (itemId == kPremiumsRowId) {
|
||||
_selected.premiums = false;
|
||||
} else if (itemId == kMiniAppsRowId) {
|
||||
_selected.miniapps = false;
|
||||
}
|
||||
_typesDelegate->peerListSetRowChecked(row, false);
|
||||
}
|
||||
@@ -337,10 +403,14 @@ auto PrivacyExceptionsBoxController::preparePremiumsRowList()
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool PrivacyExceptionsBoxController::premiumsSelected() const {
|
||||
bool PrivacyExceptionsBoxController::premiumsSelected() const {
|
||||
return _selected.premiums;
|
||||
}
|
||||
|
||||
bool PrivacyExceptionsBoxController::miniAppsSelected() const {
|
||||
return _selected.miniapps;
|
||||
}
|
||||
|
||||
void PrivacyExceptionsBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||
const auto peer = row->peer();
|
||||
|
||||
@@ -356,11 +426,12 @@ void PrivacyExceptionsBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||
|
||||
auto PrivacyExceptionsBoxController::createRow(not_null<History*> history)
|
||||
-> std::unique_ptr<Row> {
|
||||
if (history->peer->isSelf() || history->peer->isRepliesChat()) {
|
||||
const auto peer = history->peer;
|
||||
if (peer->isSelf() || peer->isRepliesChat() || peer->isVerifyCodes()) {
|
||||
return nullptr;
|
||||
} else if (!history->peer->isUser()
|
||||
&& !history->peer->isChat()
|
||||
&& !history->peer->isMegagroup()) {
|
||||
} else if (!peer->isUser()
|
||||
&& !peer->isChat()
|
||||
&& !peer->isMegagroup()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto result = std::make_unique<Row>(history);
|
||||
@@ -411,6 +482,11 @@ EditPrivacyBox::EditPrivacyBox(
|
||||
// If we switch from Everyone to Contacts or Nobody suggest Premiums.
|
||||
_value.always.premiums = true;
|
||||
}
|
||||
if (_controller->allowMiniAppsToggle(Exception::Always)
|
||||
&& _value.option == Option::Everyone) {
|
||||
// If we switch from Everyone to Contacts or Nobody suggest MiniApps.
|
||||
_value.always.miniapps = true;
|
||||
}
|
||||
}
|
||||
|
||||
void EditPrivacyBox::prepare() {
|
||||
@@ -426,12 +502,18 @@ void EditPrivacyBox::editExceptions(
|
||||
&_window->session(),
|
||||
_controller->exceptionBoxTitle(exception),
|
||||
exceptions(exception),
|
||||
_controller->allowPremiumsToggle(exception));
|
||||
(_controller->allowPremiumsToggle(exception)
|
||||
? SpecialRowType::Premiums
|
||||
: _controller->allowMiniAppsToggle(exception)
|
||||
? SpecialRowType::MiniApps
|
||||
: std::optional<SpecialRowType>()));
|
||||
auto initBox = [=, controller = controller.get()](
|
||||
not_null<PeerListBox*> box) {
|
||||
box->addButton(tr::lng_settings_save(), crl::guard(this, [=] {
|
||||
exceptions(exception).peers = box->collectSelectedRows();
|
||||
exceptions(exception).premiums = controller->premiumsSelected();
|
||||
auto &setTo = exceptions(exception);
|
||||
setTo.peers = box->collectSelectedRows();
|
||||
setTo.premiums = controller->premiumsSelected();
|
||||
setTo.miniapps = controller->miniAppsSelected();
|
||||
const auto type = [&] {
|
||||
switch (exception) {
|
||||
case Exception::Always: return Exception::Never;
|
||||
@@ -439,11 +521,17 @@ void EditPrivacyBox::editExceptions(
|
||||
}
|
||||
Unexpected("Invalid exception value.");
|
||||
}();
|
||||
auto &removeFrom = exceptions(type).peers;
|
||||
auto &removeFrom = exceptions(type);
|
||||
for (const auto peer : exceptions(exception).peers) {
|
||||
removeFrom.erase(
|
||||
ranges::remove(removeFrom, peer),
|
||||
end(removeFrom));
|
||||
removeFrom.peers.erase(
|
||||
ranges::remove(removeFrom.peers, peer),
|
||||
end(removeFrom.peers));
|
||||
}
|
||||
if (setTo.premiums) {
|
||||
removeFrom.premiums = false;
|
||||
}
|
||||
if (setTo.miniapps) {
|
||||
removeFrom.miniapps = false;
|
||||
}
|
||||
done();
|
||||
box->closeBox();
|
||||
@@ -565,14 +653,21 @@ void EditPrivacyBox::setupContent() {
|
||||
lt_count,
|
||||
count)
|
||||
: tr::lng_edit_privacy_exceptions_add(tr::now);
|
||||
return !value.premiums
|
||||
? users
|
||||
: !count
|
||||
? tr::lng_edit_privacy_premium(tr::now)
|
||||
: tr::lng_edit_privacy_exceptions_premium_and(
|
||||
tr::now,
|
||||
lt_users,
|
||||
users);
|
||||
return value.premiums
|
||||
? (!count
|
||||
? tr::lng_edit_privacy_premium(tr::now)
|
||||
: tr::lng_edit_privacy_exceptions_premium_and(
|
||||
tr::now,
|
||||
lt_users,
|
||||
users))
|
||||
: value.miniapps
|
||||
? (!count
|
||||
? tr::lng_edit_privacy_miniapps(tr::now)
|
||||
: tr::lng_edit_privacy_exceptions_miniapps_and(
|
||||
tr::now,
|
||||
lt_users,
|
||||
users))
|
||||
: users;
|
||||
});
|
||||
_controller->handleExceptionsChange(
|
||||
exception,
|
||||
|
||||
@@ -61,6 +61,10 @@ public:
|
||||
Exception exception) const {
|
||||
return false;
|
||||
}
|
||||
[[nodiscard]] virtual bool allowMiniAppsToggle(
|
||||
Exception exception) const {
|
||||
return false;
|
||||
}
|
||||
virtual void handleExceptionsChange(
|
||||
Exception exception,
|
||||
rpl::producer<int> value) {
|
||||
|
||||
@@ -131,10 +131,13 @@ ExceptionRow::ExceptionRow(not_null<History*> history) : Row(history) {
|
||||
}
|
||||
|
||||
QString ExceptionRow::generateName() {
|
||||
return peer()->isSelf()
|
||||
const auto peer = this->peer();
|
||||
return peer->isSelf()
|
||||
? tr::lng_saved_messages(tr::now)
|
||||
: peer()->isRepliesChat()
|
||||
: peer->isRepliesChat()
|
||||
? tr::lng_replies_messages(tr::now)
|
||||
: peer->isVerifyCodes()
|
||||
? tr::lng_verification_codes(tr::now)
|
||||
: Row::generateName();
|
||||
}
|
||||
|
||||
@@ -152,10 +155,11 @@ PaintRoundImageCallback ExceptionRow::generatePaintUserpicCallback(
|
||||
return ForceRoundUserpicCallback(peer);
|
||||
}
|
||||
return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
|
||||
using namespace Ui;
|
||||
if (saved) {
|
||||
Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
|
||||
EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
|
||||
} else if (replies) {
|
||||
Ui::EmptyUserpic::PaintRepliesMessages(p, x, y, outerWidth, size);
|
||||
EmptyUserpic::PaintRepliesMessages(p, x, y, outerWidth, size);
|
||||
} else {
|
||||
peer->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
|
||||
}
|
||||
|
||||
@@ -122,9 +122,11 @@ void FilterChatsPreview::paintEvent(QPaintEvent *e) {
|
||||
top += st.height;
|
||||
}
|
||||
for (auto &[history, userpic, name, button] : _removePeer) {
|
||||
const auto savedMessages = history->peer->isSelf();
|
||||
const auto repliesMessages = history->peer->isRepliesChat();
|
||||
if (savedMessages || repliesMessages) {
|
||||
const auto peer = history->peer;
|
||||
const auto savedMessages = peer->isSelf();
|
||||
const auto repliesMessages = peer->isRepliesChat();
|
||||
const auto verifyCodes = peer->isVerifyCodes();
|
||||
if (savedMessages || repliesMessages || verifyCodes) {
|
||||
if (savedMessages) {
|
||||
Ui::EmptyUserpic::PaintSavedMessages(
|
||||
p,
|
||||
@@ -132,13 +134,21 @@ void FilterChatsPreview::paintEvent(QPaintEvent *e) {
|
||||
top + iconTop,
|
||||
width(),
|
||||
st.photoSize);
|
||||
} else {
|
||||
} else if (repliesMessages) {
|
||||
Ui::EmptyUserpic::PaintRepliesMessages(
|
||||
p,
|
||||
iconLeft,
|
||||
top + iconTop,
|
||||
width(),
|
||||
st.photoSize);
|
||||
} else {
|
||||
history->peer->paintUserpicLeft(
|
||||
p,
|
||||
userpic,
|
||||
iconLeft,
|
||||
top + iconTop,
|
||||
width(),
|
||||
st.photoSize);
|
||||
}
|
||||
p.setPen(st::contactsNameFg);
|
||||
p.drawTextLeft(
|
||||
@@ -147,7 +157,9 @@ void FilterChatsPreview::paintEvent(QPaintEvent *e) {
|
||||
width(),
|
||||
(savedMessages
|
||||
? tr::lng_saved_messages(tr::now)
|
||||
: tr::lng_replies_messages(tr::now)));
|
||||
: repliesMessages
|
||||
? tr::lng_replies_messages(tr::now)
|
||||
: tr::lng_verification_codes(tr::now)));
|
||||
} else {
|
||||
history->peer->paintUserpicLeft(
|
||||
p,
|
||||
|
||||
@@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_info.h"
|
||||
@@ -336,12 +337,13 @@ PaintRoundImageCallback ChatRow::generatePaintUserpicCallback(
|
||||
int y,
|
||||
int outerWidth,
|
||||
int size) mutable {
|
||||
using namespace Ui;
|
||||
if (forceRound && peer->isForum()) {
|
||||
ForceRoundUserpicCallback(peer)(p, x, y, outerWidth, size);
|
||||
} else if (saved) {
|
||||
Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
|
||||
EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
|
||||
} else if (replies) {
|
||||
Ui::EmptyUserpic::PaintRepliesMessages(p, x, y, outerWidth, size);
|
||||
EmptyUserpic::PaintRepliesMessages(p, x, y, outerWidth, size);
|
||||
} else {
|
||||
peer->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
|
||||
}
|
||||
@@ -581,6 +583,7 @@ void LinkController::addLinkBlock(not_null<Ui::VerticalLayout*> container) {
|
||||
});
|
||||
const auto getLinkQr = crl::guard(weak, [=] {
|
||||
delegate()->peerListUiShow()->showBox(InviteLinkQrBox(
|
||||
nullptr,
|
||||
link,
|
||||
tr::lng_group_invite_qr_title(),
|
||||
tr::lng_filters_link_qr_about()));
|
||||
@@ -889,6 +892,7 @@ base::unique_qptr<Ui::PopupMenu> LinksController::createRowContextMenu(
|
||||
};
|
||||
const auto getLinkQr = [=] {
|
||||
delegate()->peerListUiShow()->showBox(InviteLinkQrBox(
|
||||
nullptr,
|
||||
link,
|
||||
tr::lng_group_invite_qr_title(),
|
||||
tr::lng_filters_link_qr_about()));
|
||||
@@ -965,9 +969,9 @@ void LinksController::rowPaintIcon(
|
||||
p.setBrush(*bg);
|
||||
{
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.drawEllipse(QRect(0, 0, inner, inner));
|
||||
p.drawEllipse(Rect(Size(inner)));
|
||||
}
|
||||
st::inviteLinkIcon.paintInCenter(p, { 0, 0, inner, inner });
|
||||
st::inviteLinkIcon.paintInCenter(p, Rect(Size(inner)));
|
||||
}
|
||||
p.drawImage(x + skip, y + skip, icon);
|
||||
}
|
||||
@@ -1113,7 +1117,7 @@ QString FilterChatStatusText(not_null<PeerData*> peer) {
|
||||
? tr::lng_chat_status_subscribers
|
||||
: tr::lng_chat_status_members)(
|
||||
tr::now,
|
||||
lt_count,
|
||||
lt_count_decimal,
|
||||
channel->membersCount());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ void GiftCreditsBox(
|
||||
) | rpl::map([](TextWithEntities text) {
|
||||
return Ui::Text::Link(
|
||||
std::move(text),
|
||||
tr::lng_credits_box_history_entry_gift_about_url(tr::now));
|
||||
u"internal:stars_examples"_q);
|
||||
});
|
||||
content->add(
|
||||
object_ptr<Ui::CenterWrap<>>(
|
||||
@@ -123,7 +123,9 @@ void GiftCreditsBox(
|
||||
box->verticalLayout(),
|
||||
peer,
|
||||
0,
|
||||
[=] { gifted(); box->uiShow()->hideLayer(); });
|
||||
[=] { gifted(); box->uiShow()->hideLayer(); },
|
||||
tr::lng_credits_summary_options_subtitle(),
|
||||
{});
|
||||
|
||||
box->setPinnedToBottomContent(
|
||||
object_ptr<Ui::VerticalLayout>(box));
|
||||
|
||||
@@ -9,13 +9,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
class UserData;
|
||||
|
||||
namespace Api {
|
||||
struct GiftCode;
|
||||
} // namespace Api
|
||||
|
||||
namespace Data {
|
||||
struct Boost;
|
||||
struct CreditsHistoryEntry;
|
||||
struct GiveawayStart;
|
||||
struct GiveawayResults;
|
||||
@@ -28,29 +27,9 @@ class VerticalLayout;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
class SessionNavigation;
|
||||
} // namespace Window
|
||||
|
||||
class GiftPremiumValidator final {
|
||||
public:
|
||||
GiftPremiumValidator(not_null<Window::SessionController*> controller);
|
||||
|
||||
void showBox(not_null<UserData*> user);
|
||||
void showChoosePeerBox(const QString &ref);
|
||||
void showChosenPeerBox(not_null<UserData*> user, const QString &ref);
|
||||
void cancel();
|
||||
|
||||
private:
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
MTP::Sender _api;
|
||||
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
rpl::lifetime _manyGiftsLifetime;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] rpl::producer<QString> GiftDurationValue(int months);
|
||||
[[nodiscard]] QString GiftDuration(int months);
|
||||
|
||||
@@ -75,6 +54,11 @@ void ResolveGiveawayInfo(
|
||||
std::optional<Data::GiveawayStart> start,
|
||||
std::optional<Data::GiveawayResults> results);
|
||||
|
||||
void AddStarGiftTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
const Data::CreditsHistoryEntry &entry,
|
||||
Fn<void()> convertToStars);
|
||||
void AddCreditsHistoryEntryTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
@@ -89,3 +73,8 @@ void AddSubscriberEntryTable(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<PeerData*> peer,
|
||||
TimeId date);
|
||||
|
||||
void AddCreditsBoostTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
const Data::Boost &boost);
|
||||
|
||||
@@ -240,7 +240,8 @@ int LocalStorageBox::Row::resizeGetHeight(int newWidth) {
|
||||
}
|
||||
|
||||
void LocalStorageBox::Row::paintEvent(QPaintEvent *e) {
|
||||
if (!_progress || true) {
|
||||
#if 0 // not used
|
||||
if (!_progress) {
|
||||
return;
|
||||
}
|
||||
auto p = QPainter(this);
|
||||
@@ -254,6 +255,7 @@ void LocalStorageBox::Row::paintEvent(QPaintEvent *e) {
|
||||
st::proxyCheckingPosition.y() + bottom
|
||||
},
|
||||
width());
|
||||
#endif
|
||||
}
|
||||
|
||||
QString LocalStorageBox::Row::titleText(const Database::TaggedSummary &data) const {
|
||||
|
||||
@@ -436,14 +436,16 @@ void CreateModerateMessagesBox(
|
||||
return result;
|
||||
}();
|
||||
|
||||
auto [checkboxes, getRestrictions, changes] = CreateEditRestrictions(
|
||||
box,
|
||||
Ui::AddSubsectionTitle(
|
||||
inner,
|
||||
rpl::conditional(
|
||||
rpl::single(isSingle),
|
||||
tr::lng_restrict_users_part_single_header(),
|
||||
tr::lng_restrict_users_part_header(
|
||||
lt_count,
|
||||
rpl::single(participants.size()) | tr::to_count())),
|
||||
rpl::single(participants.size()) | tr::to_count())));
|
||||
auto [checkboxes, getRestrictions, changes] = CreateEditRestrictions(
|
||||
box,
|
||||
prepareFlags,
|
||||
disabledMessages,
|
||||
{ .isForum = peer->isForum() });
|
||||
|
||||
@@ -206,7 +206,9 @@ void PeerListBox::keyPressEvent(QKeyEvent *e) {
|
||||
content()->selectSkipPage(height(), 1);
|
||||
} else if (e->key() == Qt::Key_PageUp) {
|
||||
content()->selectSkipPage(height(), -1);
|
||||
} else if (e->key() == Qt::Key_Escape && _select && !_select->entity()->getQuery().isEmpty()) {
|
||||
} else if (e->key() == Qt::Key_Escape
|
||||
&& _select
|
||||
&& !_select->entity()->getQuery().isEmpty()) {
|
||||
_select->entity()->clearQuery();
|
||||
} else {
|
||||
BoxContent::keyPressEvent(e);
|
||||
@@ -215,7 +217,19 @@ void PeerListBox::keyPressEvent(QKeyEvent *e) {
|
||||
|
||||
void PeerListBox::searchQueryChanged(const QString &query) {
|
||||
scrollToY(0);
|
||||
content()->searchQueryChanged(query);
|
||||
const auto isEmpty = content()->searchQueryChanged(query);
|
||||
if (_specialTabsMode.enabled) {
|
||||
const auto was = _specialTabsMode.searchIsActive;
|
||||
_specialTabsMode.searchIsActive = !isEmpty;
|
||||
if (was != _specialTabsMode.searchIsActive) {
|
||||
if (_specialTabsMode.searchIsActive) {
|
||||
_specialTabsMode.topSkip = _addedTopScrollSkip;
|
||||
setAddedTopScrollSkip(0);
|
||||
} else {
|
||||
setAddedTopScrollSkip(_specialTabsMode.topSkip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListBox::resizeEvent(QResizeEvent *e) {
|
||||
@@ -447,6 +461,8 @@ void PeerListBox::addSelectItem(
|
||||
? tr::lng_saved_short(tr::now)
|
||||
: (respect && peer->isRepliesChat())
|
||||
? tr::lng_replies_messages(tr::now)
|
||||
: (respect && peer->isVerifyCodes())
|
||||
? tr::lng_verification_codes(tr::now)
|
||||
: peer->shortName();
|
||||
addSelectItem(
|
||||
peer->id.value,
|
||||
@@ -541,6 +557,19 @@ auto PeerListBox::collectSelectedRows()
|
||||
return result;
|
||||
}
|
||||
|
||||
rpl::producer<int> PeerListBox::multiSelectHeightValue() const {
|
||||
return _select ? _select->heightValue() : rpl::single(0);
|
||||
}
|
||||
|
||||
void PeerListBox::setSpecialTabMode(bool value) {
|
||||
content()->setIgnoreHiddenRowsOnSearch(value);
|
||||
if (value) {
|
||||
_specialTabsMode.enabled = true;
|
||||
} else {
|
||||
_specialTabsMode = {};
|
||||
}
|
||||
}
|
||||
|
||||
PeerListRow::PeerListRow(not_null<PeerData*> peer)
|
||||
: PeerListRow(peer, peer->id.value) {
|
||||
}
|
||||
@@ -625,6 +654,8 @@ void PeerListRow::refreshName(const style::PeerListItem &st) {
|
||||
? tr::lng_saved_messages(tr::now)
|
||||
: _isRepliesMessagesChat
|
||||
? tr::lng_replies_messages(tr::now)
|
||||
: _isVerifyCodesChat
|
||||
? tr::lng_verification_codes(tr::now)
|
||||
: generateName();
|
||||
_name.setText(st.nameStyle, text, Ui::NameTextOptions());
|
||||
}
|
||||
@@ -695,6 +726,8 @@ QString PeerListRow::generateShortName() {
|
||||
? tr::lng_saved_short(tr::now)
|
||||
: _isRepliesMessagesChat
|
||||
? tr::lng_replies_messages(tr::now)
|
||||
: _isVerifyCodesChat
|
||||
? tr::lng_verification_codes(tr::now)
|
||||
: peer()->shortName();
|
||||
}
|
||||
|
||||
@@ -715,10 +748,11 @@ PaintRoundImageCallback PeerListRow::generatePaintUserpicCallback(
|
||||
return ForceRoundUserpicCallback(peer);
|
||||
}
|
||||
return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
|
||||
using namespace Ui;
|
||||
if (saved) {
|
||||
Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
|
||||
EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
|
||||
} else if (replies) {
|
||||
Ui::EmptyUserpic::PaintRepliesMessages(p, x, y, outerWidth, size);
|
||||
EmptyUserpic::PaintRepliesMessages(p, x, y, outerWidth, size);
|
||||
} else {
|
||||
peer->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
|
||||
}
|
||||
@@ -757,12 +791,14 @@ int PeerListRow::paintNameIconGetWidth(
|
||||
int availableWidth,
|
||||
int outerWidth,
|
||||
bool selected) {
|
||||
if (special()
|
||||
if (_skipPeerBadge
|
||||
|| special()
|
||||
|| !_savedMessagesStatus.isEmpty()
|
||||
|| _isRepliesMessagesChat) {
|
||||
|| _isRepliesMessagesChat
|
||||
|| _isVerifyCodesChat) {
|
||||
return 0;
|
||||
}
|
||||
return _bagde.drawGetWidth(
|
||||
return _badge.drawGetWidth(
|
||||
p,
|
||||
QRect(
|
||||
nameLeft,
|
||||
@@ -874,12 +910,13 @@ void PeerListRow::paintDisabledCheckUserpic(
|
||||
auto iconBorderPen = st.checkbox.check.border->p;
|
||||
iconBorderPen.setWidth(st.checkbox.selectWidth);
|
||||
|
||||
const auto size = userpicRadius * 2;
|
||||
if (!_savedMessagesStatus.isEmpty()) {
|
||||
Ui::EmptyUserpic::PaintSavedMessages(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
|
||||
Ui::EmptyUserpic::PaintSavedMessages(p, userpicLeft, userpicTop, outerWidth, size);
|
||||
} else if (_isRepliesMessagesChat) {
|
||||
Ui::EmptyUserpic::PaintRepliesMessages(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
|
||||
Ui::EmptyUserpic::PaintRepliesMessages(p, userpicLeft, userpicTop, outerWidth, size);
|
||||
} else {
|
||||
peer()->paintUserpicLeft(p, _userpic, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
|
||||
peer()->paintUserpicLeft(p, _userpic, userpicLeft, userpicTop, outerWidth, size);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1069,10 +1106,13 @@ void PeerListContent::setRowHidden(not_null<PeerListRow*> row, bool hidden) {
|
||||
void PeerListContent::addRowEntry(not_null<PeerListRow*> row) {
|
||||
const auto savedMessagesStatus = _controller->savedMessagesChatStatus();
|
||||
if (!savedMessagesStatus.isEmpty() && !row->special()) {
|
||||
if (row->peer()->isSelf()) {
|
||||
const auto peer = row->peer();
|
||||
if (peer->isSelf()) {
|
||||
row->setSavedMessagesChatStatus(savedMessagesStatus);
|
||||
} else if (row->peer()->isRepliesChat()) {
|
||||
} else if (peer->isRepliesChat()) {
|
||||
row->setIsRepliesMessagesChat(true);
|
||||
} else if (peer->isVerifyCodes()) {
|
||||
row->setIsVerifyCodesChat(true);
|
||||
}
|
||||
}
|
||||
_rowsById.emplace(row->id(), row);
|
||||
@@ -1372,10 +1412,12 @@ int PeerListContent::labelHeight() const {
|
||||
|
||||
void PeerListContent::refreshRows() {
|
||||
if (!_hiddenRows.empty()) {
|
||||
_filterResults.clear();
|
||||
for (const auto &row : _rows) {
|
||||
if (!row->hidden()) {
|
||||
_filterResults.push_back(row.get());
|
||||
if (!_ignoreHiddenRowsOnSearch || _normalizedSearchQuery.isEmpty()) {
|
||||
_filterResults.clear();
|
||||
for (const auto &row : _rows) {
|
||||
if (!row->hidden()) {
|
||||
_filterResults.push_back(row.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1931,6 +1973,13 @@ PeerListContent::SkipResult PeerListContent::selectSkip(int direction) {
|
||||
}
|
||||
}
|
||||
|
||||
if (_controller->overrideKeyboardNavigation(
|
||||
direction,
|
||||
_selected.index.value,
|
||||
newSelectedIndex)) {
|
||||
return { _selected.index.value, _selected.index.value };
|
||||
}
|
||||
|
||||
_selected.index.value = newSelectedIndex;
|
||||
_selected.element = 0;
|
||||
if (newSelectedIndex >= 0) {
|
||||
@@ -2030,13 +2079,16 @@ void PeerListContent::checkScrollForPreload() {
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListContent::searchQueryChanged(QString query) {
|
||||
PeerListContent::IsEmpty PeerListContent::searchQueryChanged(QString query) {
|
||||
const auto searchWordsList = TextUtilities::PrepareSearchWords(query);
|
||||
const auto normalizedQuery = searchWordsList.join(' ');
|
||||
if (_ignoreHiddenRowsOnSearch && !normalizedQuery.isEmpty()) {
|
||||
_filterResults.clear();
|
||||
}
|
||||
if (_normalizedSearchQuery != normalizedQuery) {
|
||||
setSearchQuery(query, normalizedQuery);
|
||||
if (_controller->searchInLocal() && !searchWordsList.isEmpty()) {
|
||||
Assert(_hiddenRows.empty());
|
||||
Assert(_hiddenRows.empty() || _ignoreHiddenRowsOnSearch);
|
||||
|
||||
auto minimalList = (const std::vector<not_null<PeerListRow*>>*)nullptr;
|
||||
for (const auto &searchWord : searchWordsList) {
|
||||
@@ -2084,6 +2136,7 @@ void PeerListContent::searchQueryChanged(QString query) {
|
||||
}
|
||||
refreshRows();
|
||||
}
|
||||
return _normalizedSearchQuery.isEmpty();
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListState> PeerListContent::saveState() const {
|
||||
@@ -2172,6 +2225,10 @@ void PeerListContent::dragLeft() {
|
||||
clearSelection();
|
||||
}
|
||||
|
||||
void PeerListContent::setIgnoreHiddenRowsOnSearch(bool value) {
|
||||
_ignoreHiddenRowsOnSearch = value;
|
||||
}
|
||||
|
||||
void PeerListContent::visibleTopBottomUpdated(
|
||||
int visibleTop,
|
||||
int visibleBottom) {
|
||||
|
||||
@@ -200,6 +200,9 @@ public:
|
||||
void setIsRepliesMessagesChat(bool isRepliesMessagesChat) {
|
||||
_isRepliesMessagesChat = isRepliesMessagesChat;
|
||||
}
|
||||
void setIsVerifyCodesChat(bool isVerifyCodesChat) {
|
||||
_isVerifyCodesChat = isVerifyCodesChat;
|
||||
}
|
||||
|
||||
template <typename UpdateCallback>
|
||||
void setChecked(
|
||||
@@ -251,6 +254,10 @@ public:
|
||||
return _nameFirstLetters;
|
||||
}
|
||||
|
||||
void setSkipPeerBadge(bool skip) {
|
||||
_skipPeerBadge = skip;
|
||||
}
|
||||
|
||||
virtual void lazyInitialize(const style::PeerListItem &st);
|
||||
virtual void paintStatusText(
|
||||
Painter &p,
|
||||
@@ -288,7 +295,7 @@ private:
|
||||
std::unique_ptr<Ui::RoundImageCheckbox> _checkbox;
|
||||
Ui::Text::String _name;
|
||||
Ui::Text::String _status;
|
||||
Ui::PeerBadge _bagde;
|
||||
Ui::PeerBadge _badge;
|
||||
StatusType _statusType = StatusType::Online;
|
||||
crl::time _statusValidTill = 0;
|
||||
base::flat_set<QChar> _nameFirstLetters;
|
||||
@@ -299,6 +306,8 @@ private:
|
||||
bool _initialized : 1 = false;
|
||||
bool _isSearchResult : 1 = false;
|
||||
bool _isRepliesMessagesChat : 1 = false;
|
||||
bool _isVerifyCodesChat : 1 = false;
|
||||
bool _skipPeerBadge : 1 = false;
|
||||
|
||||
};
|
||||
|
||||
@@ -348,6 +357,8 @@ public:
|
||||
virtual int peerListPartitionRows(Fn<bool(const PeerListRow &a)> border) = 0;
|
||||
virtual std::shared_ptr<Main::SessionShow> peerListUiShow() = 0;
|
||||
|
||||
virtual void peerListSelectSkip(int direction) = 0;
|
||||
|
||||
virtual void peerListPressLeftToContextMenu(bool shown) = 0;
|
||||
virtual bool peerListTrackRowPressFromGlobal(QPoint globalPosition) = 0;
|
||||
|
||||
@@ -564,6 +575,13 @@ public:
|
||||
Unexpected("PeerListController::customRowRippleMaskGenerator.");
|
||||
}
|
||||
|
||||
virtual bool overrideKeyboardNavigation(
|
||||
int direction,
|
||||
int fromIndex,
|
||||
int toIndex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime() {
|
||||
return _lifetime;
|
||||
}
|
||||
@@ -634,12 +652,15 @@ public:
|
||||
[[nodiscard]] bool hasPressed() const;
|
||||
void clearSelection();
|
||||
|
||||
void searchQueryChanged(QString query);
|
||||
using IsEmpty = bool;
|
||||
IsEmpty searchQueryChanged(QString query);
|
||||
bool submitted();
|
||||
|
||||
PeerListRowId updateFromParentDrag(QPoint globalPosition);
|
||||
void dragLeft();
|
||||
|
||||
void setIgnoreHiddenRowsOnSearch(bool value);
|
||||
|
||||
// Interface for the controller.
|
||||
void appendRow(std::unique_ptr<PeerListRow> row);
|
||||
void appendSearchRow(std::unique_ptr<PeerListRow> row);
|
||||
@@ -861,6 +882,7 @@ private:
|
||||
int _aboveHeight = 0;
|
||||
int _belowHeight = 0;
|
||||
bool _hideEmpty = false;
|
||||
bool _ignoreHiddenRowsOnSearch = false;
|
||||
object_ptr<Ui::RpWidget> _aboveWidget = { nullptr };
|
||||
object_ptr<Ui::RpWidget> _aboveSearchWidget = { nullptr };
|
||||
object_ptr<Ui::RpWidget> _belowWidget = { nullptr };
|
||||
@@ -1007,6 +1029,10 @@ public:
|
||||
bool highlightRow,
|
||||
Fn<void(not_null<Ui::PopupMenu*>)> destroyed = nullptr) override;
|
||||
|
||||
void peerListSelectSkip(int direction) override {
|
||||
_content->selectSkip(direction);
|
||||
}
|
||||
|
||||
void peerListPressLeftToContextMenu(bool shown) override {
|
||||
_content->pressLeftToContextMenu(shown);
|
||||
}
|
||||
@@ -1080,6 +1106,9 @@ public:
|
||||
|
||||
[[nodiscard]] std::vector<PeerListRowId> collectSelectedIds();
|
||||
[[nodiscard]] std::vector<not_null<PeerData*>> collectSelectedRows();
|
||||
[[nodiscard]] rpl::producer<int> multiSelectHeightValue() const;
|
||||
|
||||
void setSpecialTabMode(bool value);
|
||||
|
||||
void peerListSetTitle(rpl::producer<QString> title) override {
|
||||
setTitle(std::move(title));
|
||||
@@ -1146,4 +1175,11 @@ private:
|
||||
bool _scrollBottomFixed = false;
|
||||
int _addedTopScrollSkip = 0;
|
||||
|
||||
struct SpecialTabsMode final {
|
||||
bool enabled = false;
|
||||
bool searchIsActive = false;
|
||||
int topSkip = 0;
|
||||
};
|
||||
SpecialTabsMode _specialTabsMode;
|
||||
|
||||
};
|
||||
|
||||
@@ -842,6 +842,7 @@ auto ChooseRecipientBoxController::createRow(
|
||||
? !_filter(history)
|
||||
: ((peer->isBroadcast() && !Data::CanSendAnything(peer))
|
||||
|| peer->isRepliesChat()
|
||||
|| peer->isVerifyCodes()
|
||||
|| (peer->isUser() && (_premiumRequiredError
|
||||
? !peer->asUser()->canSendIgnoreRequirePremium()
|
||||
: !Data::CanSendAnything(peer))));
|
||||
@@ -1064,6 +1065,11 @@ std::unique_ptr<PeerListRow> ChooseTopicBoxController::createSearchRow(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListRow> ChooseTopicBoxController::MakeRow(
|
||||
not_null<Data::ForumTopic*> topic) {
|
||||
return std::make_unique<Row>(topic);
|
||||
}
|
||||
|
||||
auto ChooseTopicBoxController::createRow(not_null<Data::ForumTopic*> topic)
|
||||
-> std::unique_ptr<Row> {
|
||||
const auto skip = _filter && !_filter(topic);
|
||||
|
||||
@@ -335,6 +335,9 @@ public:
|
||||
void loadMoreRows() override;
|
||||
std::unique_ptr<PeerListRow> createSearchRow(PeerListRowId id) override;
|
||||
|
||||
[[nodiscard]] static std::unique_ptr<PeerListRow> MakeRow(
|
||||
not_null<Data::ForumTopic*> topic);
|
||||
|
||||
private:
|
||||
class Row final : public PeerListRow {
|
||||
public:
|
||||
|
||||
@@ -170,11 +170,15 @@ void AddBotToGroupBoxController::requestExistingRights(
|
||||
channel);
|
||||
_existingRights = participant.rights().flags;
|
||||
_existingRank = participant.rank();
|
||||
_promotedSince = participant.promotedSince();
|
||||
_promotedBy = participant.by();
|
||||
addBotToGroup(_existingRightsChannel);
|
||||
});
|
||||
}).fail([=] {
|
||||
_existingRights = ChatAdminRights();
|
||||
_existingRank = QString();
|
||||
_promotedSince = 0;
|
||||
_promotedBy = 0;
|
||||
addBotToGroup(_existingRightsChannel);
|
||||
}).send();
|
||||
}
|
||||
@@ -191,6 +195,8 @@ void AddBotToGroupBoxController::addBotToGroup(not_null<PeerData*> chat) {
|
||||
_existingRights = {};
|
||||
_existingRank = QString();
|
||||
_existingRightsChannel = nullptr;
|
||||
_promotedSince = 0;
|
||||
_promotedBy = 0;
|
||||
_bot->session().api().request(_existingRightsRequestId).cancel();
|
||||
}
|
||||
const auto requestedAddAdmin = (_scope == Scope::GroupAdmin)
|
||||
@@ -241,9 +247,12 @@ void AddBotToGroupBoxController::addBotToGroup(not_null<PeerData*> chat) {
|
||||
bot,
|
||||
ChatAdminRightsInfo(rights),
|
||||
_existingRank,
|
||||
_promotedSince,
|
||||
_promotedBy ? chat->owner().user(_promotedBy).get() : nullptr,
|
||||
EditAdminBotFields{
|
||||
_token,
|
||||
_existingRights.value_or(ChatAdminRights()) });
|
||||
_existingRights.value_or(ChatAdminRights()),
|
||||
});
|
||||
box->setSaveCallback(saveCallback);
|
||||
controller->show(std::move(box));
|
||||
} else {
|
||||
|
||||
@@ -65,6 +65,8 @@ private:
|
||||
mtpRequestId _existingRightsRequestId = 0;
|
||||
std::optional<ChatAdminRights> _existingRights;
|
||||
QString _existingRank;
|
||||
TimeId _promotedSince = 0;
|
||||
UserId _promotedBy = 0;
|
||||
|
||||
rpl::event_stream<not_null<PeerData*>> _groups;
|
||||
rpl::event_stream<not_null<PeerData*>> _channels;
|
||||
|
||||
@@ -269,7 +269,8 @@ PaintRoundImageCallback ForbiddenRow::generatePaintUserpicCallback(
|
||||
const auto peer = this->peer();
|
||||
const auto saved = peer->isSelf();
|
||||
const auto replies = peer->isRepliesChat();
|
||||
auto userpic = (saved || replies)
|
||||
const auto verifyCodes = peer->isVerifyCodes();
|
||||
auto userpic = (saved || replies || verifyCodes)
|
||||
? Ui::PeerUserpicView()
|
||||
: ensureUserpicView();
|
||||
auto paint = [=](
|
||||
@@ -302,6 +303,7 @@ PaintRoundImageCallback ForbiddenRow::generatePaintUserpicCallback(
|
||||
repaint = (_paletteVersion != style::PaletteVersion())
|
||||
|| (!saved
|
||||
&& !replies
|
||||
&& !verifyCodes
|
||||
&& (_userpicKey != peer->userpicUniqueKey(userpic)));
|
||||
}
|
||||
if (repaint) {
|
||||
@@ -1276,7 +1278,9 @@ void AddSpecialBoxController::showAdmin(
|
||||
_peer,
|
||||
user,
|
||||
currentRights,
|
||||
_additional.adminRank(user));
|
||||
_additional.adminRank(user),
|
||||
_additional.adminPromotedSince(user),
|
||||
_additional.adminPromotedBy(user));
|
||||
const auto show = delegate()->peerListUiShow();
|
||||
if (_additional.canAddOrEditAdmin(user)) {
|
||||
const auto done = crl::guard(this, [=](
|
||||
@@ -1354,7 +1358,9 @@ void AddSpecialBoxController::showRestricted(
|
||||
_peer,
|
||||
user,
|
||||
_additional.adminRights(user).has_value(),
|
||||
currentRights);
|
||||
currentRights,
|
||||
_additional.restrictedBy(user),
|
||||
_additional.restrictedSince(user));
|
||||
if (_additional.canRestrictParticipant(user)) {
|
||||
const auto done = crl::guard(this, [=](
|
||||
ChatRestrictionsInfo newRights) {
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/controls/userpic_button.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
@@ -63,6 +64,10 @@ public:
|
||||
template <typename Widget>
|
||||
Widget *addControl(object_ptr<Widget> widget, QMargins margin);
|
||||
|
||||
[[nodiscard]] not_null<Ui::VerticalLayout*> verticalLayout() const {
|
||||
return _rows;
|
||||
}
|
||||
|
||||
protected:
|
||||
int resizeGetHeight(int newWidth) override;
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
@@ -164,6 +169,10 @@ EditParticipantBox::EditParticipantBox(
|
||||
, _hasAdminRights(hasAdminRights) {
|
||||
}
|
||||
|
||||
not_null<Ui::VerticalLayout*> EditParticipantBox::verticalLayout() const {
|
||||
return _inner->verticalLayout();
|
||||
}
|
||||
|
||||
void EditParticipantBox::prepare() {
|
||||
_inner = setInnerWidget(object_ptr<Inner>(
|
||||
this,
|
||||
@@ -197,6 +206,8 @@ EditAdminBox::EditAdminBox(
|
||||
not_null<UserData*> user,
|
||||
ChatAdminRightsInfo rights,
|
||||
const QString &rank,
|
||||
TimeId promotedSince,
|
||||
UserData *by,
|
||||
std::optional<EditAdminBotFields> addingBot)
|
||||
: EditParticipantBox(
|
||||
nullptr,
|
||||
@@ -205,6 +216,8 @@ EditAdminBox::EditAdminBox(
|
||||
(rights.flags != 0))
|
||||
, _oldRights(rights)
|
||||
, _oldRank(rank)
|
||||
, _promotedSince(promotedSince)
|
||||
, _by(by)
|
||||
, _addingBot(std::move(addingBot)) {
|
||||
}
|
||||
|
||||
@@ -279,9 +292,26 @@ void EditAdminBox::prepare() {
|
||||
object_ptr<Ui::VerticalLayout>(this)));
|
||||
const auto inner = _adminControlsWrap->entity();
|
||||
|
||||
inner->add(
|
||||
object_ptr<Ui::BoxContentDivider>(inner),
|
||||
st::rightsDividerMargin);
|
||||
if (_promotedSince) {
|
||||
const auto parsed = base::unixtime::parse(_promotedSince);
|
||||
const auto label = Ui::AddDividerText(
|
||||
inner,
|
||||
tr::lng_rights_about_by(
|
||||
lt_user,
|
||||
rpl::single(_by
|
||||
? Ui::Text::Link(_by->name(), 1)
|
||||
: TextWithEntities{ QString::fromUtf8("\U0001F47B") }),
|
||||
lt_date,
|
||||
rpl::single(TextWithEntities{ langDateTimeFull(parsed) }),
|
||||
Ui::Text::WithEntities));
|
||||
if (_by) {
|
||||
label->setLink(1, _by->createOpenLink());
|
||||
}
|
||||
Ui::AddSkip(inner);
|
||||
} else {
|
||||
Ui::AddDivider(inner);
|
||||
Ui::AddSkip(inner);
|
||||
}
|
||||
|
||||
const auto chat = peer()->asChat();
|
||||
const auto channel = peer()->asChannel();
|
||||
@@ -335,9 +365,9 @@ void EditAdminBox::prepare() {
|
||||
.isForum = peer()->isForum(),
|
||||
.anyoneCanAddMembers = anyoneCanAddMembers,
|
||||
};
|
||||
Ui::AddSubsectionTitle(inner, tr::lng_rights_edit_admin_header());
|
||||
auto [checkboxes, getChecked, changes] = CreateEditAdminRights(
|
||||
inner,
|
||||
tr::lng_rights_edit_admin_header(),
|
||||
prepareFlags,
|
||||
disabledMessages,
|
||||
options);
|
||||
@@ -348,17 +378,47 @@ void EditAdminBox::prepare() {
|
||||
) | rpl::then(std::move(
|
||||
changes
|
||||
));
|
||||
_aboutAddAdmins = inner->add(
|
||||
object_ptr<Ui::FlatLabel>(inner, st::boxDividerLabel),
|
||||
st::rightsAboutMargin);
|
||||
rpl::duplicate(
|
||||
selectedFlags
|
||||
) | rpl::map(
|
||||
(_1 & Flag::AddAdmins) != 0
|
||||
) | rpl::distinct_until_changed(
|
||||
) | rpl::start_with_next([=](bool checked) {
|
||||
refreshAboutAddAdminsText(checked);
|
||||
}, lifetime());
|
||||
|
||||
const auto hasRank = canSave() && (chat || channel->isMegagroup());
|
||||
|
||||
{
|
||||
const auto aboutAddAdminsInner = inner->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
inner,
|
||||
object_ptr<Ui::VerticalLayout>(inner)));
|
||||
const auto emptyAboutAddAdminsInner = inner->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
inner,
|
||||
object_ptr<Ui::VerticalLayout>(inner)));
|
||||
aboutAddAdminsInner->toggle(false, anim::type::instant);
|
||||
emptyAboutAddAdminsInner->toggle(false, anim::type::instant);
|
||||
Ui::AddSkip(emptyAboutAddAdminsInner->entity());
|
||||
if (hasRank) {
|
||||
Ui::AddDivider(emptyAboutAddAdminsInner->entity());
|
||||
Ui::AddSkip(emptyAboutAddAdminsInner->entity());
|
||||
}
|
||||
Ui::AddSkip(aboutAddAdminsInner->entity());
|
||||
Ui::AddDividerText(
|
||||
aboutAddAdminsInner->entity(),
|
||||
rpl::duplicate(
|
||||
selectedFlags
|
||||
) | rpl::map(
|
||||
(_1 & Flag::AddAdmins) != 0
|
||||
) | rpl::distinct_until_changed(
|
||||
) | rpl::map([=](bool canAddAdmins) -> rpl::producer<QString> {
|
||||
const auto empty = (amCreator() && user()->isSelf());
|
||||
aboutAddAdminsInner->toggle(!empty, anim::type::instant);
|
||||
emptyAboutAddAdminsInner->toggle(empty, anim::type::instant);
|
||||
if (empty) {
|
||||
return rpl::single(QString());
|
||||
} else if (!canSave()) {
|
||||
return tr::lng_rights_about_admin_cant_edit();
|
||||
} else if (canAddAdmins) {
|
||||
return tr::lng_rights_about_add_admins_yes();
|
||||
}
|
||||
return tr::lng_rights_about_add_admins_no();
|
||||
}) | rpl::flatten_latest());
|
||||
}
|
||||
|
||||
if (canTransferOwnership()) {
|
||||
const auto allFlags = AdminRightsForOwnershipTransfer(options);
|
||||
@@ -373,9 +433,7 @@ void EditAdminBox::prepare() {
|
||||
}
|
||||
|
||||
if (canSave()) {
|
||||
_rank = (chat || channel->isMegagroup())
|
||||
? addRankInput(inner).get()
|
||||
: nullptr;
|
||||
_rank = hasRank ? addRankInput(inner).get() : nullptr;
|
||||
_finishSave = [=, value = getChecked] {
|
||||
const auto newFlags = (value() | ChatAdminRight::Other)
|
||||
& ((!channel || channel->amCreator())
|
||||
@@ -441,9 +499,7 @@ void EditAdminBox::refreshButtons() {
|
||||
|
||||
not_null<Ui::InputField*> EditAdminBox::addRankInput(
|
||||
not_null<Ui::VerticalLayout*> container) {
|
||||
container->add(
|
||||
object_ptr<Ui::BoxContentDivider>(container),
|
||||
st::rightsRankMargin);
|
||||
// Ui::AddDivider(container);
|
||||
|
||||
container->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
@@ -480,14 +536,13 @@ not_null<Ui::InputField*> EditAdminBox::addRankInput(
|
||||
}
|
||||
}, result->lifetime());
|
||||
|
||||
container->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
container,
|
||||
tr::lng_rights_edit_admin_rank_about(
|
||||
lt_title,
|
||||
(isOwner ? tr::lng_owner_badge : tr::lng_admin_badge)()),
|
||||
st::boxDividerLabel),
|
||||
st::rightsAboutMargin);
|
||||
Ui::AddSkip(container);
|
||||
Ui::AddDividerText(
|
||||
container,
|
||||
tr::lng_rights_edit_admin_rank_about(
|
||||
lt_title,
|
||||
(isOwner ? tr::lng_owner_badge : tr::lng_admin_badge)()));
|
||||
Ui::AddSkip(container);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -681,27 +736,18 @@ void EditAdminBox::sendTransferRequestFrom(
|
||||
})).handleFloodErrors().send();
|
||||
}
|
||||
|
||||
void EditAdminBox::refreshAboutAddAdminsText(bool canAddAdmins) {
|
||||
_aboutAddAdmins->setText([&] {
|
||||
if (amCreator() && user()->isSelf()) {
|
||||
return QString();
|
||||
} else if (!canSave()) {
|
||||
return tr::lng_rights_about_admin_cant_edit(tr::now);
|
||||
} else if (canAddAdmins) {
|
||||
return tr::lng_rights_about_add_admins_yes(tr::now);
|
||||
}
|
||||
return tr::lng_rights_about_add_admins_no(tr::now);
|
||||
}());
|
||||
}
|
||||
|
||||
EditRestrictedBox::EditRestrictedBox(
|
||||
QWidget*,
|
||||
not_null<PeerData*> peer,
|
||||
not_null<UserData*> user,
|
||||
bool hasAdminRights,
|
||||
ChatRestrictionsInfo rights)
|
||||
ChatRestrictionsInfo rights,
|
||||
UserData *by,
|
||||
TimeId since)
|
||||
: EditParticipantBox(nullptr, peer, user, hasAdminRights)
|
||||
, _oldRights(rights) {
|
||||
, _oldRights(rights)
|
||||
, _by(by)
|
||||
, _since(since) {
|
||||
}
|
||||
|
||||
void EditRestrictedBox::prepare() {
|
||||
@@ -712,9 +758,8 @@ void EditRestrictedBox::prepare() {
|
||||
|
||||
setTitle(tr::lng_rights_user_restrictions());
|
||||
|
||||
addControl(
|
||||
object_ptr<Ui::BoxContentDivider>(this),
|
||||
st::rightsDividerMargin);
|
||||
Ui::AddDivider(verticalLayout());
|
||||
Ui::AddSkip(verticalLayout());
|
||||
|
||||
const auto chat = peer()->asChat();
|
||||
const auto channel = peer()->asChannel();
|
||||
@@ -749,16 +794,20 @@ void EditRestrictedBox::prepare() {
|
||||
return result;
|
||||
}();
|
||||
|
||||
Ui::AddSubsectionTitle(
|
||||
verticalLayout(),
|
||||
tr::lng_rights_user_restrictions_header());
|
||||
auto [checkboxes, getRestrictions, changes] = CreateEditRestrictions(
|
||||
this,
|
||||
tr::lng_rights_user_restrictions_header(),
|
||||
prepareFlags,
|
||||
disabledMessages,
|
||||
{ .isForum = peer()->isForum() });
|
||||
addControl(std::move(checkboxes), QMargins());
|
||||
|
||||
_until = prepareRights.until;
|
||||
addControl(object_ptr<Ui::BoxContentDivider>(this), st::rightsUntilMargin);
|
||||
addControl(
|
||||
object_ptr<Ui::FixedHeightWidget>(this, st::defaultVerticalListSkip));
|
||||
Ui::AddDivider(verticalLayout());
|
||||
addControl(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
this,
|
||||
@@ -773,6 +822,29 @@ void EditRestrictedBox::prepare() {
|
||||
// tr::lng_rights_chat_banned_block(tr::now),
|
||||
// st::boxLinkButton));
|
||||
|
||||
if (_since) {
|
||||
const auto parsed = base::unixtime::parse(_since);
|
||||
const auto inner = addControl(object_ptr<Ui::VerticalLayout>(this));
|
||||
const auto isBanned = (_oldRights.flags
|
||||
& ChatRestriction::ViewMessages);
|
||||
Ui::AddSkip(inner);
|
||||
const auto label = Ui::AddDividerText(
|
||||
inner,
|
||||
(isBanned
|
||||
? tr::lng_rights_chat_banned_by
|
||||
: tr::lng_rights_chat_restricted_by)(
|
||||
lt_user,
|
||||
rpl::single(_by
|
||||
? Ui::Text::Link(_by->name(), 1)
|
||||
: TextWithEntities{ QString::fromUtf8("\U0001F47B") }),
|
||||
lt_date,
|
||||
rpl::single(TextWithEntities{ langDateTimeFull(parsed) }),
|
||||
Ui::Text::WithEntities));
|
||||
if (_by) {
|
||||
label->setLink(1, _by->createOpenLink());
|
||||
}
|
||||
}
|
||||
|
||||
if (canSave()) {
|
||||
const auto save = [=, value = getRestrictions] {
|
||||
if (!_saveCallback) {
|
||||
|
||||
@@ -36,6 +36,8 @@ public:
|
||||
not_null<UserData*> user,
|
||||
bool hasAdminRights);
|
||||
|
||||
[[nodiscard]] not_null<Ui::VerticalLayout*> verticalLayout() const;
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
|
||||
@@ -77,6 +79,8 @@ public:
|
||||
not_null<UserData*> user,
|
||||
ChatAdminRightsInfo rights,
|
||||
const QString &rank,
|
||||
TimeId promotedSince,
|
||||
UserData *by,
|
||||
std::optional<EditAdminBotFields> addingBot = {});
|
||||
|
||||
void setSaveCallback(
|
||||
@@ -108,7 +112,6 @@ private:
|
||||
}
|
||||
void finishAddAdmin();
|
||||
void refreshButtons();
|
||||
void refreshAboutAddAdminsText(bool canAddAdmins);
|
||||
bool canTransferOwnership() const;
|
||||
not_null<Ui::SlideWrap<Ui::RpWidget>*> setupTransferButton(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
@@ -125,11 +128,12 @@ private:
|
||||
Ui::Checkbox *_addAsAdmin = nullptr;
|
||||
Ui::SlideWrap<Ui::VerticalLayout> *_adminControlsWrap = nullptr;
|
||||
Ui::InputField *_rank = nullptr;
|
||||
QPointer<Ui::FlatLabel> _aboutAddAdmins;
|
||||
mtpRequestId _checkTransferRequestId = 0;
|
||||
mtpRequestId _transferRequestId = 0;
|
||||
Fn<void()> _save, _finishSave;
|
||||
|
||||
TimeId _promotedSince = 0;
|
||||
UserData *_by = nullptr;
|
||||
std::optional<EditAdminBotFields> _addingBot;
|
||||
|
||||
};
|
||||
@@ -144,7 +148,9 @@ public:
|
||||
not_null<PeerData*> peer,
|
||||
not_null<UserData*> user,
|
||||
bool hasAdminRights,
|
||||
ChatRestrictionsInfo rights);
|
||||
ChatRestrictionsInfo rights,
|
||||
UserData *by,
|
||||
TimeId since);
|
||||
|
||||
void setSaveCallback(
|
||||
Fn<void(ChatRestrictionsInfo, ChatRestrictionsInfo)> callback) {
|
||||
@@ -168,6 +174,8 @@ private:
|
||||
TimeId getRealUntilValue() const;
|
||||
|
||||
const ChatRestrictionsInfo _oldRights;
|
||||
UserData *_by = nullptr;
|
||||
TimeId _since = 0;
|
||||
TimeId _until = 0;
|
||||
Fn<void(ChatRestrictionsInfo, ChatRestrictionsInfo)> _saveCallback;
|
||||
|
||||
|
||||
@@ -30,10 +30,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_changes.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "ui/effects/outline_segments.h"
|
||||
#include "ui/widgets/menu/menu_multiline_action.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "info/profile/info_profile_values.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "history/history.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
|
||||
namespace {
|
||||
@@ -387,6 +390,24 @@ QString ParticipantsAdditionalData::adminRank(
|
||||
return (i != end(_adminRanks)) ? i->second : QString();
|
||||
}
|
||||
|
||||
TimeId ParticipantsAdditionalData::adminPromotedSince(
|
||||
not_null<UserData*> user) const {
|
||||
const auto i = _adminPromotedSince.find(user);
|
||||
return (i != end(_adminPromotedSince)) ? i->second : TimeId(0);
|
||||
}
|
||||
|
||||
TimeId ParticipantsAdditionalData::restrictedSince(
|
||||
not_null<PeerData*> peer) const {
|
||||
const auto i = _restrictedSince.find(peer);
|
||||
return (i != end(_restrictedSince)) ? i->second : TimeId(0);
|
||||
}
|
||||
|
||||
TimeId ParticipantsAdditionalData::memberSince(
|
||||
not_null<UserData*> user) const {
|
||||
const auto i = _memberSince.find(user);
|
||||
return (i != end(_memberSince)) ? i->second : TimeId(0);
|
||||
}
|
||||
|
||||
auto ParticipantsAdditionalData::restrictedRights(
|
||||
not_null<PeerData*> participant) const
|
||||
-> std::optional<ChatRestrictionsInfo> {
|
||||
@@ -689,6 +710,11 @@ UserData *ParticipantsAdditionalData::applyAdmin(
|
||||
} else {
|
||||
_adminRanks.remove(user);
|
||||
}
|
||||
if (data.promotedSince()) {
|
||||
_adminPromotedSince[user] = data.promotedSince();
|
||||
} else {
|
||||
_adminPromotedSince.remove(user);
|
||||
}
|
||||
if (const auto by = _peer->owner().userLoaded(data.by())) {
|
||||
const auto i = _adminPromotedBy.find(user);
|
||||
if (i == _adminPromotedBy.end()) {
|
||||
@@ -741,6 +767,11 @@ PeerData *ParticipantsAdditionalData::applyBanned(
|
||||
} else {
|
||||
_kicked.erase(participant);
|
||||
}
|
||||
if (data.restrictedSince()) {
|
||||
_restrictedSince[participant] = data.restrictedSince();
|
||||
} else {
|
||||
_restrictedSince.remove(participant);
|
||||
}
|
||||
_restrictedRights[participant] = data.restrictions();
|
||||
if (const auto by = _peer->owner().userLoaded(data.by())) {
|
||||
const auto i = _restrictedBy.find(participant);
|
||||
@@ -1617,6 +1648,51 @@ base::unique_qptr<Ui::PopupMenu> ParticipantsBoxController::rowContextMenu(
|
||||
auto result = base::make_unique_q<Ui::PopupMenu>(
|
||||
parent,
|
||||
st::popupMenuWithIcons);
|
||||
const auto addToEnd = gsl::finally([&] {
|
||||
const auto addInfoAction = [&](
|
||||
not_null<PeerData*> by,
|
||||
tr::phrase<lngtag_user, lngtag_date> phrase,
|
||||
TimeId since) {
|
||||
auto text = phrase(
|
||||
tr::now,
|
||||
lt_user,
|
||||
Ui::Text::Bold(by->name()),
|
||||
lt_date,
|
||||
Ui::Text::Bold(
|
||||
langDateTimeFull(base::unixtime::parse(since))),
|
||||
Ui::Text::WithEntities);
|
||||
auto button = base::make_unique_q<Ui::Menu::MultilineAction>(
|
||||
result->menu(),
|
||||
result->st().menu,
|
||||
st::historyHasCustomEmoji,
|
||||
st::historyHasCustomEmojiPosition,
|
||||
std::move(text));
|
||||
if (const auto n = _navigation) {
|
||||
button->setClickedCallback([=] {
|
||||
n->parentController()->show(PrepareShortInfoBox(by, n));
|
||||
});
|
||||
}
|
||||
result->addSeparator();
|
||||
result->addAction(std::move(button));
|
||||
};
|
||||
|
||||
if (const auto by = _additional.restrictedBy(participant)) {
|
||||
if (const auto since = _additional.restrictedSince(participant)) {
|
||||
addInfoAction(
|
||||
by,
|
||||
_additional.isKicked(participant)
|
||||
? tr::lng_rights_chat_banned_by
|
||||
: tr::lng_rights_chat_restricted_by,
|
||||
since);
|
||||
}
|
||||
} else if (user) {
|
||||
if (const auto by = _additional.adminPromotedBy(user)) {
|
||||
if (const auto since = _additional.adminPromotedSince(user)) {
|
||||
addInfoAction(by, tr::lng_rights_about_by, since);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (_navigation) {
|
||||
result->addAction(
|
||||
(participant->isUser()
|
||||
@@ -1624,39 +1700,14 @@ base::unique_qptr<Ui::PopupMenu> ParticipantsBoxController::rowContextMenu(
|
||||
: participant->isBroadcast()
|
||||
? tr::lng_context_view_channel
|
||||
: tr::lng_context_view_group)(tr::now),
|
||||
crl::guard(this, [=] {
|
||||
_navigation->showPeerInfo(participant); }),
|
||||
crl::guard(this, [=, this] {
|
||||
_navigation->parentController()->show(
|
||||
PrepareShortInfoBox(participant, _navigation));
|
||||
}),
|
||||
(participant->isUser()
|
||||
? &st::menuIconProfile
|
||||
: &st::menuIconInfo));
|
||||
}
|
||||
if (const auto by = _additional.restrictedBy(participant)) {
|
||||
result->addAction(
|
||||
(_role == Role::Kicked
|
||||
? tr::lng_channel_banned_status_removed_by
|
||||
: tr::lng_channel_banned_status_restricted_by)(
|
||||
tr::now,
|
||||
lt_user,
|
||||
by->name()),
|
||||
crl::guard(this, [=] {
|
||||
_navigation->parentController()->show(
|
||||
PrepareShortInfoBox(by, _navigation));
|
||||
}),
|
||||
&st::menuIconAdmin);
|
||||
} else if (user) {
|
||||
if (const auto by = _additional.adminPromotedBy(user)) {
|
||||
result->addAction(
|
||||
tr::lng_channel_admin_status_promoted_by(
|
||||
tr::now,
|
||||
lt_user,
|
||||
by->name()),
|
||||
crl::guard(this, [=] {
|
||||
_navigation->parentController()->show(
|
||||
PrepareShortInfoBox(by, _navigation));
|
||||
}),
|
||||
&st::menuIconAdmin);
|
||||
}
|
||||
}
|
||||
if (_role == Role::Kicked) {
|
||||
if (_peer->isMegagroup()
|
||||
&& _additional.canRestrictParticipant(participant)) {
|
||||
@@ -1720,7 +1771,9 @@ void ParticipantsBoxController::showAdmin(not_null<UserData*> user) {
|
||||
_peer,
|
||||
user,
|
||||
currentRights,
|
||||
_additional.adminRank(user));
|
||||
_additional.adminRank(user),
|
||||
_additional.adminPromotedSince(user),
|
||||
_additional.adminPromotedBy(user));
|
||||
if (_additional.canAddOrEditAdmin(user)) {
|
||||
const auto done = crl::guard(this, [=](
|
||||
ChatAdminRightsInfo newRights,
|
||||
@@ -1776,7 +1829,9 @@ void ParticipantsBoxController::showRestricted(not_null<UserData*> user) {
|
||||
_peer,
|
||||
user,
|
||||
hasAdminRights,
|
||||
currentRights);
|
||||
currentRights,
|
||||
_additional.restrictedBy(user),
|
||||
_additional.restrictedSince(user));
|
||||
if (_additional.canRestrictParticipant(user)) {
|
||||
const auto done = crl::guard(this, [=](
|
||||
ChatRestrictionsInfo newRights) {
|
||||
|
||||
@@ -106,14 +106,19 @@ public:
|
||||
not_null<PeerData*> participant) const;
|
||||
[[nodiscard]] std::optional<ChatAdminRightsInfo> adminRights(
|
||||
not_null<UserData*> user) const;
|
||||
QString adminRank(not_null<UserData*> user) const;
|
||||
[[nodiscard]] QString adminRank(not_null<UserData*> user) const;
|
||||
[[nodiscard]] std::optional<ChatRestrictionsInfo> restrictedRights(
|
||||
not_null<PeerData*> participant) const;
|
||||
[[nodiscard]] bool isCreator(not_null<UserData*> user) const;
|
||||
[[nodiscard]] bool isExternal(not_null<PeerData*> participant) const;
|
||||
[[nodiscard]] bool isKicked(not_null<PeerData*> participant) const;
|
||||
[[nodiscard]] UserData *adminPromotedBy(not_null<UserData*> user) const;
|
||||
[[nodiscard]] UserData *restrictedBy(not_null<PeerData*> participant) const;
|
||||
[[nodiscard]] UserData *restrictedBy(
|
||||
not_null<PeerData*> participant) const;
|
||||
|
||||
[[nodiscard]] TimeId adminPromotedSince(not_null<UserData*>) const;
|
||||
[[nodiscard]] TimeId restrictedSince(not_null<PeerData*>) const;
|
||||
[[nodiscard]] TimeId memberSince(not_null<UserData*>) const;
|
||||
|
||||
void migrate(not_null<ChatData*> chat, not_null<ChannelData*> channel);
|
||||
|
||||
@@ -144,6 +149,9 @@ private:
|
||||
// Data for channels.
|
||||
base::flat_map<not_null<UserData*>, ChatAdminRightsInfo> _adminRights;
|
||||
base::flat_map<not_null<UserData*>, QString> _adminRanks;
|
||||
base::flat_map<not_null<UserData*>, TimeId> _adminPromotedSince;
|
||||
base::flat_map<not_null<PeerData*>, TimeId> _restrictedSince;
|
||||
base::flat_map<not_null<UserData*>, TimeId> _memberSince;
|
||||
base::flat_set<not_null<UserData*>> _adminCanEdit;
|
||||
base::flat_map<not_null<UserData*>, not_null<UserData*>> _adminPromotedBy;
|
||||
std::map<not_null<PeerData*>, ChatRestrictionsInfo> _restrictedRights;
|
||||
|
||||