Compare commits
479 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c86ea4807a | |||
| b5c769196a | |||
| 6027bad788 | |||
| 0b1e6bd1de | |||
| 01b8602437 | |||
| cc16922673 | |||
| 379a94db42 | |||
| 4f4d216987 | |||
| 6400875d55 | |||
| 06d0e78b00 | |||
| 9043c18725 | |||
| 88e742927f | |||
| a6fcc6d51d | |||
| 23a13ab54e | |||
| e8a929bdbd | |||
| eafc01e02b | |||
| f817504d67 | |||
| f91eb65239 | |||
| e978770fbd | |||
| 9c83b8bac5 | |||
| 47ce34e987 | |||
| 1656a9c3e2 | |||
| 47e06cf385 | |||
| f61f649a7e | |||
| a7bffe7abd | |||
| 51b866293f | |||
| bd20a3cfe4 | |||
| 86778aa4d9 | |||
| 12eecec501 | |||
| 26345208a9 | |||
| ee680ac1f1 | |||
| bb79a07262 | |||
| 70fe649743 | |||
| 400f0f8785 | |||
| eac7bf1c48 | |||
| f4abe37dff | |||
| b7f165a259 | |||
| c1b95afd88 | |||
| 36766e7546 | |||
| 59c016e4ce | |||
| e9e347fa6c | |||
| 7aef0b0a83 | |||
| d9572949f6 | |||
| 233e80d22d | |||
| 5c83858a50 | |||
| c681569349 | |||
| bb43afdd93 | |||
| 3ba1941808 | |||
| 72d1b43453 | |||
| 0c1b487956 | |||
| ba611d0f2d | |||
| 50ce847b31 | |||
| dd0d88ccd3 | |||
| a1049ec7ce | |||
| 0fffeac8da | |||
| 1f0acae151 | |||
| 521c17b76c | |||
| f9f51b4e41 | |||
| 4e8895ddd9 | |||
| ad342a5324 | |||
| 5cfd86b829 | |||
| 27eb3e45be | |||
| 4953246c5d | |||
| 4df5372dab | |||
| 40fbd415ef | |||
| 974bf99921 | |||
| 8c0351be4e | |||
| 67f7816088 | |||
| 924d80ecba | |||
| d219bccf2b | |||
| 02bd2bca64 | |||
| 57ecc2be1d | |||
| d3a01b6235 | |||
| 58c060c59d | |||
| cd7507fb23 | |||
| 9a5923676a | |||
| c0f3d263a3 | |||
| 056ba644ed | |||
| ebaffc333e | |||
| be099880d8 | |||
| 885dcf0b28 | |||
| a0d97f03cb | |||
| c942034ca4 | |||
| 0bd780b20f | |||
| 7d75c25214 | |||
| 5defb9fb17 | |||
| 0549c8f037 | |||
| 3c246e1e92 | |||
| 58da617e3f | |||
| 1edf0ed70b | |||
| c27c567225 | |||
| 93eff78cd6 | |||
| a2a27e115c | |||
| e9fb580ba4 | |||
| d73313479b | |||
| e4e343b871 | |||
| dda6b92bec | |||
| 3dd894ad30 | |||
| f08ff92470 | |||
| 923aaec085 | |||
| 1d3110228d | |||
| 7194781bb8 | |||
| 97a5e0c6ea | |||
| a3ef36f9f7 | |||
| d13bf19b79 | |||
| 493f0450b4 | |||
| 74861a334d | |||
| a87a221f26 | |||
| 923a9ec6a8 | |||
| b299881bf8 | |||
| 5ee2bca616 | |||
| d1e914fb30 | |||
| 43cb315f47 | |||
| dcc52a7333 | |||
| 84cde1354d | |||
| 3d81414c71 | |||
| 69c48e2b5b | |||
| 5ca9b74142 | |||
| e11755af46 | |||
| 174fb62c32 | |||
| 0e30e306ff | |||
| 5e29f382cd | |||
| 8eb24f620d | |||
| 5adde6c93a | |||
| 42d6d0d58a | |||
| 6336df2bd6 | |||
| 030d35ea7e | |||
| d81c3554cc | |||
| ca37ffa086 | |||
| 154fe63b43 | |||
| 65384d54f1 | |||
| 2bf8cb84d0 | |||
| f0a82de784 | |||
| 1a393ddebb | |||
| 9b11b95c5b | |||
| d0bfee6963 | |||
| f1636de572 | |||
| b5eb195f43 | |||
| 4a0bffe618 | |||
| 53d97b4146 | |||
| 2a224c839e | |||
| bc7aa91fbb | |||
| ac2f35f12b | |||
| 39e03c3ca7 | |||
| 1ce49df123 | |||
| 1865fd382c | |||
| e00c6ecfb8 | |||
| 279db771cf | |||
| 7b7438cd7b | |||
| 42d53e5543 | |||
| fce520c9c0 | |||
| b21bcb86cc | |||
| 7d61ab9412 | |||
| 701bf0d553 | |||
| 583bcca6a9 | |||
| 6a8edefc87 | |||
| dd5643ac67 | |||
| 787cf7853e | |||
| 5bfbae3afc | |||
| 363b700f1f | |||
| cde70b9807 | |||
| f7ab8a2174 | |||
| a011a7c316 | |||
| 487fa9728a | |||
| 8a58ded582 | |||
| ec5d8b7373 | |||
| 732b67ca04 | |||
| d102d256a9 | |||
| bbb3a51b74 | |||
| d0d1ef9e66 | |||
| b01244fc42 | |||
| b92a05011f | |||
| 5fb7992b04 | |||
| f371cd1af2 | |||
| 144109db05 | |||
| e120ae6ae6 | |||
| d1106e5ae6 | |||
| 396ba9a984 | |||
| 92133e7f50 | |||
| a19e71324b | |||
| f762634036 | |||
| ee4f83ffde | |||
| f8188f360a | |||
| 2bbc7406da | |||
| a9dd9aeb90 | |||
| aa4156d1e7 | |||
| 4f5594c8cc | |||
| 0527e9a0f7 | |||
| 28cbb02b20 | |||
| 7d636820ac | |||
| e2b78b673b | |||
| a9a0fe7cf5 | |||
| 2b9e7a6b25 | |||
| d6e827e982 | |||
| d2e6003521 | |||
| 465fc42718 | |||
| 0dd6ff9d9b | |||
| 8a6b1677f4 | |||
| 470b3a2cbd | |||
| a60783eae3 | |||
| de73d8766c | |||
| cd7cfcdf2f | |||
| aee62c7591 | |||
| 0f524ac67d | |||
| f223ae7eee | |||
| 68df8448a2 | |||
| da31fef1ae | |||
| 4427ae4306 | |||
| ef2aa05197 | |||
| e5132e3fe8 | |||
| 3b6870396c | |||
| f6b849e4f7 | |||
| 48e3802565 | |||
| 26ba7e57ce | |||
| 2605e754ff | |||
| 9e85b1aa23 | |||
| 88cd886ec8 | |||
| adc536b81d | |||
| 275327c789 | |||
| 72471c74d0 | |||
| b9f63f80f1 | |||
| 32cd2120ac | |||
| 7db9abf725 | |||
| f09a939a7c | |||
| f1c1c900bf | |||
| a43143d01c | |||
| 7357b40ba1 | |||
| ad6e34f3a4 | |||
| daf30dcab8 | |||
| 0033ad749f | |||
| 62c249015d | |||
| f54c82bcb3 | |||
| 57b2c69ce6 | |||
| 31fcca245f | |||
| f4864cddc9 | |||
| 36db1105a2 | |||
| 364f62660c | |||
| b67a1bca49 | |||
| 000911546b | |||
| e26b8e7ebc | |||
| f03935ad9a | |||
| d91b75b8f8 | |||
| 4d4cf472c8 | |||
| 685c7a865e | |||
| 0551550e61 | |||
| c3fab28e1c | |||
| 74db2167f9 | |||
| cedd8fc546 | |||
| db486e957c | |||
| 1c6f45d8d3 | |||
| ba8bb2d50f | |||
| ec6d6a78cf | |||
| f10f3c08de | |||
| 39c1da6642 | |||
| 84ec2a5f74 | |||
| 5e3e9ba824 | |||
| 198528f79f | |||
| 8a5ad581b8 | |||
| 7dbf8ca1f4 | |||
| df808df8d3 | |||
| 4fddaef112 | |||
| 2bdc204c47 | |||
| 6734f99ba8 | |||
| af4e0178d0 | |||
| 65b62485be | |||
| 98b58c1168 | |||
| ad13048cdc | |||
| a753c1515b | |||
| bd48e2fff2 | |||
| 88ad1be4c3 | |||
| 1f31d8032f | |||
| b5f5aed119 | |||
| 213326a212 | |||
| 264e35a6a8 | |||
| 76137ca1cf | |||
| e4323ecd26 | |||
| b406e2b99b | |||
| c1c49e8ddc | |||
| 41e414af37 | |||
| 2afb69e787 | |||
| 3e7d4af4a0 | |||
| bc11c66777 | |||
| 7a95b38d3c | |||
| 334fd4e951 | |||
| 923e3ee808 | |||
| 3358673ba4 | |||
| d28b9f10b4 | |||
| 6ebbca58e8 | |||
| 15697f4f2b | |||
| 2cb0651b04 | |||
| 1f3a3ec04b | |||
| c1a0172822 | |||
| 593e76479c | |||
| 5d622b906e | |||
| 7e253314ec | |||
| 78a05768d8 | |||
| ddf6514a90 | |||
| 3847b97f80 | |||
| 046bc03037 | |||
| 7ff5f81b88 | |||
| b6de66611c | |||
| 166020723f | |||
| 0bec9f742b | |||
| eeb5e5a206 | |||
| fb444e7dd4 | |||
| c4f0db5bc5 | |||
| a866ba8cdd | |||
| 129d356909 | |||
| 7289292f02 | |||
| 638e88ec79 | |||
| 19ead551d9 | |||
| fccaa587e1 | |||
| 1709fcd0df | |||
| de1f222bac | |||
| 5e1f0e9f7f | |||
| e3a14d432b | |||
| 2b013c108f | |||
| 0194687181 | |||
| bb47f4659d | |||
| 2b580d29ae | |||
| b7e52119bc | |||
| fd633ecb49 | |||
| 9c77346f36 | |||
| 641e74763f | |||
| 2e71427427 | |||
| 95ec0633ed | |||
| 14e8b8fb91 | |||
| d40951f068 | |||
| ab85d18cc8 | |||
| 6becaaa953 | |||
| 26e8c29f40 | |||
| 47800ee02d | |||
| 12a24dd473 | |||
| ad6321d3ae | |||
| 7e071c770f | |||
| a62d1dfa63 | |||
| b3eb1dbc14 | |||
| 3170a45158 | |||
| 32483fa13b | |||
| 9166a1c3a6 | |||
| 6fa0afff37 | |||
| bc649af941 | |||
| 16ce5ef046 | |||
| bb6fd4bc4d | |||
| 63e1731d7c | |||
| b9b7d9e337 | |||
| ef8c07e6eb | |||
| 20a13663a6 | |||
| 2f0aa6ef05 | |||
| 62bd1354dc | |||
| be255f1d09 | |||
| b8b02b2285 | |||
| e0db0642bd | |||
| e81465a54e | |||
| 6e67cfc7be | |||
| c0db5ee98a | |||
| 372b3da09c | |||
| 79532954dc | |||
| aff2be605e | |||
| 363c191a6e | |||
| 2949cdab61 | |||
| 7addcf2d25 | |||
| a272807a99 | |||
| c803603de4 | |||
| e6c22ec1ca | |||
| b3ae843f0e | |||
| 12a78c1f45 | |||
| 612b81ee87 | |||
| e5b91d2f3d | |||
| 82293c98eb | |||
| 629da68cfc | |||
| 643ecd2c2c | |||
| dd1cb00c62 | |||
| 2c4d8418c1 | |||
| 9fcb5d6f31 | |||
| 38fc6bfbb9 | |||
| 804991a69c | |||
| 7388f46adf | |||
| 10c427127e | |||
| c6d034174b | |||
| 471831bcd6 | |||
| b1e64419a5 | |||
| e2f17f1131 | |||
| ae4a73a15b | |||
| a84ca00270 | |||
| c16d820b88 | |||
| ef614150d5 | |||
| b46ca1ec17 | |||
| d5a347ede7 | |||
| 72d5a9b3e0 | |||
| e6d72b4861 | |||
| 3da51b1bc9 | |||
| 17b7db6219 | |||
| 8353180161 | |||
| f675a8dcf7 | |||
| 493f1d69e2 | |||
| 331e8c3ec6 | |||
| 56bce70558 | |||
| d82e48f8e4 | |||
| 97ecc57be8 | |||
| 25bd2b145b | |||
| 7111c92ae7 | |||
| e066cf1589 | |||
| 6f328b2ef8 | |||
| 7a6c55bd8a | |||
| b35d3f57fe | |||
| eef65af173 | |||
| bea715b41c | |||
| 7be68ca82c | |||
| 287d5a7413 | |||
| 1448cea01c | |||
| 768e8b457b | |||
| 05474f4a3f | |||
| 8acada9b0f | |||
| a86e7f035f | |||
| 9caff93c35 | |||
| e6ba85e112 | |||
| 046803dbed | |||
| 615f4b1d1c | |||
| a11535806d | |||
| 34a7169b4f | |||
| 4f365c73ad | |||
| 705bd9693d | |||
| a88770a8ec | |||
| da423b5bd2 | |||
| 56080bd0e4 | |||
| 63f66a1369 | |||
| ca1a30196e | |||
| b2d8e2a1e6 | |||
| 2ab8bb13c5 | |||
| 3a2c5c6d0a | |||
| ed13a325e9 | |||
| f43f99cff2 | |||
| a3b8397361 | |||
| 4fb03e532c | |||
| 645ad5e1bd | |||
| 9036e9e8e3 | |||
| 468d8b04d6 | |||
| 4b98ab1246 | |||
| d33e3dc13a | |||
| df16e7c00b | |||
| 225c0e0af3 | |||
| 5543927042 | |||
| 72b274a2bf | |||
| ac15990b48 | |||
| 7387dfdd9c | |||
| 74a7e7d1b4 | |||
| a0a9de1d18 | |||
| 360366ba9e | |||
| 0180fe9468 | |||
| 19f5d95a3c | |||
| 051ca51d3b | |||
| 2a6ff9203b | |||
| 2e0529bd9a | |||
| e24ab4f1ab | |||
| 3dbadeb232 | |||
| 4cdd939028 | |||
| c11f4efc5c | |||
| e1c21b908c | |||
| a6c1def6fe | |||
| 19ae76d8de | |||
| 56e28feb00 | |||
| b259c566b7 | |||
| 18598f8dca | |||
| 11e4c45969 | |||
| 39658ffe52 | |||
| 2f03b9aa29 | |||
| dc438cff23 | |||
| 18f5521be5 | |||
| bae9af1076 | |||
| 8d2805f226 | |||
| 0ad18c8182 | |||
| 9046b2cafb | |||
| 3d994b58a0 | |||
| 4310c4978e | |||
| 01bfa46729 | |||
| 3510ca7184 | |||
| 8d7845daa1 | |||
| cb5fdac0da |
@@ -0,0 +1,6 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
@@ -1,32 +1,11 @@
|
||||
name: No Response
|
||||
name: Can't reproduce.
|
||||
|
||||
# Both `issue_comment` and `scheduled` event types are required for this Action
|
||||
# to work properly.
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
- cron: '0 3 * * *'
|
||||
|
||||
jobs:
|
||||
waiting-for-answer:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: lee-dohm/no-response@v0.5.0
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
responseRequiredLabel: waiting for answer
|
||||
|
||||
needs-user-action:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: lee-dohm/no-response@v0.5.0
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
responseRequiredLabel: needs user action
|
||||
|
||||
cant-reproduce:
|
||||
if: github.event_name != 'issue_comment'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: lee-dohm/no-response@v0.5.0
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone.
|
||||
uses: actions/checkout@v3.1.0
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin
|
||||
|
||||
- name: Free up some disk space.
|
||||
uses: jlumbroso/free-disk-space@f68fdb76e2ea636224182cfb7377ff9a1708f9b8
|
||||
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
|
||||
|
||||
- name: Docker image build.
|
||||
run: |
|
||||
|
||||
@@ -49,7 +49,6 @@ jobs:
|
||||
defines:
|
||||
- ""
|
||||
- "DESKTOP_APP_DISABLE_X11_INTEGRATION"
|
||||
- "DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION"
|
||||
|
||||
env:
|
||||
UPLOAD_ARTIFACT: "true"
|
||||
@@ -59,7 +58,7 @@ jobs:
|
||||
run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
|
||||
|
||||
- name: Clone.
|
||||
uses: actions/checkout@v3.1.0
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
path: ${{ env.REPO_NAME }}
|
||||
@@ -117,7 +116,7 @@ jobs:
|
||||
cd $REPO_NAME/out/Debug
|
||||
sudo mkdir artifact
|
||||
sudo mv {Telegram,Updater} artifact/
|
||||
- uses: actions/upload-artifact@master
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
name: Upload artifact.
|
||||
with:
|
||||
|
||||
@@ -8,7 +8,7 @@ jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v3
|
||||
- uses: dessant/lock-threads@v5
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-inactive-days: 45
|
||||
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
|
||||
|
||||
- name: Clone.
|
||||
uses: actions/checkout@v3.1.0
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
path: ${{ env.REPO_NAME }}
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
|
||||
- name: ThirdParty cache.
|
||||
id: cache-third-party
|
||||
uses: actions/cache@v3.0.11
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ThirdParty
|
||||
key: ${{ runner.OS }}-third-party-${{ hashFiles(format('{0}/{1}', env.REPO_NAME, env.PREPARE_PATH)) }}
|
||||
@@ -84,7 +84,7 @@ jobs:
|
||||
|
||||
- name: Libraries cache.
|
||||
id: cache-libs
|
||||
uses: actions/cache@v3.0.11
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: Libraries
|
||||
key: ${{ runner.OS }}-libs-${{ hashFiles(format('{0}/{1}', env.REPO_NAME, env.PREPARE_PATH)) }}
|
||||
@@ -134,7 +134,7 @@ jobs:
|
||||
mkdir artifact
|
||||
mv Telegram.app artifact/
|
||||
mv Updater artifact/
|
||||
- uses: actions/upload-artifact@master
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
name: Upload artifact.
|
||||
with:
|
||||
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
|
||||
env:
|
||||
GIT: "https://github.com"
|
||||
OPENALDIR: "/usr/local/opt/openal-soft"
|
||||
CMAKE_PREFIX_PATH: "/usr/local/opt/ffmpeg@6:/usr/local/opt/openal-soft"
|
||||
UPLOAD_ARTIFACT: "true"
|
||||
ONLY_CACHE: "false"
|
||||
MANUAL_CACHING: "1"
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
|
||||
|
||||
- name: Clone.
|
||||
uses: actions/checkout@v3.1.0
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
path: ${{ env.REPO_NAME }}
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
run: |
|
||||
brew update
|
||||
brew upgrade || true
|
||||
brew install autoconf automake boost cmake ffmpeg openal-soft openssl opus ninja pkg-config python qt yasm xz
|
||||
brew install autoconf automake boost cmake ffmpeg@6 openal-soft openssl opus ninja pkg-config python qt yasm xz
|
||||
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
|
||||
|
||||
xcodebuild -version > CACHE_KEY.txt
|
||||
@@ -99,7 +99,7 @@ jobs:
|
||||
|
||||
- name: WebRTC cache.
|
||||
id: cache-webrtc
|
||||
uses: actions/cache@v3.0.11
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/tg_owt
|
||||
key: ${{ runner.OS }}-webrtc-${{ env.CACHE_KEY }}-${{ hashFiles('**/tg_owt-version.json') }}
|
||||
@@ -153,7 +153,7 @@ jobs:
|
||||
cd $REPO_NAME/build
|
||||
mkdir artifact
|
||||
mv Telegram.dmg artifact/
|
||||
- uses: actions/upload-artifact@master
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
name: Upload artifact.
|
||||
with:
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
SKIP: "0"
|
||||
to_branch: "master"
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
if: env.SKIP == '0'
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
name: Needs user action.
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
schedule:
|
||||
- cron: '0 2 * * *'
|
||||
|
||||
jobs:
|
||||
needs-user-action:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: lee-dohm/no-response@v0.5.0
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
responseRequiredLabel: needs user action
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone.
|
||||
uses: actions/checkout@v3.1.0
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
@@ -61,10 +61,10 @@ jobs:
|
||||
sudo snap run lxd waitready
|
||||
|
||||
- name: Free up some disk space.
|
||||
uses: jlumbroso/free-disk-space@f68fdb76e2ea636224182cfb7377ff9a1708f9b8
|
||||
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
|
||||
|
||||
- name: Telegram Desktop snap build.
|
||||
run: sg lxd -c 'snap run snapcraft -v'
|
||||
run: sg lxd -c 'snap run snapcraft --verbosity=debug'
|
||||
|
||||
- name: Move artifact.
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
@@ -75,7 +75,7 @@ jobs:
|
||||
mkdir artifact
|
||||
mv $artifact_name artifact
|
||||
|
||||
- uses: actions/upload-artifact@master
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
name: Upload artifact.
|
||||
with:
|
||||
|
||||
@@ -7,7 +7,7 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v5
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
stale-issue-message: |
|
||||
Hey there!
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
name: Waiting for answer.
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
schedule:
|
||||
- cron: '30 0 * * *'
|
||||
|
||||
jobs:
|
||||
waiting-for-answer:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: lee-dohm/no-response@v0.5.0
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
responseRequiredLabel: waiting for answer
|
||||
@@ -69,13 +69,13 @@ jobs:
|
||||
shell: bash
|
||||
run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
|
||||
|
||||
- uses: ilammy/msvc-dev-cmd@v1.12.0
|
||||
- uses: ilammy/msvc-dev-cmd@v1.13.0
|
||||
name: Native Tools Command Prompt.
|
||||
with:
|
||||
arch: ${{ matrix.arch }}
|
||||
|
||||
- name: Clone.
|
||||
uses: actions/checkout@v3.1.0
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
path: ${{ env.TBUILD }}\${{ env.REPO_NAME }}
|
||||
@@ -96,7 +96,7 @@ jobs:
|
||||
|
||||
- name: Libraries cache.
|
||||
id: cache-libs
|
||||
uses: actions/cache@v3.0.11
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.TBUILD }}\Libraries
|
||||
key: ${{ runner.OS }}-${{ matrix.arch }}-libs-${{ env.CACHE_KEY }}
|
||||
@@ -183,7 +183,7 @@ jobs:
|
||||
mkdir artifact
|
||||
move %OUT%\Telegram.exe artifact/
|
||||
move %OUT%\Updater.exe artifact/
|
||||
- uses: actions/upload-artifact@master
|
||||
- uses: actions/upload-artifact@v4
|
||||
name: Upload artifact.
|
||||
if: (env.UPLOAD_ARTIFACT == 'true') || (github.ref == 'refs/heads/nightly')
|
||||
with:
|
||||
|
||||
@@ -18,6 +18,7 @@ Release/
|
||||
*.xcodeproj
|
||||
ipch/
|
||||
.vs/
|
||||
.vscode/
|
||||
|
||||
/Telegram/log.txt
|
||||
/Telegram/data
|
||||
|
||||
@@ -82,12 +82,6 @@
|
||||
[submodule "Telegram/ThirdParty/dispatch"]
|
||||
path = Telegram/ThirdParty/dispatch
|
||||
url = https://github.com/apple/swift-corelibs-libdispatch
|
||||
[submodule "Telegram/ThirdParty/plasma-wayland-protocols"]
|
||||
path = Telegram/ThirdParty/plasma-wayland-protocols
|
||||
url = https://github.com/KDE/plasma-wayland-protocols.git
|
||||
[submodule "Telegram/ThirdParty/wayland-protocols"]
|
||||
path = Telegram/ThirdParty/wayland-protocols
|
||||
url = https://github.com/gitlab-freedesktop-mirrors/wayland-protocols.git
|
||||
[submodule "Telegram/ThirdParty/kimageformats"]
|
||||
path = Telegram/ThirdParty/kimageformats
|
||||
url = https://github.com/KDE/kimageformats.git
|
||||
@@ -97,9 +91,6 @@
|
||||
[submodule "Telegram/ThirdParty/cld3"]
|
||||
path = Telegram/ThirdParty/cld3
|
||||
url = https://github.com/google/cld3.git
|
||||
[submodule "Telegram/ThirdParty/wayland"]
|
||||
path = Telegram/ThirdParty/wayland
|
||||
url = https://github.com/gitlab-freedesktop-mirrors/wayland.git
|
||||
[submodule "Telegram/ThirdParty/libprisma"]
|
||||
path = Telegram/ThirdParty/libprisma
|
||||
url = https://github.com/desktop-app/libprisma.git
|
||||
|
||||
@@ -62,7 +62,7 @@ if (NOT DESKTOP_APP_USE_PACKAGED)
|
||||
if (WIN32)
|
||||
set(qt_version 5.15.13)
|
||||
elseif (APPLE)
|
||||
set(qt_version 6.2.7)
|
||||
set(qt_version 6.2.8)
|
||||
endif()
|
||||
endif()
|
||||
include(cmake/external/qt/package.cmake)
|
||||
|
||||
@@ -189,6 +189,8 @@ PRIVATE
|
||||
api/api_common.h
|
||||
api/api_confirm_phone.cpp
|
||||
api/api_confirm_phone.h
|
||||
api/api_credits.cpp
|
||||
api/api_credits.h
|
||||
api/api_earn.cpp
|
||||
api/api_earn.h
|
||||
api/api_editing.cpp
|
||||
@@ -342,6 +344,8 @@ PRIVATE
|
||||
boxes/local_storage_box.h
|
||||
boxes/max_invite_box.cpp
|
||||
boxes/max_invite_box.h
|
||||
boxes/moderate_messages_box.cpp
|
||||
boxes/moderate_messages_box.h
|
||||
boxes/peer_list_box.cpp
|
||||
boxes/peer_list_box.h
|
||||
boxes/peer_list_controllers.cpp
|
||||
@@ -366,6 +370,8 @@ PRIVATE
|
||||
boxes/ringtones_box.h
|
||||
boxes/self_destruction_box.cpp
|
||||
boxes/self_destruction_box.h
|
||||
boxes/send_credits_box.cpp
|
||||
boxes/send_credits_box.h
|
||||
boxes/send_files_box.cpp
|
||||
boxes/send_files_box.h
|
||||
boxes/sessions_box.cpp
|
||||
@@ -508,6 +514,8 @@ PRIVATE
|
||||
core/launcher.h
|
||||
core/local_url_handlers.cpp
|
||||
core/local_url_handlers.h
|
||||
core/phone_click_handler.cpp
|
||||
core/phone_click_handler.h
|
||||
core/sandbox.cpp
|
||||
core/sandbox.h
|
||||
core/shortcuts.cpp
|
||||
@@ -529,10 +537,16 @@ PRIVATE
|
||||
data/business/data_business_info.h
|
||||
data/business/data_shortcut_messages.cpp
|
||||
data/business/data_shortcut_messages.h
|
||||
data/components/factchecks.cpp
|
||||
data/components/factchecks.h
|
||||
data/components/recent_peers.cpp
|
||||
data/components/recent_peers.h
|
||||
data/components/scheduled_messages.cpp
|
||||
data/components/scheduled_messages.h
|
||||
data/components/sponsored_messages.cpp
|
||||
data/components/sponsored_messages.h
|
||||
data/components/top_peers.cpp
|
||||
data/components/top_peers.h
|
||||
data/notify/data_notify_settings.cpp
|
||||
data/notify/data_notify_settings.h
|
||||
data/notify/data_peer_notify_settings.cpp
|
||||
@@ -602,6 +616,8 @@ PRIVATE
|
||||
data/data_groups.h
|
||||
data/data_histories.cpp
|
||||
data/data_histories.h
|
||||
data/data_history_messages.cpp
|
||||
data/data_history_messages.h
|
||||
data/data_lastseen_status.h
|
||||
data/data_location.cpp
|
||||
data/data_location.h
|
||||
@@ -677,6 +693,18 @@ PRIVATE
|
||||
data/data_wall_paper.h
|
||||
data/data_web_page.cpp
|
||||
data/data_web_page.h
|
||||
dialogs/ui/dialogs_layout.cpp
|
||||
dialogs/ui/dialogs_layout.h
|
||||
dialogs/ui/dialogs_message_view.cpp
|
||||
dialogs/ui/dialogs_message_view.h
|
||||
dialogs/ui/dialogs_stories_content.cpp
|
||||
dialogs/ui/dialogs_stories_content.h
|
||||
dialogs/ui/dialogs_suggestions.cpp
|
||||
dialogs/ui/dialogs_suggestions.h
|
||||
dialogs/ui/dialogs_topics_view.cpp
|
||||
dialogs/ui/dialogs_topics_view.h
|
||||
dialogs/ui/dialogs_video_userpic.cpp
|
||||
dialogs/ui/dialogs_video_userpic.h
|
||||
dialogs/dialogs_entry.cpp
|
||||
dialogs/dialogs_entry.h
|
||||
dialogs/dialogs_indexed_list.cpp
|
||||
@@ -699,16 +727,6 @@ PRIVATE
|
||||
dialogs/dialogs_search_tags.h
|
||||
dialogs/dialogs_widget.cpp
|
||||
dialogs/dialogs_widget.h
|
||||
dialogs/ui/dialogs_layout.cpp
|
||||
dialogs/ui/dialogs_layout.h
|
||||
dialogs/ui/dialogs_message_view.cpp
|
||||
dialogs/ui/dialogs_message_view.h
|
||||
dialogs/ui/dialogs_stories_content.cpp
|
||||
dialogs/ui/dialogs_stories_content.h
|
||||
dialogs/ui/dialogs_topics_view.cpp
|
||||
dialogs/ui/dialogs_topics_view.h
|
||||
dialogs/ui/dialogs_video_userpic.cpp
|
||||
dialogs/ui/dialogs_video_userpic.h
|
||||
editor/color_picker.cpp
|
||||
editor/color_picker.h
|
||||
editor/controllers/controllers.h
|
||||
@@ -749,6 +767,8 @@ PRIVATE
|
||||
history/view/controls/compose_controls_common.h
|
||||
history/view/controls/history_view_compose_controls.cpp
|
||||
history/view/controls/history_view_compose_controls.h
|
||||
history/view/controls/history_view_compose_media_edit_manager.cpp
|
||||
history/view/controls/history_view_compose_media_edit_manager.h
|
||||
history/view/controls/history_view_compose_search.cpp
|
||||
history/view/controls/history_view_compose_search.h
|
||||
history/view/controls/history_view_draft_options.cpp
|
||||
@@ -840,6 +860,8 @@ PRIVATE
|
||||
history/view/history_view_about_view.h
|
||||
history/view/history_view_bottom_info.cpp
|
||||
history/view/history_view_bottom_info.h
|
||||
history/view/history_view_chat_preview.cpp
|
||||
history/view/history_view_chat_preview.h
|
||||
history/view/history_view_contact_status.cpp
|
||||
history/view/history_view_contact_status.h
|
||||
history/view/history_view_context_menu.cpp
|
||||
@@ -854,6 +876,8 @@ PRIVATE
|
||||
history/view/history_view_emoji_interactions.h
|
||||
history/view/history_view_empty_list_bubble.cpp
|
||||
history/view/history_view_empty_list_bubble.h
|
||||
history/view/history_view_fake_items.cpp
|
||||
history/view/history_view_fake_items.h
|
||||
history/view/history_view_group_call_bar.cpp
|
||||
history/view/history_view_group_call_bar.h
|
||||
history/view/history_view_item_preview.h
|
||||
@@ -884,14 +908,14 @@ PRIVATE
|
||||
history/view/history_view_send_action.h
|
||||
history/view/history_view_service_message.cpp
|
||||
history/view/history_view_service_message.h
|
||||
history/view/history_view_spoiler_click_handler.cpp
|
||||
history/view/history_view_spoiler_click_handler.h
|
||||
history/view/history_view_sponsored_click_handler.cpp
|
||||
history/view/history_view_sponsored_click_handler.h
|
||||
history/view/history_view_sticker_toast.cpp
|
||||
history/view/history_view_sticker_toast.h
|
||||
history/view/history_view_sublist_section.cpp
|
||||
history/view/history_view_sublist_section.h
|
||||
history/view/history_view_text_helper.cpp
|
||||
history/view/history_view_text_helper.h
|
||||
history/view/history_view_transcribe_button.cpp
|
||||
history/view/history_view_transcribe_button.h
|
||||
history/view/history_view_translate_bar.cpp
|
||||
@@ -1268,11 +1292,8 @@ PRIVATE
|
||||
payments/payments_checkout_process.h
|
||||
payments/payments_form.cpp
|
||||
payments/payments_form.h
|
||||
platform/linux/linux_wayland_integration_dummy.cpp
|
||||
platform/linux/linux_wayland_integration.cpp
|
||||
platform/linux/linux_wayland_integration.h
|
||||
platform/linux/linux_xdp_open_with_dialog.cpp
|
||||
platform/linux/linux_xdp_open_with_dialog.h
|
||||
payments/payments_non_panel_process.cpp
|
||||
payments/payments_non_panel_process.h
|
||||
platform/linux/file_utilities_linux.cpp
|
||||
platform/linux/file_utilities_linux.h
|
||||
platform/linux/launcher_linux.cpp
|
||||
@@ -1418,6 +1439,10 @@ PRIVATE
|
||||
settings/settings_codes.h
|
||||
settings/settings_common_session.cpp
|
||||
settings/settings_common_session.h
|
||||
settings/settings_credits.cpp
|
||||
settings/settings_credits.h
|
||||
settings/settings_credits_graphics.cpp
|
||||
settings/settings_credits_graphics.h
|
||||
settings/settings_experimental.cpp
|
||||
settings/settings_experimental.h
|
||||
settings/settings_folders.cpp
|
||||
@@ -1513,6 +1538,8 @@ PRIVATE
|
||||
ui/controls/silent_toggle.h
|
||||
ui/controls/userpic_button.cpp
|
||||
ui/controls/userpic_button.h
|
||||
ui/effects/credits_graphics.cpp
|
||||
ui/effects/credits_graphics.h
|
||||
ui/effects/emoji_fly_animation.cpp
|
||||
ui/effects/emoji_fly_animation.h
|
||||
ui/effects/message_sending_animation_common.h
|
||||
@@ -1560,6 +1587,8 @@ PRIVATE
|
||||
window/section_widget.h
|
||||
window/window_adaptive.cpp
|
||||
window/window_adaptive.h
|
||||
window/window_chat_preview.cpp
|
||||
window/window_chat_preview.h
|
||||
window/window_connecting_widget.cpp
|
||||
window/window_connecting_widget.h
|
||||
window/window_controller.cpp
|
||||
@@ -1625,16 +1654,6 @@ if (NOT build_winstore)
|
||||
)
|
||||
endif()
|
||||
|
||||
if (DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION)
|
||||
remove_target_sources(Telegram ${src_loc}
|
||||
platform/linux/linux_wayland_integration.cpp
|
||||
)
|
||||
else()
|
||||
remove_target_sources(Telegram ${src_loc}
|
||||
platform/linux/linux_wayland_integration_dummy.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if (DESKTOP_APP_USE_PACKAGED)
|
||||
remove_target_sources(Telegram ${src_loc}
|
||||
platform/mac/mac_iconv_helper.c
|
||||
@@ -1774,19 +1793,6 @@ else()
|
||||
desktop-app::external_xcb
|
||||
)
|
||||
endif()
|
||||
|
||||
if (NOT DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION)
|
||||
qt_generate_wayland_protocol_client_sources(Telegram
|
||||
FILES
|
||||
${third_party_loc}/wayland/protocol/wayland.xml
|
||||
${third_party_loc}/plasma-wayland-protocols/src/protocols/plasma-shell.xml
|
||||
)
|
||||
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
desktop-app::external_wayland_client
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (build_macstore)
|
||||
|
||||
|
After Width: | Height: | Size: 416 B |
|
After Width: | Height: | Size: 913 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 754 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 588 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 563 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
@@ -134,9 +134,32 @@ html.custom_scroll ::-webkit-scrollbar-thumb:hover {
|
||||
.page-slide {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
margin-left: 0%;
|
||||
transition: margin 240ms ease-in-out;
|
||||
}
|
||||
.page-footer {
|
||||
height: 32px;
|
||||
margin-top: -32px;
|
||||
background: var(--td-window-bg-over);
|
||||
}
|
||||
.page-footer .content {
|
||||
padding: 3px 18px;
|
||||
font-size: 15px;
|
||||
color: var(--td-window-sub-text-fg);
|
||||
text-align: center;
|
||||
}
|
||||
.page-footer .wrong {
|
||||
position: relative;
|
||||
padding: 5px;
|
||||
margin: -5px;
|
||||
color: var(--td-window-sub-text-fg);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.page-footer .wrong:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.hidden-left,
|
||||
.hidden-right {
|
||||
pointer-events: none;
|
||||
@@ -148,7 +171,7 @@ html.custom_scroll ::-webkit-scrollbar-thumb:hover {
|
||||
margin-left: 100%;
|
||||
}
|
||||
article {
|
||||
padding-bottom: 12px;
|
||||
padding-bottom: 40px;
|
||||
overflow-y: hidden;
|
||||
overflow-x: auto;
|
||||
white-space: pre-wrap;
|
||||
@@ -893,6 +916,9 @@ section.related a.related-link:after {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
section.related a.related-link:last-child:after {
|
||||
border-bottom: 0px;
|
||||
}
|
||||
section.related .related-link-url {
|
||||
display: block;
|
||||
font-size: 15px;
|
||||
@@ -1027,6 +1053,9 @@ section.channel > a > h4 {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.media-outer {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.photo-wrap,
|
||||
.video-wrap {
|
||||
width: 100%;
|
||||
|
||||
@@ -26,7 +26,7 @@ var IV = {
|
||||
}
|
||||
target = target.parentNode;
|
||||
}
|
||||
if (!target || !target.hasAttribute('href')) {
|
||||
if (!target || (context === '' && !target.hasAttribute('href'))) {
|
||||
return;
|
||||
}
|
||||
var base = document.createElement('A');
|
||||
@@ -413,9 +413,12 @@ var IV = {
|
||||
var article = function (el) {
|
||||
return el.getElementsByTagName('article')[0];
|
||||
};
|
||||
var from = article(IV.findPageScroll());
|
||||
var to = article(IV.makeScrolledContent(data.html));
|
||||
morphdom(from, to, {
|
||||
var footer = function (el) {
|
||||
return el.getElementsByClassName('page-footer')[0];
|
||||
};
|
||||
var from = IV.findPageScroll();
|
||||
var to = IV.makeScrolledContent(data.html);
|
||||
morphdom(article(from), article(to), {
|
||||
onBeforeElUpdated: function (fromEl, toEl) {
|
||||
if (fromEl.classList.contains('video')
|
||||
&& toEl.classList.contains('video')
|
||||
@@ -439,6 +442,7 @@ var IV = {
|
||||
return !fromEl.isEqualNode(toEl);
|
||||
}
|
||||
});
|
||||
morphdom(footer(from), footer(to));
|
||||
IV.initMedia();
|
||||
eval(data.js);
|
||||
},
|
||||
@@ -477,9 +481,7 @@ var IV = {
|
||||
var result = document.createElement('div');
|
||||
result.className = 'page-scroll';
|
||||
result.tabIndex = '-1';
|
||||
result.innerHTML = '<div class="page-slide"><article>'
|
||||
+ html
|
||||
+ '</article></div>';
|
||||
result.innerHTML = html.trim();
|
||||
result.onscroll = IV.frameScrolled;
|
||||
return result;
|
||||
},
|
||||
|
||||
@@ -251,6 +251,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_caption_limit2#other" = "Make the caption shorter or subscribe to **Telegram Premium** to double the limit to **{count}** characters.";
|
||||
"lng_caption_limit_reached#one" = "You've reached the media caption limit. Please make the caption shorter by {count} character.";
|
||||
"lng_caption_limit_reached#other" = "You've reached the media caption limit. Please make the caption shorter by {count} characters.";
|
||||
"lng_caption_move_up" = "Move Caption Up";
|
||||
"lng_caption_move_down" = "Move Caption Down";
|
||||
|
||||
"lng_file_size_limit_title" = "File Too Large";
|
||||
"lng_file_size_limit#one" = "{count} Gb";
|
||||
@@ -302,6 +304,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_sure_ban_admin" = "This user is an admin. Are you sure you want to go ahead and restrict them?";
|
||||
"lng_sure_enable_socks" = "Are you sure you want to enable this proxy?\n\nServer: {server}\nPort: {port}\n\nYou can change your proxy server later in the Settings (Connection Type).";
|
||||
"lng_sure_enable" = "Enable";
|
||||
"lng_proxy_box_title" = "Enable proxy";
|
||||
"lng_proxy_box_server" = "Server";
|
||||
"lng_proxy_box_port" = "Port";
|
||||
"lng_proxy_box_secret" = "Secret";
|
||||
"lng_proxy_box_status" = "Status";
|
||||
"lng_proxy_box_username" = "Username";
|
||||
"lng_proxy_box_password" = "Password";
|
||||
"lng_proxy_invalid" = "The proxy link is invalid.";
|
||||
"lng_proxy_unsupported" = "Your Telegram Desktop version doesn't support this proxy type or the proxy link is invalid. Please update Telegram Desktop to the latest version.";
|
||||
|
||||
@@ -561,6 +570,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_reaction_invoice" = "{reaction} to your invoice";
|
||||
"lng_reaction_gif" = "{reaction} to your GIF";
|
||||
|
||||
"lng_effect_add_title" = "Add an animated effect";
|
||||
"lng_effect_stickers_title" = "Effects from stickers";
|
||||
"lng_effect_send" = "Send with Effect";
|
||||
"lng_effect_none" = "No effects found.";
|
||||
"lng_effect_premium" = "Subscribe to {link} to add this animated effect.";
|
||||
"lng_effect_premium_link" = "Telegram Premium";
|
||||
|
||||
"lng_languages" = "Languages";
|
||||
"lng_languages_none" = "No languages found.";
|
||||
"lng_languages_count#one" = "{count} language";
|
||||
@@ -730,6 +746,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_angle_backend_d3d11on12" = "D3D11on12";
|
||||
"lng_settings_angle_backend_opengl" = "OpenGL";
|
||||
"lng_settings_angle_backend_disabled" = "Disabled";
|
||||
"lng_settings_top_peers_title" = "Frequent contacts";
|
||||
"lng_settings_top_peers_suggest" = "Suggest frequent contacts";
|
||||
"lng_settings_top_peers_about" = "Display people you message frequently at the top of the search section for quick access.";
|
||||
"lng_settings_sensitive_title" = "Sensitive content";
|
||||
"lng_settings_sensitive_disable_filtering" = "Disable filtering";
|
||||
"lng_settings_sensitive_about" = "Display sensitive media in public channels on all your Telegram devices.";
|
||||
@@ -766,6 +785,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_power_chat" = "Animations in Chats";
|
||||
"lng_settings_power_chat_background" = "Background rotation";
|
||||
"lng_settings_power_chat_spoiler" = "Animated spoiler effect";
|
||||
"lng_settings_power_chat_effects" = "Effects in messages";
|
||||
"lng_settings_power_calls" = "Animations in Calls";
|
||||
"lng_settings_power_ui" = "Interface animations";
|
||||
"lng_settings_power_auto" = "Save Power on Low Battery";
|
||||
@@ -840,6 +860,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_auto_night_mode_on" = "System";
|
||||
"lng_settings_auto_night_warning" = "You have enabled auto-night mode. If you want to change the dark mode settings, you'll need to disable it first.";
|
||||
"lng_settings_auto_night_disable" = "Disable";
|
||||
"lng_settings_font_family" = "Font family";
|
||||
|
||||
"lng_settings_color_title" = "Color preview";
|
||||
"lng_settings_color_reply" = "Reply to your message";
|
||||
@@ -1049,6 +1070,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_proxy_sponsor" = "Proxy sponsor";
|
||||
"lng_proxy_sponsor_about" = "This channel is shown by your proxy server.\nTo remove this channel from your chats list,\ndisable the proxy in Telegram Settings.";
|
||||
"lng_proxy_sponsor_warning" = "This proxy may display a sponsored channel in your chat list. This doesn't reveal any of your Telegram traffic.";
|
||||
"lng_proxy_add_from_clipboard" = "Add proxy from clipboard";
|
||||
"lng_proxy_add_from_clipboard_good_toast" = "Proxy was added from clipboard.";
|
||||
"lng_proxy_add_from_clipboard_failed_toast" = "This is not a proxy link.";
|
||||
"lng_proxy_add_from_clipboard_existing_toast" = "This proxy is already in the list.";
|
||||
"lng_badge_psa_default" = "PSA";
|
||||
"lng_about_psa_default" = "This message provides you with a public service announcement. To remove it from your chats list, right click it and select **Hide**.";
|
||||
"lng_tooltip_psa_default" = "This message provides you with a public service announcement.";
|
||||
@@ -1492,6 +1517,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_manage_peer_reactions_none_about" = "Members of the group can't add any reactions to messages.";
|
||||
"lng_manage_peer_reactions_some_title" = "Only allow these reactions";
|
||||
"lng_manage_peer_reactions_available" = "Available reactions";
|
||||
"lng_manage_peer_reactions_available_ph" = "Add reactions...";
|
||||
"lng_manage_peer_reactions_own" = "You can also {link} emoji packs and use them as reactions.";
|
||||
"lng_manage_peer_reactions_own_link" = "create your own";
|
||||
"lng_manage_peer_reactions_level#one" = "Your channel needs to reach level **{count}** to use **{same_count}** custom reaction.";
|
||||
@@ -1500,6 +1526,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_manage_peer_reactions_boost_link" = "here";
|
||||
"lng_manage_peer_reactions_limit" = "Channels can't have more custom reactions.";
|
||||
|
||||
"lng_manage_peer_reactions_max_title" = "Maximum number of reactions";
|
||||
"lng_manage_peer_reactions_max_slider#one" = "{count} reaction per post";
|
||||
"lng_manage_peer_reactions_max_slider#other" = "{count} reactions per post";
|
||||
"lng_manage_peer_reactions_max_about" = "Limit the number of different reactions that can be added to a post, including already published ones.";
|
||||
|
||||
"lng_manage_peer_antispam" = "Aggressive Anti-Spam";
|
||||
"lng_manage_peer_antispam_about" = "Telegram will filter more spam but may occasionally affect ordinary messages. You can report False Positives in Recent Actions.";
|
||||
"lng_manage_peer_antispam_not_enough#one" = "Aggressive filtering can be enabled only in groups with more than **{count} member**.";
|
||||
@@ -1512,8 +1543,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_manage_peer_link_invite" = "Invite link";
|
||||
"lng_manage_peer_link_expired" = "Expired link";
|
||||
"lng_manage_private_group_title" = "Private";
|
||||
"lng_manage_private_group_noforwards_title" = "Private restricted";
|
||||
"lng_manage_public_group_title" = "Public";
|
||||
"lng_manage_private_peer_title" = "Private";
|
||||
"lng_manage_private_peer_noforwards_title" = "Private restricted";
|
||||
"lng_manage_public_peer_title" = "Public";
|
||||
"lng_manage_peer_send_title" = "Who can send new messages?";
|
||||
"lng_manage_peer_send_only_members" = "Only members";
|
||||
@@ -2269,6 +2302,43 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_business_about_chat_intro" = "Customize the message people see before they start a chat with you.";
|
||||
"lng_business_subtitle_chat_links" = "Links to Chat";
|
||||
"lng_business_about_chat_links" = "Create links that start a chat with you, suggesting the first message.";
|
||||
"lng_business_subtitle_sponsored" = "Ads in Channels";
|
||||
"lng_business_button_sponsored" = "Do Not Hide Ads";
|
||||
"lng_business_about_sponsored" = "As a Premium subscriber, you don’t see any ads on Telegram, but you can turn them on, for example, to view your own ads that you launched on the {link}";
|
||||
"lng_business_about_sponsored_link" = "Telegram Ad Platform {emoji}";
|
||||
"lng_business_about_sponsored_url" = "https://ads.telegram.org";
|
||||
|
||||
"lng_credits_summary_title" = "Telegram Stars";
|
||||
"lng_credits_summary_about" = "Buy Stars to unlock content and services in miniapps on Telegram.";
|
||||
"lng_credits_summary_options_subtitle" = "Choose package";
|
||||
"lng_credits_summary_options_credits#one" = "{count} Star";
|
||||
"lng_credits_summary_options_credits#other" = "{count} Stars";
|
||||
"lng_credits_summary_options_more" = "More Options";
|
||||
"lng_credits_summary_options_about" = "By proceeding and purchasing Stars, you agree with the {link}.";
|
||||
"lng_credits_summary_options_about_link" = "Terms and Conditions";
|
||||
"lng_credits_summary_history_tab_full" = "All Transactions";
|
||||
"lng_credits_summary_history_tab_in" = "Incoming";
|
||||
"lng_credits_summary_history_tab_out" = "Outgoing";
|
||||
"lng_credits_summary_history_entry_inner_in" = "In-App Purchase";
|
||||
"lng_credits_summary_balance" = "Balance";
|
||||
"lng_credits_box_out_title" = "Confirm Your Purchase";
|
||||
"lng_credits_box_out_sure#one" = "Do you want to buy **\"{text}\"** in **{bot}** for **{count} Star**?";
|
||||
"lng_credits_box_out_sure#other" = "Do you want to buy **\"{text}\"** in **{bot}** for **{count} Stars**?";
|
||||
"lng_credits_box_out_confirm#one" = "Confirm and Pay {emoji} {count} Star";
|
||||
"lng_credits_box_out_confirm#other" = "Confirm and Pay {emoji} {count} Stars";
|
||||
"lng_credits_box_out_about" = "Review the {link} for Stars.";
|
||||
"lng_credits_summary_in_toast_title" = "Stars Acquired";
|
||||
"lng_credits_summary_in_toast_about#one" = "**{count}** Star added to your balance.";
|
||||
"lng_credits_summary_in_toast_about#other" = "**{count}** Stars added to your balance.";
|
||||
"lng_credits_box_history_entry_peer" = "Recipient";
|
||||
"lng_credits_box_history_entry_id" = "Transaction ID";
|
||||
"lng_credits_box_history_entry_id_copied" = "Transaction ID copied to clipboard.";
|
||||
"lng_credits_box_history_entry_about" = "You can dispute this transaction {link}.";
|
||||
"lng_credits_box_history_entry_about_link" = "here";
|
||||
"lng_credits_small_balance_title#one" = "{count} Star Needed";
|
||||
"lng_credits_small_balance_title#other" = "{count} Stars Needed";
|
||||
"lng_credits_small_balance_about" = "Buy **Stars** and use them on **{bot}** and other miniapps.";
|
||||
"lng_credits_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars.";
|
||||
|
||||
"lng_location_title" = "Location";
|
||||
"lng_location_about" = "Display the location of your business on your account.";
|
||||
@@ -2839,7 +2909,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_in_dlg_audio_count#other" = "{count} audio";
|
||||
|
||||
"lng_ban_user" = "Ban User";
|
||||
"lng_ban_users" = "Ban users";
|
||||
"lng_restrict_users" = "Restrict users";
|
||||
"lng_delete_all_from_user" = "Delete all from {user}";
|
||||
"lng_delete_all_from_users" = "Delete all from users";
|
||||
"lng_restrict_user_part" = "Partially restrict this user {emoji}";
|
||||
"lng_restrict_users_part" = "Partially restrict users {emoji}";
|
||||
"lng_restrict_user_full" = "Fully ban this user {emoji}";
|
||||
"lng_restrict_users_full" = "Fully ban users {emoji}";
|
||||
"lng_restrict_users_part_single_header" = "What can this user do?";
|
||||
"lng_restrict_users_part_header#one" = "What can {count} selected user do?";
|
||||
"lng_restrict_users_part_header#other" = "What can {count} selected users do?";
|
||||
"lng_report_spam" = "Report Spam";
|
||||
"lng_report_spam_and_leave" = "Report spam and leave";
|
||||
"lng_report_spam_done" = "Thank you for your report.";
|
||||
@@ -3067,6 +3147,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_unread_bar_some" = "Unread messages";
|
||||
|
||||
"lng_maps_point" = "Location";
|
||||
"lng_live_location" = "Live Location";
|
||||
"lng_live_location_now" = "updated just now";
|
||||
"lng_live_location_minutes#one" = "updated {count} minute ago";
|
||||
"lng_live_location_minutes#other" = "updated {count} minutes ago";
|
||||
"lng_live_location_hours#one" = "updated {count} hour ago";
|
||||
"lng_live_location_hours#other" = "updated {count} hours ago";
|
||||
"lng_live_location_today" = "updated today at {time}";
|
||||
"lng_live_location_yesterday" = "updated yesterday at {time}";
|
||||
"lng_live_location_date_time" = "updated {date} at {time}";
|
||||
"lng_save_photo" = "Save image";
|
||||
"lng_save_video" = "Save video";
|
||||
"lng_save_audio_file" = "Save audio file";
|
||||
@@ -3143,6 +3232,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_context_reply_msg" = "Reply";
|
||||
"lng_context_quote_and_reply" = "Quote & Reply";
|
||||
"lng_context_edit_msg" = "Edit";
|
||||
"lng_context_add_factcheck" = "Add Fact Check";
|
||||
"lng_context_edit_factcheck" = "Edit Fact Check";
|
||||
"lng_context_forward_msg" = "Forward Message";
|
||||
"lng_context_send_now_msg" = "Send now";
|
||||
"lng_context_reschedule" = "Reschedule";
|
||||
@@ -3214,6 +3305,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_context_spoiler_effect" = "Hide with Spoiler";
|
||||
"lng_context_disable_spoiler" = "Remove Spoiler";
|
||||
|
||||
"lng_factcheck_title" = "Fact Check";
|
||||
"lng_factcheck_placeholder" = "Add Facts or Context";
|
||||
"lng_factcheck_whats_this" = "what's this?";
|
||||
"lng_factcheck_about" = "This clarification was provided by a fact checking agency assigned by the department of the government of your country ({country}) responsible for combatting misinformation.";
|
||||
"lng_factcheck_add_done" = "Fact check added.";
|
||||
"lng_factcheck_edit_done" = "Fact check edited.";
|
||||
"lng_factcheck_remove_done" = "Fact check removed.";
|
||||
"lng_factcheck_bottom" = "This clarification was provided by a fact checking agency assigned by the department of the government of your country ({country}) responsible for combatting misinformation.";
|
||||
"lng_factcheck_links" = "Only **t.me/** links are allowed.";
|
||||
|
||||
"lng_translate_show_original" = "Show Original";
|
||||
"lng_translate_bar_to" = "Translate to {name}";
|
||||
"lng_translate_bar_to_other" = "Translate to {name}";
|
||||
@@ -3334,6 +3435,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_add_contact" = "Create";
|
||||
"lng_add_contact_button" = "New contact";
|
||||
"lng_contacts_header" = "Contacts";
|
||||
"lng_menu_not_contact" = "This number is not on Telegram";
|
||||
"lng_contacts_hidden_stories" = "Hidden Stories";
|
||||
"lng_contacts_stories_status#one" = "{count} story";
|
||||
"lng_contacts_stories_status#other" = "{count} stories";
|
||||
@@ -3410,6 +3512,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_mediaview_forward" = "Forward";
|
||||
"lng_mediaview_delete" = "Delete";
|
||||
"lng_mediaview_save_to_profile" = "Save to Profile";
|
||||
"lng_mediaview_pin_story_done" = "Story pinned";
|
||||
"lng_mediaview_pin_story_about" = "Now it will be always shown on the top.";
|
||||
"lng_mediaview_pin_stories_done#one" = "{count} story pinned";
|
||||
"lng_mediaview_pin_stories_done#other" = "{count} stories pinned";
|
||||
"lng_mediaview_pin_stories_about#one" = "Now it will be always shown on the top.";
|
||||
"lng_mediaview_pin_stories_about#other" = "Now they will be always shown on the top.";
|
||||
"lng_mediaview_unpin_story_done" = "Story unpinned.";
|
||||
"lng_mediaview_unpin_stories_done#one" = "{count} story unpinned";
|
||||
"lng_mediaview_unpin_stories_done#other" = "{count} stories unpinned";
|
||||
"lng_mediaview_pin_limit#one" = "You can't pin more than {count} story.";
|
||||
"lng_mediaview_pin_limit#other" = "You can't pin more than {count} stories.";
|
||||
"lng_mediaview_archive_story" = "Archive Story";
|
||||
"lng_mediaview_photos_all" = "View all photos";
|
||||
"lng_mediaview_files_all" = "View all files";
|
||||
@@ -4704,6 +4817,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_view_button_boost" = "Boost";
|
||||
"lng_view_button_giftcode" = "Open";
|
||||
"lng_view_button_iv" = "Instant View";
|
||||
"lng_view_button_stickerset" = "View stickers";
|
||||
"lng_view_button_emojipack" = "View emoji";
|
||||
|
||||
"lng_sponsored_hide_ads" = "Hide";
|
||||
"lng_sponsored_title" = "What are sponsored messages?";
|
||||
@@ -5073,6 +5188,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_iv_share" = "Share";
|
||||
"lng_iv_join_channel" = "Join";
|
||||
"lng_iv_window_title" = "Instant View";
|
||||
"lng_iv_wrong_layout" = "Wrong layout?";
|
||||
|
||||
"lng_limit_download_title" = "Download speed limited";
|
||||
"lng_limit_download_subscribe" = "Subscribe to {link} and increase download speed {increase}.";
|
||||
@@ -5087,6 +5203,58 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_limit_upload_increase_speed" = "by **{percent}**";
|
||||
"lng_limit_upload_subscribe_link" = "Telegram Premium";
|
||||
|
||||
"lng_recent_frequent" = "Frequent contacts";
|
||||
"lng_recent_frequent_all" = "Show all";
|
||||
"lng_recent_frequent_collapse" = "Collapse";
|
||||
"lng_recent_title" = "Recent";
|
||||
"lng_recent_clear" = "Clear";
|
||||
"lng_recent_clear_sure" = "Do you want to clear your search history?";
|
||||
"lng_recent_remove" = "Remove from Recent";
|
||||
"lng_recent_clear_all" = "Clear all";
|
||||
"lng_recent_hide_top" = "Remove all & Disable";
|
||||
"lng_recent_hide_sure" = "Are you sure you want to clear and disable frequent contacts list?\n\nYou can always turn this feature back on in Settings > Privacy > Suggest Frequent Contacts.";
|
||||
"lng_recent_hide_button" = "Hide";
|
||||
"lng_recent_none" = "Recent search results\nwill appear here.";
|
||||
"lng_recent_chats" = "Chats";
|
||||
"lng_recent_channels" = "Channels";
|
||||
"lng_channels_none_title" = "No channels yet...";
|
||||
"lng_channels_none_about" = "You are not currently subscribed to any channels.";
|
||||
"lng_channels_your_title" = "Channels you joined";
|
||||
"lng_channels_your_more" = "Show more";
|
||||
"lng_channels_your_less" = "Show less";
|
||||
"lng_channels_recommended" = "Recommended channels";
|
||||
|
||||
"lng_font_box_title" = "Choose font family";
|
||||
"lng_font_default" = "Default";
|
||||
"lng_font_system" = "System font";
|
||||
"lng_font_not_found" = "Font not found.";
|
||||
|
||||
"lng_search_tab_my_messages" = "My Messages";
|
||||
"lng_search_tab_this_topic" = "This Topic";
|
||||
"lng_search_tab_this_chat" = "This Chat";
|
||||
"lng_search_tab_this_channel" = "This Channel";
|
||||
"lng_search_tab_this_group" = "This Group";
|
||||
"lng_search_tab_public_posts" = "Public Posts";
|
||||
"lng_search_tab_no_results" = "No Results";
|
||||
"lng_search_tab_no_results_text" = "There were no results for \"{query}\".";
|
||||
"lng_search_tab_no_results_retry" = "Try another hashtag.";
|
||||
"lng_search_tab_by_hashtag" = "Enter a hashtag to find messages containing it.";
|
||||
|
||||
"lng_contact_details_button" = "View Contact";
|
||||
"lng_contact_details_title" = "Contact details";
|
||||
"lng_contact_details_phone" = "Phone";
|
||||
"lng_contact_details_phone_main" = "Main Phone";
|
||||
"lng_contact_details_phone_home" = "Home Phone";
|
||||
"lng_contact_details_phone_mobile" = "Mobile Phone";
|
||||
"lng_contact_details_phone_work" = "Work Phone";
|
||||
"lng_contact_details_phone_other" = "Other Phone";
|
||||
"lng_contact_details_email" = "Email";
|
||||
"lng_contact_details_address" = "Address";
|
||||
"lng_contact_details_url" = "URL";
|
||||
"lng_contact_details_note" = "Note";
|
||||
"lng_contact_details_birthday" = "Birthday";
|
||||
"lng_contact_details_organization" = "Organization";
|
||||
|
||||
// Wnd specific
|
||||
|
||||
"lng_wnd_choose_program_menu" = "Choose Default Program...";
|
||||
|
||||
@@ -24,5 +24,7 @@
|
||||
<file alias="chat_link.tgs">../../animations/chat_link.tgs</file>
|
||||
<file alias="collectible_username.tgs">../../animations/collectible_username.tgs</file>
|
||||
<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>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="4.16.8.0" />
|
||||
Version="5.1.2.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 4,16,8,0
|
||||
PRODUCTVERSION 4,16,8,0
|
||||
FILEVERSION 5,1,2,0
|
||||
PRODUCTVERSION 5,1,2,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Radolyn Labs"
|
||||
VALUE "FileDescription", "AyuGram Desktop"
|
||||
VALUE "FileVersion", "4.16.8.0"
|
||||
VALUE "FileVersion", "5.1.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "AyuGram Desktop"
|
||||
VALUE "ProductVersion", "4.16.8.0"
|
||||
VALUE "ProductVersion", "5.1.2.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 4,16,8,0
|
||||
PRODUCTVERSION 4,16,8,0
|
||||
FILEVERSION 5,1,2,0
|
||||
PRODUCTVERSION 5,1,2,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Radolyn Labs"
|
||||
VALUE "FileDescription", "AyuGram Desktop Updater"
|
||||
VALUE "FileVersion", "4.16.8.0"
|
||||
VALUE "FileVersion", "5.1.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "AyuGram Desktop"
|
||||
VALUE "ProductVersion", "4.16.8.0"
|
||||
VALUE "ProductVersion", "5.1.2.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/history_item_components.h"
|
||||
#include "inline_bots/bot_attach_web_view.h"
|
||||
#include "payments/payments_checkout_process.h"
|
||||
#include "payments/payments_non_panel_process.h"
|
||||
#include "main/main_session.h"
|
||||
#include "mainwidget.h"
|
||||
#include "mainwindow.h"
|
||||
@@ -335,7 +336,8 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
|
||||
Payments::Mode::Payment,
|
||||
crl::guard(controller, [=](auto) {
|
||||
controller->widget()->activate();
|
||||
}));
|
||||
}),
|
||||
Payments::ProcessNonPanelPaymentFormFactory(controller, item));
|
||||
} break;
|
||||
|
||||
case ButtonType::Url: {
|
||||
|
||||
@@ -212,7 +212,7 @@ void ApplyBotsList(
|
||||
}
|
||||
|
||||
[[nodiscard]] ChatParticipants::Channels ParseSimilar(
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<Main::Session*> session,
|
||||
const MTPmessages_Chats &chats) {
|
||||
auto result = ChatParticipants::Channels();
|
||||
std::vector<not_null<ChannelData*>>();
|
||||
@@ -220,13 +220,13 @@ void ApplyBotsList(
|
||||
const auto &list = data.vchats().v;
|
||||
result.list.reserve(list.size());
|
||||
for (const auto &chat : list) {
|
||||
const auto peer = channel->owner().processChat(chat);
|
||||
const auto peer = session->data().processChat(chat);
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
result.list.push_back(channel);
|
||||
}
|
||||
}
|
||||
if constexpr (MTPDmessages_chatsSlice::Is<decltype(data)>()) {
|
||||
if (channel->session().premiumPossible()) {
|
||||
if (session->premiumPossible()) {
|
||||
result.more = data.vcount().v - data.vchats().v.size();
|
||||
}
|
||||
}
|
||||
@@ -234,6 +234,12 @@ void ApplyBotsList(
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] ChatParticipants::Channels ParseSimilar(
|
||||
not_null<ChannelData*> channel,
|
||||
const MTPmessages_Chats &chats) {
|
||||
return ParseSimilar(&channel->session(), chats);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ChatParticipant::ChatParticipant(
|
||||
@@ -351,7 +357,8 @@ QString ChatParticipant::rank() const {
|
||||
}
|
||||
|
||||
ChatParticipants::ChatParticipants(not_null<ApiWrap*> api)
|
||||
: _api(&api->instance()) {
|
||||
: _session(&api->session())
|
||||
, _api(&api->instance()) {
|
||||
}
|
||||
|
||||
void ChatParticipants::requestForAdd(
|
||||
@@ -585,6 +592,33 @@ ChatParticipants::Parsed ChatParticipants::ParseRecent(
|
||||
return result;
|
||||
}
|
||||
|
||||
void ChatParticipants::Restrict(
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<PeerData*> participant,
|
||||
ChatRestrictionsInfo oldRights,
|
||||
ChatRestrictionsInfo newRights,
|
||||
Fn<void()> onDone,
|
||||
Fn<void()> onFail) {
|
||||
channel->session().api().request(MTPchannels_EditBanned(
|
||||
channel->inputChannel,
|
||||
participant->input,
|
||||
MTP_chatBannedRights(
|
||||
MTP_flags(MTPDchatBannedRights::Flags::from_raw(
|
||||
uint32(newRights.flags))),
|
||||
MTP_int(newRights.until))
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
channel->session().api().applyUpdates(result);
|
||||
channel->applyEditBanned(participant, oldRights, newRights);
|
||||
if (onDone) {
|
||||
onDone();
|
||||
}
|
||||
}).fail([=] {
|
||||
if (onFail) {
|
||||
onFail();
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ChatParticipants::requestSelf(not_null<ChannelData*> channel) {
|
||||
if (_selfParticipantRequests.contains(channel)) {
|
||||
return;
|
||||
@@ -730,8 +764,11 @@ void ChatParticipants::loadSimilarChannels(not_null<ChannelData*> channel) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
using Flag = MTPchannels_GetChannelRecommendations::Flag;
|
||||
_similar[channel].requestId = _api.request(
|
||||
MTPchannels_GetChannelRecommendations(channel->inputChannel)
|
||||
MTPchannels_GetChannelRecommendations(
|
||||
MTP_flags(Flag::f_channel),
|
||||
channel->inputChannel)
|
||||
).done([=](const MTPmessages_Chats &result) {
|
||||
auto &similar = _similar[channel];
|
||||
similar.requestId = 0;
|
||||
@@ -766,4 +803,29 @@ auto ChatParticipants::similarLoaded() const
|
||||
return _similarLoaded.events();
|
||||
}
|
||||
|
||||
void ChatParticipants::loadRecommendations() {
|
||||
if (_recommendationsLoaded.current() || _recommendations.requestId) {
|
||||
return;
|
||||
}
|
||||
_recommendations.requestId = _api.request(
|
||||
MTPchannels_GetChannelRecommendations(
|
||||
MTP_flags(0),
|
||||
MTP_inputChannelEmpty())
|
||||
).done([=](const MTPmessages_Chats &result) {
|
||||
_recommendations.requestId = 0;
|
||||
auto parsed = ParseSimilar(_session, result);
|
||||
_recommendations.channels = std::move(parsed);
|
||||
_recommendations.channels.more = 0;
|
||||
_recommendationsLoaded = true;
|
||||
}).send();
|
||||
}
|
||||
|
||||
const ChatParticipants::Channels &ChatParticipants::recommendations() const {
|
||||
return _recommendations.channels;
|
||||
}
|
||||
|
||||
rpl::producer<> ChatParticipants::recommendationsLoaded() const {
|
||||
return _recommendationsLoaded.changes() | rpl::to_empty;
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -14,6 +14,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
class ApiWrap;
|
||||
class ChannelData;
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Ui {
|
||||
class Show;
|
||||
} // namespace Ui
|
||||
@@ -96,6 +100,13 @@ public:
|
||||
static Parsed ParseRecent(
|
||||
not_null<ChannelData*> channel,
|
||||
const TLMembers &data);
|
||||
static void Restrict(
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<PeerData*> participant,
|
||||
ChatRestrictionsInfo oldRights,
|
||||
ChatRestrictionsInfo newRights,
|
||||
Fn<void()> onDone,
|
||||
Fn<void()> onFail);
|
||||
void add(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
@@ -134,12 +145,18 @@ public:
|
||||
[[nodiscard]] auto similarLoaded() const
|
||||
-> rpl::producer<not_null<ChannelData*>>;
|
||||
|
||||
void loadRecommendations();
|
||||
[[nodiscard]] const Channels &recommendations() const;
|
||||
[[nodiscard]] rpl::producer<> recommendationsLoaded() const;
|
||||
|
||||
private:
|
||||
struct SimilarChannels {
|
||||
Channels channels;
|
||||
mtpRequestId requestId = 0;
|
||||
};
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
MTP::Sender _api;
|
||||
|
||||
using PeerRequests = base::flat_map<PeerData*, mtpRequestId>;
|
||||
@@ -165,6 +182,9 @@ private:
|
||||
base::flat_map<not_null<ChannelData*>, SimilarChannels> _similar;
|
||||
rpl::event_stream<not_null<ChannelData*>> _similarLoaded;
|
||||
|
||||
SimilarChannels _recommendations;
|
||||
rpl::variable<bool> _recommendationsLoaded = false;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -23,8 +23,10 @@ struct SendOptions {
|
||||
PeerData *sendAs = nullptr;
|
||||
TimeId scheduled = 0;
|
||||
BusinessShortcutId shortcutId = 0;
|
||||
EffectId effectId = 0;
|
||||
bool silent = false;
|
||||
bool handleSupportSwitch = false;
|
||||
bool invertCaption = false;
|
||||
bool hideViaBot = false;
|
||||
crl::time ttlSeconds = 0;
|
||||
};
|
||||
|
||||
@@ -62,6 +62,10 @@ void ConfirmPhone::resolve(
|
||||
return bad("FirebaseSms");
|
||||
}, [&](const MTPDauth_sentCodeTypeEmailCode &) {
|
||||
return bad("EmailCode");
|
||||
}, [&](const MTPDauth_sentCodeTypeSmsWord &) {
|
||||
return bad("SmsWord");
|
||||
}, [&](const MTPDauth_sentCodeTypeSmsPhrase &) {
|
||||
return bad("SmsPhrase");
|
||||
}, [&](const MTPDauth_sentCodeTypeSetUpEmailRequired &) {
|
||||
return bad("SetUpEmailRequired");
|
||||
});
|
||||
@@ -93,8 +97,10 @@ void ConfirmPhone::resolve(
|
||||
box->resendRequests(
|
||||
) | rpl::start_with_next([=] {
|
||||
_api.request(MTPauth_ResendCode(
|
||||
MTP_flags(0),
|
||||
MTP_string(phone),
|
||||
MTP_string(phoneHash)
|
||||
MTP_string(phoneHash),
|
||||
MTPstring() // reason
|
||||
)).done([=] {
|
||||
if (boxWeak) {
|
||||
boxWeak->callDone();
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
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_credits.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_updates.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] Data::CreditsHistoryEntry HistoryFromTL(
|
||||
const MTPStarsTransaction &tl,
|
||||
not_null<PeerData*> peer) {
|
||||
using HistoryPeerTL = MTPDstarsTransactionPeer;
|
||||
const auto photo = tl.data().vphoto()
|
||||
? peer->owner().photoFromWeb(*tl.data().vphoto(), ImageLocation())
|
||||
: nullptr;
|
||||
return Data::CreditsHistoryEntry{
|
||||
.id = qs(tl.data().vid()),
|
||||
.title = qs(tl.data().vtitle().value_or_empty()),
|
||||
.description = qs(tl.data().vdescription().value_or_empty()),
|
||||
.date = base::unixtime::parse(tl.data().vdate().v),
|
||||
.photoId = photo ? photo->id : 0,
|
||||
.credits = tl.data().vstars().v,
|
||||
.bareId = tl.data().vpeer().match([](const HistoryPeerTL &p) {
|
||||
return peerFromMTP(p.vpeer());
|
||||
}, [](const auto &) {
|
||||
return PeerId(0);
|
||||
}).value,
|
||||
.peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::Peer;
|
||||
}, [](const MTPDstarsTransactionPeerPlayMarket &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::PlayMarket;
|
||||
}, [](const MTPDstarsTransactionPeerFragment &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::Fragment;
|
||||
}, [](const MTPDstarsTransactionPeerAppStore &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::AppStore;
|
||||
}, [](const MTPDstarsTransactionPeerUnsupported &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::Unsupported;
|
||||
}, [](const MTPDstarsTransactionPeerPremiumBot &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::PremiumBot;
|
||||
}),
|
||||
.refunded = tl.data().is_refund(),
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] Data::CreditsStatusSlice StatusFromTL(
|
||||
const MTPpayments_StarsStatus &status,
|
||||
not_null<PeerData*> peer) {
|
||||
peer->owner().processUsers(status.data().vusers());
|
||||
peer->owner().processChats(status.data().vchats());
|
||||
return Data::CreditsStatusSlice{
|
||||
.list = ranges::views::all(
|
||||
status.data().vhistory().v
|
||||
) | ranges::views::transform([&](const MTPStarsTransaction &tl) {
|
||||
return HistoryFromTL(tl, peer);
|
||||
}) | ranges::to_vector,
|
||||
.balance = status.data().vbalance().v,
|
||||
.allLoaded = !status.data().vnext_offset().has_value(),
|
||||
.token = qs(status.data().vnext_offset().value_or_empty()),
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
CreditsTopupOptions::CreditsTopupOptions(not_null<PeerData*> peer)
|
||||
: _peer(peer)
|
||||
, _api(&peer->session().api().instance()) {
|
||||
}
|
||||
|
||||
rpl::producer<rpl::no_value, QString> CreditsTopupOptions::request() {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
using TLOption = MTPStarsTopupOption;
|
||||
_api.request(MTPpayments_GetStarsTopupOptions(
|
||||
)).done([=](const MTPVector<TLOption> &result) {
|
||||
_options = ranges::views::all(
|
||||
result.v
|
||||
) | ranges::views::transform([](const TLOption &option) {
|
||||
return Data::CreditTopupOption{
|
||||
.credits = option.data().vstars().v,
|
||||
.product = qs(
|
||||
option.data().vstore_product().value_or_empty()),
|
||||
.currency = qs(option.data().vcurrency()),
|
||||
.amount = option.data().vamount().v,
|
||||
.extended = option.data().is_extended(),
|
||||
};
|
||||
}) | ranges::to_vector;
|
||||
consumer.put_done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
CreditsStatus::CreditsStatus(not_null<PeerData*> peer)
|
||||
: _peer(peer)
|
||||
, _api(&peer->session().api().instance()) {
|
||||
}
|
||||
|
||||
void CreditsStatus::request(
|
||||
const Data::CreditsStatusSlice::OffsetToken &token,
|
||||
Fn<void(Data::CreditsStatusSlice)> done) {
|
||||
if (_requestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
using TLResult = MTPpayments_StarsStatus;
|
||||
|
||||
_requestId = _api.request(MTPpayments_GetStarsStatus(
|
||||
_peer->isSelf() ? MTP_inputPeerSelf() : _peer->input
|
||||
)).done([=](const TLResult &result) {
|
||||
_requestId = 0;
|
||||
done(StatusFromTL(result, _peer));
|
||||
}).fail([=] {
|
||||
_requestId = 0;
|
||||
done({});
|
||||
}).send();
|
||||
}
|
||||
|
||||
CreditsHistory::CreditsHistory(not_null<PeerData*> peer, bool in, bool out)
|
||||
: _peer(peer)
|
||||
, _flags((in == out)
|
||||
? HistoryTL::Flags(0)
|
||||
: HistoryTL::Flags(0)
|
||||
| (in ? HistoryTL::Flag::f_inbound : HistoryTL::Flags(0))
|
||||
| (out ? HistoryTL::Flag::f_outbound : HistoryTL::Flags(0)))
|
||||
, _api(&peer->session().api().instance()) {
|
||||
}
|
||||
|
||||
void CreditsHistory::request(
|
||||
const Data::CreditsStatusSlice::OffsetToken &token,
|
||||
Fn<void(Data::CreditsStatusSlice)> done) {
|
||||
if (_requestId) {
|
||||
return;
|
||||
}
|
||||
_requestId = _api.request(MTPpayments_GetStarsTransactions(
|
||||
MTP_flags(_flags),
|
||||
_peer->isSelf() ? MTP_inputPeerSelf() : _peer->input,
|
||||
MTP_string(token)
|
||||
)).done([=](const MTPpayments_StarsStatus &result) {
|
||||
_requestId = 0;
|
||||
done(StatusFromTL(result, _peer));
|
||||
}).fail([=] {
|
||||
_requestId = 0;
|
||||
done({});
|
||||
}).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>(
|
||||
u"premium_bot_username"_q,
|
||||
QString());
|
||||
if (username.isEmpty()) {
|
||||
return rpl::never<not_null<PeerData*>>();
|
||||
}
|
||||
if (const auto p = session->data().peerByUsername(username)) {
|
||||
return rpl::single<not_null<PeerData*>>(p);
|
||||
}
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
const auto api = lifetime.make_state<MTP::Sender>(&session->mtp());
|
||||
|
||||
api->request(MTPcontacts_ResolveUsername(
|
||||
MTP_string(username)
|
||||
)).done([=](const MTPcontacts_ResolvedPeer &result) {
|
||||
session->data().processUsers(result.data().vusers());
|
||||
session->data().processChats(result.data().vchats());
|
||||
const auto botPeer = session->data().peerLoaded(
|
||||
peerFromMTP(result.data().vpeer()));
|
||||
if (!botPeer) {
|
||||
return consumer.put_done();
|
||||
}
|
||||
consumer.put_next(not_null{ botPeer });
|
||||
}).send();
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
void CreditsRefund(
|
||||
not_null<PeerData*> peer,
|
||||
const QString &entryId,
|
||||
Fn<void()> done,
|
||||
Fn<void(QString)> fail) {
|
||||
const auto user = peer->asUser();
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
peer->session().api().request(MTPpayments_RefundStarsCharge(
|
||||
user->inputUser,
|
||||
MTP_string(entryId)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
peer->session().api().updates().applyUpdates(result);
|
||||
done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
fail(error.type());
|
||||
}).send();
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "data/data_credits.h"
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Api {
|
||||
|
||||
class CreditsTopupOptions final {
|
||||
public:
|
||||
CreditsTopupOptions(not_null<PeerData*> peer);
|
||||
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> request();
|
||||
[[nodiscard]] Data::CreditTopupOptions options() const;
|
||||
|
||||
private:
|
||||
const not_null<PeerData*> _peer;
|
||||
|
||||
Data::CreditTopupOptions _options;
|
||||
|
||||
MTP::Sender _api;
|
||||
|
||||
};
|
||||
|
||||
class CreditsStatus final {
|
||||
public:
|
||||
CreditsStatus(not_null<PeerData*> peer);
|
||||
|
||||
void request(
|
||||
const Data::CreditsStatusSlice::OffsetToken &token,
|
||||
Fn<void(Data::CreditsStatusSlice)> done);
|
||||
|
||||
private:
|
||||
const not_null<PeerData*> _peer;
|
||||
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
MTP::Sender _api;
|
||||
|
||||
};
|
||||
|
||||
class CreditsHistory final {
|
||||
public:
|
||||
CreditsHistory(not_null<PeerData*> peer, bool in, bool out);
|
||||
|
||||
void request(
|
||||
const Data::CreditsStatusSlice::OffsetToken &token,
|
||||
Fn<void(Data::CreditsStatusSlice)> done);
|
||||
|
||||
private:
|
||||
using HistoryTL = MTPpayments_GetStarsTransactions;
|
||||
const not_null<PeerData*> _peer;
|
||||
const HistoryTL::Flags _flags;
|
||||
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
MTP::Sender _api;
|
||||
|
||||
};
|
||||
|
||||
void CreditsRefund(
|
||||
not_null<PeerData*> peer,
|
||||
const QString &entryId,
|
||||
Fn<void()> done,
|
||||
Fn<void(QString)> fail);
|
||||
|
||||
[[nodiscard]] rpl::producer<not_null<PeerData*>> PremiumPeerBot(
|
||||
not_null<Main::Session*> session);
|
||||
|
||||
} // namespace Api
|
||||
@@ -13,9 +13,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "data/business/data_shortcut_messages.h"
|
||||
#include "data/components/scheduled_messages.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_web_page.h"
|
||||
#include "history/view/controls/history_view_compose_media_edit_manager.h"
|
||||
#include "history/history.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
@@ -80,7 +82,8 @@ mtpRequestId EditMessage(
|
||||
| ((!webpage.removed && !webpage.url.isEmpty())
|
||||
? MTPmessages_EditMessage::Flag::f_media
|
||||
: emptyFlag)
|
||||
| ((!webpage.removed && !webpage.url.isEmpty() && webpage.invert)
|
||||
| (((!webpage.removed && !webpage.url.isEmpty() && webpage.invert)
|
||||
|| options.invertCaption)
|
||||
? MTPmessages_EditMessage::Flag::f_invert_media
|
||||
: emptyFlag)
|
||||
| (!sentEntities.v.isEmpty()
|
||||
@@ -202,6 +205,7 @@ void RescheduleMessage(
|
||||
not_null<HistoryItem*> item,
|
||||
SendOptions options) {
|
||||
const auto empty = [] {};
|
||||
options.invertCaption = item->invertMedia();
|
||||
EditMessage(item, options, empty, empty);
|
||||
}
|
||||
|
||||
@@ -252,12 +256,100 @@ mtpRequestId EditTextMessage(
|
||||
Data::WebPageDraft webpage,
|
||||
SendOptions options,
|
||||
Fn<void(mtpRequestId requestId)> done,
|
||||
Fn<void(const QString &, mtpRequestId requestId)> fail) {
|
||||
Fn<void(const QString &error, mtpRequestId requestId)> fail,
|
||||
bool spoilered) {
|
||||
const auto media = item->media();
|
||||
if (media
|
||||
&& HistoryView::MediaEditManager::CanBeSpoilered(item)
|
||||
&& spoilered != media->hasSpoiler()) {
|
||||
auto takeInputMedia = Fn<std::optional<MTPInputMedia>()>(nullptr);
|
||||
auto takeFileReference = Fn<QByteArray()>(nullptr);
|
||||
if (const auto photo = media->photo()) {
|
||||
using Flag = MTPDinputMediaPhoto::Flag;
|
||||
const auto flags = Flag()
|
||||
| (media->ttlSeconds() ? Flag::f_ttl_seconds : Flag())
|
||||
| (spoilered ? Flag::f_spoiler : Flag());
|
||||
takeInputMedia = [=] {
|
||||
return MTP_inputMediaPhoto(
|
||||
MTP_flags(flags),
|
||||
photo->mtpInput(),
|
||||
MTP_int(media->ttlSeconds()));
|
||||
};
|
||||
takeFileReference = [=] { return photo->fileReference(); };
|
||||
} else if (const auto document = media->document()) {
|
||||
using Flag = MTPDinputMediaDocument::Flag;
|
||||
const auto flags = Flag()
|
||||
| (media->ttlSeconds() ? Flag::f_ttl_seconds : Flag())
|
||||
| (spoilered ? Flag::f_spoiler : Flag());
|
||||
takeInputMedia = [=] {
|
||||
return MTP_inputMediaDocument(
|
||||
MTP_flags(flags),
|
||||
document->mtpInput(),
|
||||
MTP_int(media->ttlSeconds()),
|
||||
MTPstring()); // query
|
||||
};
|
||||
takeFileReference = [=] { return document->fileReference(); };
|
||||
}
|
||||
|
||||
const auto usedFileReference = takeFileReference
|
||||
? takeFileReference()
|
||||
: QByteArray();
|
||||
const auto origin = item->fullId();
|
||||
const auto api = &item->history()->session().api();
|
||||
const auto performRequest = [=](
|
||||
const auto &repeatRequest,
|
||||
mtpRequestId originalRequestId) -> mtpRequestId {
|
||||
const auto handleReference = [=](
|
||||
const QString &error,
|
||||
mtpRequestId requestId) {
|
||||
if (error.startsWith(u"FILE_REFERENCE_"_q)) {
|
||||
api->refreshFileReference(origin, [=](const auto &) {
|
||||
if (takeFileReference &&
|
||||
(takeFileReference() != usedFileReference)) {
|
||||
repeatRequest(
|
||||
repeatRequest,
|
||||
originalRequestId
|
||||
? originalRequestId
|
||||
: requestId);
|
||||
} else {
|
||||
fail(error, requestId);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
fail(error, requestId);
|
||||
}
|
||||
};
|
||||
const auto callback = [=](
|
||||
Fn<void()> applyUpdates,
|
||||
mtpRequestId requestId) {
|
||||
applyUpdates();
|
||||
done(originalRequestId ? originalRequestId : requestId);
|
||||
};
|
||||
const auto requestId = EditMessage(
|
||||
item,
|
||||
caption,
|
||||
webpage,
|
||||
options,
|
||||
callback,
|
||||
handleReference,
|
||||
takeInputMedia ? takeInputMedia() : std::nullopt);
|
||||
return originalRequestId ? originalRequestId : requestId;
|
||||
};
|
||||
return performRequest(performRequest, 0);
|
||||
}
|
||||
|
||||
const auto callback = [=](Fn<void()> applyUpdates, mtpRequestId id) {
|
||||
applyUpdates();
|
||||
done(id);
|
||||
};
|
||||
return EditMessage(item, caption, webpage, options, callback, fail);
|
||||
return EditMessage(
|
||||
item,
|
||||
caption,
|
||||
webpage,
|
||||
options,
|
||||
callback,
|
||||
fail,
|
||||
std::nullopt);
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -55,6 +55,7 @@ mtpRequestId EditTextMessage(
|
||||
Data::WebPageDraft webpage,
|
||||
SendOptions options,
|
||||
Fn<void(mtpRequestId requestId)> done,
|
||||
Fn<void(const QString &error, mtpRequestId requestId)> fail);
|
||||
Fn<void(const QString &error, mtpRequestId requestId)> fail,
|
||||
bool spoilered);
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -73,6 +73,9 @@ void Polls::create(
|
||||
if (action.options.shortcutId) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
|
||||
}
|
||||
if (action.options.effectId) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
|
||||
}
|
||||
const auto sendAs = action.options.sendAs;
|
||||
if (sendAs) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_send_as;
|
||||
@@ -94,7 +97,8 @@ void Polls::create(
|
||||
MTPVector<MTPMessageEntity>(),
|
||||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, action.options.shortcutId)
|
||||
Data::ShortcutIdToMTP(_session, action.options.shortcutId),
|
||||
MTP_long(action.options.effectId)
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
if (clearCloudDraft) {
|
||||
history->finishSavingCloudDraft(
|
||||
|
||||
@@ -602,6 +602,41 @@ bool PremiumGiftCodeOptions::giveawayGiftsPurchaseAvailable() const {
|
||||
false);
|
||||
}
|
||||
|
||||
SponsoredToggle::SponsoredToggle(not_null<Main::Session*> session)
|
||||
: _api(&session->api().instance()) {
|
||||
}
|
||||
|
||||
rpl::producer<bool> SponsoredToggle::toggled() {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
_api.request(MTPusers_GetFullUser(
|
||||
MTP_inputUserSelf()
|
||||
)).done([=](const MTPusers_UserFull &result) {
|
||||
consumer.put_next_copy(
|
||||
result.data().vfull_user().data().is_sponsored_enabled());
|
||||
}).fail([=] { consumer.put_next(false); }).send();
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
rpl::producer<rpl::no_value, QString> SponsoredToggle::setToggled(bool v) {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
_api.request(MTPaccount_ToggleSponsoredMessages(
|
||||
MTP_bool(v)
|
||||
)).done([=] {
|
||||
consumer.put_done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
RequirePremiumState ResolveRequiresPremiumToWrite(
|
||||
not_null<PeerData*> peer,
|
||||
History *maybeHistory) {
|
||||
|
||||
@@ -216,6 +216,18 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class SponsoredToggle final {
|
||||
public:
|
||||
explicit SponsoredToggle(not_null<Main::Session*> session);
|
||||
|
||||
[[nodiscard]] rpl::producer<bool> toggled();
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> setToggled(bool);
|
||||
|
||||
private:
|
||||
MTP::Sender _api;
|
||||
|
||||
};
|
||||
|
||||
enum class RequirePremiumState {
|
||||
Unknown,
|
||||
Yes,
|
||||
|
||||
@@ -133,6 +133,13 @@ void SendExistingMedia(
|
||||
flags |= MessageFlag::ShortcutMessage;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
|
||||
}
|
||||
if (action.options.effectId) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
|
||||
}
|
||||
if (action.options.invertCaption) {
|
||||
flags |= MessageFlag::InvertMedia;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
|
||||
}
|
||||
|
||||
session->data().registerMessageRandomId(randomId, newId);
|
||||
|
||||
@@ -144,6 +151,7 @@ void SendExistingMedia(
|
||||
.date = HistoryItem::NewMessageDate(action.options),
|
||||
.shortcutId = action.options.shortcutId,
|
||||
.postAuthor = messagePostAuthor,
|
||||
.effectId = action.options.effectId,
|
||||
}, media, caption);
|
||||
|
||||
const auto performRequest = [=](const auto &repeatRequest) -> void {
|
||||
@@ -165,7 +173,8 @@ void SendExistingMedia(
|
||||
sentEntities,
|
||||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(session, action.options.shortcutId)
|
||||
Data::ShortcutIdToMTP(session, action.options.shortcutId),
|
||||
MTP_long(action.options.effectId)
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
}, [=](const MTP::Error &error, const MTP::Response &response) {
|
||||
if (error.code() == 400
|
||||
@@ -306,6 +315,13 @@ bool SendDice(MessageToSend &message) {
|
||||
flags |= MessageFlag::ShortcutMessage;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
|
||||
}
|
||||
if (action.options.effectId) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
|
||||
}
|
||||
if (action.options.invertCaption) {
|
||||
flags |= MessageFlag::InvertMedia;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
|
||||
}
|
||||
|
||||
session->data().registerMessageRandomId(randomId, newId);
|
||||
|
||||
@@ -317,6 +333,7 @@ bool SendDice(MessageToSend &message) {
|
||||
.date = HistoryItem::NewMessageDate(action.options),
|
||||
.shortcutId = action.options.shortcutId,
|
||||
.postAuthor = messagePostAuthor,
|
||||
.effectId = action.options.effectId,
|
||||
}, TextWithEntities(), MTP_messageMediaDice(
|
||||
MTP_int(0),
|
||||
MTP_string(emoji)));
|
||||
@@ -335,7 +352,8 @@ bool SendDice(MessageToSend &message) {
|
||||
MTP_vector<MTPMessageEntity>(),
|
||||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(session, action.options.shortcutId)
|
||||
Data::ShortcutIdToMTP(session, action.options.shortcutId),
|
||||
MTP_long(action.options.effectId)
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
}, [=](const MTP::Error &error, const MTP::Response &response) {
|
||||
api->sendMessageFail(error, peer, randomId, newId);
|
||||
@@ -430,6 +448,9 @@ void SendConfirmedFile(
|
||||
flags |= MessageFlag::MediaIsUnread;
|
||||
}
|
||||
}
|
||||
if (file->to.options.invertCaption) {
|
||||
flags |= MessageFlag::InvertMedia;
|
||||
}
|
||||
|
||||
const auto messageFromId = file->to.options.sendAs
|
||||
? file->to.options.sendAs->id
|
||||
@@ -493,6 +514,7 @@ void SendConfirmedFile(
|
||||
edition.ttl = 0;
|
||||
edition.mtpMedia = &media;
|
||||
edition.textWithEntities = caption;
|
||||
edition.invertMedia = file->to.options.invertCaption;
|
||||
edition.useSameViews = true;
|
||||
edition.useSameForwards = true;
|
||||
edition.useSameMarkup = true;
|
||||
@@ -510,6 +532,7 @@ void SendConfirmedFile(
|
||||
.shortcutId = file->to.options.shortcutId,
|
||||
.postAuthor = messagePostAuthor,
|
||||
.groupedId = groupId,
|
||||
.effectId = file->to.options.effectId,
|
||||
}, caption, media);
|
||||
}
|
||||
|
||||
|
||||
@@ -760,14 +760,14 @@ rpl::producer<rpl::no_value, QString> EarnStatistics::request() {
|
||||
channel()->inputChannel
|
||||
)).done([=](const MTPstats_BroadcastRevenueStats &result) {
|
||||
const auto &data = result.data();
|
||||
|
||||
const auto &balances = data.vbalances().data();
|
||||
_data = Data::EarnStatistics{
|
||||
.topHoursGraph = StatisticalGraphFromTL(
|
||||
data.vtop_hours_graph()),
|
||||
.revenueGraph = StatisticalGraphFromTL(data.vrevenue_graph()),
|
||||
.currentBalance = data.vcurrent_balance().v,
|
||||
.availableBalance = data.vavailable_balance().v,
|
||||
.overallRevenue = data.voverall_revenue().v,
|
||||
.currentBalance = balances.vcurrent_balance().v,
|
||||
.availableBalance = balances.vavailable_balance().v,
|
||||
.overallRevenue = balances.voverall_revenue().v,
|
||||
.usdRate = data.vusd_rate().v,
|
||||
};
|
||||
|
||||
|
||||
@@ -7,12 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "api/api_text_entities.h"
|
||||
|
||||
#include "main/main_session.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "data/stickers/data_stickers_set.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_user.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
@@ -62,67 +62,168 @@ using namespace TextUtilities;
|
||||
EntitiesInText EntitiesFromMTP(
|
||||
Main::Session *session,
|
||||
const QVector<MTPMessageEntity> &entities) {
|
||||
if (entities.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
auto result = EntitiesInText();
|
||||
if (!entities.isEmpty()) {
|
||||
result.reserve(entities.size());
|
||||
for (const auto &entity : entities) {
|
||||
switch (entity.type()) {
|
||||
case mtpc_messageEntityUrl: { auto &d = entity.c_messageEntityUrl(); result.push_back({ EntityType::Url, d.voffset().v, d.vlength().v }); } break;
|
||||
case mtpc_messageEntityTextUrl: { auto &d = entity.c_messageEntityTextUrl(); result.push_back({ EntityType::CustomUrl, d.voffset().v, d.vlength().v, qs(d.vurl()) }); } break;
|
||||
case mtpc_messageEntityEmail: { auto &d = entity.c_messageEntityEmail(); result.push_back({ EntityType::Email, d.voffset().v, d.vlength().v }); } break;
|
||||
case mtpc_messageEntityHashtag: { auto &d = entity.c_messageEntityHashtag(); result.push_back({ EntityType::Hashtag, d.voffset().v, d.vlength().v }); } break;
|
||||
case mtpc_messageEntityCashtag: { auto &d = entity.c_messageEntityCashtag(); result.push_back({ EntityType::Cashtag, d.voffset().v, d.vlength().v }); } break;
|
||||
case mtpc_messageEntityPhone: break; // Skipping phones.
|
||||
case mtpc_messageEntityMention: { auto &d = entity.c_messageEntityMention(); result.push_back({ EntityType::Mention, d.voffset().v, d.vlength().v }); } break;
|
||||
case mtpc_messageEntityMentionName: if (session) {
|
||||
const auto &d = entity.c_messageEntityMentionName();
|
||||
const auto userId = UserId(d.vuser_id());
|
||||
const auto user = session->data().userLoaded(userId);
|
||||
const auto data = MentionNameDataFromFields({
|
||||
.selfId = session->userId().bare,
|
||||
.userId = userId.bare,
|
||||
.accessHash = user ? user->accessHash() : 0,
|
||||
});
|
||||
result.push_back({ EntityType::MentionName, d.voffset().v, d.vlength().v, data });
|
||||
} break;
|
||||
case mtpc_inputMessageEntityMentionName: if (session) {
|
||||
const auto &d = entity.c_inputMessageEntityMentionName();
|
||||
const auto data = d.vuser_id().match([&](
|
||||
const MTPDinputUserSelf &) {
|
||||
return MentionNameDataFromFields({
|
||||
.selfId = session->userId().bare,
|
||||
.userId = session->userId().bare,
|
||||
.accessHash = session->user()->accessHash(),
|
||||
});
|
||||
}, [&](const MTPDinputUser &data) {
|
||||
return MentionNameDataFromFields({
|
||||
.selfId = session->userId().bare,
|
||||
.userId = UserId(data.vuser_id()).bare,
|
||||
.accessHash = data.vaccess_hash().v,
|
||||
});
|
||||
}, [&](const auto &) {
|
||||
return QString();
|
||||
});
|
||||
if (!data.isEmpty()) {
|
||||
result.push_back({ EntityType::MentionName, d.voffset().v, d.vlength().v, data });
|
||||
}
|
||||
} break;
|
||||
case mtpc_messageEntityBotCommand: { auto &d = entity.c_messageEntityBotCommand(); result.push_back({ EntityType::BotCommand, d.voffset().v, d.vlength().v }); } break;
|
||||
case mtpc_messageEntityBold: { auto &d = entity.c_messageEntityBold(); result.push_back({ EntityType::Bold, d.voffset().v, d.vlength().v }); } break;
|
||||
case mtpc_messageEntityItalic: { auto &d = entity.c_messageEntityItalic(); result.push_back({ EntityType::Italic, d.voffset().v, d.vlength().v }); } break;
|
||||
case mtpc_messageEntityUnderline: { auto &d = entity.c_messageEntityUnderline(); result.push_back({ EntityType::Underline, d.voffset().v, d.vlength().v }); } break;
|
||||
case mtpc_messageEntityStrike: { auto &d = entity.c_messageEntityStrike(); result.push_back({ EntityType::StrikeOut, d.voffset().v, d.vlength().v }); } break;
|
||||
case mtpc_messageEntityCode: { auto &d = entity.c_messageEntityCode(); result.push_back({ EntityType::Code, d.voffset().v, d.vlength().v }); } break;
|
||||
case mtpc_messageEntityPre: { auto &d = entity.c_messageEntityPre(); result.push_back({ EntityType::Pre, d.voffset().v, d.vlength().v, qs(d.vlanguage()) }); } break;
|
||||
case mtpc_messageEntityBlockquote: { auto &d = entity.c_messageEntityBlockquote(); result.push_back({ EntityType::Blockquote, d.voffset().v, d.vlength().v }); } break;
|
||||
case mtpc_messageEntityBankCard: break; // Skipping cards. // #TODO entities
|
||||
case mtpc_messageEntitySpoiler: { auto &d = entity.c_messageEntitySpoiler(); result.push_back({ EntityType::Spoiler, d.voffset().v, d.vlength().v }); } break;
|
||||
case mtpc_messageEntityCustomEmoji: {
|
||||
const auto &d = entity.c_messageEntityCustomEmoji();
|
||||
result.push_back({ EntityType::CustomEmoji, d.voffset().v, d.vlength().v, CustomEmojiEntityData(d) });
|
||||
} break;
|
||||
result.reserve(entities.size());
|
||||
|
||||
for (const auto &entity : entities) {
|
||||
entity.match([&](const MTPDmessageEntityUnknown &d) {
|
||||
}, [&](const MTPDmessageEntityMention &d) {
|
||||
result.push_back({
|
||||
EntityType::Mention,
|
||||
d.voffset().v,
|
||||
d.vlength().v,
|
||||
});
|
||||
}, [&](const MTPDmessageEntityHashtag &d) {
|
||||
result.push_back({
|
||||
EntityType::Hashtag,
|
||||
d.voffset().v,
|
||||
d.vlength().v,
|
||||
});
|
||||
}, [&](const MTPDmessageEntityBotCommand &d) {
|
||||
result.push_back({
|
||||
EntityType::BotCommand,
|
||||
d.voffset().v,
|
||||
d.vlength().v,
|
||||
});
|
||||
}, [&](const MTPDmessageEntityUrl &d) {
|
||||
result.push_back({
|
||||
EntityType::Url,
|
||||
d.voffset().v,
|
||||
d.vlength().v,
|
||||
});
|
||||
}, [&](const MTPDmessageEntityEmail &d) {
|
||||
result.push_back({
|
||||
EntityType::Email,
|
||||
d.voffset().v,
|
||||
d.vlength().v,
|
||||
});
|
||||
}, [&](const MTPDmessageEntityBold &d) {
|
||||
result.push_back({
|
||||
EntityType::Bold,
|
||||
d.voffset().v,
|
||||
d.vlength().v,
|
||||
});
|
||||
}, [&](const MTPDmessageEntityItalic &d) {
|
||||
result.push_back({
|
||||
EntityType::Italic,
|
||||
d.voffset().v,
|
||||
d.vlength().v,
|
||||
});
|
||||
}, [&](const MTPDmessageEntityCode &d) {
|
||||
result.push_back({
|
||||
EntityType::Code,
|
||||
d.voffset().v,
|
||||
d.vlength().v,
|
||||
});
|
||||
}, [&](const MTPDmessageEntityPre &d) {
|
||||
result.push_back({
|
||||
EntityType::Pre,
|
||||
d.voffset().v,
|
||||
d.vlength().v,
|
||||
qs(d.vlanguage()),
|
||||
});
|
||||
}, [&](const MTPDmessageEntityTextUrl &d) {
|
||||
result.push_back({
|
||||
EntityType::CustomUrl,
|
||||
d.voffset().v,
|
||||
d.vlength().v,
|
||||
qs(d.vurl()),
|
||||
});
|
||||
}, [&](const MTPDmessageEntityMentionName &d) {
|
||||
if (!session) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const auto userId = UserId(d.vuser_id());
|
||||
const auto user = session->data().userLoaded(userId);
|
||||
const auto data = MentionNameDataFromFields({
|
||||
.selfId = session->userId().bare,
|
||||
.userId = userId.bare,
|
||||
.accessHash = user ? user->accessHash() : 0,
|
||||
});
|
||||
result.push_back({
|
||||
EntityType::MentionName,
|
||||
d.voffset().v,
|
||||
d.vlength().v,
|
||||
data,
|
||||
});
|
||||
}, [&](const MTPDinputMessageEntityMentionName &d) {
|
||||
if (!session) {
|
||||
return;
|
||||
}
|
||||
const auto data = d.vuser_id().match([&](
|
||||
const MTPDinputUserSelf &) {
|
||||
return MentionNameDataFromFields({
|
||||
.selfId = session->userId().bare,
|
||||
.userId = session->userId().bare,
|
||||
.accessHash = session->user()->accessHash(),
|
||||
});
|
||||
}, [&](const MTPDinputUser &data) {
|
||||
return MentionNameDataFromFields({
|
||||
.selfId = session->userId().bare,
|
||||
.userId = UserId(data.vuser_id()).bare,
|
||||
.accessHash = data.vaccess_hash().v,
|
||||
});
|
||||
}, [](const auto &) {
|
||||
return QString();
|
||||
});
|
||||
if (!data.isEmpty()) {
|
||||
result.push_back({
|
||||
EntityType::MentionName,
|
||||
d.voffset().v,
|
||||
d.vlength().v,
|
||||
data,
|
||||
});
|
||||
}
|
||||
}, [&](const MTPDmessageEntityPhone &d) {
|
||||
result.push_back({
|
||||
EntityType::Phone,
|
||||
d.voffset().v,
|
||||
d.vlength().v,
|
||||
});
|
||||
}, [&](const MTPDmessageEntityCashtag &d) {
|
||||
result.push_back({
|
||||
EntityType::Cashtag,
|
||||
d.voffset().v,
|
||||
d.vlength().v,
|
||||
});
|
||||
}, [&](const MTPDmessageEntityUnderline &d) {
|
||||
result.push_back({
|
||||
EntityType::Underline,
|
||||
d.voffset().v,
|
||||
d.vlength().v,
|
||||
});
|
||||
}, [&](const MTPDmessageEntityStrike &d) {
|
||||
result.push_back({
|
||||
EntityType::StrikeOut,
|
||||
d.voffset().v,
|
||||
d.vlength().v,
|
||||
});
|
||||
}, [&](const MTPDmessageEntityBankCard &d) {
|
||||
// Skipping cards. // #TODO entities
|
||||
}, [&](const MTPDmessageEntitySpoiler &d) {
|
||||
result.push_back({
|
||||
EntityType::Spoiler,
|
||||
d.voffset().v,
|
||||
d.vlength().v,
|
||||
});
|
||||
}, [&](const MTPDmessageEntityCustomEmoji &d) {
|
||||
result.push_back({
|
||||
EntityType::CustomEmoji,
|
||||
d.voffset().v,
|
||||
d.vlength().v,
|
||||
CustomEmojiEntityData(d),
|
||||
});
|
||||
}, [&](const MTPDmessageEntityBlockquote &d) {
|
||||
result.push_back({
|
||||
EntityType::Blockquote,
|
||||
d.voffset().v,
|
||||
d.vlength().v,
|
||||
d.is_collapsed() ? u"1"_q : QString(),
|
||||
});
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -134,7 +235,9 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(
|
||||
auto v = QVector<MTPMessageEntity>();
|
||||
v.reserve(entities.size());
|
||||
for (const auto &entity : entities) {
|
||||
if (entity.length() <= 0) continue;
|
||||
if (entity.length() <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (option == ConvertOption::SkipLocal
|
||||
&& entity.type() != EntityType::Bold
|
||||
//&& entity.type() != EntityType::Semibold // Not in API.
|
||||
@@ -154,28 +257,85 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(
|
||||
auto offset = MTP_int(entity.offset());
|
||||
auto length = MTP_int(entity.length());
|
||||
switch (entity.type()) {
|
||||
case EntityType::Url: v.push_back(MTP_messageEntityUrl(offset, length)); break;
|
||||
case EntityType::CustomUrl: v.push_back(MTP_messageEntityTextUrl(offset, length, MTP_string(entity.data()))); break;
|
||||
case EntityType::Email: v.push_back(MTP_messageEntityEmail(offset, length)); break;
|
||||
case EntityType::Hashtag: v.push_back(MTP_messageEntityHashtag(offset, length)); break;
|
||||
case EntityType::Cashtag: v.push_back(MTP_messageEntityCashtag(offset, length)); break;
|
||||
case EntityType::Mention: v.push_back(MTP_messageEntityMention(offset, length)); break;
|
||||
case EntityType::Url: {
|
||||
v.push_back(MTP_messageEntityUrl(offset, length));
|
||||
} break;
|
||||
case EntityType::CustomUrl: {
|
||||
v.push_back(
|
||||
MTP_messageEntityTextUrl(
|
||||
offset,
|
||||
length,
|
||||
MTP_string(entity.data())));
|
||||
} break;
|
||||
case EntityType::Email: {
|
||||
v.push_back(MTP_messageEntityEmail(offset, length));
|
||||
} break;
|
||||
case EntityType::Phone: {
|
||||
v.push_back(MTP_messageEntityPhone(offset, length));
|
||||
} break;
|
||||
case EntityType::Hashtag: {
|
||||
v.push_back(MTP_messageEntityHashtag(offset, length));
|
||||
} break;
|
||||
case EntityType::Cashtag: {
|
||||
v.push_back(MTP_messageEntityCashtag(offset, length));
|
||||
} break;
|
||||
case EntityType::Mention: {
|
||||
v.push_back(MTP_messageEntityMention(offset, length));
|
||||
} break;
|
||||
case EntityType::MentionName: {
|
||||
if (const auto valid = MentionNameEntity(session, offset, length, entity.data())) {
|
||||
const auto valid = MentionNameEntity(
|
||||
session,
|
||||
offset,
|
||||
length,
|
||||
entity.data());
|
||||
if (valid) {
|
||||
v.push_back(*valid);
|
||||
}
|
||||
} break;
|
||||
case EntityType::BotCommand: v.push_back(MTP_messageEntityBotCommand(offset, length)); break;
|
||||
case EntityType::Bold: v.push_back(MTP_messageEntityBold(offset, length)); break;
|
||||
case EntityType::Italic: v.push_back(MTP_messageEntityItalic(offset, length)); break;
|
||||
case EntityType::Underline: v.push_back(MTP_messageEntityUnderline(offset, length)); break;
|
||||
case EntityType::StrikeOut: v.push_back(MTP_messageEntityStrike(offset, length)); break;
|
||||
case EntityType::Code: v.push_back(MTP_messageEntityCode(offset, length)); break; // #TODO entities
|
||||
case EntityType::Pre: v.push_back(MTP_messageEntityPre(offset, length, MTP_string(entity.data()))); break;
|
||||
case EntityType::Blockquote: v.push_back(MTP_messageEntityBlockquote(offset, length)); break;
|
||||
case EntityType::Spoiler: v.push_back(MTP_messageEntitySpoiler(offset, length)); break;
|
||||
case EntityType::BotCommand: {
|
||||
v.push_back(MTP_messageEntityBotCommand(offset, length));
|
||||
} break;
|
||||
case EntityType::Bold: {
|
||||
v.push_back(MTP_messageEntityBold(offset, length));
|
||||
} break;
|
||||
case EntityType::Italic: {
|
||||
v.push_back(MTP_messageEntityItalic(offset, length));
|
||||
} break;
|
||||
case EntityType::Underline: {
|
||||
v.push_back(MTP_messageEntityUnderline(offset, length));
|
||||
} break;
|
||||
case EntityType::StrikeOut: {
|
||||
v.push_back(MTP_messageEntityStrike(offset, length));
|
||||
} break;
|
||||
case EntityType::Code: {
|
||||
// #TODO entities.
|
||||
v.push_back(MTP_messageEntityCode(offset, length));
|
||||
} break;
|
||||
case EntityType::Pre: {
|
||||
v.push_back(
|
||||
MTP_messageEntityPre(
|
||||
offset,
|
||||
length,
|
||||
MTP_string(entity.data())));
|
||||
} break;
|
||||
case EntityType::Blockquote: {
|
||||
using Flag = MTPDmessageEntityBlockquote::Flag;
|
||||
const auto collapsed = !entity.data().isEmpty();
|
||||
v.push_back(
|
||||
MTP_messageEntityBlockquote(
|
||||
MTP_flags(collapsed ? Flag::f_collapsed : Flag()),
|
||||
offset,
|
||||
length));
|
||||
} break;
|
||||
case EntityType::Spoiler: {
|
||||
v.push_back(MTP_messageEntitySpoiler(offset, length));
|
||||
} break;
|
||||
case EntityType::CustomEmoji: {
|
||||
if (const auto valid = CustomEmojiEntity(offset, length, entity.data())) {
|
||||
const auto valid = CustomEmojiEntity(
|
||||
offset,
|
||||
length,
|
||||
entity.data());
|
||||
if (valid) {
|
||||
v.push_back(*valid);
|
||||
}
|
||||
} break;
|
||||
|
||||
@@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "mtproto/mtproto_dc_options.h"
|
||||
#include "data/business/data_shortcut_messages.h"
|
||||
#include "data/components/scheduled_messages.h"
|
||||
#include "data/components/top_peers.h"
|
||||
#include "data/notify/data_notify_settings.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "data/data_saved_messages.h"
|
||||
@@ -1144,7 +1145,9 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
|
||||
MTPMessageReactions(),
|
||||
MTPVector<MTPRestrictionReason>(),
|
||||
MTP_int(d.vttl_period().value_or_empty()),
|
||||
MTPint()), // quick_reply_shortcut_id
|
||||
MTPint(), // quick_reply_shortcut_id
|
||||
MTPlong(), // effect
|
||||
MTPFactCheck()),
|
||||
MessageFlags(),
|
||||
NewMessageType::Unread);
|
||||
} break;
|
||||
@@ -1179,7 +1182,9 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
|
||||
MTPMessageReactions(),
|
||||
MTPVector<MTPRestrictionReason>(),
|
||||
MTP_int(d.vttl_period().value_or_empty()),
|
||||
MTPint()), // quick_reply_shortcut_id
|
||||
MTPint(), // quick_reply_shortcut_id
|
||||
MTPlong(), // effect
|
||||
MTPFactCheck()),
|
||||
MessageFlags(),
|
||||
NewMessageType::Unread);
|
||||
} break;
|
||||
@@ -1585,6 +1590,11 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
} else {
|
||||
if (existing) {
|
||||
existing->destroy();
|
||||
} else {
|
||||
// Not the server-side date, but close enough.
|
||||
session().topPeers().increment(
|
||||
local->history()->peer,
|
||||
local->date());
|
||||
}
|
||||
local->setRealId(d.vid().v);
|
||||
}
|
||||
@@ -2612,6 +2622,11 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
_session->data().stories().apply(data.vstealth_mode());
|
||||
} break;
|
||||
|
||||
case mtpc_updateStarsBalance: {
|
||||
const auto &data = update.c_updateStarsBalance();
|
||||
_session->setCredits(data.vbalance().v);
|
||||
} break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_history_messages.h"
|
||||
#include "core/core_cloud_password.h"
|
||||
#include "core/application.h"
|
||||
#include "base/unixtime.h"
|
||||
@@ -3117,6 +3118,46 @@ void ApiWrap::resolveJumpToHistoryDate(
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::requestHistory(
|
||||
not_null<History*> history,
|
||||
MsgId messageId,
|
||||
SliceType slice) {
|
||||
const auto peer = history->peer;
|
||||
const auto key = HistoryRequest{
|
||||
peer,
|
||||
messageId,
|
||||
slice,
|
||||
};
|
||||
if (_historyRequests.contains(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto prepared = Api::PrepareHistoryRequest(peer, messageId, slice);
|
||||
auto &histories = history->owner().histories();
|
||||
const auto requestType = Data::Histories::RequestType::History;
|
||||
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
||||
return request(
|
||||
std::move(prepared)
|
||||
).done([=](const Api::HistoryRequestResult &result) {
|
||||
_historyRequests.remove(key);
|
||||
auto parsed = Api::ParseHistoryResult(
|
||||
peer,
|
||||
messageId,
|
||||
slice,
|
||||
result);
|
||||
history->messages().addSlice(
|
||||
std::move(parsed.messageIds),
|
||||
parsed.noSkipRange,
|
||||
parsed.fullCount);
|
||||
finish();
|
||||
}).fail([=] {
|
||||
_historyRequests.remove(key);
|
||||
finish();
|
||||
}).send();
|
||||
});
|
||||
_historyRequests.emplace(key);
|
||||
}
|
||||
|
||||
void ApiWrap::requestSharedMedia(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId topicRootId,
|
||||
@@ -3388,6 +3429,9 @@ void ApiWrap::forwardMessages(
|
||||
.date = HistoryItem::NewMessageDate(action.options),
|
||||
.shortcutId = action.options.shortcutId,
|
||||
.postAuthor = messagePostAuthor,
|
||||
|
||||
// forwarded messages don't have effects
|
||||
//.effectId = action.options.effectId,
|
||||
}, item);
|
||||
_session->data().registerMessageRandomId(randomId, newId);
|
||||
if (!localIds) {
|
||||
@@ -3488,6 +3532,7 @@ void ApiWrap::sendSharedContact(
|
||||
.date = HistoryItem::NewMessageDate(action.options),
|
||||
.shortcutId = action.options.shortcutId,
|
||||
.postAuthor = messagePostAuthor,
|
||||
.effectId = action.options.effectId,
|
||||
}, TextWithEntities(), MTP_messageMediaContact(
|
||||
MTP_string(phone),
|
||||
MTP_string(firstName),
|
||||
@@ -3790,7 +3835,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
const auto anonymousPost = peer->amAnonymous();
|
||||
const auto silentPost = ShouldSendSilent(peer, action.options);
|
||||
FillMessagePostFlags(action, peer, flags);
|
||||
if (exactWebPage && !ignoreWebPage && message.webPage.invert) {
|
||||
if ((exactWebPage && !ignoreWebPage && message.webPage.invert)
|
||||
|| action.options.invertCaption) {
|
||||
flags |= MessageFlag::InvertMedia;
|
||||
sendFlags |= MTPmessages_SendMessage::Flag::f_invert_media;
|
||||
mediaFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
|
||||
@@ -3836,6 +3882,10 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
sendFlags |= MTPmessages_SendMessage::Flag::f_quick_reply_shortcut;
|
||||
mediaFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
|
||||
}
|
||||
if (action.options.effectId) {
|
||||
sendFlags |= MTPmessages_SendMessage::Flag::f_effect;
|
||||
mediaFlags |= MTPmessages_SendMedia::Flag::f_effect;
|
||||
}
|
||||
lastMessage = history->addNewLocalMessage({
|
||||
.id = newId.msg,
|
||||
.flags = flags,
|
||||
@@ -3844,6 +3894,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
.date = HistoryItem::NewMessageDate(action.options),
|
||||
.shortcutId = action.options.shortcutId,
|
||||
.postAuthor = messagePostAuthor,
|
||||
.effectId = action.options.effectId,
|
||||
}, sending, media);
|
||||
const auto done = [=](
|
||||
const MTPUpdates &result,
|
||||
@@ -3891,7 +3942,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
sentEntities,
|
||||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
mtpShortcut
|
||||
mtpShortcut,
|
||||
MTP_long(action.options.effectId)
|
||||
), done, fail);
|
||||
} else {
|
||||
histories.sendPreparedMessage(
|
||||
@@ -3908,7 +3960,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
sentEntities,
|
||||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
mtpShortcut
|
||||
mtpShortcut,
|
||||
MTP_long(action.options.effectId)
|
||||
), done, fail);
|
||||
}
|
||||
isFirst = false;
|
||||
@@ -4191,7 +4244,9 @@ void ApiWrap::sendMediaWithRandomId(
|
||||
| (!sentEntities.v.isEmpty() ? Flag::f_entities : Flag(0))
|
||||
| (options.scheduled ? Flag::f_schedule_date : Flag(0))
|
||||
| (options.sendAs ? Flag::f_send_as : Flag(0))
|
||||
| (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0));
|
||||
| (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0))
|
||||
| (options.effectId ? Flag::f_effect : Flag(0))
|
||||
| (options.invertCaption ? Flag::f_invert_media : Flag(0));
|
||||
|
||||
auto &histories = history->owner().histories();
|
||||
const auto peer = history->peer;
|
||||
@@ -4211,7 +4266,8 @@ void ApiWrap::sendMediaWithRandomId(
|
||||
sentEntities,
|
||||
MTP_int(options.scheduled),
|
||||
(options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, options.shortcutId)
|
||||
Data::ShortcutIdToMTP(_session, options.shortcutId),
|
||||
MTP_long(options.effectId)
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
if (done) done(true);
|
||||
if (updateRecentStickers) {
|
||||
@@ -4309,7 +4365,9 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
||||
| (sendAs ? Flag::f_send_as : Flag(0))
|
||||
| (album->options.shortcutId
|
||||
? Flag::f_quick_reply_shortcut
|
||||
: Flag(0));
|
||||
: Flag(0))
|
||||
| (album->options.effectId ? Flag::f_effect : Flag(0))
|
||||
| (album->options.invertCaption ? Flag::f_invert_media : Flag(0));
|
||||
auto &histories = history->owner().histories();
|
||||
const auto peer = history->peer;
|
||||
histories.sendPreparedMessage(
|
||||
@@ -4323,7 +4381,8 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
||||
MTP_vector<MTPInputSingleMedia>(medias),
|
||||
MTP_int(album->options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, album->options.shortcutId)
|
||||
Data::ShortcutIdToMTP(_session, album->options.shortcutId),
|
||||
MTP_long(album->options.effectId)
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
_sendingAlbums.remove(groupId);
|
||||
|
||||
|
||||
@@ -274,6 +274,10 @@ public:
|
||||
Fn<void(not_null<PeerData*>, MsgId)> callback);
|
||||
|
||||
using SliceType = Data::LoadDirection;
|
||||
void requestHistory(
|
||||
not_null<History*> history,
|
||||
MsgId messageId,
|
||||
SliceType slice);
|
||||
void requestSharedMedia(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId topicRootId,
|
||||
@@ -511,7 +515,8 @@ private:
|
||||
not_null<PeerData*> peer,
|
||||
bool justClear,
|
||||
bool revoke);
|
||||
void applyAffectedMessages(const MTPmessages_AffectedMessages &result) const;
|
||||
void applyAffectedMessages(
|
||||
const MTPmessages_AffectedMessages &result) const;
|
||||
|
||||
void deleteAllFromParticipantSend(
|
||||
not_null<ChannelData*> channel,
|
||||
@@ -645,6 +650,17 @@ private:
|
||||
};
|
||||
base::flat_set<SharedMediaRequest> _sharedMediaRequests;
|
||||
|
||||
struct HistoryRequest {
|
||||
not_null<PeerData*> peer;
|
||||
MsgId aroundId = 0;
|
||||
SliceType sliceType = {};
|
||||
|
||||
friend inline auto operator<=>(
|
||||
const HistoryRequest&,
|
||||
const HistoryRequest&) = default;
|
||||
};
|
||||
base::flat_set<HistoryRequest> _historyRequests;
|
||||
|
||||
std::unique_ptr<DialogsLoadState> _dialogsLoadState;
|
||||
TimeId _dialogsLoadTill = 0;
|
||||
rpl::variable<bool> _dialogsLoadMayBlockByDate = false;
|
||||
|
||||
@@ -29,7 +29,6 @@ void initLang() {
|
||||
void initFonts() {
|
||||
auto settings = &AyuSettings::getInstance();
|
||||
|
||||
AyuFonts::setMainFont(settings->mainFont);
|
||||
AyuFonts::setMonoFont(settings->monoFont);
|
||||
}
|
||||
|
||||
|
||||
@@ -207,7 +207,6 @@ AyuGramSettings::AyuGramSettings() {
|
||||
|
||||
disableNotificationsDelay = false;
|
||||
localPremium = false;
|
||||
copyUsernameAsLink = true;
|
||||
|
||||
// ~ Customization
|
||||
appIcon = AyuAssets::DEFAULT_ICON;
|
||||
@@ -234,7 +233,6 @@ AyuGramSettings::AyuGramSettings() {
|
||||
showGhostToggleInTray = true;
|
||||
showStreamerToggleInTray = false;
|
||||
|
||||
mainFont = "";
|
||||
monoFont = "";
|
||||
|
||||
hideNotificationCounters = false;
|
||||
@@ -342,10 +340,6 @@ void AyuGramSettings::set_localPremium(bool val) {
|
||||
localPremium = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_copyUsernameAsLink(bool val) {
|
||||
copyUsernameAsLink = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_appIcon(QString val) {
|
||||
appIcon = std::move(val);
|
||||
}
|
||||
@@ -412,10 +406,6 @@ void AyuGramSettings::set_showStreamerToggleInTray(bool val) {
|
||||
showStreamerToggleInTray = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_mainFont(QString val) {
|
||||
mainFont = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_monoFont(QString val) {
|
||||
monoFont = val;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,6 @@ public:
|
||||
|
||||
bool disableNotificationsDelay;
|
||||
bool localPremium;
|
||||
bool copyUsernameAsLink;
|
||||
|
||||
QString appIcon;
|
||||
bool simpleQuotesAndReplies;
|
||||
@@ -61,7 +60,6 @@ public:
|
||||
bool showGhostToggleInTray;
|
||||
bool showStreamerToggleInTray;
|
||||
|
||||
QString mainFont;
|
||||
QString monoFont;
|
||||
|
||||
bool hideNotificationCounters;
|
||||
@@ -99,7 +97,6 @@ public:
|
||||
|
||||
void set_disableNotificationsDelay(bool val);
|
||||
void set_localPremium(bool val);
|
||||
void set_copyUsernameAsLink(bool val);
|
||||
|
||||
void set_appIcon(QString val);
|
||||
void set_simpleQuotesAndReplies(bool val);
|
||||
@@ -121,7 +118,6 @@ public:
|
||||
void set_showGhostToggleInTray(bool val);
|
||||
void set_showStreamerToggleInTray(bool val);
|
||||
|
||||
void set_mainFont(QString val);
|
||||
void set_monoFont(QString val);
|
||||
|
||||
void set_hideNotificationCounters(bool val);
|
||||
@@ -155,7 +151,6 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(
|
||||
hideSimilarChannels,
|
||||
disableNotificationsDelay,
|
||||
localPremium,
|
||||
copyUsernameAsLink,
|
||||
appIcon,
|
||||
simpleQuotesAndReplies,
|
||||
deletedMark,
|
||||
@@ -172,7 +167,6 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(
|
||||
showStreamerToggleInDrawer,
|
||||
showGhostToggleInTray,
|
||||
showStreamerToggleInTray,
|
||||
mainFont,
|
||||
monoFont,
|
||||
hideNotificationCounters,
|
||||
hideAllChatsFolder,
|
||||
|
||||
@@ -665,7 +665,7 @@ void Content::setupContent(
|
||||
const std::vector<Font> &fonts) {
|
||||
using namespace rpl::mappers;
|
||||
|
||||
const auto current = AyuFonts::getMainFont();
|
||||
const auto current = AyuFonts::getMonoFont();
|
||||
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
|
||||
const auto add = [&](const std::vector<Font> &list)
|
||||
{
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "ayu/ui/sections/edited/edited_log_section.h"
|
||||
#include "ayu/utils/telegram_helpers.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "history/view/history_view_context_menu.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "window/window_session_controller.h"
|
||||
|
||||
@@ -77,22 +78,23 @@ void AddUserMessagesAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item) {
|
||||
}
|
||||
|
||||
if (item->history()->peer->isChat() || item->history()->peer->isMegagroup()) {
|
||||
menu->addAction(tr::ayu_UserMessagesMenuText(tr::now),
|
||||
[=]
|
||||
{
|
||||
if (const auto window = item->history()->session().tryResolveWindow()) {
|
||||
if (const auto mainWidget = window->widget()->sessionController()) {
|
||||
const auto peer = item->history()->peer;
|
||||
const auto key = (peer && !peer->isUser())
|
||||
? item->topic()
|
||||
? Dialogs::Key{item->topic()}
|
||||
: Dialogs::Key{item->history()}
|
||||
: Dialogs::Key();
|
||||
mainWidget->content()->searchMessages(QString(), key, item->from()->asUser());
|
||||
}
|
||||
}
|
||||
},
|
||||
&st::menuIconTTL);
|
||||
menu->addAction(
|
||||
tr::ayu_UserMessagesMenuText(tr::now),
|
||||
[=]
|
||||
{
|
||||
if (const auto window = item->history()->session().tryResolveWindow()) {
|
||||
if (const auto mainWidget = window->widget()->sessionController()) {
|
||||
const auto peer = item->history()->peer;
|
||||
const auto key = (peer && !peer->isUser())
|
||||
? item->topic()
|
||||
? Dialogs::Key{item->topic()}
|
||||
: Dialogs::Key{item->history()}
|
||||
: Dialogs::Key();
|
||||
mainWidget->content()->searchMessages(QString(), key, item->from()->asUser());
|
||||
}
|
||||
}
|
||||
},
|
||||
&st::menuIconTTL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +114,22 @@ void AddMessageDetailsAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item) {
|
||||
const auto media = item->media();
|
||||
|
||||
const auto isSticker = media && media->document() && media->document()->sticker();
|
||||
|
||||
const auto emojiPacks = HistoryView::CollectEmojiPacks(item, HistoryView::EmojiPacksSource::Message);
|
||||
auto containsSingleCustomEmojiPack = emojiPacks.size() == 1;
|
||||
if (!containsSingleCustomEmojiPack && emojiPacks.size() > 1) {
|
||||
const auto author = emojiPacks.front().id >> 32;
|
||||
auto sameAuthor = true;
|
||||
for (const auto pack : emojiPacks) {
|
||||
if (pack.id >> 32 != author) {
|
||||
sameAuthor = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
containsSingleCustomEmojiPack = sameAuthor;
|
||||
}
|
||||
|
||||
const auto isForwarded = forwarded && !forwarded->story && forwarded->psaType.isEmpty();
|
||||
|
||||
const auto messageId = QString::number(item->id.bare);
|
||||
@@ -278,6 +296,18 @@ void AddMessageDetailsAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (containsSingleCustomEmojiPack) {
|
||||
const auto authorId = emojiPacks.front().id >> 32;
|
||||
|
||||
if (authorId != 0) {
|
||||
menu2->addAction(Ui::ContextActionStickerAuthor(
|
||||
menu2->menu(),
|
||||
&item->history()->session(),
|
||||
authorId
|
||||
));
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -610,13 +610,18 @@ void InnerWidget::elementStartInteraction(not_null<const Element*> view) {
|
||||
}
|
||||
|
||||
void InnerWidget::elementStartPremium(
|
||||
not_null<const Element*> view,
|
||||
Element *replacing) {
|
||||
not_null<const HistoryView::Element*> view,
|
||||
HistoryView::Element *replacing) {
|
||||
}
|
||||
|
||||
void InnerWidget::elementCancelPremium(not_null<const Element*> view) {
|
||||
}
|
||||
|
||||
void InnerWidget::elementStartEffect(
|
||||
not_null<const Element*> view,
|
||||
Element *replacing) {
|
||||
}
|
||||
|
||||
QString InnerWidget::elementAuthorRank(not_null<const Element*> view) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -127,10 +127,11 @@ public:
|
||||
void elementStartPremium(
|
||||
not_null<const HistoryView::Element*> view,
|
||||
HistoryView::Element *replacing) override;
|
||||
void elementCancelPremium(
|
||||
not_null<const HistoryView::Element*> view) override;
|
||||
QString elementAuthorRank(
|
||||
not_null<const HistoryView::Element*> view) override;
|
||||
void elementCancelPremium(not_null<const HistoryView::Element*> view) override;
|
||||
void elementStartEffect(
|
||||
not_null<const HistoryView::Element*> view,
|
||||
HistoryView::Element *replacing) override;
|
||||
QString elementAuthorRank(not_null<const HistoryView::Element*> view) override;
|
||||
|
||||
~InnerWidget();
|
||||
|
||||
|
||||
@@ -729,25 +729,6 @@ void SetupQoLToggles(not_null<Ui::VerticalLayout*> container) {
|
||||
AyuSettings::save();
|
||||
},
|
||||
container->lifetime());
|
||||
|
||||
AddButtonWithIcon(
|
||||
container,
|
||||
tr::ayu_CopyUsernameAsLink(),
|
||||
st::settingsButtonNoIcon
|
||||
)->toggleOn(
|
||||
rpl::single(settings->copyUsernameAsLink)
|
||||
)->toggledValue(
|
||||
) | rpl::filter(
|
||||
[=](bool enabled)
|
||||
{
|
||||
return (enabled != settings->copyUsernameAsLink);
|
||||
}) | start_with_next(
|
||||
[=](bool enabled)
|
||||
{
|
||||
settings->set_copyUsernameAsLink(enabled);
|
||||
AyuSettings::save();
|
||||
},
|
||||
container->lifetime());
|
||||
}
|
||||
|
||||
void SetupAppIcon(not_null<Ui::VerticalLayout*> container) {
|
||||
@@ -1068,28 +1049,6 @@ void SetupRecentStickersLimitSlider(not_null<Ui::VerticalLayout*> container) {
|
||||
void SetupFonts(not_null<Ui::VerticalLayout*> container, not_null<Window::SessionController*> controller) {
|
||||
const auto settings = &AyuSettings::getInstance();
|
||||
|
||||
const auto commonButton = AddButtonWithLabel(
|
||||
container,
|
||||
tr::ayu_MainFont(),
|
||||
rpl::single(
|
||||
settings->mainFont.isEmpty() ? tr::ayu_FontDefault(tr::now) : settings->mainFont
|
||||
),
|
||||
st::settingsButtonNoIcon);
|
||||
const auto commonGuard = Ui::CreateChild<base::binary_guard>(commonButton.get());
|
||||
|
||||
commonButton->addClickHandler(
|
||||
[=]
|
||||
{
|
||||
*commonGuard = AyuUi::FontSelectorBox::Show(controller,
|
||||
[=](QString font)
|
||||
{
|
||||
auto ayuSettings = &AyuSettings::getInstance();
|
||||
|
||||
ayuSettings->set_mainFont(std::move(font));
|
||||
AyuSettings::save();
|
||||
});
|
||||
});
|
||||
|
||||
const auto monoButton = AddButtonWithLabel(
|
||||
container,
|
||||
tr::ayu_MonospaceFont(),
|
||||
|
||||
@@ -192,20 +192,27 @@ void ShowAddParticipantsError(
|
||||
&& channel
|
||||
&& !channel->isMegagroup()
|
||||
&& channel->canAddAdmins()) {
|
||||
const auto makeAdmin = [=] {
|
||||
const auto makeAdmin = [=](Fn<void()> close) {
|
||||
const auto user = forbidden.users.front();
|
||||
const auto weak = std::make_shared<QPointer<EditAdminBox>>();
|
||||
const auto close = [=](auto&&...) {
|
||||
if (*weak) {
|
||||
(*weak)->closeBox();
|
||||
const auto done = [=](auto&&...) {
|
||||
if (const auto strong = weak->data()) {
|
||||
strong->uiShow()->showToast(
|
||||
tr::lng_box_done(tr::now));
|
||||
strong->closeBox();
|
||||
}
|
||||
};
|
||||
const auto fail = [=] {
|
||||
if (const auto strong = weak->data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
};
|
||||
const auto saveCallback = SaveAdminCallback(
|
||||
show,
|
||||
channel,
|
||||
user,
|
||||
close,
|
||||
close);
|
||||
done,
|
||||
fail);
|
||||
auto box = Box<EditAdminBox>(
|
||||
channel,
|
||||
user,
|
||||
@@ -214,6 +221,7 @@ void ShowAddParticipantsError(
|
||||
box->setSaveCallback(saveCallback);
|
||||
*weak = box.data();
|
||||
show->showBox(std::move(box));
|
||||
close();
|
||||
};
|
||||
show->showBox(
|
||||
Ui::MakeConfirmBox({
|
||||
|
||||
@@ -940,7 +940,7 @@ int BackgroundPreviewBox::textsTop() const {
|
||||
- st::historyPaddingBottom
|
||||
- (_service ? _service->height() : 0)
|
||||
- _text1->height()
|
||||
- (forChannel() ? _text2->height() : 0);
|
||||
- (forChannel() ? 0 : _text2->height());
|
||||
}
|
||||
|
||||
QRect BackgroundPreviewBox::radialRect() const {
|
||||
|
||||
@@ -546,7 +546,7 @@ rightsToggle: Toggle(defaultToggle) {
|
||||
vsize: 0px;
|
||||
vshift: 1px;
|
||||
stroke: 2px;
|
||||
duration: 120;
|
||||
duration: universalDuration;
|
||||
}
|
||||
|
||||
rightsButton: SettingsButton(defaultSettingsButton) {
|
||||
@@ -642,6 +642,10 @@ proxyDropdownUpPosition: point(-2px, 20px);
|
||||
proxyAboutPadding: margins(22px, 7px, 22px, 14px);
|
||||
proxyAboutSponsorPadding: margins(22px, 7px, 22px, 0px);
|
||||
|
||||
proxyApplyBoxLabel : FlatLabel(defaultFlatLabel) {
|
||||
maxHeight: 30px;
|
||||
}
|
||||
|
||||
markdownLinkFieldPadding: margins(22px, 0px, 22px, 10px);
|
||||
|
||||
termsContent: FlatLabel(defaultFlatLabel) {
|
||||
@@ -691,6 +695,10 @@ createPollOptionField: InputField(createPollField) {
|
||||
placeholderMargins: margins(2px, 0px, 2px, 0px);
|
||||
heightMax: 68px;
|
||||
}
|
||||
createPollOptionFieldPremium: InputField(createPollOptionField) {
|
||||
textMargins: margins(22px, 11px, 68px, 11px);
|
||||
}
|
||||
createPollOptionFieldPremiumEmojiPosition: point(15px, -1px);
|
||||
createPollSolutionField: InputField(createPollField) {
|
||||
textMargins: margins(0px, 4px, 0px, 4px);
|
||||
border: 1px;
|
||||
@@ -704,7 +712,7 @@ createPollOptionRemove: CrossButton {
|
||||
cross: CrossAnimation {
|
||||
size: 22px;
|
||||
skip: 6px;
|
||||
stroke: 1.;
|
||||
stroke: 1.5;
|
||||
minScale: 0.3;
|
||||
}
|
||||
crossFg: boxTitleCloseFg;
|
||||
@@ -718,6 +726,7 @@ createPollOptionRemove: CrossButton {
|
||||
}
|
||||
}
|
||||
createPollOptionRemovePosition: point(11px, 9px);
|
||||
createPollOptionEmojiPositionSkip: 4px;
|
||||
createPollWarning: FlatLabel(defaultFlatLabel) {
|
||||
textFg: windowSubTextFg;
|
||||
palette: TextPalette(defaultTextPalette) {
|
||||
@@ -1074,3 +1083,23 @@ collectibleBox: Box(defaultBox) {
|
||||
buttonHeight: 36px;
|
||||
button: collectibleCopy;
|
||||
}
|
||||
|
||||
moderateBoxUserpic: UserpicButton(defaultUserpicButton) {
|
||||
size: size(34px, 42px);
|
||||
photoSize: 34px;
|
||||
photoPosition: point(0px, 4px);
|
||||
}
|
||||
moderateBoxExpand: icon {{ "chat/reply_type_group", boxTextFg }};
|
||||
moderateBoxExpandHeight: 20px;
|
||||
moderateBoxExpandRight: 10px;
|
||||
moderateBoxExpandInnerSkip: 2px;
|
||||
moderateBoxExpandFont: font(11px);
|
||||
moderateBoxExpandToggleSize: 4px;
|
||||
moderateBoxExpandToggleFourStrokes: 3px;
|
||||
moderateBoxExpandIcon: icon{{ "info/edit/expand_arrow_small-flip_vertical", windowActiveTextFg }};
|
||||
moderateBoxExpandIconDown: icon{{ "info/edit/expand_arrow_small", windowActiveTextFg }};
|
||||
moderateBoxDividerLabel: FlatLabel(boxDividerLabel) {
|
||||
palette: TextPalette(defaultTextPalette) {
|
||||
selectLinkFg: windowActiveTextFg;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,32 +7,37 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/connection_box.h"
|
||||
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "base/qthelp_url.h"
|
||||
#include "base/call_delayed.h"
|
||||
#include "base/qthelp_regex.h"
|
||||
#include "base/qthelp_url.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "core/local_url_handlers.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_account.h"
|
||||
#include "mtproto/facade.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "ui/basic_click_handlers.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/effects/radial_animation.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/dropdown_menu.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "ui/widgets/fields/number_input.h"
|
||||
#include "ui/widgets/fields/password_input.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/dropdown_menu.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/effects/radial_animation.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/basic_click_handlers.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "boxes/abstract_box.h" // Ui::show().
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
@@ -48,6 +53,22 @@ constexpr auto kSaveSettingsDelayedTimeout = crl::time(1000);
|
||||
|
||||
using ProxyData = MTP::ProxyData;
|
||||
|
||||
[[nodiscard]] ProxyData ProxyDataFromFields(
|
||||
ProxyData::Type type,
|
||||
const QMap<QString, QString> &fields) {
|
||||
auto proxy = ProxyData();
|
||||
proxy.type = type;
|
||||
proxy.host = fields.value(u"server"_q);
|
||||
proxy.port = fields.value(u"port"_q).toUInt();
|
||||
if (type == ProxyData::Type::Socks5) {
|
||||
proxy.user = fields.value(u"user"_q);
|
||||
proxy.password = fields.value(u"pass"_q);
|
||||
} else if (type == ProxyData::Type::Mtproto) {
|
||||
proxy.password = fields.value(u"secret"_q);
|
||||
}
|
||||
return proxy;
|
||||
};
|
||||
|
||||
class HostInput : public Ui::MaskedInputField {
|
||||
public:
|
||||
HostInput(
|
||||
@@ -203,6 +224,7 @@ protected:
|
||||
|
||||
private:
|
||||
void setupContent();
|
||||
void setupTopButton();
|
||||
void createNoRowsLabel();
|
||||
void addNewProxy();
|
||||
void applyView(View &&view);
|
||||
@@ -600,9 +622,80 @@ void ProxiesBox::prepare() {
|
||||
addButton(tr::lng_proxy_add(), [=] { addNewProxy(); });
|
||||
addButton(tr::lng_close(), [=] { closeBox(); });
|
||||
|
||||
setupTopButton();
|
||||
setupContent();
|
||||
}
|
||||
|
||||
void ProxiesBox::setupTopButton() {
|
||||
const auto top = addTopButton(st::infoTopBarMenu);
|
||||
const auto menu
|
||||
= top->lifetime().make_state<base::unique_qptr<Ui::PopupMenu>>();
|
||||
const auto callback = [=] {
|
||||
const auto maybeUrl = QGuiApplication::clipboard()->text();
|
||||
const auto local = Core::TryConvertUrlToLocal(maybeUrl);
|
||||
|
||||
const auto proxyString = u"proxy"_q;
|
||||
const auto socksString = u"socks"_q;
|
||||
const auto protocol = u"tg://"_q;
|
||||
const auto command = base::StringViewMid(
|
||||
local,
|
||||
protocol.size(),
|
||||
8192);
|
||||
|
||||
if (local.startsWith(protocol + proxyString)
|
||||
|| local.startsWith(protocol + socksString)) {
|
||||
|
||||
using namespace qthelp;
|
||||
const auto options = RegExOption::CaseInsensitive;
|
||||
for (const auto &[expression, _] : Core::LocalUrlHandlers()) {
|
||||
const auto midExpression = base::StringViewMid(
|
||||
expression,
|
||||
1);
|
||||
const auto isSocks = midExpression.startsWith(
|
||||
socksString);
|
||||
if (!midExpression.startsWith(proxyString)
|
||||
&& !isSocks) {
|
||||
continue;
|
||||
}
|
||||
const auto match = regex_match(
|
||||
expression,
|
||||
command,
|
||||
options);
|
||||
if (!match) {
|
||||
continue;
|
||||
}
|
||||
const auto type = isSocks
|
||||
? ProxyData::Type::Socks5
|
||||
: ProxyData::Type::Mtproto;
|
||||
const auto fields = url_parse_params(
|
||||
match->captured(1),
|
||||
qthelp::UrlParamNameTransform::ToLower);
|
||||
const auto proxy = ProxyDataFromFields(type, fields);
|
||||
const auto contains = _controller->contains(proxy);
|
||||
const auto toast = (contains
|
||||
? tr::lng_proxy_add_from_clipboard_existing_toast
|
||||
: tr::lng_proxy_add_from_clipboard_good_toast)(tr::now);
|
||||
uiShow()->showToast(toast);
|
||||
if (!contains) {
|
||||
_controller->addNewItem(proxy);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
uiShow()->showToast(
|
||||
tr::lng_proxy_add_from_clipboard_failed_toast(tr::now));
|
||||
}
|
||||
};
|
||||
top->setClickedCallback([=] {
|
||||
*menu = base::make_unique_q<Ui::PopupMenu>(top, st::defaultPopupMenu);
|
||||
(*menu)->addAction(
|
||||
tr::lng_proxy_add_from_clipboard(tr::now),
|
||||
callback);
|
||||
(*menu)->popup(QCursor::pos());
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void ProxiesBox::setupContent() {
|
||||
const auto inner = setInnerWidget(object_ptr<Ui::VerticalLayout>(this));
|
||||
|
||||
@@ -1094,70 +1187,84 @@ ProxiesBoxController::ProxiesBoxController(not_null<Main::Account*> account)
|
||||
}
|
||||
|
||||
void ProxiesBoxController::ShowApplyConfirmation(
|
||||
Window::SessionController *controller,
|
||||
Type type,
|
||||
const QMap<QString, QString> &fields) {
|
||||
const auto server = fields.value(u"server"_q);
|
||||
const auto port = fields.value(u"port"_q).toUInt();
|
||||
auto proxy = ProxyData();
|
||||
proxy.type = type;
|
||||
proxy.host = server;
|
||||
proxy.port = port;
|
||||
if (type == Type::Socks5) {
|
||||
proxy.user = fields.value(u"user"_q);
|
||||
proxy.password = fields.value(u"pass"_q);
|
||||
} else if (type == Type::Mtproto) {
|
||||
proxy.password = fields.value(u"secret"_q);
|
||||
const auto proxy = ProxyDataFromFields(type, fields);
|
||||
if (!proxy) {
|
||||
auto box = Ui::MakeInformBox(
|
||||
(proxy.status() == ProxyData::Status::Unsupported
|
||||
? tr::lng_proxy_unsupported(tr::now)
|
||||
: tr::lng_proxy_invalid(tr::now)));
|
||||
if (controller) {
|
||||
controller->uiShow()->showBox(std::move(box));
|
||||
} else {
|
||||
Ui::show(std::move(box));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (proxy) {
|
||||
static const auto UrlStartRegExp = QRegularExpression(
|
||||
"^https://",
|
||||
QRegularExpression::CaseInsensitiveOption);
|
||||
static const auto UrlEndRegExp = QRegularExpression("/$");
|
||||
const auto displayed = "https://" + server + "/";
|
||||
const auto parsed = QUrl::fromUserInput(displayed);
|
||||
const auto displayUrl = !UrlClickHandler::IsSuspicious(displayed)
|
||||
? displayed
|
||||
: parsed.isValid()
|
||||
? QString::fromUtf8(parsed.toEncoded())
|
||||
: UrlClickHandler::ShowEncoded(displayed);
|
||||
const auto displayServer = QString(
|
||||
displayUrl
|
||||
).replace(
|
||||
UrlStartRegExp,
|
||||
QString()
|
||||
).replace(UrlEndRegExp, QString());
|
||||
const auto text = tr::lng_sure_enable_socks(
|
||||
tr::now,
|
||||
lt_server,
|
||||
displayServer,
|
||||
lt_port,
|
||||
QString::number(port))
|
||||
+ (proxy.type == Type::Mtproto
|
||||
? "\n\n" + tr::lng_proxy_sponsor_warning(tr::now)
|
||||
: QString());
|
||||
auto callback = [=](Fn<void()> &&close) {
|
||||
static const auto UrlStartRegExp = QRegularExpression(
|
||||
"^https://",
|
||||
QRegularExpression::CaseInsensitiveOption);
|
||||
static const auto UrlEndRegExp = QRegularExpression("/$");
|
||||
const auto displayed = "https://" + proxy.host + "/";
|
||||
const auto parsed = QUrl::fromUserInput(displayed);
|
||||
const auto displayUrl = !UrlClickHandler::IsSuspicious(displayed)
|
||||
? displayed
|
||||
: parsed.isValid()
|
||||
? QString::fromUtf8(parsed.toEncoded())
|
||||
: UrlClickHandler::ShowEncoded(displayed);
|
||||
const auto displayServer = QString(
|
||||
displayUrl
|
||||
).replace(
|
||||
UrlStartRegExp,
|
||||
QString()
|
||||
).replace(UrlEndRegExp, QString());
|
||||
const auto box = [=](not_null<Ui::GenericBox*> box) {
|
||||
box->setTitle(tr::lng_proxy_box_title());
|
||||
if (type == Type::Mtproto) {
|
||||
box->addRow(object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_proxy_sponsor_warning(),
|
||||
st::boxDividerLabel));
|
||||
Ui::AddSkip(box->verticalLayout());
|
||||
Ui::AddSkip(box->verticalLayout());
|
||||
}
|
||||
const auto &stL = st::proxyApplyBoxLabel;
|
||||
const auto &stSubL = st::boxDividerLabel;
|
||||
const auto add = [&](const QString &s, tr::phrase<> phrase) {
|
||||
if (!s.isEmpty()) {
|
||||
box->addRow(object_ptr<Ui::FlatLabel>(box, s, stL));
|
||||
box->addRow(object_ptr<Ui::FlatLabel>(box, phrase(), stSubL));
|
||||
Ui::AddSkip(box->verticalLayout());
|
||||
Ui::AddSkip(box->verticalLayout());
|
||||
}
|
||||
};
|
||||
if (!displayServer.isEmpty()) {
|
||||
add(displayServer, tr::lng_proxy_box_server);
|
||||
}
|
||||
add(QString::number(proxy.port), tr::lng_proxy_box_port);
|
||||
if (type == Type::Socks5) {
|
||||
add(proxy.user, tr::lng_proxy_box_username);
|
||||
add(proxy.password, tr::lng_proxy_box_password);
|
||||
} else if (type == Type::Mtproto) {
|
||||
add(proxy.password, tr::lng_proxy_box_secret);
|
||||
}
|
||||
box->addButton(tr::lng_sure_enable(), [=] {
|
||||
auto &proxies = Core::App().settings().proxy().list();
|
||||
if (!ranges::contains(proxies, proxy)) {
|
||||
proxies.push_back(proxy);
|
||||
}
|
||||
Core::App().setCurrentProxy(
|
||||
proxy,
|
||||
ProxyData::Settings::Enabled);
|
||||
Core::App().setCurrentProxy(proxy, ProxyData::Settings::Enabled);
|
||||
Local::writeSettings();
|
||||
close();
|
||||
};
|
||||
Ui::show(
|
||||
Ui::MakeConfirmBox({
|
||||
.text = text,
|
||||
.confirmed = std::move(callback),
|
||||
.confirmText = tr::lng_sure_enable(),
|
||||
}),
|
||||
Ui::LayerOption::KeepOther);
|
||||
box->closeBox();
|
||||
});
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
};
|
||||
if (controller) {
|
||||
controller->uiShow()->showBox(Box(box));
|
||||
} else {
|
||||
Ui::show(Ui::MakeInformBox(
|
||||
(proxy.status() == ProxyData::Status::Unsupported
|
||||
? tr::lng_proxy_unsupported(tr::now)
|
||||
: tr::lng_proxy_invalid(tr::now))));
|
||||
Ui::show(Box(box));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1448,6 +1555,14 @@ object_ptr<Ui::BoxContent> ProxiesBoxController::addNewItemBox() {
|
||||
});
|
||||
}
|
||||
|
||||
bool ProxiesBoxController::contains(const ProxyData &proxy) const {
|
||||
const auto j = ranges::find(
|
||||
_list,
|
||||
proxy,
|
||||
[](const Item &item) { return item.data; });
|
||||
return (j != end(_list));
|
||||
}
|
||||
|
||||
void ProxiesBoxController::addNewItem(const ProxyData &proxy) {
|
||||
auto &proxies = _settings.list();
|
||||
proxies.push_back(proxy);
|
||||
|
||||
@@ -30,6 +30,10 @@ namespace Main {
|
||||
class Account;
|
||||
} // namespace Main
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
class ProxiesBoxController {
|
||||
public:
|
||||
using ProxyData = MTP::ProxyData;
|
||||
@@ -38,6 +42,7 @@ public:
|
||||
explicit ProxiesBoxController(not_null<Main::Account*> account);
|
||||
|
||||
static void ShowApplyConfirmation(
|
||||
Window::SessionController *controller,
|
||||
Type type,
|
||||
const QMap<QString, QString> &fields);
|
||||
|
||||
@@ -77,6 +82,9 @@ public:
|
||||
void setTryIPv6(bool enabled);
|
||||
rpl::producer<ProxyData::Settings> proxySettingsValue() const;
|
||||
|
||||
[[nodiscard]] bool contains(const ProxyData &proxy) const;
|
||||
void addNewItem(const ProxyData &proxy);
|
||||
|
||||
rpl::producer<ItemView> views() const;
|
||||
|
||||
~ProxiesBoxController();
|
||||
@@ -109,7 +117,6 @@ private:
|
||||
void replaceItemValue(
|
||||
std::vector<Item>::iterator which,
|
||||
const ProxyData &proxy);
|
||||
void addNewItem(const ProxyData &proxy);
|
||||
|
||||
const not_null<Main::Account*> _account;
|
||||
Core::SettingsProxy &_settings;
|
||||
|
||||
@@ -7,33 +7,41 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/create_poll_box.h"
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "data/data_poll.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/wrap/fade_wrap.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "main/main_session.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "base/call_delayed.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "base/random.h"
|
||||
#include "base/unique_qptr.h"
|
||||
#include "chat_helpers/emoji_suggestions_widget.h"
|
||||
#include "chat_helpers/message_field.h"
|
||||
#include "menu/menu_send.h"
|
||||
#include "chat_helpers/tabbed_panel.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "data/data_poll.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "history/view/history_view_schedule_box.h"
|
||||
#include "base/unique_qptr.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "base/call_delayed.h"
|
||||
#include "base/random.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "menu/menu_send.h"
|
||||
#include "ui/controls/emoji_button.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/wrap/fade_wrap.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_chat_helpers.h" // defaultComposeFiles.
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_settings.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -46,12 +54,107 @@ constexpr auto kSolutionLimit = 200;
|
||||
constexpr auto kWarnSolutionLimit = 60;
|
||||
constexpr auto kErrorLimit = 99;
|
||||
|
||||
[[nodiscard]] not_null<Ui::EmojiButton*> AddEmojiToggleToField(
|
||||
not_null<Ui::InputField*> field,
|
||||
not_null<Ui::BoxContent*> box,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<ChatHelpers::TabbedPanel*> emojiPanel,
|
||||
QPoint shift) {
|
||||
const auto emojiToggle = Ui::CreateChild<Ui::EmojiButton>(
|
||||
field->parentWidget(),
|
||||
st::defaultComposeFiles.emoji);
|
||||
const auto fade = Ui::CreateChild<Ui::FadeAnimation>(
|
||||
emojiToggle,
|
||||
emojiToggle,
|
||||
0.5);
|
||||
{
|
||||
const auto fadeTarget = Ui::CreateChild<Ui::RpWidget>(emojiToggle);
|
||||
fadeTarget->resize(emojiToggle->size());
|
||||
fadeTarget->paintRequest(
|
||||
) | rpl::start_with_next([=](const QRect &rect) {
|
||||
auto p = QPainter(fadeTarget);
|
||||
if (fade->animating()) {
|
||||
p.fillRect(fadeTarget->rect(), st::boxBg);
|
||||
}
|
||||
fade->paint(p);
|
||||
}, fadeTarget->lifetime());
|
||||
rpl::single(false) | rpl::then(
|
||||
field->focusedChanges()
|
||||
) | rpl::start_with_next([=](bool shown) {
|
||||
if (shown) {
|
||||
fade->fadeIn(st::universalDuration);
|
||||
} else {
|
||||
fade->fadeOut(st::universalDuration);
|
||||
}
|
||||
}, emojiToggle->lifetime());
|
||||
fade->fadeOut(1);
|
||||
fade->finish();
|
||||
}
|
||||
|
||||
|
||||
const auto outer = box->getDelegate()->outerContainer();
|
||||
const auto allow = [](not_null<DocumentData*>) { return true; };
|
||||
InitMessageFieldHandlers(
|
||||
controller,
|
||||
field,
|
||||
Window::GifPauseReason::Layer,
|
||||
allow);
|
||||
Ui::Emoji::SuggestionsController::Init(
|
||||
outer,
|
||||
field,
|
||||
&controller->session(),
|
||||
Ui::Emoji::SuggestionsController::Options{
|
||||
.suggestCustomEmoji = true,
|
||||
.allowCustomWithoutPremium = allow,
|
||||
});
|
||||
const auto updateEmojiPanelGeometry = [=] {
|
||||
const auto parent = emojiPanel->parentWidget();
|
||||
const auto global = emojiToggle->mapToGlobal({ 0, 0 });
|
||||
const auto local = parent->mapFromGlobal(global);
|
||||
const auto right = local.x() + emojiToggle->width() * 3;
|
||||
const auto isDropDown = local.y() < parent->height() / 2;
|
||||
emojiPanel->setDropDown(isDropDown);
|
||||
if (isDropDown) {
|
||||
emojiPanel->moveTopRight(
|
||||
local.y() + emojiToggle->height(),
|
||||
right);
|
||||
} else {
|
||||
emojiPanel->moveBottomRight(local.y(), right);
|
||||
}
|
||||
};
|
||||
rpl::combine(
|
||||
box->sizeValue(),
|
||||
field->geometryValue()
|
||||
) | rpl::start_with_next([=](QSize outer, QRect inner) {
|
||||
emojiToggle->moveToLeft(
|
||||
rect::right(inner) + shift.x(),
|
||||
inner.y() + shift.y());
|
||||
emojiToggle->update();
|
||||
}, emojiToggle->lifetime());
|
||||
|
||||
emojiToggle->installEventFilter(emojiPanel);
|
||||
emojiToggle->addClickHandler([=] {
|
||||
updateEmojiPanelGeometry();
|
||||
emojiPanel->toggleAnimated();
|
||||
});
|
||||
const auto filterCallback = [=](not_null<QEvent*> event) {
|
||||
if (event->type() == QEvent::Enter) {
|
||||
updateEmojiPanelGeometry();
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
};
|
||||
base::install_event_filter(emojiToggle, filterCallback);
|
||||
|
||||
return emojiToggle;
|
||||
}
|
||||
|
||||
class Options {
|
||||
public:
|
||||
Options(
|
||||
not_null<QWidget*> outer,
|
||||
not_null<Ui::BoxContent*> box,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<Main::Session*> session,
|
||||
not_null<Window::SessionController*> controller,
|
||||
ChatHelpers::TabbedPanel *emojiPanel,
|
||||
bool chooseCorrectEnabled);
|
||||
|
||||
[[nodiscard]] bool hasOptions() const;
|
||||
@@ -140,9 +243,10 @@ private:
|
||||
[[nodiscard]] auto createChooseCorrectGroup()
|
||||
-> std::shared_ptr<Ui::RadiobuttonGroup>;
|
||||
|
||||
not_null<QWidget*> _outer;
|
||||
not_null<Ui::BoxContent*> _box;
|
||||
not_null<Ui::VerticalLayout*> _container;
|
||||
const not_null<Main::Session*> _session;
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
ChatHelpers::TabbedPanel * const _emojiPanel;
|
||||
std::shared_ptr<Ui::RadiobuttonGroup> _chooseCorrectGroup;
|
||||
int _position = 0;
|
||||
std::vector<std::unique_ptr<Option>> _list;
|
||||
@@ -154,6 +258,7 @@ private:
|
||||
rpl::event_stream<not_null<QWidget*>> _scrollToWidget;
|
||||
rpl::event_stream<> _backspaceInFront;
|
||||
rpl::event_stream<> _tabbed;
|
||||
rpl::lifetime _emojiPanelLifetime;
|
||||
|
||||
};
|
||||
|
||||
@@ -193,8 +298,9 @@ not_null<Ui::FlatLabel*> CreateWarningLabel(
|
||||
if (value >= 0) {
|
||||
result->setText(QString::number(value));
|
||||
} else {
|
||||
constexpr auto kMinus = QChar(0x2212);
|
||||
result->setMarkedText(Ui::Text::Colorized(
|
||||
QString::number(value)));
|
||||
kMinus + QString::number(std::abs(value))));
|
||||
}
|
||||
result->setVisible(shown);
|
||||
}));
|
||||
@@ -223,7 +329,9 @@ Options::Option::Option(
|
||||
, _field(
|
||||
Ui::CreateChild<Ui::InputField>(
|
||||
_content.get(),
|
||||
st::createPollOptionField,
|
||||
session->user()->isPremium()
|
||||
? st::createPollOptionFieldPremium
|
||||
: st::createPollOptionField,
|
||||
Ui::InputField::Mode::NoNewlines,
|
||||
tr::lng_polls_create_option_add())) {
|
||||
InitField(outer, _field, session);
|
||||
@@ -299,7 +407,7 @@ void Options::Option::createRemove() {
|
||||
const auto remove = Ui::CreateChild<Ui::CrossButton>(
|
||||
field.get(),
|
||||
st::createPollOptionRemove);
|
||||
remove->hide(anim::type::instant);
|
||||
remove->show(anim::type::instant);
|
||||
|
||||
const auto toggle = lifetime.make_state<rpl::variable<bool>>(false);
|
||||
_removeAlways = lifetime.make_state<rpl::variable<bool>>(false);
|
||||
@@ -309,6 +417,7 @@ void Options::Option::createRemove() {
|
||||
// Don't capture 'this'! Because Option is a value type.
|
||||
*toggle = !field->getLastText().isEmpty();
|
||||
}, field->lifetime());
|
||||
#if 0
|
||||
rpl::combine(
|
||||
toggle->value(),
|
||||
_removeAlways->value(),
|
||||
@@ -316,6 +425,7 @@ void Options::Option::createRemove() {
|
||||
) | rpl::start_with_next([=](bool shown) {
|
||||
remove->toggle(shown, anim::type::normal);
|
||||
}, remove->lifetime());
|
||||
#endif
|
||||
|
||||
field->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
@@ -456,10 +566,16 @@ void Options::Option::removePlaceholder() const {
|
||||
PollAnswer Options::Option::toPollAnswer(int index) const {
|
||||
Expects(index >= 0 && index < kMaxOptionsCount);
|
||||
|
||||
const auto text = field()->getTextWithTags();
|
||||
|
||||
auto result = PollAnswer{
|
||||
field()->getLastText().trimmed(),
|
||||
QByteArray(1, ('0' + index))
|
||||
TextWithEntities{
|
||||
.text = text.text,
|
||||
.entities = TextUtilities::ConvertTextTagsToEntities(text.tags),
|
||||
},
|
||||
QByteArray(1, ('0' + index)),
|
||||
};
|
||||
TextUtilities::Trim(result.text);
|
||||
result.correct = _correct ? _correct->entity()->Checkbox::checked() : false;
|
||||
return result;
|
||||
}
|
||||
@@ -469,13 +585,15 @@ rpl::producer<Qt::MouseButton> Options::Option::removeClicks() const {
|
||||
}
|
||||
|
||||
Options::Options(
|
||||
not_null<QWidget*> outer,
|
||||
not_null<Ui::BoxContent*> box,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<Main::Session*> session,
|
||||
not_null<Window::SessionController*> controller,
|
||||
ChatHelpers::TabbedPanel *emojiPanel,
|
||||
bool chooseCorrectEnabled)
|
||||
: _outer(outer)
|
||||
: _box(box)
|
||||
, _container(container)
|
||||
, _session(session)
|
||||
, _controller(controller)
|
||||
, _emojiPanel(emojiPanel)
|
||||
, _chooseCorrectGroup(chooseCorrectEnabled
|
||||
? createChooseCorrectGroup()
|
||||
: nullptr)
|
||||
@@ -645,12 +763,40 @@ void Options::addEmptyOption() {
|
||||
(*(_list.end() - 2))->toggleRemoveAlways(true);
|
||||
}
|
||||
_list.push_back(std::make_unique<Option>(
|
||||
_outer,
|
||||
_box,
|
||||
_container,
|
||||
_session,
|
||||
&_controller->session(),
|
||||
_position + _list.size() + _destroyed.size(),
|
||||
_chooseCorrectGroup));
|
||||
const auto field = _list.back()->field();
|
||||
if (const auto emojiPanel = _emojiPanel) {
|
||||
const auto emojiToggle = AddEmojiToggleToField(
|
||||
field,
|
||||
_box,
|
||||
_controller,
|
||||
emojiPanel,
|
||||
QPoint(
|
||||
-st::createPollOptionFieldPremium.textMargins.right(),
|
||||
st::createPollOptionEmojiPositionSkip));
|
||||
emojiToggle->shownValue() | rpl::start_with_next([=](bool shown) {
|
||||
if (!shown) {
|
||||
return;
|
||||
}
|
||||
_emojiPanelLifetime.destroy();
|
||||
emojiPanel->selector()->emojiChosen(
|
||||
) | rpl::start_with_next([=](ChatHelpers::EmojiChosen data) {
|
||||
if (field->hasFocus()) {
|
||||
Ui::InsertEmojiAtCursor(field->textCursor(), data.emoji);
|
||||
}
|
||||
}, _emojiPanelLifetime);
|
||||
emojiPanel->selector()->customEmojiChosen(
|
||||
) | rpl::start_with_next([=](ChatHelpers::FileChosen data) {
|
||||
if (field->hasFocus()) {
|
||||
Data::InsertCustomEmoji(field, data.document);
|
||||
}
|
||||
}, _emojiPanelLifetime);
|
||||
}, emojiToggle->lifetime());
|
||||
}
|
||||
field->submits(
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto index = findField(field);
|
||||
@@ -697,7 +843,7 @@ void Options::addEmptyOption() {
|
||||
});
|
||||
|
||||
_list.back()->removeClicks(
|
||||
) | rpl::take(1) | rpl::start_with_next([=] {
|
||||
) | rpl::start_with_next([=] {
|
||||
Ui::PostponeCall(crl::guard(field, [=] {
|
||||
Expects(!_list.empty());
|
||||
|
||||
@@ -764,12 +910,12 @@ CreatePollBox::CreatePollBox(
|
||||
PollData::Flags chosen,
|
||||
PollData::Flags disabled,
|
||||
Api::SendType sendType,
|
||||
SendMenu::Type sendMenuType)
|
||||
SendMenu::Details sendMenuDetails)
|
||||
: _controller(controller)
|
||||
, _chosen(chosen)
|
||||
, _disabled(disabled)
|
||||
, _sendType(sendType)
|
||||
, _sendMenuType(sendMenuType) {
|
||||
, _sendMenuDetails([result = sendMenuDetails] { return result; }) {
|
||||
}
|
||||
|
||||
rpl::producer<CreatePollBox::Result> CreatePollBox::submitRequests() const {
|
||||
@@ -789,19 +935,63 @@ not_null<Ui::InputField*> CreatePollBox::setupQuestion(
|
||||
using namespace Settings;
|
||||
|
||||
const auto session = &_controller->session();
|
||||
const auto isPremium = session->user()->isPremium();
|
||||
Ui::AddSubsectionTitle(container, tr::lng_polls_create_question());
|
||||
|
||||
const auto question = container->add(
|
||||
object_ptr<Ui::InputField>(
|
||||
container,
|
||||
st::createPollField,
|
||||
Ui::InputField::Mode::MultiLine,
|
||||
tr::lng_polls_create_question_placeholder()),
|
||||
st::createPollFieldPadding);
|
||||
st::createPollFieldPadding
|
||||
+ (isPremium
|
||||
? QMargins(0, 0, st::defaultComposeFiles.emoji.inner.width, 0)
|
||||
: QMargins()));
|
||||
InitField(getDelegate()->outerContainer(), question, session);
|
||||
question->setMaxLength(kQuestionLimit + kErrorLimit);
|
||||
question->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
|
||||
question->customTab(true);
|
||||
|
||||
if (isPremium) {
|
||||
using Selector = ChatHelpers::TabbedSelector;
|
||||
const auto outer = getDelegate()->outerContainer();
|
||||
_emojiPanel = base::make_unique_q<ChatHelpers::TabbedPanel>(
|
||||
outer,
|
||||
_controller,
|
||||
object_ptr<Selector>(
|
||||
nullptr,
|
||||
_controller->uiShow(),
|
||||
Window::GifPauseReason::Layer,
|
||||
Selector::Mode::EmojiOnly));
|
||||
const auto emojiPanel = _emojiPanel.get();
|
||||
emojiPanel->setDesiredHeightValues(
|
||||
1.,
|
||||
st::emojiPanMinHeight / 2,
|
||||
st::emojiPanMinHeight);
|
||||
emojiPanel->hide();
|
||||
emojiPanel->selector()->setCurrentPeer(session->user());
|
||||
|
||||
const auto emojiToggle = AddEmojiToggleToField(
|
||||
question,
|
||||
this,
|
||||
_controller,
|
||||
emojiPanel,
|
||||
st::createPollOptionFieldPremiumEmojiPosition);
|
||||
emojiPanel->selector()->emojiChosen(
|
||||
) | rpl::start_with_next([=](ChatHelpers::EmojiChosen data) {
|
||||
if (question->hasFocus()) {
|
||||
Ui::InsertEmojiAtCursor(question->textCursor(), data.emoji);
|
||||
}
|
||||
}, emojiToggle->lifetime());
|
||||
emojiPanel->selector()->customEmojiChosen(
|
||||
) | rpl::start_with_next([=](ChatHelpers::FileChosen data) {
|
||||
if (question->hasFocus()) {
|
||||
Data::InsertCustomEmoji(question, data.document);
|
||||
}
|
||||
}, emojiToggle->lifetime());
|
||||
}
|
||||
|
||||
const auto warning = CreateWarningLabel(
|
||||
container,
|
||||
question,
|
||||
@@ -854,7 +1044,7 @@ not_null<Ui::InputField*> CreatePollBox::setupSolution(
|
||||
solution->setInstantReplaces(Ui::InstantReplaces::Default());
|
||||
solution->setInstantReplacesEnabled(
|
||||
Core::App().settings().replaceEmojiValue());
|
||||
solution->setMarkdownReplacesEnabled(rpl::single(true));
|
||||
solution->setMarkdownReplacesEnabled(true);
|
||||
solution->setEditLinkCallback(
|
||||
DefaultEditLinkCallback(_controller->uiShow(), solution));
|
||||
solution->customTab(true);
|
||||
@@ -910,9 +1100,10 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
|
||||
st::defaultSubsectionTitle),
|
||||
st::createPollFieldTitlePadding);
|
||||
const auto options = lifetime().make_state<Options>(
|
||||
getDelegate()->outerContainer(),
|
||||
this,
|
||||
container,
|
||||
&_controller->session(),
|
||||
_controller,
|
||||
_emojiPanel ? _emojiPanel.get() : nullptr,
|
||||
(_chosen & PollData::Flag::Quiz));
|
||||
auto limit = options->usedCount() | rpl::after_next([=](int count) {
|
||||
setCloseByEscape(!count);
|
||||
@@ -1029,9 +1220,13 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
|
||||
};
|
||||
|
||||
const auto collectResult = [=] {
|
||||
const auto textWithTags = question->getTextWithTags();
|
||||
using Flag = PollData::Flag;
|
||||
auto result = PollData(&_controller->session().data(), id);
|
||||
result.question = question->getLastText().trimmed();
|
||||
result.question.text = textWithTags.text;
|
||||
result.question.entities = TextUtilities::ConvertTextTagsToEntities(
|
||||
textWithTags.tags);
|
||||
TextUtilities::Trim(result.question);
|
||||
result.answers = options->toPollAnswers();
|
||||
const auto solutionWithTags = quiz->checked()
|
||||
? solution->getTextWithAppliedMarkdown()
|
||||
@@ -1093,19 +1288,9 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
|
||||
_submitRequests.fire({ collectResult(), sendOptions });
|
||||
}
|
||||
};
|
||||
const auto sendSilent = [=] {
|
||||
send({ .silent = true });
|
||||
};
|
||||
const auto sendScheduled = [=] {
|
||||
_controller->show(
|
||||
HistoryView::PrepareScheduleBox(
|
||||
this,
|
||||
SendMenu::Type::Scheduled,
|
||||
send));
|
||||
};
|
||||
const auto sendWhenOnline = [=] {
|
||||
send(Api::DefaultSendWhenOnlineOptions());
|
||||
};
|
||||
const auto sendAction = SendMenu::DefaultCallback(
|
||||
_controller->uiShow(),
|
||||
crl::guard(this, send));
|
||||
|
||||
options->scrollToWidget(
|
||||
) | rpl::start_with_next([=](not_null<QWidget*> widget) {
|
||||
@@ -1118,24 +1303,25 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
|
||||
}, lifetime());
|
||||
|
||||
const auto isNormal = (_sendType == Api::SendType::Normal);
|
||||
|
||||
const auto schedule = [=] {
|
||||
sendAction(
|
||||
{ .type = SendMenu::ActionType::Schedule },
|
||||
_sendMenuDetails());
|
||||
};
|
||||
const auto submit = addButton(
|
||||
isNormal
|
||||
(isNormal
|
||||
? tr::lng_polls_create_button()
|
||||
: tr::lng_schedule_button(),
|
||||
[=] { isNormal ? send({}) : sendScheduled(); });
|
||||
const auto sendMenuType = [=] {
|
||||
: tr::lng_schedule_button()),
|
||||
[=] { isNormal ? send({}) : schedule(); });
|
||||
const auto sendMenuDetails = [=] {
|
||||
collectError();
|
||||
return (*error)
|
||||
? SendMenu::Type::Disabled
|
||||
: _sendMenuType;
|
||||
return (*error) ? SendMenu::Details() : _sendMenuDetails();
|
||||
};
|
||||
SendMenu::SetupMenuAndShortcuts(
|
||||
submit.data(),
|
||||
sendMenuType,
|
||||
sendSilent,
|
||||
sendScheduled,
|
||||
sendWhenOnline);
|
||||
_controller->uiShow(),
|
||||
sendMenuDetails,
|
||||
sendAction);
|
||||
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||
|
||||
return result;
|
||||
|
||||
@@ -14,6 +14,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
struct PollData;
|
||||
|
||||
namespace ChatHelpers {
|
||||
class TabbedPanel;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace Ui {
|
||||
class VerticalLayout;
|
||||
} // namespace Ui
|
||||
@@ -23,7 +27,7 @@ class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace SendMenu {
|
||||
enum class Type;
|
||||
struct Details;
|
||||
} // namespace SendMenu
|
||||
|
||||
class CreatePollBox : public Ui::BoxContent {
|
||||
@@ -39,7 +43,7 @@ public:
|
||||
PollData::Flags chosen,
|
||||
PollData::Flags disabled,
|
||||
Api::SendType sendType,
|
||||
SendMenu::Type sendMenuType);
|
||||
SendMenu::Details sendMenuDetails);
|
||||
|
||||
[[nodiscard]] rpl::producer<Result> submitRequests() const;
|
||||
void submitFailed(const QString &error);
|
||||
@@ -71,7 +75,8 @@ private:
|
||||
const PollData::Flags _chosen = PollData::Flags();
|
||||
const PollData::Flags _disabled = PollData::Flags();
|
||||
const Api::SendType _sendType = Api::SendType();
|
||||
const SendMenu::Type _sendMenuType;
|
||||
const Fn<SendMenu::Details()> _sendMenuDetails;
|
||||
base::unique_qptr<ChatHelpers::TabbedPanel> _emojiPanel;
|
||||
Fn<void()> _setInnerFocus;
|
||||
Fn<rpl::producer<bool>()> _dataIsValidValue;
|
||||
rpl::event_stream<Result> _submitRequests;
|
||||
|
||||
@@ -379,13 +379,7 @@ auto DeleteMessagesBox::revokeText(not_null<PeerData*> peer) const
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto items = ranges::views::all(
|
||||
_ids
|
||||
) | ranges::views::transform([&](FullMsgId id) {
|
||||
return peer->owner().message(id);
|
||||
}) | ranges::views::filter([](HistoryItem *item) {
|
||||
return (item != nullptr);
|
||||
}) | ranges::to_vector;
|
||||
const auto items = peer->owner().idsToItems(_ids);
|
||||
|
||||
if (items.size() != _ids.size()) {
|
||||
// We don't have information about all messages.
|
||||
|
||||
@@ -38,6 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_session_settings.h"
|
||||
#include "mainwidget.h" // controller->content() -> QWidget*
|
||||
#include "menu/menu_send.h"
|
||||
#include "mtproto/mtproto_config.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "storage/localimageloader.h" // SendMediaType
|
||||
@@ -175,7 +176,7 @@ void ChooseReplacement(
|
||||
void EditPhotoImage(
|
||||
not_null<Window::SessionController*> controller,
|
||||
std::shared_ptr<Data::PhotoMedia> media,
|
||||
bool wasSpoiler,
|
||||
bool spoilered,
|
||||
Fn<void(Ui::PreparedList)> done) {
|
||||
const auto large = media
|
||||
? media->image(Data::PhotoSize::Large)
|
||||
@@ -198,7 +199,7 @@ void EditPhotoImage(
|
||||
|
||||
using ImageInfo = Ui::PreparedFileInformation::Image;
|
||||
auto &file = list.files.front();
|
||||
file.spoiler = wasSpoiler;
|
||||
file.spoiler = spoilered;
|
||||
const auto image = std::get_if<ImageInfo>(&file.information->media);
|
||||
|
||||
image->modifications = mods;
|
||||
@@ -206,6 +207,9 @@ void EditPhotoImage(
|
||||
Storage::UpdateImageDetails(file, previewWidth, sideLimit);
|
||||
done(std::move(list));
|
||||
};
|
||||
if (!large) {
|
||||
return;
|
||||
}
|
||||
const auto fileImage = std::make_shared<Image>(*large);
|
||||
auto editor = base::make_unique_q<Editor::PhotoEditor>(
|
||||
parent,
|
||||
@@ -222,25 +226,18 @@ void EditPhotoImage(
|
||||
|
||||
} // namespace
|
||||
|
||||
EditCaptionBox::EditCaptionBox(
|
||||
QWidget*,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<HistoryItem*> item)
|
||||
: EditCaptionBox({}, controller, item, PrepareEditText(item), {}, {}) {
|
||||
}
|
||||
|
||||
EditCaptionBox::EditCaptionBox(
|
||||
QWidget*,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<HistoryItem*> item,
|
||||
TextWithTags &&text,
|
||||
bool spoilered,
|
||||
bool invertCaption,
|
||||
Ui::PreparedList &&list,
|
||||
Fn<void()> saved)
|
||||
: _controller(controller)
|
||||
, _historyItem(item)
|
||||
, _isAllowedEditMedia(item->media()
|
||||
? item->media()->allowsEditMedia()
|
||||
: false)
|
||||
, _isAllowedEditMedia(item->media() && item->media()->allowsEditMedia())
|
||||
, _albumType(ComputeAlbumType(item))
|
||||
, _controls(base::make_unique_q<Ui::VerticalLayout>(this))
|
||||
, _scroll(base::make_unique_q<Ui::ScrollArea>(this, st::boxScroll))
|
||||
@@ -258,6 +255,8 @@ EditCaptionBox::EditCaptionBox(
|
||||
Expects(item->media() != nullptr);
|
||||
Expects(item->media()->allowsEditCaption());
|
||||
|
||||
_mediaEditManager.start(item, spoilered, invertCaption);
|
||||
|
||||
_controller->session().data().itemRemoved(
|
||||
_historyItem->fullId()
|
||||
) | rpl::start_with_next([=] {
|
||||
@@ -271,6 +270,8 @@ void EditCaptionBox::StartMediaReplace(
|
||||
not_null<Window::SessionController*> controller,
|
||||
FullMsgId itemId,
|
||||
TextWithTags text,
|
||||
bool spoilered,
|
||||
bool invertCaption,
|
||||
Fn<void()> saved) {
|
||||
const auto session = &controller->session();
|
||||
const auto item = session->data().message(itemId);
|
||||
@@ -282,6 +283,8 @@ void EditCaptionBox::StartMediaReplace(
|
||||
controller,
|
||||
item,
|
||||
std::move(text),
|
||||
spoilered,
|
||||
invertCaption,
|
||||
std::move(list),
|
||||
std::move(saved)));
|
||||
};
|
||||
@@ -296,6 +299,8 @@ void EditCaptionBox::StartMediaReplace(
|
||||
FullMsgId itemId,
|
||||
Ui::PreparedList &&list,
|
||||
TextWithTags text,
|
||||
bool spoilered,
|
||||
bool invertCaption,
|
||||
Fn<void()> saved) {
|
||||
const auto session = &controller->session();
|
||||
const auto item = session->data().message(itemId);
|
||||
@@ -329,6 +334,8 @@ void EditCaptionBox::StartMediaReplace(
|
||||
controller,
|
||||
item,
|
||||
std::move(text),
|
||||
spoilered,
|
||||
invertCaption,
|
||||
std::move(list),
|
||||
std::move(saved)));
|
||||
}
|
||||
@@ -339,14 +346,15 @@ void EditCaptionBox::StartPhotoEdit(
|
||||
std::shared_ptr<Data::PhotoMedia> media,
|
||||
FullMsgId itemId,
|
||||
TextWithTags text,
|
||||
bool spoilered,
|
||||
bool invertCaption,
|
||||
Fn<void()> saved) {
|
||||
const auto session = &controller->session();
|
||||
const auto item = session->data().message(itemId);
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
const auto hasSpoiler = item->media() && item->media()->hasSpoiler();
|
||||
EditPhotoImage(controller, media, hasSpoiler, [=](
|
||||
EditPhotoImage(controller, media, spoilered, [=](
|
||||
Ui::PreparedList &&list) mutable {
|
||||
const auto item = session->data().message(itemId);
|
||||
if (!item) {
|
||||
@@ -356,15 +364,48 @@ void EditCaptionBox::StartPhotoEdit(
|
||||
controller,
|
||||
item,
|
||||
std::move(text),
|
||||
spoilered,
|
||||
invertCaption,
|
||||
std::move(list),
|
||||
std::move(saved)));
|
||||
});
|
||||
}
|
||||
|
||||
void EditCaptionBox::prepare() {
|
||||
addButton(tr::lng_settings_save(), [=] { save(); });
|
||||
const auto button = addButton(tr::lng_settings_save(), [=] { save(); });
|
||||
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||
|
||||
const auto details = crl::guard(this, [=] {
|
||||
auto result = SendMenu::Details();
|
||||
const auto allWithSpoilers = ranges::all_of(
|
||||
_preparedList.files,
|
||||
&Ui::PreparedFile::spoiler);
|
||||
result.spoiler = !_preparedList.hasSpoilerMenu(!_asFile)
|
||||
? SendMenu::SpoilerState::None
|
||||
: allWithSpoilers
|
||||
? SendMenu::SpoilerState::Enabled
|
||||
: SendMenu::SpoilerState::Possible;
|
||||
const auto canMoveCaption = _preparedList.canMoveCaption(
|
||||
false,
|
||||
!_asFile
|
||||
) && _field && HasSendText(_field);
|
||||
result.caption = !canMoveCaption
|
||||
? SendMenu::CaptionState::None
|
||||
: _mediaEditManager.invertCaption()
|
||||
? SendMenu::CaptionState::Above
|
||||
: SendMenu::CaptionState::Below;
|
||||
return result;
|
||||
});
|
||||
const auto callback = [=](SendMenu::Action action, const auto &) {
|
||||
_mediaEditManager.apply(action);
|
||||
rebuildPreview();
|
||||
};
|
||||
SendMenu::SetupMenuAndShortcuts(
|
||||
button,
|
||||
nullptr,
|
||||
details,
|
||||
crl::guard(this, callback));
|
||||
|
||||
updateBoxSize();
|
||||
|
||||
setupField();
|
||||
@@ -393,7 +434,6 @@ void EditCaptionBox::rebuildPreview() {
|
||||
|
||||
applyChanges();
|
||||
|
||||
_previewHasSpoiler = nullptr;
|
||||
if (_preparedList.files.empty()) {
|
||||
const auto media = _historyItem->media();
|
||||
const auto photo = media->photo();
|
||||
@@ -427,7 +467,13 @@ void EditCaptionBox::rebuildPreview() {
|
||||
_isPhoto = (media && media->isPhoto());
|
||||
const auto withCheckbox = _isPhoto && CanBeCompressed(_albumType);
|
||||
if (media && (!withCheckbox || !_asFile)) {
|
||||
_previewHasSpoiler = [media] { return media->hasSpoiler(); };
|
||||
media->spoileredChanges(
|
||||
) | rpl::start_with_next([=](bool spoilered) {
|
||||
_mediaEditManager.apply({ .type = spoilered
|
||||
? SendMenu::ActionType::SpoilerOn
|
||||
: SendMenu::ActionType::SpoilerOff
|
||||
});
|
||||
}, media->lifetime());
|
||||
_content.reset(media);
|
||||
} else {
|
||||
_content.reset(Ui::CreateChild<Ui::SingleFilePreview>(
|
||||
@@ -754,10 +800,7 @@ bool EditCaptionBox::setPreparedList(Ui::PreparedList &&list) {
|
||||
}
|
||||
|
||||
bool EditCaptionBox::hasSpoiler() const {
|
||||
return _preparedList.files.empty()
|
||||
? (_historyItem->media()
|
||||
&& _historyItem->media()->hasSpoiler())
|
||||
: _preparedList.files.front().spoiler;
|
||||
return _mediaEditManager.spoilered();
|
||||
}
|
||||
|
||||
void EditCaptionBox::captionResized() {
|
||||
@@ -866,8 +909,8 @@ bool EditCaptionBox::validateLength(const QString &text) const {
|
||||
}
|
||||
|
||||
void EditCaptionBox::applyChanges() {
|
||||
if (!_preparedList.files.empty() && _previewHasSpoiler) {
|
||||
_preparedList.files.front().spoiler = _previewHasSpoiler();
|
||||
if (!_preparedList.files.empty()) {
|
||||
_preparedList.files.front().spoiler = _mediaEditManager.spoilered();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -896,6 +939,7 @@ void EditCaptionBox::save() {
|
||||
auto options = Api::SendOptions();
|
||||
options.scheduled = item->isScheduled() ? item->date() : 0;
|
||||
options.shortcutId = item->shortcutId();
|
||||
options.invertCaption = _mediaEditManager.invertCaption();
|
||||
|
||||
if (!_preparedList.files.empty()) {
|
||||
if ((_albumType != Ui::AlbumType::None)
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "history/view/controls/history_view_compose_media_edit_manager.h"
|
||||
#include "ui/layers/box_content.h"
|
||||
#include "ui/chat/attach/attach_prepare.h"
|
||||
|
||||
@@ -32,15 +33,13 @@ enum class AlbumType;
|
||||
|
||||
class EditCaptionBox final : public Ui::BoxContent {
|
||||
public:
|
||||
EditCaptionBox(
|
||||
QWidget*,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<HistoryItem*> item);
|
||||
EditCaptionBox(
|
||||
QWidget*,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<HistoryItem*> item,
|
||||
TextWithTags &&text,
|
||||
bool spoilered,
|
||||
bool invertCaption,
|
||||
Ui::PreparedList &&list,
|
||||
Fn<void()> saved);
|
||||
~EditCaptionBox();
|
||||
@@ -49,18 +48,24 @@ public:
|
||||
not_null<Window::SessionController*> controller,
|
||||
FullMsgId itemId,
|
||||
TextWithTags text,
|
||||
bool spoilered,
|
||||
bool invertCaption,
|
||||
Fn<void()> saved);
|
||||
static void StartMediaReplace(
|
||||
not_null<Window::SessionController*> controller,
|
||||
FullMsgId itemId,
|
||||
Ui::PreparedList &&list,
|
||||
TextWithTags text,
|
||||
bool spoilered,
|
||||
bool invertCaption,
|
||||
Fn<void()> saved);
|
||||
static void StartPhotoEdit(
|
||||
not_null<Window::SessionController*> controller,
|
||||
std::shared_ptr<Data::PhotoMedia> media,
|
||||
FullMsgId itemId,
|
||||
TextWithTags text,
|
||||
bool spoilered,
|
||||
bool invertCaption,
|
||||
Fn<void()> saved);
|
||||
|
||||
protected:
|
||||
@@ -111,7 +116,6 @@ private:
|
||||
const base::unique_qptr<Ui::EmojiButton> _emojiToggle;
|
||||
|
||||
base::unique_qptr<Ui::AbstractSinglePreview> _content;
|
||||
Fn<bool()> _previewHasSpoiler;
|
||||
base::unique_qptr<ChatHelpers::TabbedPanel> _emojiPanel;
|
||||
base::unique_qptr<QObject> _emojiFilter;
|
||||
|
||||
@@ -122,6 +126,7 @@ private:
|
||||
std::shared_ptr<Data::PhotoMedia> _photoMedia;
|
||||
|
||||
Ui::PreparedList _preparedList;
|
||||
HistoryView::MediaEditManager _mediaEditManager;
|
||||
|
||||
mtpRequestId _saveRequestId = 0;
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_boosts.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_credits.h"
|
||||
#include "data/data_media_types.h" // Data::GiveawayStart.
|
||||
#include "data/data_peer_values.h" // Data::PeerPremiumValue.
|
||||
#include "data/data_session.h"
|
||||
@@ -1147,16 +1148,18 @@ void GiftCodeBox(
|
||||
object_ptr<Ui::Premium::TopBar>(
|
||||
box,
|
||||
st::giveawayGiftCodeCover,
|
||||
nullptr,
|
||||
rpl::conditional(
|
||||
state->used.value(),
|
||||
tr::lng_gift_link_used_title(),
|
||||
tr::lng_gift_link_title()),
|
||||
rpl::conditional(
|
||||
state->used.value(),
|
||||
tr::lng_gift_link_used_about(Ui::Text::RichLangValue),
|
||||
tr::lng_gift_link_about(Ui::Text::RichLangValue)),
|
||||
true));
|
||||
Ui::Premium::TopBarDescriptor{
|
||||
.clickContextOther = nullptr,
|
||||
.title = rpl::conditional(
|
||||
state->used.value(),
|
||||
tr::lng_gift_link_used_title(),
|
||||
tr::lng_gift_link_title()),
|
||||
.about = rpl::conditional(
|
||||
state->used.value(),
|
||||
tr::lng_gift_link_used_about(Ui::Text::RichLangValue),
|
||||
tr::lng_gift_link_about(Ui::Text::RichLangValue)),
|
||||
.light = true,
|
||||
}));
|
||||
|
||||
const auto max = st::giveawayGiftCodeTopHeight;
|
||||
bar->setMaximumHeight(max);
|
||||
@@ -1283,13 +1286,15 @@ void GiftCodePendingBox(
|
||||
object_ptr<Ui::Premium::TopBar>(
|
||||
box,
|
||||
st,
|
||||
clickContext,
|
||||
tr::lng_gift_link_title(),
|
||||
tr::lng_gift_link_pending_about(
|
||||
lt_user,
|
||||
rpl::single(Ui::Text::Link(resultToName)),
|
||||
Ui::Text::RichLangValue),
|
||||
true));
|
||||
Ui::Premium::TopBarDescriptor{
|
||||
.clickContextOther = clickContext,
|
||||
.title = tr::lng_gift_link_title(),
|
||||
.about = tr::lng_gift_link_pending_about(
|
||||
lt_user,
|
||||
rpl::single(Ui::Text::Link(resultToName)),
|
||||
Ui::Text::RichLangValue),
|
||||
.light = true,
|
||||
}));
|
||||
|
||||
const auto max = st::giveawayGiftCodeTopHeight;
|
||||
bar->setMaximumHeight(max);
|
||||
@@ -1629,3 +1634,50 @@ void ResolveGiveawayInfo(
|
||||
messageId,
|
||||
crl::guard(controller, show));
|
||||
}
|
||||
|
||||
void AddCreditsHistoryEntryTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
const Data::CreditsHistoryEntry &entry) {
|
||||
auto table = container->add(
|
||||
object_ptr<Ui::TableLayout>(
|
||||
container,
|
||||
st::giveawayGiftCodeTable),
|
||||
st::giveawayGiftCodeTableMargin);
|
||||
if (entry.bareId) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_peer(),
|
||||
controller,
|
||||
PeerId(entry.bareId));
|
||||
}
|
||||
if (!entry.id.isEmpty()) {
|
||||
constexpr auto kOneLineCount = 18;
|
||||
const auto oneLine = entry.id.length() <= kOneLineCount;
|
||||
auto label = object_ptr<Ui::FlatLabel>(
|
||||
table,
|
||||
rpl::single(
|
||||
Ui::Text::Wrapped({ entry.id }, EntityType::Code, {})),
|
||||
oneLine
|
||||
? st::giveawayGiftCodeValue
|
||||
: st::giveawayGiftCodeValueMultiline);
|
||||
label->setClickHandlerFilter([=](const auto &...) {
|
||||
TextUtilities::SetClipboardText(
|
||||
TextForMimeData::Simple(entry.id));
|
||||
controller->showToast(
|
||||
tr::lng_credits_box_history_entry_id_copied(tr::now));
|
||||
return false;
|
||||
});
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_id(),
|
||||
std::move(label),
|
||||
st::giveawayGiftCodeValueMargin);
|
||||
}
|
||||
if (!entry.date.isNull()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_date(),
|
||||
rpl::single(Ui::Text::WithEntities(langDateTime(entry.date))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,14 @@ struct GiftCode;
|
||||
} // namespace Api
|
||||
|
||||
namespace Data {
|
||||
struct CreditsHistoryEntry;
|
||||
struct GiveawayStart;
|
||||
struct GiveawayResults;
|
||||
} // namespace Data
|
||||
|
||||
namespace Ui {
|
||||
class GenericBox;
|
||||
class VerticalLayout;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Window {
|
||||
@@ -71,3 +73,8 @@ void ResolveGiveawayInfo(
|
||||
MsgId messageId,
|
||||
std::optional<Data::GiveawayStart> start,
|
||||
std::optional<Data::GiveawayResults> results);
|
||||
|
||||
void AddCreditsHistoryEntryTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
const Data::CreditsHistoryEntry &entry);
|
||||
|
||||
@@ -0,0 +1,856 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/moderate_messages_box.h"
|
||||
|
||||
#include "api/api_chat_participants.h"
|
||||
#include "api/api_messages_search.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "base/timer.h"
|
||||
#include "boxes/delete_messages_box.h"
|
||||
#include "boxes/peers/edit_peer_permissions_box.h"
|
||||
#include "core/application.h"
|
||||
#include "core/ui_integration.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_chat_participant_status.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/controls/userpic_button.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/effects/toggle_arrow.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/rect_part.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_window.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using Participants = std::vector<not_null<PeerData*>>;
|
||||
|
||||
struct ModerateOptions final {
|
||||
bool allCanBan = false;
|
||||
bool allCanDelete = false;
|
||||
Participants participants;
|
||||
};
|
||||
|
||||
ModerateOptions CalculateModerateOptions(const HistoryItemsList &items) {
|
||||
Expects(!items.empty());
|
||||
|
||||
auto result = ModerateOptions{
|
||||
.allCanBan = true,
|
||||
.allCanDelete = true,
|
||||
};
|
||||
|
||||
const auto peer = items.front()->history()->peer;
|
||||
for (const auto &item : items) {
|
||||
if (!result.allCanBan && !result.allCanDelete) {
|
||||
return {};
|
||||
}
|
||||
if (peer != item->history()->peer) {
|
||||
return {};
|
||||
}
|
||||
if (!item->suggestBanReport()) {
|
||||
result.allCanBan = false;
|
||||
}
|
||||
if (!item->suggestDeleteAllReport()) {
|
||||
result.allCanDelete = false;
|
||||
}
|
||||
if (const auto p = item->from()) {
|
||||
if (!ranges::contains(result.participants, not_null{ p })) {
|
||||
result.participants.push_back(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<int> MessagesCountValue(
|
||||
not_null<History*> history,
|
||||
not_null<PeerData*> from) {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
auto search = lifetime.make_state<Api::MessagesSearch>(history);
|
||||
consumer.put_next(0);
|
||||
|
||||
search->messagesFounds(
|
||||
) | rpl::start_with_next([=](const Api::FoundMessages &found) {
|
||||
consumer.put_next_copy(found.total);
|
||||
}, lifetime);
|
||||
search->searchMessages({ .from = from });
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
class Button final : public Ui::RippleButton {
|
||||
public:
|
||||
Button(not_null<QWidget*> parent, int count);
|
||||
|
||||
void setChecked(bool checked);
|
||||
[[nodiscard]] bool checked() const;
|
||||
|
||||
[[nodiscard]] static QSize ComputeSize(int);
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
QImage prepareRippleMask() const override;
|
||||
QPoint prepareRippleStartPosition() const override;
|
||||
|
||||
const int _count;
|
||||
const QString _text;
|
||||
bool _checked = false;
|
||||
|
||||
Ui::Animations::Simple _animation;
|
||||
|
||||
};
|
||||
|
||||
Button::Button(not_null<QWidget*> parent, int count)
|
||||
: RippleButton(parent, st::defaultRippleAnimation)
|
||||
, _count(count)
|
||||
, _text(QString::number(std::abs(_count))) {
|
||||
}
|
||||
|
||||
QSize Button::ComputeSize(int count) {
|
||||
return QSize(
|
||||
st::moderateBoxExpandHeight
|
||||
+ st::moderateBoxExpand.width()
|
||||
+ st::moderateBoxExpandInnerSkip * 4
|
||||
+ st::moderateBoxExpandFont->width(
|
||||
QString::number(std::abs(count)))
|
||||
+ st::moderateBoxExpandToggleSize,
|
||||
st::moderateBoxExpandHeight);
|
||||
}
|
||||
|
||||
void Button::setChecked(bool checked) {
|
||||
if (_checked == checked) {
|
||||
return;
|
||||
}
|
||||
_checked = checked;
|
||||
_animation.stop();
|
||||
_animation.start(
|
||||
[=] { update(); },
|
||||
checked ? 0 : 1,
|
||||
checked ? 1 : 0,
|
||||
st::slideWrapDuration);
|
||||
}
|
||||
|
||||
bool Button::checked() const {
|
||||
return _checked;
|
||||
}
|
||||
|
||||
void Button::paintEvent(QPaintEvent *event) {
|
||||
auto p = Painter(this);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
Ui::RippleButton::paintRipple(p, QPoint());
|
||||
const auto radius = height() / 2;
|
||||
p.setPen(Qt::NoPen);
|
||||
st::moderateBoxExpand.paint(
|
||||
p,
|
||||
radius,
|
||||
(height() - st::moderateBoxExpand.height()) / 2,
|
||||
width());
|
||||
|
||||
const auto innerSkip = st::moderateBoxExpandInnerSkip;
|
||||
|
||||
p.setBrush(Qt::NoBrush);
|
||||
p.setPen(st::boxTextFg);
|
||||
p.setFont(st::moderateBoxExpandFont);
|
||||
p.drawText(
|
||||
QRect(
|
||||
innerSkip + radius + st::moderateBoxExpand.width(),
|
||||
0,
|
||||
width(),
|
||||
height()),
|
||||
_text,
|
||||
style::al_left);
|
||||
|
||||
const auto path = Ui::ToggleUpDownArrowPath(
|
||||
width() - st::moderateBoxExpandToggleSize - radius,
|
||||
height() / 2,
|
||||
st::moderateBoxExpandToggleSize,
|
||||
st::moderateBoxExpandToggleFourStrokes,
|
||||
_animation.value(_checked ? 1. : 0.));
|
||||
p.fillPath(path, st::boxTextFg);
|
||||
}
|
||||
|
||||
QImage Button::prepareRippleMask() const {
|
||||
return Ui::RippleAnimation::RoundRectMask(size(), size().height() / 2);
|
||||
}
|
||||
|
||||
QPoint Button::prepareRippleStartPosition() const {
|
||||
return mapFromGlobal(QCursor::pos());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void CreateModerateMessagesBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
const HistoryItemsList &items,
|
||||
Fn<void()> confirmed) {
|
||||
struct Controller final {
|
||||
rpl::event_stream<bool> toggleRequestsFromTop;
|
||||
rpl::event_stream<bool> toggleRequestsFromInner;
|
||||
rpl::event_stream<bool> checkAllRequests;
|
||||
Fn<Participants()> collectRequests;
|
||||
};
|
||||
const auto [allCanBan, allCanDelete, participants]
|
||||
= CalculateModerateOptions(items);
|
||||
const auto inner = box->verticalLayout();
|
||||
|
||||
Assert(!participants.empty());
|
||||
|
||||
const auto confirms = inner->lifetime().make_state<rpl::event_stream<>>();
|
||||
|
||||
const auto isSingle = participants.size() == 1;
|
||||
const auto buttonPadding = isSingle
|
||||
? QMargins()
|
||||
: QMargins(0, 0, Button::ComputeSize(participants.size()).width(), 0);
|
||||
|
||||
const auto session = &items.front()->history()->session();
|
||||
const auto historyPeerId = items.front()->history()->peer->id;
|
||||
|
||||
using Request = Fn<void(not_null<PeerData*>, not_null<ChannelData*>)>;
|
||||
const auto sequentiallyRequest = [=](
|
||||
Request request,
|
||||
Participants participants) {
|
||||
constexpr auto kSmallDelayMs = 5;
|
||||
const auto participantIds = ranges::views::all(
|
||||
participants
|
||||
) | ranges::views::transform([](not_null<PeerData*> peer) {
|
||||
return peer->id;
|
||||
}) | ranges::to_vector;
|
||||
const auto lifetime = std::make_shared<rpl::lifetime>();
|
||||
const auto counter = lifetime->make_state<int>(0);
|
||||
const auto timer = lifetime->make_state<base::Timer>();
|
||||
timer->setCallback(crl::guard(session, [=] {
|
||||
if ((*counter) < participantIds.size()) {
|
||||
const auto peer = session->data().peer(historyPeerId);
|
||||
const auto channel = peer ? peer->asChannel() : nullptr;
|
||||
const auto from = session->data().peer(
|
||||
participantIds[*counter]);
|
||||
if (channel && from) {
|
||||
request(from, channel);
|
||||
}
|
||||
(*counter)++;
|
||||
} else {
|
||||
lifetime->destroy();
|
||||
}
|
||||
}));
|
||||
timer->callEach(kSmallDelayMs);
|
||||
};
|
||||
|
||||
const auto handleConfirmation = [=](
|
||||
not_null<Ui::Checkbox*> checkbox,
|
||||
not_null<Controller*> controller,
|
||||
Request request) {
|
||||
confirms->events() | rpl::start_with_next([=] {
|
||||
if (checkbox->checked() && controller->collectRequests) {
|
||||
sequentiallyRequest(request, controller->collectRequests());
|
||||
}
|
||||
}, checkbox->lifetime());
|
||||
};
|
||||
|
||||
const auto isEnter = [=](not_null<QEvent*> event) {
|
||||
if (event->type() == QEvent::KeyPress) {
|
||||
if (const auto k = static_cast<QKeyEvent*>(event.get())) {
|
||||
return (k->key() == Qt::Key_Enter)
|
||||
|| (k->key() == Qt::Key_Return);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
base::install_event_filter(box, [=](not_null<QEvent*> event) {
|
||||
if (isEnter(event)) {
|
||||
box->triggerButton(0);
|
||||
return base::EventFilterResult::Cancel;
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
|
||||
const auto handleSubmition = [=](not_null<Ui::Checkbox*> checkbox) {
|
||||
base::install_event_filter(box, [=](not_null<QEvent*> event) {
|
||||
if (!isEnter(event) || !checkbox->checked()) {
|
||||
return base::EventFilterResult::Continue;
|
||||
}
|
||||
box->uiShow()->show(Ui::MakeConfirmBox({
|
||||
.text = tr::lng_gigagroup_warning_title(),
|
||||
.confirmed = [=](Fn<void()> close) {
|
||||
box->triggerButton(0);
|
||||
close();
|
||||
},
|
||||
.confirmText = tr::lng_box_yes(),
|
||||
.cancelText = tr::lng_box_no(),
|
||||
}));
|
||||
return base::EventFilterResult::Cancel;
|
||||
});
|
||||
};
|
||||
|
||||
const auto createParticipantsList = [&](
|
||||
not_null<Controller*> controller) {
|
||||
const auto wrap = inner->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
inner,
|
||||
object_ptr<Ui::VerticalLayout>(inner)));
|
||||
wrap->toggle(false, anim::type::instant);
|
||||
|
||||
controller->toggleRequestsFromTop.events(
|
||||
) | rpl::start_with_next([=](bool toggled) {
|
||||
wrap->toggle(toggled, anim::type::normal);
|
||||
}, wrap->lifetime());
|
||||
|
||||
const auto container = wrap->entity();
|
||||
Ui::AddSkip(container);
|
||||
|
||||
auto &lifetime = wrap->lifetime();
|
||||
const auto clicks = lifetime.make_state<rpl::event_stream<>>();
|
||||
const auto checkboxes = ranges::views::all(
|
||||
participants
|
||||
) | ranges::views::transform([&](not_null<PeerData*> peer) {
|
||||
const auto line = container->add(
|
||||
object_ptr<Ui::AbstractButton>(container));
|
||||
const auto &st = st::moderateBoxUserpic;
|
||||
line->resize(line->width(), st.size.height());
|
||||
|
||||
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
|
||||
line,
|
||||
peer,
|
||||
st);
|
||||
const auto checkbox = Ui::CreateChild<Ui::Checkbox>(
|
||||
line,
|
||||
peer->name(),
|
||||
false,
|
||||
st::defaultBoxCheckbox);
|
||||
line->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
userpic->moveToLeft(
|
||||
st::boxRowPadding.left()
|
||||
+ checkbox->checkRect().width()
|
||||
+ st::defaultBoxCheckbox.textPosition.x(),
|
||||
0);
|
||||
const auto skip = st::defaultBoxCheckbox.textPosition.x();
|
||||
checkbox->resizeToWidth(width
|
||||
- rect::right(userpic)
|
||||
- skip
|
||||
- st::boxRowPadding.right());
|
||||
checkbox->moveToLeft(
|
||||
rect::right(userpic) + skip,
|
||||
((userpic->height() - checkbox->height()) / 2)
|
||||
+ st::defaultBoxCheckbox.margin.top());
|
||||
}, checkbox->lifetime());
|
||||
|
||||
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
checkbox->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
line->setClickedCallback([=] {
|
||||
checkbox->setChecked(!checkbox->checked());
|
||||
clicks->fire({});
|
||||
});
|
||||
|
||||
return checkbox;
|
||||
}) | ranges::to_vector;
|
||||
|
||||
clicks->events(
|
||||
) | rpl::start_with_next([=] {
|
||||
controller->toggleRequestsFromInner.fire_copy(
|
||||
ranges::any_of(checkboxes, &Ui::Checkbox::checked));
|
||||
}, container->lifetime());
|
||||
|
||||
controller->checkAllRequests.events(
|
||||
) | rpl::start_with_next([=](bool checked) {
|
||||
for (const auto &c : checkboxes) {
|
||||
c->setChecked(checked);
|
||||
}
|
||||
}, container->lifetime());
|
||||
|
||||
controller->collectRequests = [=] {
|
||||
auto result = Participants();
|
||||
for (auto i = 0; i < checkboxes.size(); i++) {
|
||||
if (checkboxes[i]->checked()) {
|
||||
result.push_back(participants[i]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
};
|
||||
|
||||
const auto appendList = [&](
|
||||
not_null<Ui::Checkbox*> checkbox,
|
||||
not_null<Controller*> controller) {
|
||||
if (isSingle) {
|
||||
const auto p = participants.front();
|
||||
controller->collectRequests = [=] { return Participants{ p }; };
|
||||
return;
|
||||
}
|
||||
const auto count = int(participants.size());
|
||||
const auto button = Ui::CreateChild<Button>(inner, count);
|
||||
button->resize(Button::ComputeSize(count));
|
||||
|
||||
const auto overlay = Ui::CreateChild<Ui::AbstractButton>(inner);
|
||||
|
||||
checkbox->geometryValue(
|
||||
) | rpl::start_with_next([=](const QRect &rect) {
|
||||
overlay->setGeometry(rect);
|
||||
overlay->raise();
|
||||
|
||||
button->moveToRight(
|
||||
st::moderateBoxExpandRight,
|
||||
rect.top() + (rect.height() - button->height()) / 2,
|
||||
box->width());
|
||||
button->raise();
|
||||
}, button->lifetime());
|
||||
|
||||
controller->toggleRequestsFromInner.events(
|
||||
) | rpl::start_with_next([=](bool toggled) {
|
||||
checkbox->setChecked(toggled);
|
||||
}, checkbox->lifetime());
|
||||
button->setClickedCallback([=] {
|
||||
button->setChecked(!button->checked());
|
||||
controller->toggleRequestsFromTop.fire_copy(button->checked());
|
||||
});
|
||||
overlay->setClickedCallback([=] {
|
||||
checkbox->setChecked(!checkbox->checked());
|
||||
controller->checkAllRequests.fire_copy(checkbox->checked());
|
||||
});
|
||||
createParticipantsList(controller);
|
||||
};
|
||||
|
||||
Ui::AddSkip(inner);
|
||||
const auto title = box->addRow(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
(items.size() == 1)
|
||||
? tr::lng_selected_delete_sure_this()
|
||||
: tr::lng_selected_delete_sure(
|
||||
lt_count,
|
||||
rpl::single(items.size()) | tr::to_count()),
|
||||
st::boxLabel));
|
||||
Ui::AddSkip(inner);
|
||||
Ui::AddSkip(inner);
|
||||
Ui::AddSkip(inner);
|
||||
{
|
||||
const auto report = box->addRow(
|
||||
object_ptr<Ui::Checkbox>(
|
||||
box,
|
||||
tr::lng_report_spam(tr::now),
|
||||
false,
|
||||
st::defaultBoxCheckbox),
|
||||
st::boxRowPadding + buttonPadding);
|
||||
const auto controller = box->lifetime().make_state<Controller>();
|
||||
appendList(report, controller);
|
||||
handleSubmition(report);
|
||||
|
||||
const auto ids = items.front()->from()->owner().itemsToIds(items);
|
||||
handleConfirmation(report, controller, [=](
|
||||
not_null<PeerData*> p,
|
||||
not_null<ChannelData*> c) {
|
||||
auto filtered = ranges::views::all(
|
||||
ids
|
||||
) | ranges::views::transform([](const FullMsgId &id) {
|
||||
return MTP_int(id.msg);
|
||||
}) | ranges::to<QVector<MTPint>>();
|
||||
c->session().api().request(
|
||||
MTPchannels_ReportSpam(
|
||||
c->inputChannel,
|
||||
p->input,
|
||||
MTP_vector<MTPint>(std::move(filtered)))
|
||||
).send();
|
||||
});
|
||||
}
|
||||
|
||||
if (allCanDelete) {
|
||||
Ui::AddSkip(inner);
|
||||
Ui::AddSkip(inner);
|
||||
|
||||
const auto deleteAll = inner->add(
|
||||
object_ptr<Ui::Checkbox>(
|
||||
inner,
|
||||
!(isSingle)
|
||||
? tr::lng_delete_all_from_users(
|
||||
tr::now,
|
||||
Ui::Text::WithEntities)
|
||||
: tr::lng_delete_all_from_user(
|
||||
tr::now,
|
||||
lt_user,
|
||||
Ui::Text::Bold(items.front()->from()->name()),
|
||||
Ui::Text::WithEntities),
|
||||
false,
|
||||
st::defaultBoxCheckbox),
|
||||
st::boxRowPadding + buttonPadding);
|
||||
if (isSingle) {
|
||||
const auto history = items.front()->history();
|
||||
tr::lng_selected_delete_sure(
|
||||
lt_count,
|
||||
rpl::combine(
|
||||
MessagesCountValue(history, participants.front()),
|
||||
deleteAll->checkedValue()
|
||||
) | rpl::map([s = items.size()](int all, bool checked) {
|
||||
return float64((checked && all) ? all : s);
|
||||
})
|
||||
) | rpl::start_with_next([=](const QString &text) {
|
||||
title->setText(text);
|
||||
title->resizeToWidth(inner->width()
|
||||
- rect::m::sum::h(st::boxRowPadding));
|
||||
}, title->lifetime());
|
||||
}
|
||||
|
||||
const auto controller = box->lifetime().make_state<Controller>();
|
||||
appendList(deleteAll, controller);
|
||||
handleSubmition(deleteAll);
|
||||
|
||||
handleConfirmation(deleteAll, controller, [=](
|
||||
not_null<PeerData*> p,
|
||||
not_null<ChannelData*> c) {
|
||||
p->session().api().deleteAllFromParticipant(c, p);
|
||||
});
|
||||
}
|
||||
if (allCanBan) {
|
||||
auto ownedWrap = object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
inner,
|
||||
object_ptr<Ui::VerticalLayout>(inner));
|
||||
|
||||
Ui::AddSkip(inner);
|
||||
Ui::AddSkip(inner);
|
||||
const auto ban = inner->add(
|
||||
object_ptr<Ui::Checkbox>(
|
||||
box,
|
||||
rpl::conditional(
|
||||
ownedWrap->toggledValue(),
|
||||
tr::lng_context_restrict_user(),
|
||||
rpl::conditional(
|
||||
rpl::single(isSingle),
|
||||
tr::lng_ban_user(),
|
||||
tr::lng_ban_users())),
|
||||
false,
|
||||
st::defaultBoxCheckbox),
|
||||
st::boxRowPadding + buttonPadding);
|
||||
const auto controller = box->lifetime().make_state<Controller>();
|
||||
appendList(ban, controller);
|
||||
handleSubmition(ban);
|
||||
|
||||
Ui::AddSkip(inner);
|
||||
Ui::AddSkip(inner);
|
||||
|
||||
const auto wrap = inner->add(std::move(ownedWrap));
|
||||
const auto container = wrap->entity();
|
||||
wrap->toggle(false, anim::type::instant);
|
||||
|
||||
const auto session = &participants.front()->session();
|
||||
const auto emojiMargin = QMargins(
|
||||
-st::moderateBoxExpandInnerSkip,
|
||||
-st::moderateBoxExpandInnerSkip / 2,
|
||||
0,
|
||||
0);
|
||||
const auto emojiUp = Ui::Text::SingleCustomEmoji(
|
||||
session->data().customEmojiManager().registerInternalEmoji(
|
||||
st::moderateBoxExpandIcon,
|
||||
emojiMargin,
|
||||
false));
|
||||
const auto emojiDown = Ui::Text::SingleCustomEmoji(
|
||||
session->data().customEmojiManager().registerInternalEmoji(
|
||||
st::moderateBoxExpandIconDown,
|
||||
emojiMargin,
|
||||
false));
|
||||
|
||||
auto label = object_ptr<Ui::FlatLabel>(
|
||||
inner,
|
||||
QString(),
|
||||
st::moderateBoxDividerLabel);
|
||||
const auto raw = label.data();
|
||||
|
||||
auto &lifetime = wrap->lifetime();
|
||||
const auto scrollLifetime = lifetime.make_state<rpl::lifetime>();
|
||||
label->setClickHandlerFilter([=](
|
||||
const ClickHandlerPtr &handler,
|
||||
Qt::MouseButton button) {
|
||||
if (button != Qt::LeftButton) {
|
||||
return false;
|
||||
}
|
||||
wrap->toggle(!wrap->toggled(), anim::type::normal);
|
||||
{
|
||||
inner->heightValue() | rpl::start_with_next([=] {
|
||||
if (!wrap->animating()) {
|
||||
scrollLifetime->destroy();
|
||||
Ui::PostponeCall(crl::guard(box, [=] {
|
||||
box->scrollToY(std::numeric_limits<int>::max());
|
||||
}));
|
||||
} else {
|
||||
box->scrollToY(std::numeric_limits<int>::max());
|
||||
}
|
||||
}, *scrollLifetime);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
wrap->toggledValue(
|
||||
) | rpl::map([isSingle, emojiUp, emojiDown](bool toggled) {
|
||||
return ((toggled && isSingle)
|
||||
? tr::lng_restrict_user_part
|
||||
: (toggled && !isSingle)
|
||||
? tr::lng_restrict_users_part
|
||||
: isSingle
|
||||
? tr::lng_restrict_user_full
|
||||
: tr::lng_restrict_users_full)(
|
||||
lt_emoji,
|
||||
rpl::single(toggled ? emojiUp : emojiDown),
|
||||
Ui::Text::WithEntities);
|
||||
}) | rpl::flatten_latest(
|
||||
) | rpl::start_with_next([=](const TextWithEntities &text) {
|
||||
raw->setMarkedText(
|
||||
Ui::Text::Link(text, u"internal:"_q),
|
||||
Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = [=] { raw->update(); },
|
||||
});
|
||||
}, label->lifetime());
|
||||
|
||||
Ui::AddSkip(inner);
|
||||
inner->add(object_ptr<Ui::DividerLabel>(
|
||||
inner,
|
||||
std::move(label),
|
||||
st::defaultBoxDividerLabelPadding,
|
||||
RectPart::Top | RectPart::Bottom));
|
||||
|
||||
using Flag = ChatRestriction;
|
||||
using Flags = ChatRestrictions;
|
||||
const auto peer = items.front()->history()->peer;
|
||||
const auto chat = peer->asChat();
|
||||
const auto channel = peer->asChannel();
|
||||
const auto defaultRestrictions = chat
|
||||
? chat->defaultRestrictions()
|
||||
: channel->defaultRestrictions();
|
||||
const auto prepareFlags = FixDependentRestrictions(
|
||||
defaultRestrictions
|
||||
| ((channel && channel->isPublic())
|
||||
? (Flag::ChangeInfo | Flag::PinMessages)
|
||||
: Flags(0)));
|
||||
const auto disabledMessages = [&] {
|
||||
auto result = base::flat_map<Flags, QString>();
|
||||
{
|
||||
const auto disabled = FixDependentRestrictions(
|
||||
defaultRestrictions
|
||||
| ((channel && channel->isPublic())
|
||||
? (Flag::ChangeInfo | Flag::PinMessages)
|
||||
: Flags(0)));
|
||||
result.emplace(
|
||||
disabled,
|
||||
tr::lng_rights_restriction_for_all(tr::now));
|
||||
}
|
||||
return result;
|
||||
}();
|
||||
|
||||
auto [checkboxes, getRestrictions, changes] = CreateEditRestrictions(
|
||||
box,
|
||||
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())),
|
||||
prepareFlags,
|
||||
disabledMessages,
|
||||
{ .isForum = peer->isForum() });
|
||||
std::move(changes) | rpl::start_with_next([=] {
|
||||
ban->setChecked(true);
|
||||
}, ban->lifetime());
|
||||
Ui::AddSkip(container);
|
||||
Ui::AddDivider(container);
|
||||
Ui::AddSkip(container);
|
||||
container->add(std::move(checkboxes));
|
||||
|
||||
// Handle confirmation manually.
|
||||
confirms->events() | rpl::start_with_next([=] {
|
||||
if (ban->checked() && controller->collectRequests) {
|
||||
const auto kick = !wrap->toggled();
|
||||
const auto restrictions = getRestrictions();
|
||||
const auto request = [=](
|
||||
not_null<PeerData*> peer,
|
||||
not_null<ChannelData*> channel) {
|
||||
if (!kick) {
|
||||
Api::ChatParticipants::Restrict(
|
||||
channel,
|
||||
peer,
|
||||
ChatRestrictionsInfo(), // Unused.
|
||||
ChatRestrictionsInfo(restrictions, 0),
|
||||
nullptr,
|
||||
nullptr);
|
||||
} else {
|
||||
channel->session().api().chatParticipants().kick(
|
||||
channel,
|
||||
peer,
|
||||
{ channel->restrictions(), 0 });
|
||||
}
|
||||
};
|
||||
sequentiallyRequest(request, controller->collectRequests());
|
||||
}
|
||||
}, ban->lifetime());
|
||||
}
|
||||
|
||||
const auto close = crl::guard(box, [=] { box->closeBox(); });
|
||||
{
|
||||
const auto data = &participants.front()->session().data();
|
||||
const auto ids = data->itemsToIds(items);
|
||||
box->addButton(tr::lng_box_delete(), [=] {
|
||||
confirms->fire({});
|
||||
if (confirmed) {
|
||||
confirmed();
|
||||
}
|
||||
data->histories().deleteMessages(ids, true);
|
||||
data->sendHistoryChangeNotifications();
|
||||
close();
|
||||
});
|
||||
}
|
||||
box->addButton(tr::lng_cancel(), close);
|
||||
}
|
||||
|
||||
bool CanCreateModerateMessagesBox(const HistoryItemsList &items) {
|
||||
const auto options = CalculateModerateOptions(items);
|
||||
return (options.allCanBan || options.allCanDelete)
|
||||
&& !options.participants.empty();
|
||||
}
|
||||
|
||||
void DeleteChatBox(not_null<Ui::GenericBox*> box, not_null<PeerData*> peer) {
|
||||
const auto container = box->verticalLayout();
|
||||
|
||||
const auto maybeUser = peer->asUser();
|
||||
|
||||
Ui::AddSkip(container);
|
||||
Ui::AddSkip(container);
|
||||
|
||||
base::install_event_filter(box, [=](not_null<QEvent*> event) {
|
||||
if (event->type() == QEvent::KeyPress) {
|
||||
if (const auto k = static_cast<QKeyEvent*>(event.get())) {
|
||||
if ((k->key() == Qt::Key_Enter)
|
||||
|| (k->key() == Qt::Key_Return)) {
|
||||
box->uiShow()->show(Ui::MakeConfirmBox({
|
||||
.text = tr::lng_gigagroup_warning_title(),
|
||||
.confirmed = [=](Fn<void()> close) {
|
||||
box->triggerButton(0);
|
||||
close();
|
||||
},
|
||||
.confirmText = tr::lng_box_yes(),
|
||||
.cancelText = tr::lng_box_no(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
|
||||
const auto line = container->add(object_ptr<Ui::RpWidget>(container));
|
||||
const auto &st = st::mainMenuUserpic;
|
||||
line->resize(line->width(), st.size.height());
|
||||
|
||||
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
|
||||
line,
|
||||
peer,
|
||||
st);
|
||||
userpic->showSavedMessagesOnSelf(true);
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
line,
|
||||
peer->isSelf()
|
||||
? tr::lng_saved_messages() | Ui::Text::ToBold()
|
||||
: maybeUser
|
||||
? tr::lng_profile_delete_conversation() | Ui::Text::ToBold()
|
||||
: rpl::single(Ui::Text::Bold(peer->name())) | rpl::type_erased(),
|
||||
box->getDelegate()->style().title);
|
||||
line->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
userpic->moveToLeft(st::boxRowPadding.left(), 0);
|
||||
const auto skip = st::defaultBoxCheckbox.textPosition.x();
|
||||
label->resizeToWidth(width
|
||||
- rect::right(userpic)
|
||||
- skip
|
||||
- st::boxRowPadding.right());
|
||||
label->moveToLeft(
|
||||
rect::right(userpic) + skip,
|
||||
((userpic->height() - label->height()) / 2));
|
||||
}, label->lifetime());
|
||||
|
||||
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
label->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
Ui::AddSkip(container);
|
||||
Ui::AddSkip(container);
|
||||
|
||||
box->addRow(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
container,
|
||||
peer->isSelf()
|
||||
? tr::lng_sure_delete_saved_messages()
|
||||
: maybeUser
|
||||
? tr::lng_sure_delete_history(
|
||||
lt_contact,
|
||||
rpl::single(peer->name()))
|
||||
: (peer->isChannel() && !peer->isMegagroup())
|
||||
? tr::lng_sure_leave_channel()
|
||||
: tr::lng_sure_leave_group(),
|
||||
st::boxLabel));
|
||||
|
||||
const auto maybeCheckbox = [&]() -> Ui::Checkbox* {
|
||||
if (!peer->canRevokeFullHistory()) {
|
||||
return nullptr;
|
||||
}
|
||||
Ui::AddSkip(container);
|
||||
Ui::AddSkip(container);
|
||||
return box->addRow(
|
||||
object_ptr<Ui::Checkbox>(
|
||||
container,
|
||||
maybeUser
|
||||
? tr::lng_delete_for_other_check(
|
||||
tr::now,
|
||||
lt_user,
|
||||
TextWithEntities{ maybeUser->firstName },
|
||||
Ui::Text::RichLangValue)
|
||||
: tr::lng_delete_for_everyone_check(
|
||||
tr::now,
|
||||
Ui::Text::WithEntities),
|
||||
false,
|
||||
st::defaultBoxCheckbox));
|
||||
}();
|
||||
|
||||
Ui::AddSkip(container);
|
||||
|
||||
auto buttonText = maybeUser
|
||||
? tr::lng_box_delete()
|
||||
: !maybeCheckbox
|
||||
? tr::lng_box_leave()
|
||||
: maybeCheckbox->checkedValue() | rpl::map([](bool checked) {
|
||||
return checked ? tr::lng_box_delete() : tr::lng_box_leave();
|
||||
}) | rpl::flatten_latest();
|
||||
|
||||
const auto close = crl::guard(box, [=] { box->closeBox(); });
|
||||
box->addButton(std::move(buttonText), [=] {
|
||||
const auto revoke = maybeCheckbox && maybeCheckbox->checked();
|
||||
Core::App().closeChatFromWindows(peer);
|
||||
// Don't delete old history by default,
|
||||
// because Android app doesn't.
|
||||
//
|
||||
//if (const auto from = peer->migrateFrom()) {
|
||||
// peer->session().api().deleteConversation(from, false);
|
||||
//}
|
||||
peer->session().api().deleteConversation(peer, revoke);
|
||||
close();
|
||||
}, st::attentionBoxButton);
|
||||
box->addButton(tr::lng_cancel(), close);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
class PeerData;
|
||||
|
||||
namespace Ui {
|
||||
class GenericBox;
|
||||
} // namespace Ui
|
||||
|
||||
void CreateModerateMessagesBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
const HistoryItemsList &items,
|
||||
Fn<void()> confirmed);
|
||||
|
||||
[[nodiscard]] bool CanCreateModerateMessagesBox(const HistoryItemsList &);
|
||||
|
||||
void DeleteChatBox(not_null<Ui::GenericBox*> box, not_null<PeerData*> peer);
|
||||
@@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_widgets.h"
|
||||
|
||||
#include <xxhash.h> // XXH64.
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
[[nodiscard]] PeerListRowId UniqueRowIdFromString(const QString &d) {
|
||||
return XXH64(d.data(), d.size() * sizeof(ushort), 0);
|
||||
@@ -734,6 +735,10 @@ auto PeerListRow::generateNameWords() const
|
||||
return peer()->nameWords();
|
||||
}
|
||||
|
||||
const style::PeerListItem &PeerListRow::computeSt(
|
||||
const style::PeerListItem &st) const {
|
||||
return st;
|
||||
}
|
||||
|
||||
void PeerListRow::invalidatePixmapsCache() {
|
||||
if (_checkbox) {
|
||||
@@ -816,9 +821,14 @@ void PeerListRow::stopLastRipple() {
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListRow::paintRipple(Painter &p, int x, int y, int outerWidth) {
|
||||
void PeerListRow::paintRipple(
|
||||
Painter &p,
|
||||
const style::PeerListItem &st,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth) {
|
||||
if (_ripple) {
|
||||
_ripple->paint(p, x, y, outerWidth);
|
||||
_ripple->paint(p, x, y, outerWidth, &st.button.ripple.color->c);
|
||||
if (_ripple->empty()) {
|
||||
_ripple.reset();
|
||||
}
|
||||
@@ -1543,15 +1553,44 @@ void PeerListContent::handleMouseMove(QPoint globalPosition) {
|
||||
&& *_lastMousePosition == globalPosition) {
|
||||
return;
|
||||
}
|
||||
if (_trackPressStart
|
||||
&& ((*_trackPressStart - globalPosition).manhattanLength()
|
||||
> QApplication::startDragDistance())) {
|
||||
_trackPressStart = {};
|
||||
_controller->rowTrackPressCancel();
|
||||
}
|
||||
if (!_controller->rowTrackPressSkipMouseSelection()) {
|
||||
selectByMouse(globalPosition);
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListContent::pressLeftToContextMenu(bool shown) {
|
||||
if (shown) {
|
||||
setContexted(_pressed);
|
||||
setPressed(Selected());
|
||||
} else {
|
||||
setContexted(Selected());
|
||||
}
|
||||
}
|
||||
|
||||
bool PeerListContent::trackRowPressFromGlobal(QPoint globalPosition) {
|
||||
selectByMouse(globalPosition);
|
||||
if (const auto row = getRow(_selected.index)) {
|
||||
if (_controller->rowTrackPress(row)) {
|
||||
_trackPressStart = globalPosition;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PeerListContent::mousePressEvent(QMouseEvent *e) {
|
||||
_pressButton = e->button();
|
||||
selectByMouse(e->globalPos());
|
||||
setPressed(_selected);
|
||||
if (auto row = getRow(_selected.index)) {
|
||||
auto updateCallback = [this, row, hint = _selected.index] {
|
||||
_trackPressStart = {};
|
||||
if (const auto row = getRow(_selected.index)) {
|
||||
const auto updateCallback = [this, row, hint = _selected.index] {
|
||||
updateRow(row, hint);
|
||||
};
|
||||
if (_selected.element) {
|
||||
@@ -1577,8 +1616,11 @@ void PeerListContent::mousePressEvent(QMouseEvent *e) {
|
||||
row->addRipple(_st.item, maskGenerator, point, std::move(updateCallback));
|
||||
}
|
||||
}
|
||||
if (_pressButton == Qt::LeftButton && _controller->rowTrackPress(row)) {
|
||||
_trackPressStart = e->globalPos();
|
||||
}
|
||||
}
|
||||
if (anim::Disabled() && !_selected.element) {
|
||||
if (anim::Disabled() && !_trackPressStart && !_selected.element) {
|
||||
mousePressReleased(e->button());
|
||||
}
|
||||
}
|
||||
@@ -1588,6 +1630,9 @@ void PeerListContent::mouseReleaseEvent(QMouseEvent *e) {
|
||||
}
|
||||
|
||||
void PeerListContent::mousePressReleased(Qt::MouseButton button) {
|
||||
_trackPressStart = {};
|
||||
_controller->rowTrackPressCancel();
|
||||
|
||||
updateRow(_pressed.index);
|
||||
updateRow(_selected.index);
|
||||
|
||||
@@ -1689,7 +1734,9 @@ crl::time PeerListContent::paintRow(
|
||||
const auto row = getRow(index);
|
||||
Assert(row != nullptr);
|
||||
|
||||
row->lazyInitialize(_st.item);
|
||||
const auto &st = row->computeSt(_st.item);
|
||||
|
||||
row->lazyInitialize(st);
|
||||
const auto outerWidth = width();
|
||||
|
||||
auto refreshStatusAt = row->refreshStatusTime();
|
||||
@@ -1717,8 +1764,8 @@ crl::time PeerListContent::paintRow(
|
||||
|
||||
const auto opacity = row->opacity();
|
||||
const auto &bg = selected
|
||||
? _st.item.button.textBgOver
|
||||
: _st.item.button.textBg;
|
||||
? st.button.textBgOver
|
||||
: st.button.textBg;
|
||||
if (opacity < 1.) {
|
||||
p.setOpacity(opacity);
|
||||
}
|
||||
@@ -1729,36 +1776,37 @@ crl::time PeerListContent::paintRow(
|
||||
});
|
||||
|
||||
p.fillRect(0, 0, outerWidth, _rowHeight, bg);
|
||||
row->paintRipple(p, 0, 0, outerWidth);
|
||||
row->paintRipple(p, st, 0, 0, outerWidth);
|
||||
row->paintUserpic(
|
||||
p,
|
||||
_st.item,
|
||||
_st.item.photoPosition.x(),
|
||||
_st.item.photoPosition.y(),
|
||||
st,
|
||||
st.photoPosition.x(),
|
||||
st.photoPosition.y(),
|
||||
outerWidth);
|
||||
|
||||
p.setPen(st::contactsNameFg);
|
||||
|
||||
const auto skipRight = _st.item.photoPosition.x();
|
||||
const auto skipRight = st.photoPosition.x();
|
||||
const auto rightActionSize = row->rightActionSize();
|
||||
const auto rightActionMargins = rightActionSize.isEmpty()
|
||||
? QMargins()
|
||||
: row->rightActionMargins();
|
||||
const auto &name = row->name();
|
||||
const auto namex = _st.item.namePosition.x();
|
||||
const auto namey = _st.item.namePosition.y();
|
||||
const auto namePosition = st.namePosition;
|
||||
const auto namex = namePosition.x();
|
||||
const auto namey = namePosition.y();
|
||||
auto namew = outerWidth - namex - skipRight;
|
||||
if (!rightActionSize.isEmpty()
|
||||
&& (namey < rightActionMargins.top() + rightActionSize.height())
|
||||
&& (namey + _st.item.nameStyle.font->height
|
||||
&& (namey + st.nameStyle.font->height
|
||||
> rightActionMargins.top())) {
|
||||
namew -= rightActionMargins.left()
|
||||
+ rightActionSize.width()
|
||||
+ rightActionMargins.right()
|
||||
- skipRight;
|
||||
}
|
||||
const auto statusx = _st.item.statusPosition.x();
|
||||
const auto statusy = _st.item.statusPosition.y();
|
||||
const auto statusx = st.statusPosition.x();
|
||||
const auto statusy = st.statusPosition.y();
|
||||
auto statusw = outerWidth - statusx - skipRight;
|
||||
if (!rightActionSize.isEmpty()
|
||||
&& (statusy < rightActionMargins.top() + rightActionSize.height())
|
||||
@@ -1780,7 +1828,7 @@ crl::time PeerListContent::paintRow(
|
||||
width(),
|
||||
selected);
|
||||
auto nameCheckedRatio = row->disabled() ? 0. : row->checkedRatio();
|
||||
p.setPen(anim::pen(_st.item.nameFg, _st.item.nameFgChecked, nameCheckedRatio));
|
||||
p.setPen(anim::pen(st.nameFg, st.nameFgChecked, nameCheckedRatio));
|
||||
name.drawLeftElided(p, namex, namey, namew, width());
|
||||
|
||||
p.setFont(st::contactsStatusFont);
|
||||
@@ -1799,17 +1847,17 @@ crl::time PeerListContent::paintRow(
|
||||
if (highlightedWidth > availableWidth) {
|
||||
highlightedPart = st::contactsStatusFont->elided(highlightedPart, availableWidth);
|
||||
}
|
||||
p.setPen(_st.item.statusFgActive);
|
||||
p.setPen(st.statusFgActive);
|
||||
p.drawTextLeft(statusx, statusy, width(), highlightedPart);
|
||||
} else {
|
||||
grayedPart = st::contactsStatusFont->elided(grayedPart, availableWidth - highlightedWidth);
|
||||
p.setPen(_st.item.statusFgActive);
|
||||
p.setPen(st.statusFgActive);
|
||||
p.drawTextLeft(statusx, statusy, width(), highlightedPart);
|
||||
p.setPen(selected ? _st.item.statusFgOver : _st.item.statusFg);
|
||||
p.setPen(selected ? st.statusFgOver : st.statusFg);
|
||||
p.drawTextLeft(statusx + highlightedWidth, statusy, width(), grayedPart);
|
||||
}
|
||||
} else {
|
||||
row->paintStatusText(p, _st.item, statusx, statusy, statusw, width(), selected);
|
||||
row->paintStatusText(p, st, statusx, statusy, statusw, width(), selected);
|
||||
}
|
||||
|
||||
row->elementsPaint(
|
||||
@@ -1905,10 +1953,30 @@ void PeerListContent::selectSkipPage(int height, int direction) {
|
||||
selectSkip(rowsToSkip * direction);
|
||||
}
|
||||
|
||||
void PeerListContent::selectLast() {
|
||||
const auto rowsCount = shownRowsCount();
|
||||
const auto newSelectedIndex = rowsCount - 1;
|
||||
_selected.index.value = newSelectedIndex;
|
||||
_selected.element = 0;
|
||||
if (newSelectedIndex >= 0) {
|
||||
auto top = (newSelectedIndex > 0) ? getRowTop(RowIndex(newSelectedIndex)) : 0;
|
||||
auto bottom = (newSelectedIndex + 1 < rowsCount) ? getRowTop(RowIndex(newSelectedIndex + 1)) : height();
|
||||
_scrollToRequests.fire({ top, bottom });
|
||||
}
|
||||
|
||||
update();
|
||||
|
||||
_selectedIndex = _selected.index.value;
|
||||
}
|
||||
|
||||
rpl::producer<int> PeerListContent::selectedIndexValue() const {
|
||||
return _selectedIndex.value();
|
||||
}
|
||||
|
||||
int PeerListContent::selectedIndex() const {
|
||||
return _selectedIndex.current();
|
||||
}
|
||||
|
||||
bool PeerListContent::hasSelection() const {
|
||||
return _selected.index.value >= 0;
|
||||
}
|
||||
@@ -2093,6 +2161,16 @@ bool PeerListContent::submitted() {
|
||||
return false;
|
||||
}
|
||||
|
||||
PeerListRowId PeerListContent::updateFromParentDrag(QPoint globalPosition) {
|
||||
selectByMouse(globalPosition);
|
||||
const auto row = getRow(_selected.index);
|
||||
return row ? row->id() : 0;
|
||||
}
|
||||
|
||||
void PeerListContent::dragLeft() {
|
||||
clearSelection();
|
||||
}
|
||||
|
||||
void PeerListContent::visibleTopBottomUpdated(
|
||||
int visibleTop,
|
||||
int visibleBottom) {
|
||||
|
||||
@@ -100,6 +100,8 @@ public:
|
||||
-> const base::flat_set<QChar> &;
|
||||
[[nodiscard]] virtual auto generateNameWords() const
|
||||
-> const base::flat_set<QString> &;
|
||||
[[nodiscard]] virtual const style::PeerListItem &computeSt(
|
||||
const style::PeerListItem &st) const;
|
||||
|
||||
virtual void preloadUserpic();
|
||||
|
||||
@@ -228,7 +230,12 @@ public:
|
||||
QPoint point,
|
||||
UpdateCallback &&updateCallback);
|
||||
void stopLastRipple();
|
||||
void paintRipple(Painter &p, int x, int y, int outerWidth);
|
||||
void paintRipple(
|
||||
Painter &p,
|
||||
const style::PeerListItem &st,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth);
|
||||
void paintUserpic(
|
||||
Painter &p,
|
||||
const style::PeerListItem &st,
|
||||
@@ -341,6 +348,9 @@ public:
|
||||
virtual int peerListPartitionRows(Fn<bool(const PeerListRow &a)> border) = 0;
|
||||
virtual std::shared_ptr<Main::SessionShow> peerListUiShow() = 0;
|
||||
|
||||
virtual void peerListPressLeftToContextMenu(bool shown) = 0;
|
||||
virtual bool peerListTrackRowPressFromGlobal(QPoint globalPosition) = 0;
|
||||
|
||||
template <typename PeerDataRange>
|
||||
void peerListAddSelectedPeers(PeerDataRange &&range) {
|
||||
for (const auto peer : range) {
|
||||
@@ -471,6 +481,15 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool rowTrackPress(not_null<PeerListRow*> row) {
|
||||
return false;
|
||||
}
|
||||
virtual void rowTrackPressCancel() {
|
||||
}
|
||||
virtual bool rowTrackPressSkipMouseSelection() {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual void loadMoreRows() {
|
||||
}
|
||||
virtual void itemDeselectedHook(not_null<PeerData*> peer) {
|
||||
@@ -601,6 +620,7 @@ public:
|
||||
};
|
||||
SkipResult selectSkip(int direction);
|
||||
void selectSkipPage(int height, int direction);
|
||||
void selectLast();
|
||||
|
||||
enum class Mode {
|
||||
Default,
|
||||
@@ -609,6 +629,7 @@ public:
|
||||
void setMode(Mode mode);
|
||||
|
||||
[[nodiscard]] rpl::producer<int> selectedIndexValue() const;
|
||||
[[nodiscard]] int selectedIndex() const;
|
||||
[[nodiscard]] bool hasSelection() const;
|
||||
[[nodiscard]] bool hasPressed() const;
|
||||
void clearSelection();
|
||||
@@ -616,6 +637,9 @@ public:
|
||||
void searchQueryChanged(QString query);
|
||||
bool submitted();
|
||||
|
||||
PeerListRowId updateFromParentDrag(QPoint globalPosition);
|
||||
void dragLeft();
|
||||
|
||||
// Interface for the controller.
|
||||
void appendRow(std::unique_ptr<PeerListRow> row);
|
||||
void appendSearchRow(std::unique_ptr<PeerListRow> row);
|
||||
@@ -643,6 +667,8 @@ public:
|
||||
void refreshRows();
|
||||
|
||||
void mouseLeftGeometry();
|
||||
void pressLeftToContextMenu(bool shown);
|
||||
bool trackRowPressFromGlobal(QPoint globalPosition);
|
||||
|
||||
void setSearchMode(PeerListSearchMode mode);
|
||||
void changeCheckState(
|
||||
@@ -817,6 +843,7 @@ private:
|
||||
bool _mouseSelection = false;
|
||||
std::optional<QPoint> _lastMousePosition;
|
||||
Qt::MouseButton _pressButton = Qt::LeftButton;
|
||||
std::optional<QPoint> _trackPressStart;
|
||||
|
||||
rpl::event_stream<Ui::ScrollToRequest> _scrollToRequests;
|
||||
|
||||
@@ -980,6 +1007,13 @@ public:
|
||||
bool highlightRow,
|
||||
Fn<void(not_null<Ui::PopupMenu*>)> destroyed = nullptr) override;
|
||||
|
||||
void peerListPressLeftToContextMenu(bool shown) override {
|
||||
_content->pressLeftToContextMenu(shown);
|
||||
}
|
||||
bool peerListTrackRowPressFromGlobal(QPoint globalPosition) override {
|
||||
return _content->trackRowPressFromGlobal(globalPosition);
|
||||
}
|
||||
|
||||
protected:
|
||||
not_null<PeerListContent*> content() const {
|
||||
return _content;
|
||||
|
||||
@@ -42,16 +42,14 @@ namespace {
|
||||
|
||||
constexpr auto kDefaultIconId = DocumentId(0x7FFF'FFFF'FFFF'FFFFULL);
|
||||
|
||||
struct DefaultIcon {
|
||||
QString title;
|
||||
int32 colorId = 0;
|
||||
};
|
||||
using DefaultIcon = Data::TopicIconDescriptor;
|
||||
|
||||
class DefaultIconEmoji final : public Ui::Text::CustomEmoji {
|
||||
public:
|
||||
DefaultIconEmoji(
|
||||
rpl::producer<DefaultIcon> value,
|
||||
Fn<void()> repaint);
|
||||
Fn<void()> repaint,
|
||||
Data::CustomEmojiSizeTag tag);
|
||||
|
||||
int width() override;
|
||||
QString entityData() override;
|
||||
@@ -64,14 +62,17 @@ public:
|
||||
private:
|
||||
DefaultIcon _icon = {};
|
||||
QImage _image;
|
||||
Data::CustomEmojiSizeTag _tag = {};
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
DefaultIconEmoji::DefaultIconEmoji(
|
||||
rpl::producer<DefaultIcon> value,
|
||||
Fn<void()> repaint) {
|
||||
rpl::producer<DefaultIcon> value,
|
||||
Fn<void()> repaint,
|
||||
Data::CustomEmojiSizeTag tag)
|
||||
: _tag(tag) {
|
||||
std::move(value) | rpl::start_with_next([=](DefaultIcon value) {
|
||||
_icon = value;
|
||||
_image = QImage();
|
||||
@@ -88,15 +89,22 @@ QString DefaultIconEmoji::entityData() {
|
||||
}
|
||||
|
||||
void DefaultIconEmoji::paint(QPainter &p, const Context &context) {
|
||||
const auto &st = (_tag == Data::CustomEmojiSizeTag::Normal)
|
||||
? st::normalForumTopicIcon
|
||||
: st::defaultForumTopicIcon;
|
||||
if (_image.isNull()) {
|
||||
_image = Data::ForumTopicIconFrame(
|
||||
_icon.colorId,
|
||||
_icon.title,
|
||||
st::defaultForumTopicIcon);
|
||||
_image = Data::IsForumGeneralIconTitle(_icon.title)
|
||||
? Data::ForumTopicGeneralIconFrame(
|
||||
st.size,
|
||||
Data::ParseForumGeneralIconColor(_icon.colorId))
|
||||
: Data::ForumTopicIconFrame(_icon.colorId, _icon.title, st);
|
||||
}
|
||||
const auto esize = Ui::Emoji::GetSizeLarge() / style::DevicePixelRatio();
|
||||
const auto full = (_tag == Data::CustomEmojiSizeTag::Normal)
|
||||
? Ui::Emoji::GetSizeNormal()
|
||||
: Ui::Emoji::GetSizeLarge();
|
||||
const auto esize = full / style::DevicePixelRatio();
|
||||
const auto customSize = Ui::Text::AdjustCustomEmojiSize(esize);
|
||||
const auto skip = (customSize - st::defaultForumTopicIcon.size) / 2;
|
||||
const auto skip = (customSize - st.size) / 2;
|
||||
p.drawImage(context.position + QPoint(skip, skip), _image);
|
||||
}
|
||||
|
||||
@@ -212,7 +220,7 @@ bool DefaultIconEmoji::readyInDefaultState() {
|
||||
) | rpl::start_with_next([=] {
|
||||
state->frame = Data::ForumTopicGeneralIconFrame(
|
||||
st::largeForumTopicIcon.size,
|
||||
st::windowSubTextFg);
|
||||
st::windowSubTextFg->c);
|
||||
result->update();
|
||||
}, result->lifetime());
|
||||
|
||||
@@ -261,7 +269,8 @@ struct IconSelector {
|
||||
if (id == kDefaultIconId) {
|
||||
return std::make_unique<DefaultIconEmoji>(
|
||||
rpl::duplicate(defaultIcon),
|
||||
repaint);
|
||||
std::move(repaint),
|
||||
tag);
|
||||
}
|
||||
return manager->create(id, std::move(repaint), tag);
|
||||
};
|
||||
@@ -572,3 +581,13 @@ void EditForumTopicBox(
|
||||
box->closeBox();
|
||||
});
|
||||
}
|
||||
|
||||
std::unique_ptr<Ui::Text::CustomEmoji> MakeTopicIconEmoji(
|
||||
Data::TopicIconDescriptor descriptor,
|
||||
Fn<void()> repaint,
|
||||
Data::CustomEmojiSizeTag tag) {
|
||||
return std::make_unique<DefaultIconEmoji>(
|
||||
rpl::single(descriptor),
|
||||
std::move(repaint),
|
||||
tag);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
class History;
|
||||
|
||||
namespace Data {
|
||||
struct TopicIconDescriptor;
|
||||
enum class CustomEmojiSizeTag : uchar;
|
||||
} // namespace Data
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
@@ -25,3 +30,8 @@ void EditForumTopicBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<History*> forum,
|
||||
MsgId rootId);
|
||||
|
||||
[[nodiscard]] std::unique_ptr<Ui::Text::CustomEmoji> MakeTopicIconEmoji(
|
||||
Data::TopicIconDescriptor descriptor,
|
||||
Fn<void()> repaint,
|
||||
Data::CustomEmojiSizeTag tag);
|
||||
|
||||
@@ -166,33 +166,6 @@ void SaveChannelAdmin(
|
||||
}).send();
|
||||
}
|
||||
|
||||
void SaveChannelRestriction(
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<PeerData*> participant,
|
||||
ChatRestrictionsInfo oldRights,
|
||||
ChatRestrictionsInfo newRights,
|
||||
Fn<void()> onDone,
|
||||
Fn<void()> onFail) {
|
||||
channel->session().api().request(MTPchannels_EditBanned(
|
||||
channel->inputChannel,
|
||||
participant->input,
|
||||
MTP_chatBannedRights(
|
||||
MTP_flags(MTPDchatBannedRights::Flags::from_raw(
|
||||
uint32(newRights.flags))),
|
||||
MTP_int(newRights.until))
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
channel->session().api().applyUpdates(result);
|
||||
channel->applyEditBanned(participant, oldRights, newRights);
|
||||
if (onDone) {
|
||||
onDone();
|
||||
}
|
||||
}).fail([=] {
|
||||
if (onFail) {
|
||||
onFail();
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
void SaveChatParticipantKick(
|
||||
not_null<ChatData*> chat,
|
||||
not_null<UserData*> user,
|
||||
@@ -275,7 +248,7 @@ Fn<void(
|
||||
ChatRestrictionsInfo newRights) {
|
||||
const auto done = [=] { if (onDone) onDone(newRights); };
|
||||
const auto saveForChannel = [=](not_null<ChannelData*> channel) {
|
||||
SaveChannelRestriction(
|
||||
Api::ChatParticipants::Restrict(
|
||||
channel,
|
||||
participant,
|
||||
oldRights,
|
||||
|
||||
@@ -312,6 +312,7 @@ PreviewWrap::PreviewWrap(
|
||||
nullptr, // document
|
||||
WebPageCollage(),
|
||||
nullptr, // iv
|
||||
nullptr, // stickerSet
|
||||
0, // duration
|
||||
QString(), // author
|
||||
false, // hasLargeMedia
|
||||
@@ -1427,14 +1428,15 @@ void SetupPeerColorSample(
|
||||
void AddPeerColorButton(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> peer) {
|
||||
not_null<PeerData*> peer,
|
||||
const style::SettingsButton &st) {
|
||||
auto label = peer->isSelf()
|
||||
? tr::lng_settings_theme_name_color()
|
||||
: tr::lng_edit_channel_color();
|
||||
const auto button = AddButtonWithIcon(
|
||||
container,
|
||||
rpl::duplicate(label),
|
||||
st::settingsColorButton,
|
||||
st,
|
||||
{ &st::menuIconChangeColors });
|
||||
|
||||
const auto style = std::make_shared<Ui::ChatStyle>(
|
||||
|
||||
@@ -48,7 +48,8 @@ void EditPeerColorBox(
|
||||
void AddPeerColorButton(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> peer);
|
||||
not_null<PeerData*> peer,
|
||||
const style::SettingsButton &st);
|
||||
|
||||
void CheckBoostLevel(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
|
||||
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_user_names.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "boxes/peers/edit_participants_box.h"
|
||||
#include "boxes/peers/edit_peer_color_box.h"
|
||||
#include "boxes/peers/edit_peer_common.h"
|
||||
@@ -27,6 +28,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/stickers_box.h"
|
||||
#include "boxes/username_box.h"
|
||||
#include "chat_helpers/emoji_suggestions_widget.h"
|
||||
#include "chat_helpers/tabbed_panel.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "data/data_channel.h"
|
||||
@@ -47,6 +50,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "main/main_app_config.h"
|
||||
#include "settings/settings_common.h"
|
||||
#include "ui/boxes/boost_box.h"
|
||||
#include "ui/controls/emoji_button.h"
|
||||
#include "ui/controls/userpic_button.h"
|
||||
#include "ui/rp_widget.h"
|
||||
#include "ui/vertical_list.h"
|
||||
@@ -61,6 +65,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "api/api_invite_links.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
#include "styles/style_boxes.h"
|
||||
@@ -410,7 +415,12 @@ private:
|
||||
std::deque<FnMut<void()>> _saveStagesQueue;
|
||||
Saving _savingData;
|
||||
|
||||
const rpl::event_stream<Privacy> _privacyTypeUpdates;
|
||||
struct PrivacyAndForwards {
|
||||
Privacy privacy;
|
||||
bool noForwards = false;
|
||||
};
|
||||
|
||||
const rpl::event_stream<PrivacyAndForwards> _privacyTypeUpdates;
|
||||
const rpl::event_stream<ChannelData*> _linkedChatUpdates;
|
||||
mtpRequestId _linkedChatsRequestId = 0;
|
||||
|
||||
@@ -533,7 +543,7 @@ object_ptr<Ui::RpWidget> Controller::createTitleEdit() {
|
||||
_wrap,
|
||||
object_ptr<Ui::InputField>(
|
||||
_wrap,
|
||||
st::defaultInputField,
|
||||
st::editPeerTitleField,
|
||||
(_isBot
|
||||
? tr::lng_dlg_new_bot_name
|
||||
: _isGroup
|
||||
@@ -555,6 +565,76 @@ object_ptr<Ui::RpWidget> Controller::createTitleEdit() {
|
||||
submitTitle();
|
||||
}, result->entity()->lifetime());
|
||||
|
||||
{
|
||||
const auto field = result->entity();
|
||||
const auto container = _box->getDelegate()->outerContainer();
|
||||
using Selector = ChatHelpers::TabbedSelector;
|
||||
using PanelPtr = base::unique_qptr<ChatHelpers::TabbedPanel>;
|
||||
const auto emojiPanelPtr = field->lifetime().make_state<PanelPtr>(
|
||||
base::make_unique_q<ChatHelpers::TabbedPanel>(
|
||||
container,
|
||||
ChatHelpers::TabbedPanelDescriptor{
|
||||
.ownedSelector = object_ptr<Selector>(
|
||||
nullptr,
|
||||
ChatHelpers::TabbedSelectorDescriptor{
|
||||
.show = _navigation->uiShow(),
|
||||
.st = st::defaultComposeControls.tabbed,
|
||||
.level = Window::GifPauseReason::Layer,
|
||||
.mode = Selector::Mode::PeerTitle,
|
||||
}),
|
||||
}));
|
||||
const auto emojiPanel = emojiPanelPtr->get();
|
||||
emojiPanel->setDesiredHeightValues(
|
||||
1.,
|
||||
st::emojiPanMinHeight / 2,
|
||||
st::emojiPanMinHeight);
|
||||
emojiPanel->hide();
|
||||
emojiPanel->selector()->setCurrentPeer(_peer);
|
||||
emojiPanel->selector()->emojiChosen(
|
||||
) | rpl::start_with_next([=](ChatHelpers::EmojiChosen data) {
|
||||
Ui::InsertEmojiAtCursor(field->textCursor(), data.emoji);
|
||||
field->setFocus();
|
||||
}, field->lifetime());
|
||||
emojiPanel->setDropDown(true);
|
||||
|
||||
const auto emojiToggle = Ui::CreateChild<Ui::EmojiButton>(
|
||||
field,
|
||||
st::defaultComposeControls.files.emoji);
|
||||
emojiToggle->show();
|
||||
emojiToggle->installEventFilter(emojiPanel);
|
||||
emojiToggle->addClickHandler([=] { emojiPanel->toggleAnimated(); });
|
||||
|
||||
const auto updateEmojiPanelGeometry = [=] {
|
||||
const auto parent = emojiPanel->parentWidget();
|
||||
const auto global = emojiToggle->mapToGlobal({ 0, 0 });
|
||||
const auto local = parent->mapFromGlobal(global);
|
||||
emojiPanel->moveTopRight(
|
||||
local.y() + emojiToggle->height(),
|
||||
local.x() + emojiToggle->width() * 3);
|
||||
};
|
||||
|
||||
base::install_event_filter(container, [=](not_null<QEvent*> event) {
|
||||
const auto type = event->type();
|
||||
if (type == QEvent::Move || type == QEvent::Resize) {
|
||||
crl::on_main(field, [=] { updateEmojiPanelGeometry(); });
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
|
||||
field->widthValue() | rpl::start_with_next([=](int width) {
|
||||
const auto &p = st::editPeerTitleEmojiPosition;
|
||||
emojiToggle->moveToRight(p.x(), p.y(), width);
|
||||
updateEmojiPanelGeometry();
|
||||
}, emojiToggle->lifetime());
|
||||
|
||||
base::install_event_filter(emojiToggle, [=](not_null<QEvent*> event) {
|
||||
if (event->type() == QEvent::Enter) {
|
||||
updateEmojiPanelGeometry();
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
}
|
||||
|
||||
_controls.title = result->entity();
|
||||
return result;
|
||||
}
|
||||
@@ -686,7 +766,7 @@ void Controller::refreshHistoryVisibility() {
|
||||
void Controller::showEditPeerTypeBox(
|
||||
std::optional<rpl::producer<QString>> error) {
|
||||
const auto boxCallback = crl::guard(this, [=](EditPeerTypeData data) {
|
||||
_privacyTypeUpdates.fire_copy(data.privacy);
|
||||
_privacyTypeUpdates.fire({ data.privacy, data.noForwards });
|
||||
_typeDataSavedValue = data;
|
||||
refreshHistoryVisibility();
|
||||
});
|
||||
@@ -807,7 +887,8 @@ void Controller::fillPrivacyTypeButton() {
|
||||
? tr::lng_manage_peer_group_type
|
||||
: tr::lng_manage_peer_channel_type)(),
|
||||
_privacyTypeUpdates.events(
|
||||
) | rpl::map([=](Privacy flag) {
|
||||
) | rpl::map([=](PrivacyAndForwards data) {
|
||||
const auto flag = data.privacy;
|
||||
if (flag == Privacy::HasUsername) {
|
||||
_peer->session().api().usernames().requestToCache(_peer);
|
||||
}
|
||||
@@ -819,14 +900,21 @@ void Controller::fillPrivacyTypeButton() {
|
||||
: tr::lng_manage_public_peer_title)()
|
||||
: (hasLocation
|
||||
? tr::lng_manage_peer_link_invite
|
||||
: isGroup
|
||||
: ((!data.noForwards) && isGroup)
|
||||
? tr::lng_manage_private_group_title
|
||||
: tr::lng_manage_private_peer_title)();
|
||||
: ((!data.noForwards) && !isGroup)
|
||||
? tr::lng_manage_private_peer_title
|
||||
: isGroup
|
||||
? tr::lng_manage_private_group_noforwards_title
|
||||
: tr::lng_manage_private_peer_noforwards_title)();
|
||||
}) | rpl::flatten_latest(),
|
||||
[=] { showEditPeerTypeBox(); },
|
||||
{ &st::menuIconCustomize });
|
||||
|
||||
_privacyTypeUpdates.fire_copy(_typeDataSavedValue->privacy);
|
||||
_privacyTypeUpdates.fire_copy({
|
||||
_typeDataSavedValue->privacy,
|
||||
_typeDataSavedValue->noForwards,
|
||||
});
|
||||
}
|
||||
|
||||
void Controller::fillLinkedChatButton() {
|
||||
@@ -941,7 +1029,11 @@ void Controller::fillColorIndexButton() {
|
||||
Expects(_controls.buttonsLayout != nullptr);
|
||||
|
||||
const auto show = _navigation->uiShow();
|
||||
AddPeerColorButton(_controls.buttonsLayout, show, _peer);
|
||||
AddPeerColorButton(
|
||||
_controls.buttonsLayout,
|
||||
_navigation->uiShow(),
|
||||
_peer,
|
||||
st::managePeerColorsButton);
|
||||
}
|
||||
|
||||
void Controller::fillSignaturesButton() {
|
||||
|
||||
@@ -7,30 +7,33 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/peers/edit_peer_reactions.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "chat_helpers/tabbed_panel.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_peer_values.h" // UniqueReactionsLimit.
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "history/view/reactions/history_view_reactions_selector.h"
|
||||
#include "main/main_session.h"
|
||||
#include "apiwrap.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/boxes/boost_box.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/continuous_sliders.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "window/window_session_controller_link_info.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_info.h"
|
||||
#include "styles/style_settings.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_settings.h"
|
||||
|
||||
#include <QtWidgets/QTextEdit>
|
||||
#include <QtGui/QTextBlock>
|
||||
@@ -705,12 +708,16 @@ void EditAllowedReactionsBox(
|
||||
}
|
||||
};
|
||||
changed(selected.empty() ? DefaultSelected() : std::move(selected), {});
|
||||
Ui::AddSubsectionTitle(
|
||||
reactions,
|
||||
enabled
|
||||
? tr::lng_manage_peer_reactions_available()
|
||||
: tr::lng_manage_peer_reactions_some_title(),
|
||||
st::manageGroupReactionsFieldPadding);
|
||||
reactions->add(AddReactionsSelector(reactions, {
|
||||
.outer = box->getDelegate()->outerContainer(),
|
||||
.controller = args.navigation->parentController(),
|
||||
.title = (enabled
|
||||
? tr::lng_manage_peer_reactions_available()
|
||||
: tr::lng_manage_peer_reactions_some_title()),
|
||||
.title = tr::lng_manage_peer_reactions_available_ph(),
|
||||
.list = all,
|
||||
.selected = state->selected,
|
||||
.callback = changed,
|
||||
@@ -726,6 +733,7 @@ void EditAllowedReactionsBox(
|
||||
}
|
||||
});
|
||||
|
||||
const auto reactionsLimit = container->lifetime().make_state<int>(0);
|
||||
if (!isGroup) {
|
||||
AddReactionsText(
|
||||
container,
|
||||
@@ -733,9 +741,109 @@ void EditAllowedReactionsBox(
|
||||
args.allowedCustomReactions,
|
||||
state->customCount.value(),
|
||||
args.askForBoosts);
|
||||
|
||||
const auto session = &args.navigation->parentController()->session();
|
||||
|
||||
const auto wrap = container->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
container,
|
||||
object_ptr<Ui::VerticalLayout>(container)));
|
||||
const auto max = Data::UniqueReactionsLimit(session->user());
|
||||
const auto inactiveColor = std::make_optional(st::windowSubTextFg->c);
|
||||
const auto activeColor = std::make_optional(
|
||||
st::windowActiveTextFg->c);
|
||||
const auto inner = wrap->entity();
|
||||
Ui::AddSkip(inner);
|
||||
Ui::AddSubsectionTitle(
|
||||
inner,
|
||||
tr::lng_manage_peer_reactions_max_title(),
|
||||
st::manageGroupReactionsMaxSubtitlePadding);
|
||||
Ui::AddSkip(inner);
|
||||
const auto line = inner->add(
|
||||
object_ptr<Ui::RpWidget>(inner),
|
||||
st::boxRowPadding);
|
||||
Ui::AddSkip(inner);
|
||||
Ui::AddSkip(inner);
|
||||
const auto left = Ui::CreateChild<Ui::FlatLabel>(
|
||||
line,
|
||||
QString::number(1),
|
||||
st::defaultFlatLabel);
|
||||
const auto center = Ui::CreateChild<Ui::FlatLabel>(
|
||||
line,
|
||||
st::defaultFlatLabel);
|
||||
const auto right = Ui::CreateChild<Ui::FlatLabel>(
|
||||
line,
|
||||
QString::number(max),
|
||||
st::defaultFlatLabel);
|
||||
const auto slider = Ui::CreateChild<Ui::MediaSlider>(
|
||||
line,
|
||||
st::settingsScale);
|
||||
rpl::combine(
|
||||
line->sizeValue(),
|
||||
left->sizeValue(),
|
||||
center->sizeValue(),
|
||||
right->sizeValue()
|
||||
) | rpl::start_with_next([=](
|
||||
const QSize &s,
|
||||
const QSize &leftSize,
|
||||
const QSize ¢erSize,
|
||||
const QSize &rightSize) {
|
||||
const auto sliderHeight = st::settingsScale.seekSize.height();
|
||||
line->resize(
|
||||
line->width(),
|
||||
leftSize.height() + sliderHeight * 2);
|
||||
{
|
||||
const auto r = line->rect();
|
||||
slider->setGeometry(
|
||||
0,
|
||||
r.height() - sliderHeight * 1.5,
|
||||
r.width(),
|
||||
sliderHeight);
|
||||
}
|
||||
left->moveToLeft(0, 0);
|
||||
right->moveToRight(0, 0);
|
||||
center->moveToLeft((s.width() - centerSize.width()) / 2, 0);
|
||||
}, line->lifetime());
|
||||
|
||||
const auto updateLabels = [=](int limit) {
|
||||
left->setTextColorOverride((limit <= 1)
|
||||
? activeColor
|
||||
: inactiveColor);
|
||||
|
||||
center->setText(tr::lng_manage_peer_reactions_max_slider(
|
||||
tr::now,
|
||||
lt_count,
|
||||
limit));
|
||||
center->setTextColorOverride(activeColor);
|
||||
|
||||
right->setTextColorOverride((limit >= max)
|
||||
? activeColor
|
||||
: inactiveColor);
|
||||
|
||||
(*reactionsLimit) = limit;
|
||||
};
|
||||
const auto current = args.allowed.maxCount
|
||||
? std::clamp(1, args.allowed.maxCount, max)
|
||||
: max / 2;
|
||||
slider->setPseudoDiscrete(
|
||||
max,
|
||||
[=](int index) { return index + 1; },
|
||||
current,
|
||||
updateLabels,
|
||||
updateLabels);
|
||||
updateLabels(current);
|
||||
|
||||
wrap->toggleOn(rpl::single(
|
||||
optionInitial != Option::None
|
||||
) | rpl::then(
|
||||
state->selectorState.value(
|
||||
) | rpl::map(rpl::mappers::_1 == SelectorState::Active)));
|
||||
|
||||
Ui::AddDividerText(inner, tr::lng_manage_peer_reactions_max_about());
|
||||
}
|
||||
const auto collect = [=] {
|
||||
auto result = AllowedReactions();
|
||||
result.maxCount = (*reactionsLimit);
|
||||
if (isGroup
|
||||
? (state->option.current() == Option::Some)
|
||||
: (enabled->toggled())) {
|
||||
@@ -783,6 +891,7 @@ void SaveAllowedReactions(
|
||||
Data::ReactionToMTP
|
||||
) | ranges::to<QVector<MTPReaction>>;
|
||||
|
||||
using Flag = MTPmessages_SetChatAvailableReactions::Flag;
|
||||
using Type = Data::AllowedReactionsType;
|
||||
const auto updated = (allowed.type != Type::Some)
|
||||
? MTP_chatReactionsAll(MTP_flags((allowed.type == Type::Default)
|
||||
@@ -792,14 +901,18 @@ void SaveAllowedReactions(
|
||||
? MTP_chatReactionsNone()
|
||||
: MTP_chatReactionsSome(MTP_vector<MTPReaction>(ids));
|
||||
peer->session().api().request(MTPmessages_SetChatAvailableReactions(
|
||||
allowed.maxCount ? MTP_flags(Flag::f_reactions_limit) : MTP_flags(0),
|
||||
peer->input,
|
||||
updated
|
||||
updated,
|
||||
MTP_int(allowed.maxCount)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
peer->session().api().applyUpdates(result);
|
||||
auto parsed = Data::Parse(updated);
|
||||
parsed.maxCount = allowed.maxCount;
|
||||
if (const auto chat = peer->asChat()) {
|
||||
chat->setAllowedReactions(Data::Parse(updated));
|
||||
chat->setAllowedReactions(parsed);
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
channel->setAllowedReactions(Data::Parse(updated));
|
||||
channel->setAllowedReactions(parsed);
|
||||
} else {
|
||||
Unexpected("Invalid peer type in SaveAllowedReactions.");
|
||||
}
|
||||
|
||||
@@ -398,7 +398,7 @@ void PeerShortInfoCover::paintRadial(QPainter &p) {
|
||||
QImage PeerShortInfoCover::currentVideoFrame() const {
|
||||
const auto size = QSize(_st.size, _st.size);
|
||||
const auto request = Media::Streaming::FrameRequest{
|
||||
.resize = size * style::DevicePixelRatio(),
|
||||
.resize = size,
|
||||
.outer = size,
|
||||
};
|
||||
return (_videoInstance
|
||||
|
||||
@@ -286,7 +286,7 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
|
||||
document,
|
||||
media->videoThumbnailContent(),
|
||||
QString(),
|
||||
true);
|
||||
Stickers::EffectType::PremiumSticker);
|
||||
|
||||
const auto update = [=] {
|
||||
if (!state->readyInvoked
|
||||
@@ -913,7 +913,7 @@ void PreviewBox(
|
||||
auto businessOrder = Settings::BusinessFeaturesOrder(&show->session());
|
||||
state->order = ranges::contains(businessOrder, descriptor.section)
|
||||
? std::move(businessOrder)
|
||||
: ranges::contains(businessOrder, descriptor.section)
|
||||
: ranges::contains(premiumOrder, descriptor.section)
|
||||
? std::move(premiumOrder)
|
||||
: std::vector{ descriptor.section };
|
||||
|
||||
|
||||
@@ -16,8 +16,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/admin_log/history_admin_log_item.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "history/view/reactions/history_view_reactions_strip.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "history/view/history_view_fake_items.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "main/main_session.h"
|
||||
@@ -43,53 +44,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace {
|
||||
|
||||
PeerId GenerateUser(not_null<History*> history, const QString &name) {
|
||||
Expects(history->peer->isUser());
|
||||
|
||||
const auto peerId = Data::FakePeerIdForJustName(name);
|
||||
history->owner().processUser(MTP_user(
|
||||
MTP_flags(MTPDuser::Flag::f_first_name | MTPDuser::Flag::f_min),
|
||||
peerToBareMTPInt(peerId),
|
||||
MTP_long(0),
|
||||
MTP_string(tr::lng_settings_chat_message_reply_from(tr::now)),
|
||||
MTPstring(), // last name
|
||||
MTPstring(), // username
|
||||
MTPstring(), // phone
|
||||
MTPUserProfilePhoto(), // profile photo
|
||||
MTPUserStatus(), // status
|
||||
MTP_int(0), // bot info version
|
||||
MTPVector<MTPRestrictionReason>(), // restrictions
|
||||
MTPstring(), // bot placeholder
|
||||
MTPstring(), // lang code
|
||||
MTPEmojiStatus(),
|
||||
MTPVector<MTPUsername>(),
|
||||
MTPint(), // stories_max_id
|
||||
MTPPeerColor(), // color
|
||||
MTPPeerColor())); // profile_color
|
||||
return peerId;
|
||||
}
|
||||
|
||||
AdminLog::OwnedItem GenerateItem(
|
||||
not_null<HistoryView::ElementDelegate*> delegate,
|
||||
not_null<History*> history,
|
||||
PeerId from,
|
||||
FullMsgId replyTo,
|
||||
const QString &text) {
|
||||
Expects(history->peer->isUser());
|
||||
|
||||
const auto item = history->addNewLocalMessage({
|
||||
.id = history->nextNonHistoryEntryId(),
|
||||
.flags = (MessageFlag::FakeHistoryItem
|
||||
| MessageFlag::HasFromId
|
||||
| MessageFlag::HasReplyInfo),
|
||||
.from = from,
|
||||
.replyTo = FullReplyTo{ .messageId = replyTo },
|
||||
.date = base::unixtime::now(),
|
||||
}, TextWithEntities{ .text = text }, MTP_messageMediaEmpty());
|
||||
|
||||
return AdminLog::OwnedItem(delegate, item);
|
||||
}
|
||||
|
||||
void AddMessage(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<Window::SessionController*> controller,
|
||||
@@ -135,15 +89,15 @@ void AddMessage(
|
||||
|
||||
const auto history = controller->session().data().history(
|
||||
PeerData::kServiceNotificationsId);
|
||||
state->reply = GenerateItem(
|
||||
state->reply = HistoryView::GenerateItem(
|
||||
state->delegate.get(),
|
||||
history,
|
||||
GenerateUser(
|
||||
HistoryView::GenerateUser(
|
||||
history,
|
||||
tr::lng_settings_chat_message_reply_from(tr::now)),
|
||||
FullMsgId(),
|
||||
tr::lng_settings_chat_message_reply(tr::now));
|
||||
auto message = GenerateItem(
|
||||
auto message = HistoryView::GenerateItem(
|
||||
state->delegate.get(),
|
||||
history,
|
||||
history->peer->id,
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/send_credits_box.h"
|
||||
|
||||
#include "api/api_credits.h"
|
||||
#include "apiwrap.h"
|
||||
#include "core/ui_integration.h" // Core::MarkedTextContext.
|
||||
#include "data/data_credits.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "info/channel_statistics/boosts/giveaway/boost_badge.h" // InfiniteRadialAnimationWidget.
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "payments/payments_checkout_process.h"
|
||||
#include "payments/payments_form.h"
|
||||
#include "settings/settings_credits_graphics.h"
|
||||
#include "ui/controls/userpic_button.h"
|
||||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/effects/premium_top_bar.h" // Ui::Premium::ColorizedSvg.
|
||||
#include "ui/image/image_prepare.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_credits.h"
|
||||
#include "styles/style_giveaway.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_premium.h"
|
||||
#include "styles/style_settings.h"
|
||||
|
||||
namespace Ui {
|
||||
|
||||
void SendCreditsBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
std::shared_ptr<Payments::CreditsFormData> form,
|
||||
Fn<void()> sent) {
|
||||
if (!form) {
|
||||
return;
|
||||
}
|
||||
struct State {
|
||||
rpl::variable<bool> confirmButtonBusy = false;
|
||||
};
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
box->setStyle(st::giveawayGiftCodeBox);
|
||||
box->setNoContentMargin(true);
|
||||
|
||||
const auto session = form->invoice.session;
|
||||
|
||||
const auto photoSize = st::defaultUserpicButton.photoSize;
|
||||
|
||||
const auto content = box->verticalLayout();
|
||||
Ui::AddSkip(content, photoSize / 2);
|
||||
|
||||
{
|
||||
const auto ministarsContainer = Ui::CreateChild<Ui::RpWidget>(box);
|
||||
const auto fullHeight = photoSize * 2;
|
||||
using MiniStars = Ui::Premium::ColoredMiniStars;
|
||||
const auto ministars = box->lifetime().make_state<MiniStars>(
|
||||
ministarsContainer,
|
||||
false,
|
||||
Ui::Premium::MiniStars::Type::BiStars);
|
||||
ministars->setColorOverride(Ui::Premium::CreditsIconGradientStops());
|
||||
|
||||
ministarsContainer->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
auto p = QPainter(ministarsContainer);
|
||||
ministars->paint(p);
|
||||
}, ministarsContainer->lifetime());
|
||||
|
||||
box->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
ministarsContainer->resize(width, fullHeight);
|
||||
const auto w = fullHeight / 3 * 2;
|
||||
ministars->setCenter(QRect(
|
||||
(width - w) / 2,
|
||||
(fullHeight - w) / 2,
|
||||
w,
|
||||
w));
|
||||
}, ministarsContainer->lifetime());
|
||||
}
|
||||
|
||||
const auto bot = session->data().user(form->botId);
|
||||
|
||||
if (form->photo) {
|
||||
box->addRow(object_ptr<Ui::CenterWrap<>>(
|
||||
content,
|
||||
Settings::HistoryEntryPhoto(content, form->photo, photoSize)));
|
||||
} else {
|
||||
const auto widget = box->addRow(
|
||||
object_ptr<Ui::CenterWrap<>>(
|
||||
content,
|
||||
object_ptr<Ui::UserpicButton>(
|
||||
content,
|
||||
bot,
|
||||
st::defaultUserpicButton)));
|
||||
widget->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
}
|
||||
|
||||
Ui::AddSkip(content);
|
||||
box->addRow(object_ptr<Ui::CenterWrap<>>(
|
||||
box,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_credits_box_out_title(),
|
||||
st::settingsPremiumUserTitle)));
|
||||
Ui::AddSkip(content);
|
||||
box->addRow(object_ptr<Ui::CenterWrap<>>(
|
||||
box,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_credits_box_out_sure(
|
||||
lt_count,
|
||||
rpl::single(form->invoice.amount) | tr::to_count(),
|
||||
lt_text,
|
||||
rpl::single(TextWithEntities{ form->title }),
|
||||
lt_bot,
|
||||
rpl::single(TextWithEntities{ bot->name() }),
|
||||
Ui::Text::RichLangValue),
|
||||
st::creditsBoxAbout)));
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
|
||||
const auto button = box->addButton(rpl::single(QString()), [=] {
|
||||
if (state->confirmButtonBusy.current()) {
|
||||
return;
|
||||
}
|
||||
state->confirmButtonBusy = true;
|
||||
session->api().request(
|
||||
MTPpayments_SendStarsForm(
|
||||
MTP_flags(0),
|
||||
MTP_long(form->formId),
|
||||
form->inputInvoice)
|
||||
).done([=](auto result) {
|
||||
state->confirmButtonBusy = false;
|
||||
box->closeBox();
|
||||
sent();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
state->confirmButtonBusy = false;
|
||||
box->uiShow()->showToast(error.type());
|
||||
}).send();
|
||||
});
|
||||
{
|
||||
using namespace Info::Statistics;
|
||||
const auto loadingAnimation = InfiniteRadialAnimationWidget(
|
||||
button,
|
||||
st::giveawayGiftCodeStartButton.height / 2);
|
||||
AddChildToWidgetCenter(button.data(), loadingAnimation);
|
||||
loadingAnimation->showOn(state->confirmButtonBusy.value());
|
||||
}
|
||||
{
|
||||
const auto emojiMargin = QMargins(
|
||||
0,
|
||||
-st::moderateBoxExpandInnerSkip,
|
||||
0,
|
||||
0);
|
||||
const auto buttonEmoji = Ui::Text::SingleCustomEmoji(
|
||||
session->data().customEmojiManager().registerInternalEmoji(
|
||||
st::settingsPremiumIconStar,
|
||||
emojiMargin,
|
||||
true));
|
||||
auto buttonText = tr::lng_credits_box_out_confirm(
|
||||
lt_count,
|
||||
rpl::single(form->invoice.amount) | tr::to_count(),
|
||||
lt_emoji,
|
||||
rpl::single(buttonEmoji),
|
||||
Ui::Text::RichLangValue);
|
||||
const auto buttonLabel = Ui::CreateChild<Ui::FlatLabel>(
|
||||
button,
|
||||
rpl::single(QString()),
|
||||
st::defaultFlatLabel);
|
||||
std::move(
|
||||
buttonText
|
||||
) | rpl::start_with_next([=](const TextWithEntities &text) {
|
||||
buttonLabel->setMarkedText(
|
||||
text,
|
||||
Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = [=] { buttonLabel->update(); },
|
||||
});
|
||||
}, buttonLabel->lifetime());
|
||||
buttonLabel->setTextColorOverride(
|
||||
box->getDelegate()->style().button.textFg->c);
|
||||
button->sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &size) {
|
||||
buttonLabel->moveToLeft(
|
||||
(size.width() - buttonLabel->width()) / 2,
|
||||
(size.height() - buttonLabel->height()) / 2);
|
||||
}, buttonLabel->lifetime());
|
||||
buttonLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
state->confirmButtonBusy.value(
|
||||
) | rpl::start_with_next([=](bool busy) {
|
||||
buttonLabel->setVisible(!busy);
|
||||
}, buttonLabel->lifetime());
|
||||
}
|
||||
|
||||
const auto buttonWidth = st::boxWidth
|
||||
- rect::m::sum::h(st::giveawayGiftCodeBox.buttonPadding);
|
||||
button->widthValue() | rpl::filter([=] {
|
||||
return (button->widthNoMargins() != buttonWidth);
|
||||
}) | rpl::start_with_next([=] {
|
||||
button->resizeToWidth(buttonWidth);
|
||||
}, button->lifetime());
|
||||
|
||||
{
|
||||
const auto close = Ui::CreateChild<Ui::IconButton>(
|
||||
box.get(),
|
||||
st::boxTitleClose);
|
||||
close->setClickedCallback([=] {
|
||||
box->closeBox();
|
||||
});
|
||||
box->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
close->moveToRight(0, 0);
|
||||
close->raise();
|
||||
}, close->lifetime());
|
||||
}
|
||||
|
||||
{
|
||||
const auto balance = Settings::AddBalanceWidget(
|
||||
content,
|
||||
session->creditsValue(),
|
||||
false);
|
||||
const auto api = balance->lifetime().make_state<Api::CreditsStatus>(
|
||||
session->user());
|
||||
api->request({}, [=](Data::CreditsStatusSlice slice) {
|
||||
session->setCredits(slice.balance);
|
||||
});
|
||||
rpl::combine(
|
||||
balance->sizeValue(),
|
||||
content->sizeValue()
|
||||
) | rpl::start_with_next([=](const QSize &, const QSize &) {
|
||||
balance->moveToLeft(
|
||||
st::creditsHistoryRightSkip * 2,
|
||||
st::creditsHistoryRightSkip);
|
||||
balance->update();
|
||||
}, balance->lifetime());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
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
|
||||
|
||||
class HistoryItem;
|
||||
|
||||
namespace Payments {
|
||||
struct CreditsFormData;
|
||||
} // namespace Payments
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class GenericBox;
|
||||
|
||||
void SendCreditsBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
std::shared_ptr<Payments::CreditsFormData> data,
|
||||
Fn<void()> sent);
|
||||
|
||||
} // namespace Ui
|
||||
@@ -336,7 +336,7 @@ SendFilesBox::SendFilesBox(
|
||||
const TextWithTags &caption,
|
||||
not_null<PeerData*> toPeer,
|
||||
Api::SendType sendType,
|
||||
SendMenu::Type sendMenuType)
|
||||
SendMenu::Details sendMenuDetails)
|
||||
: SendFilesBox(nullptr, {
|
||||
.show = controller->uiShow(),
|
||||
.list = std::move(list),
|
||||
@@ -345,7 +345,7 @@ SendFilesBox::SendFilesBox(
|
||||
.limits = DefaultLimitsForPeer(toPeer),
|
||||
.check = DefaultCheckForPeer(controller, toPeer),
|
||||
.sendType = sendType,
|
||||
.sendMenuType = sendMenuType,
|
||||
.sendMenuDetails = [=] { return sendMenuDetails; },
|
||||
}) {
|
||||
}
|
||||
|
||||
@@ -358,7 +358,8 @@ SendFilesBox::SendFilesBox(QWidget*, SendFilesBoxDescriptor &&descriptor)
|
||||
, _titleHeight(st::boxTitleHeight)
|
||||
, _list(std::move(descriptor.list))
|
||||
, _limits(descriptor.limits)
|
||||
, _sendMenuType(descriptor.sendMenuType)
|
||||
, _sendMenuDetails(prepareSendMenuDetails(descriptor))
|
||||
, _sendMenuCallback(prepareSendMenuCallback())
|
||||
, _captionToPeer(descriptor.captionToPeer)
|
||||
, _check(std::move(descriptor.check))
|
||||
, _confirmedCallback(std::move(descriptor.confirmed))
|
||||
@@ -372,6 +373,50 @@ SendFilesBox::SendFilesBox(QWidget*, SendFilesBoxDescriptor &&descriptor)
|
||||
enqueueNextPrepare();
|
||||
}
|
||||
|
||||
Fn<SendMenu::Details()> SendFilesBox::prepareSendMenuDetails(
|
||||
const SendFilesBoxDescriptor &descriptor) {
|
||||
auto initial = descriptor.sendMenuDetails;
|
||||
return crl::guard(this, [=] {
|
||||
auto result = initial ? initial() : SendMenu::Details();
|
||||
result.spoiler = !hasSpoilerMenu()
|
||||
? SendMenu::SpoilerState::None
|
||||
: allWithSpoilers()
|
||||
? SendMenu::SpoilerState::Enabled
|
||||
: SendMenu::SpoilerState::Possible;
|
||||
const auto way = _sendWay.current();
|
||||
const auto canMoveCaption = _list.canMoveCaption(
|
||||
way.groupFiles() && way.sendImagesAsPhotos(),
|
||||
way.sendImagesAsPhotos()
|
||||
) && _caption && HasSendText(_caption);
|
||||
result.caption = !canMoveCaption
|
||||
? SendMenu::CaptionState::None
|
||||
: _invertCaption
|
||||
? SendMenu::CaptionState::Above
|
||||
: SendMenu::CaptionState::Below;
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
auto SendFilesBox::prepareSendMenuCallback()
|
||||
-> Fn<void(MenuAction, MenuDetails)> {
|
||||
return crl::guard(this, [=](MenuAction action, MenuDetails details) {
|
||||
using Type = SendMenu::ActionType;
|
||||
switch (action.type) {
|
||||
case Type::CaptionDown: _invertCaption = false; break;
|
||||
case Type::CaptionUp: _invertCaption = true; break;
|
||||
case Type::SpoilerOn: toggleSpoilers(true); break;
|
||||
case Type::SpoilerOff: toggleSpoilers(false); break;
|
||||
default:
|
||||
SendMenu::DefaultCallback(
|
||||
_show,
|
||||
sendCallback())(
|
||||
action,
|
||||
details);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SendFilesBox::initPreview() {
|
||||
using namespace rpl::mappers;
|
||||
|
||||
@@ -537,10 +582,9 @@ void SendFilesBox::refreshButtons() {
|
||||
if (_sendType == Api::SendType::Normal) {
|
||||
SendMenu::SetupMenuAndShortcuts(
|
||||
_send,
|
||||
[=] { return _sendMenuType; },
|
||||
[=] { sendSilent(); },
|
||||
[=] { sendScheduled(); },
|
||||
[=] { sendWhenOnline(); });
|
||||
_show,
|
||||
_sendMenuDetails,
|
||||
_sendMenuCallback);
|
||||
}
|
||||
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||
_addFile = addLeftButton(
|
||||
@@ -552,21 +596,14 @@ void SendFilesBox::refreshButtons() {
|
||||
addMenuButton();
|
||||
}
|
||||
|
||||
bool SendFilesBox::hasSendMenu() const {
|
||||
return (_sendMenuType != SendMenu::Type::Disabled);
|
||||
bool SendFilesBox::hasSendMenu(const SendMenu::Details &details) const {
|
||||
return (details.type != SendMenu::Type::Disabled)
|
||||
|| (details.spoiler != SendMenu::SpoilerState::None)
|
||||
|| (details.caption != SendMenu::CaptionState::None);
|
||||
}
|
||||
|
||||
bool SendFilesBox::hasSpoilerMenu() const {
|
||||
const auto allAreVideo = !ranges::any_of(_list.files, [](const auto &f) {
|
||||
using Type = Ui::PreparedFile::Type;
|
||||
return (f.type != Type::Video);
|
||||
});
|
||||
const auto allAreMedia = !ranges::any_of(_list.files, [](const auto &f) {
|
||||
using Type = Ui::PreparedFile::Type;
|
||||
return (f.type != Type::Photo) && (f.type != Type::Video);
|
||||
});
|
||||
return allAreVideo
|
||||
|| (allAreMedia && _sendWay.current().sendImagesAsPhotos());
|
||||
return _list.hasSpoilerMenu(_sendWay.current().sendImagesAsPhotos());
|
||||
}
|
||||
|
||||
void SendFilesBox::applyBlockChanges() {
|
||||
@@ -590,36 +627,23 @@ void SendFilesBox::toggleSpoilers(bool enabled) {
|
||||
}
|
||||
|
||||
void SendFilesBox::addMenuButton() {
|
||||
if (!hasSendMenu() && !hasSpoilerMenu()) {
|
||||
const auto details = _sendMenuDetails();
|
||||
if (!hasSendMenu(details)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto top = addTopButton(_st.files.menu);
|
||||
top->setClickedCallback([=] {
|
||||
const auto &tabbed = _st.tabbed;
|
||||
const auto &icons = tabbed.icons;
|
||||
_menu = base::make_unique_q<Ui::PopupMenu>(top, tabbed.menu);
|
||||
if (hasSpoilerMenu()) {
|
||||
const auto spoilered = allWithSpoilers();
|
||||
_menu->addAction(
|
||||
(spoilered
|
||||
? tr::lng_context_disable_spoiler(tr::now)
|
||||
: tr::lng_context_spoiler_effect(tr::now)),
|
||||
[=] { toggleSpoilers(!spoilered); },
|
||||
spoilered ? &icons.menuSpoilerOff : &icons.menuSpoiler);
|
||||
if (hasSendMenu()) {
|
||||
_menu->addSeparator(&tabbed.expandedSeparator);
|
||||
}
|
||||
}
|
||||
if (hasSendMenu()) {
|
||||
SendMenu::FillSendMenu(
|
||||
_menu.get(),
|
||||
_sendMenuType,
|
||||
[=] { sendSilent(); },
|
||||
[=] { sendScheduled(); },
|
||||
[=] { sendWhenOnline(); },
|
||||
&_st.tabbed.icons);
|
||||
}
|
||||
const auto position = QCursor::pos();
|
||||
SendMenu::FillSendMenu(
|
||||
_menu.get(),
|
||||
_show,
|
||||
_sendMenuDetails(),
|
||||
_sendMenuCallback,
|
||||
&_st.tabbed.icons,
|
||||
position);
|
||||
|
||||
using ImageInfo = Ui::PreparedFileInformation::Image;
|
||||
if (_list.files.size() == 1 && std::get_if<ImageInfo>(&_list.files[0].information->media)) {
|
||||
@@ -650,10 +674,9 @@ void SendFilesBox::addMenuButton() {
|
||||
},
|
||||
&st::menuIconStickers);
|
||||
}
|
||||
_menu->popup(QCursor::pos());
|
||||
_menu->popup(position);
|
||||
return true;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
void SendFilesBox::initSendWay() {
|
||||
@@ -695,9 +718,7 @@ void SendFilesBox::initSendWay() {
|
||||
for (auto &block : _blocks) {
|
||||
block.setSendWay(value);
|
||||
}
|
||||
if (!hasSendMenu()) {
|
||||
refreshButtons();
|
||||
}
|
||||
refreshButtons();
|
||||
if (was != hidden()) {
|
||||
updateBoxSize();
|
||||
updateControlsGeometry();
|
||||
@@ -909,9 +930,7 @@ void SendFilesBox::pushBlock(int from, int till) {
|
||||
}
|
||||
|
||||
void SendFilesBox::refreshControls(bool initial) {
|
||||
if (initial || !hasSendMenu()) {
|
||||
refreshButtons();
|
||||
}
|
||||
refreshButtons();
|
||||
refreshTitleText();
|
||||
updateSendWayControls();
|
||||
updateCaptionPlaceholder();
|
||||
@@ -1477,7 +1496,12 @@ void SendFilesBox::send(
|
||||
if ((_sendType == Api::SendType::Scheduled
|
||||
|| _sendType == Api::SendType::ScheduledToUser)
|
||||
&& !options.scheduled) {
|
||||
return sendScheduled();
|
||||
auto child = _sendMenuDetails();
|
||||
child.spoiler = SendMenu::SpoilerState::None;
|
||||
child.caption = SendMenu::CaptionState::None;
|
||||
return SendMenu::DefaultCallback(_show, sendCallback())(
|
||||
{ .type = SendMenu::ActionType::Schedule },
|
||||
child);
|
||||
}
|
||||
if (_preparing) {
|
||||
_whenReadySend = [=] {
|
||||
@@ -1502,6 +1526,7 @@ void SendFilesBox::send(
|
||||
auto caption = (_caption && !_caption->isHidden())
|
||||
? _caption->getTextWithAppliedMarkdown()
|
||||
: TextWithTags();
|
||||
options.invertCaption = _invertCaption;
|
||||
if (!validateLength(caption.text)) {
|
||||
return;
|
||||
}
|
||||
@@ -1515,25 +1540,10 @@ void SendFilesBox::send(
|
||||
closeBox();
|
||||
}
|
||||
|
||||
void SendFilesBox::sendSilent() {
|
||||
send({ .silent = true });
|
||||
}
|
||||
|
||||
void SendFilesBox::sendScheduled() {
|
||||
const auto type = (_sendType == Api::SendType::ScheduledToUser)
|
||||
? SendMenu::Type::ScheduledToUser
|
||||
: _sendMenuType;
|
||||
const auto callback = [=](Api::SendOptions options) { send(options); };
|
||||
auto box = HistoryView::PrepareScheduleBox(this, type, callback);
|
||||
const auto weak = Ui::MakeWeak(box.data());
|
||||
_show->showBox(std::move(box));
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->setCloseByOutsideClick(false);
|
||||
}
|
||||
}
|
||||
|
||||
void SendFilesBox::sendWhenOnline() {
|
||||
send(Api::DefaultSendWhenOnlineOptions());
|
||||
Fn<void(Api::SendOptions)> SendFilesBox::sendCallback() {
|
||||
return crl::guard(this, [=](Api::SendOptions options) {
|
||||
send(options, false);
|
||||
});
|
||||
}
|
||||
|
||||
SendFilesBox::~SendFilesBox() = default;
|
||||
|
||||
@@ -47,7 +47,8 @@ class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace SendMenu {
|
||||
enum class Type;
|
||||
struct Details;
|
||||
struct Action;
|
||||
} // namespace SendMenu
|
||||
|
||||
namespace HistoryView::Controls {
|
||||
@@ -96,7 +97,7 @@ struct SendFilesBoxDescriptor {
|
||||
SendFilesLimits limits = {};
|
||||
SendFilesCheck check;
|
||||
Api::SendType sendType = {};
|
||||
SendMenu::Type sendMenuType = {};
|
||||
Fn<SendMenu::Details()> sendMenuDetails = nullptr;
|
||||
const style::ComposeControls *stOverride = nullptr;
|
||||
SendFilesConfirmed confirmed;
|
||||
Fn<void()> cancelled;
|
||||
@@ -115,7 +116,7 @@ public:
|
||||
const TextWithTags &caption,
|
||||
not_null<PeerData*> toPeer,
|
||||
Api::SendType sendType,
|
||||
SendMenu::Type sendMenuType);
|
||||
SendMenu::Details sendMenuDetails);
|
||||
SendFilesBox(QWidget*, SendFilesBoxDescriptor &&descriptor);
|
||||
|
||||
void setConfirmedCallback(SendFilesConfirmed callback) {
|
||||
@@ -136,6 +137,9 @@ protected:
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
private:
|
||||
using MenuAction = SendMenu::Action;
|
||||
using MenuDetails = SendMenu::Details;
|
||||
|
||||
class Block final {
|
||||
public:
|
||||
Block(
|
||||
@@ -173,7 +177,7 @@ private:
|
||||
|
||||
void initSendWay();
|
||||
void initPreview();
|
||||
[[nodiscard]] bool hasSendMenu() const;
|
||||
[[nodiscard]] bool hasSendMenu(const MenuDetails &details) const;
|
||||
[[nodiscard]] bool hasSpoilerMenu() const;
|
||||
[[nodiscard]] bool allWithSpoilers();
|
||||
[[nodiscard]] bool checkWithWay(
|
||||
@@ -202,9 +206,7 @@ private:
|
||||
void generatePreviewFrom(int fromBlock);
|
||||
|
||||
void send(Api::SendOptions options, bool ctrlShiftEnter = false);
|
||||
void sendSilent();
|
||||
void sendScheduled();
|
||||
void sendWhenOnline();
|
||||
[[nodiscard]] Fn<void(Api::SendOptions)> sendCallback();
|
||||
void captionResized();
|
||||
void saveSendWaySettings();
|
||||
|
||||
@@ -227,6 +229,11 @@ private:
|
||||
|
||||
void checkCharsLimitation();
|
||||
|
||||
[[nodiscard]] Fn<MenuDetails()> prepareSendMenuDetails(
|
||||
const SendFilesBoxDescriptor &descriptor);
|
||||
[[nodiscard]] auto prepareSendMenuCallback()
|
||||
-> Fn<void(MenuAction, MenuDetails)>;
|
||||
|
||||
const std::shared_ptr<ChatHelpers::Show> _show;
|
||||
const style::ComposeControls &_st;
|
||||
const Api::SendType _sendType = Api::SendType();
|
||||
@@ -238,12 +245,14 @@ private:
|
||||
std::optional<int> _removingIndex;
|
||||
|
||||
SendFilesLimits _limits = {};
|
||||
SendMenu::Type _sendMenuType = {};
|
||||
Fn<MenuDetails()> _sendMenuDetails;
|
||||
Fn<void(MenuAction, MenuDetails)> _sendMenuCallback;
|
||||
PeerData *_captionToPeer = nullptr;
|
||||
SendFilesCheck _check;
|
||||
SendFilesConfirmed _confirmedCallback;
|
||||
Fn<void()> _cancelledCallback;
|
||||
bool _confirmed = false;
|
||||
bool _invertCaption = false;
|
||||
|
||||
object_ptr<Ui::InputField> _caption = { nullptr };
|
||||
TextWithTags _prefilledCaptionText;
|
||||
|
||||
@@ -239,7 +239,10 @@ void ShareBox::prepareCommentField() {
|
||||
const auto field = _comment->entity();
|
||||
|
||||
field->submits(
|
||||
) | rpl::start_with_next([=] { submit({}); }, field->lifetime());
|
||||
) | rpl::start_with_next([=] {
|
||||
submit({});
|
||||
}, field->lifetime());
|
||||
|
||||
if (const auto show = uiShow(); show->valid()) {
|
||||
InitMessageFieldHandlers(
|
||||
_descriptor.session,
|
||||
@@ -251,6 +254,14 @@ void ShareBox::prepareCommentField() {
|
||||
}
|
||||
field->setSubmitSettings(Core::App().settings().sendSubmitWay());
|
||||
|
||||
field->changes() | rpl::start_with_next([=] {
|
||||
if (!field->getLastText().isEmpty()) {
|
||||
setCloseByOutsideClick(false);
|
||||
} else if (_inner->selected().empty()) {
|
||||
setCloseByOutsideClick(true);
|
||||
}
|
||||
}, field->lifetime());
|
||||
|
||||
Ui::SendPendingMoveResizeEvents(_comment);
|
||||
if (_bottomWidget) {
|
||||
Ui::SendPendingMoveResizeEvents(_bottomWidget);
|
||||
@@ -260,8 +271,6 @@ void ShareBox::prepareCommentField() {
|
||||
void ShareBox::prepare() {
|
||||
prepareCommentField();
|
||||
|
||||
setCloseByOutsideClick(false);
|
||||
|
||||
_select->resizeToWidth(st::boxWideWidth);
|
||||
Ui::SendPendingMoveResizeEvents(_select);
|
||||
|
||||
@@ -318,6 +327,12 @@ void ShareBox::prepare() {
|
||||
not_null<Data::Thread*> thread,
|
||||
bool checked) {
|
||||
innerSelectedChanged(thread, checked);
|
||||
if (checked) {
|
||||
setCloseByOutsideClick(false);
|
||||
} else if (_inner->selected().empty()
|
||||
&& _comment->entity()->getLastText().isEmpty()) {
|
||||
setCloseByOutsideClick(true);
|
||||
}
|
||||
});
|
||||
|
||||
Ui::Emoji::SuggestionsController::Init(
|
||||
@@ -463,15 +478,18 @@ void ShareBox::keyPressEvent(QKeyEvent *e) {
|
||||
}
|
||||
}
|
||||
|
||||
SendMenu::Type ShareBox::sendMenuType() const {
|
||||
SendMenu::Details ShareBox::sendMenuDetails() const {
|
||||
const auto selected = _inner->selected();
|
||||
return ranges::all_of(
|
||||
const auto type = ranges::all_of(
|
||||
selected | ranges::views::transform(&Data::Thread::peer),
|
||||
HistoryView::CanScheduleUntilOnline)
|
||||
? SendMenu::Type::ScheduledToUser
|
||||
: (selected.size() == 1 && selected.front()->peer()->isSelf())
|
||||
? SendMenu::Type::Reminder
|
||||
: SendMenu::Type::Scheduled;
|
||||
|
||||
// We can't support effect here because we don't have ChatHelpers::Show.
|
||||
return { .type = type, .effectAllowed = false };
|
||||
}
|
||||
|
||||
void ShareBox::showMenu(not_null<Ui::RpWidget*> parent) {
|
||||
@@ -508,15 +526,32 @@ void ShareBox::showMenu(not_null<Ui::RpWidget*> parent) {
|
||||
_menu->addSeparator();
|
||||
}
|
||||
|
||||
const auto result = SendMenu::FillSendMenu(
|
||||
using namespace SendMenu;
|
||||
const auto sendAction = crl::guard(this, [=](Action action, Details) {
|
||||
if (action.type == ActionType::Send) {
|
||||
submit(action.options);
|
||||
return;
|
||||
}
|
||||
uiShow()->showBox(
|
||||
HistoryView::PrepareScheduleBox(
|
||||
this,
|
||||
nullptr, // ChatHelpers::Show for effect attachment.
|
||||
sendMenuDetails(),
|
||||
[=](Api::SendOptions options) { submit(options); },
|
||||
action.options,
|
||||
HistoryView::DefaultScheduleTime(),
|
||||
_descriptor.scheduleBoxStyle));
|
||||
});
|
||||
_menu->setForcedVerticalOrigin(Ui::PopupMenu::VerticalOrigin::Bottom);
|
||||
const auto result = FillSendMenu(
|
||||
_menu.get(),
|
||||
sendMenuType(),
|
||||
[=] { submitSilent(); },
|
||||
[=] { submitScheduled(); },
|
||||
[=] { submitWhenOnline(); });
|
||||
const auto success = (result == SendMenu::FillMenuResult::Success);
|
||||
if (_descriptor.forwardOptions.show || success) {
|
||||
_menu->setForcedVerticalOrigin(Ui::PopupMenu::VerticalOrigin::Bottom);
|
||||
nullptr, // showForEffect.
|
||||
sendMenuDetails(),
|
||||
sendAction);
|
||||
if (result == SendMenu::FillMenuResult::Prepared) {
|
||||
_menu->popupPrepared();
|
||||
} else if (_descriptor.forwardOptions.show
|
||||
&& result != SendMenu::FillMenuResult::Failed) {
|
||||
_menu->popup(QCursor::pos());
|
||||
}
|
||||
}
|
||||
@@ -597,25 +632,6 @@ void ShareBox::submit(Api::SendOptions options) {
|
||||
}
|
||||
}
|
||||
|
||||
void ShareBox::submitSilent() {
|
||||
submit({ .silent = true });
|
||||
}
|
||||
|
||||
void ShareBox::submitScheduled() {
|
||||
const auto callback = [=](Api::SendOptions options) { submit(options); };
|
||||
uiShow()->showBox(
|
||||
HistoryView::PrepareScheduleBox(
|
||||
this,
|
||||
sendMenuType(),
|
||||
callback,
|
||||
HistoryView::DefaultScheduleTime(),
|
||||
_descriptor.scheduleBoxStyle));
|
||||
}
|
||||
|
||||
void ShareBox::submitWhenOnline() {
|
||||
submit(Api::DefaultSendWhenOnlineOptions());
|
||||
}
|
||||
|
||||
void ShareBox::copyLink() const {
|
||||
if (const auto onstack = _descriptor.copyCallback) {
|
||||
onstack();
|
||||
@@ -1690,6 +1706,101 @@ void FastShareMessage(
|
||||
Ui::LayerOption::CloseOther);
|
||||
}
|
||||
|
||||
void FastShareLink(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const QString &url) {
|
||||
FastShareLink(controller->uiShow(), url);
|
||||
}
|
||||
|
||||
void FastShareLink(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
const QString &url) {
|
||||
const auto box = std::make_shared<QPointer<Ui::BoxContent>>();
|
||||
const auto sending = std::make_shared<bool>();
|
||||
auto copyCallback = [=] {
|
||||
QGuiApplication::clipboard()->setText(url);
|
||||
show->showToast(tr::lng_background_link_copied(tr::now));
|
||||
};
|
||||
auto submitCallback = [=](
|
||||
std::vector<not_null<::Data::Thread*>> &&result,
|
||||
TextWithTags &&comment,
|
||||
Api::SendOptions options,
|
||||
::Data::ForwardOptions) {
|
||||
if (*sending || result.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto error = [&] {
|
||||
for (const auto thread : result) {
|
||||
const auto error = GetErrorTextForSending(
|
||||
thread,
|
||||
{ .text = &comment });
|
||||
if (!error.isEmpty()) {
|
||||
return std::make_pair(error, thread);
|
||||
}
|
||||
}
|
||||
return std::make_pair(QString(), result.front());
|
||||
}();
|
||||
if (!error.first.isEmpty()) {
|
||||
auto text = TextWithEntities();
|
||||
if (result.size() > 1) {
|
||||
text.append(
|
||||
Ui::Text::Bold(error.second->chatListName())
|
||||
).append("\n\n");
|
||||
}
|
||||
text.append(error.first);
|
||||
if (const auto weak = *box) {
|
||||
weak->getDelegate()->show(Ui::MakeConfirmBox({
|
||||
.text = text,
|
||||
.inform = true,
|
||||
}));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
*sending = true;
|
||||
if (!comment.text.isEmpty()) {
|
||||
comment.text = url + "\n" + comment.text;
|
||||
const auto add = url.size() + 1;
|
||||
for (auto &tag : comment.tags) {
|
||||
tag.offset += add;
|
||||
}
|
||||
} else {
|
||||
comment.text = url;
|
||||
}
|
||||
auto &api = show->session().api();
|
||||
for (const auto thread : result) {
|
||||
auto message = Api::MessageToSend(
|
||||
Api::SendAction(thread, options));
|
||||
message.textWithTags = comment;
|
||||
message.action.clearDraft = false;
|
||||
api.sendMessage(std::move(message));
|
||||
}
|
||||
if (*box) {
|
||||
(*box)->closeBox();
|
||||
}
|
||||
show->showToast(tr::lng_share_done(tr::now));
|
||||
};
|
||||
auto filterCallback = [](not_null<::Data::Thread*> thread) {
|
||||
if (const auto user = thread->peer()->asUser()) {
|
||||
if (user->canSendIgnoreRequirePremium()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return ::Data::CanSend(thread, ChatRestriction::SendOther);
|
||||
};
|
||||
*box = show->show(
|
||||
Box<ShareBox>(ShareBox::Descriptor{
|
||||
.session = &show->session(),
|
||||
.copyCallback = std::move(copyCallback),
|
||||
.submitCallback = std::move(submitCallback),
|
||||
.filterCallback = std::move(filterCallback),
|
||||
.premiumRequiredError = SharePremiumRequiredError(),
|
||||
}),
|
||||
Ui::LayerOption::KeepOther,
|
||||
anim::type::normal);
|
||||
}
|
||||
|
||||
auto SharePremiumRequiredError()
|
||||
-> Fn<RecipientPremiumRequiredError(not_null<UserData*>)> {
|
||||
return WritePremiumRequiredError;
|
||||
|
||||
@@ -24,7 +24,7 @@ struct PeerList;
|
||||
} // namespace style
|
||||
|
||||
namespace SendMenu {
|
||||
enum class Type;
|
||||
struct Details;
|
||||
} // namespace SendMenu
|
||||
|
||||
namespace Window {
|
||||
@@ -37,6 +37,7 @@ struct SendOptions;
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
class SessionShow;
|
||||
} // namespace Main
|
||||
|
||||
namespace Dialogs {
|
||||
@@ -68,6 +69,12 @@ void ShareGameScoreByHash(
|
||||
void FastShareMessage(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<HistoryItem*> item);
|
||||
void FastShareLink(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const QString &url);
|
||||
void FastShareLink(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
const QString &url);
|
||||
|
||||
struct RecipientPremiumRequiredError;
|
||||
[[nodiscard]] auto SharePremiumRequiredError()
|
||||
@@ -123,13 +130,10 @@ private:
|
||||
void scrollAnimationCallback();
|
||||
|
||||
void submit(Api::SendOptions options);
|
||||
void submitSilent();
|
||||
void submitScheduled();
|
||||
void submitWhenOnline();
|
||||
void copyLink() const;
|
||||
bool searchByUsername(bool useCache = false);
|
||||
|
||||
SendMenu::Type sendMenuType() const;
|
||||
[[nodiscard]] SendMenu::Details sendMenuDetails() const;
|
||||
|
||||
void scrollTo(Ui::ScrollToRequest request);
|
||||
void needSearchByUsername();
|
||||
|
||||
@@ -79,7 +79,9 @@ using Data::StickersSet;
|
||||
using Data::StickersPack;
|
||||
using SetFlag = Data::StickersSetFlag;
|
||||
|
||||
[[nodiscard]] std::optional<QColor> ComputeImageColor(const QImage &frame) {
|
||||
[[nodiscard]] std::optional<QColor> ComputeImageColor(
|
||||
const style::icon &lockIcon,
|
||||
const QImage &frame) {
|
||||
if (frame.isNull()
|
||||
|| frame.format() != QImage::Format_ARGB32_Premultiplied) {
|
||||
return {};
|
||||
@@ -89,7 +91,7 @@ using SetFlag = Data::StickersSetFlag;
|
||||
auto sb = int64();
|
||||
auto sa = int64();
|
||||
const auto factor = frame.devicePixelRatio();
|
||||
const auto size = st::stickersPremiumLock.size() * factor;
|
||||
const auto size = lockIcon.size() * factor;
|
||||
const auto width = std::min(frame.width(), size.width());
|
||||
const auto height = std::min(frame.height(), size.height());
|
||||
const auto skipx = (frame.width() - width) / 2;
|
||||
@@ -116,22 +118,30 @@ using SetFlag = Data::StickersSetFlag;
|
||||
|
||||
}
|
||||
|
||||
[[nodiscard]] QColor ComputeLockColor(const QImage &frame) {
|
||||
return ComputeImageColor(frame).value_or(st::windowSubTextFg->c);
|
||||
[[nodiscard]] QColor ComputeLockColor(
|
||||
const style::icon &lockIcon,
|
||||
const QImage &frame) {
|
||||
return ComputeImageColor(
|
||||
lockIcon,
|
||||
frame
|
||||
).value_or(st::windowSubTextFg->c);
|
||||
}
|
||||
|
||||
void ValidatePremiumLockBg(QImage &image, const QImage &frame) {
|
||||
void ValidatePremiumLockBg(
|
||||
const style::icon &lockIcon,
|
||||
QImage &image,
|
||||
const QImage &frame) {
|
||||
if (!image.isNull()) {
|
||||
return;
|
||||
}
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto size = st::stickersPremiumLock.size();
|
||||
const auto size = lockIcon.size();
|
||||
image = QImage(
|
||||
size * factor,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
image.setDevicePixelRatio(factor);
|
||||
auto p = QPainter(&image);
|
||||
const auto color = ComputeLockColor(frame);
|
||||
const auto color = ComputeLockColor(lockIcon, frame);
|
||||
p.fillRect(
|
||||
QRect(QPoint(), size),
|
||||
anim::color(color, st::windowSubTextFg, kGrayLockOpacity));
|
||||
@@ -140,12 +150,12 @@ void ValidatePremiumLockBg(QImage &image, const QImage &frame) {
|
||||
image = Images::Circle(std::move(image));
|
||||
}
|
||||
|
||||
void ValidatePremiumStarFg(QImage &image) {
|
||||
void ValidatePremiumStarFg(const style::icon &lockIcon, QImage &image) {
|
||||
if (!image.isNull()) {
|
||||
return;
|
||||
}
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto size = st::stickersPremiumLock.size();
|
||||
const auto size = lockIcon.size();
|
||||
image = QImage(
|
||||
size * factor,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
@@ -182,7 +192,10 @@ void ValidatePremiumStarFg(QImage &image) {
|
||||
|
||||
} // namespace
|
||||
|
||||
StickerPremiumMark::StickerPremiumMark(not_null<Main::Session*> session) {
|
||||
StickerPremiumMark::StickerPremiumMark(
|
||||
not_null<Main::Session*> session,
|
||||
const style::icon &lockIcon)
|
||||
: _lockIcon(lockIcon) {
|
||||
style::PaletteChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
_lockGray = QImage();
|
||||
@@ -208,16 +221,14 @@ void StickerPremiumMark::paint(
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto radius = st::roundRadiusSmall;
|
||||
const auto point = position + QPoint(
|
||||
(_premium
|
||||
? (singleSize.width() - (bg.width() / factor) - radius)
|
||||
: (singleSize.width() - (bg.width() / factor)) / 2),
|
||||
(singleSize.width() - (bg.width() / factor) - radius),
|
||||
singleSize.height() - (bg.height() / factor) - radius);
|
||||
p.drawImage(point, bg);
|
||||
if (_premium) {
|
||||
validateStar();
|
||||
p.drawImage(point, _star);
|
||||
} else {
|
||||
st::stickersPremiumLock.paint(p, point, outerWidth);
|
||||
_lockIcon.paint(p, point, outerWidth);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,11 +236,11 @@ void StickerPremiumMark::validateLock(
|
||||
const QImage &frame,
|
||||
QImage &backCache) {
|
||||
auto &image = frame.isNull() ? _lockGray : backCache;
|
||||
ValidatePremiumLockBg(image, frame);
|
||||
ValidatePremiumLockBg(_lockIcon, image, frame);
|
||||
}
|
||||
|
||||
void StickerPremiumMark::validateStar() {
|
||||
ValidatePremiumStarFg(_star);
|
||||
ValidatePremiumStarFg(_lockIcon, _star);
|
||||
}
|
||||
|
||||
class StickerSetBox::Inner final : public Ui::RpWidget {
|
||||
@@ -543,9 +554,9 @@ void StickerSetBox::updateButtons() {
|
||||
? tr::lng_stickers_copied_emoji(tr::now)
|
||||
: tr::lng_stickers_copied(tr::now));
|
||||
};
|
||||
const auto addAuthorPack = [=](const std::shared_ptr<base::unique_qptr<Ui::PopupMenu>> &menu)
|
||||
const auto addPackOwner = [=](const std::shared_ptr<base::unique_qptr<Ui::PopupMenu>> &menu)
|
||||
{
|
||||
if (type == Data::StickersType::Stickers) {
|
||||
if (type == Data::StickersType::Stickers || type == Data::StickersType::Emoji) {
|
||||
const auto pointer = Ui::MakeWeak(this);
|
||||
(*menu)->addAction(
|
||||
tr::ayu_MessageDetailsPackOwnerPC(tr::now),
|
||||
@@ -625,7 +636,7 @@ void StickerSetBox::updateButtons() {
|
||||
: tr::lng_stickers_share_pack)(tr::now),
|
||||
[=] { share(); closeBox(); },
|
||||
&st::menuIconShare);
|
||||
addAuthorPack(menu);
|
||||
addPackOwner(menu);
|
||||
(*menu)->popup(QCursor::pos());
|
||||
return true;
|
||||
});
|
||||
@@ -675,7 +686,7 @@ void StickerSetBox::updateButtons() {
|
||||
archive,
|
||||
&st::menuIconArchive);
|
||||
}
|
||||
addAuthorPack(menu);
|
||||
addPackOwner(menu);
|
||||
(*menu)->popup(QCursor::pos());
|
||||
return true;
|
||||
});
|
||||
@@ -708,7 +719,7 @@ StickerSetBox::Inner::Inner(
|
||||
st::windowBgRipple,
|
||||
st::windowBgOver,
|
||||
[=] { repaintItems(); }))
|
||||
, _premiumMark(_session)
|
||||
, _premiumMark(_session, st::stickersPremiumLock)
|
||||
, _updateItemsTimer([=] { updateItems(); })
|
||||
, _input(set)
|
||||
, _padding((type == Data::StickersType::Emoji)
|
||||
@@ -1058,7 +1069,7 @@ void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) {
|
||||
_menu = base::make_unique_q<Ui::PopupMenu>(
|
||||
this,
|
||||
st::popupMenuWithIcons);
|
||||
const auto type = _show->sendMenuType();
|
||||
const auto details = _show->sendMenuDetails();
|
||||
if (setType() == Data::StickersType::Emoji) {
|
||||
if (const auto t = PrepareTextFromEmoji(_pack[index]); !t.empty()) {
|
||||
_menu->addAction(tr::lng_mediaview_copy(tr::now), [=] {
|
||||
@@ -1067,17 +1078,16 @@ void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) {
|
||||
}
|
||||
}, &st::menuIconCopy);
|
||||
}
|
||||
} else if (type != SendMenu::Type::Disabled) {
|
||||
} else if (details.type != SendMenu::Type::Disabled) {
|
||||
const auto document = _pack[index];
|
||||
const auto sendSelected = [=](Api::SendOptions options) {
|
||||
const auto send = crl::guard(this, [=](Api::SendOptions options) {
|
||||
chosen(index, document, options);
|
||||
};
|
||||
});
|
||||
SendMenu::FillSendMenu(
|
||||
_menu.get(),
|
||||
type,
|
||||
SendMenu::DefaultSilentCallback(sendSelected),
|
||||
SendMenu::DefaultScheduleCallback(this, type, sendSelected),
|
||||
SendMenu::DefaultWhenOnlineCallback(sendSelected));
|
||||
_show,
|
||||
details,
|
||||
SendMenu::DefaultCallback(_show, send));
|
||||
|
||||
const auto show = _show;
|
||||
const auto toggleFavedSticker = [=] {
|
||||
|
||||
@@ -23,10 +23,6 @@ namespace Data {
|
||||
class StickersSet;
|
||||
} // namespace Data
|
||||
|
||||
namespace SendMenu {
|
||||
enum class Type;
|
||||
} // namespace SendMenu
|
||||
|
||||
namespace ChatHelpers {
|
||||
struct FileChosen;
|
||||
class Show;
|
||||
@@ -34,7 +30,9 @@ class Show;
|
||||
|
||||
class StickerPremiumMark final {
|
||||
public:
|
||||
explicit StickerPremiumMark(not_null<Main::Session*> session);
|
||||
StickerPremiumMark(
|
||||
not_null<Main::Session*> session,
|
||||
const style::icon &lockIcon);
|
||||
|
||||
void paint(
|
||||
QPainter &p,
|
||||
@@ -48,6 +46,7 @@ private:
|
||||
void validateLock(const QImage &frame, QImage &backCache);
|
||||
void validateStar();
|
||||
|
||||
const style::icon &_lockIcon;
|
||||
QImage _lockGray;
|
||||
QImage _star;
|
||||
bool _premium = false;
|
||||
|
||||
@@ -563,7 +563,7 @@ groupCallPopupMenuWithCover: PopupMenu(groupCallPopupMenu) {
|
||||
}
|
||||
}
|
||||
groupCallMenuCover: ShortInfoCover(shortInfoCover) {
|
||||
radius: roundRadiusSmall;
|
||||
radius: roundRadiusLarge;
|
||||
size: groupCallMenuCoverSize;
|
||||
namePosition: point(17px, 28px);
|
||||
statusPosition: point(17px, 8px);
|
||||
|
||||
@@ -7,31 +7,29 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "calls/calls_call.h"
|
||||
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_account.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "apiwrap.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "boxes/abstract_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/boxes/rate_call_box.h"
|
||||
#include "calls/calls_instance.h"
|
||||
#include "base/battery_saving.h"
|
||||
#include "base/openssl_help.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/random.h"
|
||||
#include "mtproto/mtproto_dh_utils.h"
|
||||
#include "mtproto/mtproto_config.h"
|
||||
#include "boxes/abstract_box.h"
|
||||
#include "calls/calls_instance.h"
|
||||
#include "calls/calls_panel.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "main/main_session.h"
|
||||
#include "media/audio/media_audio_track.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "calls/calls_panel.h"
|
||||
#include "mtproto/mtproto_config.h"
|
||||
#include "mtproto/mtproto_dh_utils.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/boxes/rate_call_box.h"
|
||||
#include "webrtc/webrtc_create_adm.h"
|
||||
#include "webrtc/webrtc_environment.h"
|
||||
#include "webrtc/webrtc_video_track.h"
|
||||
#include "webrtc/webrtc_create_adm.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_session.h"
|
||||
#include "window/window_controller.h"
|
||||
|
||||
#include <tgcalls/Instance.h>
|
||||
#include <tgcalls/VideoCaptureInterface.h>
|
||||
@@ -1072,6 +1070,7 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
|
||||
Core::App().mediaDevices().setCaptureMuted(muted);
|
||||
}, _instanceLifetime);
|
||||
|
||||
#if 0
|
||||
Core::App().batterySaving().value(
|
||||
) | rpl::start_with_next([=](bool isSaving) {
|
||||
crl::on_main(weak, [=] {
|
||||
@@ -1080,6 +1079,7 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
|
||||
}
|
||||
});
|
||||
}, _instanceLifetime);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Call::handleControllerStateChange(tgcalls::State state) {
|
||||
|
||||