Compare commits
213 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 59c2cbaabc | |||
| 6d39a5f1d2 | |||
| 4fd0b008ba | |||
| fccc93ca53 | |||
| 37d1940993 | |||
| f25638f492 | |||
| 6e6f15e711 | |||
| b38e72dcd9 | |||
| 9287af1752 | |||
| 254ca57bf3 | |||
| 52ef8e780a | |||
| 16a2d4ec96 | |||
| 3b50bc71b3 | |||
| bb31357c58 | |||
| 66afcbdae8 | |||
| 5a28e69f1a | |||
| ce78074df7 | |||
| 973f91b5e4 | |||
| d2246337a2 | |||
| c7f11eb05a | |||
| 758219265a | |||
| d8d9441731 | |||
| 098e797045 | |||
| 69f8cb5951 | |||
| ca86dce760 | |||
| 5cf0b6b50e | |||
| b6bbf3a560 | |||
| e0c2294d50 | |||
| 7426d285b4 | |||
| fba53e1ec8 | |||
| 74196732ac | |||
| 8c41a69e67 | |||
| b1e91ab746 | |||
| 7a139ecda7 | |||
| e52fe9ddb0 | |||
| 2e514a748c | |||
| cee2961632 | |||
| 50bc6a33f6 | |||
| 5933535c9b | |||
| a3b91da66c | |||
| 34d9a21aae | |||
| 233c6b18ed | |||
| 4274f9d3f3 | |||
| c8dd94601b | |||
| 382dab4ecb | |||
| bdf67645bb | |||
| 5c29cc59c8 | |||
| ca9caa36da | |||
| 0b5f05c7d4 | |||
| 676e85983d | |||
| 7638f4cc3d | |||
| e2e55312b8 | |||
| c51a8816eb | |||
| 175914f02b | |||
| fbd6b5b640 | |||
| 1b11731d6b | |||
| 8ecb49f132 | |||
| 2f069bcd02 | |||
| 74abaccab3 | |||
| 115517fae1 | |||
| 2aef563374 | |||
| 040794586b | |||
| c102f4c0e5 | |||
| 7cdbbc318a | |||
| 0ae537478f | |||
| 4aa432ecbe | |||
| d5a0f4890d | |||
| 5e255e56eb | |||
| a4d7309209 | |||
| a30d0eccda | |||
| a4f4e4564a | |||
| 3948341d6a | |||
| c408c5fb87 | |||
| fa8124cc1b | |||
| 8d93b21947 | |||
| bfe7683cdb | |||
| fbc600a978 | |||
| 70eb452a09 | |||
| 9f7c74ae72 | |||
| 65a3cf136b | |||
| 2d86ec1e84 | |||
| fdef19a009 | |||
| 26df482b54 | |||
| ee5b7a5100 | |||
| bd67bc4433 | |||
| 9d582040e6 | |||
| f3bda59019 | |||
| 0d72d47318 | |||
| 29646707a1 | |||
| e6b9a07163 | |||
| 4b297bfa09 | |||
| 00e785a3af | |||
| 78e6b3e13f | |||
| ad84750130 | |||
| 686310489b | |||
| 84c5310262 | |||
| f53397e26a | |||
| 2a8a74b5b1 | |||
| 9392550c01 | |||
| d2565dc944 | |||
| 22f68b430d | |||
| 3962e5a680 | |||
| a1c7a48958 | |||
| 85286684e3 | |||
| c2712b0104 | |||
| 1cb5ef7476 | |||
| 9f0b42bbbd | |||
| 634687881a | |||
| 878b4bb5af | |||
| 452257dcd5 | |||
| 4e6d8f06d9 | |||
| fd417024fb | |||
| 18c4d210e5 | |||
| ead40c759e | |||
| be9aa3a097 | |||
| bdcb146d06 | |||
| bf743836c1 | |||
| 70115a24bb | |||
| ea37e83b13 | |||
| e4e6fb14c7 | |||
| dc94f02345 | |||
| 2482da3c3f | |||
| 2c6439eeb6 | |||
| 0eab40b22f | |||
| 1a279c4409 | |||
| f1adebb23e | |||
| 032de55103 | |||
| 931c17418d | |||
| 5a47ed268c | |||
| e079a69d7e | |||
| 421aa31d59 | |||
| 9cc0ca5d81 | |||
| 6fab4e1dc3 | |||
| d63ebbe62c | |||
| cb4fce251e | |||
| 4aa8a41119 | |||
| 13cba72945 | |||
| cf63b0138e | |||
| 3cbe0aae4a | |||
| 5ab8e68366 | |||
| 1d345299f5 | |||
| fc50d5c30f | |||
| 4e3c1460f6 | |||
| 5bc954396c | |||
| b24290b019 | |||
| 23cce64d00 | |||
| 73690d14f7 | |||
| 2a5698cf34 | |||
| fd64718502 | |||
| 941126ad69 | |||
| 0e8058adb1 | |||
| 01906c1161 | |||
| 9201cf24f1 | |||
| d5a1c354d0 | |||
| 41ae1f56ed | |||
| ed7212f864 | |||
| 8bcb784f12 | |||
| 431549c81a | |||
| 12110e17a2 | |||
| db8338156a | |||
| de8b09d7fc | |||
| f06747f2a1 | |||
| d940dc193e | |||
| fddbce5dce | |||
| 081817f62a | |||
| 8efbd7a1cb | |||
| bce310d5c8 | |||
| ed9ecbd235 | |||
| 1e756dd380 | |||
| b9b6226692 | |||
| 82d73e2396 | |||
| cd5a6025f6 | |||
| b6c679449e | |||
| ac744b957a | |||
| 805a5d73b6 | |||
| 6aaf841a73 | |||
| 60e72768e1 | |||
| 94e8f2a791 | |||
| 1fb4a2f4ba | |||
| 28d68acfe6 | |||
| d87a0a2d25 | |||
| 8e92778b62 | |||
| f7e2c7977b | |||
| 4337f0b509 | |||
| 2b960a1f21 | |||
| 4b9648d8d9 | |||
| 62f9f3c94b | |||
| e854f0b60c | |||
| 19f38f3c6f | |||
| d56724f290 | |||
| 8abc35ca86 | |||
| e135f8954f | |||
| f5b59c9456 | |||
| 6471d43c71 | |||
| 563b8d1468 | |||
| f41a3fe01f | |||
| 29c9266ef5 | |||
| 1a856e359f | |||
| 59099a8d46 | |||
| 98c6a3ff79 | |||
| cccc2ce0f1 | |||
| 88b20f6700 | |||
| 3adbfb1fb5 | |||
| 0ee0ffa7f1 | |||
| 4c82620677 | |||
| 73294bfabf | |||
| 6c42095108 | |||
| fbe93b0afc | |||
| a98e92639c | |||
| e173c727f7 | |||
| 85f56217a8 | |||
| 06564efe0e | |||
| 664ebe4ed0 |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
@@ -28,6 +28,7 @@ jobs:
|
||||
run: |
|
||||
sudo apt update
|
||||
curl -sSL https://install.python-poetry.org | python3 -
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin
|
||||
|
||||
- name: Free up some disk space.
|
||||
uses: jlumbroso/free-disk-space@f68fdb76e2ea636224182cfb7377ff9a1708f9b8
|
||||
@@ -40,6 +41,4 @@ jobs:
|
||||
|
||||
- name: Push the Docker image.
|
||||
if: ${{ github.ref_name == github.event.repository.default_branch }}
|
||||
run: |
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin
|
||||
docker push $IMAGE_TAG
|
||||
run: docker push $IMAGE_TAG
|
||||
|
||||
@@ -43,15 +43,6 @@ jobs:
|
||||
linux:
|
||||
name: Rocky Linux 8
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ghcr.io/${{ github.repository }}/centos_env
|
||||
credentials:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: scl enable gcc-toolset-12 -- bash --noprofile --norc -eo pipefail {0}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -75,12 +66,13 @@ jobs:
|
||||
|
||||
- name: First set up.
|
||||
run: |
|
||||
gcc --version
|
||||
ln -s /usr/src/Libraries
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin
|
||||
docker pull ghcr.io/$GITHUB_REPOSITORY/centos_env
|
||||
docker tag ghcr.io/$GITHUB_REPOSITORY/centos_env tdesktop:centos_env
|
||||
|
||||
- name: Telegram Desktop build.
|
||||
run: |
|
||||
cd $REPO_NAME/Telegram
|
||||
cd $REPO_NAME
|
||||
|
||||
DEFINE=""
|
||||
if [ -n "${{ matrix.defines }}" ]; then
|
||||
@@ -91,18 +83,21 @@ jobs:
|
||||
echo "ARTIFACT_NAME=Telegram" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
./configure.sh \
|
||||
docker run --rm \
|
||||
-v $PWD:/usr/src/tdesktop \
|
||||
-e DEBUG=1 \
|
||||
tdesktop:centos_env \
|
||||
/usr/src/tdesktop/Telegram/build/docker/centos_env/build.sh \
|
||||
-D CMAKE_C_FLAGS_DEBUG="" \
|
||||
-D CMAKE_CXX_FLAGS_DEBUG="" \
|
||||
-D CMAKE_C_FLAGS="-Werror" \
|
||||
-D CMAKE_CXX_FLAGS="-Werror" \
|
||||
-D CMAKE_EXE_LINKER_FLAGS="-s" \
|
||||
-D TDESKTOP_API_TEST=ON \
|
||||
-D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF \
|
||||
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \
|
||||
$DEFINE
|
||||
|
||||
cmake --build ../out --config Debug --parallel
|
||||
|
||||
- name: Check.
|
||||
run: |
|
||||
filePath="$REPO_NAME/out/Debug/Telegram"
|
||||
@@ -121,7 +116,7 @@ jobs:
|
||||
run: |
|
||||
cd $REPO_NAME/out/Debug
|
||||
mkdir artifact
|
||||
mv Telegram artifact/
|
||||
mv {Telegram,Updater} artifact/
|
||||
- uses: actions/upload-artifact@master
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
name: Upload artifact.
|
||||
|
||||
@@ -115,6 +115,7 @@ jobs:
|
||||
-D CMAKE_CXX_FLAGS="-Werror" \
|
||||
-D CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=NO \
|
||||
-D TDESKTOP_API_TEST=ON \
|
||||
-D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF \
|
||||
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \
|
||||
$DEFINE
|
||||
|
||||
|
||||
@@ -169,6 +169,7 @@ jobs:
|
||||
%TDESKTOP_BUILD_GENERATOR% ^
|
||||
%TDESKTOP_BUILD_ARCH% ^
|
||||
%TDESKTOP_BUILD_API% ^
|
||||
-D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF ^
|
||||
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF ^
|
||||
-D DESKTOP_APP_NO_PDB=ON ^
|
||||
%TDESKTOP_BUILD_DEFINE%
|
||||
@@ -178,8 +179,10 @@ jobs:
|
||||
- name: Move artifact.
|
||||
if: (env.UPLOAD_ARTIFACT == 'true') || (github.ref == 'refs/heads/nightly')
|
||||
run: |
|
||||
set OUT=%TBUILD%\%REPO_NAME%\out\Debug
|
||||
mkdir artifact
|
||||
move %TBUILD%\%REPO_NAME%\out\Debug\Telegram.exe artifact/
|
||||
move %OUT%\Telegram.exe artifact/
|
||||
move %OUT%\Updater.exe artifact/
|
||||
- uses: actions/upload-artifact@master
|
||||
name: Upload artifact.
|
||||
if: (env.UPLOAD_ARTIFACT == 'true') || (github.ref == 'refs/heads/nightly')
|
||||
|
||||
@@ -60,9 +60,9 @@ include(cmake/options.cmake)
|
||||
|
||||
if (NOT DESKTOP_APP_USE_PACKAGED)
|
||||
if (WIN32)
|
||||
set(qt_version 5.15.11)
|
||||
set(qt_version 5.15.12)
|
||||
elseif (APPLE)
|
||||
set(qt_version 6.2.6)
|
||||
set(qt_version 6.2.7)
|
||||
endif()
|
||||
endif()
|
||||
include(cmake/external/qt/package.cmake)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
Copyright (c) 2014-2023 The Telegram Desktop Authors.
|
||||
Copyright (c) 2014-2024 The Telegram Desktop Authors.
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
||||
@@ -9,11 +9,17 @@
|
||||
- Anti-recall
|
||||
- Font customization
|
||||
- Streamer mode
|
||||
- Material Design switches
|
||||
- Local Telegram Premium
|
||||
- Sync read states and message history with AyuSync *(TBA)*
|
||||
- Enhanced appearance
|
||||
|
||||
<img src='.github/demos/demo1.png' width='250'> <img src='.github/demos/demo2.png' width='250'> <img src='.github/demos/demo3.png' width='250'>
|
||||
And many more. Check out our [Documentation](https://docs.ayugram.one/desktop/).
|
||||
|
||||
## Preferences screenshots
|
||||
|
||||
<img src='.github/demos/demo1.png' width='268'>
|
||||
<img src='.github/demos/demo2.png' width='268'>
|
||||
<img src='.github/demos/demo3.png' width='268'>
|
||||
<img src='.github/demos/demo4.png' width='268'>
|
||||
|
||||
## Downloads
|
||||
|
||||
@@ -27,12 +33,7 @@ build by yourself.
|
||||
|
||||
### Arch Linux
|
||||
|
||||
It is recommended to build AyuGram from source on any Linux distro.
|
||||
|
||||
But if you're brave enough, you can install `ayugram-desktop-git` or `ayugram-desktop-bin`
|
||||
from [AUR](https://aur.archlinux.org/packages?O=0&K=ayugram).
|
||||
|
||||
These packages maintained by [westpain](https://github.com/incycledream). *We're not responsible for them.*
|
||||
You can install `ayugram-desktop` or `ayugram-desktop-git` from [AUR](https://aur.archlinux.org/packages?O=0&K=ayugram).
|
||||
|
||||
### Any other Linux distro
|
||||
|
||||
@@ -48,23 +49,27 @@ Make sure you have these components installed with VS Build Tools:
|
||||
|
||||
## Donation
|
||||
|
||||
If you enjoy using **AyuGram** and want to send us a tip, here's how you can do it:
|
||||
Enjoy using **AyuGram**? Consider sending us a tip!
|
||||
|
||||
- Using [Boosty](https://boosty.to/alexeyzavar) - any card and PayPal
|
||||
- Using cryptocurrency - `TRpbajq38qU8joThgAfKJLyEPbNjzsdPJ1` (Tron + USDT)
|
||||
[Here's available methods.](https://docs.ayugram.one/donate/)
|
||||
|
||||
## Credits
|
||||
|
||||
### Telegram clients
|
||||
|
||||
- [Telegram Desktop](https://github.com/telegramdesktop/tdesktop)
|
||||
- [Kotatogram](https://github.com/kotatogram/kotatogram-desktop)
|
||||
- [64Gram](https://github.com/TDesktop-x64/tdesktop)
|
||||
- [Forkgram](https://github.com/forkgram/tdesktop)
|
||||
- [SQLite](https://github.com/sqlite/sqlite)
|
||||
- [sqlite_orm](https://github.com/fnc12/sqlite_orm)
|
||||
|
||||
### Very special thanks to
|
||||
### Libraries used
|
||||
|
||||
- [Solar Icon Set](https://solariconset.com/)
|
||||
- [JSON for Modern C++](https://github.com/nlohmann/json)
|
||||
- [BitConverter](https://github.com/YanjieHe/BitConverter)
|
||||
- [Not Enough Standards](https://github.com/Alairion/not-enough-standards)
|
||||
- [SQLite](https://github.com/sqlite/sqlite)
|
||||
- [sqlite_orm](https://github.com/fnc12/sqlite_orm)
|
||||
|
||||
### Icons
|
||||
|
||||
- [Solar Icon Set](https://www.figma.com/community/file/1166831539721848736)
|
||||
|
||||
@@ -96,6 +96,8 @@ nice_target_sources(Telegram ${src_loc}
|
||||
PRIVATE
|
||||
${style_files}
|
||||
|
||||
ayu/ayu_url_handlers.cpp
|
||||
ayu/ayu_url_handlers.h
|
||||
ayu/ayu_state.cpp
|
||||
ayu/ayu_state.h
|
||||
ayu/ayu_settings.cpp
|
||||
@@ -107,6 +109,7 @@ PRIVATE
|
||||
ayu/ayu_constants.h
|
||||
ayu/utils/ayu_mapper.cpp
|
||||
ayu/utils/ayu_mapper.h
|
||||
ayu/utils/qt_key_modifiers_extended.h
|
||||
ayu/utils/telegram_helpers.cpp
|
||||
ayu/utils/telegram_helpers.h
|
||||
ayu/utils/windows_utils.cpp
|
||||
@@ -123,6 +126,8 @@ PRIVATE
|
||||
ayu/ui/settings/settings_ayu.h
|
||||
ayu/ui/context_menu/context_menu.cpp
|
||||
ayu/ui/context_menu/context_menu.h
|
||||
ayu/ui/context_menu/menu_item_subtext.cpp
|
||||
ayu/ui/context_menu/menu_item_subtext.h
|
||||
ayu/ui/sections/edited/edited_log_inner.cpp
|
||||
ayu/ui/sections/edited/edited_log_inner.h
|
||||
ayu/ui/sections/edited/edited_log_item.cpp
|
||||
@@ -617,6 +622,10 @@ PRIVATE
|
||||
data/data_replies_list.h
|
||||
data/data_reply_preview.cpp
|
||||
data/data_reply_preview.h
|
||||
data/data_saved_messages.cpp
|
||||
data/data_saved_messages.h
|
||||
data/data_saved_sublist.cpp
|
||||
data/data_saved_sublist.h
|
||||
data/data_search_controller.cpp
|
||||
data/data_search_controller.h
|
||||
data/data_send_action.cpp
|
||||
@@ -863,6 +872,8 @@ PRIVATE
|
||||
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_transcribe_button.cpp
|
||||
history/view/history_view_transcribe_button.h
|
||||
history/view/history_view_translate_bar.cpp
|
||||
@@ -964,6 +975,8 @@ PRIVATE
|
||||
info/profile/info_profile_values.h
|
||||
info/profile/info_profile_widget.cpp
|
||||
info/profile/info_profile_widget.h
|
||||
info/saved/info_saved_sublists_widget.cpp
|
||||
info/saved/info_saved_sublists_widget.h
|
||||
info/settings/info_settings_widget.cpp
|
||||
info/settings/info_settings_widget.h
|
||||
info/similar_channels/info_similar_channels_widget.cpp
|
||||
@@ -1934,9 +1947,9 @@ endif()
|
||||
|
||||
if (LINUX AND DESKTOP_APP_USE_PACKAGED)
|
||||
include(GNUInstallDirs)
|
||||
configure_file("../lib/xdg/ayugram.desktop.service" "${CMAKE_CURRENT_BINARY_DIR}/ayugram.desktop.service" @ONLY)
|
||||
configure_file("../lib/xdg/ayugram.desktop.metainfo.xml" "${CMAKE_CURRENT_BINARY_DIR}/ayugram.desktop.metainfo.xml" @ONLY)
|
||||
generate_appdata_changelog(Telegram "${CMAKE_SOURCE_DIR}/changelog.txt" "${CMAKE_CURRENT_BINARY_DIR}/ayugram.desktop.metainfo.xml")
|
||||
configure_file("../lib/xdg/com.ayugram.desktop.service" "${CMAKE_CURRENT_BINARY_DIR}/com.ayugram.desktop.service" @ONLY)
|
||||
configure_file("../lib/xdg/com.ayugram.desktop.metainfo.xml" "${CMAKE_CURRENT_BINARY_DIR}/com.ayugram.desktop.metainfo.xml" @ONLY)
|
||||
generate_appdata_changelog(Telegram "${CMAKE_SOURCE_DIR}/changelog.txt" "${CMAKE_CURRENT_BINARY_DIR}/com.ayugram.desktop.metainfo.xml")
|
||||
install(TARGETS Telegram RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" BUNDLE DESTINATION "${CMAKE_INSTALL_BINDIR}")
|
||||
install(FILES "Resources/art/icon16.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/16x16/apps" RENAME "ayugram.png")
|
||||
install(FILES "Resources/art/icon32.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/32x32/apps" RENAME "ayugram.png")
|
||||
@@ -1945,8 +1958,8 @@ if (LINUX AND DESKTOP_APP_USE_PACKAGED)
|
||||
install(FILES "Resources/art/icon128.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128/apps" RENAME "ayugram.png")
|
||||
install(FILES "Resources/art/icon256.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/256x256/apps" RENAME "ayugram.png")
|
||||
install(FILES "Resources/art/icon512.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/512x512/apps" RENAME "ayugram.png")
|
||||
install(FILES "Resources/icons/tray_monochrome.svg" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/symbolic/apps" RENAME "telegram-symbolic.svg")
|
||||
install(FILES "../lib/xdg/ayugram.desktop.desktop" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications")
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/ayugram.desktop.service" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/dbus-1/services")
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/ayugram.desktop.metainfo.xml" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo")
|
||||
install(FILES "Resources/icons/tray_monochrome.svg" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/symbolic/apps" RENAME "ayugram-symbolic.svg")
|
||||
install(FILES "../lib/xdg/com.ayugram.desktop.desktop" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications")
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.ayugram.desktop.service" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/dbus-1/services")
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.ayugram.desktop.metainfo.xml" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo")
|
||||
endif()
|
||||
|
||||
|
After Width: | Height: | Size: 428 B |
|
After Width: | Height: | Size: 713 B |
|
After Width: | Height: | Size: 1005 B |
|
After Width: | Height: | Size: 919 B |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 753 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 380 B |
|
After Width: | Height: | Size: 664 B |
|
After Width: | Height: | Size: 954 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 686 B |
|
After Width: | Height: | Size: 952 B |
@@ -395,6 +395,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_dlg_new_bot_name" = "Bot name";
|
||||
"lng_no_chats" = "Your chats will be here";
|
||||
"lng_no_chats_filter" = "No chats currently belong to this folder.";
|
||||
"lng_no_saved_sublists" = "You can save messages from other chats here.";
|
||||
"lng_contacts_loading" = "Loading...";
|
||||
"lng_contacts_not_found" = "No contacts found";
|
||||
"lng_topics_not_found" = "No topics found.";
|
||||
@@ -591,6 +592,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_section_background" = "Chat background";
|
||||
"lng_settings_bg_from_gallery" = "Choose from gallery";
|
||||
"lng_settings_bg_from_file" = "Choose from file";
|
||||
"lng_settings_bg_remove" = "Remove wallpaper";
|
||||
"lng_settings_bg_theme_edit" = "Edit theme";
|
||||
"lng_settings_bg_theme_create" = "Create new theme";
|
||||
"lng_settings_bg_cloud_themes" = "Custom themes";
|
||||
@@ -813,6 +815,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_manage_enabled_dictionary" = "Dictionary is enabled";
|
||||
"lng_settings_manage_remove_dictionary" = "Remove Dictionary";
|
||||
|
||||
"lng_settings_gift_premium" = "Premium Gifting";
|
||||
"lng_settings_gift_premium_users_confirm" = "Proceed";
|
||||
"lng_settings_gift_premium_users_error#one" = "You can select maximum {count} user.";
|
||||
"lng_settings_gift_premium_users_error#other" = "You can select maximum {count} users.";
|
||||
|
||||
"lng_backgrounds_header" = "Choose Wallpaper";
|
||||
"lng_theme_sure_keep" = "Keep this theme?";
|
||||
"lng_theme_reverting#one" = "Reverting to the old theme in {count} second.";
|
||||
@@ -833,6 +840,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_background_blur" = "Blurred";
|
||||
"lng_background_sure_delete" = "Are you sure you want to delete this background?";
|
||||
"lng_background_other_info" = "{user} will be able to apply this wallpaper";
|
||||
"lng_background_other_channel" = "All subscribers will see this wallpaper";
|
||||
"lng_background_apply1" = "Apply the wallpaper in this chat.";
|
||||
"lng_background_apply2" = "Enjoy the view.";
|
||||
"lng_background_apply_button" = "Apply For This Chat";
|
||||
@@ -841,6 +849,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_background_reset_default" = "Reset";
|
||||
"lng_background_apply_me" = "Apply for me";
|
||||
"lng_background_apply_both" = "Apply for me and {user}";
|
||||
"lng_background_apply_channel" = "Apply For Channel";
|
||||
|
||||
"lng_download_path_ask" = "Ask download path for each file";
|
||||
"lng_download_path" = "Download path";
|
||||
@@ -1179,6 +1188,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_profile_common_groups#other" = "{count} groups in common";
|
||||
"lng_profile_similar_channels#one" = "{count} similar channel";
|
||||
"lng_profile_similar_channels#other" = "{count} similar channels";
|
||||
"lng_profile_saved_messages#one" = "{count} saved message";
|
||||
"lng_profile_saved_messages#other" = "{count} saved messages";
|
||||
"lng_profile_participants_section" = "Members";
|
||||
"lng_profile_subscribers_section" = "Subscribers";
|
||||
"lng_profile_add_contact" = "Add Contact";
|
||||
@@ -1682,7 +1693,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_action_giveaway_started" = "{from} just started a giveaway of Telegram Premium subscriptions to its followers.";
|
||||
"lng_action_giveaway_results#one" = "{count} winner of the giveaway was randomly selected by Telegram and received private messages with giftcodes.";
|
||||
"lng_action_giveaway_results#other" = "{count} winners of the giveaway were randomly selected by Telegram and received private messages with giftcodes.";
|
||||
"lng_action_giveaway_results_some" = "Some winners of the giveaway was randomly selected by Telegram and received private messages with giftcodes.";
|
||||
"lng_action_giveaway_results_some" = "Some winners of the giveaway were randomly selected by Telegram and received private messages with giftcodes.";
|
||||
"lng_action_giveaway_results_none" = "No winners of the giveaway could be selected.";
|
||||
|
||||
"lng_similar_channels_title" = "Similar channels";
|
||||
@@ -1704,6 +1715,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_ttl_video_received" = "{from} sent you a self-destructing video. Please view it on your mobile.";
|
||||
"lng_ttl_video_sent" = "You sent a self-destructing video.";
|
||||
"lng_ttl_video_expired" = "Video has expired";
|
||||
"lng_ttl_voice_sent" = "You sent a self-destructing voice messsage.";
|
||||
"lng_ttl_voice_expired" = "Voice message expired";
|
||||
"lng_ttl_round_sent" = "You sent a self-destructing video message.";
|
||||
"lng_ttl_round_expired" = "Round message expired";
|
||||
|
||||
"lng_profile_add_more_after_create" = "You will be able to add more members after you create the group.";
|
||||
"lng_profile_camera_title" = "Capture yourself";
|
||||
@@ -1836,6 +1851,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_sponsored_message_title" = "Sponsored";
|
||||
"lng_recommended_message_title" = "Recommended";
|
||||
"lng_edited" = "edited";
|
||||
"lng_commented" = "commented";
|
||||
"lng_edited_date" = "Edited: {date}";
|
||||
"lng_sent_date" = "Sent: {date}";
|
||||
"lng_views_tooltip#one" = "Views: {count}";
|
||||
@@ -2055,6 +2071,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_premium_gift_per" = "{cost} / month";
|
||||
"lng_premium_gift_terms" = "You can review the list of features and terms of use for Telegram Premium {link}.";
|
||||
"lng_premium_gift_terms_link" = "here";
|
||||
"lng_premium_gifts_about_user1" = "Give **{user}** access to exclusive features.";
|
||||
"lng_premium_gifts_about_user2" = "Give **{user}** and **{second_user}** access to exclusive features.";
|
||||
"lng_premium_gifts_about_user3" = "Give **{user}**, **{second_user}** and **{name}** access to exclusive features.";
|
||||
"lng_premium_gifts_about_user_more#one" = "Give **{user}**, **{second_user}**, **{name}** and **{count}** more friend access to exclusive features.";
|
||||
"lng_premium_gifts_about_user_more#other" = "Give **{user}**, **{second_user}**, **{name}** and **{count}** more friends access to exclusive features.";
|
||||
"lng_premium_gifts_about_reward#one" = "You will receive {emoji}**{count}** boost.";
|
||||
"lng_premium_gifts_about_reward#other" = "You will receive {emoji}**{count}** boosts.";
|
||||
"lng_premium_gifts_about_paid_title" = "Gifts Sent!";
|
||||
"lng_premium_gifts_about_paid1" = "**{user}** has been notified about the gifts you purchased.";
|
||||
"lng_premium_gifts_about_paid2" = "**{user}** and **{second_user}** have been notified about the gifts you purchased.";
|
||||
"lng_premium_gifts_about_paid3" = "**{user}**, **{second_user}** and **{name}** have been notified about the gifts you purchased.";
|
||||
"lng_premium_gifts_about_paid_more#one" = "**{user}**, **{second_user}**, **{name}** and **{count}** other have been notified about the gifts you purchased.";
|
||||
"lng_premium_gifts_about_paid_more#other" = "**{user}**, **{second_user}**, **{name}** and **{count}** others have been notified about the gifts you purchased.";
|
||||
"lng_premium_gifts_about_paid_below#one" = "They now have access to additional features.";
|
||||
"lng_premium_gifts_about_paid_below#other" = "They now have access to additional features.";
|
||||
"lng_premium_gifts_summary_subtitle" = "What's Included";
|
||||
"lng_premium_gifts_terms" = "By gifting Telegram Premium, you agree to the Telegram {link} and {policy}.";
|
||||
"lng_premium_gifts_terms_policy" = "Privacy Policy";
|
||||
|
||||
"lng_boost_channel_button" = "Boost Channel";
|
||||
"lng_boost_again_button" = "Boost Again";
|
||||
@@ -2111,6 +2145,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_boost_channel_needs_level_color#one" = "Your channel needs to reach **Level {count}** to change channel color.";
|
||||
"lng_boost_channel_needs_level_color#other" = "Your channel needs to reach **Level {count}** to change channel color.";
|
||||
|
||||
"lng_boost_channel_title_wallpaper" = "Enable wallpapers";
|
||||
"lng_boost_channel_needs_level_wallpaper#one" = "Your channel needs to reach **Level {count}** to change channel wallpaper.";
|
||||
"lng_boost_channel_needs_level_wallpaper#other" = "Your channel needs to reach **Level {count}** to change channel wallpaper.";
|
||||
|
||||
"lng_boost_channel_title_status" = "Enable emoji status";
|
||||
"lng_boost_channel_needs_level_status#one" = "Your channel needs to reach **Level {count}** to set emoji status.";
|
||||
"lng_boost_channel_needs_level_status#other" = "Your channel needs to reach **Level {count}** to set emoji status.";
|
||||
|
||||
"lng_boost_channel_title_reactions" = "Custom reactions";
|
||||
"lng_boost_channel_needs_level_reactions#one" = "Your channel needs to reach **Level {count}** to add **{same_count}** custom emoji as a reaction.";
|
||||
"lng_boost_channel_needs_level_reactions#other" = "Your channel needs to reach **Level {count}** to add **{same_count}** custom emoji as reactions.";
|
||||
@@ -2170,6 +2212,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_giveaway_maximum_users_error#other" = "You can select maximum {count} users.";
|
||||
"lng_giveaway_channels_confirm_title" = "Channel is Private";
|
||||
"lng_giveaway_channels_confirm_about" = "Are you sure you want to add a private channel? Users won't be able to join it without an invite link.";
|
||||
"lng_giveaway_additional_prizes" = "Additional prizes";
|
||||
"lng_giveaway_additional_about" = "Turn this on if you want to give the winners your own prizes in addition to Premium subscriptions.";
|
||||
"lng_giveaway_additional_prizes_ph" = "Enter your prize";
|
||||
"lng_giveaway_prizes_just_premium#one" = "All prizes: **{count}** Telegram Premium subscription {duration}.";
|
||||
"lng_giveaway_prizes_just_premium#other" = "All prizes: **{count}** Telegram Premium subscriptions {duration}.";
|
||||
"lng_giveaway_prizes_additional#one" = "All prizes: **{count}** {prize} with Telegram Premium subscription {duration}.";
|
||||
"lng_giveaway_prizes_additional#other" = "All prizes: **{count}** {prize} with Telegram Premium subscriptions {duration}.";
|
||||
"lng_giveaway_show_winners" = "Show winners";
|
||||
"lng_giveaway_show_winners_about" = "Choose whether to make the list of winners public when the giveaway ends.";
|
||||
|
||||
"lng_giveaway_created_title" = "Giveaway created";
|
||||
"lng_giveaway_created_body" = "Check your channels' {link} to see how this giveaway boosted your channel.";
|
||||
@@ -2189,6 +2240,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
"lng_prizes_title#one" = "Giveaway Prize";
|
||||
"lng_prizes_title#other" = "Giveaway Prizes";
|
||||
"lng_prizes_additional#one" = "**{count}** {prize}";
|
||||
"lng_prizes_additional#other" = "**{count}** {prize}";
|
||||
"lng_prizes_additional_with" = "with";
|
||||
"lng_prizes_about#one" = "**{count}** Telegram Premium Subscription {duration}.";
|
||||
"lng_prizes_about#other" = "**{count}** Telegram Premium Subscriptions {duration}.";
|
||||
"lng_prizes_participants" = "Participants";
|
||||
@@ -2207,6 +2261,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_prizes_end_text" = "This giveaway was sponsored by {admins}.";
|
||||
"lng_prizes_admins#one" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscription {duration} for its followers";
|
||||
"lng_prizes_admins#other" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscriptions {duration} for its followers.";
|
||||
"lng_prizes_additional_added#one" = "{channel} also included **{count} {prize}** in the prize. Admins of the channel are responsible for delivering this prize.";
|
||||
"lng_prizes_additional_added#other" = "{channel} also included **{count} {prize}** in the prizes. Admins of the channel are responsible for delivering these prizes.";
|
||||
"lng_prizes_how_when_finish" = "On {date}, Telegram will automatically select {winners}.";
|
||||
"lng_prizes_end_when_finish" = "On {date}, Telegram automatically selected {winners}.";
|
||||
"lng_prizes_end_activated#one" = "**{count}** of the winners already used their gift link.";
|
||||
@@ -2232,6 +2288,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_prizes_cancelled" = "The channel cancelled the prizes by reversing the payment for them.";
|
||||
"lng_prizes_badge" = "x{amount}";
|
||||
|
||||
"lng_prizes_results_title" = "Winners Selected!";
|
||||
"lng_prizes_results_about#one" = "**{count}** winner of the {link} was randomly selected by Telegram.";
|
||||
"lng_prizes_results_about#other" = "**{count}** winners of the {link} were randomly selected by Telegram.";
|
||||
"lng_prizes_results_link" = "Giveaway";
|
||||
"lng_prizes_results_winners" = "Winners";
|
||||
"lng_prizes_results_more#one" = "and {count} more!";
|
||||
"lng_prizes_results_more#other" = "and {count} more!";
|
||||
"lng_prizes_results_all" = "All winners received gift links in private messages.";
|
||||
"lng_prizes_results_some" = "Some winners couldn't be selected.";
|
||||
|
||||
"lng_gift_link_title" = "Gift Link";
|
||||
"lng_gift_link_about" = "This link allows you to activate\na **Telegram Premium** subscription.";
|
||||
"lng_gift_link_label_from" = "From";
|
||||
@@ -2435,6 +2501,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_saved_short" = "Save";
|
||||
"lng_saved_forward_here" = "Forward messages here for quick access";
|
||||
"lng_saved_quote_here" = "Quote here to save";
|
||||
"lng_saved_open_chat" = "Open Chat";
|
||||
"lng_saved_open_channel" = "Open Channel";
|
||||
"lng_saved_open_group" = "Open Group";
|
||||
"lng_saved_about_hidden" = "Senders of this messages restricted to link their name when forwarding.";
|
||||
|
||||
"lng_scheduled_messages" = "Scheduled Messages";
|
||||
"lng_scheduled_messages_empty" = "No scheduled messages here yet...";
|
||||
@@ -2461,6 +2531,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_comments_open_none" = "Leave a comment";
|
||||
"lng_replies_view_original" = "View in chat";
|
||||
"lng_replies_messages" = "Replies";
|
||||
"lng_hidden_author_messages" = "Author Hidden";
|
||||
"lng_my_notes" = "My Notes";
|
||||
"lng_replies_discussion_started" = "Discussion started";
|
||||
"lng_replies_no_comments" = "No comments here yet...";
|
||||
|
||||
@@ -2801,6 +2873,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_edit_sign_messages" = "Sign messages";
|
||||
"lng_edit_group" = "Edit group";
|
||||
"lng_edit_channel_color" = "Change name color";
|
||||
"lng_edit_channel_level_min" = "Level 1+";
|
||||
"lng_edit_channel_wallpaper" = "Channel wallpaper";
|
||||
"lng_edit_channel_wallpaper_about" = "Set a wallpaper that will be visible for everyone reading your channel.";
|
||||
"lng_edit_channel_status" = "Channel emoji status";
|
||||
"lng_edit_channel_status_about" = "Choose a status that will be shown next to the channel's name.";
|
||||
"lng_edit_self_title" = "Edit your name";
|
||||
"lng_confirm_contact_data" = "New Contact";
|
||||
"lng_add_contact" = "Create";
|
||||
@@ -3615,6 +3692,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_admin_log_set_background_emoji" = "{from} set channel background emoji to {emoji}";
|
||||
"lng_admin_log_change_background_emoji" = "{from} changed channel background emoji from {previous} to {emoji}";
|
||||
"lng_admin_log_removed_background_emoji" = "{from} removed channel background emoji {emoji}";
|
||||
"lng_admin_log_change_profile_color" = "{from} changed channel profile color from {previous} to {color}";
|
||||
"lng_admin_log_set_profile_background_emoji" = "{from} set channel profile background emoji to {emoji}";
|
||||
"lng_admin_log_change_profile_background_emoji" = "{from} changed channel profile background emoji from {previous} to {emoji}";
|
||||
"lng_admin_log_removed_profile_background_emoji" = "{from} removed channel profile background emoji {emoji}";
|
||||
"lng_admin_log_change_wallpaper" = "{from} changed channel wallpaper";
|
||||
"lng_admin_log_set_status" = "{from} set channel emoji status to {emoji}";
|
||||
"lng_admin_log_change_status" = "{from} changed channel emoji status from {previous} to {emoji}";
|
||||
"lng_admin_log_removed_status" = "{from} removed channel emoji status {emoji}";
|
||||
"lng_admin_log_set_status_until" = "{from} set channel emoji status to {emoji} until {date}";
|
||||
"lng_admin_log_change_status_until" = "{from} changed channel emoji status from {previous} to {emoji} until {date}";
|
||||
"lng_admin_log_user_with_username" = "{name} ({mention})";
|
||||
"lng_admin_log_messages_ttl_set" = "{from} enabled messages auto-delete after {duration}";
|
||||
"lng_admin_log_messages_ttl_changed" = "{from} changed messages auto-delete period from {previous} to {duration}";
|
||||
@@ -4207,6 +4294,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_request_peer_confirm_rights" = "This will also add {bot} to {chat} with the following rights: {rights}.";
|
||||
"lng_request_peer_confirm_send" = "Send";
|
||||
"lng_request_user_title" = "Choose User";
|
||||
"lng_request_users_title" = "Choose Users";
|
||||
"lng_request_user_premium_yes" = "The user should have a Premium subscription.";
|
||||
"lng_request_user_premium_no" = "The user shouldn't have a Premium subscription.";
|
||||
"lng_request_user_no" = "No such users";
|
||||
@@ -4523,7 +4611,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"ayu_SpyEssentialsHeader" = "Spy essentials";
|
||||
"ayu_SaveDeletedMessages" = "Save deleted messages";
|
||||
"ayu_SaveMessagesHistory" = "Save edits history";
|
||||
"ayu_MessageSavingBtn" = "Message Saving Preferences";
|
||||
"ayu_MessageSavingActionBarHeader" = "Message Saving Preferences";
|
||||
"ayu_MessageSavingBtn" = "Preferences";
|
||||
"ayu_MessageSavingSubTitle" = "Configure media & per dialog saving";
|
||||
"ayu_MessageSavingMediaHeader" = "Media";
|
||||
"ayu_MessageSavingSaveMedia" = "Save media";
|
||||
"ayu_MessageSavingSaveMediaHint" = "Click for more";
|
||||
@@ -4535,9 +4625,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"ayu_MaximumMediaSizeCellular" = "Media size limit (cellular data)";
|
||||
"ayu_MaximumMediaSizeWiFi" = "Media size limit (WiFi)";
|
||||
"ayu_MessageSavingOtherHeader" = "Other";
|
||||
"ayu_MessageSavingSaveFormatting" = "Save formatting";
|
||||
"ayu_MessageSavingSaveReactions" = "Save reactions";
|
||||
"ayu_MessageSavingSaveForBots" = "Save in bot dialogs";
|
||||
"ayu_MessageSavingSavePath" = "Attachments folder";
|
||||
"ayu_MessageSavingSavePathTitle" = "Choose folder";
|
||||
"ayu_SpyAllToggle" = "Spy on people";
|
||||
"ayu_SpySaveReadMarks" = "Save read date";
|
||||
"ayu_SpySaveLastActivity" = "Save last activity";
|
||||
"ayu_QoLTogglesHeader" = "Useful features";
|
||||
"ayu_KeepAliveService" = "AyuGram Push Service";
|
||||
"ayu_DisableAds" = "Disable ads";
|
||||
@@ -4548,6 +4642,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"ayu_CustomizationHeader" = "Customization";
|
||||
"ayu_DeletedMarkText" = "Deleted mark";
|
||||
"ayu_EditedMarkText" = "Edited mark";
|
||||
"ayu_HideNotificationCounters" = "Hide notification counters";
|
||||
"ayu_HideAllChats" = "Hide \"All chats\" folder";
|
||||
"ayu_ShowGhostToggleInDrawer" = "Show ghost mode toggle";
|
||||
"ayu_ShowKllButtonInDrawer" = "Show kill app button";
|
||||
@@ -4555,12 +4650,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"ayu_SettingsShowID_Hide" = "Hide";
|
||||
"ayu_SettingsRecentStickersCount" = "Recent stickers count";
|
||||
"ayu_SettingsCustomizationHint" = "After making changes to the \"Customization\" section, you must restart the application.";
|
||||
"ayu_SettingsContextMenuTitle" = "Select item show type";
|
||||
"ayu_SettingsContextMenuDescription" = "Extended menu items will be displayed if you hold CTRL or SHIFT while right-clicking on the message.";
|
||||
"ayu_SettingsContextMenuItemHidden" = "Hidden";
|
||||
"ayu_SettingsContextMenuItemShown" = "Shown";
|
||||
"ayu_SettingsContextMenuItemExtended" = "Extended menu";
|
||||
"ayu_SettingsContextMenuReactionsPanel" = "Reactions Panel";
|
||||
"ayu_SettingsContextMenuViewsPanel" = "Views Panel";
|
||||
"ayu_ContextMenuElementsHeader" = "Context Menu Elements";
|
||||
"ayu_DrawerElementsHeader" = "Drawer Elements";
|
||||
"ayu_TrayElementsHeader" = "Tray Elements";
|
||||
"ayu_RegexFilters" = "Message Filters";
|
||||
"ayu_RegexFiltersAmount" = "filters";
|
||||
"ayu_RegexFiltersHeader" = "Filters";
|
||||
"ayu_RegexFiltersShared" = "Shared filters";
|
||||
"ayu_RegexFiltersExcluded" = "Excluded filters";
|
||||
"ayu_FiltersHideFromBlocked" = "Hide from blocked users";
|
||||
"ayu_FiltersHideFromBlockedNote" = "Message filters were enabled.";
|
||||
"ayu_RegexFiltersEnableSharedInChats" = "Enable shared filters in chats";
|
||||
@@ -4571,6 +4675,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"ayu_CaseInsensitiveExpression" = "Case insensitive expression";
|
||||
"ayu_RegexFiltersAddError" = "Regex syntax error";
|
||||
"ayu_RegexFilterQuickAdd" = "Add filter";
|
||||
"ayu_RegexFilterBulletinText" = "Filter added to the **Shared filters**.";
|
||||
"ayu_RegexFilterBulletinAction" = "Move to current chat";
|
||||
"ayu_AyuSyncHeader" = "AyuSync";
|
||||
"ayu_AyuSyncStatusTitle" = "Sync status";
|
||||
"ayu_AyuSyncStatusOk" = "connected";
|
||||
@@ -4610,6 +4716,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"ayu_StickerConfirmation" = "For stickers";
|
||||
"ayu_GIFConfirmation" = "For GIFs";
|
||||
"ayu_VoiceConfirmation" = "For voice messages";
|
||||
"ayu_MessageDetailsPC" = "Details";
|
||||
"ayu_MessageDetailsViewsPC" = "Views";
|
||||
"ayu_MessageDetailsSharesPC" = "Shares";
|
||||
"ayu_MessageDetailsDatePC" = "Date";
|
||||
"ayu_MessageDetailsEditedDatePC" = "Edit Date";
|
||||
"ayu_MessageDetailsForwardedDatePC" = "Forward Date";
|
||||
"ayu_MessageDetailsFileSizePC" = "File Size";
|
||||
"ayu_MessageDetailsFileNamePC" = "File Name";
|
||||
"ayu_MessageDetailsFilePathPC" = "File Path";
|
||||
"ayu_MessageDetailsResolutionPC" = "Resolution";
|
||||
"ayu_MessageDetailsBitratePC" = "Bitrate";
|
||||
"ayu_MessageDetailsMimeTypePC" = "Mime Type";
|
||||
"ayu_MessageDetailsDatacenterPC" = "Datacenter";
|
||||
"ayu_MessageDetailsPackOwnerPC" = "Pack Owner";
|
||||
"ayu_MessageDetailsPackOwnerFetchingPC" = "fetching...";
|
||||
"ayu_MessageDetailsPackOwnerNotFoundPC" = "not found";
|
||||
"ayu_KillApp" = "Kill App";
|
||||
"ayu_LReadMessages" = "Read on Local";
|
||||
"ayu_SReadMessages" = "Read on Server";
|
||||
@@ -4630,6 +4752,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"ayu_ViewFiltersMenuText" = "View Filters";
|
||||
"ayu_PeekOnlineSuccess" = "Peeked via";
|
||||
"ayu_OneViewTTL" = "one view";
|
||||
"ayu_OnePlayTTL" = "one play";
|
||||
"ayu_UserMessagesMenuText" = "User Messages";
|
||||
"ayu_ReadUntilMenuText" = "Read Message";
|
||||
"ayu_DeleteKeepLocally" = "Keep locally";
|
||||
"ayu_BoxActionReset" = "Reset";
|
||||
@@ -4649,6 +4773,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"ayu_DisableSimilarChannels" = "Disable similar channels";
|
||||
"ayu_CollapseSimilarChannels" = "Collapse similar channels";
|
||||
"ayu_HideSimilarChannelsTab" = "Hide similar channels tab";
|
||||
"ayu_UploadSpeedBoostPC" = "Upload speed boost";
|
||||
"ayu_MainFont" = "Application font";
|
||||
"ayu_MonospaceFont" = "Monospace font";
|
||||
"ayu_FontDefault" = "Default";
|
||||
@@ -4657,7 +4782,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"ayu_AyuForwardStatusLoadingMedia" = "Loading media…";
|
||||
"ayu_AyuForwardForwardingDescription" = "Please keep this window open while AyuGram is forwarding your messages.";
|
||||
"ayu_AyuForwardLoadingMediaDescription" = "Please keep this window open while AyuGram is downloading media from your messages.";
|
||||
"ayu_UserNotFoundMessage" = "User not found.";
|
||||
"ayu_DeleteDateMenuText" = "Delete Date";
|
||||
"ayu_ReadDateMenuText" = "Read Date";
|
||||
"ayu_ContentsReadDateMenuText" = "Contents Read Date";
|
||||
"ayu_ForwardableMenuText" = "Forwardable";
|
||||
"ayu_ForwardableMenuTextNo" = "No";
|
||||
"ayu_LikelyOfflineStatus" = "offline ?";
|
||||
|
||||
@@ -11,5 +11,7 @@
|
||||
<file alias="ttl.tgs">../../animations/ttl.tgs</file>
|
||||
<file alias="discussion.tgs">../../animations/discussion.tgs</file>
|
||||
<file alias="stats.tgs">../../animations/stats.tgs</file>
|
||||
<file alias="voice_ttl_idle.tgs">../../animations/voice_ttl_idle.tgs</file>
|
||||
<file alias="voice_ttl_start.tgs">../../animations/voice_ttl_start.tgs</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<file alias="art/slot_2_idle.tgs">../../art/slot_2_idle.tgs</file>
|
||||
<file alias="art/slot_back.tgs">../../art/slot_back.tgs</file>
|
||||
<file alias="art/slot_pull.tgs">../../art/slot_pull.tgs</file>
|
||||
<file alias="art/winners.tgs">../../art/winners.tgs</file>
|
||||
<file alias="day-blue.tdesktop-theme">../../day-blue.tdesktop-theme</file>
|
||||
<file alias="night.tdesktop-theme">../../night.tdesktop-theme</file>
|
||||
<file alias="night-green.tdesktop-theme">../../night-green.tdesktop-theme</file>
|
||||
@@ -55,6 +56,6 @@
|
||||
</qresource>
|
||||
<qresource prefix="/misc">
|
||||
<file alias="default_shortcuts-custom.json">../../default_shortcuts-custom.json</file>
|
||||
<file alias="ayugram.desktop.desktop">../../../../lib/xdg/ayugram.desktop.desktop</file>
|
||||
<file alias="com.ayugram.desktop.desktop">../../../../lib/xdg/com.ayugram.desktop.desktop</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="4.12.2.0" />
|
||||
Version="4.14.3.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,12,2,0
|
||||
PRODUCTVERSION 4,12,2,0
|
||||
FILEVERSION 4,14,3,0
|
||||
PRODUCTVERSION 4,14,3,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Radolyn Labs"
|
||||
VALUE "FileDescription", "AyuGram Desktop"
|
||||
VALUE "FileVersion", "4.12.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
|
||||
VALUE "FileVersion", "4.14.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "AyuGram Desktop"
|
||||
VALUE "ProductVersion", "4.12.2.0"
|
||||
VALUE "ProductVersion", "4.14.3.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 4,12,2,0
|
||||
PRODUCTVERSION 4,12,2,0
|
||||
FILEVERSION 4,14,3,0
|
||||
PRODUCTVERSION 4,14,3,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.12.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
|
||||
VALUE "FileVersion", "4.14.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "AyuGram Desktop"
|
||||
VALUE "ProductVersion", "4.12.2.0"
|
||||
VALUE "ProductVersion", "4.14.3.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -6,6 +6,7 @@ For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
@@ -537,11 +537,12 @@ HANDLE _generateDumpFileAtPath(const WCHAR *path) {
|
||||
|
||||
GetLocalTime(&stLocalTime);
|
||||
|
||||
wsprintf(szFileName, L"%s%s-%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp",
|
||||
szPath, szExeName, updaterVersionStr,
|
||||
stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay,
|
||||
stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond,
|
||||
GetCurrentProcessId(), GetCurrentThreadId());
|
||||
wsprintf(
|
||||
szFileName, L"%s%s-%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp",
|
||||
szPath, szExeName, updaterVersionStr,
|
||||
stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay,
|
||||
stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond,
|
||||
GetCurrentProcessId(), GetCurrentThreadId());
|
||||
return CreateFile(szFileName, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);
|
||||
}
|
||||
|
||||
@@ -562,7 +563,7 @@ void _generateDump(EXCEPTION_POINTERS* pExceptionPointers) {
|
||||
DWORD len = GetModuleFileName(GetModuleHandle(0), szPath, maxFileLen);
|
||||
if (!len) return;
|
||||
|
||||
WCHAR *pathEnd = szPath + len;
|
||||
WCHAR *pathEnd = szPath + len;
|
||||
|
||||
if (!_wcsicmp(pathEnd - wcslen(_exeName), _exeName)) {
|
||||
wsprintf(pathEnd - wcslen(_exeName), L"");
|
||||
|
||||
@@ -415,12 +415,16 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
|
||||
const auto peer = item->history()->peer;
|
||||
const auto itemId = item->id;
|
||||
const auto id = int32(button->buttonId);
|
||||
const auto chosen = [=](not_null<PeerData*> result) {
|
||||
const auto chosen = [=](std::vector<not_null<PeerData*>> result) {
|
||||
peer->session().api().request(MTPmessages_SendBotRequestedPeer(
|
||||
peer->input,
|
||||
MTP_int(itemId),
|
||||
MTP_int(id),
|
||||
result->input
|
||||
MTP_vector_from_range(
|
||||
result
|
||||
| ranges::views::transform([](
|
||||
not_null<PeerData*> peer) {
|
||||
return MTPInputPeer(peer->input); }))
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
peer->session().api().applyUpdates(result);
|
||||
}).send();
|
||||
|
||||
@@ -25,6 +25,7 @@ struct SendOptions {
|
||||
bool silent = false;
|
||||
bool handleSupportSwitch = false;
|
||||
bool hideViaBot = false;
|
||||
crl::time ttlSeconds = 0;
|
||||
};
|
||||
[[nodiscard]] SendOptions DefaultSendWhenOnlineOptions();
|
||||
|
||||
|
||||
@@ -79,16 +79,19 @@ MTPInputMedia PrepareUploadedPhoto(
|
||||
not_null<HistoryItem*> item,
|
||||
RemoteFileInfo info) {
|
||||
using Flag = MTPDinputMediaUploadedPhoto::Flag;
|
||||
const auto spoiler = item->media()
|
||||
&& item->media()->hasSpoiler();
|
||||
const auto spoiler = item->media() && item->media()->hasSpoiler();
|
||||
const auto ttlSeconds = item->media()
|
||||
? item->media()->ttlSeconds()
|
||||
: 0;
|
||||
const auto flags = (spoiler ? Flag::f_spoiler : Flag())
|
||||
| (info.attachedStickers.empty() ? Flag() : Flag::f_stickers);
|
||||
| (info.attachedStickers.empty() ? Flag() : Flag::f_stickers)
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag());
|
||||
return MTP_inputMediaUploadedPhoto(
|
||||
MTP_flags(flags),
|
||||
info.file,
|
||||
MTP_vector<MTPInputDocument>(
|
||||
ranges::to<QVector<MTPInputDocument>>(info.attachedStickers)),
|
||||
MTP_int(0));
|
||||
MTP_int(ttlSeconds));
|
||||
}
|
||||
|
||||
MTPInputMedia PrepareUploadedDocument(
|
||||
@@ -98,12 +101,15 @@ MTPInputMedia PrepareUploadedDocument(
|
||||
return MTP_inputMediaEmpty();
|
||||
}
|
||||
using Flag = MTPDinputMediaUploadedDocument::Flag;
|
||||
const auto spoiler = item->media()
|
||||
&& item->media()->hasSpoiler();
|
||||
const auto spoiler = item->media() && item->media()->hasSpoiler();
|
||||
const auto ttlSeconds = item->media()
|
||||
? item->media()->ttlSeconds()
|
||||
: 0;
|
||||
const auto flags = (spoiler ? Flag::f_spoiler : Flag())
|
||||
| (info.thumb ? Flag::f_thumb : Flag())
|
||||
| (item->groupId() ? Flag::f_nosound_video : Flag())
|
||||
| (info.attachedStickers.empty() ? Flag::f_stickers : Flag());
|
||||
| (info.attachedStickers.empty() ? Flag::f_stickers : Flag())
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag());
|
||||
const auto document = item->media()->document();
|
||||
return MTP_inputMediaUploadedDocument(
|
||||
MTP_flags(flags),
|
||||
@@ -113,7 +119,7 @@ MTPInputMedia PrepareUploadedDocument(
|
||||
ComposeSendingDocumentAttributes(document),
|
||||
MTP_vector<MTPInputDocument>(
|
||||
ranges::to<QVector<MTPInputDocument>>(info.attachedStickers)),
|
||||
MTP_int(0));
|
||||
MTP_int(ttlSeconds));
|
||||
}
|
||||
|
||||
bool HasAttachedStickers(MTPInputMedia media) {
|
||||
|
||||
@@ -90,6 +90,7 @@ void MessagesSearch::searchRequest() {
|
||||
(_from
|
||||
? _from->input
|
||||
: MTP_inputPeerEmpty()),
|
||||
MTPInputPeer(), // saved_peer_id
|
||||
MTPint(), // top_msg_id
|
||||
MTP_inputMessagesFilterEmpty(),
|
||||
MTP_int(0), // min_date
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_peer_colors.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
|
||||
namespace Api {
|
||||
@@ -62,6 +63,16 @@ auto PeerColors::indicesValue() const
|
||||
}));
|
||||
}
|
||||
|
||||
int PeerColors::requiredLevelFor(PeerId channel, uint8 index) const {
|
||||
if (Data::DecideColorIndex(channel) == index) {
|
||||
return 0;
|
||||
} else if (const auto i = _requiredLevels.find(index)
|
||||
; i != end(_requiredLevels)) {
|
||||
return i->second;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void PeerColors::apply(const MTPDhelp_peerColors &data) {
|
||||
auto suggested = std::vector<uint8>();
|
||||
auto colors = std::make_shared<
|
||||
@@ -89,6 +100,7 @@ void PeerColors::apply(const MTPDhelp_peerColors &data) {
|
||||
};
|
||||
|
||||
const auto &list = data.vcolors().v;
|
||||
_requiredLevels.clear();
|
||||
suggested.reserve(list.size());
|
||||
for (const auto &color : list) {
|
||||
const auto &data = color.data();
|
||||
@@ -98,6 +110,9 @@ void PeerColors::apply(const MTPDhelp_peerColors &data) {
|
||||
continue;
|
||||
}
|
||||
const auto colorIndex = uint8(colorIndexBare);
|
||||
if (const auto min = data.vchannel_min_level()) {
|
||||
_requiredLevels[colorIndex] = min->v;
|
||||
}
|
||||
if (!data.is_hidden()) {
|
||||
suggested.push_back(colorIndex);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,10 @@ public:
|
||||
[[nodiscard]] auto indicesValue() const
|
||||
-> rpl::producer<Ui::ColorIndicesCompressed>;
|
||||
|
||||
[[nodiscard]] int requiredLevelFor(
|
||||
PeerId channel,
|
||||
uint8 index) const;
|
||||
|
||||
private:
|
||||
void request();
|
||||
void apply(const MTPDhelp_peerColors &data);
|
||||
@@ -38,6 +42,7 @@ private:
|
||||
mtpRequestId _requestId = 0;
|
||||
base::Timer _timer;
|
||||
rpl::variable<std::vector<uint8>> _suggested;
|
||||
base::flat_map<uint8, int> _requiredLevels;
|
||||
rpl::event_stream<> _colorIndicesChanged;
|
||||
std::unique_ptr<Ui::ColorIndicesCompressed> _colorIndicesCurrent;
|
||||
|
||||
|
||||
@@ -515,6 +515,7 @@ auto PeerPhoto::emojiList(EmojiListType type) -> EmojiListData & {
|
||||
case EmojiListType::Profile: return _profileEmojiList;
|
||||
case EmojiListType::Group: return _groupEmojiList;
|
||||
case EmojiListType::Background: return _backgroundEmojiList;
|
||||
case EmojiListType::NoChannelStatus: return _noChannelStatusEmojiList;
|
||||
}
|
||||
Unexpected("Type in PeerPhoto::emojiList.");
|
||||
}
|
||||
@@ -551,6 +552,8 @@ void PeerPhoto::requestEmojiList(EmojiListType type) {
|
||||
? send(MTPaccount_GetDefaultProfilePhotoEmojis())
|
||||
: (type == EmojiListType::Group)
|
||||
? send(MTPaccount_GetDefaultGroupPhotoEmojis())
|
||||
: (type == EmojiListType::NoChannelStatus)
|
||||
? send(MTPaccount_GetChannelRestrictedStatusEmojis())
|
||||
: send(MTPaccount_GetDefaultBackgroundEmojis());
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ public:
|
||||
Profile,
|
||||
Group,
|
||||
Background,
|
||||
NoChannelStatus,
|
||||
};
|
||||
|
||||
struct UserPhoto {
|
||||
@@ -112,6 +113,7 @@ private:
|
||||
EmojiListData _profileEmojiList;
|
||||
EmojiListData _groupEmojiList;
|
||||
EmojiListData _backgroundEmojiList;
|
||||
EmojiListData _noChannelStatusEmojiList;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace {
|
||||
|
||||
[[nodiscard]] GiftCode Parse(const MTPDpayments_checkedGiftCode &data) {
|
||||
return {
|
||||
.from = peerFromMTP(data.vfrom_id()),
|
||||
.from = data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(),
|
||||
.to = data.vto_id() ? peerFromUser(*data.vto_id()) : PeerId(),
|
||||
.giveawayId = data.vgiveaway_msg_id().value_or_empty(),
|
||||
.date = data.vdate().v,
|
||||
@@ -342,15 +342,12 @@ PremiumGiftCodeOptions::PremiumGiftCodeOptions(not_null<PeerData*> peer)
|
||||
rpl::producer<rpl::no_value, QString> PremiumGiftCodeOptions::request() {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
const auto channel = _peer->asChannel();
|
||||
if (!channel) {
|
||||
return lifetime;
|
||||
}
|
||||
|
||||
using TLOption = MTPPremiumGiftCodeOption;
|
||||
_api.request(MTPpayments_GetPremiumGiftCodeOptions(
|
||||
MTP_flags(
|
||||
MTPpayments_GetPremiumGiftCodeOptions::Flag::f_boost_peer),
|
||||
MTP_flags(_peer->isChannel()
|
||||
? MTPpayments_GetPremiumGiftCodeOptions::Flag::f_boost_peer
|
||||
: MTPpayments_GetPremiumGiftCodeOptions::Flag(0)),
|
||||
_peer->input
|
||||
)).done([=](const MTPVector<TLOption> &result) {
|
||||
auto tlMapOptions = base::flat_map<Amount, QVector<TLOption>>();
|
||||
@@ -420,6 +417,8 @@ const std::vector<int> &PremiumGiftCodeOptions::availablePresets() const {
|
||||
}
|
||||
|
||||
[[nodiscard]] int PremiumGiftCodeOptions::monthsFromPreset(int monthsIndex) {
|
||||
Expects(monthsIndex >= 0 && monthsIndex < _availablePresets.size());
|
||||
|
||||
return _optionsForOnePerson.months[monthsIndex];
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,9 @@ void InnerFillMessagePostFlags(
|
||||
not_null<PeerData*> peer,
|
||||
MessageFlags &flags) {
|
||||
const auto anonymousPost = peer->amAnonymous();
|
||||
if (ShouldSendSilent(peer, options)) {
|
||||
flags |= MessageFlag::Silent;
|
||||
}
|
||||
if (!anonymousPost || options.sendAs) {
|
||||
flags |= MessageFlag::HasFromId;
|
||||
return;
|
||||
@@ -401,9 +404,6 @@ void SendConfirmedFile(
|
||||
const auto anonymousPost = peer->amAnonymous();
|
||||
const auto silentPost = ShouldSendSilent(peer, file->to.options);
|
||||
FillMessagePostFlags(action, peer, flags);
|
||||
if (silentPost) {
|
||||
flags |= MessageFlag::Silent;
|
||||
}
|
||||
if (file->to.options.scheduled) {
|
||||
flags |= MessageFlag::IsOrWasScheduled;
|
||||
|
||||
@@ -443,11 +443,30 @@ void SendConfirmedFile(
|
||||
MTPDocument(), // alt_document
|
||||
MTPint());
|
||||
} else if (file->type == SendMediaType::Audio) {
|
||||
const auto ttlSeconds = file->to.options.ttlSeconds;
|
||||
const auto isVoice = [&] {
|
||||
return file->document.match([](const MTPDdocumentEmpty &d) {
|
||||
return false;
|
||||
}, [](const MTPDdocument &d) {
|
||||
return ranges::any_of(d.vattributes().v, [&](
|
||||
const MTPDocumentAttribute &attribute) {
|
||||
using Att = MTPDdocumentAttributeAudio;
|
||||
return attribute.match([](const Att &data) -> bool {
|
||||
return data.vflags().v & Att::Flag::f_voice;
|
||||
}, [](const auto &) {
|
||||
return false;
|
||||
});
|
||||
});
|
||||
});
|
||||
}();
|
||||
using Flag = MTPDmessageMediaDocument::Flag;
|
||||
return MTP_messageMediaDocument(
|
||||
MTP_flags(MTPDmessageMediaDocument::Flag::f_document),
|
||||
MTP_flags(Flag::f_document
|
||||
| (isVoice ? Flag::f_voice : Flag())
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())),
|
||||
file->document,
|
||||
MTPDocument(), // alt_document
|
||||
MTPint());
|
||||
MTP_int(ttlSeconds));
|
||||
} else {
|
||||
Unexpected("Type in sendFilesConfirmed.");
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_stories.h"
|
||||
#include "data/data_story.h"
|
||||
#include "history/history.h"
|
||||
#include "main/main_session.h"
|
||||
#include "statistics/statistics_data_deserialize.h"
|
||||
@@ -359,161 +361,54 @@ PublicForwards::PublicForwards(
|
||||
void PublicForwards::request(
|
||||
const Data::PublicForwardsSlice::OffsetToken &token,
|
||||
Fn<void(Data::PublicForwardsSlice)> done) {
|
||||
if (!_requestId) {
|
||||
if (_fullId.messageId) {
|
||||
requestMessage(token, std::move(done));
|
||||
} else if (_fullId.storyId) {
|
||||
requestStory(token, std::move(done));
|
||||
}
|
||||
if (_requestId) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void PublicForwards::requestMessage(
|
||||
const Data::PublicForwardsSlice::OffsetToken &token,
|
||||
Fn<void(Data::PublicForwardsSlice)> done) {
|
||||
Expects(_fullId.messageId);
|
||||
|
||||
const auto offsetPeer = channel()->owner().peer(token.fullId.peer);
|
||||
const auto tlOffsetPeer = offsetPeer
|
||||
? offsetPeer->input
|
||||
: MTP_inputPeerEmpty();
|
||||
constexpr auto kLimit = tl::make_int(100);
|
||||
_requestId = makeRequest(MTPstats_GetMessagePublicForwards(
|
||||
channel()->inputChannel,
|
||||
MTP_int(_fullId.messageId.msg),
|
||||
MTP_int(token.rate),
|
||||
tlOffsetPeer,
|
||||
MTP_int(token.fullId.msg),
|
||||
kLimit
|
||||
)).done([=, channel = channel()](const MTPmessages_Messages &result) {
|
||||
const auto channel = StatisticsRequestSender::channel();
|
||||
const auto processResult = [=](const MTPstats_PublicForwards &tl) {
|
||||
using Messages = QVector<Data::RecentPostId>;
|
||||
_requestId = 0;
|
||||
|
||||
auto nextToken = Data::PublicForwardsSlice::OffsetToken();
|
||||
const auto process = [&](const MTPVector<MTPMessage> &messages) {
|
||||
auto result = Messages();
|
||||
for (const auto &message : messages.v) {
|
||||
const auto msgId = IdFromMessage(message);
|
||||
const auto peerId = PeerFromMessage(message);
|
||||
const auto lastDate = DateFromMessage(message);
|
||||
if (const auto peer = channel->owner().peerLoaded(peerId)) {
|
||||
if (lastDate) {
|
||||
channel->owner().addNewMessage(
|
||||
message,
|
||||
MessageFlags(),
|
||||
NewMessageType::Existing);
|
||||
nextToken.fullId = { peerId, msgId };
|
||||
result.push_back({ .messageId = nextToken.fullId });
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
const auto &data = tl.data();
|
||||
auto &owner = channel->owner();
|
||||
|
||||
auto allLoaded = false;
|
||||
auto fullCount = 0;
|
||||
auto messages = result.match([&](const MTPDmessages_messages &data) {
|
||||
channel->owner().processUsers(data.vusers());
|
||||
channel->owner().processChats(data.vchats());
|
||||
auto list = process(data.vmessages());
|
||||
allLoaded = true;
|
||||
fullCount = list.size();
|
||||
return list;
|
||||
}, [&](const MTPDmessages_messagesSlice &data) {
|
||||
channel->owner().processUsers(data.vusers());
|
||||
channel->owner().processChats(data.vchats());
|
||||
auto list = process(data.vmessages());
|
||||
owner.processUsers(data.vusers());
|
||||
owner.processChats(data.vchats());
|
||||
|
||||
if (const auto nextRate = data.vnext_rate()) {
|
||||
const auto rateUpdated = (nextRate->v != token.rate);
|
||||
if (rateUpdated) {
|
||||
nextToken.rate = nextRate->v;
|
||||
} else {
|
||||
allLoaded = true;
|
||||
}
|
||||
}
|
||||
fullCount = data.vcount().v;
|
||||
return list;
|
||||
}, [&](const MTPDmessages_channelMessages &data) {
|
||||
channel->owner().processUsers(data.vusers());
|
||||
channel->owner().processChats(data.vchats());
|
||||
auto list = process(data.vmessages());
|
||||
allLoaded = true;
|
||||
fullCount = data.vcount().v;
|
||||
return list;
|
||||
}, [&](const MTPDmessages_messagesNotModified &) {
|
||||
allLoaded = true;
|
||||
return Messages();
|
||||
});
|
||||
const auto nextToken = data.vnext_offset()
|
||||
? qs(*data.vnext_offset())
|
||||
: Data::PublicForwardsSlice::OffsetToken();
|
||||
|
||||
_lastTotal = std::max(_lastTotal, fullCount);
|
||||
done({
|
||||
.list = std::move(messages),
|
||||
.total = _lastTotal,
|
||||
.allLoaded = allLoaded,
|
||||
.token = nextToken,
|
||||
});
|
||||
}).fail([=] {
|
||||
_requestId = 0;
|
||||
}).send();
|
||||
}
|
||||
|
||||
void PublicForwards::requestStory(
|
||||
const Data::PublicForwardsSlice::OffsetToken &token,
|
||||
Fn<void(Data::PublicForwardsSlice)> done) {
|
||||
Expects(_fullId.storyId);
|
||||
|
||||
constexpr auto kLimit = tl::make_int(100);
|
||||
_requestId = makeRequest(MTPstats_GetStoryPublicForwards(
|
||||
channel()->input,
|
||||
MTP_int(_fullId.storyId.story),
|
||||
MTP_string(token.storyOffset),
|
||||
kLimit
|
||||
)).done([=, channel = channel()](
|
||||
const MTPstats_PublicForwards &tlForwards) {
|
||||
using Messages = QVector<Data::RecentPostId>;
|
||||
_requestId = 0;
|
||||
|
||||
const auto &data = tlForwards.data();
|
||||
|
||||
channel->owner().processUsers(data.vusers());
|
||||
channel->owner().processChats(data.vchats());
|
||||
|
||||
const auto nextToken = Data::PublicForwardsSlice::OffsetToken({
|
||||
.storyOffset = data.vnext_offset().value_or_empty(),
|
||||
});
|
||||
|
||||
const auto allLoaded = nextToken.storyOffset.isEmpty()
|
||||
|| (nextToken.storyOffset == token.storyOffset);
|
||||
const auto fullCount = data.vcount().v;
|
||||
|
||||
auto recentList = Messages();
|
||||
auto recentList = Messages(data.vforwards().v.size());
|
||||
for (const auto &tlForward : data.vforwards().v) {
|
||||
tlForward.match([&](const MTPDpublicForwardMessage &data) {
|
||||
const auto &message = data.vmessage();
|
||||
const auto msgId = IdFromMessage(message);
|
||||
const auto peerId = PeerFromMessage(message);
|
||||
const auto lastDate = DateFromMessage(message);
|
||||
if (const auto peer = channel->owner().peerLoaded(peerId)) {
|
||||
if (const auto peer = owner.peerLoaded(peerId)) {
|
||||
if (!lastDate) {
|
||||
return;
|
||||
}
|
||||
channel->owner().addNewMessage(
|
||||
owner.addNewMessage(
|
||||
message,
|
||||
MessageFlags(),
|
||||
NewMessageType::Existing);
|
||||
recentList.push_back({ .messageId = { peerId, msgId } });
|
||||
}
|
||||
}, [&](const MTPDpublicForwardStory &data) {
|
||||
data.vstory().match([&](const MTPDstoryItem &d) {
|
||||
recentList.push_back({
|
||||
.storyId = { peerFromMTP(data.vpeer()), d.vid().v }
|
||||
});
|
||||
}, [](const auto &) {
|
||||
});
|
||||
const auto story = owner.stories().applySingle(
|
||||
peerFromMTP(data.vpeer()),
|
||||
data.vstory());
|
||||
if (story) {
|
||||
recentList.push_back({ .storyId = story->fullId() });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const auto allLoaded = nextToken.isEmpty() || (nextToken == token);
|
||||
_lastTotal = std::max(_lastTotal, fullCount);
|
||||
done({
|
||||
.list = std::move(recentList),
|
||||
@@ -521,9 +416,24 @@ void PublicForwards::requestStory(
|
||||
.allLoaded = allLoaded,
|
||||
.token = nextToken,
|
||||
});
|
||||
}).fail([=] {
|
||||
_requestId = 0;
|
||||
}).send();
|
||||
};
|
||||
|
||||
constexpr auto kLimit = tl::make_int(100);
|
||||
if (_fullId.messageId) {
|
||||
_requestId = makeRequest(MTPstats_GetMessagePublicForwards(
|
||||
channel->inputChannel,
|
||||
MTP_int(_fullId.messageId.msg),
|
||||
MTP_string(token),
|
||||
kLimit
|
||||
)).done(processResult).fail([=] { _requestId = 0; }).send();
|
||||
} else if (_fullId.storyId) {
|
||||
_requestId = makeRequest(MTPstats_GetStoryPublicForwards(
|
||||
channel->input,
|
||||
MTP_int(_fullId.storyId.story),
|
||||
MTP_string(token),
|
||||
kLimit
|
||||
)).done(processResult).fail([=] { _requestId = 0; }).send();
|
||||
}
|
||||
}
|
||||
|
||||
MessageStatistics::MessageStatistics(
|
||||
@@ -702,6 +612,7 @@ rpl::producer<rpl::no_value, QString> Boosts::request() {
|
||||
_peer->input
|
||||
)).done([=](const MTPpremium_BoostsStatus &result) {
|
||||
const auto &data = result.data();
|
||||
channel->updateLevelHint(data.vlevel().v);
|
||||
const auto hasPremium = !!data.vpremium_audience();
|
||||
const auto premiumMemberCount = hasPremium
|
||||
? std::max(0, int(data.vpremium_audience()->data().vpart().v))
|
||||
|
||||
@@ -77,13 +77,6 @@ public:
|
||||
Fn<void(Data::PublicForwardsSlice)> done);
|
||||
|
||||
private:
|
||||
void requestMessage(
|
||||
const Data::PublicForwardsSlice::OffsetToken &token,
|
||||
Fn<void(Data::PublicForwardsSlice)> done);
|
||||
void requestStory(
|
||||
const Data::PublicForwardsSlice::OffsetToken &token,
|
||||
Fn<void(Data::PublicForwardsSlice)> done);
|
||||
|
||||
const Data::RecentPostId _fullId;
|
||||
mtpRequestId _requestId = 0;
|
||||
int _lastTotal = 0;
|
||||
|
||||
@@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "mtproto/mtproto_dc_options.h"
|
||||
#include "data/notify/data_notify_settings.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "data/data_saved_messages.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_chat.h"
|
||||
@@ -44,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lang/lang_cloud_manager.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_item_helpers.h"
|
||||
#include "history/history_unread_things.h"
|
||||
#include "core/application.h"
|
||||
#include "storage/storage_account.h"
|
||||
@@ -1143,6 +1145,7 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
|
||||
? peerToMTP(_session->userPeerId())
|
||||
: MTP_peerUser(d.vuser_id())),
|
||||
MTP_peerUser(d.vuser_id()),
|
||||
MTPPeer(), // saved_peer_id
|
||||
d.vfwd_from() ? *d.vfwd_from() : MTPMessageFwdHeader(),
|
||||
MTP_long(d.vvia_bot_id().value_or_empty()),
|
||||
d.vreply_to() ? *d.vreply_to() : MTPMessageReplyHeader(),
|
||||
@@ -1174,6 +1177,7 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
|
||||
d.vid(),
|
||||
MTP_peerUser(d.vfrom_id()),
|
||||
MTP_peerChat(d.vchat_id()),
|
||||
MTPPeer(), // saved_peer_id
|
||||
d.vfwd_from() ? *d.vfwd_from() : MTPMessageFwdHeader(),
|
||||
MTP_long(d.vvia_bot_id().value_or_empty()),
|
||||
d.vreply_to() ? *d.vreply_to() : MTPMessageReplyHeader(),
|
||||
@@ -1234,11 +1238,12 @@ void Updates::applyUpdateNoPtsCheck(const MTPUpdate &update) {
|
||||
item->markMediaAndMentionRead();
|
||||
_session->data().requestItemRepaint(item);
|
||||
|
||||
if (item->out()
|
||||
&& item->history()->peer->isUser()
|
||||
&& !requestingDifference()) {
|
||||
item->history()->peer->asUser()->madeAction(
|
||||
base::unixtime::now());
|
||||
if (item->out()) {
|
||||
const auto user = item->history()->peer->asUser();
|
||||
if (user && !requestingDifference()) {
|
||||
user->madeAction(base::unixtime::now());
|
||||
}
|
||||
ClearMediaAsExpired(item);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -2236,6 +2241,16 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_updatePinnedSavedDialogs: {
|
||||
session().data().savedMessages().apply(
|
||||
update.c_updatePinnedSavedDialogs());
|
||||
} break;
|
||||
|
||||
case mtpc_updateSavedDialogPinned: {
|
||||
session().data().savedMessages().apply(
|
||||
update.c_updateSavedDialogPinned());
|
||||
} break;
|
||||
|
||||
case mtpc_updateChannel: {
|
||||
auto &d = update.c_updateChannel();
|
||||
if (const auto channel = session().data().channelLoaded(d.vchannel_id())) {
|
||||
|
||||
@@ -39,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_folder.h"
|
||||
#include "data/data_forum_topic.h"
|
||||
#include "data/data_forum.h"
|
||||
#include "data/data_saved_sublist.h"
|
||||
#include "data/data_search_controller.h"
|
||||
#include "data/data_scheduled_messages.h"
|
||||
#include "data/data_session.h"
|
||||
@@ -225,11 +226,11 @@ void ApiWrap::setupSupportMode() {
|
||||
void ApiWrap::requestChangelog(
|
||||
const QString &sinceVersion,
|
||||
Fn<void(const MTPUpdates &result)> callback) {
|
||||
request(MTPhelp_GetAppChangelog(
|
||||
MTP_string(sinceVersion)
|
||||
)).done(
|
||||
callback
|
||||
).send();
|
||||
//request(MTPhelp_GetAppChangelog(
|
||||
// MTP_string(sinceVersion)
|
||||
//)).done(
|
||||
// callback
|
||||
//).send();
|
||||
}
|
||||
|
||||
void ApiWrap::refreshTopPromotion() {
|
||||
@@ -444,6 +445,26 @@ void ApiWrap::savePinnedOrder(not_null<Data::Forum*> forum) {
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ApiWrap::savePinnedOrder(not_null<Data::SavedMessages*> saved) {
|
||||
const auto &order = _session->data().pinnedChatsOrder(saved);
|
||||
const auto input = [](Dialogs::Key key) {
|
||||
if (const auto sublist = key.sublist()) {
|
||||
return MTP_inputDialogPeer(sublist->peer()->input);
|
||||
}
|
||||
Unexpected("Key type in pinnedDialogsOrder().");
|
||||
};
|
||||
auto peers = QVector<MTPInputDialogPeer>();
|
||||
peers.reserve(order.size());
|
||||
ranges::transform(
|
||||
order,
|
||||
ranges::back_inserter(peers),
|
||||
input);
|
||||
request(MTPmessages_ReorderPinnedSavedDialogs(
|
||||
MTP_flags(MTPmessages_ReorderPinnedSavedDialogs::Flag::f_force),
|
||||
MTP_vector(peers)
|
||||
)).send();
|
||||
}
|
||||
|
||||
void ApiWrap::toggleHistoryArchived(
|
||||
not_null<History*> history,
|
||||
bool archived,
|
||||
@@ -1333,6 +1354,12 @@ void ApiWrap::markContentsRead(
|
||||
markedIds.push_back(MTP_int(item->id));
|
||||
}
|
||||
}
|
||||
|
||||
const auto settings = &AyuSettings::getInstance();
|
||||
if (!settings->sendReadMessages) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!markedIds.isEmpty()) {
|
||||
request(MTPmessages_ReadMessageContents(
|
||||
MTP_vector<MTPint>(markedIds)
|
||||
@@ -1352,6 +1379,12 @@ void ApiWrap::markContentsRead(not_null<HistoryItem*> item) {
|
||||
if (!item->markContentsRead(true) || !item->isRegular()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto settings = &AyuSettings::getInstance();
|
||||
if (!settings->sendReadMessages) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto ids = MTP_vector<MTPint>(1, MTP_int(item->id));
|
||||
if (const auto channel = item->history()->peer->asChannel()) {
|
||||
request(MTPchannels_ReadMessageContents(
|
||||
|
||||
@@ -34,6 +34,7 @@ class Forum;
|
||||
class ForumTopic;
|
||||
class Thread;
|
||||
class Story;
|
||||
class SavedMessages;
|
||||
} // namespace Data
|
||||
|
||||
namespace InlineBots {
|
||||
@@ -152,6 +153,7 @@ public:
|
||||
|
||||
void savePinnedOrder(Data::Folder *folder);
|
||||
void savePinnedOrder(not_null<Data::Forum*> forum);
|
||||
void savePinnedOrder(not_null<Data::SavedMessages*> saved);
|
||||
void toggleHistoryArchived(
|
||||
not_null<History*> history,
|
||||
bool archived,
|
||||
|
||||
@@ -11,6 +11,15 @@
|
||||
#include "core/core_settings.h"
|
||||
#include "lang/lang_instance.h"
|
||||
|
||||
// hard-coded languages
|
||||
std::map<QString, QString> langMapping = {
|
||||
{"pt-br", "pt"},
|
||||
{"zh-hans-beta", "zh-hans"},
|
||||
{"zh-hant-beta", "zh-hant"},
|
||||
{"zh-hans-raw", "zh-hans"},
|
||||
{"zh-hant-raw", "zh-hant"},
|
||||
};
|
||||
|
||||
CustomLangPack *CustomLangPack::instance = nullptr;
|
||||
|
||||
CustomLangPack::CustomLangPack() = default;
|
||||
@@ -30,11 +39,7 @@ void CustomLangPack::fetchCustomLangPack(const QString &langPackId, const QStrin
|
||||
{
|
||||
LOG(("Current Language pack ID: %1, Base ID: %2").arg(langPackId, langPackBaseId));
|
||||
|
||||
auto finalLangPackId = langPackId;
|
||||
if (finalLangPackId == qsl("pt-br")) {
|
||||
// meh
|
||||
finalLangPackId = qsl("pt");
|
||||
}
|
||||
auto finalLangPackId = langMapping.contains(langPackId) ? langMapping[langPackId] : langPackId;
|
||||
|
||||
const auto proxy = Core::App().settings().proxy().isEnabled()
|
||||
? Core::App().settings().proxy().selected()
|
||||
@@ -44,13 +49,15 @@ void CustomLangPack::fetchCustomLangPack(const QString &langPackId, const QStrin
|
||||
networkManager.setProxy(LocaleProxy);
|
||||
}
|
||||
|
||||
// using `jsdelivr` since China (...and maybe other?) users have some problems with GitHub
|
||||
// https://crowdin.com/project/ayugram/discussions/6
|
||||
QUrl url;
|
||||
if (!finalLangPackId.isEmpty() && !langPackBaseId.isEmpty() && !needFallback) {
|
||||
url.setUrl(qsl("https://raw.githubusercontent.com/AyuGram/Languages/l10n_main/values/langs/%1/Shared.json").arg(
|
||||
url.setUrl(qsl("https://cdn.jsdelivr.net/gh/AyuGram/Languages@l10n_main/values/langs/%1/Shared.json").arg(
|
||||
finalLangPackId));
|
||||
}
|
||||
else {
|
||||
url.setUrl(qsl("https://raw.githubusercontent.com/AyuGram/Languages/l10n_main/values/langs/%1/Shared.json").arg(
|
||||
url.setUrl(qsl("https://cdn.jsdelivr.net/gh/AyuGram/Languages@l10n_main/values/langs/%1/Shared.json").arg(
|
||||
needFallback ? langPackBaseId : finalLangPackId));
|
||||
}
|
||||
_chkReply = networkManager.get(QNetworkRequest(url));
|
||||
|
||||
@@ -59,54 +59,64 @@ void initialize()
|
||||
|
||||
settings = AyuGramSettings();
|
||||
|
||||
sendReadMessagesReactive.value() | rpl::filter([=](bool val)
|
||||
{
|
||||
return (val != settings->sendReadMessages);
|
||||
}) | start_with_next([=](bool val)
|
||||
{
|
||||
ghostModeEnabled =
|
||||
ghostModeEnabled_util(settings.value());
|
||||
}, lifetime);
|
||||
sendReadMessagesReactive.value() | rpl::filter(
|
||||
[=](bool val)
|
||||
{
|
||||
return (val != settings->sendReadMessages);
|
||||
}) | start_with_next(
|
||||
[=](bool val)
|
||||
{
|
||||
ghostModeEnabled =
|
||||
ghostModeEnabled_util(settings.value());
|
||||
}, lifetime);
|
||||
// ..
|
||||
sendReadStoriesReactive.value() | rpl::filter([=](bool val)
|
||||
{
|
||||
return (val != settings->sendReadStories);
|
||||
}) | start_with_next([=](bool val)
|
||||
{
|
||||
ghostModeEnabled =
|
||||
ghostModeEnabled_util(settings.value());
|
||||
}, lifetime);
|
||||
sendReadStoriesReactive.value() | rpl::filter(
|
||||
[=](bool val)
|
||||
{
|
||||
return (val != settings->sendReadStories);
|
||||
}) | start_with_next(
|
||||
[=](bool val)
|
||||
{
|
||||
ghostModeEnabled =
|
||||
ghostModeEnabled_util(settings.value());
|
||||
}, lifetime);
|
||||
// ..
|
||||
sendOnlinePacketsReactive.value() | rpl::filter([=](bool val)
|
||||
{
|
||||
return (val != settings->sendOnlinePackets);
|
||||
}) | start_with_next([=](bool val)
|
||||
{
|
||||
ghostModeEnabled =
|
||||
ghostModeEnabled_util(settings
|
||||
.value());
|
||||
}, lifetime);
|
||||
sendOnlinePacketsReactive.value() | rpl::filter(
|
||||
[=](bool val)
|
||||
{
|
||||
return (val != settings->sendOnlinePackets);
|
||||
}) | start_with_next(
|
||||
[=](bool val)
|
||||
{
|
||||
ghostModeEnabled =
|
||||
ghostModeEnabled_util(settings
|
||||
.value());
|
||||
}, lifetime);
|
||||
// ..
|
||||
sendUploadProgressReactive.value() | rpl::filter([=](bool val)
|
||||
{
|
||||
return (val != settings->sendUploadProgress);
|
||||
}) | start_with_next([=](bool val)
|
||||
{
|
||||
ghostModeEnabled =
|
||||
ghostModeEnabled_util(settings
|
||||
.value());
|
||||
}, lifetime);
|
||||
sendUploadProgressReactive.value() | rpl::filter(
|
||||
[=](bool val)
|
||||
{
|
||||
return (val != settings->sendUploadProgress);
|
||||
}) | start_with_next(
|
||||
[=](bool val)
|
||||
{
|
||||
ghostModeEnabled =
|
||||
ghostModeEnabled_util(settings
|
||||
.value());
|
||||
}, lifetime);
|
||||
// ..
|
||||
sendOfflinePacketAfterOnlineReactive.value() | rpl::filter([=](bool val)
|
||||
{
|
||||
return (val
|
||||
!= settings->sendOfflinePacketAfterOnline);
|
||||
}) | start_with_next([=](bool val)
|
||||
{
|
||||
ghostModeEnabled =
|
||||
ghostModeEnabled_util(
|
||||
settings.value());
|
||||
}, lifetime);
|
||||
sendOfflinePacketAfterOnlineReactive.value() | rpl::filter(
|
||||
[=](bool val)
|
||||
{
|
||||
return (val
|
||||
!= settings->sendOfflinePacketAfterOnline);
|
||||
}) | start_with_next(
|
||||
[=](bool val)
|
||||
{
|
||||
ghostModeEnabled =
|
||||
ghostModeEnabled_util(
|
||||
settings.value());
|
||||
}, lifetime);
|
||||
}
|
||||
|
||||
void postinitialize()
|
||||
@@ -139,15 +149,20 @@ void load()
|
||||
|
||||
initialize();
|
||||
|
||||
json p;
|
||||
file >> p;
|
||||
file.close();
|
||||
|
||||
try {
|
||||
settings = p.get<AyuGramSettings>();
|
||||
json p;
|
||||
file >> p;
|
||||
file.close();
|
||||
|
||||
try {
|
||||
settings = p.get<AyuGramSettings>();
|
||||
}
|
||||
catch (...) {
|
||||
LOG(("AyuGramSettings: failed to parse settings file"));
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
LOG(("AyuGramSettings: failed to parse settings file"));
|
||||
LOG(("AyuGramSettings: failed to read settings file (not json-like)"));
|
||||
}
|
||||
postinitialize();
|
||||
}
|
||||
@@ -255,6 +270,11 @@ void AyuGramSettings::set_hideSimilarChannels(bool val)
|
||||
hideSimilarChannels = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_uploadSpeedBoost(bool val)
|
||||
{
|
||||
uploadSpeedBoost = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_disableNotificationsDelay(bool val)
|
||||
{
|
||||
disableNotificationsDelay = val;
|
||||
@@ -275,7 +295,8 @@ void AyuGramSettings::set_appIcon(QString val)
|
||||
appIcon = std::move(val);
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_simpleQuotesAndReplies(bool val) {
|
||||
void AyuGramSettings::set_simpleQuotesAndReplies(bool val)
|
||||
{
|
||||
simpleQuotesAndReplies = val;
|
||||
}
|
||||
|
||||
@@ -296,6 +317,31 @@ void AyuGramSettings::set_recentStickersCount(int val)
|
||||
recentStickersCount = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_showReactionsPanelInContextMenu(int val)
|
||||
{
|
||||
showReactionsPanelInContextMenu = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_showViewsPanelInContextMenu(int val)
|
||||
{
|
||||
showViewsPanelInContextMenu = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_showHideMessageInContextMenu(int val)
|
||||
{
|
||||
showHideMessageInContextMenu = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_showUserMessagesInContextMenu(int val)
|
||||
{
|
||||
showUserMessagesInContextMenu = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_showMessageDetailsInContextMenu(int val)
|
||||
{
|
||||
showMessageDetailsInContextMenu = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_showLReadToggleInDrawer(bool val)
|
||||
{
|
||||
showLReadToggleInDrawer = val;
|
||||
@@ -342,9 +388,9 @@ void AyuGramSettings::set_showPeerId(int val)
|
||||
showPeerIdReactive = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_showMessageSeconds(bool val)
|
||||
void AyuGramSettings::set_hideNotificationCounters(bool val)
|
||||
{
|
||||
showMessageSeconds = val;
|
||||
hideNotificationCounters = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_hideAllChatsFolder(bool val)
|
||||
@@ -352,6 +398,11 @@ void AyuGramSettings::set_hideAllChatsFolder(bool val)
|
||||
hideAllChatsFolder = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_showMessageSeconds(bool val)
|
||||
{
|
||||
showMessageSeconds = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_stickerConfirmation(bool val)
|
||||
{
|
||||
stickerConfirmation = val;
|
||||
|
||||
@@ -9,27 +9,12 @@
|
||||
#include "lang_auto.h"
|
||||
#include "ayu/libs/json.hpp"
|
||||
#include "ayu/libs/json_ext.hpp"
|
||||
#include "ayu/ui/ayu_assets.h"
|
||||
#include "rpl/producer.h"
|
||||
|
||||
namespace AyuSettings
|
||||
{
|
||||
|
||||
const auto DEFAULT_ICON = QString("default");
|
||||
|
||||
const auto ALT_ICON = QString("alt");
|
||||
|
||||
const auto DISCORD_ICON = QString("discord");
|
||||
|
||||
const auto SPOTIFY_ICON = QString("spotify");
|
||||
|
||||
const auto EXTERA_ICON = QString("extera");
|
||||
|
||||
const auto NOTHING_ICON = QString("nothing");
|
||||
|
||||
const auto BARD_ICON = QString("bard");
|
||||
|
||||
const auto YAPLUS_ICON = QString("yaplus");
|
||||
|
||||
class AyuGramSettings
|
||||
{
|
||||
public:
|
||||
@@ -56,17 +41,30 @@ public:
|
||||
disableStories = false;
|
||||
collapseSimilarChannels = true;
|
||||
hideSimilarChannels = false;
|
||||
|
||||
uploadSpeedBoost = false;
|
||||
|
||||
disableNotificationsDelay = false;
|
||||
localPremium = false;
|
||||
copyUsernameAsLink = true;
|
||||
|
||||
// ~ Customization
|
||||
appIcon = DEFAULT_ICON;
|
||||
appIcon = AyuAssets::DEFAULT_ICON;
|
||||
simpleQuotesAndReplies = true;
|
||||
deletedMark = "🧹";
|
||||
editedMark = tr::lng_edited(tr::now);
|
||||
recentStickersCount = 50;
|
||||
|
||||
// context menu items
|
||||
// 0 - hide
|
||||
// 1 - show normally
|
||||
// 2 - show with SHIFT or CTRL pressed
|
||||
showReactionsPanelInContextMenu = 1;
|
||||
showViewsPanelInContextMenu = 1;
|
||||
showHideMessageInContextMenu = 0;
|
||||
showUserMessagesInContextMenu = 2;
|
||||
showMessageDetailsInContextMenu = 2;
|
||||
|
||||
showLReadToggleInDrawer = true;
|
||||
showSReadToggleInDrawer = true;
|
||||
showGhostToggleInDrawer = true;
|
||||
@@ -78,14 +76,15 @@ public:
|
||||
mainFont = "";
|
||||
monoFont = "";
|
||||
|
||||
hideNotificationCounters = false;
|
||||
hideAllChatsFolder = false;
|
||||
|
||||
/*
|
||||
* showPeerId = 0 means no ID shown
|
||||
* showPeerId = 1 means ID shown as for Telegram API devs
|
||||
* showPeerId = 2 means ID shown as for Bot API devs (-100)
|
||||
*/
|
||||
showPeerId = 2;
|
||||
|
||||
hideAllChatsFolder = false;
|
||||
showMessageSeconds = false;
|
||||
|
||||
// ~ Confirmations
|
||||
@@ -99,114 +98,118 @@ public:
|
||||
bool sendOnlinePackets;
|
||||
bool sendUploadProgress;
|
||||
bool sendOfflinePacketAfterOnline;
|
||||
|
||||
bool markReadAfterSend;
|
||||
bool markReadAfterReaction;
|
||||
bool markReadAfterPoll;
|
||||
bool useScheduledMessages;
|
||||
|
||||
bool saveDeletedMessages;
|
||||
bool saveMessagesHistory;
|
||||
|
||||
bool disableAds;
|
||||
bool disableStories;
|
||||
bool collapseSimilarChannels;
|
||||
bool hideSimilarChannels;
|
||||
|
||||
bool uploadSpeedBoost;
|
||||
|
||||
bool disableNotificationsDelay;
|
||||
bool localPremium;
|
||||
bool copyUsernameAsLink;
|
||||
|
||||
QString appIcon;
|
||||
bool simpleQuotesAndReplies;
|
||||
QString deletedMark;
|
||||
QString editedMark;
|
||||
int recentStickersCount;
|
||||
|
||||
int showReactionsPanelInContextMenu;
|
||||
int showViewsPanelInContextMenu;
|
||||
int showHideMessageInContextMenu;
|
||||
int showUserMessagesInContextMenu;
|
||||
int showMessageDetailsInContextMenu;
|
||||
|
||||
bool showLReadToggleInDrawer;
|
||||
bool showSReadToggleInDrawer;
|
||||
bool showGhostToggleInDrawer;
|
||||
bool showStreamerToggleInDrawer;
|
||||
|
||||
bool showGhostToggleInTray;
|
||||
bool showStreamerToggleInTray;
|
||||
|
||||
QString mainFont;
|
||||
QString monoFont;
|
||||
int showPeerId;
|
||||
|
||||
bool hideNotificationCounters;
|
||||
bool hideAllChatsFolder;
|
||||
|
||||
int showPeerId;
|
||||
bool showMessageSeconds;
|
||||
|
||||
bool stickerConfirmation;
|
||||
bool gifConfirmation;
|
||||
bool voiceConfirmation;
|
||||
|
||||
public:
|
||||
void set_sendReadMessages(bool val);
|
||||
|
||||
void set_sendReadStories(bool val);
|
||||
|
||||
void set_sendOnlinePackets(bool val);
|
||||
|
||||
void set_sendUploadProgress(bool val);
|
||||
|
||||
void set_sendOfflinePacketAfterOnline(bool val);
|
||||
|
||||
void set_ghostModeEnabled(bool val);
|
||||
|
||||
void set_markReadAfterSend(bool val);
|
||||
|
||||
void set_markReadAfterReaction(bool val);
|
||||
|
||||
void set_markReadAfterPoll(bool val);
|
||||
|
||||
void set_useScheduledMessages(bool val);
|
||||
|
||||
void set_saveDeletedMessages(bool val);
|
||||
|
||||
void set_saveMessagesHistory(bool val);
|
||||
|
||||
void set_disableAds(bool val);
|
||||
|
||||
void set_disableStories(bool val);
|
||||
|
||||
void set_collapseSimilarChannels(bool val);
|
||||
|
||||
void set_hideSimilarChannels(bool val);
|
||||
|
||||
void set_uploadSpeedBoost(bool val);
|
||||
|
||||
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);
|
||||
|
||||
void set_deletedMark(QString val);
|
||||
|
||||
void set_editedMark(QString val);
|
||||
|
||||
void set_recentStickersCount(int val);
|
||||
|
||||
void set_showReactionsPanelInContextMenu(int val);
|
||||
void set_showViewsPanelInContextMenu(int val);
|
||||
void set_showHideMessageInContextMenu(int val);
|
||||
void set_showUserMessagesInContextMenu(int val);
|
||||
void set_showMessageDetailsInContextMenu(int val);
|
||||
|
||||
void set_showLReadToggleInDrawer(bool val);
|
||||
|
||||
void set_showSReadToggleInDrawer(bool val);
|
||||
|
||||
void set_showGhostToggleInDrawer(bool val);
|
||||
|
||||
void set_showStreamerToggleInDrawer(bool val);
|
||||
|
||||
void set_showGhostToggleInTray(bool val);
|
||||
|
||||
void set_showStreamerToggleInTray(bool val);
|
||||
|
||||
void set_mainFont(QString val);
|
||||
|
||||
void set_monoFont(QString val);
|
||||
|
||||
void set_showPeerId(int val);
|
||||
|
||||
void set_showMessageSeconds(bool val);
|
||||
|
||||
void set_hideNotificationCounters(bool val);
|
||||
void set_hideAllChatsFolder(bool val);
|
||||
|
||||
void set_showPeerId(int val);
|
||||
void set_showMessageSeconds(bool val);
|
||||
|
||||
void set_stickerConfirmation(bool val);
|
||||
|
||||
void set_gifConfirmation(bool val);
|
||||
|
||||
void set_voiceConfirmation(bool val);
|
||||
};
|
||||
|
||||
@@ -227,6 +230,7 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(
|
||||
disableStories,
|
||||
collapseSimilarChannels,
|
||||
hideSimilarChannels,
|
||||
uploadSpeedBoost,
|
||||
disableNotificationsDelay,
|
||||
localPremium,
|
||||
copyUsernameAsLink,
|
||||
@@ -235,6 +239,11 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(
|
||||
deletedMark,
|
||||
editedMark,
|
||||
recentStickersCount,
|
||||
showReactionsPanelInContextMenu,
|
||||
showViewsPanelInContextMenu,
|
||||
showHideMessageInContextMenu,
|
||||
showUserMessagesInContextMenu,
|
||||
showMessageDetailsInContextMenu,
|
||||
showLReadToggleInDrawer,
|
||||
showSReadToggleInDrawer,
|
||||
showGhostToggleInDrawer,
|
||||
@@ -243,9 +252,10 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(
|
||||
showStreamerToggleInTray,
|
||||
mainFont,
|
||||
monoFont,
|
||||
hideNotificationCounters,
|
||||
hideAllChatsFolder,
|
||||
showPeerId,
|
||||
showMessageSeconds,
|
||||
hideAllChatsFolder,
|
||||
stickerConfirmation,
|
||||
gifConfirmation,
|
||||
voiceConfirmation
|
||||
@@ -254,17 +264,14 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(
|
||||
AyuGramSettings &getInstance();
|
||||
|
||||
void load();
|
||||
|
||||
void save();
|
||||
|
||||
rpl::producer<QString> get_deletedMarkReactive();
|
||||
|
||||
rpl::producer<QString> get_editedMarkReactive();
|
||||
|
||||
rpl::producer<int> get_showPeerIdReactive();
|
||||
|
||||
bool get_ghostModeEnabled();
|
||||
|
||||
rpl::producer<bool> get_ghostModeEnabledReactive();
|
||||
|
||||
}
|
||||
|
||||
@@ -8,4 +8,31 @@
|
||||
|
||||
namespace AyuState
|
||||
{
|
||||
|
||||
std::unordered_map<PeerId, std::unordered_set<MsgId>> hiddenMessages;
|
||||
|
||||
void hide(PeerId peerId, MsgId messageId)
|
||||
{
|
||||
hiddenMessages[peerId].insert(messageId);
|
||||
}
|
||||
|
||||
void hide(not_null<HistoryItem *> item)
|
||||
{
|
||||
hide(item->history()->peer->id, item->id);
|
||||
}
|
||||
|
||||
bool isHidden(PeerId peerId, MsgId messageId)
|
||||
{
|
||||
auto it = hiddenMessages.find(peerId);
|
||||
if (it != hiddenMessages.end()) {
|
||||
return it->second.find(messageId) != it->second.end();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isHidden(not_null<HistoryItem *> item)
|
||||
{
|
||||
return isHidden(item->history()->peer->id, item->id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,37 +8,15 @@
|
||||
|
||||
#include "ayu_settings.h"
|
||||
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
|
||||
namespace AyuState
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
class AyuStateVariable
|
||||
{
|
||||
public:
|
||||
bool val;
|
||||
int resetAfter;
|
||||
};
|
||||
|
||||
AyuStateVariable allowSendReadPacket;
|
||||
|
||||
bool processVariable(AyuStateVariable &variable)
|
||||
{
|
||||
if (variable.resetAfter == -1) {
|
||||
return variable.val;
|
||||
}
|
||||
|
||||
variable.resetAfter -= 1;
|
||||
auto val = variable.val;
|
||||
|
||||
if (variable.resetAfter == 0) {
|
||||
variable.val = false;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
}
|
||||
void hide(PeerId peerId, MsgId messageId);
|
||||
void hide(not_null<HistoryItem *> item);
|
||||
bool isHidden(PeerId peerId, MsgId messageId);
|
||||
bool isHidden(not_null<HistoryItem *> item);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
// This is the source code of AyuGram for Desktop.
|
||||
//
|
||||
// We do not and cannot prevent the use of our code,
|
||||
// but be respectful and credit the original author.
|
||||
//
|
||||
// Copyright @Radolyn, 2023
|
||||
#include "ayu/ayu_url_handlers.h"
|
||||
|
||||
#include "base/qthelp_url.h"
|
||||
|
||||
#include "lang_auto.h"
|
||||
#include "mainwindow.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ayu/utils/telegram_helpers.h"
|
||||
#include "boxes/abstract_box.h"
|
||||
#include "core/application.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
|
||||
namespace AyuUrlHandlers
|
||||
{
|
||||
|
||||
bool ResolveUser(
|
||||
Window::SessionController *controller,
|
||||
const Match &match,
|
||||
const QVariant &context)
|
||||
{
|
||||
if (!controller) {
|
||||
return false;
|
||||
}
|
||||
const auto params = url_parse_params(
|
||||
match->captured(1),
|
||||
qthelp::UrlParamNameTransform::ToLower);
|
||||
const auto userId = params.value(qsl("id")).toLongLong();
|
||||
if (!userId) {
|
||||
return false;
|
||||
}
|
||||
const auto peer = controller->session().data().peerLoaded(static_cast<PeerId>(userId));
|
||||
if (peer != nullptr) {
|
||||
controller->showPeerInfo(peer);
|
||||
return true;
|
||||
}
|
||||
|
||||
searchById(userId, &controller->session(), false, [=](const QString &title, UserData *data)
|
||||
{
|
||||
if (data) {
|
||||
controller->showPeerInfo(data);
|
||||
return;
|
||||
}
|
||||
|
||||
Core::App().hideMediaView();
|
||||
Ui::show(Ui::MakeInformBox(tr::ayu_UserNotFoundMessage()));
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HandleAyu(
|
||||
Window::SessionController *controller,
|
||||
const Match &match,
|
||||
const QVariant &context)
|
||||
{
|
||||
if (!controller) {
|
||||
return false;
|
||||
}
|
||||
controller->showToast(QString(":3"), 500);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// This is the source code of AyuGram for Desktop.
|
||||
//
|
||||
// We do not and cannot prevent the use of our code,
|
||||
// but be respectful and credit the original author.
|
||||
//
|
||||
// Copyright @Radolyn, 2023
|
||||
#pragma once
|
||||
|
||||
#include "window/window_session_controller.h"
|
||||
#include "base/qthelp_regex.h"
|
||||
|
||||
namespace AyuUrlHandlers
|
||||
{
|
||||
|
||||
using Match = qthelp::RegularExpressionMatch;
|
||||
|
||||
bool ResolveUser(
|
||||
Window::SessionController *controller,
|
||||
const Match &match,
|
||||
const QVariant &context);
|
||||
|
||||
bool HandleAyu(
|
||||
Window::SessionController *controller,
|
||||
const Match &match,
|
||||
const QVariant &context);
|
||||
|
||||
}
|
||||
@@ -13,6 +13,9 @@ static QImage LAST_LOADED;
|
||||
|
||||
static QImage LAST_LOADED_NO_MARGIN;
|
||||
|
||||
namespace AyuAssets
|
||||
{
|
||||
|
||||
void loadAppIco()
|
||||
{
|
||||
auto settings = &AyuSettings::getInstance();
|
||||
@@ -62,3 +65,5 @@ QImage currentAppLogoNoMargin()
|
||||
loadIcons();
|
||||
return LAST_LOADED_NO_MARGIN;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,27 @@
|
||||
// Copyright @Radolyn, 2023
|
||||
#pragma once
|
||||
|
||||
#define ICON(name, value) const auto name##_ICON = QStringLiteral(value)
|
||||
|
||||
namespace AyuAssets
|
||||
{
|
||||
|
||||
ICON(DEFAULT, "default");
|
||||
|
||||
ICON(ALT, "alt");
|
||||
|
||||
ICON(DISCORD, "discord");
|
||||
|
||||
ICON(SPOTIFY, "spotify");
|
||||
|
||||
ICON(EXTERA, "extera");
|
||||
|
||||
ICON(NOTHING, "nothing");
|
||||
|
||||
ICON(BARD, "bard");
|
||||
|
||||
ICON(YAPLUS, "yaplus");
|
||||
|
||||
void loadAppIco();
|
||||
|
||||
QImage loadPreview(QString name);
|
||||
@@ -13,3 +34,5 @@ QImage loadPreview(QString name);
|
||||
QString currentAppLogoName();
|
||||
QImage currentAppLogo();
|
||||
QImage currentAppLogoNoMargin();
|
||||
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ using "ui/widgets/widgets.style";
|
||||
|
||||
ayuGhostIcon: icon {{ "ayu/ghost", menuIconColor }};
|
||||
ayuMenuIcon: icon {{ "ayu/ayu_menu", menuIconColor }};
|
||||
ayuEditsHistoryIcon: icon {{ "ayu/edits_history", menuIconColor }};
|
||||
ayuLReadMenuIcon: icon {{ "ayu/lread", menuIconColor }};
|
||||
ayuSReadMenuIcon: icon {{ "ayu/sread", menuIconColor }};
|
||||
ayuStreamerModeMenuIcon: icon {{ "ayu/streamer", menuIconColor }};
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
namespace AyuUi
|
||||
{
|
||||
|
||||
ConfirmationBox::ConfirmationBox(
|
||||
ServerReadConfirmationBox::ServerReadConfirmationBox(
|
||||
QWidget *,
|
||||
not_null<Window::SessionController *> controller)
|
||||
: _controller(controller)
|
||||
@@ -26,7 +26,7 @@ ConfirmationBox::ConfirmationBox(
|
||||
//
|
||||
}
|
||||
|
||||
void ConfirmationBox::prepare()
|
||||
void ServerReadConfirmationBox::prepare()
|
||||
{
|
||||
_text.create(this, tr::ayu_ReadConfirmationBoxQuestion(), st::boxLabel);
|
||||
|
||||
@@ -45,7 +45,7 @@ void ConfirmationBox::prepare()
|
||||
{ closeBox(); });
|
||||
}
|
||||
|
||||
void ConfirmationBox::resizeEvent(QResizeEvent *e)
|
||||
void ServerReadConfirmationBox::resizeEvent(QResizeEvent *e)
|
||||
{
|
||||
BoxContent::resizeEvent(e);
|
||||
|
||||
@@ -53,7 +53,7 @@ void ConfirmationBox::resizeEvent(QResizeEvent *e)
|
||||
_text->moveToLeft(padding.left(), padding.top());
|
||||
}
|
||||
|
||||
void ConfirmationBox::ReadAllPeers()
|
||||
void ServerReadConfirmationBox::ReadAllPeers()
|
||||
{
|
||||
auto settings = &AyuSettings::getInstance();
|
||||
auto prev = settings->sendReadMessages;
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
namespace AyuUi
|
||||
{
|
||||
|
||||
class ConfirmationBox : public Ui::BoxContent
|
||||
class ServerReadConfirmationBox : public Ui::BoxContent
|
||||
{
|
||||
public:
|
||||
ConfirmationBox(QWidget *, not_null<Window::SessionController *> controller);
|
||||
ServerReadConfirmationBox(QWidget *, not_null<Window::SessionController *> controller);
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
|
||||
@@ -5,14 +5,18 @@
|
||||
//
|
||||
// Copyright @Radolyn, 2023
|
||||
#include "ayu/ui/context_menu/context_menu.h"
|
||||
#include "ayu/utils/qt_key_modifiers_extended.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "lang_auto.h"
|
||||
#include "ayu/ayu_state.h"
|
||||
#include "ayu/messages/ayu_messages_controller.h"
|
||||
#include "ayu/ui/boxes/message_history_box.h"
|
||||
#include "ayu/ui/context_menu/menu_item_subtext.h"
|
||||
|
||||
#include "history/history_inner_widget.h"
|
||||
#include "settings/settings_common.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "mainwidget.h"
|
||||
#include "core/mime_type.h"
|
||||
#include "styles/style_ayu_icons.h"
|
||||
#include "window/window_peer_menu.h"
|
||||
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
|
||||
#include "ayu/ui/sections/edited/edited_log_section.h"
|
||||
@@ -22,6 +26,11 @@
|
||||
namespace AyuUi
|
||||
{
|
||||
|
||||
bool needToShowItem(int state)
|
||||
{
|
||||
return state == 1 || state == 2 && base::IsExtendedContextMenuModifierPressed();
|
||||
}
|
||||
|
||||
void AddHistoryAction(not_null<Ui::PopupMenu *> menu, HistoryItem *item)
|
||||
{
|
||||
if (AyuMessages::getInstance().hasRevisions(item)) {
|
||||
@@ -29,26 +38,241 @@ void AddHistoryAction(not_null<Ui::PopupMenu *> menu, HistoryItem *item)
|
||||
{
|
||||
item->history()->session().tryResolveWindow()
|
||||
->showSection(std::make_shared<EditedLog::SectionMemento>(item->history()->peer, item));
|
||||
}, &st::menuIconInfo);
|
||||
}, &st::ayuEditsHistoryIcon);
|
||||
}
|
||||
}
|
||||
|
||||
void AddHideMessageAction(not_null<Ui::PopupMenu *> menu, HistoryItem *item)
|
||||
{
|
||||
const auto settings = &AyuSettings::getInstance();
|
||||
if (!needToShowItem(settings->showHideMessageInContextMenu)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto history = item->history();
|
||||
menu->addAction(tr::ayu_ContextHideMessage(tr::now), [=]()
|
||||
{
|
||||
const auto initSaveDeleted = settings->saveDeletedMessages;
|
||||
item->destroy();
|
||||
history->requestChatListMessage();
|
||||
|
||||
settings->set_saveDeletedMessages(false);
|
||||
history->destroyMessage(item);
|
||||
settings->set_saveDeletedMessages(initSaveDeleted);
|
||||
AyuState::hide(item);
|
||||
}, &st::menuIconClear);
|
||||
}
|
||||
|
||||
void AddUserMessagesAction(not_null<Ui::PopupMenu *> menu, HistoryItem *item)
|
||||
{
|
||||
const auto settings = &AyuSettings::getInstance();
|
||||
if (!needToShowItem(settings->showUserMessagesInContextMenu)) {
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void AddMessageDetailsAction(not_null<Ui::PopupMenu *> menu, HistoryItem *item)
|
||||
{
|
||||
const auto settings = &AyuSettings::getInstance();
|
||||
if (!needToShowItem(settings->showMessageDetailsInContextMenu)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (item->isLocal()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto view = item->mainView();
|
||||
const auto forwarded = item->Get<HistoryMessageForwarded>();
|
||||
const auto views = item->Get<HistoryMessageViews>();
|
||||
const auto media = item->media();
|
||||
|
||||
const auto isSticker = media && media->document() && media->document()->sticker();
|
||||
const auto isForwarded = forwarded && !forwarded->story && forwarded->psaType.isEmpty();
|
||||
|
||||
const auto messageId = QString::number(item->id.bare);
|
||||
const auto messageDate = base::unixtime::parse(item->date());
|
||||
const auto messageEditDate = base::unixtime::parse(view ? view->displayedEditDate() : TimeId(0));
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma ide diagnostic ignored "NullDereference"
|
||||
const auto messageForwardedDate =
|
||||
isForwarded ? base::unixtime::parse(forwarded->originalDate)
|
||||
: QDateTime();
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
const auto
|
||||
messageViews = item->hasViews() && item->viewsCount() > 0 ? QString::number(item->viewsCount()) : QString();
|
||||
const auto messageForwards = views && views->forwardsCount > 0 ? QString::number(views->forwardsCount) : QString();
|
||||
|
||||
const auto mediaSize = media ? getMediaSize(item) : QString();
|
||||
const auto mediaMime = media ? getMediaMime(item) : QString();
|
||||
// todo: bitrate (?)
|
||||
const auto mediaName = media ? getMediaName(item) : QString();
|
||||
const auto mediaResolution = media ? getMediaResolution(item) : QString();
|
||||
const auto mediaDC = media ? getMediaDC(item) : QString();
|
||||
|
||||
const auto hasAnyPostField =
|
||||
!messageViews.isEmpty() ||
|
||||
!messageForwards.isEmpty();
|
||||
|
||||
const auto hasAnyMediaField =
|
||||
!mediaSize.isEmpty() ||
|
||||
!mediaMime.isEmpty() ||
|
||||
!mediaName.isEmpty() ||
|
||||
!mediaResolution.isEmpty() ||
|
||||
!mediaDC.isEmpty();
|
||||
|
||||
const auto callback = Ui::Menu::CreateAddActionCallback(menu);
|
||||
|
||||
callback(Window::PeerMenuCallback::Args{
|
||||
.text = tr::ayu_MessageDetailsPC(tr::now),
|
||||
.handler = nullptr,
|
||||
.icon = &st::menuIconInfo,
|
||||
.fillSubmenu = [&](not_null<Ui::PopupMenu *> menu2)
|
||||
{
|
||||
if (hasAnyPostField) {
|
||||
if (!messageViews.isEmpty()) {
|
||||
menu2->addAction(Ui::ContextActionWithSubText(
|
||||
menu2->menu(),
|
||||
st::menuIconShowInChat,
|
||||
tr::ayu_MessageDetailsViewsPC(tr::now),
|
||||
messageViews
|
||||
));
|
||||
}
|
||||
|
||||
if (!messageForwards.isEmpty()) {
|
||||
menu2->addAction(Ui::ContextActionWithSubText(
|
||||
menu2->menu(),
|
||||
st::menuIconViewReplies,
|
||||
tr::ayu_MessageDetailsSharesPC(tr::now),
|
||||
messageForwards
|
||||
));
|
||||
}
|
||||
|
||||
menu2->addSeparator();
|
||||
}
|
||||
|
||||
menu2->addAction(Ui::ContextActionWithSubText(
|
||||
menu2->menu(),
|
||||
st::menuIconInfo,
|
||||
QString("ID"),
|
||||
messageId
|
||||
));
|
||||
|
||||
menu2->addAction(Ui::ContextActionWithSubText(
|
||||
menu2->menu(),
|
||||
st::menuIconSchedule,
|
||||
tr::ayu_MessageDetailsDatePC(tr::now),
|
||||
formatDateTime(messageDate)
|
||||
));
|
||||
|
||||
if (view && view->displayedEditDate()) {
|
||||
menu2->addAction(Ui::ContextActionWithSubText(
|
||||
menu2->menu(),
|
||||
st::menuIconEdit,
|
||||
tr::ayu_MessageDetailsEditedDatePC(tr::now),
|
||||
formatDateTime(messageEditDate)
|
||||
));
|
||||
}
|
||||
|
||||
if (isForwarded) {
|
||||
menu2->addAction(Ui::ContextActionWithSubText(
|
||||
menu2->menu(),
|
||||
st::menuIconTTL,
|
||||
tr::ayu_MessageDetailsForwardedDatePC(tr::now),
|
||||
formatDateTime(messageForwardedDate)
|
||||
));
|
||||
}
|
||||
|
||||
if (media && hasAnyMediaField) {
|
||||
menu2->addSeparator();
|
||||
|
||||
if (!mediaSize.isEmpty()) {
|
||||
menu2->addAction(Ui::ContextActionWithSubText(
|
||||
menu2->menu(),
|
||||
st::menuIconDownload,
|
||||
tr::ayu_MessageDetailsFileSizePC(tr::now),
|
||||
mediaSize
|
||||
));
|
||||
}
|
||||
|
||||
if (!mediaMime.isEmpty()) {
|
||||
const auto mime = Core::MimeTypeForName(mediaMime);
|
||||
|
||||
menu2->addAction(Ui::ContextActionWithSubText(
|
||||
menu2->menu(),
|
||||
st::menuIconShowAll,
|
||||
tr::ayu_MessageDetailsMimeTypePC(tr::now),
|
||||
mime.name()
|
||||
));
|
||||
}
|
||||
|
||||
if (!mediaName.isEmpty()) {
|
||||
auto const shortified = mediaName.length() > 20 ? "…" + mediaName.right(20) : mediaName;
|
||||
|
||||
menu2->addAction(Ui::ContextActionWithSubText(
|
||||
menu2->menu(),
|
||||
st::ayuEditsHistoryIcon,
|
||||
tr::ayu_MessageDetailsFileNamePC(tr::now),
|
||||
shortified,
|
||||
[=]
|
||||
{
|
||||
QGuiApplication::clipboard()->setText(mediaName);
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
if (!mediaResolution.isEmpty()) {
|
||||
menu2->addAction(Ui::ContextActionWithSubText(
|
||||
menu2->menu(),
|
||||
st::menuIconStats,
|
||||
tr::ayu_MessageDetailsResolutionPC(tr::now),
|
||||
mediaResolution
|
||||
));
|
||||
}
|
||||
|
||||
if (!mediaDC.isEmpty()) {
|
||||
menu2->addAction(Ui::ContextActionWithSubText(
|
||||
menu2->menu(),
|
||||
st::menuIconBoosts,
|
||||
tr::ayu_MessageDetailsDatacenterPC(tr::now),
|
||||
mediaDC
|
||||
));
|
||||
}
|
||||
|
||||
if (isSticker) {
|
||||
const auto authorId = media->document()->sticker()->set.id >> 32;
|
||||
menu2->addAction(Ui::ContextActionStickerAuthor(
|
||||
menu2->menu(),
|
||||
&item->history()->session(),
|
||||
authorId
|
||||
));
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
void AddReadUntilAction(not_null<Ui::PopupMenu *> menu, HistoryItem *item)
|
||||
{
|
||||
if (item->isLocal()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto settings = &AyuSettings::getInstance();
|
||||
if (settings->sendReadMessages) {
|
||||
return;
|
||||
|
||||
@@ -78,15 +78,23 @@
|
||||
#include "history/view/history_view_context_menu.h"
|
||||
|
||||
#include "ayu/ayu_settings.h"
|
||||
#include "ayu/utils/qt_key_modifiers_extended.h"
|
||||
|
||||
#include "styles/style_info.h"
|
||||
|
||||
namespace AyuUi
|
||||
{
|
||||
|
||||
bool needToShowItem(int state);
|
||||
|
||||
void AddHistoryAction(not_null<Ui::PopupMenu *> menu, HistoryItem *item);
|
||||
|
||||
void AddHideMessageAction(not_null<Ui::PopupMenu *> menu, HistoryItem *item);
|
||||
|
||||
void AddUserMessagesAction(not_null<Ui::PopupMenu *> menu, HistoryItem *item);
|
||||
|
||||
void AddMessageDetailsAction(not_null<Ui::PopupMenu *> menu, HistoryItem *item);
|
||||
|
||||
void AddReadUntilAction(not_null<Ui::PopupMenu *> menu, HistoryItem *item);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,311 @@
|
||||
// This is the source code of AyuGram for Desktop.
|
||||
//
|
||||
// We do not and cannot prevent the use of our code,
|
||||
// but be respectful and credit the original author.
|
||||
//
|
||||
// Copyright @Radolyn, 2023
|
||||
#include <utility>
|
||||
|
||||
#include "ayu/ui/context_menu/menu_item_subtext.h"
|
||||
|
||||
#include "ayu/utils/telegram_helpers.h"
|
||||
#include "ayu/database/entities.h"
|
||||
#include "mainwindow.h"
|
||||
#include "qguiapplication.h"
|
||||
|
||||
#include "ui/widgets/menu/menu_action.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/painter.h"
|
||||
#include "data/data_user.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
#include "window/window_session_controller.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
namespace
|
||||
{
|
||||
|
||||
class ActionWithSubText : public Menu::ItemBase
|
||||
{
|
||||
public:
|
||||
ActionWithSubText(
|
||||
not_null<RpWidget *> parent,
|
||||
const style::Menu &st,
|
||||
const style::icon &icon,
|
||||
Fn<void()> callback,
|
||||
const QString &title,
|
||||
QString subtext);
|
||||
|
||||
bool isEnabled() const override;
|
||||
not_null<QAction *> action() const override;
|
||||
|
||||
void handleKeyPress(not_null<QKeyEvent *> e) override;
|
||||
|
||||
protected:
|
||||
QPoint prepareRippleStartPosition() const override;
|
||||
QImage prepareRippleMask() const override;
|
||||
|
||||
int contentHeight() const override;
|
||||
|
||||
void prepare(const QString &title);
|
||||
void paint(Painter &p);
|
||||
|
||||
const not_null<QAction *> _dummyAction;
|
||||
const style::Menu &_st;
|
||||
const style::icon &_icon;
|
||||
|
||||
Text::String _title;
|
||||
int _textWidth = 0;
|
||||
QString _subText;
|
||||
const int _height;
|
||||
|
||||
};
|
||||
|
||||
class ActionStickerPackAuthor final : public ActionWithSubText
|
||||
{
|
||||
public:
|
||||
ActionStickerPackAuthor(not_null<Menu::Menu *> menu, not_null<Main::Session *> session, ID authorId);
|
||||
|
||||
private:
|
||||
not_null<Main::Session *> _session;
|
||||
|
||||
void searchAuthor(ID authorId);
|
||||
};
|
||||
|
||||
TextParseOptions MenuTextOptions = {
|
||||
TextParseLinks, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
|
||||
ActionWithSubText::ActionWithSubText(
|
||||
not_null<RpWidget *> parent,
|
||||
const style::Menu &st,
|
||||
const style::icon &icon,
|
||||
Fn<void()> callback,
|
||||
const QString &title,
|
||||
QString subtext)
|
||||
: ItemBase(parent, st),
|
||||
_dummyAction(new QAction(parent)),
|
||||
_st(st),
|
||||
_icon(icon),
|
||||
_subText(std::move(subtext)),
|
||||
_height(st::ttlItemPadding.top()
|
||||
+ _st.itemStyle.font->height
|
||||
+ st::ttlItemTimerFont->height
|
||||
+ st::ttlItemPadding.bottom())
|
||||
{
|
||||
setAcceptBoth(true);
|
||||
initResizeHook(parent->sizeValue());
|
||||
setClickedCallback(std::move(callback));
|
||||
|
||||
paintRequest(
|
||||
) | rpl::start_with_next([=]
|
||||
{
|
||||
Painter p(this);
|
||||
paint(p);
|
||||
}, lifetime());
|
||||
|
||||
enableMouseSelecting();
|
||||
prepare(title);
|
||||
}
|
||||
|
||||
void ActionWithSubText::paint(Painter &p)
|
||||
{
|
||||
const auto selected = isSelected();
|
||||
if (selected && _st.itemBgOver->c.alpha() < 255) {
|
||||
p.fillRect(0, 0, width(), _height, _st.itemBg);
|
||||
}
|
||||
p.fillRect(0, 0, width(), _height, selected ? _st.itemBgOver : _st.itemBg);
|
||||
if (isEnabled()) {
|
||||
paintRipple(p, 0, 0);
|
||||
}
|
||||
|
||||
const auto normalHeight = _st.itemPadding.top()
|
||||
+ _st.itemStyle.font->height
|
||||
+ _st.itemPadding.bottom();
|
||||
const auto deltaHeight = _height - normalHeight;
|
||||
_icon.paint(
|
||||
p,
|
||||
_st.itemIconPosition + QPoint(0, deltaHeight / 2),
|
||||
width());
|
||||
|
||||
p.setPen(selected ? _st.itemFgOver : _st.itemFg);
|
||||
_title.drawLeftElided(
|
||||
p,
|
||||
_st.itemPadding.left(),
|
||||
st::ttlItemPadding.top(),
|
||||
_textWidth,
|
||||
width());
|
||||
|
||||
p.setFont(st::ttlItemTimerFont);
|
||||
p.setPen(selected ? _st.itemFgShortcutOver : _st.itemFgShortcut);
|
||||
p.drawTextLeft(
|
||||
_st.itemPadding.left(),
|
||||
st::ttlItemPadding.top() + _st.itemStyle.font->height,
|
||||
width(),
|
||||
_subText);
|
||||
}
|
||||
|
||||
void ActionWithSubText::prepare(const QString &title)
|
||||
{
|
||||
_title.setMarkedText(
|
||||
_st.itemStyle,
|
||||
{title},
|
||||
MenuTextOptions);
|
||||
const auto textWidth = _title.maxWidth();
|
||||
const auto &padding = _st.itemPadding;
|
||||
|
||||
const auto goodWidth = padding.left()
|
||||
+ textWidth
|
||||
+ padding.right();
|
||||
const auto subTextMaxWidth = padding.left()
|
||||
+ st::ttlItemTimerFont->width(_subText)
|
||||
+ padding.right();
|
||||
|
||||
const auto w = std::clamp(
|
||||
std::max({goodWidth, subTextMaxWidth}),
|
||||
_st.widthMin,
|
||||
_st.widthMax);
|
||||
_textWidth = w - (goodWidth - textWidth);
|
||||
setMinWidth(w);
|
||||
update();
|
||||
}
|
||||
|
||||
bool ActionWithSubText::isEnabled() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
not_null<QAction *> ActionWithSubText::action() const
|
||||
{
|
||||
return _dummyAction;
|
||||
}
|
||||
|
||||
QPoint ActionWithSubText::prepareRippleStartPosition() const
|
||||
{
|
||||
return mapFromGlobal(QCursor::pos());
|
||||
}
|
||||
|
||||
QImage ActionWithSubText::prepareRippleMask() const
|
||||
{
|
||||
return Ui::RippleAnimation::RectMask(size());
|
||||
}
|
||||
|
||||
int ActionWithSubText::contentHeight() const
|
||||
{
|
||||
return _height;
|
||||
}
|
||||
|
||||
void ActionWithSubText::handleKeyPress(not_null<QKeyEvent *> e)
|
||||
{
|
||||
if (!isSelected()) {
|
||||
return;
|
||||
}
|
||||
const auto key = e->key();
|
||||
if (key == Qt::Key_Enter || key == Qt::Key_Return) {
|
||||
setClicked(Menu::TriggeredSource::Keyboard);
|
||||
}
|
||||
}
|
||||
|
||||
ActionStickerPackAuthor::ActionStickerPackAuthor(not_null<Menu::Menu *> menu,
|
||||
not_null<Main::Session *> session,
|
||||
ID authorId)
|
||||
: ActionWithSubText(menu, menu->st(), st::menuIconStickers, [=]
|
||||
{ }, tr::ayu_MessageDetailsPackOwnerPC(tr::now), QString(tr::ayu_MessageDetailsPackOwnerFetchingPC(tr::now))),
|
||||
_session(session)
|
||||
{
|
||||
searchAuthor(authorId);
|
||||
}
|
||||
|
||||
void ActionStickerPackAuthor::searchAuthor(ID authorId)
|
||||
{
|
||||
const auto pointer = Ui::MakeWeak(this);
|
||||
searchById(authorId, _session, [=](const QString &username, UserData *user)
|
||||
{
|
||||
if (!pointer) {
|
||||
LOG(("ContextActionStickerAuthor: searchById callback after destruction"));
|
||||
return;
|
||||
}
|
||||
if (username.isEmpty() && !user) {
|
||||
_subText = QString(tr::ayu_MessageDetailsPackOwnerNotFoundPC(tr::now));
|
||||
setClickedCallback(
|
||||
[=]
|
||||
{
|
||||
const auto text =
|
||||
QString("int32: %1\nint64: %2").arg(authorId).arg(0x100000000L + authorId);
|
||||
QGuiApplication::clipboard()->setText(text);
|
||||
});
|
||||
|
||||
crl::on_main(
|
||||
[=]
|
||||
{
|
||||
update();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const auto title = username.isEmpty() ? user ? user->name() : QString() : username;
|
||||
const auto callback = [=]
|
||||
{
|
||||
if (user) {
|
||||
if (const auto window = _session->tryResolveWindow()) {
|
||||
if (const auto mainWidget = window->widget()->sessionController()) {
|
||||
mainWidget->showPeer(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
QGuiApplication::clipboard()->setText(title);
|
||||
}
|
||||
};
|
||||
|
||||
setClickedCallback(callback);
|
||||
|
||||
_subText = QString(title);
|
||||
crl::on_main(
|
||||
[=]
|
||||
{
|
||||
update();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
base::unique_qptr<Menu::ItemBase> ContextActionWithSubText(
|
||||
not_null<Menu::Menu *> menu,
|
||||
const style::icon &icon,
|
||||
const QString &title,
|
||||
const QString &subtext,
|
||||
Fn<void()> callback)
|
||||
{
|
||||
if (!callback) {
|
||||
callback = [=]()
|
||||
{
|
||||
QGuiApplication::clipboard()->setText(subtext);
|
||||
};
|
||||
}
|
||||
|
||||
return base::make_unique_q<ActionWithSubText>(
|
||||
menu,
|
||||
menu->st(),
|
||||
icon,
|
||||
std::move(callback),
|
||||
title,
|
||||
subtext);
|
||||
}
|
||||
|
||||
base::unique_qptr<Menu::ItemBase> ContextActionStickerAuthor(
|
||||
not_null<Menu::Menu *> menu,
|
||||
not_null<Main::Session *> session,
|
||||
ID authorId)
|
||||
{
|
||||
return base::make_unique_q<ActionStickerPackAuthor>(menu, session, authorId);
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
@@ -0,0 +1,36 @@
|
||||
// This is the source code of AyuGram for Desktop.
|
||||
//
|
||||
// We do not and cannot prevent the use of our code,
|
||||
// but be respectful and credit the original author.
|
||||
//
|
||||
// Copyright @Radolyn, 2023
|
||||
#pragma once
|
||||
|
||||
#include "ayu/database/entities.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
#include "base/unique_qptr.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
namespace Menu
|
||||
{
|
||||
class Menu;
|
||||
class ItemBase;
|
||||
} // namespace Menu
|
||||
|
||||
class PopupMenu;
|
||||
|
||||
[[nodiscard]] base::unique_qptr<Menu::ItemBase> ContextActionWithSubText(
|
||||
not_null<Menu::Menu *> menu,
|
||||
const style::icon &icon,
|
||||
const QString &title,
|
||||
const QString &subtext,
|
||||
Fn<void()> callback = nullptr);
|
||||
|
||||
[[nodiscard]] base::unique_qptr<Menu::ItemBase> ContextActionStickerAuthor(
|
||||
not_null<Menu::Menu *> menu,
|
||||
not_null<Main::Session *> session,
|
||||
ID authorId);
|
||||
|
||||
} // namespace Ui
|
||||
@@ -938,7 +938,10 @@ TextForMimeData InnerWidget::getSelectedText() const
|
||||
|
||||
void InnerWidget::keyPressEvent(QKeyEvent *e)
|
||||
{
|
||||
if (e == QKeySequence::Copy && _selectedItem != nullptr) {
|
||||
if (e->key() == Qt::Key_Escape) {
|
||||
_controller->showBackFromStack();
|
||||
}
|
||||
else if (e == QKeySequence::Copy && _selectedItem != nullptr) {
|
||||
copySelectedText();
|
||||
#ifdef Q_OS_MAC
|
||||
} else if (e->key() == Qt::Key_E && e->modifiers().testFlag(Qt::ControlModifier)) {
|
||||
|
||||
@@ -21,14 +21,14 @@
|
||||
#endif
|
||||
|
||||
const QVector<QString> icons{
|
||||
AyuSettings::DEFAULT_ICON,
|
||||
AyuSettings::ALT_ICON,
|
||||
AyuSettings::DISCORD_ICON,
|
||||
AyuSettings::SPOTIFY_ICON,
|
||||
AyuSettings::EXTERA_ICON,
|
||||
AyuSettings::NOTHING_ICON,
|
||||
AyuSettings::BARD_ICON,
|
||||
AyuSettings::YAPLUS_ICON
|
||||
AyuAssets::DEFAULT_ICON,
|
||||
AyuAssets::ALT_ICON,
|
||||
AyuAssets::DISCORD_ICON,
|
||||
AyuAssets::SPOTIFY_ICON,
|
||||
AyuAssets::EXTERA_ICON,
|
||||
AyuAssets::NOTHING_ICON,
|
||||
AyuAssets::BARD_ICON,
|
||||
AyuAssets::YAPLUS_ICON
|
||||
};
|
||||
|
||||
const auto rows = static_cast<int>(icons.size()) / 4 + std::min(1, static_cast<int>(icons.size()) % 4);
|
||||
@@ -51,6 +51,19 @@ void drawIcon(QPainter &p, const QImage &icon, int xOffset, int yOffset, bool se
|
||||
p.drawImage(rect, icon);
|
||||
}
|
||||
|
||||
void applyIcon()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
AyuAssets::loadAppIco();
|
||||
reloadAppIconFromTaskBar();
|
||||
#endif
|
||||
|
||||
Window::OverrideApplicationIcon(AyuAssets::currentAppLogo());
|
||||
Core::App().refreshApplicationIcon();
|
||||
Core::App().tray().updateIconCounters();
|
||||
Core::App().domain().notifyUnreadBadgeChanged();
|
||||
}
|
||||
|
||||
IconPicker::IconPicker(QWidget *parent)
|
||||
: RpWidget(parent)
|
||||
{
|
||||
@@ -70,13 +83,13 @@ void IconPicker::paintEvent(QPaintEvent *e)
|
||||
auto const idx = i + row * 4;
|
||||
|
||||
const auto &iconName = icons[idx];
|
||||
auto icon = loadPreview(iconName)
|
||||
auto icon = AyuAssets::loadPreview(iconName)
|
||||
.scaled(st::cpIconSize, st::cpIconSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
drawIcon(p,
|
||||
icon,
|
||||
(st::cpIconSize + st::cpSpacingX) * i + offset,
|
||||
row * (st::cpIconSize + st::cpSpacingY),
|
||||
currentAppLogoName() == iconName);
|
||||
AyuAssets::currentAppLogoName() == iconName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,16 +121,7 @@ void IconPicker::mousePressEvent(QMouseEvent *e)
|
||||
|
||||
if (changed) {
|
||||
AyuSettings::save();
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
loadAppIco();
|
||||
reloadAppIconFromTaskBar();
|
||||
#endif
|
||||
|
||||
Window::OverrideApplicationIcon(currentAppLogo());
|
||||
Core::App().refreshApplicationIcon();
|
||||
Core::App().tray().updateIconCounters();
|
||||
Core::App().domain().notifyUnreadBadgeChanged();
|
||||
applyIcon();
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
@@ -29,37 +29,6 @@ public:
|
||||
[[nodiscard]] rpl::producer<QString> title() override;
|
||||
|
||||
private:
|
||||
void SetupGhostModeToggle(not_null<Ui::VerticalLayout *> container);
|
||||
|
||||
void SetupReadAfterActionToggle(not_null<Ui::VerticalLayout *> container);
|
||||
|
||||
void SetupGhostEssentials(not_null<Ui::VerticalLayout *> container);
|
||||
|
||||
void SetupSpyEssentials(not_null<Ui::VerticalLayout *> container);
|
||||
|
||||
void SetupQoLToggles(not_null<Ui::VerticalLayout *> container);
|
||||
|
||||
void SetupAppIcon(not_null<Ui::VerticalLayout *> container);
|
||||
|
||||
void SetupCustomization(not_null<Ui::VerticalLayout *> container,
|
||||
not_null<Window::SessionController *> controller);
|
||||
|
||||
void SetupDrawerElements(not_null<Ui::VerticalLayout *> container);
|
||||
|
||||
void SetupTrayElements(not_null<Ui::VerticalLayout *> container);
|
||||
|
||||
void SetupShowPeerId(not_null<Ui::VerticalLayout *> container, not_null<Window::SessionController *> controller);
|
||||
|
||||
void SetupRecentStickersLimitSlider(not_null<Ui::VerticalLayout *> container);
|
||||
|
||||
void SetupFonts(not_null<Ui::VerticalLayout *> container, not_null<Window::SessionController *> controller);
|
||||
|
||||
void SetupAyuSync(not_null<Ui::VerticalLayout *> container);
|
||||
|
||||
void SetupSendConfirmations(not_null<Ui::VerticalLayout *> container);
|
||||
|
||||
void SetupAyuGramSettings(not_null<Ui::VerticalLayout *> container, not_null<Window::SessionController *> null);
|
||||
|
||||
void setupContent(not_null<Window::SessionController *> controller);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// This is the source code of AyuGram for Desktop.
|
||||
//
|
||||
// We do not and cannot prevent the use of our code,
|
||||
// but be respectful and credit the original author.
|
||||
//
|
||||
// Copyright @Radolyn, 2023
|
||||
#pragma once
|
||||
|
||||
#include "base/qt/qt_key_modifiers.h"
|
||||
|
||||
namespace base {
|
||||
|
||||
[[nodiscard]] inline bool IsShiftPressed() {
|
||||
return (QGuiApplication::keyboardModifiers() == Qt::ShiftModifier);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool IsExtendedContextMenuModifierPressed() {
|
||||
return IsShiftPressed() || IsCtrlPressed();
|
||||
}
|
||||
|
||||
} // namespace base
|
||||
@@ -10,10 +10,16 @@
|
||||
|
||||
#include "api/api_text_entities.h"
|
||||
|
||||
#include "core/mime_type.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_photo_media.h"
|
||||
#include "inline_bots/inline_bot_result.h"
|
||||
#include "lang_auto.h"
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_forum.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_forum_topic.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_peer_id.h"
|
||||
@@ -21,10 +27,12 @@
|
||||
#include "ayu/sync/models.h"
|
||||
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_document.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_unread_things.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "ui/text/format_values.h"
|
||||
|
||||
// https://github.com/AyuGram/AyuGram4AX/blob/rewrite/TMessagesProj/src/main/java/com/radolyn/ayugram/AyuConstants.java
|
||||
std::unordered_set<ID> ayugram_channels = {
|
||||
@@ -90,7 +98,7 @@ bool accountExists(ID userId)
|
||||
return userId == 0 || getSession(userId) != nullptr;
|
||||
}
|
||||
|
||||
void dispatchToMainThread(std::function<void()> callback)
|
||||
void dispatchToMainThread(std::function<void()> callback, int delay)
|
||||
{
|
||||
auto timer = new QTimer();
|
||||
timer->moveToThread(qApp->thread());
|
||||
@@ -100,7 +108,7 @@ void dispatchToMainThread(std::function<void()> callback)
|
||||
callback();
|
||||
timer->deleteLater();
|
||||
});
|
||||
QMetaObject::invokeMethod(timer, "start", Qt::QueuedConnection, Q_ARG(int, 0));
|
||||
QMetaObject::invokeMethod(timer, "start", Qt::QueuedConnection, Q_ARG(int, delay));
|
||||
}
|
||||
|
||||
not_null<History *> getHistoryFromDialogId(ID dialogId, Main::Session *session)
|
||||
@@ -328,4 +336,382 @@ QString formatTTL(int time)
|
||||
}
|
||||
|
||||
return QString("🕓 %1s").arg(time);
|
||||
}
|
||||
}
|
||||
|
||||
QString getDCName(int dc)
|
||||
{
|
||||
const auto getName = [=](int dc)
|
||||
{
|
||||
switch (dc) {
|
||||
case 1:
|
||||
case 3: return "Miami FL, USA";
|
||||
case 2:
|
||||
case 4: return "Amsterdam, NL";
|
||||
case 5: return "Singapore, SG";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
};
|
||||
|
||||
if (dc < 1) {
|
||||
return {"DC_UNKNOWN"};
|
||||
}
|
||||
|
||||
return QString("DC%1, %2").arg(dc).arg(getName(dc));
|
||||
}
|
||||
|
||||
QString getLocalizedAt()
|
||||
{
|
||||
static const auto val = tr::lng_mediaview_date_time(
|
||||
tr::now,
|
||||
lt_date,
|
||||
"",
|
||||
lt_time,
|
||||
"");
|
||||
return val;
|
||||
}
|
||||
|
||||
QString formatDateTime(const QDateTime &date)
|
||||
{
|
||||
const auto locale = QLocale::system();
|
||||
const auto datePart = locale.toString(date.date(), QLocale::ShortFormat);
|
||||
const auto timePart = locale.toString(date, "HH:mm:ss");
|
||||
|
||||
return datePart + getLocalizedAt() + timePart;
|
||||
}
|
||||
|
||||
QString getMediaSize(not_null<HistoryItem *> message)
|
||||
{
|
||||
if (!message->media()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto media = message->media();
|
||||
|
||||
const auto document = media->document();
|
||||
const auto photo = media->photo();
|
||||
|
||||
int64 size = -1;
|
||||
if (document) { // any file
|
||||
size = document->size;
|
||||
}
|
||||
else if (photo && photo->hasVideo()) { // video
|
||||
size = photo->videoByteSize(Data::PhotoSize::Large);
|
||||
if (size == 0) {
|
||||
size = photo->videoByteSize(Data::PhotoSize::Small);
|
||||
}
|
||||
if (size == 0) {
|
||||
size = photo->videoByteSize(Data::PhotoSize::Thumbnail);
|
||||
}
|
||||
}
|
||||
else if (photo && !photo->hasVideo()) { // photo
|
||||
size = photo->imageByteSize(Data::PhotoSize::Large);
|
||||
if (size == 0) {
|
||||
size = photo->imageByteSize(Data::PhotoSize::Small);
|
||||
}
|
||||
if (size == 0) {
|
||||
size = photo->imageByteSize(Data::PhotoSize::Thumbnail);
|
||||
}
|
||||
}
|
||||
|
||||
if (size == -1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return Ui::FormatSizeText(size);
|
||||
}
|
||||
|
||||
QString getMediaMime(not_null<HistoryItem *> message)
|
||||
{
|
||||
if (!message->media()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto media = message->media();
|
||||
|
||||
const auto document = media->document();
|
||||
const auto photo = media->photo();
|
||||
|
||||
if (document) { // any file
|
||||
return document->mimeString();
|
||||
}
|
||||
else if (photo && photo->hasVideo()) { // video
|
||||
return "video/mp4";
|
||||
}
|
||||
else if (photo && !photo->hasVideo()) { // photo
|
||||
return "image/jpeg";
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
QString getMediaName(not_null<HistoryItem *> message)
|
||||
{
|
||||
if (!message->media()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto media = message->media();
|
||||
|
||||
const auto document = media->document();
|
||||
|
||||
if (document) {
|
||||
return document->filename();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
QString getMediaResolution(not_null<HistoryItem *> message)
|
||||
{
|
||||
if (!message->media()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto media = message->media();
|
||||
|
||||
const auto document = media->document();
|
||||
const auto photo = media->photo();
|
||||
|
||||
const auto formatQSize = [=](QSize size)
|
||||
{
|
||||
if (size.isNull() || size.isEmpty() || !size.isValid()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
return QString("%1x%2").arg(size.width()).arg(size.height());
|
||||
};
|
||||
|
||||
if (document) {
|
||||
return formatQSize(document->dimensions);
|
||||
}
|
||||
else if (photo) {
|
||||
auto result = photo->size(Data::PhotoSize::Large);
|
||||
if (!result.has_value()) {
|
||||
result = photo->size(Data::PhotoSize::Small);
|
||||
}
|
||||
if (!result.has_value()) {
|
||||
result = photo->size(Data::PhotoSize::Thumbnail);
|
||||
}
|
||||
return result.has_value() ? formatQSize(result.value()) : QString();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
QString getMediaDC(not_null<HistoryItem *> message)
|
||||
{
|
||||
if (!message->media()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto media = message->media();
|
||||
|
||||
const auto document = media->document();
|
||||
const auto photo = media->photo();
|
||||
|
||||
if (document) {
|
||||
return getDCName(document->getDC());
|
||||
}
|
||||
else if (photo) {
|
||||
return getDCName(photo->getDC());
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void resolveUser(ID userId, const QString &username, Main::Session *session, const Callback &callback)
|
||||
{
|
||||
auto normalized = username.trimmed().toLower();
|
||||
if (normalized.isEmpty()) {
|
||||
callback(QString(), nullptr);
|
||||
return;
|
||||
}
|
||||
normalized = normalized.startsWith("@") ? normalized.mid(1) : normalized;
|
||||
|
||||
if (normalized.isEmpty()) {
|
||||
callback(QString(), nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
session->api().request(MTPcontacts_ResolveUsername(
|
||||
MTP_string(normalized)
|
||||
)).done([=](const MTPcontacts_ResolvedPeer &result)
|
||||
{
|
||||
Expects(result.type() == mtpc_contacts_resolvedPeer);
|
||||
|
||||
auto &data = result.c_contacts_resolvedPeer();
|
||||
session->data().processUsers(data.vusers());
|
||||
session->data().processChats(data.vchats());
|
||||
const auto peer = session->data().peerLoaded(
|
||||
peerFromMTP(data.vpeer()));
|
||||
if (const auto user = peer ? peer->asUser() : nullptr) {
|
||||
if (user->id.value == userId) {
|
||||
callback(normalized, user);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
callback(normalized, nullptr);
|
||||
}).fail([=]
|
||||
{
|
||||
callback(QString(), nullptr);
|
||||
}).send();
|
||||
}
|
||||
|
||||
void searchUser(ID userId, Main::Session *session, bool searchUserFlag, bool cache, const Callback &callback)
|
||||
{
|
||||
const auto botId = 1696868284;
|
||||
const auto bot = session->data().userLoaded(botId);
|
||||
|
||||
if (!bot) {
|
||||
if (searchUserFlag) {
|
||||
resolveUser(botId, "tgdb_bot", session, [=](const QString &title, UserData *data)
|
||||
{
|
||||
searchUser(userId, session, false, false, callback);
|
||||
});
|
||||
}
|
||||
else {
|
||||
callback(QString(), nullptr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
session->api().request(MTPmessages_GetInlineBotResults(
|
||||
MTP_flags(0),
|
||||
bot->inputUser,
|
||||
MTP_inputPeerEmpty(),
|
||||
MTPInputGeoPoint(),
|
||||
MTP_string(QString::number(userId)),
|
||||
MTP_string("")
|
||||
)).done([=](const MTPmessages_BotResults &result)
|
||||
{
|
||||
if (result.type() != mtpc_messages_botResults) {
|
||||
callback(QString(), nullptr);
|
||||
return;
|
||||
}
|
||||
auto &d = result.c_messages_botResults();
|
||||
session->data().processUsers(d.vusers());
|
||||
|
||||
auto &v = d.vresults().v;
|
||||
auto queryId = d.vquery_id().v;
|
||||
|
||||
auto added = 0;
|
||||
for (const auto &res : v) {
|
||||
const auto message = res.match(
|
||||
[&](const MTPDbotInlineResult &data)
|
||||
{
|
||||
return &data.vsend_message();
|
||||
}, [&](const MTPDbotInlineMediaResult &data)
|
||||
{
|
||||
return &data.vsend_message();
|
||||
});
|
||||
|
||||
const auto text = message->match(
|
||||
[&](const MTPDbotInlineMessageMediaAuto &data)
|
||||
{
|
||||
return QString();
|
||||
}, [&](const MTPDbotInlineMessageText &data)
|
||||
{
|
||||
return qs(data.vmessage());
|
||||
}, [&](const MTPDbotInlineMessageMediaGeo &data)
|
||||
{
|
||||
return QString();
|
||||
}, [&](const MTPDbotInlineMessageMediaVenue &data)
|
||||
{
|
||||
return QString();
|
||||
}, [&](const MTPDbotInlineMessageMediaContact &data)
|
||||
{
|
||||
return QString();
|
||||
}, [&](const MTPDbotInlineMessageMediaInvoice &data)
|
||||
{
|
||||
return QString();
|
||||
}, [&](const MTPDbotInlineMessageMediaWebPage &data)
|
||||
{
|
||||
return QString();
|
||||
});
|
||||
|
||||
if (text.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ID id = 0; // 🆔
|
||||
QString title; // 🏷
|
||||
QString username; // 📧
|
||||
|
||||
for (const auto &line : text.split('\n')) {
|
||||
if (line.startsWith("🆔")) {
|
||||
id = line.mid(line.indexOf(':') + 1).toLongLong();
|
||||
}
|
||||
else if (line.startsWith("🏷")) {
|
||||
title = line.mid(line.indexOf(':') + 1);
|
||||
}
|
||||
else if (line.startsWith("📧")) {
|
||||
username = line.mid(line.indexOf(':') + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (id == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (id != userId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!username.isEmpty()) {
|
||||
resolveUser(id, username, session, [=](const QString &titleInner, UserData *data)
|
||||
{
|
||||
if (data) {
|
||||
callback(titleInner, data);
|
||||
}
|
||||
else {
|
||||
callback(title, nullptr);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!title.isEmpty()) {
|
||||
callback(title, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
callback(QString(), nullptr);
|
||||
}).fail([=]
|
||||
{
|
||||
callback(QString(), nullptr);
|
||||
}).handleAllErrors().send();
|
||||
}
|
||||
|
||||
void searchById(ID userId, Main::Session *session, bool retry, const Callback &callback)
|
||||
{
|
||||
if (userId == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto dataLoaded = session->data().userLoaded(userId);
|
||||
if (dataLoaded) {
|
||||
callback(dataLoaded->username(), dataLoaded);
|
||||
return;
|
||||
}
|
||||
|
||||
searchUser(userId, session, true, true, [=](const QString &title, UserData *data)
|
||||
{
|
||||
if (data && data->accessHash()) {
|
||||
callback(title, data);
|
||||
}
|
||||
else {
|
||||
if (retry) {
|
||||
searchById(0x100000000 + userId, session, false, callback);
|
||||
}
|
||||
else {
|
||||
callback(QString(), nullptr);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void searchById(ID userId, Main::Session *session, const Callback &callback)
|
||||
{
|
||||
searchById(userId, session, true, callback);
|
||||
}
|
||||
|
||||
@@ -14,9 +14,11 @@
|
||||
#include "main/main_session.h"
|
||||
#include "dialogs/dialogs_main_list.h"
|
||||
|
||||
using Callback = Fn<void(const QString&, UserData *)>;
|
||||
|
||||
Main::Session *getSession(ID userId);
|
||||
bool accountExists(ID userId);
|
||||
void dispatchToMainThread(std::function<void()> callback);
|
||||
void dispatchToMainThread(std::function<void()> callback, int delay = 0);
|
||||
not_null<History *> getHistoryFromDialogId(ID dialogId, Main::Session *session);
|
||||
ID getDialogIdFromPeer(not_null<PeerData *> peer);
|
||||
std::pair<std::string, std::string> serializeTextWithEntities(not_null<HistoryItem *> item);
|
||||
@@ -32,3 +34,15 @@ void MarkAsReadThread(not_null<Data::Thread *> thread);
|
||||
void readHistory(not_null<HistoryItem *> message);
|
||||
|
||||
QString formatTTL(int time);
|
||||
QString formatDateTime(const QDateTime &date);
|
||||
|
||||
QString getDCName(int dc);
|
||||
|
||||
QString getMediaSize(not_null<HistoryItem *> message);
|
||||
QString getMediaMime(not_null<HistoryItem *> message);
|
||||
QString getMediaName(not_null<HistoryItem *> message);
|
||||
QString getMediaResolution(not_null<HistoryItem *> message);
|
||||
QString getMediaDC(not_null<HistoryItem *> message);
|
||||
|
||||
void searchById(ID userId, Main::Session *session, bool retry, const Callback &callback);
|
||||
void searchById(ID userId, Main::Session *session, const Callback& callback);
|
||||
|
||||
@@ -129,6 +129,8 @@ private:
|
||||
int row) const;
|
||||
void validatePaperThumbnail(const Paper &paper) const;
|
||||
|
||||
[[nodiscard]] bool forChannel() const;
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
PeerData * const _forPeer = nullptr;
|
||||
|
||||
@@ -176,6 +178,23 @@ void BackgroundBox::prepare() {
|
||||
st::infoIconMediaPhoto,
|
||||
st::infoSharedMediaButtonIconPosition);
|
||||
|
||||
if (forChannel() && _forPeer->wallPaper()) {
|
||||
const auto remove = container->add(object_ptr<Ui::SettingsButton>(
|
||||
container,
|
||||
tr::lng_settings_bg_remove(),
|
||||
st::infoBlockButton));
|
||||
object_ptr<Info::Profile::FloatingIcon>(
|
||||
remove,
|
||||
st::infoIconDeleteRed,
|
||||
st::infoSharedMediaButtonIconPosition);
|
||||
|
||||
remove->setClickedCallback([=] {
|
||||
if (const auto resolved = _inner->resolveResetCustomPaper()) {
|
||||
chosen(*resolved);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
button->setClickedCallback([=] {
|
||||
chooseFromFile();
|
||||
});
|
||||
@@ -290,6 +309,23 @@ void BackgroundBox::chosen(const Data::WallPaper &paper) {
|
||||
closeBox();
|
||||
}
|
||||
return;
|
||||
} else if (forChannel()) {
|
||||
if (_forPeer->wallPaper() && _forPeer->wallPaper()->equals(paper)) {
|
||||
closeBox();
|
||||
return;
|
||||
}
|
||||
const auto &themes = _forPeer->owner().cloudThemes();
|
||||
for (const auto &theme : themes.chatThemes()) {
|
||||
for (const auto &[type, themed] : theme.settings) {
|
||||
if (themed.paper && themed.paper->equals(paper)) {
|
||||
_controller->show(Box<BackgroundPreviewBox>(
|
||||
_controller,
|
||||
Data::WallPaper::FromEmojiId(theme.emoticon),
|
||||
BackgroundPreviewArgs{ _forPeer }));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_controller->show(Box<BackgroundPreviewBox>(
|
||||
_controller,
|
||||
@@ -316,6 +352,10 @@ void BackgroundBox::resetForPeer() {
|
||||
}
|
||||
}
|
||||
|
||||
bool BackgroundBox::forChannel() const {
|
||||
return _forPeer && _forPeer->isChannel();
|
||||
}
|
||||
|
||||
void BackgroundBox::removePaper(const Data::WallPaper &paper) {
|
||||
const auto session = &_controller->session();
|
||||
const auto remove = [=, weak = Ui::MakeWeak(this)](Fn<void()> &&close) {
|
||||
@@ -345,9 +385,16 @@ BackgroundBox::Inner::Inner(
|
||||
, _session(session)
|
||||
, _forPeer(forPeer)
|
||||
, _api(&_session->mtp())
|
||||
, _check(std::make_unique<Ui::RoundCheckbox>(st::overviewCheck, [=] { update(); })) {
|
||||
, _check(
|
||||
std::make_unique<Ui::RoundCheckbox>(
|
||||
st::overviewCheck,
|
||||
[=] { update(); })) {
|
||||
_check->setChecked(true, anim::type::instant);
|
||||
resize(st::boxWideWidth, 2 * (st::backgroundSize.height() + st::backgroundPadding) + st::backgroundPadding);
|
||||
resize(
|
||||
st::boxWideWidth,
|
||||
(2 * (st::backgroundSize.height() + st::backgroundPadding)
|
||||
+ st::backgroundPadding));
|
||||
|
||||
Window::Theme::IsNightModeValue(
|
||||
) | rpl::start_with_next([=] {
|
||||
updatePapers();
|
||||
@@ -364,21 +411,31 @@ BackgroundBox::Inner::Inner(
|
||||
_check->invalidateCache();
|
||||
}, lifetime());
|
||||
|
||||
using Update = Window::Theme::BackgroundUpdate;
|
||||
Window::Theme::Background()->updates(
|
||||
) | rpl::start_with_next([=](const Update &update) {
|
||||
if (update.type == Update::Type::New) {
|
||||
sortPapers();
|
||||
requestPapers();
|
||||
this->update();
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
if (forChannel()) {
|
||||
_session->data().cloudThemes().chatThemesUpdated(
|
||||
) | rpl::start_with_next([=] {
|
||||
updatePapers();
|
||||
}, lifetime());
|
||||
} else {
|
||||
using Update = Window::Theme::BackgroundUpdate;
|
||||
Window::Theme::Background()->updates(
|
||||
) | rpl::start_with_next([=](const Update &update) {
|
||||
if (update.type == Update::Type::New) {
|
||||
sortPapers();
|
||||
requestPapers();
|
||||
this->update();
|
||||
}
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
void BackgroundBox::Inner::requestPapers() {
|
||||
if (forChannel()) {
|
||||
_session->data().cloudThemes().refreshChatThemes();
|
||||
return;
|
||||
}
|
||||
_api.request(MTPaccount_GetWallPapers(
|
||||
MTP_long(_session->data().wallpapersHash())
|
||||
)).done([=](const MTPaccount_WallPapers &result) {
|
||||
@@ -395,7 +452,7 @@ auto BackgroundBox::Inner::resolveResetCustomPaper() const
|
||||
}
|
||||
const auto nonCustom = Window::Theme::Background()->paper();
|
||||
const auto themeEmoji = _forPeer->themeEmoji();
|
||||
if (themeEmoji.isEmpty()) {
|
||||
if (forChannel() || themeEmoji.isEmpty()) {
|
||||
return nonCustom;
|
||||
}
|
||||
const auto &themes = _forPeer->owner().cloudThemes();
|
||||
@@ -443,6 +500,8 @@ void BackgroundBox::Inner::pushCustomPapers() {
|
||||
}
|
||||
|
||||
void BackgroundBox::Inner::sortPapers() {
|
||||
Expects(!forChannel());
|
||||
|
||||
const auto currentCustom = _forPeer ? _forPeer->wallPaper() : nullptr;
|
||||
_currentId = currentCustom
|
||||
? currentCustom->id()
|
||||
@@ -472,23 +531,60 @@ void BackgroundBox::Inner::sortPapers() {
|
||||
}
|
||||
|
||||
void BackgroundBox::Inner::updatePapers() {
|
||||
if (_session->data().wallpapers().empty()) {
|
||||
return;
|
||||
if (forChannel()) {
|
||||
if (_session->data().cloudThemes().chatThemes().empty()) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (_session->data().wallpapers().empty()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
_over = _overDown = Selection();
|
||||
|
||||
_papers = _session->data().wallpapers(
|
||||
) | ranges::views::filter([&](const Data::WallPaper &paper) {
|
||||
return (!paper.isPattern() || !paper.backgroundColors().empty())
|
||||
&& (!_forPeer
|
||||
|| (!Data::IsDefaultWallPaper(paper)
|
||||
&& (Data::IsCloudWallPaper(paper)
|
||||
|| Data::IsCustomWallPaper(paper))));
|
||||
}) | ranges::views::transform([](const Data::WallPaper &paper) {
|
||||
return Paper{ paper };
|
||||
}) | ranges::to_vector;
|
||||
pushCustomPapers();
|
||||
sortPapers();
|
||||
const auto was = base::take(_papers);
|
||||
if (forChannel()) {
|
||||
const auto now = _forPeer->wallPaper();
|
||||
const auto &list = _session->data().cloudThemes().chatThemes();
|
||||
if (list.empty()) {
|
||||
return;
|
||||
}
|
||||
using Type = Data::CloudThemeType;
|
||||
const auto type = Window::Theme::IsNightMode()
|
||||
? Type::Dark
|
||||
: Type::Light;
|
||||
_papers.reserve(list.size() + 1);
|
||||
const auto nowEmojiId = now ? now->emojiId() : QString();
|
||||
if (!now || !now->emojiId().isEmpty()) {
|
||||
_papers.push_back({ Window::Theme::Background()->paper() });
|
||||
_currentId = _papers.back().data.id();
|
||||
} else {
|
||||
_papers.push_back({ *now });
|
||||
_currentId = now->id();
|
||||
}
|
||||
for (const auto &theme : list) {
|
||||
const auto i = theme.settings.find(type);
|
||||
if (i != end(theme.settings) && i->second.paper) {
|
||||
_papers.push_back({ *i->second.paper });
|
||||
if (nowEmojiId == theme.emoticon) {
|
||||
_currentId = _papers.back().data.id();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_papers = _session->data().wallpapers(
|
||||
) | ranges::views::filter([&](const Data::WallPaper &paper) {
|
||||
return (!paper.isPattern() || !paper.backgroundColors().empty())
|
||||
&& (!_forPeer
|
||||
|| (!Data::IsDefaultWallPaper(paper)
|
||||
&& (Data::IsCloudWallPaper(paper)
|
||||
|| Data::IsCustomWallPaper(paper))));
|
||||
}) | ranges::views::transform([](const Data::WallPaper &paper) {
|
||||
return Paper{ paper };
|
||||
}) | ranges::to_vector;
|
||||
pushCustomPapers();
|
||||
sortPapers();
|
||||
}
|
||||
resizeToContentAndPreload();
|
||||
}
|
||||
|
||||
@@ -587,6 +683,10 @@ void BackgroundBox::Inner::validatePaperThumbnail(
|
||||
paper.thumbnail.setDevicePixelRatio(cRetinaFactor());
|
||||
}
|
||||
|
||||
bool BackgroundBox::Inner::forChannel() const {
|
||||
return _forPeer && _forPeer->isChannel();
|
||||
}
|
||||
|
||||
void BackgroundBox::Inner::paintPaper(
|
||||
QPainter &p,
|
||||
const Paper &paper,
|
||||
@@ -604,7 +704,8 @@ void BackgroundBox::Inner::paintPaper(
|
||||
const auto checkLeft = x + st::backgroundSize.width() - st::overviewCheckSkip - st::overviewCheck.size;
|
||||
const auto checkTop = y + st::backgroundSize.height() - st::overviewCheckSkip - st::overviewCheck.size;
|
||||
_check->paint(p, checkLeft, checkTop, width());
|
||||
} else if (Data::IsCloudWallPaper(paper.data)
|
||||
} else if (!forChannel()
|
||||
&& Data::IsCloudWallPaper(paper.data)
|
||||
&& !Data::IsDefaultWallPaper(paper.data)
|
||||
&& !Data::IsLegacy2DefaultWallPaper(paper.data)
|
||||
&& !Data::IsLegacy3DefaultWallPaper(paper.data)
|
||||
@@ -642,7 +743,8 @@ void BackgroundBox::Inner::mouseMoveEvent(QMouseEvent *e) {
|
||||
- st::stickerPanDeleteIconBg.width();
|
||||
const auto deleteBottom = row * (height + skip) + skip
|
||||
+ st::stickerPanDeleteIconBg.height();
|
||||
const auto inDelete = (x >= deleteLeft)
|
||||
const auto inDelete = !forChannel()
|
||||
&& (x >= deleteLeft)
|
||||
&& (y < deleteBottom)
|
||||
&& Data::IsCloudWallPaper(data)
|
||||
&& !Data::IsDefaultWallPaper(data)
|
||||
|
||||
@@ -38,6 +38,7 @@ private:
|
||||
const Data::WallPaper &paper) const;
|
||||
void removePaper(const Data::WallPaper &paper);
|
||||
void resetForPeer();
|
||||
[[nodiscard]] bool forChannel() const;
|
||||
|
||||
void chooseFromFile();
|
||||
|
||||
|
||||
@@ -8,11 +8,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/background_preview_box.h"
|
||||
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/peers/edit_peer_color_box.h"
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "mainwidget.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/boxes/boost_box.h"
|
||||
#include "ui/controls/chat_service_checkbox.h"
|
||||
#include "ui/chat/chat_theme.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
@@ -29,6 +31,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_item_helpers.h"
|
||||
#include "history/view/history_view_message.h"
|
||||
#include "main/main_account.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "main/main_session.h"
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_session.h"
|
||||
@@ -53,7 +57,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace {
|
||||
|
||||
constexpr auto kMaxWallPaperSlugLength = 255;
|
||||
constexpr auto kDefaultDimming = 50;
|
||||
|
||||
[[nodiscard]] bool IsValidWallPaperSlug(const QString &slug) {
|
||||
if (slug.isEmpty() || slug.size() > kMaxWallPaperSlugLength) {
|
||||
@@ -159,6 +162,25 @@ constexpr auto kDefaultDimming = 50;
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] Data::WallPaper Resolve(
|
||||
not_null<Main::Session*> session,
|
||||
const Data::WallPaper &paper,
|
||||
bool dark) {
|
||||
if (paper.emojiId().isEmpty()) {
|
||||
return paper;
|
||||
}
|
||||
const auto &themes = session->data().cloudThemes();
|
||||
if (const auto theme = themes.themeForEmoji(paper.emojiId())) {
|
||||
using Type = Data::CloudThemeType;
|
||||
const auto type = dark ? Type::Dark : Type::Light;
|
||||
const auto i = theme->settings.find(type);
|
||||
if (i != end(theme->settings) && i->second.paper) {
|
||||
return *i->second.paper;
|
||||
}
|
||||
}
|
||||
return paper;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct BackgroundPreviewBox::OverridenStyle {
|
||||
@@ -196,15 +218,17 @@ BackgroundPreviewBox::BackgroundPreviewBox(
|
||||
? tr::lng_background_apply2(tr::now)
|
||||
: tr::lng_background_text2(tr::now)),
|
||||
true))
|
||||
, _paper(paper)
|
||||
, _paperEmojiId(paper.emojiId())
|
||||
, _paper(
|
||||
Resolve(&controller->session(), paper, Window::Theme::IsNightMode()))
|
||||
, _media(_paper.document() ? _paper.document()->createMediaView() : nullptr)
|
||||
, _radial([=](crl::time now) { radialAnimationCallback(now); })
|
||||
, _appNightMode(Window::Theme::IsNightModeValue())
|
||||
, _boxDarkMode(_appNightMode.current())
|
||||
, _dimmingIntensity(std::clamp(paper.patternIntensity(), 0, 100))
|
||||
, _dimmingIntensity(std::clamp(_paper.patternIntensity(), 0, 100))
|
||||
, _dimmed(_forPeer
|
||||
&& (paper.document() || paper.localThumbnail())
|
||||
&& !paper.isPattern()) {
|
||||
&& (_paper.document() || _paper.localThumbnail())
|
||||
&& !_paper.isPattern()) {
|
||||
if (_media) {
|
||||
_media->thumbnailWanted(_paper.fileOrigin());
|
||||
}
|
||||
@@ -244,7 +268,36 @@ BackgroundPreviewBox::BackgroundPreviewBox(
|
||||
|
||||
BackgroundPreviewBox::~BackgroundPreviewBox() = default;
|
||||
|
||||
void BackgroundPreviewBox::recreate(bool dark) {
|
||||
_paper = Resolve(
|
||||
&_controller->session(),
|
||||
Data::WallPaper::FromEmojiId(_paperEmojiId),
|
||||
dark);
|
||||
_media = _paper.document()
|
||||
? _paper.document()->createMediaView()
|
||||
: nullptr;
|
||||
if (_media) {
|
||||
_media->thumbnailWanted(_paper.fileOrigin());
|
||||
}
|
||||
_full = QImage();
|
||||
_generated = _scaled = _blurred = _fadeOutThumbnail = QPixmap();
|
||||
_generating = {};
|
||||
generateBackground();
|
||||
_paper.loadDocument();
|
||||
if (const auto document = _paper.document()) {
|
||||
if (document->loading()) {
|
||||
_radial.start(_media->progress());
|
||||
}
|
||||
}
|
||||
checkLoadedDocument();
|
||||
updateServiceBg(_paper.backgroundColors());
|
||||
update();
|
||||
}
|
||||
|
||||
void BackgroundPreviewBox::applyDarkMode(bool dark) {
|
||||
if (!_paperEmojiId.isEmpty()) {
|
||||
recreate(dark);
|
||||
}
|
||||
const auto equals = (dark == Window::Theme::IsNightMode());
|
||||
const auto &palette = (dark ? _darkPalette : _lightPalette);
|
||||
if (!equals && !palette) {
|
||||
@@ -410,6 +463,10 @@ auto BackgroundPreviewBox::prepareOverridenStyle(bool dark)
|
||||
return result;
|
||||
}
|
||||
|
||||
bool BackgroundPreviewBox::forChannel() const {
|
||||
return _forPeer && _forPeer->isChannel();
|
||||
}
|
||||
|
||||
void BackgroundPreviewBox::generateBackground() {
|
||||
if (_paper.backgroundColors().empty()) {
|
||||
return;
|
||||
@@ -435,7 +492,9 @@ void BackgroundPreviewBox::resetTitle() {
|
||||
|
||||
void BackgroundPreviewBox::rebuildButtons(bool dark) {
|
||||
clearButtons();
|
||||
addButton(_forPeer
|
||||
addButton(forChannel()
|
||||
? tr::lng_background_apply_channel()
|
||||
: _forPeer
|
||||
? tr::lng_background_apply_button()
|
||||
: tr::lng_settings_apply(), [=] { apply(); });
|
||||
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||
@@ -624,6 +683,36 @@ void BackgroundPreviewBox::setExistingForPeer(
|
||||
_controller->finishChatThemeEdit(_forPeer);
|
||||
}
|
||||
|
||||
void BackgroundPreviewBox::checkLevelForChannel() {
|
||||
Expects(forChannel());
|
||||
|
||||
const auto show = _controller->uiShow();
|
||||
_forPeerLevelCheck = true;
|
||||
const auto weak = Ui::MakeWeak(this);
|
||||
CheckBoostLevel(show, _forPeer, [=](int level) {
|
||||
if (!weak) {
|
||||
return std::optional<Ui::AskBoostReason>();
|
||||
}
|
||||
const auto appConfig = &_forPeer->session().account().appConfig();
|
||||
const auto defaultRequired = appConfig->get<int>(
|
||||
"channel_wallpaper_level_min",
|
||||
9);
|
||||
const auto customRequired = appConfig->get<int>(
|
||||
"channel_custom_wallpaper_level_min",
|
||||
10);
|
||||
const auto required = _paperEmojiId.isEmpty()
|
||||
? customRequired
|
||||
: defaultRequired;
|
||||
if (level >= required) {
|
||||
applyForPeer(false);
|
||||
return std::optional<Ui::AskBoostReason>();
|
||||
}
|
||||
return std::make_optional(Ui::AskBoostReason{
|
||||
Ui::AskBoostWallpaper{ required }
|
||||
});
|
||||
}, [=] { _forPeerLevelCheck = false; });
|
||||
}
|
||||
|
||||
void BackgroundPreviewBox::applyForPeer() {
|
||||
Expects(_forPeer != nullptr);
|
||||
|
||||
@@ -636,105 +725,110 @@ void BackgroundPreviewBox::applyForPeer() {
|
||||
}
|
||||
}
|
||||
|
||||
if (!_fromMessageId && _forPeer->session().premiumPossible()) {
|
||||
if (_forBothOverlay) {
|
||||
return;
|
||||
}
|
||||
const auto size = this->size() * style::DevicePixelRatio();
|
||||
const auto bg = Images::DitherImage(
|
||||
Images::BlurLargeImage(
|
||||
Ui::GrabWidgetToImage(this).scaled(
|
||||
size / style::ConvertScale(4),
|
||||
Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation),
|
||||
24).scaled(
|
||||
size,
|
||||
Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation));
|
||||
|
||||
_forBothOverlay = std::make_unique<Ui::FadeWrap<>>(
|
||||
this,
|
||||
object_ptr<Ui::RpWidget>(this));
|
||||
const auto overlay = _forBothOverlay->entity();
|
||||
|
||||
sizeValue() | rpl::start_with_next([=](QSize size) {
|
||||
_forBothOverlay->setGeometry({ QPoint(), size });
|
||||
overlay->setGeometry({ QPoint(), size });
|
||||
}, _forBothOverlay->lifetime());
|
||||
|
||||
overlay->paintRequest(
|
||||
) | rpl::start_with_next([=](QRect clip) {
|
||||
auto p = QPainter(overlay);
|
||||
p.drawImage(0, 0, bg);
|
||||
p.fillRect(clip, QColor(0, 0, 0, 64));
|
||||
}, overlay->lifetime());
|
||||
|
||||
using namespace Ui;
|
||||
const auto forMe = CreateChild<RoundButton>(
|
||||
overlay,
|
||||
tr::lng_background_apply_me(),
|
||||
st::backgroundConfirm);
|
||||
forMe->setClickedCallback([=] {
|
||||
applyForPeer(false);
|
||||
});
|
||||
using namespace rpl::mappers;
|
||||
const auto forBoth = ::Settings::CreateLockedButton(
|
||||
overlay,
|
||||
tr::lng_background_apply_both(
|
||||
lt_user,
|
||||
rpl::single(_forPeer->shortName())),
|
||||
st::backgroundConfirm,
|
||||
Data::AmPremiumValue(&_forPeer->session()) | rpl::map(!_1));
|
||||
forBoth->setClickedCallback([=] {
|
||||
if (_forPeer->session().premium()) {
|
||||
applyForPeer(true);
|
||||
} else {
|
||||
ShowPremiumPreviewBox(
|
||||
_controller->uiShow(),
|
||||
PremiumPreview::Wallpapers);
|
||||
}
|
||||
});
|
||||
const auto cancel = CreateChild<RoundButton>(
|
||||
overlay,
|
||||
tr::lng_cancel(),
|
||||
st::backgroundConfirmCancel);
|
||||
cancel->setClickedCallback([=] {
|
||||
const auto raw = _forBothOverlay.release();
|
||||
raw->shownValue() | rpl::filter(
|
||||
!rpl::mappers::_1
|
||||
) | rpl::take(1) | rpl::start_with_next(crl::guard(raw, [=] {
|
||||
delete raw;
|
||||
}), raw->lifetime());
|
||||
raw->toggle(false, anim::type::normal);
|
||||
});
|
||||
forMe->setTextTransform(RoundButton::TextTransform::NoTransform);
|
||||
forBoth->setTextTransform(RoundButton::TextTransform::NoTransform);
|
||||
cancel->setTextTransform(RoundButton::TextTransform::NoTransform);
|
||||
|
||||
overlay->sizeValue(
|
||||
) | rpl::start_with_next([=](QSize size) {
|
||||
const auto padding = st::backgroundConfirmPadding;
|
||||
const auto width = size.width()
|
||||
- padding.left()
|
||||
- padding.right();
|
||||
const auto height = cancel->height();
|
||||
auto top = size.height() - padding.bottom() - height;
|
||||
cancel->setGeometry(padding.left(), top, width, height);
|
||||
top -= height + padding.top();
|
||||
forBoth->setGeometry(padding.left(), top, width, height);
|
||||
top -= height + padding.top();
|
||||
forMe->setGeometry(padding.left(), top, width, height);
|
||||
}, _forBothOverlay->lifetime());
|
||||
|
||||
_forBothOverlay->hide(anim::type::instant);
|
||||
_forBothOverlay->show(anim::type::normal);
|
||||
} else {
|
||||
if (forChannel()) {
|
||||
checkLevelForChannel();
|
||||
return;
|
||||
} else if (_fromMessageId || !_forPeer->session().premiumPossible()) {
|
||||
applyForPeer(false);
|
||||
return;
|
||||
} else if (_forBothOverlay) {
|
||||
return;
|
||||
}
|
||||
const auto size = this->size() * style::DevicePixelRatio();
|
||||
const auto bg = Images::DitherImage(
|
||||
Images::BlurLargeImage(
|
||||
Ui::GrabWidgetToImage(this).scaled(
|
||||
size / style::ConvertScale(4),
|
||||
Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation),
|
||||
24).scaled(
|
||||
size,
|
||||
Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation));
|
||||
|
||||
_forBothOverlay = std::make_unique<Ui::FadeWrap<>>(
|
||||
this,
|
||||
object_ptr<Ui::RpWidget>(this));
|
||||
const auto overlay = _forBothOverlay->entity();
|
||||
|
||||
sizeValue() | rpl::start_with_next([=](QSize size) {
|
||||
_forBothOverlay->setGeometry({ QPoint(), size });
|
||||
overlay->setGeometry({ QPoint(), size });
|
||||
}, _forBothOverlay->lifetime());
|
||||
|
||||
overlay->paintRequest(
|
||||
) | rpl::start_with_next([=](QRect clip) {
|
||||
auto p = QPainter(overlay);
|
||||
p.drawImage(0, 0, bg);
|
||||
p.fillRect(clip, QColor(0, 0, 0, 64));
|
||||
}, overlay->lifetime());
|
||||
|
||||
using namespace Ui;
|
||||
const auto forMe = CreateChild<RoundButton>(
|
||||
overlay,
|
||||
tr::lng_background_apply_me(),
|
||||
st::backgroundConfirm);
|
||||
forMe->setClickedCallback([=] {
|
||||
applyForPeer(false);
|
||||
});
|
||||
using namespace rpl::mappers;
|
||||
const auto forBoth = ::Settings::CreateLockedButton(
|
||||
overlay,
|
||||
tr::lng_background_apply_both(
|
||||
lt_user,
|
||||
rpl::single(_forPeer->shortName())),
|
||||
st::backgroundConfirm,
|
||||
Data::AmPremiumValue(&_forPeer->session()) | rpl::map(!_1));
|
||||
forBoth->setClickedCallback([=] {
|
||||
if (_forPeer->session().premium()) {
|
||||
applyForPeer(true);
|
||||
} else {
|
||||
ShowPremiumPreviewBox(
|
||||
_controller->uiShow(),
|
||||
PremiumPreview::Wallpapers);
|
||||
}
|
||||
});
|
||||
const auto cancel = CreateChild<RoundButton>(
|
||||
overlay,
|
||||
tr::lng_cancel(),
|
||||
st::backgroundConfirmCancel);
|
||||
cancel->setClickedCallback([=] {
|
||||
const auto raw = _forBothOverlay.release();
|
||||
raw->shownValue() | rpl::filter(
|
||||
!rpl::mappers::_1
|
||||
) | rpl::take(1) | rpl::start_with_next(crl::guard(raw, [=] {
|
||||
delete raw;
|
||||
}), raw->lifetime());
|
||||
raw->toggle(false, anim::type::normal);
|
||||
});
|
||||
forMe->setTextTransform(RoundButton::TextTransform::NoTransform);
|
||||
forBoth->setTextTransform(RoundButton::TextTransform::NoTransform);
|
||||
cancel->setTextTransform(RoundButton::TextTransform::NoTransform);
|
||||
|
||||
overlay->sizeValue(
|
||||
) | rpl::start_with_next([=](QSize size) {
|
||||
const auto padding = st::backgroundConfirmPadding;
|
||||
const auto width = size.width()
|
||||
- padding.left()
|
||||
- padding.right();
|
||||
const auto height = cancel->height();
|
||||
auto top = size.height() - padding.bottom() - height;
|
||||
cancel->setGeometry(padding.left(), top, width, height);
|
||||
top -= height + padding.top();
|
||||
forBoth->setGeometry(padding.left(), top, width, height);
|
||||
top -= height + padding.top();
|
||||
forMe->setGeometry(padding.left(), top, width, height);
|
||||
}, _forBothOverlay->lifetime());
|
||||
|
||||
_forBothOverlay->hide(anim::type::instant);
|
||||
_forBothOverlay->show(anim::type::normal);
|
||||
}
|
||||
|
||||
void BackgroundPreviewBox::applyForPeer(bool both) {
|
||||
if (Data::IsCustomWallPaper(_paper)) {
|
||||
using namespace Data;
|
||||
if (forChannel() && !_paperEmojiId.isEmpty()) {
|
||||
setExistingForPeer(WallPaper::FromEmojiId(_paperEmojiId), both);
|
||||
} else if (IsCustomWallPaper(_paper)) {
|
||||
uploadForPeer(both);
|
||||
} else {
|
||||
setExistingForPeer(_paper, both);
|
||||
@@ -855,7 +949,7 @@ int BackgroundPreviewBox::textsTop() const {
|
||||
- st::historyPaddingBottom
|
||||
- (_service ? _service->height() : 0)
|
||||
- _text1->height()
|
||||
- _text2->height();
|
||||
- (forChannel() ? _text2->height() : 0);
|
||||
}
|
||||
|
||||
QRect BackgroundPreviewBox::radialRect() const {
|
||||
@@ -885,10 +979,11 @@ void BackgroundPreviewBox::paintTexts(Painter &p, crl::time ms) {
|
||||
context.outbg = _text1->hasOutLayout();
|
||||
_text1->draw(p, context);
|
||||
p.translate(0, height1);
|
||||
|
||||
context.outbg = _text2->hasOutLayout();
|
||||
_text2->draw(p, context);
|
||||
p.translate(0, height2);
|
||||
if (!forChannel()) {
|
||||
context.outbg = _text2->hasOutLayout();
|
||||
_text2->draw(p, context);
|
||||
p.translate(0, height2);
|
||||
}
|
||||
}
|
||||
|
||||
void BackgroundPreviewBox::radialAnimationCallback(crl::time now) {
|
||||
@@ -988,7 +1083,9 @@ void BackgroundPreviewBox::updateServiceBg(const std::vector<QColor> &bg) {
|
||||
_service = GenerateServiceItem(
|
||||
delegate(),
|
||||
_serviceHistory,
|
||||
((_forPeer && !_fromMessageId)
|
||||
(forChannel()
|
||||
? tr::lng_background_other_channel(tr::now)
|
||||
: (_forPeer && !_fromMessageId)
|
||||
? tr::lng_background_other_info(
|
||||
tr::now,
|
||||
lt_user,
|
||||
|
||||
@@ -94,18 +94,24 @@ private:
|
||||
void applyDarkMode(bool dark);
|
||||
[[nodiscard]] OverridenStyle prepareOverridenStyle(bool dark);
|
||||
|
||||
[[nodiscard]] bool forChannel() const;
|
||||
void checkLevelForChannel();
|
||||
|
||||
void recreate(bool dark);
|
||||
void resetTitle();
|
||||
void rebuildButtons(bool dark);
|
||||
void createDimmingSlider(bool dark);
|
||||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
PeerData * const _forPeer = nullptr;
|
||||
bool _forPeerLevelCheck = false;
|
||||
FullMsgId _fromMessageId;
|
||||
std::unique_ptr<Ui::ChatStyle> _chatStyle;
|
||||
const not_null<History*> _serviceHistory;
|
||||
AdminLog::OwnedItem _service;
|
||||
AdminLog::OwnedItem _text1;
|
||||
AdminLog::OwnedItem _text2;
|
||||
QString _paperEmojiId;
|
||||
Data::WallPaper _paper;
|
||||
std::shared_ptr<Data::DocumentMedia> _media;
|
||||
QImage _full;
|
||||
|
||||
@@ -113,7 +113,8 @@ Base64UrlInput::Base64UrlInput(
|
||||
rpl::producer<QString> placeholder,
|
||||
const QString &val)
|
||||
: MaskedInputField(parent, st, std::move(placeholder), val) {
|
||||
if (!QRegularExpression("^[a-zA-Z0-9_\\-]+$").match(val).hasMatch()) {
|
||||
static const auto RegExp = QRegularExpression("^[a-zA-Z0-9_\\-]+$");
|
||||
if (!RegExp.match(val).hasMatch()) {
|
||||
setText(QString());
|
||||
}
|
||||
}
|
||||
@@ -831,8 +832,9 @@ void ProxyBox::prepare() {
|
||||
connect(_host.data(), &HostInput::changed, [=] {
|
||||
Ui::PostponeCall(_host, [=] {
|
||||
const auto host = _host->getLastText().trimmed();
|
||||
static const auto mask = u"^\\d+\\.\\d+\\.\\d+\\.\\d+:(\\d*)$"_q;
|
||||
const auto match = QRegularExpression(mask).match(host);
|
||||
static const auto mask = QRegularExpression(
|
||||
u"^\\d+\\.\\d+\\.\\d+\\.\\d+:(\\d*)$"_q);
|
||||
const auto match = mask.match(host);
|
||||
if (_host->cursorPosition() == host.size()
|
||||
&& match.hasMatch()) {
|
||||
const auto port = match.captured(1);
|
||||
@@ -1107,6 +1109,10 @@ void ProxiesBoxController::ShowApplyConfirmation(
|
||||
proxy.password = fields.value(u"secret"_q);
|
||||
}
|
||||
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)
|
||||
@@ -1117,11 +1123,9 @@ void ProxiesBoxController::ShowApplyConfirmation(
|
||||
const auto displayServer = QString(
|
||||
displayUrl
|
||||
).replace(
|
||||
QRegularExpression(
|
||||
"^https://",
|
||||
QRegularExpression::CaseInsensitiveOption),
|
||||
UrlStartRegExp,
|
||||
QString()
|
||||
).replace(QRegularExpression("/$"), QString());
|
||||
).replace(UrlEndRegExp, QString());
|
||||
const auto text = tr::lng_sure_enable_socks(
|
||||
tr::now,
|
||||
lt_server,
|
||||
|
||||
@@ -12,18 +12,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "apiwrap.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "base/weak_ptr.h"
|
||||
#include "boxes/peer_list_controllers.h" // ContactsBoxController.
|
||||
#include "boxes/peers/prepare_short_info_box.h"
|
||||
#include "boxes/peers/replace_boost_box.h" // BoostsForGift.
|
||||
#include "boxes/premium_preview_box.h" // ShowPremiumPreviewBox.
|
||||
#include "core/ui_integration.h" // Core::MarkedTextContext.
|
||||
#include "data/data_boosts.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_media_types.h" // Data::Giveaway
|
||||
#include "data/data_media_types.h" // Data::GiveawayStart.
|
||||
#include "data/data_peer_values.h" // Data::PeerPremiumValue.
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_subscription_option.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "info/boosts/giveaway/boost_badge.h" // InfiniteRadialAnimationWidget.
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "mainwidget.h"
|
||||
#include "payments/payments_checkout_process.h"
|
||||
#include "payments/payments_form.h"
|
||||
#include "settings/settings_premium.h"
|
||||
#include "ui/basic_click_handlers.h" // UrlClickHandler::Open.
|
||||
#include "ui/boxes/boost_box.h" // StartFireworks.
|
||||
@@ -33,11 +41,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/effects/premium_top_bar.h"
|
||||
#include "ui/effects/spoiler_mess.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/gradient_round_button.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/wrap/table_layout.h"
|
||||
#include "window/window_peer_menu.h" // ShowChooseRecipientBox.
|
||||
#include "window/window_session_controller.h"
|
||||
@@ -51,7 +62,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kDiscountDivider = 5.;
|
||||
constexpr auto kUserpicsMax = size_t(3);
|
||||
|
||||
using GiftOption = Data::SubscriptionOption;
|
||||
using GiftOptions = Data::SubscriptionOptions;
|
||||
@@ -72,6 +83,137 @@ GiftOptions GiftOptionFromTL(const MTPDuserFull &data) {
|
||||
return result;
|
||||
}
|
||||
|
||||
using TagUser1 = lngtag_user;
|
||||
using TagUser2 = lngtag_second_user;
|
||||
using TagUser3 = lngtag_name;
|
||||
[[nodiscard]] rpl::producer<TextWithEntities> ComplexAboutLabel(
|
||||
const std::vector<not_null<UserData*>> &users,
|
||||
tr::phrase<TagUser1> phrase1,
|
||||
tr::phrase<TagUser1, TagUser2> phrase2,
|
||||
tr::phrase<TagUser1, TagUser2, TagUser3> phrase3,
|
||||
tr::phrase<lngtag_count, TagUser1, TagUser2, TagUser3> phraseMore) {
|
||||
Expects(!users.empty());
|
||||
|
||||
const auto count = users.size();
|
||||
const auto nameValue = [&](not_null<UserData*> user) {
|
||||
return user->session().changes().peerFlagsValue(
|
||||
user,
|
||||
Data::PeerUpdate::Flag::Name
|
||||
) | rpl::map([=] { return TextWithEntities{ user->firstName }; });
|
||||
};
|
||||
if (count == 1) {
|
||||
return phrase1(
|
||||
lt_user,
|
||||
nameValue(users.front()),
|
||||
Ui::Text::RichLangValue);
|
||||
} else if (count == 2) {
|
||||
return phrase2(
|
||||
lt_user,
|
||||
nameValue(users.front()),
|
||||
lt_second_user,
|
||||
nameValue(users[1]),
|
||||
Ui::Text::RichLangValue);
|
||||
} else if (count == 3) {
|
||||
return phrase3(
|
||||
lt_user,
|
||||
nameValue(users.front()),
|
||||
lt_second_user,
|
||||
nameValue(users[1]),
|
||||
lt_name,
|
||||
nameValue(users[2]),
|
||||
Ui::Text::RichLangValue);
|
||||
} else {
|
||||
return phraseMore(
|
||||
lt_count,
|
||||
rpl::single(count - kUserpicsMax) | tr::to_count(),
|
||||
lt_user,
|
||||
nameValue(users.front()),
|
||||
lt_second_user,
|
||||
nameValue(users[1]),
|
||||
lt_name,
|
||||
nameValue(users[2]),
|
||||
Ui::Text::RichLangValue);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] not_null<Ui::RpWidget*> CircleBadge(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
const QString &text) {
|
||||
const auto widget = Ui::CreateChild<Ui::RpWidget>(parent.get());
|
||||
|
||||
const auto full = Rect(st::premiumGiftsUserpicBadgeSize);
|
||||
const auto inner = full - Margins(st::premiumGiftsUserpicBadgeInner);
|
||||
auto gradient = QLinearGradient(
|
||||
QPointF(0, full.height()),
|
||||
QPointF(full.width(), 0));
|
||||
gradient.setStops(Ui::Premium::GiftGradientStops());
|
||||
|
||||
widget->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
auto p = QPainter(widget);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::boxBg);
|
||||
p.drawEllipse(full);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(gradient);
|
||||
p.drawEllipse(inner);
|
||||
p.setFont(st::premiumGiftsUserpicBadgeFont);
|
||||
p.setPen(st::premiumButtonFg);
|
||||
p.drawText(full, text, style::al_center);
|
||||
}, widget->lifetime());
|
||||
widget->resize(full.size());
|
||||
return widget;
|
||||
}
|
||||
|
||||
[[nodiscard]] not_null<Ui::RpWidget*> UserpicsContainer(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
std::vector<not_null<UserData*>> users) {
|
||||
Expects(!users.empty());
|
||||
|
||||
if (users.size() == 1) {
|
||||
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
|
||||
parent.get(),
|
||||
users.front(),
|
||||
st::defaultUserpicButton);
|
||||
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
return userpic;
|
||||
}
|
||||
|
||||
const auto &singleSize = st::defaultUserpicButton.size;
|
||||
|
||||
const auto container = Ui::CreateChild<Ui::RpWidget>(parent.get());
|
||||
const auto single = singleSize.width();
|
||||
const auto shift = single - st::boostReplaceUserpicsShift;
|
||||
const auto maxWidth = users.size() * (single - shift) + shift;
|
||||
container->resize(maxWidth, singleSize.height());
|
||||
container->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
const auto diff = (single - st::premiumGiftsUserpicButton.size.width())
|
||||
/ 2;
|
||||
for (auto i = 0; i < users.size(); i++) {
|
||||
const auto bg = Ui::CreateChild<Ui::RpWidget>(container);
|
||||
bg->resize(singleSize);
|
||||
bg->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
auto p = QPainter(bg);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::boxBg);
|
||||
p.drawEllipse(bg->rect());
|
||||
}, bg->lifetime());
|
||||
bg->moveToLeft(std::max(0, i * (single - shift)), 0);
|
||||
|
||||
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
|
||||
bg,
|
||||
users[i],
|
||||
st::premiumGiftsUserpicButton);
|
||||
userpic->moveToLeft(diff, diff);
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
void GiftBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionController*> controller,
|
||||
@@ -95,12 +237,11 @@ void GiftBox(
|
||||
+ st::defaultUserpicButton.size.height()));
|
||||
|
||||
using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
|
||||
const auto stars = box->lifetime().make_state<ColoredMiniStars>(top, true);
|
||||
|
||||
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
|
||||
const auto stars = box->lifetime().make_state<ColoredMiniStars>(
|
||||
top,
|
||||
user,
|
||||
st::defaultUserpicButton);
|
||||
true);
|
||||
|
||||
const auto userpic = UserpicsContainer(top, { user });
|
||||
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
top->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
@@ -211,7 +352,7 @@ void GiftBox(
|
||||
auto raw = Settings::CreateSubscribeButton({
|
||||
controller,
|
||||
box,
|
||||
[] { return QString("gift"); },
|
||||
[] { return u"gift"_q; },
|
||||
state->buttonText.events(),
|
||||
Ui::Premium::GiftGradientStops(),
|
||||
[=] {
|
||||
@@ -222,10 +363,8 @@ void GiftBox(
|
||||
},
|
||||
});
|
||||
auto button = object_ptr<Ui::GradientButton>::fromRaw(raw);
|
||||
button->resizeToWidth(boxWidth
|
||||
- stButton.buttonPadding.left()
|
||||
- stButton.buttonPadding.right());
|
||||
box->setShowFinishedCallback([raw = button.data()]{
|
||||
button->resizeToWidth(boxWidth - rect::m::sum::h(stButton.buttonPadding));
|
||||
box->setShowFinishedCallback([raw = button.data()] {
|
||||
raw->startGlareAnimation();
|
||||
});
|
||||
box->addButton(std::move(button));
|
||||
@@ -239,6 +378,316 @@ void GiftBox(
|
||||
}, box->lifetime());
|
||||
}
|
||||
|
||||
void GiftsBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionController*> controller,
|
||||
std::vector<not_null<UserData*>> users,
|
||||
not_null<Api::PremiumGiftCodeOptions*> api,
|
||||
const QString &ref) {
|
||||
Expects(!users.empty());
|
||||
|
||||
const auto boxWidth = st::boxWideWidth;
|
||||
box->setWidth(boxWidth);
|
||||
box->setNoContentMargin(true);
|
||||
const auto buttonsParent = box->verticalLayout().get();
|
||||
const auto session = &users.front()->session();
|
||||
|
||||
struct State {
|
||||
rpl::event_stream<QString> buttonText;
|
||||
rpl::variable<bool> confirmButtonBusy = false;
|
||||
rpl::variable<bool> isPaymentComplete = false;
|
||||
};
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
|
||||
const auto userpicPadding = st::premiumGiftUserpicPadding;
|
||||
const auto top = box->addRow(object_ptr<Ui::FixedHeightWidget>(
|
||||
buttonsParent,
|
||||
userpicPadding.top()
|
||||
+ userpicPadding.bottom()
|
||||
+ st::defaultUserpicButton.size.height()));
|
||||
|
||||
using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
|
||||
const auto stars = box->lifetime().make_state<ColoredMiniStars>(
|
||||
top,
|
||||
true);
|
||||
|
||||
const auto maxWithUserpic = std::min(users.size(), kUserpicsMax);
|
||||
const auto userpics = UserpicsContainer(
|
||||
top,
|
||||
{ users.begin(), users.begin() + maxWithUserpic });
|
||||
top->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
userpics->moveToLeft(
|
||||
(width - userpics->width()) / 2,
|
||||
userpicPadding.top());
|
||||
|
||||
const auto center = top->rect().center();
|
||||
const auto size = QSize(
|
||||
userpics->width() * Ui::Premium::MiniStars::kSizeFactor,
|
||||
userpics->height());
|
||||
const auto ministarsRect = QRect(
|
||||
QPoint(center.x() - size.width(), center.y() - size.height()),
|
||||
QPoint(center.x() + size.width(), center.y() + size.height()));
|
||||
stars->setPosition(ministarsRect.topLeft());
|
||||
stars->setSize(ministarsRect.size());
|
||||
}, userpics->lifetime());
|
||||
if (const auto rest = users.size() - maxWithUserpic; rest > 0) {
|
||||
const auto badge = CircleBadge(
|
||||
userpics,
|
||||
QChar('+') + QString::number(rest));
|
||||
badge->moveToRight(0, userpics->height() - badge->height());
|
||||
}
|
||||
|
||||
top->paintRequest(
|
||||
) | rpl::start_with_next([=](const QRect &r) {
|
||||
auto p = QPainter(top);
|
||||
|
||||
p.fillRect(r, Qt::transparent);
|
||||
stars->paint(p);
|
||||
}, top->lifetime());
|
||||
|
||||
const auto close = Ui::CreateChild<Ui::IconButton>(
|
||||
buttonsParent,
|
||||
st::infoTopBarClose);
|
||||
close->setClickedCallback([=] { box->closeBox(); });
|
||||
|
||||
buttonsParent->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
close->moveToRight(0, 0, width);
|
||||
}, close->lifetime());
|
||||
|
||||
// Header.
|
||||
const auto &padding = st::premiumGiftAboutPadding;
|
||||
const auto available = boxWidth - padding.left() - padding.right();
|
||||
const auto &stTitle = st::premiumPreviewAboutTitle;
|
||||
auto titleLabel = object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
rpl::conditional(
|
||||
state->isPaymentComplete.value(),
|
||||
tr::lng_premium_gifts_about_paid_title(),
|
||||
tr::lng_premium_gift_title()),
|
||||
stTitle);
|
||||
titleLabel->resizeToWidth(available);
|
||||
box->addRow(
|
||||
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
|
||||
box,
|
||||
std::move(titleLabel)),
|
||||
st::premiumGiftTitlePadding);
|
||||
|
||||
// About.
|
||||
{
|
||||
const auto emoji = Ui::Text::SingleCustomEmoji(
|
||||
session->data().customEmojiManager().registerInternalEmoji(
|
||||
st::premiumGiftsBoostIcon,
|
||||
QMargins(0, st::premiumGiftsUserpicBadgeInner, 0, 0),
|
||||
false));
|
||||
auto text = rpl::conditional(
|
||||
state->isPaymentComplete.value(),
|
||||
ComplexAboutLabel(
|
||||
users,
|
||||
tr::lng_premium_gifts_about_paid1,
|
||||
tr::lng_premium_gifts_about_paid2,
|
||||
tr::lng_premium_gifts_about_paid3,
|
||||
tr::lng_premium_gifts_about_paid_more
|
||||
) | rpl::map([count = users.size()](TextWithEntities text) {
|
||||
text.append('\n');
|
||||
text.append('\n');
|
||||
text.append(tr::lng_premium_gifts_about_paid_below(
|
||||
tr::now,
|
||||
lt_count,
|
||||
float64(count),
|
||||
Ui::Text::RichLangValue));
|
||||
return text;
|
||||
}),
|
||||
ComplexAboutLabel(
|
||||
users,
|
||||
tr::lng_premium_gifts_about_user1,
|
||||
tr::lng_premium_gifts_about_user2,
|
||||
tr::lng_premium_gifts_about_user3,
|
||||
tr::lng_premium_gifts_about_user_more
|
||||
) | rpl::map([=, count = users.size()](TextWithEntities text) {
|
||||
text.append('\n');
|
||||
text.append('\n');
|
||||
text.append(tr::lng_premium_gifts_about_reward(
|
||||
tr::now,
|
||||
lt_count,
|
||||
count * BoostsForGift(session),
|
||||
lt_emoji,
|
||||
emoji,
|
||||
Ui::Text::RichLangValue));
|
||||
return text;
|
||||
})
|
||||
);
|
||||
const auto label = box->addRow(
|
||||
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
|
||||
box,
|
||||
object_ptr<Ui::FlatLabel>(box, st::premiumPreviewAbout)),
|
||||
padding)->entity();
|
||||
std::move(
|
||||
text
|
||||
) | rpl::start_with_next([=](const TextWithEntities &t) {
|
||||
using namespace Core;
|
||||
label->setMarkedText(t, MarkedTextContext{ .session = session });
|
||||
}, label->lifetime());
|
||||
label->setTextColorOverride(stTitle.textFg->c);
|
||||
label->resizeToWidth(available);
|
||||
}
|
||||
|
||||
// List.
|
||||
const auto optionsContainer = buttonsParent->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
buttonsParent,
|
||||
object_ptr<Ui::VerticalLayout>(buttonsParent)));
|
||||
const auto options = api->options(users.size());
|
||||
const auto group = std::make_shared<Ui::RadiobuttonGroup>();
|
||||
const auto groupValueChangedCallback = [=](int value) {
|
||||
Expects(value < options.size() && value >= 0);
|
||||
auto text = tr::lng_premium_gift_button(
|
||||
tr::now,
|
||||
lt_cost,
|
||||
options[value].costTotal);
|
||||
state->buttonText.fire(std::move(text));
|
||||
};
|
||||
group->setChangedCallback(groupValueChangedCallback);
|
||||
Ui::Premium::AddGiftOptions(
|
||||
optionsContainer->entity(),
|
||||
group,
|
||||
options,
|
||||
st::premiumGiftOption);
|
||||
optionsContainer->toggleOn(
|
||||
state->isPaymentComplete.value() | rpl::map(!rpl::mappers::_1),
|
||||
anim::type::instant);
|
||||
|
||||
// Summary.
|
||||
{
|
||||
{
|
||||
// Will be hidden after payment.
|
||||
const auto content = optionsContainer->entity();
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddDivider(content);
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSubsectionTitle(
|
||||
content,
|
||||
tr::lng_premium_gifts_summary_subtitle());
|
||||
}
|
||||
const auto content = box->addRow(
|
||||
object_ptr<Ui::VerticalLayout>(box),
|
||||
{});
|
||||
auto buttonCallback = [=](PremiumPreview section) {
|
||||
stars->setPaused(true);
|
||||
const auto previewBoxShown = [=](
|
||||
not_null<Ui::BoxContent*> previewBox) {
|
||||
previewBox->boxClosing(
|
||||
) | rpl::start_with_next(crl::guard(box, [=] {
|
||||
stars->setPaused(false);
|
||||
}), previewBox->lifetime());
|
||||
};
|
||||
|
||||
ShowPremiumPreviewBox(
|
||||
controller->uiShow(),
|
||||
section,
|
||||
previewBoxShown,
|
||||
true);
|
||||
};
|
||||
Settings::AddSummaryPremium(
|
||||
content,
|
||||
controller,
|
||||
ref,
|
||||
std::move(buttonCallback));
|
||||
}
|
||||
|
||||
// Footer.
|
||||
{
|
||||
box->addRow(
|
||||
object_ptr<Ui::DividerLabel>(
|
||||
box,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_premium_gifts_terms(
|
||||
lt_link,
|
||||
tr::lng_payments_terms_link(
|
||||
) | rpl::map([](const QString &t) {
|
||||
using namespace Ui::Text;
|
||||
return Link(t, u"https://telegram.org/tos"_q);
|
||||
}),
|
||||
lt_policy,
|
||||
tr::lng_premium_gifts_terms_policy(
|
||||
) | rpl::map([](const QString &t) {
|
||||
using namespace Ui::Text;
|
||||
return Link(t, u"https://telegram.org/privacy"_q);
|
||||
}),
|
||||
Ui::Text::RichLangValue),
|
||||
st::premiumGiftTerms),
|
||||
st::defaultBoxDividerLabelPadding),
|
||||
{});
|
||||
}
|
||||
|
||||
// Button.
|
||||
const auto &stButton = st::premiumGiftBox;
|
||||
box->setStyle(stButton);
|
||||
auto raw = Settings::CreateSubscribeButton({
|
||||
controller,
|
||||
box,
|
||||
[=] { return ref; },
|
||||
rpl::combine(
|
||||
state->buttonText.events(),
|
||||
state->confirmButtonBusy.value(),
|
||||
state->isPaymentComplete.value()
|
||||
) | rpl::map([](const QString &text, bool busy, bool paid) {
|
||||
return busy
|
||||
? QString()
|
||||
: paid
|
||||
? tr::lng_close(tr::now)
|
||||
: text;
|
||||
}),
|
||||
Ui::Premium::GiftGradientStops(),
|
||||
});
|
||||
raw->setClickedCallback([=] {
|
||||
if (state->confirmButtonBusy.current()) {
|
||||
return;
|
||||
}
|
||||
if (state->isPaymentComplete.current()) {
|
||||
return box->closeBox();
|
||||
}
|
||||
auto invoice = api->invoice(
|
||||
users.size(),
|
||||
api->monthsFromPreset(group->value()));
|
||||
invoice.purpose = Payments::InvoicePremiumGiftCodeUsers{ users };
|
||||
|
||||
state->confirmButtonBusy = true;
|
||||
const auto show = box->uiShow();
|
||||
const auto weak = Ui::MakeWeak(box.get());
|
||||
const auto done = [=](Payments::CheckoutResult result) {
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->window()->setFocus();
|
||||
state->confirmButtonBusy = false;
|
||||
if (result == Payments::CheckoutResult::Paid) {
|
||||
state->isPaymentComplete = true;
|
||||
Ui::StartFireworks(box->parentWidget());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Payments::CheckoutProcess::Start(std::move(invoice), done);
|
||||
});
|
||||
{
|
||||
using namespace Info::Statistics;
|
||||
const auto loadingAnimation = InfiniteRadialAnimationWidget(
|
||||
raw,
|
||||
raw->height() / 2);
|
||||
AddChildToWidgetCenter(raw, loadingAnimation);
|
||||
loadingAnimation->showOn(state->confirmButtonBusy.value());
|
||||
}
|
||||
auto button = object_ptr<Ui::GradientButton>::fromRaw(raw);
|
||||
button->resizeToWidth(boxWidth - rect::m::sum::h(stButton.buttonPadding));
|
||||
box->setShowFinishedCallback([raw = button.data()] {
|
||||
raw->startGlareAnimation();
|
||||
});
|
||||
box->addButton(std::move(button));
|
||||
|
||||
groupValueChangedCallback(0);
|
||||
}
|
||||
|
||||
[[nodiscard]] Data::GiftCodeLink MakeGiftCodeLink(
|
||||
not_null<Main::Session*> session,
|
||||
const QString &slug) {
|
||||
@@ -370,18 +819,20 @@ void AddTable(
|
||||
container,
|
||||
st::giveawayGiftCodeTable),
|
||||
st::giveawayGiftCodeTableMargin);
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_from(),
|
||||
controller,
|
||||
current.from);
|
||||
if (current.to) {
|
||||
if (current.from) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_from(),
|
||||
controller,
|
||||
current.from);
|
||||
}
|
||||
if (current.from && current.to) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_to(),
|
||||
controller,
|
||||
current.to);
|
||||
} else {
|
||||
} else if (current.from) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_to(),
|
||||
@@ -394,7 +845,7 @@ void AddTable(
|
||||
lt_duration,
|
||||
GiftDurationValue(current.months) | Ui::Text::ToWithEntities(),
|
||||
Ui::Text::WithEntities));
|
||||
if (!skipReason) {
|
||||
if (!skipReason && current.from) {
|
||||
const auto reason = AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_reason(),
|
||||
@@ -439,6 +890,116 @@ void GiftPremiumValidator::cancel() {
|
||||
_requestId = 0;
|
||||
}
|
||||
|
||||
void GiftPremiumValidator::showChoosePeerBox(const QString &ref) {
|
||||
if (_manyGiftsLifetime) {
|
||||
return;
|
||||
}
|
||||
using namespace Api;
|
||||
const auto api = _manyGiftsLifetime.make_state<PremiumGiftCodeOptions>(
|
||||
_controller->session().user());
|
||||
const auto show = _controller->uiShow();
|
||||
api->request(
|
||||
) | rpl::start_with_error_done([=](const QString &error) {
|
||||
show->showToast(error);
|
||||
}, [=] {
|
||||
const auto maxAmount = *ranges::max_element(api->availablePresets());
|
||||
|
||||
class Controller final : public ContactsBoxController {
|
||||
public:
|
||||
Controller(
|
||||
not_null<Main::Session*> session,
|
||||
Fn<bool(int)> checkErrorCallback)
|
||||
: ContactsBoxController(session)
|
||||
, _checkErrorCallback(std::move(checkErrorCallback)) {
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unique_ptr<PeerListRow> createRow(
|
||||
not_null<UserData*> user) override {
|
||||
if (user->isSelf()
|
||||
|| user->isBot()
|
||||
|| user->isServiceUser()
|
||||
|| user->isInaccessible()) {
|
||||
return nullptr;
|
||||
}
|
||||
return ContactsBoxController::createRow(user);
|
||||
}
|
||||
|
||||
void rowClicked(not_null<PeerListRow*> row) override {
|
||||
const auto checked = !row->checked();
|
||||
if (checked
|
||||
&& _checkErrorCallback
|
||||
&& _checkErrorCallback(
|
||||
delegate()->peerListSelectedRowsCount())) {
|
||||
return;
|
||||
}
|
||||
delegate()->peerListSetRowChecked(row, checked);
|
||||
}
|
||||
|
||||
private:
|
||||
const Fn<bool(int)> _checkErrorCallback;
|
||||
|
||||
};
|
||||
auto initBox = [=](not_null<PeerListBox*> peersBox) {
|
||||
const auto ignoreClose = peersBox->lifetime().make_state<bool>(0);
|
||||
|
||||
auto process = [=] {
|
||||
const auto selected = peersBox->collectSelectedRows();
|
||||
const auto users = ranges::views::all(
|
||||
selected
|
||||
) | ranges::views::transform([](not_null<PeerData*> p) {
|
||||
return p->asUser();
|
||||
}) | ranges::views::filter([](UserData *u) -> bool {
|
||||
return u;
|
||||
}) | ranges::to<std::vector<not_null<UserData*>>>();
|
||||
if (!users.empty()) {
|
||||
const auto giftBox = show->show(
|
||||
Box(GiftsBox, _controller, users, api, ref));
|
||||
giftBox->boxClosing(
|
||||
) | rpl::start_with_next([=] {
|
||||
_manyGiftsLifetime.destroy();
|
||||
}, giftBox->lifetime());
|
||||
}
|
||||
(*ignoreClose) = true;
|
||||
peersBox->closeBox();
|
||||
};
|
||||
|
||||
peersBox->setTitle(tr::lng_premium_gift_title());
|
||||
peersBox->addButton(
|
||||
tr::lng_settings_gift_premium_users_confirm(),
|
||||
std::move(process));
|
||||
peersBox->addButton(tr::lng_cancel(), [=] {
|
||||
peersBox->closeBox();
|
||||
});
|
||||
peersBox->boxClosing(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (!(*ignoreClose)) {
|
||||
_manyGiftsLifetime.destroy();
|
||||
}
|
||||
}, peersBox->lifetime());
|
||||
};
|
||||
|
||||
auto listController = std::make_unique<Controller>(
|
||||
&_controller->session(),
|
||||
[=](int count) {
|
||||
if (count <= maxAmount) {
|
||||
return false;
|
||||
}
|
||||
show->showToast(tr::lng_settings_gift_premium_users_error(
|
||||
tr::now,
|
||||
lt_count,
|
||||
maxAmount));
|
||||
return true;
|
||||
});
|
||||
show->showBox(
|
||||
Box<PeerListBox>(
|
||||
std::move(listController),
|
||||
std::move(initBox)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
|
||||
}, _manyGiftsLifetime);
|
||||
}
|
||||
|
||||
void GiftPremiumValidator::showBox(not_null<UserData*> user) {
|
||||
if (_requestId) {
|
||||
return;
|
||||
@@ -493,7 +1054,7 @@ void GiftCodeBox(
|
||||
state->data = session->api().premium().giftCodeValue(slug);
|
||||
state->used = state->data.value(
|
||||
) | rpl::map([=](const Api::GiftCode &data) {
|
||||
return data.used;
|
||||
return data.used != 0;
|
||||
});
|
||||
|
||||
box->setWidth(st::boxWideWidth);
|
||||
@@ -723,10 +1284,22 @@ void GiftCodePendingBox(
|
||||
|
||||
void ResolveGiftCode(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
const QString &slug) {
|
||||
const QString &slug,
|
||||
PeerId fromId,
|
||||
PeerId toId) {
|
||||
const auto done = [=](Api::GiftCode code) {
|
||||
const auto session = &controller->session();
|
||||
const auto selfId = session->userPeerId();
|
||||
if (!code) {
|
||||
controller->showToast(tr::lng_gift_link_expired(tr::now));
|
||||
} else if (!code.from && fromId == selfId) {
|
||||
code.from = fromId;
|
||||
code.to = toId;
|
||||
const auto self = (fromId == selfId);
|
||||
const auto peer = session->data().peer(self ? toId : fromId);
|
||||
const auto months = code.months;
|
||||
const auto parent = controller->parentController();
|
||||
Settings::ShowGiftPremium(parent, peer, months, self);
|
||||
} else {
|
||||
controller->uiShow()->showBox(Box(GiftCodeBox, controller, slug));
|
||||
}
|
||||
@@ -739,8 +1312,11 @@ void ResolveGiftCode(
|
||||
void GiveawayInfoBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
Data::Giveaway giveaway,
|
||||
std::optional<Data::GiveawayStart> start,
|
||||
std::optional<Data::GiveawayResults> results,
|
||||
Api::GiveawayInfo info) {
|
||||
Expects(start || results);
|
||||
|
||||
using State = Api::GiveawayState;
|
||||
const auto finished = (info.state == State::Finished)
|
||||
|| (info.state == State::Refunded);
|
||||
@@ -749,10 +1325,31 @@ void GiveawayInfoBox(
|
||||
? tr::lng_prizes_end_title
|
||||
: tr::lng_prizes_how_title)());
|
||||
|
||||
const auto first = !giveaway.channels.empty()
|
||||
? giveaway.channels.front()->name()
|
||||
const auto first = results
|
||||
? results->channel->name()
|
||||
: !start->channels.empty()
|
||||
? start->channels.front()->name()
|
||||
: u"channel"_q;
|
||||
auto text = (finished
|
||||
auto text = TextWithEntities();
|
||||
|
||||
if (!info.giftCode.isEmpty()) {
|
||||
text.append("\n\n");
|
||||
text.append(Ui::Text::Bold(tr::lng_prizes_you_won(
|
||||
tr::now,
|
||||
lt_cup,
|
||||
QString::fromUtf8("\xf0\x9f\x8f\x86"))));
|
||||
text.append("\n\n");
|
||||
} else if (info.state == State::Finished) {
|
||||
text.append("\n\n");
|
||||
text.append(Ui::Text::Bold(tr::lng_prizes_you_didnt(tr::now)));
|
||||
text.append("\n\n");
|
||||
}
|
||||
|
||||
const auto quantity = start
|
||||
? start->quantity
|
||||
: (results->winnersCount + results->unclaimedCount);
|
||||
const auto months = start ? start->months : results->months;
|
||||
text.append((finished
|
||||
? tr::lng_prizes_end_text
|
||||
: tr::lng_prizes_how_text)(
|
||||
tr::now,
|
||||
@@ -760,18 +1357,21 @@ void GiveawayInfoBox(
|
||||
tr::lng_prizes_admins(
|
||||
tr::now,
|
||||
lt_count,
|
||||
giveaway.quantity,
|
||||
quantity,
|
||||
lt_channel,
|
||||
Ui::Text::Bold(first),
|
||||
lt_duration,
|
||||
TextWithEntities{ GiftDuration(giveaway.months) },
|
||||
TextWithEntities{ GiftDuration(months) },
|
||||
Ui::Text::RichLangValue),
|
||||
Ui::Text::RichLangValue);
|
||||
const auto many = (giveaway.channels.size() > 1);
|
||||
Ui::Text::RichLangValue));
|
||||
const auto many = start
|
||||
? (start->channels.size() > 1)
|
||||
: (results->additionalPeersCount > 0);
|
||||
const auto count = info.winnersCount
|
||||
? info.winnersCount
|
||||
: giveaway.quantity;
|
||||
auto winners = giveaway.all
|
||||
: quantity;
|
||||
const auto all = start ? start->all : results->all;
|
||||
auto winners = all
|
||||
? (many
|
||||
? tr::lng_prizes_winners_all_of_many
|
||||
: tr::lng_prizes_winners_all_of_one)(
|
||||
@@ -793,13 +1393,30 @@ void GiveawayInfoBox(
|
||||
Ui::Text::Bold(
|
||||
langDateTime(base::unixtime::parse(info.startDate))),
|
||||
Ui::Text::RichLangValue);
|
||||
const auto additionalPrize = results
|
||||
? results->additionalPrize
|
||||
: start->additionalPrize;
|
||||
if (!additionalPrize.isEmpty()) {
|
||||
text.append("\n\n").append(tr::lng_prizes_additional_added(
|
||||
tr::now,
|
||||
lt_count,
|
||||
count,
|
||||
lt_channel,
|
||||
Ui::Text::Bold(first),
|
||||
lt_prize,
|
||||
TextWithEntities{ additionalPrize },
|
||||
Ui::Text::RichLangValue));
|
||||
}
|
||||
const auto untilDate = start
|
||||
? start->untilDate
|
||||
: results->untilDate;
|
||||
text.append("\n\n").append((finished
|
||||
? tr::lng_prizes_end_when_finish
|
||||
: tr::lng_prizes_how_when_finish)(
|
||||
tr::now,
|
||||
lt_date,
|
||||
Ui::Text::Bold(langDayOfMonthFull(
|
||||
base::unixtime::parse(giveaway.untilDate).date())),
|
||||
base::unixtime::parse(untilDate).date())),
|
||||
lt_winners,
|
||||
winners,
|
||||
Ui::Text::RichLangValue));
|
||||
@@ -810,17 +1427,9 @@ void GiveawayInfoBox(
|
||||
info.activatedCount,
|
||||
Ui::Text::RichLangValue));
|
||||
}
|
||||
if (!info.giftCode.isEmpty()) {
|
||||
text.append("\n\n");
|
||||
text.append(tr::lng_prizes_you_won(
|
||||
tr::now,
|
||||
lt_cup,
|
||||
QString::fromUtf8("\xf0\x9f\x8f\x86")));
|
||||
} else if (info.state == State::Finished) {
|
||||
text.append("\n\n");
|
||||
text.append(tr::lng_prizes_you_didnt(tr::now));
|
||||
} else if (info.state == State::Preparing) {
|
||||
|
||||
if (!info.giftCode.isEmpty()
|
||||
|| info.state == State::Finished
|
||||
|| info.state == State::Preparing) {
|
||||
} else if (info.state != State::Refunded) {
|
||||
if (info.adminChannelId) {
|
||||
const auto channel = controller->session().data().channel(
|
||||
@@ -858,7 +1467,7 @@ void GiveawayInfoBox(
|
||||
Ui::Text::Bold(first),
|
||||
lt_date,
|
||||
Ui::Text::Bold(langDayOfMonthFull(
|
||||
base::unixtime::parse(giveaway.untilDate).date())),
|
||||
base::unixtime::parse(untilDate).date())),
|
||||
Ui::Text::RichLangValue));
|
||||
}
|
||||
}
|
||||
@@ -902,14 +1511,15 @@ void ResolveGiveawayInfo(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<PeerData*> peer,
|
||||
MsgId messageId,
|
||||
Data::Giveaway giveaway) {
|
||||
std::optional<Data::GiveawayStart> start,
|
||||
std::optional<Data::GiveawayResults> results) {
|
||||
const auto show = [=](Api::GiveawayInfo info) {
|
||||
if (!info) {
|
||||
controller->showToast(
|
||||
tr::lng_confirm_phone_link_invalid(tr::now));
|
||||
} else {
|
||||
controller->uiShow()->showBox(
|
||||
Box(GiveawayInfoBox, controller, giveaway, info));
|
||||
Box(GiveawayInfoBox, controller, start, results, info));
|
||||
}
|
||||
};
|
||||
controller->session().api().premium().resolveGiveawayInfo(
|
||||
|
||||
@@ -16,7 +16,8 @@ struct GiftCode;
|
||||
} // namespace Api
|
||||
|
||||
namespace Data {
|
||||
struct Giveaway;
|
||||
struct GiveawayStart;
|
||||
struct GiveawayResults;
|
||||
} // namespace Data
|
||||
|
||||
namespace Ui {
|
||||
@@ -33,6 +34,7 @@ public:
|
||||
GiftPremiumValidator(not_null<Window::SessionController*> controller);
|
||||
|
||||
void showBox(not_null<UserData*> user);
|
||||
void showChoosePeerBox(const QString &ref);
|
||||
void cancel();
|
||||
|
||||
private:
|
||||
@@ -41,6 +43,8 @@ private:
|
||||
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
rpl::lifetime _manyGiftsLifetime;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] rpl::producer<QString> GiftDurationValue(int months);
|
||||
@@ -56,10 +60,13 @@ void GiftCodePendingBox(
|
||||
const Api::GiftCode &data);
|
||||
void ResolveGiftCode(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
const QString &slug);
|
||||
const QString &slug,
|
||||
PeerId fromId = 0,
|
||||
PeerId toId = 0);
|
||||
|
||||
void ResolveGiveawayInfo(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<PeerData*> peer,
|
||||
MsgId messageId,
|
||||
Data::Giveaway giveaway);
|
||||
std::optional<Data::GiveawayStart> start,
|
||||
std::optional<Data::GiveawayResults> results);
|
||||
|
||||
@@ -40,11 +40,14 @@ public:
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<UserData*> bot,
|
||||
RequestPeerQuery query,
|
||||
Fn<void(not_null<PeerData*>)> callback);
|
||||
Fn<void(std::vector<not_null<PeerData*>>)> callback);
|
||||
|
||||
Main::Session &session() const override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
|
||||
[[nodiscard]] rpl::producer<int> selectedCountValue() const;
|
||||
void submit();
|
||||
|
||||
QString savedMessagesChatStatus() const override {
|
||||
return tr::lng_saved_forward_here(tr::now);
|
||||
}
|
||||
@@ -60,7 +63,9 @@ private:
|
||||
not_null<UserData*> _bot;
|
||||
RequestPeerQuery _query;
|
||||
base::flat_set<not_null<PeerData*>> _commonGroups;
|
||||
Fn<void(not_null<PeerData*>)> _callback;
|
||||
base::flat_set<not_null<PeerData*>> _selected;
|
||||
rpl::variable<int> _selectedCount;
|
||||
Fn<void(std::vector<not_null<PeerData*>>)> _callback;
|
||||
|
||||
};
|
||||
|
||||
@@ -253,10 +258,10 @@ object_ptr<Ui::BoxContent> CreatePeerByQueryBox(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<UserData*> bot,
|
||||
RequestPeerQuery query,
|
||||
Fn<void(not_null<PeerData*>)> done) {
|
||||
Fn<void(std::vector<not_null<PeerData*>>)> done) {
|
||||
const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
|
||||
auto callback = [=](not_null<PeerData*> peer) {
|
||||
done(peer);
|
||||
done({ peer });
|
||||
if (const auto strong = weak->data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
@@ -332,7 +337,7 @@ ChoosePeerBoxController::ChoosePeerBoxController(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<UserData*> bot,
|
||||
RequestPeerQuery query,
|
||||
Fn<void(not_null<PeerData*>)> callback)
|
||||
Fn<void(std::vector<not_null<PeerData*>>)> callback)
|
||||
: ChatsListBoxController(&navigation->session())
|
||||
, _navigation(navigation)
|
||||
, _bot(bot)
|
||||
@@ -415,6 +420,8 @@ void ChoosePeerBoxController::prepareViewHook() {
|
||||
switch (_query.type) {
|
||||
case Type::User: return (_query.userIsBot == Restriction::Yes)
|
||||
? tr::lng_request_bot_title()
|
||||
: (_query.maxQuantity > 1)
|
||||
? tr::lng_request_users_title()
|
||||
: tr::lng_request_user_title();
|
||||
case Type::Group: return tr::lng_request_group_title();
|
||||
case Type::Broadcast: return tr::lng_request_channel_title();
|
||||
@@ -425,10 +432,24 @@ void ChoosePeerBoxController::prepareViewHook() {
|
||||
}
|
||||
|
||||
void ChoosePeerBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||
const auto limit = _query.maxQuantity;
|
||||
const auto multiselect = (limit > 1);
|
||||
const auto peer = row->peer();
|
||||
if (multiselect) {
|
||||
if (_selected.contains(peer) || _selected.size() < limit) {
|
||||
delegate()->peerListSetRowChecked(row, !row->checked());
|
||||
if (row->checked()) {
|
||||
_selected.emplace(peer);
|
||||
} else {
|
||||
_selected.remove(peer);
|
||||
}
|
||||
_selectedCount = int(_selected.size());
|
||||
}
|
||||
return;
|
||||
}
|
||||
const auto done = [callback = _callback, peer] {
|
||||
const auto onstack = callback;
|
||||
onstack(peer);
|
||||
onstack({ peer });
|
||||
};
|
||||
if (const auto user = peer->asUser()) {
|
||||
done();
|
||||
@@ -438,6 +459,15 @@ void ChoosePeerBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<int> ChoosePeerBoxController::selectedCountValue() const {
|
||||
return _selectedCount.value();
|
||||
}
|
||||
|
||||
void ChoosePeerBoxController::submit() {
|
||||
const auto onstack = _callback;
|
||||
onstack(ranges::to_vector(_selected));
|
||||
}
|
||||
|
||||
auto ChoosePeerBoxController::createRow(not_null<History*> history)
|
||||
-> std::unique_ptr<Row> {
|
||||
return FilterPeerByQuery(history->peer, _query, _commonGroups)
|
||||
@@ -474,7 +504,7 @@ void ShowChoosePeerBox(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<UserData*> bot,
|
||||
RequestPeerQuery query,
|
||||
Fn<void(not_null<PeerData*>)> chosen) {
|
||||
Fn<void(std::vector<not_null<PeerData*>>)> chosen) {
|
||||
const auto needCommonGroups = query.isBotParticipant
|
||||
&& (query.type == RequestPeerQuery::Type::Group)
|
||||
&& !query.myRights;
|
||||
@@ -488,22 +518,39 @@ void ShowChoosePeerBox(
|
||||
return;
|
||||
}
|
||||
const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
|
||||
auto initBox = [=](not_null<PeerListBox*> box) {
|
||||
box->addButton(tr::lng_cancel(), [box] {
|
||||
box->closeBox();
|
||||
});
|
||||
};
|
||||
auto callback = [=, done = std::move(chosen)](not_null<PeerData*> peer) {
|
||||
done(peer);
|
||||
auto callback = [=, done = std::move(chosen)](
|
||||
std::vector<not_null<PeerData*>> peers) {
|
||||
done(std::move(peers));
|
||||
if (const auto strong = weak->data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
};
|
||||
const auto limit = query.maxQuantity;
|
||||
auto controller = std::make_unique<ChoosePeerBoxController>(
|
||||
navigation,
|
||||
bot,
|
||||
query,
|
||||
std::move(callback));
|
||||
auto initBox = [=, ptr = controller.get()](not_null<PeerListBox*> box) {
|
||||
ptr->selectedCountValue() | rpl::start_with_next([=](int count) {
|
||||
box->clearButtons();
|
||||
if (limit > 1) {
|
||||
box->setAdditionalTitle(rpl::single(u"%1 / %2"_q.arg(count).arg(limit)));
|
||||
}
|
||||
if (count > 0) {
|
||||
box->addButton(tr::lng_intro_submit(), [=] {
|
||||
ptr->submit();
|
||||
if (*weak) {
|
||||
(*weak)->closeBox();
|
||||
}
|
||||
});
|
||||
}
|
||||
box->addButton(tr::lng_cancel(), [box] {
|
||||
box->closeBox();
|
||||
});
|
||||
}, box->lifetime());
|
||||
};
|
||||
*weak = navigation->parentController()->show(Box<PeerListBox>(
|
||||
std::make_unique<ChoosePeerBoxController>(
|
||||
navigation,
|
||||
bot,
|
||||
query,
|
||||
std::move(callback)),
|
||||
std::move(controller),
|
||||
std::move(initBox)));
|
||||
}
|
||||
|
||||
@@ -21,4 +21,4 @@ void ShowChoosePeerBox(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<UserData*> bot,
|
||||
RequestPeerQuery query,
|
||||
Fn<void(not_null<PeerData*>)> chosen);
|
||||
Fn<void(std::vector<not_null<PeerData*>>)> chosen);
|
||||
|
||||
@@ -9,12 +9,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_peer_colors.h"
|
||||
#include "api/api_peer_photo.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/peers/replace_boost_box.h"
|
||||
#include "boxes/background_box.h"
|
||||
#include "chat_helpers/compose/compose_show.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "data/data_emoji_statuses.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_web_page.h"
|
||||
@@ -428,11 +431,17 @@ HistoryView::Context PreviewDelegate::elementContext() {
|
||||
return HistoryView::Context::AdminLog;
|
||||
}
|
||||
|
||||
struct SetValues {
|
||||
uint8 colorIndex = 0;
|
||||
DocumentId backgroundEmojiId = 0;
|
||||
DocumentId statusId = 0;
|
||||
TimeId statusUntil = 0;
|
||||
bool statusChanged = false;
|
||||
};
|
||||
void Set(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
uint8 colorIndex,
|
||||
DocumentId backgroundEmojiId) {
|
||||
SetValues values) {
|
||||
const auto wasIndex = peer->colorIndex();
|
||||
const auto wasEmojiId = peer->backgroundEmojiId();
|
||||
|
||||
@@ -444,7 +453,7 @@ void Set(
|
||||
peer,
|
||||
UpdateFlag::Color | UpdateFlag::BackgroundEmoji);
|
||||
};
|
||||
setLocal(colorIndex, backgroundEmojiId);
|
||||
setLocal(values.colorIndex, values.backgroundEmojiId);
|
||||
|
||||
const auto done = [=] {
|
||||
show->showToast(peer->isSelf()
|
||||
@@ -452,8 +461,11 @@ void Set(
|
||||
: tr::lng_settings_color_changed_channel(tr::now));
|
||||
};
|
||||
const auto fail = [=](const MTP::Error &error) {
|
||||
setLocal(wasIndex, wasEmojiId);
|
||||
show->showToast(error.type());
|
||||
const auto type = error.type();
|
||||
if (type != u"CHAT_NOT_MODIFIED"_q) {
|
||||
setLocal(wasIndex, wasEmojiId);
|
||||
show->showToast(type);
|
||||
}
|
||||
};
|
||||
const auto send = [&](auto &&request) {
|
||||
peer->session().api().request(
|
||||
@@ -464,15 +476,23 @@ void Set(
|
||||
using Flag = MTPaccount_UpdateColor::Flag;
|
||||
send(MTPaccount_UpdateColor(
|
||||
MTP_flags(Flag::f_color | Flag::f_background_emoji_id),
|
||||
MTP_int(colorIndex),
|
||||
MTP_long(backgroundEmojiId)));
|
||||
MTP_int(values.colorIndex),
|
||||
MTP_long(values.backgroundEmojiId)));
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
using Flag = MTPchannels_UpdateColor::Flag;
|
||||
send(MTPchannels_UpdateColor(
|
||||
MTP_flags(Flag::f_background_emoji_id),
|
||||
MTP_flags(Flag::f_color | Flag::f_background_emoji_id),
|
||||
channel->inputChannel,
|
||||
MTP_int(colorIndex),
|
||||
MTP_long(backgroundEmojiId)));
|
||||
MTP_int(values.colorIndex),
|
||||
MTP_long(values.backgroundEmojiId)));
|
||||
|
||||
if (values.statusChanged
|
||||
&& (values.statusId || peer->emojiStatusId())) {
|
||||
peer->owner().emojiStatuses().set(
|
||||
channel,
|
||||
values.statusId,
|
||||
values.statusUntil);
|
||||
}
|
||||
} else {
|
||||
Unexpected("Invalid peer type in Set(colorIndex).");
|
||||
}
|
||||
@@ -481,13 +501,13 @@ void Set(
|
||||
void Apply(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
uint8 colorIndex,
|
||||
DocumentId backgroundEmojiId,
|
||||
SetValues values,
|
||||
Fn<void()> close,
|
||||
Fn<void()> cancel) {
|
||||
const auto session = &peer->session();
|
||||
if (peer->colorIndex() == colorIndex
|
||||
&& peer->backgroundEmojiId() == backgroundEmojiId) {
|
||||
if (peer->colorIndex() == values.colorIndex
|
||||
&& peer->backgroundEmojiId() == values.backgroundEmojiId
|
||||
&& !values.statusChanged) {
|
||||
close();
|
||||
} else if (peer->isSelf() && !session->premium()) {
|
||||
Settings::ShowPremiumPromoToast(
|
||||
@@ -502,39 +522,45 @@ void Apply(
|
||||
u"name_color"_q);
|
||||
cancel();
|
||||
} else if (peer->isSelf()) {
|
||||
Set(show, peer, colorIndex, backgroundEmojiId);
|
||||
Set(show, peer, values);
|
||||
close();
|
||||
} else {
|
||||
session->api().request(MTPpremium_GetBoostsStatus(
|
||||
peer->input
|
||||
)).done([=](const MTPpremium_BoostsStatus &result) {
|
||||
const auto &data = result.data();
|
||||
const auto required = session->account().appConfig().get<int>(
|
||||
"channel_color_level_min",
|
||||
5);
|
||||
if (data.vlevel().v >= required) {
|
||||
Set(show, peer, colorIndex, backgroundEmojiId);
|
||||
CheckBoostLevel(show, peer, [=](int level) {
|
||||
const auto peerColors = &peer->session().api().peerColors();
|
||||
const auto colorRequired = peerColors->requiredLevelFor(
|
||||
peer->id,
|
||||
values.colorIndex);
|
||||
const auto iconRequired = values.backgroundEmojiId
|
||||
? session->account().appConfig().get<int>(
|
||||
"channel_bg_icon_level_min",
|
||||
5)
|
||||
: 0;
|
||||
const auto statusRequired = (values.statusChanged
|
||||
&& values.statusId)
|
||||
? session->account().appConfig().get<int>(
|
||||
"channel_emoji_status_level_min",
|
||||
8)
|
||||
: 0;
|
||||
const auto required = std::max({
|
||||
colorRequired,
|
||||
iconRequired,
|
||||
statusRequired,
|
||||
});
|
||||
if (level >= required) {
|
||||
Set(show, peer, values);
|
||||
close();
|
||||
return;
|
||||
return std::optional<Ui::AskBoostReason>();
|
||||
}
|
||||
const auto openStatistics = [=] {
|
||||
if (const auto controller = show->resolveWindow(
|
||||
ChatHelpers::WindowUsage::PremiumPromo)) {
|
||||
controller->showSection(Info::Boosts::Make(peer));
|
||||
const auto reason = [&]() -> Ui::AskBoostReason {
|
||||
if (level < statusRequired) {
|
||||
return { Ui::AskBoostEmojiStatus{ statusRequired } };
|
||||
} else if (level < iconRequired) {
|
||||
return { Ui::AskBoostChannelColor{ iconRequired } };
|
||||
}
|
||||
};
|
||||
auto counters = ParseBoostCounters(result);
|
||||
counters.mine = 0; // Don't show current level as just-reached.
|
||||
show->show(Box(Ui::AskBoostBox, Ui::AskBoostBoxData{
|
||||
.link = qs(data.vboost_url()),
|
||||
.boost = counters,
|
||||
.reason = { Ui::AskBoostChannelColor{ required } },
|
||||
}, openStatistics, nullptr));
|
||||
cancel();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
show->showToast(error.type());
|
||||
cancel();
|
||||
}).send();
|
||||
return { Ui::AskBoostChannelColor{ colorRequired } };
|
||||
}();
|
||||
return std::make_optional(reason);
|
||||
}, cancel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -672,15 +698,18 @@ int ColorSelector::resizeGetHeight(int newWidth) {
|
||||
const auto right = Ui::CreateChild<Ui::RpWidget>(raw);
|
||||
right->show();
|
||||
|
||||
using namespace Info::Profile;
|
||||
struct State {
|
||||
Info::Profile::EmojiStatusPanel panel;
|
||||
EmojiStatusPanel panel;
|
||||
std::unique_ptr<Ui::Text::CustomEmoji> emoji;
|
||||
DocumentId emojiId = 0;
|
||||
uint8 index = 0;
|
||||
};
|
||||
const auto state = right->lifetime().make_state<State>();
|
||||
state->panel.backgroundEmojiChosen(
|
||||
) | rpl::start_with_next(emojiIdChosen, raw->lifetime());
|
||||
state->panel.someCustomChosen(
|
||||
) | rpl::start_with_next([=](EmojiStatusPanel::CustomChosen chosen) {
|
||||
emojiIdChosen(chosen.id);
|
||||
}, raw->lifetime());
|
||||
|
||||
std::move(colorIndexValue) | rpl::start_with_next([=](uint8 index) {
|
||||
state->index = index;
|
||||
@@ -748,7 +777,7 @@ int ColorSelector::resizeGetHeight(int newWidth) {
|
||||
state->panel.show({
|
||||
.controller = controller,
|
||||
.button = right,
|
||||
.currentBackgroundEmojiId = state->emojiId,
|
||||
.ensureAddedEmojiId = state->emojiId,
|
||||
.customTextColor = customTextColor,
|
||||
.backgroundEmojiMode = true,
|
||||
});
|
||||
@@ -758,6 +787,108 @@ int ColorSelector::resizeGetHeight(int newWidth) {
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::SettingsButton> CreateEmojiStatusButton(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
rpl::producer<DocumentId> statusIdValue,
|
||||
Fn<void(DocumentId,TimeId)> statusIdChosen) {
|
||||
const auto &basicSt = st::settingsButtonNoIcon;
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
const auto added = st::normalFont->spacew;
|
||||
const auto emojiSize = Data::FrameSizeFromTag({}) / ratio;
|
||||
const auto noneWidth = added
|
||||
+ st::normalFont->width(tr::lng_settings_color_emoji_off(tr::now));
|
||||
const auto emojiWidth = added + emojiSize;
|
||||
const auto rightPadding = std::max(noneWidth, emojiWidth)
|
||||
+ basicSt.padding.right();
|
||||
const auto st = parent->lifetime().make_state<style::SettingsButton>(
|
||||
basicSt);
|
||||
st->padding.setRight(rightPadding);
|
||||
auto result = object_ptr<Ui::SettingsButton>(
|
||||
parent,
|
||||
tr::lng_edit_channel_status(),
|
||||
*st);
|
||||
const auto raw = result.data();
|
||||
|
||||
const auto right = Ui::CreateChild<Ui::RpWidget>(raw);
|
||||
right->show();
|
||||
|
||||
using namespace Info::Profile;
|
||||
struct State {
|
||||
EmojiStatusPanel panel;
|
||||
std::unique_ptr<Ui::Text::CustomEmoji> emoji;
|
||||
DocumentId statusId = 0;
|
||||
};
|
||||
const auto state = right->lifetime().make_state<State>();
|
||||
state->panel.someCustomChosen(
|
||||
) | rpl::start_with_next([=](EmojiStatusPanel::CustomChosen chosen) {
|
||||
statusIdChosen(chosen.id, chosen.until);
|
||||
}, raw->lifetime());
|
||||
|
||||
const auto session = &show->session();
|
||||
std::move(statusIdValue) | rpl::start_with_next([=](DocumentId id) {
|
||||
state->statusId = id;
|
||||
state->emoji = id
|
||||
? session->data().customEmojiManager().create(
|
||||
id,
|
||||
[=] { right->update(); })
|
||||
: nullptr;
|
||||
right->resize(
|
||||
(id ? emojiWidth : noneWidth) + added,
|
||||
right->height());
|
||||
right->update();
|
||||
}, right->lifetime());
|
||||
|
||||
rpl::combine(
|
||||
raw->sizeValue(),
|
||||
right->widthValue()
|
||||
) | rpl::start_with_next([=](QSize outer, int width) {
|
||||
right->resize(width, outer.height());
|
||||
const auto skip = st::settingsButton.padding.right();
|
||||
right->moveToRight(skip - added, 0, outer.width());
|
||||
}, right->lifetime());
|
||||
|
||||
right->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (state->panel.paintBadgeFrame(right)) {
|
||||
return;
|
||||
}
|
||||
auto p = QPainter(right);
|
||||
const auto height = right->height();
|
||||
if (state->emoji) {
|
||||
state->emoji->paint(p, {
|
||||
.textColor = anim::color(
|
||||
st::stickerPanPremium1,
|
||||
st::stickerPanPremium2,
|
||||
0.5),
|
||||
.position = QPoint(added, (height - emojiSize) / 2),
|
||||
});
|
||||
} else {
|
||||
const auto &font = st::normalFont;
|
||||
p.setFont(font);
|
||||
p.setPen(st::windowActiveTextFg);
|
||||
p.drawText(
|
||||
QPoint(added, (height - font->height) / 2 + font->ascent),
|
||||
tr::lng_settings_color_emoji_off(tr::now));
|
||||
}
|
||||
}, right->lifetime());
|
||||
|
||||
raw->setClickedCallback([=] {
|
||||
const auto controller = show->resolveWindow(
|
||||
ChatHelpers::WindowUsage::PremiumPromo);
|
||||
if (controller) {
|
||||
state->panel.show({
|
||||
.controller = controller,
|
||||
.button = right,
|
||||
.ensureAddedEmojiId = state->statusId,
|
||||
.channelStatusMode = true,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void EditPeerColorBox(
|
||||
@@ -772,12 +903,16 @@ void EditPeerColorBox(
|
||||
struct State {
|
||||
rpl::variable<uint8> index;
|
||||
rpl::variable<DocumentId> emojiId;
|
||||
rpl::variable<DocumentId> statusId;
|
||||
TimeId statusUntil = 0;
|
||||
bool statusChanged = false;
|
||||
bool changing = false;
|
||||
bool applying = false;
|
||||
};
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
state->index = peer->colorIndex();
|
||||
state->emojiId = peer->backgroundEmojiId();
|
||||
state->statusId = peer->emojiStatusId();
|
||||
|
||||
box->addRow(object_ptr<PreviewWrap>(
|
||||
box,
|
||||
@@ -820,14 +955,61 @@ void EditPeerColorBox(
|
||||
? tr::lng_settings_color_emoji_about()
|
||||
: tr::lng_settings_color_emoji_about_channel());
|
||||
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
Ui::AddSkip(container, st::settingsColorSampleSkip);
|
||||
container->add(object_ptr<Ui::SettingsButton>(
|
||||
container,
|
||||
tr::lng_edit_channel_wallpaper(),
|
||||
st::settingsButtonNoIcon)
|
||||
)->setClickedCallback([=] {
|
||||
const auto usage = ChatHelpers::WindowUsage::PremiumPromo;
|
||||
if (const auto strong = show->resolveWindow(usage)) {
|
||||
show->show(Box<BackgroundBox>(strong, channel));
|
||||
}
|
||||
});
|
||||
|
||||
Ui::AddSkip(container, st::settingsColorSampleSkip);
|
||||
Ui::AddDividerText(
|
||||
container,
|
||||
tr::lng_edit_channel_wallpaper_about());
|
||||
|
||||
// Preload exceptions list.
|
||||
const auto peerPhoto = &channel->session().api().peerPhoto();
|
||||
[[maybe_unused]] auto list = peerPhoto->emojiListValue(
|
||||
Api::PeerPhoto::EmojiListType::NoChannelStatus
|
||||
);
|
||||
|
||||
const auto statuses = &channel->owner().emojiStatuses();
|
||||
statuses->refreshChannelDefault();
|
||||
statuses->refreshChannelColored();
|
||||
|
||||
Ui::AddSkip(container, st::settingsColorSampleSkip);
|
||||
container->add(CreateEmojiStatusButton(
|
||||
container,
|
||||
show,
|
||||
state->statusId.value(),
|
||||
[=](DocumentId id, TimeId until) {
|
||||
state->statusId = id;
|
||||
state->statusUntil = until;
|
||||
state->statusChanged = true;
|
||||
}));
|
||||
|
||||
Ui::AddSkip(container, st::settingsColorSampleSkip);
|
||||
Ui::AddDividerText(container, tr::lng_edit_channel_status_about());
|
||||
}
|
||||
|
||||
box->addButton(tr::lng_settings_apply(), [=] {
|
||||
if (state->applying) {
|
||||
return;
|
||||
}
|
||||
state->applying = true;
|
||||
const auto index = state->index.current();
|
||||
const auto emojiId = state->emojiId.current();
|
||||
Apply(show, peer, index, emojiId, crl::guard(box, [=] {
|
||||
Apply(show, peer, {
|
||||
state->index.current(),
|
||||
state->emojiId.current(),
|
||||
state->statusId.current(),
|
||||
state->statusUntil,
|
||||
state->statusChanged,
|
||||
}, crl::guard(box, [=] {
|
||||
box->closeBox();
|
||||
}), crl::guard(box, [=] {
|
||||
state->applying = false;
|
||||
@@ -842,11 +1024,12 @@ void AddPeerColorButton(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> peer) {
|
||||
auto label = peer->isSelf()
|
||||
? tr::lng_settings_theme_name_color()
|
||||
: tr::lng_edit_channel_color();
|
||||
const auto button = AddButtonWithIcon(
|
||||
container,
|
||||
(peer->isSelf()
|
||||
? tr::lng_settings_theme_name_color()
|
||||
: tr::lng_edit_channel_color()),
|
||||
rpl::duplicate(label),
|
||||
st::settingsColorButton,
|
||||
{ &st::menuIconChangeColors });
|
||||
|
||||
@@ -873,7 +1056,7 @@ void AddPeerColorButton(
|
||||
|
||||
rpl::combine(
|
||||
button->widthValue(),
|
||||
tr::lng_settings_theme_name_color(),
|
||||
rpl::duplicate(label),
|
||||
rpl::duplicate(colorIndexValue)
|
||||
) | rpl::start_with_next([=](
|
||||
int width,
|
||||
@@ -920,3 +1103,39 @@ void AddPeerColorButton(
|
||||
show->show(Box(EditPeerColorBox, show, peer, style, theme));
|
||||
});
|
||||
}
|
||||
|
||||
void CheckBoostLevel(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
Fn<std::optional<Ui::AskBoostReason>(int level)> askMore,
|
||||
Fn<void()> cancel) {
|
||||
peer->session().api().request(MTPpremium_GetBoostsStatus(
|
||||
peer->input
|
||||
)).done([=](const MTPpremium_BoostsStatus &result) {
|
||||
const auto &data = result.data();
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
channel->updateLevelHint(data.vlevel().v);
|
||||
}
|
||||
const auto reason = askMore(data.vlevel().v);
|
||||
if (!reason) {
|
||||
return;
|
||||
}
|
||||
const auto openStatistics = [=] {
|
||||
if (const auto controller = show->resolveWindow(
|
||||
ChatHelpers::WindowUsage::PremiumPromo)) {
|
||||
controller->showSection(Info::Boosts::Make(peer));
|
||||
}
|
||||
};
|
||||
auto counters = ParseBoostCounters(result);
|
||||
counters.mine = 0; // Don't show current level as just-reached.
|
||||
show->show(Box(Ui::AskBoostBox, Ui::AskBoostBoxData{
|
||||
.link = qs(data.vboost_url()),
|
||||
.boost = counters,
|
||||
.reason = *reason,
|
||||
}, openStatistics, nullptr));
|
||||
cancel();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
show->showToast(error.type());
|
||||
cancel();
|
||||
}).send();
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ class GenericBox;
|
||||
class ChatStyle;
|
||||
class ChatTheme;
|
||||
class VerticalLayout;
|
||||
struct AskBoostReason;
|
||||
} // namespace Ui
|
||||
|
||||
void EditPeerColorBox(
|
||||
@@ -29,3 +30,9 @@ void AddPeerColorButton(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> peer);
|
||||
|
||||
void CheckBoostLevel(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
Fn<std::optional<Ui::AskBoostReason>(int level)> askMore,
|
||||
Fn<void()> cancel);
|
||||
|
||||
@@ -1294,6 +1294,9 @@ void Controller::editReactions() {
|
||||
_peer->input
|
||||
)).done([=](const MTPpremium_BoostsStatus &result) {
|
||||
_controls.levelRequested = false;
|
||||
if (const auto channel = _peer->asChannel()) {
|
||||
channel->updateLevelHint(result.data().vlevel().v);
|
||||
}
|
||||
const auto link = qs(result.data().vboost_url());
|
||||
const auto weak = base::make_weak(_navigation->parentController());
|
||||
auto counters = ParseBoostCounters(result);
|
||||
|
||||
@@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_forum.h"
|
||||
#include "data/data_saved_messages.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_folder.h"
|
||||
#include "data/data_premium_limits.h"
|
||||
@@ -67,6 +68,8 @@ public:
|
||||
void prepare() override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
|
||||
[[nodiscard]] rpl::producer<int> countValue() const;
|
||||
|
||||
private:
|
||||
void appendRow(not_null<PeerData*> peer, TimeId date);
|
||||
[[nodiscard]] std::unique_ptr<PeerListRow> createRow(
|
||||
@@ -74,6 +77,7 @@ private:
|
||||
TimeId date) const;
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
rpl::variable<int> _count;
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
};
|
||||
@@ -90,12 +94,15 @@ public:
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
void rowRightActionClicked(not_null<PeerListRow*> row) override;
|
||||
|
||||
[[nodiscard]] rpl::producer<int> countValue() const;
|
||||
|
||||
private:
|
||||
void appendRow(not_null<PeerData*> peer);
|
||||
[[nodiscard]] std::unique_ptr<PeerListRow> createRow(
|
||||
not_null<PeerData*> peer) const;
|
||||
|
||||
const not_null<Window::SessionNavigation*> _navigation;
|
||||
rpl::variable<int> _count;
|
||||
Fn<void()> _closeBox;
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
@@ -209,17 +216,17 @@ void InactiveController::prepare() {
|
||||
_requestId = _session->api().request(MTPchannels_GetInactiveChannels(
|
||||
)).done([=](const MTPmessages_InactiveChats &result) {
|
||||
_requestId = 0;
|
||||
result.match([&](const MTPDmessages_inactiveChats &data) {
|
||||
_session->data().processUsers(data.vusers());
|
||||
const auto &list = data.vchats().v;
|
||||
const auto &dates = data.vdates().v;
|
||||
for (auto i = 0, count = int(list.size()); i != count; ++i) {
|
||||
const auto peer = _session->data().processChat(list[i]);
|
||||
const auto date = (i < dates.size()) ? dates[i].v : TimeId();
|
||||
appendRow(peer, date);
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
});
|
||||
const auto &data = result.data();
|
||||
_session->data().processUsers(data.vusers());
|
||||
const auto &list = data.vchats().v;
|
||||
const auto &dates = data.vdates().v;
|
||||
for (auto i = 0, count = int(list.size()); i != count; ++i) {
|
||||
const auto peer = _session->data().processChat(list[i]);
|
||||
const auto date = (i < dates.size()) ? dates[i].v : TimeId();
|
||||
appendRow(peer, date);
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
_count = delegate()->peerListFullRowsCount();
|
||||
}).send();
|
||||
}
|
||||
|
||||
@@ -227,6 +234,10 @@ void InactiveController::rowClicked(not_null<PeerListRow*> row) {
|
||||
delegate()->peerListSetRowChecked(row, !row->checked());
|
||||
}
|
||||
|
||||
rpl::producer<int> InactiveController::countValue() const {
|
||||
return _count.value();
|
||||
}
|
||||
|
||||
void InactiveController::appendRow(
|
||||
not_null<PeerData*> participant,
|
||||
TimeId date) {
|
||||
@@ -295,6 +306,10 @@ Main::Session &PublicsController::session() const {
|
||||
return _navigation->session();
|
||||
}
|
||||
|
||||
rpl::producer<int> PublicsController::countValue() const {
|
||||
return _count.value();
|
||||
}
|
||||
|
||||
void PublicsController::prepare() {
|
||||
_requestId = _navigation->session().api().request(
|
||||
MTPchannels_GetAdminedPublicChannels(MTP_flags(0))
|
||||
@@ -314,6 +329,7 @@ void PublicsController::prepare() {
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
_count = delegate()->peerListFullRowsCount();
|
||||
}).send();
|
||||
}
|
||||
|
||||
@@ -570,7 +586,7 @@ void ChannelsLimitBox(
|
||||
{});
|
||||
|
||||
using namespace rpl::mappers;
|
||||
content->heightValue(
|
||||
controller->countValue(
|
||||
) | rpl::filter(_1 > 0) | rpl::start_with_next([=] {
|
||||
delete placeholder;
|
||||
}, placeholder->lifetime());
|
||||
@@ -661,7 +677,7 @@ void PublicLinksLimitBox(
|
||||
{});
|
||||
|
||||
using namespace rpl::mappers;
|
||||
content->heightValue(
|
||||
controller->countValue(
|
||||
) | rpl::filter(_1 > 0) | rpl::start_with_next([=] {
|
||||
delete placeholder;
|
||||
}, placeholder->lifetime());
|
||||
@@ -882,6 +898,18 @@ void PinsLimitBox(
|
||||
limits.dialogsPinnedPremium(),
|
||||
PinsCount(session->data().chatsList()));
|
||||
}
|
||||
void SublistsPinsLimitBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Main::Session*> session) {
|
||||
const auto limits = Data::PremiumLimits(session);
|
||||
SimplePinsLimitBox(
|
||||
box,
|
||||
session,
|
||||
"saved_dialog_pinned",
|
||||
limits.savedSublistsPinnedDefault(),
|
||||
limits.savedSublistsPinnedPremium(),
|
||||
PinsCount(session->data().savedMessages().chatsList()));
|
||||
}
|
||||
|
||||
void ForumPinsLimitBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
|
||||
@@ -60,6 +60,9 @@ void PinsLimitBox(
|
||||
void ForumPinsLimitBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Data::Forum*> forum);
|
||||
void SublistsPinsLimitBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Main::Session*> session);
|
||||
void CaptionLimitBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Main::Session*> session,
|
||||
|
||||
@@ -51,8 +51,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace {
|
||||
|
||||
constexpr auto kPremiumShift = 21. / 240;
|
||||
constexpr auto kReactionsPerRow = 5;
|
||||
constexpr auto kDisabledOpacity = 0.5;
|
||||
constexpr auto kToggleStickerTimeout = 2 * crl::time(1000);
|
||||
constexpr auto kStarOpacityOff = 0.1;
|
||||
constexpr auto kStarOpacityOn = 1.;
|
||||
@@ -66,6 +64,7 @@ struct Descriptor {
|
||||
bool fromSettings = false;
|
||||
Fn<void()> hiddenCallback;
|
||||
Fn<void(not_null<Ui::BoxContent*>)> shownCallback;
|
||||
bool hideSubscriptionButton = false;
|
||||
};
|
||||
|
||||
bool operator==(const Descriptor &a, const Descriptor &b) {
|
||||
@@ -1025,7 +1024,8 @@ void PreviewBox(
|
||||
state->preload();
|
||||
}
|
||||
};
|
||||
if (descriptor.fromSettings && show->session().premium()) {
|
||||
if ((descriptor.fromSettings && show->session().premium())
|
||||
|| descriptor.hideSubscriptionButton) {
|
||||
box->setShowFinishedCallback(showFinished);
|
||||
box->addButton(tr::lng_close(), [=] { box->closeBox(); });
|
||||
} else {
|
||||
@@ -1151,7 +1151,7 @@ void DecorateListPromoBox(
|
||||
box->boxClosing() | rpl::start_with_next(hidden, box->lifetime());
|
||||
}
|
||||
|
||||
if (session->premium()) {
|
||||
if (session->premium() || descriptor.hideSubscriptionButton) {
|
||||
box->addButton(tr::lng_close(), [=] {
|
||||
box->closeBox();
|
||||
});
|
||||
@@ -1286,10 +1286,12 @@ void ShowPremiumPreviewBox(
|
||||
void ShowPremiumPreviewBox(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
PremiumPreview section,
|
||||
Fn<void(not_null<Ui::BoxContent*>)> shown) {
|
||||
Fn<void(not_null<Ui::BoxContent*>)> shown,
|
||||
bool hideSubscriptionButton) {
|
||||
Show(std::move(show), Descriptor{
|
||||
.section = section,
|
||||
.shownCallback = std::move(shown),
|
||||
.hideSubscriptionButton = hideSubscriptionButton,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,8 @@ void ShowPremiumPreviewBox(
|
||||
void ShowPremiumPreviewBox(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
PremiumPreview section,
|
||||
Fn<void(not_null<Ui::BoxContent*>)> shown = nullptr);
|
||||
Fn<void(not_null<Ui::BoxContent*>)> shown = nullptr,
|
||||
bool hideSubscriptionButton = false);
|
||||
|
||||
void ShowPremiumPreviewToBuy(
|
||||
not_null<Window::SessionController*> controller,
|
||||
|
||||
@@ -211,7 +211,7 @@ void RenameBox(not_null<Ui::GenericBox*> box) {
|
||||
return Type::Other;
|
||||
} else if (const auto browser = detectBrowser()) {
|
||||
return *browser;
|
||||
} else if (device.contains("iphone")) {
|
||||
} else if (device.contains("iphone")) {
|
||||
return Type::iPhone;
|
||||
} else if (device.contains("ipad")) {
|
||||
return Type::iPad;
|
||||
@@ -221,9 +221,9 @@ void RenameBox(not_null<Ui::GenericBox*> box) {
|
||||
return *desktop;
|
||||
} else if (platform.contains("android") || system.contains("android")) {
|
||||
return Type::Android;
|
||||
} else if (platform.contains("ios") || system.contains("ios")) {
|
||||
} else if (platform.contains("ios") || system.contains("ios")) {
|
||||
return Type::iPhone;
|
||||
}
|
||||
}
|
||||
return Type::Other;
|
||||
}
|
||||
|
||||
|
||||
@@ -848,7 +848,7 @@ void ShareBox::Inner::loadProfilePhotos(int yFrom) {
|
||||
if (!_chatsIndexed->empty()) {
|
||||
const auto index = yFrom / _rowHeight;
|
||||
auto i = _chatsIndexed->begin()
|
||||
+ std::min(index, _chatsIndexed->size());;
|
||||
+ std::min(index, _chatsIndexed->size());
|
||||
for (auto end = _chatsIndexed->cend(); i != end; ++i) {
|
||||
if (((*i)->index() * _rowHeight) >= yTo) {
|
||||
break;
|
||||
|
||||
@@ -61,6 +61,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <QtGui/QClipboard>
|
||||
#include <QtSvg/QSvgRenderer>
|
||||
|
||||
// AyuGram includes
|
||||
#include "ayu/utils/telegram_helpers.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "data/data_user.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kStickersPerRow = 5;
|
||||
@@ -536,6 +542,26 @@ 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) {
|
||||
if (type == Data::StickersType::Stickers) {
|
||||
(*menu)->addAction(tr::ayu_MessageDetailsPackOwnerPC(tr::now), [=]
|
||||
{
|
||||
searchById(_inner->setId() >> 32, _session, [=](const QString &username, UserData *user)
|
||||
{
|
||||
if (!user) {
|
||||
showToast(tr::ayu_UserNotFoundMessage(tr::now));
|
||||
return;
|
||||
}
|
||||
|
||||
if (const auto window = _session->tryResolveWindow()) {
|
||||
if (const auto mainWidget = window->widget()->sessionController()) {
|
||||
mainWidget->showPeer(user);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, &st::menuIconProfile);
|
||||
}
|
||||
};
|
||||
if (_inner->notInstalled()) {
|
||||
if (!_session->premium()
|
||||
&& _session->premiumPossible()
|
||||
@@ -582,6 +608,7 @@ void StickerSetBox::updateButtons() {
|
||||
: tr::lng_stickers_share_pack)(tr::now),
|
||||
[=] { share(); closeBox(); },
|
||||
&st::menuIconShare);
|
||||
addAuthorPack(menu);
|
||||
(*menu)->popup(QCursor::pos());
|
||||
return true;
|
||||
});
|
||||
@@ -631,6 +658,7 @@ void StickerSetBox::updateButtons() {
|
||||
archive,
|
||||
&st::menuIconArchive);
|
||||
}
|
||||
addAuthorPack(menu);
|
||||
(*menu)->popup(QCursor::pos());
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -305,7 +305,7 @@ bool SkipTranslate(TextWithEntities textWithEntities) {
|
||||
const auto skip = Core::App().settings().skipTranslationLanguages();
|
||||
return result.known() && ranges::contains(skip, result);
|
||||
#else
|
||||
return false;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -520,6 +520,7 @@ void BoxController::loadMoreRows() {
|
||||
MTP_inputPeerEmpty(),
|
||||
MTP_string(), // q
|
||||
MTP_inputPeerEmpty(),
|
||||
MTPInputPeer(), // saved_peer_id
|
||||
MTPint(), // top_msg_id
|
||||
MTP_inputMessagesFilterPhoneCalls(MTP_flags(0)),
|
||||
MTP_int(0), // min_date
|
||||
|
||||
@@ -321,9 +321,6 @@ void Viewport::RendererGL::init(
|
||||
_frameBuffer->bind();
|
||||
_frameBuffer->allocate(kValues * sizeof(GLfloat));
|
||||
_downscaleProgram.yuv420.emplace();
|
||||
const auto downscaleVertexSource = VertexShader({
|
||||
VertexPassTextureCoord(),
|
||||
});
|
||||
_downscaleVertexShader = LinkProgram(
|
||||
&*_downscaleProgram.yuv420,
|
||||
VertexShader({
|
||||
|
||||
@@ -454,8 +454,10 @@ void ChooseSourceProcess::fillSources() {
|
||||
|
||||
auto screenIndex = 0;
|
||||
auto windowIndex = 0;
|
||||
auto firstScreenSelected = false;
|
||||
const auto active = _delegate->chooseSourceActiveDeviceId();
|
||||
const auto append = [&](const tgcalls::DesktopCaptureSource &source) {
|
||||
const auto firstScreen = !source.isWindow() && !screenIndex;
|
||||
const auto title = !source.isWindow()
|
||||
? tr::lng_group_call_screen_title(
|
||||
tr::now,
|
||||
@@ -471,6 +473,10 @@ void ChooseSourceProcess::fillSources() {
|
||||
if (!active.isEmpty() && active.toStdString() == id) {
|
||||
_selected = raw;
|
||||
raw->setActive(true);
|
||||
} else if (active.isEmpty() && firstScreen) {
|
||||
_selected = raw;
|
||||
raw->setActive(true);
|
||||
firstScreenSelected = true;
|
||||
}
|
||||
_sources.back()->activations(
|
||||
) | rpl::filter([=] {
|
||||
@@ -489,6 +495,9 @@ void ChooseSourceProcess::fillSources() {
|
||||
for (const auto &source : windowsManager.sources()) {
|
||||
append(source);
|
||||
}
|
||||
if (firstScreenSelected) {
|
||||
updateButtonsVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
void ChooseSourceProcess::updateButtonsVisibility() {
|
||||
|
||||