Compare commits
188 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6af28f6089 | |||
| 98b8840019 | |||
| 5f5d5e43be | |||
| 1fc63407e5 | |||
| dc6cd45f50 | |||
| 8a4dd965e1 | |||
| 627322f563 | |||
| df8c80226b | |||
| 53b342f722 | |||
| d2b6012bfd | |||
| 69efd127eb | |||
| 22cedbc9cc | |||
| d4d2eef291 | |||
| 98bd5757b0 | |||
| edb7cf197a | |||
| 617be49e55 | |||
| 9324ceeb24 | |||
| 1e6cf839e2 | |||
| 95f18f2120 | |||
| ae3dfacdf9 | |||
| 468d4c5e4f | |||
| c530d7cc89 | |||
| f7ea0e1d00 | |||
| aa0d733cde | |||
| b959877598 | |||
| 8d0fe601cf | |||
| a5b446f6df | |||
| 2afc171bec | |||
| d6bd88d119 | |||
| 639a82ce28 | |||
| ef1b197771 | |||
| ad05998575 | |||
| b403f600e7 | |||
| 9b2f2b104f | |||
| e4d920b148 | |||
| c5d1739e95 | |||
| a41bbd27c8 | |||
| 2d67557a91 | |||
| ca1a548fb8 | |||
| 97bd865916 | |||
| f7ab8298cf | |||
| 33cf9a0702 | |||
| 282c076931 | |||
| 135ffb13fd | |||
| 9d4e1689fa | |||
| 27e2a4b5d2 | |||
| c9cad4186b | |||
| 3522d9c62e | |||
| 43aa8825a5 | |||
| 4b28e910e8 | |||
| 4150cdff86 | |||
| 474fa56cc0 | |||
| 6ca777102c | |||
| 49d5cf939c | |||
| b56b9b1fb9 | |||
| 0293700e0f | |||
| 524a7a6a23 | |||
| b1459e41d3 | |||
| ef685aa0da | |||
| 3ac222db62 | |||
| 642b0ed032 | |||
| a7090c5fba | |||
| ebcd4eb97b | |||
| a76534b19f | |||
| ee33d139cd | |||
| c9cabfb084 | |||
| 3dd415d0c2 | |||
| 4592e4e4bb | |||
| 1cb762913c | |||
| 6eb7d68d49 | |||
| 36662c51dd | |||
| 4e915e9d1a | |||
| 86d6280776 | |||
| eea7caeb94 | |||
| b77e74de7f | |||
| 8a804fcfad | |||
| f7ad91e80c | |||
| 61a9d9c01d | |||
| e98f56b0b7 | |||
| b2eeab53c5 | |||
| d7539349c7 | |||
| 5350c97f82 | |||
| 20ba417ddd | |||
| 5b6f26af21 | |||
| ac1da83401 | |||
| 7b32e9af5e | |||
| 332ae22111 | |||
| b8c76cac61 | |||
| 150432e4d1 | |||
| d6c3760646 | |||
| 537c656ee1 | |||
| 4e0490494e | |||
| 56ad825693 | |||
| aab4ac8526 | |||
| 5c98406e1a | |||
| 3b40bc6297 | |||
| 12e164c4df | |||
| 855cc8e900 | |||
| 0a38cf2501 | |||
| 6def067e98 | |||
| a4e5ea01dc | |||
| f4cfbc5ed8 | |||
| 336705a503 | |||
| 764b0d3dcd | |||
| 72c8999e50 | |||
| 79b5f7eda9 | |||
| 6fcf80c6cf | |||
| e9a8acdc54 | |||
| 01573af0de | |||
| 69b24c494e | |||
| 2b4047b20d | |||
| 2b1a46356a | |||
| 36a91dd32b | |||
| d8e38b43d9 | |||
| d2d69a7a36 | |||
| 85fdc04d48 | |||
| 5e28b2d601 | |||
| 33f6fc7d8c | |||
| 3c8188e0b4 | |||
| 3ecf3f7c55 | |||
| fc6d4d66b7 | |||
| 2d097ca9ae | |||
| af03660cab | |||
| 986d347ea4 | |||
| 2dcd8a9640 | |||
| 5a55e850d9 | |||
| aa6edea33a | |||
| e67d2e5db2 | |||
| 67bbb477c7 | |||
| 5b0c48bb52 | |||
| e00016312e | |||
| 9df551a145 | |||
| 1852161fbf | |||
| 1851b6ff30 | |||
| 98f3fa76a2 | |||
| 6db4b8821e | |||
| 0f45abd534 | |||
| 4ad0804d55 | |||
| 9e10a80e00 | |||
| 01d9864036 | |||
| 8927a1b9a2 | |||
| b41c94be29 | |||
| 8ebf329cd9 | |||
| c3251aaf7b | |||
| d031046edb | |||
| 7808cc6d41 | |||
| a7b60c43b5 | |||
| a8b959826c | |||
| 42f96f3c43 | |||
| cc97093c5a | |||
| 7d5d086ade | |||
| 3da44eb5dd | |||
| 4955cdcdce | |||
| b806e714a6 | |||
| 597195a2e2 | |||
| 5966c74a27 | |||
| caa1ae4436 | |||
| 15ee46dab4 | |||
| acea390f3a | |||
| af5ad84e72 | |||
| 728ed02a1c | |||
| 8e369a4aa5 | |||
| 1f66e8ebeb | |||
| d0b0b02b62 | |||
| 35e457c924 | |||
| 097c3c4a5a | |||
| 10022a3c6d | |||
| 0dbb195106 | |||
| 6493cb9ed8 | |||
| 1cfda38345 | |||
| bf20dbe3bc | |||
| 46d3f232af | |||
| ade97fd2d2 | |||
| 39614aab3f | |||
| 6bab2b4df6 | |||
| b4c7272351 | |||
| d831775e2f | |||
| bde39970a0 | |||
| 076291b98f | |||
| 1341907cfd | |||
| b793c06759 | |||
| 475b2ac739 | |||
| d1c310de00 | |||
| 8615a25cd1 | |||
| de4eb1e59b | |||
| 8e8f6f905f | |||
| a57eecd420 | |||
| f306b11676 |
@@ -0,0 +1,158 @@
|
||||
name: MacOS Packaged.
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- '**.md'
|
||||
- 'changelog.txt'
|
||||
- 'LEGAL'
|
||||
- 'LICENSE'
|
||||
- '.github/**'
|
||||
- '!.github/workflows/mac_packaged.yml'
|
||||
- 'lib/xdg/**'
|
||||
- 'snap/**'
|
||||
- 'Telegram/build/**'
|
||||
- 'Telegram/Resources/uwp/**'
|
||||
- 'Telegram/Resources/winrc/**'
|
||||
- 'Telegram/SourceFiles/platform/win/**'
|
||||
- 'Telegram/SourceFiles/platform/linux/**'
|
||||
- 'Telegram/configure.bat'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- '**.md'
|
||||
- 'changelog.txt'
|
||||
- 'LEGAL'
|
||||
- 'LICENSE'
|
||||
- '.github/**'
|
||||
- '!.github/workflows/mac_packaged.yml'
|
||||
- 'lib/xdg/**'
|
||||
- 'snap/**'
|
||||
- 'Telegram/build/**'
|
||||
- 'Telegram/Resources/uwp/**'
|
||||
- 'Telegram/Resources/winrc/**'
|
||||
- 'Telegram/SourceFiles/platform/win/**'
|
||||
- 'Telegram/SourceFiles/platform/linux/**'
|
||||
- 'Telegram/configure.bat'
|
||||
|
||||
jobs:
|
||||
|
||||
macos:
|
||||
name: MacOS
|
||||
runs-on: macos-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
defines:
|
||||
- ""
|
||||
|
||||
env:
|
||||
GIT: "https://github.com"
|
||||
OPENALDIR: "/usr/local/opt/openal-soft"
|
||||
UPLOAD_ARTIFACT: "false"
|
||||
ONLY_CACHE: "false"
|
||||
MANUAL_CACHING: "1"
|
||||
AUTO_CACHING: "1"
|
||||
|
||||
steps:
|
||||
- name: Get repository name.
|
||||
run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
|
||||
|
||||
- name: Clone.
|
||||
uses: actions/checkout@v3.1.0
|
||||
with:
|
||||
submodules: recursive
|
||||
path: ${{ env.REPO_NAME }}
|
||||
|
||||
- name: First set up.
|
||||
run: |
|
||||
brew install autoconf automake boost cmake ffmpeg openal-soft openssl opus ninja pkg-config python qt yasm xz
|
||||
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
|
||||
|
||||
xcodebuild -version > CACHE_KEY.txt
|
||||
echo $MANUAL_CACHING >> CACHE_KEY.txt
|
||||
echo "$GITHUB_WORKSPACE" >> CACHE_KEY.txt
|
||||
if [ "$AUTO_CACHING" = "1" ]; then
|
||||
thisFile=$REPO_NAME/.github/workflows/mac_packaged.yml
|
||||
echo `md5 -q $thisFile` >> CACHE_KEY.txt
|
||||
fi
|
||||
echo "CACHE_KEY=`md5 -q CACHE_KEY.txt`" >> $GITHUB_ENV
|
||||
|
||||
echo "LibrariesPath=`pwd`" >> $GITHUB_ENV
|
||||
|
||||
curl -o tg_owt-version.json https://api.github.com/repos/desktop-app/tg_owt/git/refs/heads/master
|
||||
|
||||
- name: RNNoise.
|
||||
run: |
|
||||
cd $LibrariesPath
|
||||
|
||||
git clone --depth=1 https://gitlab.xiph.org/xiph/rnnoise.git
|
||||
cd rnnoise
|
||||
./autogen.sh
|
||||
./configure --disable-examples --disable-doc
|
||||
make -j$(sysctl -n hw.logicalcpu)
|
||||
make install
|
||||
|
||||
- name: WebRTC cache.
|
||||
id: cache-webrtc
|
||||
uses: actions/cache@v3.0.11
|
||||
with:
|
||||
path: ${{ env.LibrariesPath }}/tg_owt
|
||||
key: ${{ runner.OS }}-webrtc-${{ env.CACHE_KEY }}-${{ hashFiles('**/tg_owt-version.json') }}
|
||||
- name: WebRTC.
|
||||
if: steps.cache-webrtc.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cd $LibrariesPath
|
||||
|
||||
git clone --recursive --depth=1 $GIT/desktop-app/tg_owt.git
|
||||
cd tg_owt
|
||||
|
||||
cmake -B build . -GNinja -DCMAKE_BUILD_TYPE=Debug
|
||||
cmake --build build --parallel
|
||||
|
||||
- name: Telegram Desktop build.
|
||||
if: env.ONLY_CACHE == 'false'
|
||||
env:
|
||||
tg_owt_DIR: ${{ env.LibrariesPath }}/tg_owt/build
|
||||
run: |
|
||||
cd $REPO_NAME
|
||||
|
||||
DEFINE=""
|
||||
if [ -n "${{ matrix.defines }}" ]; then
|
||||
DEFINE="-D ${{ matrix.defines }}=ON"
|
||||
echo Define from matrix: $DEFINE
|
||||
echo "ARTIFACT_NAME=Telegram_${{ matrix.defines }}" >> $GITHUB_ENV
|
||||
else
|
||||
echo "ARTIFACT_NAME=Telegram" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
cmake -Bbuild -GNinja . \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DCMAKE_FIND_FRAMEWORK=LAST \
|
||||
-DTDESKTOP_API_TEST=ON \
|
||||
-DDESKTOP_APP_USE_PACKAGED_LAZY=ON \
|
||||
$DEFINE
|
||||
|
||||
cmake --build build --parallel
|
||||
|
||||
cd build
|
||||
macdeployqt Telegram.app
|
||||
codesign --remove-signature Telegram.app
|
||||
|
||||
mkdir dmgsrc
|
||||
mv Telegram.app dmgsrc
|
||||
hdiutil create -volname Telegram -srcfolder dmgsrc -ov -format UDZO Telegram.dmg
|
||||
|
||||
- name: Move artifact.
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
run: |
|
||||
cd $REPO_NAME/build
|
||||
mkdir artifact
|
||||
mv Telegram.dmg artifact/
|
||||
- uses: actions/upload-artifact@master
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
name: Upload artifact.
|
||||
with:
|
||||
name: ${{ env.ARTIFACT_NAME }}
|
||||
path: ${{ env.REPO_NAME }}/build/artifact/
|
||||
@@ -59,9 +59,9 @@ include(cmake/options.cmake)
|
||||
|
||||
if (NOT DESKTOP_APP_USE_PACKAGED)
|
||||
if (WIN32)
|
||||
set(qt_version 5.15.10)
|
||||
set(qt_version 5.15.11)
|
||||
elseif (APPLE)
|
||||
set(qt_version 6.2.5)
|
||||
set(qt_version 6.2.6)
|
||||
endif()
|
||||
endif()
|
||||
include(cmake/external/qt/package.cmake)
|
||||
|
||||
@@ -288,6 +288,8 @@ PRIVATE
|
||||
boxes/peers/peer_short_info_box.h
|
||||
boxes/peers/prepare_short_info_box.cpp
|
||||
boxes/peers/prepare_short_info_box.h
|
||||
boxes/peers/replace_boost_box.cpp
|
||||
boxes/peers/replace_boost_box.h
|
||||
boxes/about_box.cpp
|
||||
boxes/about_box.h
|
||||
boxes/about_sponsored_box.cpp
|
||||
@@ -837,6 +839,8 @@ PRIVATE
|
||||
history/view/history_view_quick_action.h
|
||||
history/view/history_view_replies_section.cpp
|
||||
history/view/history_view_replies_section.h
|
||||
history/view/history_view_reply.cpp
|
||||
history/view/history_view_reply.h
|
||||
history/view/history_view_requests_bar.cpp
|
||||
history/view/history_view_requests_bar.h
|
||||
history/view/history_view_schedule_box.cpp
|
||||
@@ -891,6 +895,10 @@ PRIVATE
|
||||
history/history_view_highlight_manager.h
|
||||
history/history_widget.cpp
|
||||
history/history_widget.h
|
||||
info/boosts/giveaway/giveaway_list_controllers.cpp
|
||||
info/boosts/giveaway/giveaway_list_controllers.h
|
||||
info/boosts/create_giveaway_box.cpp
|
||||
info/boosts/create_giveaway_box.h
|
||||
info/boosts/info_boosts_inner_widget.cpp
|
||||
info/boosts/info_boosts_inner_widget.h
|
||||
info/boosts/info_boosts_widget.cpp
|
||||
@@ -1609,7 +1617,39 @@ elseif (APPLE)
|
||||
endif()
|
||||
|
||||
set(icons_path ${CMAKE_CURRENT_SOURCE_DIR}/Telegram/Images.xcassets)
|
||||
target_add_resource(Telegram ${icons_path})
|
||||
if (CMAKE_GENERATOR STREQUAL Xcode)
|
||||
target_add_resource(Telegram ${icons_path})
|
||||
else()
|
||||
set(icon_path ${icons_path}/Icon.iconset)
|
||||
find_program(ICONUTIL iconutil)
|
||||
find_program(PNG2ICNS png2icns)
|
||||
if (ICONUTIL)
|
||||
add_custom_command(
|
||||
OUTPUT Icon.icns
|
||||
COMMAND ${ICONUTIL}
|
||||
ARGS
|
||||
--convert icns
|
||||
--output Icon.icns
|
||||
${icon_path}
|
||||
)
|
||||
elseif (PNG2ICNS)
|
||||
add_custom_command(
|
||||
OUTPUT Icon.icns
|
||||
COMMAND ${PNG2ICNS}
|
||||
ARGS
|
||||
Icon.icns
|
||||
${icon_path}/icon_16x16.png
|
||||
${icon_path}/icon_32x32.png
|
||||
${icon_path}/icon_128x128.png
|
||||
${icon_path}/icon_256x256.png
|
||||
${icon_path}/icon_512x512.png
|
||||
)
|
||||
endif()
|
||||
if (ICONUTIL OR PNG2ICNS)
|
||||
set_source_files_properties(Icon.icns PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
||||
target_add_resource(Telegram Icon.icns)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(lang_packs
|
||||
en
|
||||
@@ -1636,24 +1676,17 @@ elseif (APPLE)
|
||||
COMMAND cp ${CMAKE_BINARY_DIR}/lib_ui.rcc $<TARGET_FILE_DIR:Telegram>/../Resources
|
||||
COMMAND cp ${CMAKE_BINARY_DIR}/lib_spellcheck.rcc $<TARGET_FILE_DIR:Telegram>/../Resources
|
||||
)
|
||||
if (NOT build_macstore)
|
||||
if (NOT build_macstore AND NOT DESKTOP_APP_DISABLE_CRASH_REPORTS)
|
||||
if (DESKTOP_APP_MAC_ARCH STREQUAL "x86_64" OR DESKTOP_APP_MAC_ARCH STREQUAL "arm64")
|
||||
set(crashpad_dir_part ".${DESKTOP_APP_MAC_ARCH}")
|
||||
else()
|
||||
set(crashpad_dir_part "")
|
||||
endif()
|
||||
add_custom_command(TARGET Telegram
|
||||
PRE_LINK
|
||||
COMMAND mkdir -p $<TARGET_FILE_DIR:Telegram>/../Frameworks
|
||||
COMMAND cp $<TARGET_FILE:Updater> $<TARGET_FILE_DIR:Telegram>/../Frameworks/
|
||||
COMMAND mkdir -p $<TARGET_FILE_DIR:Telegram>/../Helpers
|
||||
COMMAND cp ${libs_loc}/crashpad/out/$<IF:$<CONFIG:Debug>,Debug,Release>${crashpad_dir_part}/crashpad_handler $<TARGET_FILE_DIR:Telegram>/../Helpers/
|
||||
)
|
||||
if (NOT DESKTOP_APP_DISABLE_CRASH_REPORTS)
|
||||
if (DESKTOP_APP_MAC_ARCH STREQUAL "x86_64" OR DESKTOP_APP_MAC_ARCH STREQUAL "arm64")
|
||||
set(crashpad_dir_part ".${DESKTOP_APP_MAC_ARCH}")
|
||||
else()
|
||||
set(crashpad_dir_part "")
|
||||
endif()
|
||||
add_custom_command(TARGET Telegram
|
||||
PRE_LINK
|
||||
COMMAND mkdir -p $<TARGET_FILE_DIR:Telegram>/../Helpers
|
||||
COMMAND cp ${libs_loc}/crashpad/out/$<IF:$<CONFIG:Debug>,Debug,Release>${crashpad_dir_part}/crashpad_handler $<TARGET_FILE_DIR:Telegram>/../Helpers/
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
target_link_libraries(Telegram
|
||||
@@ -1702,7 +1735,11 @@ if (build_macstore)
|
||||
COMMAND rm -rf $<TARGET_FILE_DIR:Telegram>/../Frameworks/Breakpad.framework/Resources/Inspector
|
||||
)
|
||||
else()
|
||||
set(bundle_identifier "com.tdesktop.Telegram$<$<CONFIG:Debug>:Debug>")
|
||||
if (CMAKE_GENERATOR STREQUAL Xcode)
|
||||
set(bundle_identifier "com.tdesktop.Telegram$<$<CONFIG:Debug>:Debug>")
|
||||
else()
|
||||
set(bundle_identifier "com.tdesktop.Telegram")
|
||||
endif()
|
||||
set(bundle_entitlements "Telegram.entitlements")
|
||||
if (LINUX AND DESKTOP_APP_USE_PACKAGED)
|
||||
set(output_name "ayugram-desktop")
|
||||
@@ -1711,6 +1748,12 @@ else()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (CMAKE_GENERATOR STREQUAL Xcode)
|
||||
set(bundle_identifier_plist "$(PRODUCT_BUNDLE_IDENTIFIER)")
|
||||
else()
|
||||
set(bundle_identifier_plist ${bundle_identifier})
|
||||
endif()
|
||||
|
||||
set_target_properties(Telegram PROPERTIES
|
||||
OUTPUT_NAME ${output_name}
|
||||
MACOSX_BUNDLE_GUI_IDENTIFIER ${bundle_identifier}
|
||||
@@ -1784,7 +1827,7 @@ endif()
|
||||
|
||||
target_prepare_qrc(Telegram)
|
||||
|
||||
if ((NOT DESKTOP_APP_DISABLE_AUTOUPDATE OR APPLE) AND NOT build_macstore AND NOT build_winstore)
|
||||
if (NOT DESKTOP_APP_DISABLE_AUTOUPDATE AND NOT build_macstore AND NOT build_winstore)
|
||||
add_executable(Updater WIN32)
|
||||
init_non_host_target(Updater)
|
||||
|
||||
@@ -1822,6 +1865,12 @@ if ((NOT DESKTOP_APP_DISABLE_AUTOUPDATE OR APPLE) AND NOT build_macstore AND NOT
|
||||
else()
|
||||
target_link_options(Updater PRIVATE -municode)
|
||||
endif()
|
||||
elseif (APPLE)
|
||||
add_custom_command(TARGET Updater
|
||||
PRE_LINK
|
||||
COMMAND mkdir -p $<TARGET_FILE_DIR:Telegram>/../Frameworks
|
||||
COMMAND cp $<TARGET_FILE:Updater> $<TARGET_FILE_DIR:Telegram>/../Frameworks/
|
||||
)
|
||||
endif()
|
||||
|
||||
if (DESKTOP_APP_SPECIAL_TARGET)
|
||||
@@ -1875,6 +1924,7 @@ 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")
|
||||
|
||||
|
After Width: | Height: | Size: 324 B |
|
After Width: | Height: | Size: 521 B |
|
After Width: | Height: | Size: 692 B |
|
After Width: | Height: | Size: 613 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 621 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 727 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 386 B |
|
After Width: | Height: | Size: 630 B |
|
After Width: | Height: | Size: 841 B |
|
After Width: | Height: | Size: 368 B |
|
After Width: | Height: | Size: 648 B |
|
After Width: | Height: | Size: 864 B |
|
Before Width: | Height: | Size: 402 B After Width: | Height: | Size: 759 B |
|
Before Width: | Height: | Size: 858 B After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 829 B |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 593 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 676 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 711 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 661 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 277 B |
|
After Width: | Height: | Size: 474 B |
|
After Width: | Height: | Size: 619 B |
|
After Width: | Height: | Size: 324 B |
|
After Width: | Height: | Size: 610 B |
|
After Width: | Height: | Size: 890 B |
|
After Width: | Height: | Size: 239 B |
|
After Width: | Height: | Size: 416 B |
|
After Width: | Height: | Size: 554 B |
|
Before Width: | Height: | Size: 280 B After Width: | Height: | Size: 141 B |
|
Before Width: | Height: | Size: 519 B After Width: | Height: | Size: 151 B |
|
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 182 B |
|
Before Width: | Height: | Size: 357 B After Width: | Height: | Size: 622 B |
|
Before Width: | Height: | Size: 603 B After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 822 B After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 383 B After Width: | Height: | Size: 599 B |
|
Before Width: | Height: | Size: 593 B After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 806 B After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="plane" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<path d="M1.3311718,6.36592184 C5.3576954,4.67244493 8.04267511,3.5560013 9.38611094,3.01659096 C13.2218932,1.47646481 14.0189359,1.2089284 14.5384372,1.2 C14.6526967,1.19815119 14.9081723,1.22548649 15.0736587,1.35511219 C15.2133922,1.4645656 15.2518384,1.61242159 15.2702362,1.71619544 C15.288634,1.81996929 15.3115436,2.05636876 15.2933322,2.24108442 C15.0854698,4.34939964 14.1860526,9.46572464 13.7284802,11.8270738 C13.5348641,12.8262491 13.1536281,13.1612675 12.7845475,13.1940535 C11.9824498,13.265305 11.3733733,12.6823476 10.5965026,12.190753 C9.3808532,11.4215044 8.69408865,10.9426448 7.51409044,10.1920004 C6.15039834,9.32450079 7.03442319,8.84770795 7.81158733,8.06849502 C8.01497489,7.86457129 11.5490353,4.7615061 11.6174372,4.48000946 C11.625992,4.44480359 11.6339313,4.31357282 11.5531696,4.24427815 C11.472408,4.17498349 11.3532107,4.19867957 11.2671947,4.21752527 C11.1452695,4.24423848 9.20325394,5.48334063 5.44114787,7.93483171 C4.88991321,8.30022994 4.39062196,8.47826423 3.94327414,8.46893456 C3.45010907,8.45864936 2.50145729,8.19975808 1.79623221,7.97846422 C0.931244952,7.70703829 0.243770289,7.56353344 0.303633888,7.10256824 C0.334814555,6.86246904 0.677327192,6.61692024 1.3311718,6.36592184 Z" id="Path-3" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -546,6 +546,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_workmode_tray" = "Show tray icon";
|
||||
"lng_settings_workmode_window" = "Show taskbar icon";
|
||||
"lng_settings_close_to_taskbar" = "Close to taskbar";
|
||||
"lng_settings_monochrome_icon" = "Use monochrome icon";
|
||||
"lng_settings_window_system" = "Window title";
|
||||
"lng_settings_title_chat_name" = "Show chat name";
|
||||
"lng_settings_title_account_name" = "Show active account";
|
||||
@@ -2029,6 +2030,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_premium_gift_terms_link" = "here";
|
||||
|
||||
"lng_boost_channel_button" = "Boost Channel";
|
||||
"lng_boost_again_button" = "Boost Again";
|
||||
"lng_boost_level#one" = "Level {count}";
|
||||
"lng_boost_level#other" = "Level {count}";
|
||||
"lng_boost_channel_title_first" = "Enable stories for channel";
|
||||
@@ -2050,6 +2052,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_boost_channel_post_stories#other" = "post **{count} stories** per day";
|
||||
"lng_boost_error_gifted_title" = "Can't boost with gifted Premium!";
|
||||
"lng_boost_error_gifted_text" = "Because your **Telegram Premium** subscription was gifted to you, you can't use it to boost channels.";
|
||||
"lng_boost_need_more" = "More boosts needed";
|
||||
"lng_boost_need_more_text#one" = "To boost {channel}, gift **Telegram Premium** to a friend and get **{count}** boosts.";
|
||||
"lng_boost_need_more_text#other" = "To boost {channel}, gift **Telegram Premium** to a friend and get **{count}** boosts.";
|
||||
"lng_boost_need_more_again#one" = "To boost {channel} again, gift **Telegram Premium** to a friend and get **{count}** additional boost.";
|
||||
"lng_boost_need_more_again#other" = "To boost {channel} again, gift **Telegram Premium** to a friend and get **{count}** additional boosts.";
|
||||
"lng_boost_error_already_title" = "Already Boosted!";
|
||||
"lng_boost_error_already_text" = "You are already boosting this channel.";
|
||||
"lng_boost_error_premium_title" = "Premium needed!";
|
||||
@@ -2059,6 +2066,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_boost_error_flood_text" = "You can change the channel you boost only once a day. Next time you can boost is in {left}.";
|
||||
"lng_boost_now_instead" = "You currently boost {channel}. Do you want to boost {other} instead?";
|
||||
"lng_boost_now_replace" = "Replace";
|
||||
"lng_boost_reassign_title" = "Reassign boost";
|
||||
"lng_boost_reassign_text" = "To boost {channel}, reassign a previous boost or {gift}.";
|
||||
"lng_boost_reassign_gift#one" = "gift **Telegram Premium** to a friend to get **{count}** additional boost";
|
||||
"lng_boost_reassign_gift#other" = "gift **Telegram Premium** to a friend to get **{count}** additional boosts";
|
||||
"lng_boost_remove_title" = "Remove your boost from";
|
||||
"lng_boost_reassign_button" = "Reassign";
|
||||
"lng_boost_available_in" = "available in {duration}";
|
||||
"lng_boost_available_in_toast#one" = "Wait until the boost is available or get **{count}** more boost by gifting a **Telegram Premium** subscription.";
|
||||
"lng_boost_available_in_toast#other" = "Wait until the boost is available or get **{count}** more boosts by gifting a **Telegram Premium** subscription.";
|
||||
"lng_boost_reassign_done#one" = "{count} boost is reassigned from {channels}.";
|
||||
"lng_boost_reassign_done#other" = "{count} boosts are reassigned from {channels}.";
|
||||
"lng_boost_reassign_channels#one" = "{count} channel";
|
||||
"lng_boost_reassign_channels#other" = "{count} channels";
|
||||
|
||||
"lng_boost_channel_title_color" = "Enable colors";
|
||||
"lng_boost_channel_needs_level_color#one" = "Your channel needs to reach **Level {count}** to change channel color.";
|
||||
@@ -2077,9 +2097,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_giveaway_award_subtitle" = "Select recipients >";
|
||||
"lng_giveaway_award_chosen#one" = "{count} recipient >";
|
||||
"lng_giveaway_award_chosen#other" = "{count} recipients >";
|
||||
"lng_giveaway_quantity_title" = "Quantity of prizes / boosts";
|
||||
"lng_giveaway_quantity#one" = "{count} Subscription / Boost";
|
||||
"lng_giveaway_quantity#other" = "{count} Subscriptions / Boosts";
|
||||
"lng_giveaway_quantity_title" = "Quantity of prizes";
|
||||
"lng_giveaway_quantity#one" = "{count} boost";
|
||||
"lng_giveaway_quantity#other" = "{count} boosts";
|
||||
"lng_giveaway_quantity_about" = "Choose how many Premium subscriptions to give away and boosts to receive.";
|
||||
"lng_giveaway_channels_title" = "Channels included in the giveaway";
|
||||
"lng_giveaway_channels_this#one" = "this channel will receive {count} boost";
|
||||
@@ -2088,10 +2108,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_giveaway_channels_about" = "Choose the channels the users need to join to take part in the giveaway.";
|
||||
"lng_giveaway_users_title" = "Users eligible for the giveaway";
|
||||
"lng_giveaway_users_all" = "All subscribers";
|
||||
"lng_giveaway_users_from_all_countries" = "from all countries";
|
||||
"lng_giveaway_users_from_one_country" = "from {country}";
|
||||
"lng_giveaway_users_from_countries#one" = "from {count} country";
|
||||
"lng_giveaway_users_from_countries#other" = "from {count} countries";
|
||||
"lng_giveaway_users_new" = "Only new subscribers";
|
||||
"lng_giveaway_users_about" = "Choose if you want to limit the giveaway only to the newly joined subscribers.";
|
||||
"lng_giveaway_users_about" = "Choose if you want to limit the giveaway only to those who joined the channel after the giveaway started or to users from specific countries.";
|
||||
"lng_giveaway_start" = "Start Giveaway";
|
||||
"lng_giveaway_award" = "Gift Premium";
|
||||
"lng_giveaway_start_sure" = "Are you sure you want to start this prepaid giveaway now? This action cannot be undone.";
|
||||
"lng_giveaway_date_title" = "Date when giveaway ends";
|
||||
"lng_giveaway_date" = "Date and Time";
|
||||
"lng_giveaway_date_about#one" = "Choose when {count} subscriber of your channel will be randomly selected to receive Telegram Premium.";
|
||||
@@ -2099,14 +2124,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_giveaway_duration_title#one" = "Duration of Premium subscription";
|
||||
"lng_giveaway_duration_title#other" = "Duration of Premium subscriptions";
|
||||
"lng_giveaway_duration_price" = "{price} x {amount}";
|
||||
"lng_giveaway_duration_about" = "You can review the list of features and terms of use for Telegram Premium {link}.";
|
||||
"lng_giveaway_duration_about_link" = "here";
|
||||
"lng_giveaway_date_select" = "Select Date and Time";
|
||||
"lng_giveaway_date_confirm" = "Confirm";
|
||||
"lng_giveaway_channels_select#one" = "Select up to {count} channel";
|
||||
"lng_giveaway_channels_select#other" = "Select up to {count} channels";
|
||||
"lng_giveaway_recipients_save" = "Save Recipients";
|
||||
"lng_giveaway_recipients_deselect" = "Deselect All";
|
||||
"lng_giveaway_maximum_countries_error#one" = "You can select maximum {count} country.";
|
||||
"lng_giveaway_maximum_countries_error#other" = "You can select maximum {count} countries.";
|
||||
"lng_giveaway_maximum_channels_error#one" = "You can select maximum {count} channel.";
|
||||
"lng_giveaway_maximum_channels_error#other" = "You can select maximum {count} channels.";
|
||||
"lng_giveaway_maximum_users_error#one" = "You can select maximum {count} user.";
|
||||
"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_created_title" = "Giveaway created";
|
||||
"lng_giveaway_created_body" = "Check your channels' {link} to see how this giveaway boosted your channel.";
|
||||
"lng_giveaway_awarded_title" = "Premium subscriptions gifted";
|
||||
"lng_giveaway_awarded_body" = "Check your channels' {link} to see how gifts boosted your channel.";
|
||||
"lng_giveaway_created_link" = "Statistics";
|
||||
|
||||
"lng_prize_title" = "Congratulations!";
|
||||
"lng_prize_about" = "You won a prize in a giveaway organized by {channel}.";
|
||||
@@ -2142,10 +2179,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_prizes_end_when_finish" = "On {date}, Telegram automatically selected {winners}.";
|
||||
"lng_prizes_end_activated#one" = "**{count}** of the winners already used their gift link.";
|
||||
"lng_prizes_end_activated#other" = "**{count}** of the winners already used their gift links.";
|
||||
"lng_prizes_winners_all_of_one#one" = "{count} random subscribers of {channel}.";
|
||||
"lng_prizes_winners_all_of_one#other" = "{count} random subscribers of {channel}.";
|
||||
"lng_prizes_winners_all_of_many#one" = "{count} random subscribers of {channel} and other listed channels.";
|
||||
"lng_prizes_winners_all_of_many#other" = "{count} random subscribers of {channel} and other listed channels.";
|
||||
"lng_prizes_winners_all_of_one#one" = "{count} random subscribers of {channel}";
|
||||
"lng_prizes_winners_all_of_one#other" = "{count} random subscribers of {channel}";
|
||||
"lng_prizes_winners_all_of_many#one" = "{count} random subscribers of {channel} and other listed channels";
|
||||
"lng_prizes_winners_all_of_many#other" = "{count} random subscribers of {channel} and other listed channels";
|
||||
"lng_prizes_winners_new_of_one#one" = "{count} random user that joined {channel} after {start_date}";
|
||||
"lng_prizes_winners_new_of_one#other" = "{count} random users that joined {channel} after {start_date}";
|
||||
"lng_prizes_winners_new_of_many#one" = "{count} random user that joined {channel} and other listed channels after {start_date}";
|
||||
@@ -2182,6 +2219,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_gift_link_used_about" = "This link was used to activate\na **Telegram Premium** subscription.";
|
||||
"lng_gift_link_used_footer" = "This link was used on {date}.";
|
||||
"lng_gift_link_expired" = "Gift code link expired";
|
||||
"lng_gift_link_pending_about" = "This link allows {user} to activate\na **Telegram Premium** subscription.";
|
||||
"lng_gift_link_pending_toast" = "Only the recipient can see the link.";
|
||||
"lng_gift_link_pending_footer" = "This link hasn't been activated yet.";
|
||||
|
||||
"lng_accounts_limit_title" = "Limit Reached";
|
||||
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected accounts.";
|
||||
@@ -2674,6 +2714,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_forwarding_from_two" = "{user} and {second_user}";
|
||||
"lng_inline_switch_choose" = "Choose conversation...";
|
||||
"lng_inline_switch_cant" = "Sorry, no way to write here :(";
|
||||
"lng_preview_reply_to" = "Reply to {name}";
|
||||
"lng_preview_reply_to_quote" = "Reply to quote by {name}";
|
||||
|
||||
"lng_reply_in_another_title" = "Reply in...";
|
||||
"lng_reply_in_another_chat" = "Reply in Another Chat";
|
||||
@@ -2684,6 +2726,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_reply_options_quote" = "Update Quote";
|
||||
"lng_reply_header_short" = "Reply";
|
||||
"lng_reply_quote_selected" = "Quote Selected";
|
||||
"lng_reply_from_private_chat" = "This reply is from a private chat.";
|
||||
"lng_link_options_header" = "Link Preview Settings";
|
||||
"lng_link_header_short" = "Link";
|
||||
"lng_link_move_up" = "Move Up";
|
||||
@@ -4307,13 +4350,32 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_boosts_existing" = "Existing boosts";
|
||||
"lng_boosts_premium_audience" = "Premium subscribers";
|
||||
"lng_boosts_next_level" = "Boosts to level up";
|
||||
"lng_boosts_list_title#one" = "{count} booster";
|
||||
"lng_boosts_list_title#other" = "{count} boosters";
|
||||
"lng_boosts_list_title#one" = "{count} Boost";
|
||||
"lng_boosts_list_title#other" = "{count} Boosts";
|
||||
"lng_boosts_list_subtext" = "Your channel is currently boosted by these users.";
|
||||
"lng_boosts_show_more" = "Show More Boosts";
|
||||
"lng_boosts_show_more_boosts#one" = "Show {count} More Boosts";
|
||||
"lng_boosts_show_more_boosts#other" = "Show {count} More Boosts";
|
||||
"lng_boosts_show_more_gifts#one" = "Show {count} More Boosts";
|
||||
"lng_boosts_show_more_gifts#other" = "Show {count} More Boosts";
|
||||
"lng_boosts_list_status" = "boost expires on {date}";
|
||||
"lng_boosts_link_title" = "Link for boosting";
|
||||
"lng_boosts_link_subtext" = "Share this link with your subscribers to get more boosts.";
|
||||
"lng_boosts_get_boosts" = "Get Boosts via Gifts";
|
||||
"lng_boosts_get_boosts_subtext" = "Get more boosts for your channel by gifting Telegram Premium to your subscribers.";
|
||||
"lng_boosts_list_unclaimed" = "Unclaimed";
|
||||
"lng_boosts_list_pending" = "To be distributed";
|
||||
"lng_boosts_list_pending_about" = "The recipient will be selected when the giveaway ends.";
|
||||
"lng_boosts_list_tab_gifts#one" = "{count} Gifts";
|
||||
"lng_boosts_list_tab_gifts#other" = "{count} Gifts";
|
||||
|
||||
"lng_boosts_prepaid_giveaway_title" = "Prepaid giveaways";
|
||||
"lng_boosts_prepaid_giveaway_single" = "Prepaid giveaway";
|
||||
"lng_boosts_prepaid_giveaway_quantity#one" = "{count} Telegram Premium";
|
||||
"lng_boosts_prepaid_giveaway_quantity#other" = "{count} Telegram Premium";
|
||||
"lng_boosts_prepaid_giveaway_moths#one" = "{count}-month subscriptions";
|
||||
"lng_boosts_prepaid_giveaway_moths#other" = "{count}-month subscriptions";
|
||||
"lng_boosts_prepaid_giveaway_status#one" = "{count} subscription {duration}";
|
||||
"lng_boosts_prepaid_giveaway_status#other" = "{count} subscriptions {duration}";
|
||||
|
||||
// Wnd specific
|
||||
|
||||
@@ -4385,6 +4447,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
// AyuGram keys generated
|
||||
|
||||
"ayu_AyuPreferences" = "AyuGram Preferences";
|
||||
"ayu_DocsText" = "Documentation";
|
||||
"ayu_GhostEssentialsHeader" = "Ghost essentials";
|
||||
"ayu_DontReadMessages" = "Don't read messages";
|
||||
"ayu_DontReadStories" = "Don't read stories";
|
||||
@@ -4432,6 +4495,8 @@ 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_DrawerElementsHeader" = "Drawer Elements";
|
||||
"ayu_TrayElementsHeader" = "Tray Elements";
|
||||
"ayu_RegexFilters" = "Message Filters";
|
||||
"ayu_RegexFiltersAmount" = "filters";
|
||||
"ayu_RegexFiltersHeader" = "Filters";
|
||||
@@ -4445,6 +4510,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"ayu_EnableExpression" = "Enable expression";
|
||||
"ayu_CaseInsensitiveExpression" = "Case insensitive expression";
|
||||
"ayu_RegexFiltersAddError" = "Regex syntax error";
|
||||
"ayu_RegexFilterQuickAdd" = "Add filter";
|
||||
"ayu_AyuSyncHeader" = "AyuSync";
|
||||
"ayu_AyuSyncStatusTitle" = "Sync status";
|
||||
"ayu_AyuSyncStatusOk" = "connected";
|
||||
@@ -4467,7 +4533,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"ayu_AyuSyncDownloadAgent" = "Download agent";
|
||||
"ayu_IconDefault" = "Default";
|
||||
"ayu_IconAlternative" = "AyuGram Alt";
|
||||
"ayu_IconDiscord" = "Discord";
|
||||
"ayu_IconSpotify" = "Spotify";
|
||||
"ayu_IconExtera" = "extera";
|
||||
"ayu_IconNothing" = "Nothing";
|
||||
"ayu_IconBard" = "Google Bard";
|
||||
"ayu_IconYaPlus" = "Yandex Plus";
|
||||
"ayu_WALMode" = "Enable WAL mode";
|
||||
"ayu_PushNotificationCount" = "FCM notifications received";
|
||||
"ayu_ClearAyuDatabase" = "Clear Ayu Database";
|
||||
@@ -4494,8 +4565,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"ayu_DisableStreamerModeTray" = "Disable Streamer Mode";
|
||||
"ayu_EditsHistoryTitle" = "Edits history";
|
||||
"ayu_EditsHistoryMenuText" = "History";
|
||||
"ayu_PeekOnlineMenuText" = "Peek Online";
|
||||
"ayu_ClearDeletedMenuText" = "Clear Deleted";
|
||||
"ayu_ViewFiltersMenuText" = "View Filters";
|
||||
"ayu_PeekOnlineSuccess" = "Peeked via";
|
||||
"ayu_OneViewTTL" = "one view";
|
||||
"ayu_ReadUntilMenuText" = "Read Message";
|
||||
"ayu_DeleteKeepLocally" = "Keep locally";
|
||||
@@ -4506,11 +4579,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"ayu_ClearDeletedMessagesText" = "Are you sure you want to clear all deleted messages in this chat?";
|
||||
"ayu_ClearDeletedMessagesActionText" = "Clear";
|
||||
"ayu_SuggestGhostModeTitle" = "Ghost Mode";
|
||||
"ayu_SuggestGhostModeStoryText" = "Do you want to enable Ghost Mode before viewing the story?";
|
||||
"ayu_SuggestGhostModeStoryActionText" = "Enable";
|
||||
"ayu_SuggestGhostModeStoryText" = "Do you want to enable **Ghost Mode** before viewing the story?";
|
||||
"ayu_SuggestGhostModeStoryActionTextYes" = "Yes";
|
||||
"ayu_SuggestGhostModeStoryActionTextNo" = "No";
|
||||
"ayu_HideNextViewsDescriptionAyu" = "Hide my views forever, until Ghost Mode disabled.";
|
||||
"ayu_EnableGhostModeStories" = "Enable Ghost Mode";
|
||||
"ayu_GhostModeIsActive" = "Ghost Mode Is Active";
|
||||
"ayu_SimpleQuotesAndReplies" = "Simple quotes & replies";
|
||||
"ayu_MainFont" = "Application font";
|
||||
"ayu_MonospaceFont" = "Monospace font";
|
||||
"ayu_FontDefault" = "Default";
|
||||
@@ -4520,6 +4595,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"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_DeleteDateMenuText" = "Delete Date";
|
||||
"ayu_ForwardableMenuText" = "Forwardable";
|
||||
"ayu_ForwardableMenuTextNo" = "No";
|
||||
"ayu_LikelyOfflineStatus" = "offline ?";
|
||||
"ayu_GhostModeShortcut" = "Enter with Ghost";
|
||||
"ayu_SettingsShowMessageSeconds" = "Show message seconds";
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
<file alias="icons/settings/dino.svg">../../icons/settings/dino.svg</file>
|
||||
<file alias="icons/settings/star.svg">../../icons/settings/star.svg</file>
|
||||
<file alias="icons/settings/starmini.svg">../../icons/settings/starmini.svg</file>
|
||||
<file alias="icons/tray/monochrome.svg">../../icons/tray_monochrome.svg</file>
|
||||
<file alias="topic_icons/blue.svg">../../art/topic_icons/blue.svg</file>
|
||||
<file alias="topic_icons/yellow.svg">../../art/topic_icons/yellow.svg</file>
|
||||
<file alias="topic_icons/violet.svg">../../art/topic_icons/violet.svg</file>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="4.11.1.0" />
|
||||
Version="4.11.7.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,11,1,0
|
||||
PRODUCTVERSION 4,11,1,0
|
||||
FILEVERSION 4,11,7,0
|
||||
PRODUCTVERSION 4,11,7,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Radolyn Labs"
|
||||
VALUE "FileDescription", "AyuGram Desktop"
|
||||
VALUE "FileVersion", "4.11.1.0"
|
||||
VALUE "FileVersion", "4.11.7.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
|
||||
VALUE "ProductName", "AyuGram Desktop"
|
||||
VALUE "ProductVersion", "4.11.1.0"
|
||||
VALUE "ProductVersion", "4.11.7.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 4,11,1,0
|
||||
PRODUCTVERSION 4,11,1,0
|
||||
FILEVERSION 4,11,7,0
|
||||
PRODUCTVERSION 4,11,7,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.11.1.0"
|
||||
VALUE "FileVersion", "4.11.7.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
|
||||
VALUE "ProductName", "AyuGram Desktop"
|
||||
VALUE "ProductVersion", "4.11.1.0"
|
||||
VALUE "ProductVersion", "4.11.7.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -9,12 +9,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "api/api_premium_option.h"
|
||||
#include "api/api_text_entities.h"
|
||||
#include "main/main_session.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/random.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "data/data_session.h"
|
||||
#include "main/main_account.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "main/main_session.h"
|
||||
#include "payments/payments_form.h"
|
||||
#include "ui/text/format_values.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
@@ -31,6 +36,24 @@ namespace {
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] Data::SubscriptionOptions GiftCodesFromTL(
|
||||
const QVector<MTPPremiumGiftCodeOption> &tlOptions) {
|
||||
auto options = SubscriptionOptionsFromTL(tlOptions);
|
||||
for (auto i = 0; i < options.size(); i++) {
|
||||
const auto &tlOption = tlOptions[i].data();
|
||||
const auto perUserText = Ui::FillAmountAndCurrency(
|
||||
tlOption.vamount().v / float64(tlOption.vusers().v),
|
||||
qs(tlOption.vcurrency()),
|
||||
false);
|
||||
options[i].costPerMonth = perUserText
|
||||
+ ' '
|
||||
+ QChar(0x00D7)
|
||||
+ ' '
|
||||
+ QString::number(tlOption.vusers().v);
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Premium::Premium(not_null<ApiWrap*> api)
|
||||
@@ -311,4 +334,165 @@ const Data::SubscriptionOptions &Premium::subscriptionOptions() const {
|
||||
return _subscriptionOptions;
|
||||
}
|
||||
|
||||
PremiumGiftCodeOptions::PremiumGiftCodeOptions(not_null<PeerData*> peer)
|
||||
: _peer(peer)
|
||||
, _api(&peer->session().api().instance()) {
|
||||
}
|
||||
|
||||
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),
|
||||
_peer->input
|
||||
)).done([=](const MTPVector<TLOption> &result) {
|
||||
auto tlMapOptions = base::flat_map<Amount, QVector<TLOption>>();
|
||||
for (const auto &tlOption : result.v) {
|
||||
const auto &data = tlOption.data();
|
||||
tlMapOptions[data.vusers().v].push_back(tlOption);
|
||||
|
||||
const auto token = Token{ data.vusers().v, data.vmonths().v };
|
||||
_stores[token] = Store{
|
||||
.amount = data.vamount().v,
|
||||
.product = qs(data.vstore_product().value_or_empty()),
|
||||
.quantity = data.vstore_quantity().value_or_empty(),
|
||||
};
|
||||
if (!ranges::contains(_availablePresets, data.vusers().v)) {
|
||||
_availablePresets.push_back(data.vusers().v);
|
||||
}
|
||||
}
|
||||
for (const auto &[amount, tlOptions] : tlMapOptions) {
|
||||
if (amount == 1 && _optionsForOnePerson.currency.isEmpty()) {
|
||||
_optionsForOnePerson.currency = qs(
|
||||
tlOptions.front().data().vcurrency());
|
||||
for (const auto &option : tlOptions) {
|
||||
_optionsForOnePerson.months.push_back(
|
||||
option.data().vmonths().v);
|
||||
_optionsForOnePerson.totalCosts.push_back(
|
||||
option.data().vamount().v);
|
||||
}
|
||||
}
|
||||
_subscriptionOptions[amount] = GiftCodesFromTL(tlOptions);
|
||||
}
|
||||
consumer.put_done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
rpl::producer<rpl::no_value, QString> PremiumGiftCodeOptions::applyPrepaid(
|
||||
const Payments::InvoicePremiumGiftCode &invoice,
|
||||
uint64 prepaidId) {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
const auto channel = _peer->asChannel();
|
||||
if (!channel) {
|
||||
return lifetime;
|
||||
}
|
||||
|
||||
_api.request(MTPpayments_LaunchPrepaidGiveaway(
|
||||
_peer->input,
|
||||
MTP_long(prepaidId),
|
||||
Payments::InvoicePremiumGiftCodeGiveawayToTL(invoice)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_peer->session().api().applyUpdates(result);
|
||||
consumer.put_done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
const std::vector<int> &PremiumGiftCodeOptions::availablePresets() const {
|
||||
return _availablePresets;
|
||||
}
|
||||
|
||||
[[nodiscard]] int PremiumGiftCodeOptions::monthsFromPreset(int monthsIndex) {
|
||||
return _optionsForOnePerson.months[monthsIndex];
|
||||
}
|
||||
|
||||
Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice(
|
||||
int users,
|
||||
int months) {
|
||||
const auto randomId = base::RandomValue<uint64>();
|
||||
const auto token = Token{ users, months };
|
||||
const auto &store = _stores[token];
|
||||
return Payments::InvoicePremiumGiftCode{
|
||||
.randomId = randomId,
|
||||
.currency = _optionsForOnePerson.currency,
|
||||
.amount = store.amount,
|
||||
.storeProduct = store.product,
|
||||
.storeQuantity = store.quantity,
|
||||
.users = token.users,
|
||||
.months = token.months,
|
||||
};
|
||||
}
|
||||
|
||||
Data::SubscriptionOptions PremiumGiftCodeOptions::options(int amount) {
|
||||
const auto it = _subscriptionOptions.find(amount);
|
||||
if (it != end(_subscriptionOptions)) {
|
||||
return it->second;
|
||||
} else {
|
||||
auto tlOptions = QVector<MTPPremiumGiftCodeOption>();
|
||||
for (auto i = 0; i < _optionsForOnePerson.months.size(); i++) {
|
||||
tlOptions.push_back(MTP_premiumGiftCodeOption(
|
||||
MTP_flags(MTPDpremiumGiftCodeOption::Flags(0)),
|
||||
MTP_int(amount),
|
||||
MTP_int(_optionsForOnePerson.months[i]),
|
||||
MTPstring(),
|
||||
MTPint(),
|
||||
MTP_string(_optionsForOnePerson.currency),
|
||||
MTP_long(_optionsForOnePerson.totalCosts[i] * amount)));
|
||||
}
|
||||
_subscriptionOptions[amount] = GiftCodesFromTL(tlOptions);
|
||||
return _subscriptionOptions[amount];
|
||||
}
|
||||
}
|
||||
|
||||
int PremiumGiftCodeOptions::giveawayBoostsPerPremium() const {
|
||||
constexpr auto kFallbackCount = 4;
|
||||
return _peer->session().account().appConfig().get<int>(
|
||||
u"giveaway_boosts_per_premium"_q,
|
||||
kFallbackCount);
|
||||
}
|
||||
|
||||
int PremiumGiftCodeOptions::giveawayCountriesMax() const {
|
||||
constexpr auto kFallbackCount = 10;
|
||||
return _peer->session().account().appConfig().get<int>(
|
||||
u"giveaway_countries_max"_q,
|
||||
kFallbackCount);
|
||||
}
|
||||
|
||||
int PremiumGiftCodeOptions::giveawayAddPeersMax() const {
|
||||
constexpr auto kFallbackCount = 10;
|
||||
return _peer->session().account().appConfig().get<int>(
|
||||
u"giveaway_add_peers_max"_q,
|
||||
kFallbackCount);
|
||||
}
|
||||
|
||||
int PremiumGiftCodeOptions::giveawayPeriodMax() const {
|
||||
constexpr auto kFallbackCount = 3600 * 24 * 7;
|
||||
return _peer->session().account().appConfig().get<int>(
|
||||
u"giveaway_period_max"_q,
|
||||
kFallbackCount);
|
||||
}
|
||||
|
||||
bool PremiumGiftCodeOptions::giveawayGiftsPurchaseAvailable() const {
|
||||
return _peer->session().account().appConfig().get<bool>(
|
||||
u"giveaway_gifts_purchase_available"_q,
|
||||
false);
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -16,6 +16,10 @@ namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Payments {
|
||||
struct InvoicePremiumGiftCode;
|
||||
} // namespace Payments
|
||||
|
||||
namespace Api {
|
||||
|
||||
struct GiftCode {
|
||||
@@ -141,4 +145,55 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class PremiumGiftCodeOptions final {
|
||||
public:
|
||||
PremiumGiftCodeOptions(not_null<PeerData*> peer);
|
||||
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> request();
|
||||
[[nodiscard]] Data::SubscriptionOptions options(int amount);
|
||||
[[nodiscard]] const std::vector<int> &availablePresets() const;
|
||||
[[nodiscard]] int monthsFromPreset(int monthsIndex);
|
||||
[[nodiscard]] Payments::InvoicePremiumGiftCode invoice(
|
||||
int users,
|
||||
int months);
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> applyPrepaid(
|
||||
const Payments::InvoicePremiumGiftCode &invoice,
|
||||
uint64 prepaidId);
|
||||
|
||||
[[nodiscard]] int giveawayBoostsPerPremium() const;
|
||||
[[nodiscard]] int giveawayCountriesMax() const;
|
||||
[[nodiscard]] int giveawayAddPeersMax() const;
|
||||
[[nodiscard]] int giveawayPeriodMax() const;
|
||||
[[nodiscard]] bool giveawayGiftsPurchaseAvailable() const;
|
||||
|
||||
private:
|
||||
struct Token final {
|
||||
int users = 0;
|
||||
int months = 0;
|
||||
|
||||
friend inline constexpr auto operator<=>(Token, Token) = default;
|
||||
|
||||
};
|
||||
struct Store final {
|
||||
uint64 amount = 0;
|
||||
QString product;
|
||||
int quantity = 0;
|
||||
};
|
||||
using Amount = int;
|
||||
const not_null<PeerData*> _peer;
|
||||
base::flat_map<Amount, Data::SubscriptionOptions> _subscriptionOptions;
|
||||
struct {
|
||||
std::vector<int> months;
|
||||
std::vector<float64> totalCosts;
|
||||
QString currency;
|
||||
} _optionsForOnePerson;
|
||||
|
||||
std::vector<int> _availablePresets;
|
||||
|
||||
base::flat_map<Token, Store> _stores;
|
||||
|
||||
MTP::Sender _api;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -36,7 +36,10 @@ template<typename Option>
|
||||
result.reserve(tlOptions.size());
|
||||
for (const auto &tlOption : tlOptions) {
|
||||
const auto &option = tlOption.data();
|
||||
const auto botUrl = qs(option.vbot_url());
|
||||
auto botUrl = QString();
|
||||
if constexpr (!std::is_same_v<Option, MTPPremiumGiftCodeOption>) {
|
||||
botUrl = qs(option.vbot_url());
|
||||
}
|
||||
const auto months = option.vmonths().v;
|
||||
const auto amount = option.vamount().v;
|
||||
const auto currency = qs(option.vcurrency());
|
||||
|
||||
@@ -270,7 +270,6 @@ bool SendDice(MessageToSend &message) {
|
||||
flags |= MessageFlag::HasReplyInfo;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to;
|
||||
}
|
||||
const auto replyHeader = NewMessageReplyHeader(message.action);
|
||||
const auto anonymousPost = peer->amAnonymous();
|
||||
const auto silentPost = ShouldSendSilent(peer, message.action.options);
|
||||
InnerFillMessagePostFlags(message.action.options, peer, flags);
|
||||
@@ -399,7 +398,6 @@ void SendConfirmedFile(
|
||||
if (file->to.replyTo) {
|
||||
flags |= MessageFlag::HasReplyInfo;
|
||||
}
|
||||
const auto replyHeader = NewMessageReplyHeader(action);
|
||||
const auto anonymousPost = peer->amAnonymous();
|
||||
const auto silentPost = ShouldSendSilent(peer, file->to.options);
|
||||
FillMessagePostFlags(action, peer, flags);
|
||||
|
||||
@@ -18,6 +18,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
constexpr auto kCheckRequestsTimer = 10 * crl::time(1000);
|
||||
|
||||
[[nodiscard]] Data::StatisticalGraph StatisticalGraphFromTL(
|
||||
const MTPStatsGraph &tl) {
|
||||
return tl.match([&](const MTPDstatsGraph &d) {
|
||||
@@ -187,23 +189,73 @@ namespace {
|
||||
|
||||
} // namespace
|
||||
|
||||
Statistics::Statistics(not_null<ApiWrap*> api)
|
||||
: _api(&api->instance()) {
|
||||
Statistics::Statistics(not_null<ChannelData*> channel)
|
||||
: StatisticsRequestSender(channel) {
|
||||
}
|
||||
|
||||
rpl::producer<rpl::no_value, QString> Statistics::request(
|
||||
not_null<PeerData*> peer) {
|
||||
StatisticsRequestSender::StatisticsRequestSender(not_null<ChannelData *> channel)
|
||||
: _channel(channel)
|
||||
, _api(&_channel->session().api().instance())
|
||||
, _timer([=] { checkRequests(); }) {
|
||||
}
|
||||
|
||||
StatisticsRequestSender::~StatisticsRequestSender() {
|
||||
for (const auto &[dcId, ids] : _requests) {
|
||||
for (const auto id : ids) {
|
||||
_channel->session().api().unregisterStatsRequest(dcId, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StatisticsRequestSender::checkRequests() {
|
||||
for (auto i = begin(_requests); i != end(_requests);) {
|
||||
for (auto j = begin(i->second); j != end(i->second);) {
|
||||
if (_api.pending(*j)) {
|
||||
++j;
|
||||
} else {
|
||||
_channel->session().api().unregisterStatsRequest(
|
||||
i->first,
|
||||
*j);
|
||||
j = i->second.erase(j);
|
||||
}
|
||||
}
|
||||
if (i->second.empty()) {
|
||||
i = _requests.erase(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
if (_requests.empty()) {
|
||||
_timer.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Request, typename, typename>
|
||||
auto StatisticsRequestSender::makeRequest(Request &&request) {
|
||||
const auto id = _api.allocateRequestId();
|
||||
const auto dcId = _channel->owner().statsDcId(_channel);
|
||||
if (dcId) {
|
||||
_channel->session().api().registerStatsRequest(dcId, id);
|
||||
_requests[dcId].emplace(id);
|
||||
if (!_timer.isActive()) {
|
||||
_timer.callEach(kCheckRequestsTimer);
|
||||
}
|
||||
}
|
||||
return std::move(_api.request(
|
||||
std::forward<Request>(request)
|
||||
).toDC(
|
||||
dcId ? MTP::ShiftDcId(dcId, MTP::kStatsDcShift) : 0
|
||||
).overrideId(id));
|
||||
}
|
||||
|
||||
rpl::producer<rpl::no_value, QString> Statistics::request() {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
const auto channel = peer->asChannel();
|
||||
if (!channel) {
|
||||
return lifetime;
|
||||
}
|
||||
|
||||
if (!channel->isMegagroup()) {
|
||||
_api.request(MTPstats_GetBroadcastStats(
|
||||
if (!channel()->isMegagroup()) {
|
||||
makeRequest(MTPstats_GetBroadcastStats(
|
||||
MTP_flags(MTPstats_GetBroadcastStats::Flags(0)),
|
||||
channel->inputChannel
|
||||
channel()->inputChannel
|
||||
)).done([=](const MTPstats_BroadcastStats &result) {
|
||||
_channelStats = ChannelStatisticsFromTL(result.data());
|
||||
consumer.put_done();
|
||||
@@ -211,12 +263,13 @@ rpl::producer<rpl::no_value, QString> Statistics::request(
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
} else {
|
||||
_api.request(MTPstats_GetMegagroupStats(
|
||||
makeRequest(MTPstats_GetMegagroupStats(
|
||||
MTP_flags(MTPstats_GetMegagroupStats::Flags(0)),
|
||||
channel->inputChannel
|
||||
channel()->inputChannel
|
||||
)).done([=](const MTPstats_MegagroupStats &result) {
|
||||
_supergroupStats = SupergroupStatisticsFromTL(result.data());
|
||||
peer->owner().processUsers(result.data().vusers());
|
||||
const auto &data = result.data();
|
||||
_supergroupStats = SupergroupStatisticsFromTL(data);
|
||||
channel()->owner().processUsers(data.vusers());
|
||||
consumer.put_done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
@@ -228,18 +281,13 @@ rpl::producer<rpl::no_value, QString> Statistics::request(
|
||||
}
|
||||
|
||||
Statistics::GraphResult Statistics::requestZoom(
|
||||
not_null<PeerData*> peer,
|
||||
const QString &token,
|
||||
float64 x) {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
const auto channel = peer->asChannel();
|
||||
if (!channel) {
|
||||
return lifetime;
|
||||
}
|
||||
const auto wasEmpty = _zoomDeque.empty();
|
||||
_zoomDeque.push_back([=] {
|
||||
_api.request(MTPstats_LoadAsyncGraph(
|
||||
makeRequest(MTPstats_LoadAsyncGraph(
|
||||
MTP_flags(x
|
||||
? MTPstats_LoadAsyncGraph::Flag::f_x
|
||||
: MTPstats_LoadAsyncGraph::Flag(0)),
|
||||
@@ -266,32 +314,6 @@ Statistics::GraphResult Statistics::requestZoom(
|
||||
};
|
||||
}
|
||||
|
||||
Statistics::GraphResult Statistics::requestMessage(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId msgId) {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
const auto channel = peer->asChannel();
|
||||
if (!channel) {
|
||||
return lifetime;
|
||||
}
|
||||
|
||||
_api.request(MTPstats_GetMessageStats(
|
||||
MTP_flags(MTPstats_GetMessageStats::Flags(0)),
|
||||
channel->inputChannel,
|
||||
MTP_int(msgId.bare)
|
||||
)).done([=](const MTPstats_MessageStats &result) {
|
||||
consumer.put_next(
|
||||
StatisticalGraphFromTL(result.data().vviews_graph()));
|
||||
consumer.put_done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
Data::ChannelStatistics Statistics::channelStats() const {
|
||||
return _channelStats;
|
||||
}
|
||||
@@ -303,9 +325,8 @@ Data::SupergroupStatistics Statistics::supergroupStats() const {
|
||||
PublicForwards::PublicForwards(
|
||||
not_null<ChannelData*> channel,
|
||||
FullMsgId fullId)
|
||||
: _channel(channel)
|
||||
, _fullId(fullId)
|
||||
, _api(&channel->session().api().instance()) {
|
||||
: StatisticsRequestSender(channel)
|
||||
, _fullId(fullId) {
|
||||
}
|
||||
|
||||
void PublicForwards::request(
|
||||
@@ -314,19 +335,19 @@ void PublicForwards::request(
|
||||
if (_requestId) {
|
||||
return;
|
||||
}
|
||||
const auto offsetPeer = _channel->owner().peer(token.fullId.peer);
|
||||
const auto offsetPeer = channel()->owner().peer(token.fullId.peer);
|
||||
const auto tlOffsetPeer = offsetPeer
|
||||
? offsetPeer->input
|
||||
: MTP_inputPeerEmpty();
|
||||
constexpr auto kLimit = tl::make_int(100);
|
||||
_requestId = _api.request(MTPstats_GetMessagePublicForwards(
|
||||
_channel->inputChannel,
|
||||
_requestId = makeRequest(MTPstats_GetMessagePublicForwards(
|
||||
channel()->inputChannel,
|
||||
MTP_int(_fullId.msg),
|
||||
MTP_int(token.rate),
|
||||
tlOffsetPeer,
|
||||
MTP_int(token.fullId.msg),
|
||||
kLimit
|
||||
)).done([=, channel = _channel](const MTPmessages_Messages &result) {
|
||||
)).done([=, channel = channel()](const MTPmessages_Messages &result) {
|
||||
using Messages = QVector<FullMsgId>;
|
||||
_requestId = 0;
|
||||
|
||||
@@ -402,10 +423,9 @@ void PublicForwards::request(
|
||||
MessageStatistics::MessageStatistics(
|
||||
not_null<ChannelData*> channel,
|
||||
FullMsgId fullId)
|
||||
: _publicForwards(channel, fullId)
|
||||
, _channel(channel)
|
||||
, _fullId(fullId)
|
||||
, _api(&channel->session().api().instance()) {
|
||||
: StatisticsRequestSender(channel)
|
||||
, _publicForwards(channel, fullId)
|
||||
, _fullId(fullId) {
|
||||
}
|
||||
|
||||
Data::PublicForwardsSlice MessageStatistics::firstSlice() const {
|
||||
@@ -413,10 +433,9 @@ Data::PublicForwardsSlice MessageStatistics::firstSlice() const {
|
||||
}
|
||||
|
||||
void MessageStatistics::request(Fn<void(Data::MessageStatistics)> done) {
|
||||
if (_channel->isMegagroup()) {
|
||||
if (channel()->isMegagroup()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto requestFirstPublicForwards = [=](
|
||||
const Data::StatisticalGraph &messageGraph,
|
||||
const Data::StatisticsMessageInteractionInfo &info) {
|
||||
@@ -434,8 +453,8 @@ void MessageStatistics::request(Fn<void(Data::MessageStatistics)> done) {
|
||||
|
||||
const auto requestPrivateForwards = [=](
|
||||
const Data::StatisticalGraph &messageGraph) {
|
||||
_api.request(MTPchannels_GetMessages(
|
||||
_channel->inputChannel,
|
||||
api().request(MTPchannels_GetMessages(
|
||||
channel()->inputChannel,
|
||||
MTP_vector<MTPInputMessage>(
|
||||
1,
|
||||
MTP_inputMessageID(MTP_int(_fullId.msg))))
|
||||
@@ -475,9 +494,9 @@ void MessageStatistics::request(Fn<void(Data::MessageStatistics)> done) {
|
||||
}).send();
|
||||
};
|
||||
|
||||
_api.request(MTPstats_GetMessageStats(
|
||||
makeRequest(MTPstats_GetMessageStats(
|
||||
MTP_flags(MTPstats_GetMessageStats::Flags(0)),
|
||||
_channel->inputChannel,
|
||||
channel()->inputChannel,
|
||||
MTP_int(_fullId.msg.bare)
|
||||
)).done([=](const MTPstats_MessageStats &result) {
|
||||
requestPrivateForwards(
|
||||
@@ -485,7 +504,6 @@ void MessageStatistics::request(Fn<void(Data::MessageStatistics)> done) {
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
requestPrivateForwards({});
|
||||
}).send();
|
||||
|
||||
}
|
||||
|
||||
Boosts::Boosts(not_null<PeerData*> peer)
|
||||
@@ -518,8 +536,9 @@ rpl::producer<rpl::no_value, QString> Boosts::request() {
|
||||
? (100. * premiumMemberCount / participantCount)
|
||||
: 0;
|
||||
|
||||
const auto slots = data.vmy_boost_slots();
|
||||
_boostStatus.overview = Data::BoostsOverview{
|
||||
.isBoosted = data.is_my_boost(),
|
||||
.mine = slots ? int(slots->v.size()) : 0,
|
||||
.level = std::max(data.vlevel().v, 0),
|
||||
.boostCount = std::max(
|
||||
data.vboosts().v,
|
||||
@@ -533,9 +552,27 @@ rpl::producer<rpl::no_value, QString> Boosts::request() {
|
||||
};
|
||||
_boostStatus.link = qs(data.vboost_url());
|
||||
|
||||
requestBoosts({}, [=](Data::BoostsListSlice &&slice) {
|
||||
_boostStatus.firstSlice = std::move(slice);
|
||||
consumer.put_done();
|
||||
if (data.vprepaid_giveaways()) {
|
||||
_boostStatus.prepaidGiveaway = ranges::views::all(
|
||||
data.vprepaid_giveaways()->v
|
||||
) | ranges::views::transform([](const MTPPrepaidGiveaway &r) {
|
||||
return Data::BoostPrepaidGiveaway{
|
||||
.months = r.data().vmonths().v,
|
||||
.id = r.data().vid().v,
|
||||
.quantity = r.data().vquantity().v,
|
||||
.date = QDateTime::fromSecsSinceEpoch(
|
||||
r.data().vdate().v),
|
||||
};
|
||||
}) | ranges::to_vector;
|
||||
}
|
||||
|
||||
using namespace Data;
|
||||
requestBoosts({ .gifts = false }, [=](BoostsListSlice &&slice) {
|
||||
_boostStatus.firstSliceBoosts = std::move(slice);
|
||||
requestBoosts({ .gifts = true }, [=](BoostsListSlice &&s) {
|
||||
_boostStatus.firstSliceGifts = std::move(s);
|
||||
consumer.put_done();
|
||||
});
|
||||
});
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
@@ -553,8 +590,11 @@ void Boosts::requestBoosts(
|
||||
}
|
||||
constexpr auto kTlFirstSlice = tl::make_int(kFirstSlice);
|
||||
constexpr auto kTlLimit = tl::make_int(kLimit);
|
||||
const auto gifts = token.gifts;
|
||||
_requestId = _api.request(MTPpremium_GetBoostsList(
|
||||
MTP_flags(0),
|
||||
gifts
|
||||
? MTP_flags(MTPpremium_GetBoostsList::Flag::f_gifts)
|
||||
: MTP_flags(0),
|
||||
_peer->input,
|
||||
MTP_string(token.next),
|
||||
token.next.isEmpty() ? kTlFirstSlice : kTlLimit
|
||||
@@ -566,20 +606,44 @@ void Boosts::requestBoosts(
|
||||
|
||||
auto list = std::vector<Data::Boost>();
|
||||
list.reserve(data.vboosts().v.size());
|
||||
constexpr auto kMonthsDivider = int(30 * 86400);
|
||||
for (const auto &boost : data.vboosts().v) {
|
||||
const auto &data = boost.data();
|
||||
const auto path = data.vused_gift_slug()
|
||||
? (u"giftcode/"_q + qs(data.vused_gift_slug()->v))
|
||||
: QString();
|
||||
auto giftCodeLink = !path.isEmpty()
|
||||
? Data::GiftCodeLink{
|
||||
_peer->session().createInternalLink(path),
|
||||
_peer->session().createInternalLinkFull(path),
|
||||
qs(data.vused_gift_slug()->v),
|
||||
}
|
||||
: Data::GiftCodeLink();
|
||||
list.push_back({
|
||||
boost.data().vuser_id().value_or_empty(),
|
||||
QDateTime::fromSecsSinceEpoch(boost.data().vexpires().v),
|
||||
data.is_gift(),
|
||||
data.is_giveaway(),
|
||||
data.is_unclaimed(),
|
||||
qs(data.vid()),
|
||||
data.vuser_id().value_or_empty(),
|
||||
data.vgiveaway_msg_id()
|
||||
? FullMsgId{ _peer->id, data.vgiveaway_msg_id()->v }
|
||||
: FullMsgId(),
|
||||
QDateTime::fromSecsSinceEpoch(data.vdate().v),
|
||||
QDateTime::fromSecsSinceEpoch(data.vexpires().v),
|
||||
(data.vexpires().v - data.vdate().v) / kMonthsDivider,
|
||||
std::move(giftCodeLink),
|
||||
data.vmultiplier().value_or_empty(),
|
||||
});
|
||||
}
|
||||
done(Data::BoostsListSlice{
|
||||
.list = std::move(list),
|
||||
.total = data.vcount().v,
|
||||
.multipliedTotal = data.vcount().v,
|
||||
.allLoaded = (data.vcount().v == data.vboosts().v.size()),
|
||||
.token = Data::BoostsListSlice::OffsetToken{
|
||||
data.vnext_offset()
|
||||
.next = data.vnext_offset()
|
||||
? qs(*data.vnext_offset())
|
||||
: QString()
|
||||
: QString(),
|
||||
.gifts = gifts,
|
||||
},
|
||||
});
|
||||
}).fail([=] {
|
||||
|
||||
@@ -7,30 +7,53 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/timer.h"
|
||||
#include "data/data_boosts.h"
|
||||
#include "data/data_statistics.h"
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
class ApiWrap;
|
||||
class ChannelData;
|
||||
class PeerData;
|
||||
|
||||
namespace Api {
|
||||
|
||||
class Statistics final {
|
||||
public:
|
||||
explicit Statistics(not_null<ApiWrap*> api);
|
||||
class StatisticsRequestSender {
|
||||
protected:
|
||||
explicit StatisticsRequestSender(not_null<ChannelData*> channel);
|
||||
~StatisticsRequestSender();
|
||||
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> request(
|
||||
not_null<PeerData*> peer);
|
||||
template <
|
||||
typename Request,
|
||||
typename = std::enable_if_t<!std::is_reference_v<Request>>,
|
||||
typename = typename Request::Unboxed>
|
||||
[[nodiscard]] auto makeRequest(Request &&request);
|
||||
|
||||
[[nodiscard]] MTP::Sender &api() {
|
||||
return _api;
|
||||
}
|
||||
[[nodiscard]] not_null<ChannelData*> channel() {
|
||||
return _channel;
|
||||
}
|
||||
|
||||
private:
|
||||
void checkRequests();
|
||||
|
||||
const not_null<ChannelData*> _channel;
|
||||
MTP::Sender _api;
|
||||
base::Timer _timer;
|
||||
base::flat_map<MTP::DcId, base::flat_set<mtpRequestId>> _requests;
|
||||
|
||||
};
|
||||
|
||||
class Statistics final : public StatisticsRequestSender {
|
||||
public:
|
||||
explicit Statistics(not_null<ChannelData*> channel);
|
||||
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> request();
|
||||
using GraphResult = rpl::producer<Data::StatisticalGraph, QString>;
|
||||
[[nodiscard]] GraphResult requestZoom(
|
||||
not_null<PeerData*> peer,
|
||||
const QString &token,
|
||||
float64 x);
|
||||
[[nodiscard]] GraphResult requestMessage(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId msgId);
|
||||
|
||||
[[nodiscard]] Data::ChannelStatistics channelStats() const;
|
||||
[[nodiscard]] Data::SupergroupStatistics supergroupStats() const;
|
||||
@@ -38,31 +61,27 @@ public:
|
||||
private:
|
||||
Data::ChannelStatistics _channelStats;
|
||||
Data::SupergroupStatistics _supergroupStats;
|
||||
MTP::Sender _api;
|
||||
|
||||
std::deque<Fn<void()>> _zoomDeque;
|
||||
|
||||
};
|
||||
|
||||
class PublicForwards final {
|
||||
class PublicForwards final : public StatisticsRequestSender {
|
||||
public:
|
||||
explicit PublicForwards(not_null<ChannelData*> channel, FullMsgId fullId);
|
||||
PublicForwards(not_null<ChannelData*> channel, FullMsgId fullId);
|
||||
|
||||
void request(
|
||||
const Data::PublicForwardsSlice::OffsetToken &token,
|
||||
Fn<void(Data::PublicForwardsSlice)> done);
|
||||
|
||||
private:
|
||||
const not_null<ChannelData*> _channel;
|
||||
const FullMsgId _fullId;
|
||||
mtpRequestId _requestId = 0;
|
||||
int _lastTotal = 0;
|
||||
|
||||
MTP::Sender _api;
|
||||
|
||||
};
|
||||
|
||||
class MessageStatistics final {
|
||||
class MessageStatistics final : public StatisticsRequestSender {
|
||||
public:
|
||||
explicit MessageStatistics(
|
||||
not_null<ChannelData*> channel,
|
||||
@@ -74,13 +93,11 @@ public:
|
||||
|
||||
private:
|
||||
PublicForwards _publicForwards;
|
||||
const not_null<ChannelData*> _channel;
|
||||
const FullMsgId _fullId;
|
||||
|
||||
Data::PublicForwardsSlice _firstSlice;
|
||||
|
||||
mtpRequestId _requestId = 0;
|
||||
MTP::Sender _api;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -118,6 +118,7 @@ constexpr auto kStickersByEmojiInvalidateTimeout = crl::time(6 * 1000);
|
||||
constexpr auto kNotifySettingSaveTimeout = crl::time(1000);
|
||||
constexpr auto kDialogsFirstLoad = 20;
|
||||
constexpr auto kDialogsPerPage = 500;
|
||||
constexpr auto kStatsSessionKillTimeout = 10 * crl::time(1000);
|
||||
|
||||
using PhotoFileLocationId = Data::PhotoFileLocationId;
|
||||
using DocumentFileLocationId = Data::DocumentFileLocationId;
|
||||
@@ -163,6 +164,7 @@ ApiWrap::ApiWrap(not_null<Main::Session*> session)
|
||||
, _fileLoader(std::make_unique<TaskQueue>(kFileLoaderQueueStopTimeout))
|
||||
, _topPromotionTimer([=] { refreshTopPromotion(); })
|
||||
, _updateNotifyTimer([=] { sendNotifySettingsUpdates(); })
|
||||
, _statsSessionKillTimer([=] { checkStatsSessions(); })
|
||||
, _authorizations(std::make_unique<Api::Authorizations>(this))
|
||||
, _attachedStickers(std::make_unique<Api::AttachedStickers>(this))
|
||||
, _blockedPeers(std::make_unique<Api::BlockedPeers>(this))
|
||||
@@ -3391,7 +3393,6 @@ void ApiWrap::sendSharedContact(
|
||||
if (action.replyTo) {
|
||||
flags |= MessageFlag::HasReplyInfo;
|
||||
}
|
||||
const auto replyHeader = NewMessageReplyHeader(action);
|
||||
FillMessagePostFlags(action, peer, flags);
|
||||
if (action.options.scheduled) {
|
||||
flags |= MessageFlag::IsOrWasScheduled;
|
||||
@@ -3614,13 +3615,15 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
action.generateLocal = true;
|
||||
sendAction(action);
|
||||
|
||||
const auto clearCloudDraft = action.clearDraft;
|
||||
const auto draftTopicRootId = action.replyTo.topicRootId;
|
||||
const auto replyTo = action.replyTo.messageId
|
||||
? peer->owner().message(action.replyTo.messageId)
|
||||
: nullptr;
|
||||
const auto topicRootId = replyTo
|
||||
const auto topicRootId = draftTopicRootId
|
||||
? draftTopicRootId
|
||||
: replyTo
|
||||
? replyTo->topicRootId()
|
||||
: action.replyTo.topicRootId
|
||||
? action.replyTo.topicRootId
|
||||
: Data::ForumTopic::kGeneralId;
|
||||
const auto topic = peer->forumTopicFor(topicRootId);
|
||||
if (!(topic ? Data::CanSendTexts(topic) : Data::CanSendTexts(peer))
|
||||
@@ -3658,7 +3661,10 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
TextUtilities::Trim(sending);
|
||||
|
||||
_session->data().registerMessageRandomId(randomId, newId);
|
||||
_session->data().registerMessageSentData(randomId, peer->id, sending.text);
|
||||
_session->data().registerMessageSentData(
|
||||
randomId,
|
||||
peer->id,
|
||||
sending.text);
|
||||
|
||||
MTPstring msgText(MTP_string(sending.text));
|
||||
auto flags = NewMessageFlags(peer);
|
||||
@@ -3674,7 +3680,6 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
const auto manualWebPage = exactWebPage
|
||||
&& !ignoreWebPage
|
||||
&& (message.webPage.manual || (isLast && !isFirst));
|
||||
const auto replyHeader = NewMessageReplyHeader(action);
|
||||
MTPMessageMedia media = MTP_messageMediaEmpty();
|
||||
if (ignoreWebPage) {
|
||||
sendFlags |= MTPmessages_SendMessage::Flag::f_no_webpage;
|
||||
@@ -3718,13 +3723,11 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
sendFlags |= MTPmessages_SendMessage::Flag::f_entities;
|
||||
mediaFlags |= MTPmessages_SendMedia::Flag::f_entities;
|
||||
}
|
||||
const auto clearCloudDraft = action.clearDraft;
|
||||
const auto topicRootId = action.replyTo.topicRootId;
|
||||
if (clearCloudDraft) {
|
||||
sendFlags |= MTPmessages_SendMessage::Flag::f_clear_draft;
|
||||
mediaFlags |= MTPmessages_SendMedia::Flag::f_clear_draft;
|
||||
history->clearCloudDraft(topicRootId);
|
||||
history->startSavingCloudDraft(topicRootId);
|
||||
history->clearCloudDraft(draftTopicRootId);
|
||||
history->startSavingCloudDraft(draftTopicRootId);
|
||||
}
|
||||
const auto sendAs = action.options.sendAs;
|
||||
const auto messageFromId = sendAs
|
||||
@@ -3761,7 +3764,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
const MTP::Response &response) {
|
||||
if (clearCloudDraft) {
|
||||
history->finishSavingCloudDraft(
|
||||
topicRootId,
|
||||
draftTopicRootId,
|
||||
UnixtimeFromMsgId(response.outerMsgId));
|
||||
}
|
||||
};
|
||||
@@ -3775,7 +3778,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
}
|
||||
if (clearCloudDraft) {
|
||||
history->finishSavingCloudDraft(
|
||||
topicRootId,
|
||||
draftTopicRootId,
|
||||
UnixtimeFromMsgId(response.outerMsgId));
|
||||
}
|
||||
};
|
||||
@@ -4343,6 +4346,32 @@ void ApiWrap::saveSelfBio(const QString &text) {
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ApiWrap::registerStatsRequest(MTP::DcId dcId, mtpRequestId id) {
|
||||
_statsRequests[dcId].emplace(id);
|
||||
}
|
||||
|
||||
void ApiWrap::unregisterStatsRequest(MTP::DcId dcId, mtpRequestId id) {
|
||||
const auto i = _statsRequests.find(dcId);
|
||||
Assert(i != end(_statsRequests));
|
||||
const auto removed = i->second.remove(id);
|
||||
Assert(removed);
|
||||
if (i->second.empty()) {
|
||||
_statsSessionKillTimer.callOnce(kStatsSessionKillTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::checkStatsSessions() {
|
||||
for (auto i = begin(_statsRequests); i != end(_statsRequests);) {
|
||||
if (i->second.empty()) {
|
||||
instance().killSession(
|
||||
MTP::ShiftDcId(i->first, MTP::kStatsDcShift));
|
||||
i = _statsRequests.erase(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Api::Authorizations &ApiWrap::authorizations() {
|
||||
return *_authorizations;
|
||||
}
|
||||
|
||||
@@ -369,6 +369,9 @@ public:
|
||||
|
||||
void saveSelfBio(const QString &text);
|
||||
|
||||
void registerStatsRequest(MTP::DcId dcId, mtpRequestId id);
|
||||
void unregisterStatsRequest(MTP::DcId dcId, mtpRequestId id);
|
||||
|
||||
[[nodiscard]] Api::Authorizations &authorizations();
|
||||
[[nodiscard]] Api::AttachedStickers &attachedStickers();
|
||||
[[nodiscard]] Api::BlockedPeers &blockedPeers();
|
||||
@@ -547,6 +550,8 @@ private:
|
||||
not_null<ChannelData*> channel);
|
||||
void migrateFail(not_null<PeerData*> peer, const QString &error);
|
||||
|
||||
void checkStatsSessions();
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
base::flat_map<QString, int> _modifyRequests;
|
||||
@@ -683,6 +688,9 @@ private:
|
||||
QString requestedText;
|
||||
} _bio;
|
||||
|
||||
base::flat_map<MTP::DcId, base::flat_set<mtpRequestId>> _statsRequests;
|
||||
base::Timer _statsSessionKillTimer;
|
||||
|
||||
const std::unique_ptr<Api::Authorizations> _authorizations;
|
||||
const std::unique_ptr<Api::AttachedStickers> _attachedStickers;
|
||||
const std::unique_ptr<Api::BlockedPeers> _blockedPeers;
|
||||
|
||||
@@ -265,6 +265,10 @@ void AyuGramSettings::set_appIcon(QString val)
|
||||
appIcon = std::move(val);
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_simpleQuotesAndReplies(bool val) {
|
||||
simpleQuotesAndReplies = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_deletedMark(QString val)
|
||||
{
|
||||
deletedMark = std::move(val);
|
||||
@@ -282,11 +286,36 @@ void AyuGramSettings::set_recentStickersCount(int val)
|
||||
recentStickersCount = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_showLReadToggleInDrawer(bool val)
|
||||
{
|
||||
showLReadToggleInDrawer = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_showSReadToggleInDrawer(bool val)
|
||||
{
|
||||
showSReadToggleInDrawer = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_showGhostToggleInDrawer(bool val)
|
||||
{
|
||||
showGhostToggleInDrawer = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_showStreamerToggleInDrawer(bool val)
|
||||
{
|
||||
showStreamerToggleInDrawer = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_showGhostToggleInTray(bool val)
|
||||
{
|
||||
showGhostToggleInTray = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_showStreamerToggleInTray(bool val)
|
||||
{
|
||||
showStreamerToggleInTray = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_mainFont(QString val)
|
||||
{
|
||||
mainFont = val;
|
||||
|
||||
@@ -60,10 +60,19 @@ public:
|
||||
|
||||
// ~ Customization
|
||||
appIcon = DEFAULT_ICON;
|
||||
simpleQuotesAndReplies = true;
|
||||
deletedMark = "🧹";
|
||||
editedMark = tr::lng_edited(tr::now);
|
||||
recentStickersCount = 20;
|
||||
recentStickersCount = 50;
|
||||
|
||||
showLReadToggleInDrawer = true;
|
||||
showSReadToggleInDrawer = true;
|
||||
showGhostToggleInDrawer = true;
|
||||
showStreamerToggleInDrawer = false;
|
||||
|
||||
showGhostToggleInTray = true;
|
||||
showStreamerToggleInTray = false;
|
||||
|
||||
mainFont = "";
|
||||
monoFont = "";
|
||||
|
||||
@@ -100,10 +109,16 @@ public:
|
||||
bool localPremium;
|
||||
bool copyUsernameAsLink;
|
||||
QString appIcon;
|
||||
bool simpleQuotesAndReplies;
|
||||
QString deletedMark;
|
||||
QString editedMark;
|
||||
int recentStickersCount;
|
||||
bool showLReadToggleInDrawer;
|
||||
bool showSReadToggleInDrawer;
|
||||
bool showGhostToggleInDrawer;
|
||||
bool showStreamerToggleInDrawer;
|
||||
bool showGhostToggleInTray;
|
||||
bool showStreamerToggleInTray;
|
||||
QString mainFont;
|
||||
QString monoFont;
|
||||
int showPeerId;
|
||||
@@ -150,14 +165,26 @@ public:
|
||||
|
||||
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_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);
|
||||
@@ -194,10 +221,16 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(
|
||||
localPremium,
|
||||
copyUsernameAsLink,
|
||||
appIcon,
|
||||
simpleQuotesAndReplies,
|
||||
deletedMark,
|
||||
editedMark,
|
||||
recentStickersCount,
|
||||
showLReadToggleInDrawer,
|
||||
showSReadToggleInDrawer,
|
||||
showGhostToggleInDrawer,
|
||||
showStreamerToggleInDrawer,
|
||||
showGhostToggleInTray,
|
||||
showStreamerToggleInTray,
|
||||
mainFont,
|
||||
monoFont,
|
||||
showPeerId,
|
||||
|
||||
@@ -531,12 +531,6 @@ bool InnerWidget::elementUnderCursor(
|
||||
return (Element::Hovered() == view);
|
||||
}
|
||||
|
||||
float64 InnerWidget::elementHighlightOpacity(
|
||||
not_null<const HistoryItem *> item) const
|
||||
{
|
||||
return 0.;
|
||||
}
|
||||
|
||||
bool InnerWidget::elementInSelectionMode()
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -93,8 +93,6 @@ public:
|
||||
HistoryView::Context elementContext() override;
|
||||
bool elementUnderCursor(
|
||||
not_null<const HistoryView::Element *> view) override;
|
||||
[[nodiscard]] float64 elementHighlightOpacity(
|
||||
not_null<const HistoryItem *> item) const override;
|
||||
bool elementInSelectionMode() override;
|
||||
bool elementIntersectsRange(
|
||||
not_null<const HistoryView::Element *> view,
|
||||
|
||||
@@ -46,39 +46,22 @@
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_ayu_icons.h"
|
||||
|
||||
|
||||
class PainterHighQualityEnabler;
|
||||
|
||||
const char kStreamerMode[] =
|
||||
"streamer-mode";
|
||||
|
||||
base::options::toggle StreamerMode(
|
||||
{
|
||||
.id = kStreamerMode,
|
||||
.name = "Show streamer mode toggles",
|
||||
.description = "Streamer mode completely hides AyuGram windows and notifications from capture apps.",
|
||||
.scope = base::options::windows,
|
||||
.restartRequired = true
|
||||
}
|
||||
);
|
||||
|
||||
not_null<Ui::RpWidget *> AddInnerToggle(
|
||||
not_null<Ui::VerticalLayout *> container,
|
||||
const style::SettingsButton &st,
|
||||
std::vector<not_null<Ui::AbstractCheckView *>> innerCheckViews,
|
||||
not_null<Ui::SlideWrap<> *> wrap,
|
||||
rpl::producer<QString> buttonLabel,
|
||||
std::optional<QString> locked,
|
||||
bool toggledWhenAll,
|
||||
Settings::IconDescriptor &&icon)
|
||||
not_null<Ui::RpWidget *> AddInnerToggle(not_null<Ui::VerticalLayout *> container,
|
||||
const style::SettingsButton &st,
|
||||
std::vector<not_null<Ui::AbstractCheckView *>> innerCheckViews,
|
||||
not_null<Ui::SlideWrap<> *> wrap,
|
||||
rpl::producer<QString> buttonLabel,
|
||||
bool toggledWhenAll)
|
||||
{
|
||||
const auto button = container->add(object_ptr<Ui::SettingsButton>(
|
||||
container,
|
||||
nullptr,
|
||||
st));
|
||||
if (icon) {
|
||||
AddButtonIcon(button, st, std::move(icon));
|
||||
}
|
||||
st::settingsButtonNoIcon));
|
||||
|
||||
const auto toggleButton = Ui::CreateChild<Ui::SettingsButton>(
|
||||
container.get(),
|
||||
@@ -175,7 +158,7 @@ not_null<Ui::RpWidget *> AddInnerToggle(
|
||||
anim::type::normal);
|
||||
}
|
||||
}, toggleButton->lifetime());
|
||||
checkView->setLocked(locked.has_value());
|
||||
checkView->setLocked(false);
|
||||
checkView->finishAnimating();
|
||||
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
@@ -241,34 +224,23 @@ not_null<Ui::RpWidget *> AddInnerToggle(
|
||||
{ arrow->update(); },
|
||||
toggled ? 0. : 1.,
|
||||
toggled ? 1. : 0.,
|
||||
st::slideWrapDuration);
|
||||
st::slideWrapDuration,
|
||||
anim::easeOutCubic);
|
||||
}, button->lifetime());
|
||||
|
||||
const auto handleLocked = [=]
|
||||
{
|
||||
if (locked.has_value()) {
|
||||
Ui::Toast::Show(container, *locked);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
wrap->ease = anim::easeOutCubic;
|
||||
|
||||
button->clicks(
|
||||
) | start_with_next([=]
|
||||
{
|
||||
if (!handleLocked()) {
|
||||
wrap->toggle(!wrap->toggled(), anim::type::normal);
|
||||
}
|
||||
wrap->toggle(!wrap->toggled(), anim::type::normal);
|
||||
}, button->lifetime());
|
||||
|
||||
toggleButton->clicks(
|
||||
) | start_with_next([=]
|
||||
{
|
||||
if (!handleLocked()) {
|
||||
const auto checked = !checkView->checked();
|
||||
for (const auto &innerCheck : state->innerChecks) {
|
||||
innerCheck->setChecked(checked, anim::type::normal);
|
||||
}
|
||||
const auto checked = !checkView->checked();
|
||||
for (const auto &innerCheck : state->innerChecks) {
|
||||
innerCheck->setChecked(checked, anim::type::normal);
|
||||
}
|
||||
}, toggleButton->lifetime());
|
||||
|
||||
@@ -476,9 +448,7 @@ void Ayu::SetupGhostModeToggle(not_null<Ui::VerticalLayout *> container)
|
||||
innerChecks,
|
||||
raw,
|
||||
tr::ayu_GhostModeToggle(),
|
||||
std::nullopt,
|
||||
true,
|
||||
{});
|
||||
true);
|
||||
container->add(std::move(wrap));
|
||||
container->widthValue(
|
||||
) | start_with_next([=](int w)
|
||||
@@ -598,9 +568,7 @@ void Ayu::SetupReadAfterActionToggle(not_null<Ui::VerticalLayout *> container)
|
||||
innerChecks,
|
||||
raw,
|
||||
tr::ayu_MarkReadAfterAction(),
|
||||
std::nullopt,
|
||||
false,
|
||||
{});
|
||||
false);
|
||||
container->add(std::move(wrap));
|
||||
container->widthValue(
|
||||
) | start_with_next([=](int w)
|
||||
@@ -823,17 +791,17 @@ void Ayu::SetupCustomization(not_null<Ui::VerticalLayout *> container,
|
||||
|
||||
AddButton(
|
||||
container,
|
||||
tr::ayu_ShowGhostToggleInDrawer(),
|
||||
tr::ayu_SimpleQuotesAndReplies(),
|
||||
st::settingsButtonNoIcon
|
||||
)->toggleOn(
|
||||
rpl::single(settings->showGhostToggleInDrawer)
|
||||
rpl::single(settings->simpleQuotesAndReplies)
|
||||
)->toggledValue(
|
||||
) | rpl::filter([=](bool enabled)
|
||||
{
|
||||
return (enabled != settings->showGhostToggleInDrawer);
|
||||
return (enabled != settings->simpleQuotesAndReplies);
|
||||
}) | start_with_next([=](bool enabled)
|
||||
{
|
||||
settings->set_showGhostToggleInDrawer(enabled);
|
||||
settings->set_simpleQuotesAndReplies(enabled);
|
||||
AyuSettings::save();
|
||||
}, container->lifetime());
|
||||
|
||||
@@ -853,12 +821,134 @@ void Ayu::SetupCustomization(not_null<Ui::VerticalLayout *> container,
|
||||
AyuSettings::save();
|
||||
}, container->lifetime());
|
||||
|
||||
AddSkip(container);
|
||||
AddDivider(container);
|
||||
SetupDrawerElements(container);
|
||||
AddSkip(container);
|
||||
AddDivider(container);
|
||||
SetupTrayElements(container);
|
||||
AddSkip(container);
|
||||
AddDivider(container);
|
||||
AddSkip(container);
|
||||
SetupFonts(container, controller);
|
||||
}
|
||||
|
||||
void Ayu::SetupDrawerElements(not_null<Ui::VerticalLayout *> container)
|
||||
{
|
||||
auto settings = &AyuSettings::getInstance();
|
||||
|
||||
AddSkip(container);
|
||||
AddSubsectionTitle(container, tr::ayu_DrawerElementsHeader());
|
||||
|
||||
AddButton(
|
||||
container,
|
||||
tr::ayu_LReadMessages(),
|
||||
st::settingsButton,
|
||||
{&st::ayuLReadMenuIcon}
|
||||
)->toggleOn(
|
||||
rpl::single(settings->showLReadToggleInDrawer)
|
||||
)->toggledValue(
|
||||
) | rpl::filter([=](bool enabled)
|
||||
{
|
||||
return (enabled != settings->showLReadToggleInDrawer);
|
||||
}) | start_with_next([=](bool enabled)
|
||||
{
|
||||
settings->set_showLReadToggleInDrawer(enabled);
|
||||
AyuSettings::save();
|
||||
}, container->lifetime());
|
||||
|
||||
AddButton(
|
||||
container,
|
||||
tr::ayu_SReadMessages(),
|
||||
st::settingsButton,
|
||||
{&st::ayuSReadMenuIcon}
|
||||
)->toggleOn(
|
||||
rpl::single(settings->showSReadToggleInDrawer)
|
||||
)->toggledValue(
|
||||
) | rpl::filter([=](bool enabled)
|
||||
{
|
||||
return (enabled != settings->showSReadToggleInDrawer);
|
||||
}) | start_with_next([=](bool enabled)
|
||||
{
|
||||
settings->set_showSReadToggleInDrawer(enabled);
|
||||
AyuSettings::save();
|
||||
}, container->lifetime());
|
||||
|
||||
AddButton(
|
||||
container,
|
||||
tr::ayu_GhostModeToggle(),
|
||||
st::settingsButton,
|
||||
{&st::ayuGhostIcon}
|
||||
)->toggleOn(
|
||||
rpl::single(settings->showGhostToggleInDrawer)
|
||||
)->toggledValue(
|
||||
) | rpl::filter([=](bool enabled)
|
||||
{
|
||||
return (enabled != settings->showGhostToggleInDrawer);
|
||||
}) | start_with_next([=](bool enabled)
|
||||
{
|
||||
settings->set_showGhostToggleInDrawer(enabled);
|
||||
AyuSettings::save();
|
||||
}, container->lifetime());
|
||||
|
||||
AddButton(
|
||||
container,
|
||||
tr::ayu_StreamerModeToggle(),
|
||||
st::settingsButton,
|
||||
{&st::ayuStreamerModeMenuIcon}
|
||||
)->toggleOn(
|
||||
rpl::single(settings->showStreamerToggleInDrawer)
|
||||
)->toggledValue(
|
||||
) | rpl::filter([=](bool enabled)
|
||||
{
|
||||
return (enabled != settings->showStreamerToggleInDrawer);
|
||||
}) | start_with_next([=](bool enabled)
|
||||
{
|
||||
settings->set_showStreamerToggleInDrawer(enabled);
|
||||
AyuSettings::save();
|
||||
}, container->lifetime());
|
||||
}
|
||||
|
||||
void Ayu::SetupTrayElements(not_null<Ui::VerticalLayout *> container)
|
||||
{
|
||||
auto settings = &AyuSettings::getInstance();
|
||||
|
||||
AddSkip(container);
|
||||
AddSubsectionTitle(container, tr::ayu_TrayElementsHeader());
|
||||
|
||||
AddButton(
|
||||
container,
|
||||
tr::ayu_EnableGhostModeTray(),
|
||||
st::settingsButtonNoIcon
|
||||
)->toggleOn(
|
||||
rpl::single(settings->showGhostToggleInTray)
|
||||
)->toggledValue(
|
||||
) | rpl::filter([=](bool enabled)
|
||||
{
|
||||
return (enabled != settings->showGhostToggleInTray);
|
||||
}) | start_with_next([=](bool enabled)
|
||||
{
|
||||
settings->set_showGhostToggleInTray(enabled);
|
||||
AyuSettings::save();
|
||||
}, container->lifetime());
|
||||
|
||||
AddButton(
|
||||
container,
|
||||
tr::ayu_EnableStreamerModeTray(),
|
||||
st::settingsButtonNoIcon
|
||||
)->toggleOn(
|
||||
rpl::single(settings->showStreamerToggleInTray)
|
||||
)->toggledValue(
|
||||
) | rpl::filter([=](bool enabled)
|
||||
{
|
||||
return (enabled != settings->showStreamerToggleInTray);
|
||||
}) | start_with_next([=](bool enabled)
|
||||
{
|
||||
settings->set_showStreamerToggleInTray(enabled);
|
||||
AyuSettings::save();
|
||||
}, container->lifetime());
|
||||
}
|
||||
|
||||
void Ayu::SetupShowPeerId(not_null<Ui::VerticalLayout *> container,
|
||||
not_null<Window::SessionController *> controller)
|
||||
{
|
||||
@@ -1062,13 +1152,6 @@ void Ayu::SetupSendConfirmations(not_null<Ui::VerticalLayout *> container)
|
||||
}, container->lifetime());
|
||||
}
|
||||
|
||||
void Ayu::SetupExperimental(not_null<Ui::VerticalLayout *> container,
|
||||
not_null<Window::SessionController *> controller)
|
||||
{
|
||||
AddSubsectionTitle(container, tr::lng_settings_experimental());
|
||||
AddPlatformOption(controller, container, StreamerMode, rpl::producer<>());
|
||||
}
|
||||
|
||||
void Ayu::SetupAyuGramSettings(not_null<Ui::VerticalLayout *> container,
|
||||
not_null<Window::SessionController *> controller)
|
||||
{
|
||||
@@ -1107,10 +1190,7 @@ void Ayu::SetupAyuGramSettings(not_null<Ui::VerticalLayout *> container,
|
||||
AddSkip(container);
|
||||
SetupSendConfirmations(container);
|
||||
AddSkip(container);
|
||||
|
||||
AddDivider(container);
|
||||
AddSkip(container);
|
||||
SetupExperimental(container, controller);
|
||||
|
||||
AddDividerText(container, tr::ayu_SettingsWatermark());
|
||||
}
|
||||
|
||||
@@ -17,10 +17,6 @@ class Controller;
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
extern const char kStreamerMode[];
|
||||
|
||||
extern base::options::toggle StreamerMode;
|
||||
|
||||
namespace Settings
|
||||
{
|
||||
|
||||
@@ -53,6 +49,10 @@ private:
|
||||
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);
|
||||
@@ -63,9 +63,6 @@ private:
|
||||
|
||||
void SetupSendConfirmations(not_null<Ui::VerticalLayout *> container);
|
||||
|
||||
void SetupExperimental(not_null<Ui::VerticalLayout *> container,
|
||||
not_null<Window::SessionController *> controller);
|
||||
|
||||
void SetupAyuGramSettings(not_null<Ui::VerticalLayout *> container, not_null<Window::SessionController *> null);
|
||||
|
||||
void setupContent(not_null<Window::SessionController *> controller);
|
||||
|
||||
@@ -542,8 +542,8 @@ rightsToggle: Toggle(defaultToggle) {
|
||||
toggledFg: windowBgActive;
|
||||
untoggledFg: attentionButtonFg;
|
||||
lockIcon: icon {{ "info/info_rights_lock", windowBgActive }};
|
||||
xsize: 8px;
|
||||
vsize: 5px;
|
||||
xsize: 0px;
|
||||
vsize: 0px;
|
||||
vshift: 1px;
|
||||
stroke: 2px;
|
||||
duration: 120;
|
||||
|
||||
@@ -362,7 +362,7 @@ void EditFilterChatsListController::rowClicked(not_null<PeerListRow*> row) {
|
||||
delegate()->peerListSetRowChecked(row, !row->checked());
|
||||
updateTitle();
|
||||
} else {
|
||||
delegate()->peerListShowBox(_limitBox(count));
|
||||
delegate()->peerListUiShow()->showBox(_limitBox(count));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,8 +37,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_menu_icons.h"
|
||||
#include "styles/style_settings.h"
|
||||
|
||||
#include <xxhash.h>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kMaxLinkTitleLength = 32;
|
||||
@@ -223,14 +221,6 @@ private:
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] uint64 ComputeRowId(const QString &link) {
|
||||
return XXH64(link.data(), link.size() * sizeof(ushort), 0);
|
||||
}
|
||||
|
||||
[[nodiscard]] uint64 ComputeRowId(const InviteLinkData &data) {
|
||||
return ComputeRowId(data.url);
|
||||
}
|
||||
|
||||
[[nodiscard]] Color ComputeColor(const InviteLinkData &link) {
|
||||
return Color::Permanent;
|
||||
}
|
||||
@@ -242,7 +232,7 @@ private:
|
||||
LinkRow::LinkRow(
|
||||
not_null<LinkRowDelegate*> delegate,
|
||||
const InviteLinkData &data)
|
||||
: PeerListRow(ComputeRowId(data))
|
||||
: PeerListRow(UniqueRowIdFromString(data.url))
|
||||
, _delegate(delegate)
|
||||
, _data(data)
|
||||
, _color(ComputeColor(data)) {
|
||||
@@ -585,19 +575,19 @@ void LinkController::addLinkBlock(not_null<Ui::VerticalLayout*> container) {
|
||||
CopyInviteLink(delegate()->peerListUiShow(), link);
|
||||
});
|
||||
const auto shareLink = crl::guard(weak, [=] {
|
||||
delegate()->peerListShowBox(
|
||||
delegate()->peerListUiShow()->showBox(
|
||||
ShareInviteLinkBox(&_window->session(), link));
|
||||
});
|
||||
const auto getLinkQr = crl::guard(weak, [=] {
|
||||
delegate()->peerListShowBox(
|
||||
delegate()->peerListUiShow()->showBox(
|
||||
InviteLinkQrBox(link, tr::lng_filters_link_qr_about()));
|
||||
});
|
||||
const auto editLink = crl::guard(weak, [=] {
|
||||
delegate()->peerListShowBox(
|
||||
delegate()->peerListUiShow()->showBox(
|
||||
Box(ChatFilterLinkBox, &_window->session(), _data));
|
||||
});
|
||||
const auto deleteLink = crl::guard(weak, [=] {
|
||||
delegate()->peerListShowBox(DeleteLinkBox(_window, _data));
|
||||
delegate()->peerListUiShow()->showBox(DeleteLinkBox(_window, _data));
|
||||
});
|
||||
|
||||
const auto createMenu = [=] {
|
||||
@@ -856,7 +846,7 @@ void LinksController::rebuild(const std::vector<InviteLinkData> &rows) {
|
||||
|
||||
void LinksController::rowClicked(not_null<PeerListRow*> row) {
|
||||
const auto link = static_cast<LinkRow*>(row.get())->data();
|
||||
delegate()->peerListShowBox(
|
||||
delegate()->peerListUiShow()->showBox(
|
||||
ShowLinkBox(_window, _currentFilter(), link));
|
||||
}
|
||||
|
||||
@@ -891,19 +881,19 @@ base::unique_qptr<Ui::PopupMenu> LinksController::createRowContextMenu(
|
||||
CopyInviteLink(delegate()->peerListUiShow(), link);
|
||||
};
|
||||
const auto shareLink = [=] {
|
||||
delegate()->peerListShowBox(
|
||||
delegate()->peerListUiShow()->showBox(
|
||||
ShareInviteLinkBox(&_window->session(), link));
|
||||
};
|
||||
const auto getLinkQr = [=] {
|
||||
delegate()->peerListShowBox(
|
||||
delegate()->peerListUiShow()->showBox(
|
||||
InviteLinkQrBox(link, tr::lng_filters_link_qr_about()));
|
||||
};
|
||||
const auto editLink = [=] {
|
||||
delegate()->peerListShowBox(
|
||||
delegate()->peerListUiShow()->showBox(
|
||||
Box(ChatFilterLinkBox, &_window->session(), data));
|
||||
};
|
||||
const auto deleteLink = [=] {
|
||||
delegate()->peerListShowBox(DeleteLinkBox(_window, data));
|
||||
delegate()->peerListUiShow()->showBox(DeleteLinkBox(_window, data));
|
||||
};
|
||||
auto result = base::make_unique_q<Ui::PopupMenu>(
|
||||
parent,
|
||||
|
||||
@@ -7,12 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/gift_premium_box.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_premium.h"
|
||||
#include "api/api_premium_option.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "base/weak_ptr.h"
|
||||
#include "boxes/peers/prepare_short_info_box.h"
|
||||
#include "data/data_boosts.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_media_types.h" // Data::Giveaway
|
||||
@@ -30,8 +31,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/effects/premium_stars_colored.h"
|
||||
#include "ui/effects/premium_top_bar.h"
|
||||
#include "ui/effects/spoiler_mess.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/format_values.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/gradient_round_button.h"
|
||||
@@ -40,9 +42,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "window/window_peer_menu.h" // ShowChooseRecipientBox.
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_giveaway.h"
|
||||
#include "styles/style_info.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_premium.h"
|
||||
|
||||
#include <QtGui/QGuiApplication>
|
||||
@@ -93,7 +95,7 @@ void GiftBox(
|
||||
+ st::defaultUserpicButton.size.height()));
|
||||
|
||||
using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
|
||||
const auto stars = box->lifetime().make_state<ColoredMiniStars>(top);
|
||||
const auto stars = box->lifetime().make_state<ColoredMiniStars>(top, true);
|
||||
|
||||
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
|
||||
top,
|
||||
@@ -237,11 +239,7 @@ void GiftBox(
|
||||
}, box->lifetime());
|
||||
}
|
||||
|
||||
struct GiftCodeLink {
|
||||
QString text;
|
||||
QString link;
|
||||
};
|
||||
[[nodiscard]] GiftCodeLink MakeGiftCodeLink(
|
||||
[[nodiscard]] Data::GiftCodeLink MakeGiftCodeLink(
|
||||
not_null<Main::Session*> session,
|
||||
const QString &slug) {
|
||||
const auto path = u"giftcode/"_q + slug;
|
||||
@@ -281,7 +279,7 @@ struct GiftCodeLink {
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> MakePeerTableValue(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
PeerId id) {
|
||||
auto result = object_ptr<Ui::AbstractButton>(parent);
|
||||
const auto raw = result.data();
|
||||
@@ -309,7 +307,7 @@ struct GiftCodeLink {
|
||||
label->setTextColorOverride(st::windowActiveTextFg->c);
|
||||
|
||||
raw->setClickedCallback([=] {
|
||||
controller->show(PrepareShortInfoBox(peer, controller));
|
||||
controller->uiShow()->showBox(PrepareShortInfoBox(peer, controller));
|
||||
});
|
||||
|
||||
return result;
|
||||
@@ -350,7 +348,7 @@ not_null<Ui::FlatLabel*> AddTableRow(
|
||||
void AddTableRow(
|
||||
not_null<Ui::TableLayout*> table,
|
||||
rpl::producer<QString> label,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
PeerId id) {
|
||||
if (!id) {
|
||||
return;
|
||||
@@ -362,6 +360,73 @@ void AddTableRow(
|
||||
st::giveawayGiftCodePeerMargin);
|
||||
}
|
||||
|
||||
void AddTable(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
const Api::GiftCode ¤t,
|
||||
bool skipReason) {
|
||||
auto table = container->add(
|
||||
object_ptr<Ui::TableLayout>(
|
||||
container,
|
||||
st::giveawayGiftCodeTable),
|
||||
st::giveawayGiftCodeTableMargin);
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_from(),
|
||||
controller,
|
||||
current.from);
|
||||
if (current.to) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_to(),
|
||||
controller,
|
||||
current.to);
|
||||
} else {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_to(),
|
||||
tr::lng_gift_link_label_to_unclaimed(Ui::Text::WithEntities));
|
||||
}
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_gift(),
|
||||
tr::lng_gift_link_gift_premium(
|
||||
lt_duration,
|
||||
GiftDurationValue(current.months) | Ui::Text::ToWithEntities(),
|
||||
Ui::Text::WithEntities));
|
||||
if (!skipReason) {
|
||||
const auto reason = AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_reason(),
|
||||
(current.giveawayId
|
||||
? ((current.to
|
||||
? tr::lng_gift_link_reason_giveaway
|
||||
: tr::lng_gift_link_reason_unclaimed)(
|
||||
) | Ui::Text::ToLink())
|
||||
: current.giveaway
|
||||
? ((current.to
|
||||
? tr::lng_gift_link_reason_giveaway
|
||||
: tr::lng_gift_link_reason_unclaimed)(
|
||||
Ui::Text::WithEntities
|
||||
) | rpl::type_erased())
|
||||
: tr::lng_gift_link_reason_chosen(Ui::Text::WithEntities)));
|
||||
reason->setClickHandlerFilter([=](const auto &...) {
|
||||
controller->showPeerHistory(
|
||||
current.from,
|
||||
Window::SectionShow::Way::Forward,
|
||||
current.giveawayId);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
if (current.date) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_date(),
|
||||
rpl::single(Ui::Text::WithEntities(
|
||||
langDateTime(base::unixtime::parse(current.date)))));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
GiftPremiumValidator::GiftPremiumValidator(
|
||||
@@ -416,7 +481,7 @@ QString GiftDuration(int months) {
|
||||
|
||||
void GiftCodeBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
const QString &slug) {
|
||||
struct State {
|
||||
rpl::variable<Api::GiftCode> data;
|
||||
@@ -466,65 +531,7 @@ void GiftCodeBox(
|
||||
MakeLinkCopyIcon(box)),
|
||||
st::giveawayGiftCodeLinkMargin);
|
||||
|
||||
auto table = box->addRow(
|
||||
object_ptr<Ui::TableLayout>(
|
||||
box,
|
||||
st::giveawayGiftCodeTable),
|
||||
st::giveawayGiftCodeTableMargin);
|
||||
const auto current = state->data.current();
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_from(),
|
||||
controller,
|
||||
current.from);
|
||||
if (current.to) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_to(),
|
||||
controller,
|
||||
current.to);
|
||||
} else {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_to(),
|
||||
tr::lng_gift_link_label_to_unclaimed(Ui::Text::WithEntities));
|
||||
}
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_gift(),
|
||||
tr::lng_gift_link_gift_premium(
|
||||
lt_duration,
|
||||
GiftDurationValue(current.months) | Ui::Text::ToWithEntities(),
|
||||
Ui::Text::WithEntities));
|
||||
const auto reason = AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_reason(),
|
||||
(current.giveawayId
|
||||
? ((current.to
|
||||
? tr::lng_gift_link_reason_giveaway
|
||||
: tr::lng_gift_link_reason_unclaimed)(
|
||||
) | Ui::Text::ToLink())
|
||||
: current.giveaway
|
||||
? ((current.to
|
||||
? tr::lng_gift_link_reason_giveaway
|
||||
: tr::lng_gift_link_reason_unclaimed)(
|
||||
Ui::Text::WithEntities
|
||||
) | rpl::type_erased())
|
||||
: tr::lng_gift_link_reason_chosen(Ui::Text::WithEntities)));
|
||||
reason->setClickHandlerFilter([=](const auto &...) {
|
||||
controller->showPeerHistory(
|
||||
current.from,
|
||||
Window::SectionShow::Way::Forward,
|
||||
current.giveawayId);
|
||||
return false;
|
||||
});
|
||||
if (current.date) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_date(),
|
||||
rpl::single(Ui::Text::WithEntities(
|
||||
langDateTime(base::unixtime::parse(current.date)))));
|
||||
}
|
||||
AddTable(box->verticalLayout(), controller, state->data.current(), false);
|
||||
|
||||
auto shareLink = tr::lng_gift_link_also_send_link(
|
||||
) | rpl::map([](const QString &text) {
|
||||
@@ -552,7 +559,7 @@ void GiftCodeBox(
|
||||
st::giveawayGiftCodeFooterMargin);
|
||||
footer->setClickHandlerFilter([=](const auto &...) {
|
||||
const auto chosen = [=](not_null<Data::Thread*> thread) {
|
||||
const auto content = controller->content();
|
||||
const auto content = controller->parentController()->content();
|
||||
return content->shareUrl(
|
||||
thread,
|
||||
MakeGiftCodeLink(&controller->session(), slug).link,
|
||||
@@ -607,14 +614,121 @@ void GiftCodeBox(
|
||||
}, button->lifetime());
|
||||
}
|
||||
|
||||
|
||||
void GiftCodePendingBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
const Api::GiftCode &data) {
|
||||
box->setWidth(st::boxWideWidth);
|
||||
box->setStyle(st::giveawayGiftCodeBox);
|
||||
box->setNoContentMargin(true);
|
||||
|
||||
{
|
||||
const auto peerTo = controller->session().data().peer(data.to);
|
||||
const auto clickContext = [=, weak = base::make_weak(controller)] {
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->uiShow()->showBox(
|
||||
PrepareShortInfoBox(peerTo, strong));
|
||||
}
|
||||
return QVariant();
|
||||
};
|
||||
const auto &st = st::giveawayGiftCodeCover;
|
||||
const auto resultToName = st.about.style.font->elided(
|
||||
peerTo->shortName(),
|
||||
st.about.minWidth / 2,
|
||||
Qt::ElideMiddle);
|
||||
const auto bar = box->setPinnedToTopContent(
|
||||
object_ptr<Ui::Premium::TopBar>(
|
||||
box,
|
||||
st,
|
||||
clickContext,
|
||||
tr::lng_gift_link_title(),
|
||||
tr::lng_gift_link_pending_about(
|
||||
lt_user,
|
||||
rpl::single(Ui::Text::Link(resultToName)),
|
||||
Ui::Text::RichLangValue),
|
||||
true));
|
||||
|
||||
const auto max = st::giveawayGiftCodeTopHeight;
|
||||
bar->setMaximumHeight(max);
|
||||
bar->setMinimumHeight(st::infoLayerTopBarHeight);
|
||||
|
||||
bar->resize(bar->width(), bar->maximumHeight());
|
||||
}
|
||||
|
||||
{
|
||||
const auto linkLabel = box->addRow(
|
||||
Ui::MakeLinkLabel(box, nullptr, nullptr, nullptr, nullptr),
|
||||
st::giveawayGiftCodeLinkMargin);
|
||||
const auto spoiler = Ui::CreateChild<Ui::AbstractButton>(linkLabel);
|
||||
spoiler->lifetime().make_state<Ui::Animations::Basic>([=] {
|
||||
spoiler->update();
|
||||
})->start();
|
||||
linkLabel->sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &s) {
|
||||
spoiler->setGeometry(Rect(s));
|
||||
}, spoiler->lifetime());
|
||||
const auto spoilerCached = Ui::SpoilerMessCached(
|
||||
Ui::DefaultTextSpoilerMask(),
|
||||
st::giveawayGiftCodeLink.textFg->c);
|
||||
const auto textHeight = st::giveawayGiftCodeLink.style.font->height;
|
||||
spoiler->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
auto p = QPainter(spoiler);
|
||||
const auto rect = spoiler->rect();
|
||||
const auto r = rect
|
||||
- QMargins(
|
||||
st::boxRowPadding.left(),
|
||||
(rect.height() - textHeight) / 2,
|
||||
st::boxRowPadding.right(),
|
||||
(rect.height() - textHeight) / 2);
|
||||
Ui::FillSpoilerRect(p, r, spoilerCached.frame());
|
||||
}, spoiler->lifetime());
|
||||
spoiler->setClickedCallback([show = box->uiShow()] {
|
||||
show->showToast(tr::lng_gift_link_pending_toast(tr::now));
|
||||
});
|
||||
spoiler->show();
|
||||
}
|
||||
|
||||
AddTable(box->verticalLayout(), controller, data, true);
|
||||
|
||||
box->addRow(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_gift_link_pending_footer(),
|
||||
st::giveawayGiftCodeFooter),
|
||||
st::giveawayGiftCodeFooterMargin);
|
||||
|
||||
const auto close = Ui::CreateChild<Ui::IconButton>(
|
||||
box.get(),
|
||||
st::boxTitleClose);
|
||||
const auto closeCallback = [=] { box->closeBox(); };
|
||||
close->setClickedCallback(closeCallback);
|
||||
box->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
close->moveToRight(0, 0);
|
||||
}, box->lifetime());
|
||||
|
||||
const auto button = box->addButton(tr::lng_close(), closeCallback);
|
||||
const auto buttonPadding = st::giveawayGiftCodeBox.buttonPadding;
|
||||
const auto buttonWidth = st::boxWideWidth
|
||||
- buttonPadding.left()
|
||||
- buttonPadding.right();
|
||||
button->widthValue() | rpl::filter([=] {
|
||||
return (button->widthNoMargins() != buttonWidth);
|
||||
}) | rpl::start_with_next([=] {
|
||||
button->resizeToWidth(buttonWidth);
|
||||
}, button->lifetime());
|
||||
}
|
||||
|
||||
void ResolveGiftCode(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
const QString &slug) {
|
||||
const auto done = [=](Api::GiftCode code) {
|
||||
if (!code) {
|
||||
controller->showToast(tr::lng_gift_link_expired(tr::now));
|
||||
} else {
|
||||
controller->show(Box(GiftCodeBox, controller, slug));
|
||||
controller->uiShow()->showBox(Box(GiftCodeBox, controller, slug));
|
||||
}
|
||||
};
|
||||
controller->session().api().premium().checkGiftCode(
|
||||
@@ -624,7 +738,7 @@ void ResolveGiftCode(
|
||||
|
||||
void GiveawayInfoBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
Data::Giveaway giveaway,
|
||||
Api::GiveawayInfo info) {
|
||||
using State = Api::GiveawayState;
|
||||
@@ -693,7 +807,8 @@ void GiveawayInfoBox(
|
||||
text.append(' ').append(tr::lng_prizes_end_activated(
|
||||
tr::now,
|
||||
lt_count,
|
||||
info.activatedCount));
|
||||
info.activatedCount,
|
||||
Ui::Text::RichLangValue));
|
||||
}
|
||||
if (!info.giftCode.isEmpty()) {
|
||||
text.append("\n\n");
|
||||
@@ -784,7 +899,7 @@ void GiveawayInfoBox(
|
||||
}
|
||||
|
||||
void ResolveGiveawayInfo(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<PeerData*> peer,
|
||||
MsgId messageId,
|
||||
Data::Giveaway giveaway) {
|
||||
@@ -793,7 +908,7 @@ void ResolveGiveawayInfo(
|
||||
controller->showToast(
|
||||
tr::lng_confirm_phone_link_invalid(tr::now));
|
||||
} else {
|
||||
controller->show(
|
||||
controller->uiShow()->showBox(
|
||||
Box(GiveawayInfoBox, controller, giveaway, info));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -25,6 +25,7 @@ class GenericBox;
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
class SessionNavigation;
|
||||
} // namespace Window
|
||||
|
||||
class GiftPremiumValidator final {
|
||||
@@ -47,14 +48,18 @@ private:
|
||||
|
||||
void GiftCodeBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
const QString &slug);
|
||||
void GiftCodePendingBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
const Api::GiftCode &data);
|
||||
void ResolveGiftCode(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
const QString &slug);
|
||||
|
||||
void ResolveGiveawayInfo(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<PeerData*> peer,
|
||||
MsgId messageId,
|
||||
Data::Giveaway giveaway);
|
||||
|
||||
@@ -567,12 +567,10 @@ void PasscodeBox::validateEmail(
|
||||
} else if (error.type() == u"EMAIL_HASH_EXPIRED"_q) {
|
||||
const auto weak = Ui::MakeWeak(this);
|
||||
_clearUnconfirmedPassword.fire({});
|
||||
if (weak) {
|
||||
auto box = Ui::MakeInformBox({
|
||||
Lang::Hard::EmailConfirmationExpired()
|
||||
});
|
||||
weak->getDelegate()->show(
|
||||
std::move(box),
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->getDelegate()->show(
|
||||
Ui::MakeInformBox(
|
||||
Lang::Hard::EmailConfirmationExpired()),
|
||||
Ui::LayerOption::CloseOther);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -35,6 +35,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_dialogs.h"
|
||||
#include "styles/style_widgets.h"
|
||||
|
||||
#include <xxhash.h> // XXH64.
|
||||
|
||||
[[nodiscard]] PeerListRowId UniqueRowIdFromString(const QString &d) {
|
||||
return XXH64(d.data(), d.size() * sizeof(ushort), 0);
|
||||
}
|
||||
|
||||
PaintRoundImageCallback PaintUserpicCallback(
|
||||
not_null<PeerData*> peer,
|
||||
bool respectSavedMessagesChat) {
|
||||
@@ -79,16 +85,6 @@ PeerListContentDelegateShow::PeerListContentDelegateShow(
|
||||
: _show(show) {
|
||||
}
|
||||
|
||||
void PeerListContentDelegateShow::peerListShowBox(
|
||||
object_ptr<Ui::BoxContent> content,
|
||||
Ui::LayerOptions options) {
|
||||
_show->showBox(std::move(content), options);
|
||||
}
|
||||
|
||||
void PeerListContentDelegateShow::peerListHideLayer() {
|
||||
_show->hideLayer();
|
||||
}
|
||||
|
||||
auto PeerListContentDelegateShow::peerListUiShow()
|
||||
-> std::shared_ptr<Main::SessionShow>{
|
||||
return _show;
|
||||
@@ -318,16 +314,6 @@ void PeerListBox::peerListSetSearchMode(PeerListSearchMode mode) {
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListBox::peerListShowBox(
|
||||
object_ptr<Ui::BoxContent> content,
|
||||
Ui::LayerOptions options) {
|
||||
_show->showBox(std::move(content), options);
|
||||
}
|
||||
|
||||
void PeerListBox::peerListHideLayer() {
|
||||
_show->hideLayer();
|
||||
}
|
||||
|
||||
std::shared_ptr<Main::SessionShow> PeerListBox::peerListUiShow() {
|
||||
return _show;
|
||||
}
|
||||
@@ -760,7 +746,7 @@ int PeerListRow::paintNameIconGetWidth(
|
||||
nameWidth,
|
||||
outerWidth,
|
||||
{
|
||||
.peer = _peer,
|
||||
.peer = peer(),
|
||||
.verified = &(selected
|
||||
? st::dialogsVerifiedIconOver
|
||||
: st::dialogsVerifiedIcon),
|
||||
@@ -1696,9 +1682,19 @@ crl::time PeerListContent::paintRow(
|
||||
return refreshStatusIn;
|
||||
}
|
||||
|
||||
const auto opacity = row->opacity();
|
||||
const auto &bg = selected
|
||||
? _st.item.button.textBgOver
|
||||
: _st.item.button.textBg;
|
||||
if (opacity < 1.) {
|
||||
p.setOpacity(opacity);
|
||||
}
|
||||
const auto guard = gsl::finally([&] {
|
||||
if (opacity < 1.) {
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
});
|
||||
|
||||
p.fillRect(0, 0, outerWidth, _rowHeight, bg);
|
||||
row->paintRipple(p, 0, 0, outerWidth);
|
||||
row->paintUserpic(
|
||||
|
||||
@@ -54,6 +54,8 @@ using PaintRoundImageCallback = Fn<void(
|
||||
|
||||
using PeerListRowId = uint64;
|
||||
|
||||
[[nodiscard]] PeerListRowId UniqueRowIdFromString(const QString &d);
|
||||
|
||||
class PeerListRow {
|
||||
public:
|
||||
enum class State {
|
||||
@@ -139,6 +141,9 @@ public:
|
||||
}
|
||||
virtual void rightActionStopLastRipple() {
|
||||
}
|
||||
[[nodiscard]] virtual float64 opacity() {
|
||||
return 1.;
|
||||
}
|
||||
|
||||
// By default elements code falls back to a simple right action code.
|
||||
virtual int elementsCount() const;
|
||||
@@ -330,10 +335,6 @@ public:
|
||||
virtual std::optional<QPoint> peerListLastRowMousePosition() = 0;
|
||||
virtual void peerListSortRows(Fn<bool(const PeerListRow &a, const PeerListRow &b)> compare) = 0;
|
||||
virtual int peerListPartitionRows(Fn<bool(const PeerListRow &a)> border) = 0;
|
||||
virtual void peerListShowBox(
|
||||
object_ptr<Ui::BoxContent> content,
|
||||
Ui::LayerOptions options = Ui::LayerOption::KeepOther) = 0;
|
||||
virtual void peerListHideLayer() = 0;
|
||||
virtual std::shared_ptr<Main::SessionShow> peerListUiShow() = 0;
|
||||
|
||||
template <typename PeerDataRange>
|
||||
@@ -1005,14 +1006,6 @@ public:
|
||||
object_ptr<Ui::FlatLabel> description) override {
|
||||
description.destroy();
|
||||
}
|
||||
void peerListShowBox(
|
||||
object_ptr<Ui::BoxContent> content,
|
||||
Ui::LayerOptions options = Ui::LayerOption::KeepOther) override {
|
||||
Unexpected("...DelegateSimple::peerListShowBox");
|
||||
}
|
||||
void peerListHideLayer() override {
|
||||
Unexpected("...DelegateSimple::peerListHideLayer");
|
||||
}
|
||||
std::shared_ptr<Main::SessionShow> peerListUiShow() override {
|
||||
Unexpected("...DelegateSimple::peerListUiShow");
|
||||
}
|
||||
@@ -1023,10 +1016,6 @@ class PeerListContentDelegateShow : public PeerListContentDelegateSimple {
|
||||
public:
|
||||
explicit PeerListContentDelegateShow(
|
||||
std::shared_ptr<Main::SessionShow> show);
|
||||
void peerListShowBox(
|
||||
object_ptr<Ui::BoxContent> content,
|
||||
Ui::LayerOptions options = Ui::LayerOption::KeepOther) override;
|
||||
void peerListHideLayer() override;
|
||||
std::shared_ptr<Main::SessionShow> peerListUiShow() override;
|
||||
|
||||
private:
|
||||
@@ -1062,10 +1051,6 @@ public:
|
||||
bool peerListIsRowChecked(not_null<PeerListRow*> row) override;
|
||||
int peerListSelectedRowsCount() override;
|
||||
void peerListScrollToTop() override;
|
||||
void peerListShowBox(
|
||||
object_ptr<Ui::BoxContent> content,
|
||||
Ui::LayerOptions options = Ui::LayerOption::KeepOther) override;
|
||||
void peerListHideLayer() override;
|
||||
std::shared_ptr<Main::SessionShow> peerListUiShow() override;
|
||||
|
||||
void setAddedTopScrollSkip(int skip);
|
||||
|
||||
@@ -685,7 +685,7 @@ void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||
}, box->lifetime());
|
||||
});
|
||||
*weak = owned.data();
|
||||
delegate()->peerListShowBox(std::move(owned));
|
||||
delegate()->peerListUiShow()->showBox(std::move(owned));
|
||||
return;
|
||||
}
|
||||
const auto history = peer->owner().history(peer);
|
||||
|
||||
@@ -372,16 +372,6 @@ void PeerListsBox::Delegate::peerListFinishSelectedRowsBunch() {
|
||||
_box->_select->entity()->finishItemsBunch();
|
||||
}
|
||||
|
||||
void PeerListsBox::Delegate::peerListShowBox(
|
||||
object_ptr<Ui::BoxContent> content,
|
||||
Ui::LayerOptions options) {
|
||||
_show->showBox(std::move(content), options);
|
||||
}
|
||||
|
||||
void PeerListsBox::Delegate::peerListHideLayer() {
|
||||
_show->hideLayer();
|
||||
}
|
||||
|
||||
auto PeerListsBox::Delegate::peerListUiShow()
|
||||
-> std::shared_ptr<Main::SessionShow> {
|
||||
return _show;
|
||||
|
||||
@@ -54,10 +54,6 @@ private:
|
||||
_box->addSelectItem(row, anim::type::instant);
|
||||
}
|
||||
void peerListFinishSelectedRowsBunch() override;
|
||||
void peerListShowBox(
|
||||
object_ptr<Ui::BoxContent> content,
|
||||
Ui::LayerOptions options = Ui::LayerOption::KeepOther) override;
|
||||
void peerListHideLayer() override;
|
||||
std::shared_ptr<Main::SessionShow> peerListUiShow() override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -366,7 +366,7 @@ bool AddParticipantsBoxController::needsInviteLinkButton() {
|
||||
QPointer<Ui::BoxContent> AddParticipantsBoxController::showBox(
|
||||
object_ptr<Ui::BoxContent> box) const {
|
||||
const auto weak = Ui::MakeWeak(box.data());
|
||||
delegate()->peerListShowBox(std::move(box));
|
||||
delegate()->peerListUiShow()->showBox(std::move(box));
|
||||
return weak;
|
||||
}
|
||||
|
||||
@@ -668,7 +668,7 @@ void AddSpecialBoxController::migrate(
|
||||
QPointer<Ui::BoxContent> AddSpecialBoxController::showBox(
|
||||
object_ptr<Ui::BoxContent> box) const {
|
||||
const auto weak = Ui::MakeWeak(box.data());
|
||||
delegate()->peerListShowBox(std::move(box));
|
||||
delegate()->peerListUiShow()->showBox(std::move(box));
|
||||
return weak;
|
||||
}
|
||||
|
||||
|
||||
@@ -433,7 +433,7 @@ void ChoosePeerBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||
if (const auto user = peer->asUser()) {
|
||||
done();
|
||||
} else {
|
||||
delegate()->peerListShowBox(
|
||||
delegate()->peerListUiShow()->showBox(
|
||||
MakeConfirmBox(_bot, peer, _query, done));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ public:
|
||||
rpl::producer<DefaultIcon> value,
|
||||
Fn<void()> repaint);
|
||||
|
||||
int width() override;
|
||||
QString entityData() override;
|
||||
|
||||
void paint(QPainter &p, const Context &context) override;
|
||||
@@ -80,6 +81,10 @@ DefaultIconEmoji::DefaultIconEmoji(
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
int DefaultIconEmoji::width() {
|
||||
return st::emojiSize + 2 * st::emojiPadding;
|
||||
}
|
||||
|
||||
QString DefaultIconEmoji::entityData() {
|
||||
return u"topic_icon:%1"_q.arg(_icon.colorId);
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ void Controller::choose(not_null<ChannelData*> chat) {
|
||||
const auto onstack = _callback;
|
||||
onstack(chat);
|
||||
};
|
||||
delegate()->peerListShowBox(Ui::MakeConfirmBox({
|
||||
delegate()->peerListUiShow()->showBox(Ui::MakeConfirmBox({
|
||||
.text = text,
|
||||
.confirmed = sure,
|
||||
.confirmText = tr::lng_manage_discussion_group_link(tr::now),
|
||||
@@ -199,7 +199,7 @@ void Controller::choose(not_null<ChatData*> chat) {
|
||||
};
|
||||
chat->session().api().migrateChat(chat, crl::guard(this, done));
|
||||
};
|
||||
delegate()->peerListShowBox(Ui::MakeConfirmBox({
|
||||
delegate()->peerListUiShow()->showBox(Ui::MakeConfirmBox({
|
||||
.text = text,
|
||||
.confirmed = sure,
|
||||
.confirmText = tr::lng_manage_discussion_group_link(tr::now),
|
||||
|
||||
@@ -1281,7 +1281,7 @@ void ParticipantsBoxController::rebuild() {
|
||||
QPointer<Ui::BoxContent> ParticipantsBoxController::showBox(
|
||||
object_ptr<Ui::BoxContent> box) const {
|
||||
const auto weak = Ui::MakeWeak(box.data());
|
||||
delegate()->peerListShowBox(std::move(box));
|
||||
delegate()->peerListUiShow()->showBox(std::move(box));
|
||||
return weak;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/peers/replace_boost_box.h"
|
||||
#include "chat_helpers/compose/compose_show.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
@@ -514,21 +515,17 @@ void Apply(
|
||||
close();
|
||||
return;
|
||||
}
|
||||
const auto next = data.vnext_level_boosts().value_or_empty();
|
||||
const auto openStatistics = [=] {
|
||||
if (const auto controller = show->resolveWindow(
|
||||
ChatHelpers::WindowUsage::PremiumPromo)) {
|
||||
controller->showSection(Info::Boosts::Make(peer));
|
||||
}
|
||||
};
|
||||
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 = {
|
||||
.level = data.vlevel().v,
|
||||
.boosts = data.vboosts().v,
|
||||
.thisLevelBoosts = data.vcurrent_level_boosts().v,
|
||||
.nextLevelBoosts = next,
|
||||
},
|
||||
.boost = counters,
|
||||
.requiredLevel = required,
|
||||
}, openStatistics, nullptr));
|
||||
cancel();
|
||||
|
||||
@@ -347,21 +347,24 @@ void Controller::addHeaderBlock(not_null<Ui::VerticalLayout*> container) {
|
||||
const auto copyLink = crl::guard(weak, [=] {
|
||||
CopyInviteLink(delegate()->peerListUiShow(), link);
|
||||
});
|
||||
const auto shareLink = crl::guard(weak, [=] {
|
||||
delegate()->peerListShowBox(ShareInviteLinkBox(_peer, link));
|
||||
const auto shareLink = crl::guard(weak, [=, peer = _peer] {
|
||||
delegate()->peerListUiShow()->showBox(ShareInviteLinkBox(peer, link));
|
||||
});
|
||||
const auto getLinkQr = crl::guard(weak, [=] {
|
||||
delegate()->peerListShowBox(
|
||||
delegate()->peerListUiShow()->showBox(
|
||||
InviteLinkQrBox(link, tr::lng_group_invite_qr_about()));
|
||||
});
|
||||
const auto revokeLink = crl::guard(weak, [=] {
|
||||
delegate()->peerListShowBox(RevokeLinkBox(_peer, admin, link));
|
||||
delegate()->peerListUiShow()->showBox(
|
||||
RevokeLinkBox(_peer, admin, link));
|
||||
});
|
||||
const auto editLink = crl::guard(weak, [=] {
|
||||
delegate()->peerListShowBox(EditLinkBox(_peer, _data.current()));
|
||||
delegate()->peerListUiShow()->showBox(
|
||||
EditLinkBox(_peer, _data.current()));
|
||||
});
|
||||
const auto deleteLink = crl::guard(weak, [=] {
|
||||
delegate()->peerListShowBox(DeleteLinkBox(_peer, admin, link));
|
||||
delegate()->peerListUiShow()->showBox(
|
||||
DeleteLinkBox(_peer, admin, link));
|
||||
});
|
||||
|
||||
const auto createMenu = [=] {
|
||||
@@ -1332,7 +1335,7 @@ object_ptr<Ui::BoxContent> ShowInviteLinkBox(
|
||||
auto data = rpl::single(link) | rpl::then(std::move(updates));
|
||||
|
||||
auto initBox = [=, data = rpl::duplicate(data)](
|
||||
not_null<Ui::BoxContent*> box) {
|
||||
not_null<Ui::BoxContent*> box) {
|
||||
rpl::duplicate(
|
||||
data
|
||||
) | rpl::start_with_next([=](const LinkData &link) {
|
||||
|
||||
@@ -32,8 +32,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_settings.h" // st::settingsDividerLabelPadding
|
||||
#include "styles/style_menu_icons.h"
|
||||
|
||||
#include <xxhash.h>
|
||||
|
||||
namespace {
|
||||
|
||||
enum class Color {
|
||||
@@ -112,12 +110,8 @@ private:
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] uint64 ComputeRowId(const QString &link) {
|
||||
return XXH64(link.data(), link.size() * sizeof(ushort), 0);
|
||||
}
|
||||
|
||||
[[nodiscard]] uint64 ComputeRowId(const InviteLinkData &data) {
|
||||
return ComputeRowId(data.link);
|
||||
return UniqueRowIdFromString(data.link);
|
||||
}
|
||||
|
||||
[[nodiscard]] float64 ComputeProgress(
|
||||
@@ -543,7 +537,7 @@ void LinksController::appendSlice(const InviteLinksSlice &slice) {
|
||||
}
|
||||
|
||||
void LinksController::rowClicked(not_null<PeerListRow*> row) {
|
||||
delegate()->peerListShowBox(
|
||||
delegate()->peerListUiShow()->showBox(
|
||||
ShowInviteLinkBox(_peer, static_cast<Row*>(row.get())->data()));
|
||||
}
|
||||
|
||||
@@ -579,25 +573,27 @@ base::unique_qptr<Ui::PopupMenu> LinksController::createRowContextMenu(
|
||||
st::popupMenuWithIcons);
|
||||
if (data.revoked) {
|
||||
result->addAction(tr::lng_group_invite_context_delete(tr::now), [=] {
|
||||
delegate()->peerListShowBox(DeleteLinkBox(_peer, _admin, link));
|
||||
delegate()->peerListUiShow()->showBox(
|
||||
DeleteLinkBox(_peer, _admin, link));
|
||||
}, &st::menuIconDelete);
|
||||
} else {
|
||||
result->addAction(tr::lng_group_invite_context_copy(tr::now), [=] {
|
||||
CopyInviteLink(delegate()->peerListUiShow(), link);
|
||||
}, &st::menuIconCopy);
|
||||
result->addAction(tr::lng_group_invite_context_share(tr::now), [=] {
|
||||
delegate()->peerListShowBox(
|
||||
delegate()->peerListUiShow()->showBox(
|
||||
ShareInviteLinkBox(_peer, link));
|
||||
}, &st::menuIconShare);
|
||||
result->addAction(tr::lng_group_invite_context_qr(tr::now), [=] {
|
||||
delegate()->peerListShowBox(
|
||||
delegate()->peerListUiShow()->showBox(
|
||||
InviteLinkQrBox(link, tr::lng_group_invite_qr_about()));
|
||||
}, &st::menuIconQrCode);
|
||||
result->addAction(tr::lng_group_invite_context_edit(tr::now), [=] {
|
||||
delegate()->peerListShowBox(EditLinkBox(_peer, data));
|
||||
delegate()->peerListUiShow()->showBox(EditLinkBox(_peer, data));
|
||||
}, &st::menuIconEdit);
|
||||
result->addAction(tr::lng_group_invite_context_revoke(tr::now), [=] {
|
||||
delegate()->peerListShowBox(RevokeLinkBox(_peer, _admin, link));
|
||||
delegate()->peerListUiShow()->showBox(
|
||||
RevokeLinkBox(_peer, _admin, link));
|
||||
}, &st::menuIconRemove);
|
||||
}
|
||||
return result;
|
||||
@@ -628,7 +624,8 @@ void LinksController::updateRow(const InviteLinkData &data, TimeId now) {
|
||||
}
|
||||
|
||||
bool LinksController::removeRow(const QString &link) {
|
||||
if (const auto row = delegate()->peerListFindRow(ComputeRowId(link))) {
|
||||
const auto id = UniqueRowIdFromString(link);
|
||||
if (const auto row = delegate()->peerListFindRow(id)) {
|
||||
delegate()->peerListRemoveRow(row);
|
||||
return true;
|
||||
}
|
||||
@@ -804,7 +801,7 @@ void AdminsController::loadMoreRows() {
|
||||
}
|
||||
|
||||
void AdminsController::rowClicked(not_null<PeerListRow*> row) {
|
||||
delegate()->peerListShowBox(
|
||||
delegate()->peerListUiShow()->showBox(
|
||||
Box(ManageInviteLinksBox, _peer, row->peer()->asUser(), 0, 0));
|
||||
}
|
||||
|
||||
|
||||
@@ -468,8 +468,10 @@ not_null<Ui::RpWidget*> AddInnerToggle(
|
||||
[=] { arrow->update(); },
|
||||
toggled ? 0. : 1.,
|
||||
toggled ? 1. : 0.,
|
||||
st::slideWrapDuration);
|
||||
st::slideWrapDuration,
|
||||
anim::easeOutCubic);
|
||||
}, button->lifetime());
|
||||
wrap->ease = anim::easeOutCubic;
|
||||
|
||||
const auto handleLocked = [=] {
|
||||
if (locked.has_value()) {
|
||||
|
||||
@@ -0,0 +1,613 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/peers/replace_boost_box.h"
|
||||
|
||||
#include "base/event_filter.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_session.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_account.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/session/session_show.h"
|
||||
#include "ui/boxes/boost_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/controls/userpic_button.h"
|
||||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/painter.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_premium.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kWaitingOpacity = 0.5;
|
||||
|
||||
class Row final : public PeerListRow {
|
||||
public:
|
||||
Row(
|
||||
not_null<Main::Session*> session,
|
||||
TakenBoostSlot slot,
|
||||
TimeId unixtimeNow,
|
||||
crl::time preciseNow);
|
||||
|
||||
void updateStatus(TimeId unixtimeNow, crl::time preciseNow);
|
||||
[[nodiscard]] TakenBoostSlot data() const {
|
||||
return _data;
|
||||
}
|
||||
[[nodiscard]] bool waiting() const {
|
||||
return _waiting;
|
||||
}
|
||||
|
||||
QString generateName() override;
|
||||
QString generateShortName() override;
|
||||
PaintRoundImageCallback generatePaintUserpicCallback(
|
||||
bool forceRound) override;
|
||||
float64 opacity() override;
|
||||
|
||||
private:
|
||||
[[nodiscard]]PaintRoundImageCallback peerPaintUserpicCallback();
|
||||
|
||||
TakenBoostSlot _data;
|
||||
PeerData *_peer = nullptr;
|
||||
std::shared_ptr<Ui::EmptyUserpic> _empty;
|
||||
Ui::PeerUserpicView _userpic;
|
||||
crl::time _startPreciseTime = 0;
|
||||
TimeId _startUnixtime = 0;
|
||||
bool _waiting = false;
|
||||
|
||||
};
|
||||
|
||||
class Controller final : public PeerListController {
|
||||
public:
|
||||
Controller(not_null<ChannelData*> to, std::vector<TakenBoostSlot> from);
|
||||
|
||||
[[nodiscard]] rpl::producer<std::vector<int>> selectedValue() const {
|
||||
return _selected.value();
|
||||
}
|
||||
|
||||
Main::Session &session() const override;
|
||||
void prepare() override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
bool trackSelectedList() override {
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
void updateWaitingState();
|
||||
|
||||
not_null<ChannelData*> _to;
|
||||
std::vector<TakenBoostSlot> _from;
|
||||
rpl::variable<std::vector<int>> _selected;
|
||||
rpl::variable<std::vector<not_null<PeerData*>>> _selectedPeers;
|
||||
base::Timer _waitingTimer;
|
||||
bool _hasWaitingRows = false;
|
||||
|
||||
};
|
||||
|
||||
Row::Row(
|
||||
not_null<Main::Session*> session,
|
||||
TakenBoostSlot slot,
|
||||
TimeId unixtimeNow,
|
||||
crl::time preciseNow)
|
||||
: PeerListRow(PeerListRowId(slot.id))
|
||||
, _data(slot)
|
||||
, _peer(session->data().peerLoaded(_data.peerId))
|
||||
, _startPreciseTime(preciseNow)
|
||||
, _startUnixtime(unixtimeNow) {
|
||||
updateStatus(unixtimeNow, preciseNow);
|
||||
}
|
||||
|
||||
void Row::updateStatus(TimeId unixtimeNow, crl::time preciseNow) {
|
||||
_waiting = (_data.cooldown > unixtimeNow);
|
||||
if (_waiting) {
|
||||
const auto initial = crl::time(_data.cooldown - _startUnixtime);
|
||||
const auto elapsed = (preciseNow + 500 - _startPreciseTime) / 1000;
|
||||
const auto seconds = initial
|
||||
- std::clamp(elapsed, crl::time(), initial);
|
||||
const auto hours = seconds / 3600;
|
||||
const auto minutes = seconds / 60;
|
||||
const auto duration = (hours > 0)
|
||||
? u"%1:%2:%3"_q.arg(
|
||||
hours
|
||||
).arg(minutes % 60, 2, 10, QChar('0')
|
||||
).arg(seconds % 60, 2, 10, QChar('0'))
|
||||
: u"%1:%2"_q.arg(
|
||||
minutes
|
||||
).arg(seconds % 60, 2, 10, QChar('0'));
|
||||
setCustomStatus(
|
||||
tr::lng_boost_available_in(tr::now, lt_duration, duration));
|
||||
} else {
|
||||
const auto date = base::unixtime::parse(_data.expires);
|
||||
setCustomStatus(tr::lng_boosts_list_status(
|
||||
tr::now,
|
||||
lt_date,
|
||||
langDayOfMonth(date.date())));
|
||||
}
|
||||
}
|
||||
|
||||
QString Row::generateName() {
|
||||
return _peer ? _peer->name() : u" "_q;
|
||||
}
|
||||
|
||||
QString Row::generateShortName() {
|
||||
return _peer ? _peer->shortName() : generateName();
|
||||
}
|
||||
|
||||
PaintRoundImageCallback Row::generatePaintUserpicCallback(
|
||||
bool forceRound) {
|
||||
if (_peer) {
|
||||
return (forceRound && _peer->isForum())
|
||||
? ForceRoundUserpicCallback(_peer)
|
||||
: peerPaintUserpicCallback();
|
||||
} else if (!_empty) {
|
||||
const auto colorIndex = _data.id % Ui::kColorIndexCount;
|
||||
_empty = std::make_shared<Ui::EmptyUserpic>(
|
||||
Ui::EmptyUserpic::UserpicColor(colorIndex),
|
||||
u" "_q);
|
||||
}
|
||||
const auto empty = _empty;
|
||||
return [=](Painter &p, int x, int y, int outerWidth, int size) {
|
||||
empty->paintCircle(p, x, y, outerWidth, size);
|
||||
};
|
||||
}
|
||||
|
||||
float64 Row::opacity() {
|
||||
return _waiting ? kWaitingOpacity : 1.;
|
||||
}
|
||||
|
||||
PaintRoundImageCallback Row::peerPaintUserpicCallback() {
|
||||
const auto peer = _peer;
|
||||
if (!_userpic.cloud && peer->hasUserpic()) {
|
||||
_userpic = peer->createUserpicView();
|
||||
}
|
||||
auto userpic = _userpic;
|
||||
return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
|
||||
peer->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
|
||||
};
|
||||
}
|
||||
|
||||
Controller::Controller(
|
||||
not_null<ChannelData*> to,
|
||||
std::vector<TakenBoostSlot> from)
|
||||
: _to(to)
|
||||
, _from(std::move(from))
|
||||
, _waitingTimer([=] { updateWaitingState(); }) {
|
||||
}
|
||||
|
||||
Main::Session &Controller::session() const {
|
||||
return _to->session();
|
||||
}
|
||||
|
||||
void Controller::prepare() {
|
||||
delegate()->peerListSetTitle(tr::lng_boost_reassign_title());
|
||||
|
||||
const auto session = &_to->session();
|
||||
auto above = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
|
||||
above->add(
|
||||
CreateBoostReplaceUserpics(
|
||||
above.data(),
|
||||
_selectedPeers.value(),
|
||||
_to),
|
||||
st::boxRowPadding + st::boostReplaceUserpicsPadding);
|
||||
above->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
above.data(),
|
||||
tr::lng_boost_reassign_text(
|
||||
lt_channel,
|
||||
rpl::single(Ui::Text::Bold(_to->name())),
|
||||
lt_gift,
|
||||
tr::lng_boost_reassign_gift(
|
||||
lt_count,
|
||||
rpl::single(1. * BoostsForGift(session)),
|
||||
Ui::Text::RichLangValue),
|
||||
Ui::Text::RichLangValue),
|
||||
st::boostReassignText),
|
||||
st::boxRowPadding);
|
||||
delegate()->peerListSetAboveWidget(std::move(above));
|
||||
|
||||
const auto now = base::unixtime::now();
|
||||
const auto precise = crl::now();
|
||||
ranges::stable_sort(_from, ranges::less(), [&](TakenBoostSlot slot) {
|
||||
return (slot.cooldown > now) ? slot.cooldown : -slot.cooldown;
|
||||
});
|
||||
for (const auto &slot : _from) {
|
||||
auto row = std::make_unique<Row>(session, slot, now, precise);
|
||||
if (row->waiting()) {
|
||||
_hasWaitingRows = true;
|
||||
}
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
}
|
||||
|
||||
if (_hasWaitingRows) {
|
||||
_waitingTimer.callEach(1000);
|
||||
}
|
||||
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
|
||||
void Controller::updateWaitingState() {
|
||||
_hasWaitingRows = false;
|
||||
const auto now = base::unixtime::now();
|
||||
const auto precise = crl::now();
|
||||
const auto count = delegate()->peerListFullRowsCount();
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
const auto bare = delegate()->peerListRowAt(i);
|
||||
const auto row = static_cast<Row*>(bare.get());
|
||||
if (row->waiting()) {
|
||||
row->updateStatus(now, precise);
|
||||
delegate()->peerListUpdateRow(row);
|
||||
if (row->waiting()) {
|
||||
_hasWaitingRows = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!_hasWaitingRows) {
|
||||
_waitingTimer.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::rowClicked(not_null<PeerListRow*> row) {
|
||||
const auto slot = static_cast<Row*>(row.get())->data();
|
||||
if (slot.cooldown > base::unixtime::now()) {
|
||||
delegate()->peerListUiShow()->showToast({
|
||||
.text = tr::lng_boost_available_in_toast(
|
||||
tr::now,
|
||||
lt_count,
|
||||
BoostsForGift(&session()),
|
||||
Ui::Text::RichLangValue),
|
||||
.adaptive = true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
auto now = _selected.current();
|
||||
const auto id = slot.id;
|
||||
const auto checked = !row->checked();
|
||||
delegate()->peerListSetRowChecked(row, checked);
|
||||
const auto peer = slot.peerId
|
||||
? _to->owner().peerLoaded(slot.peerId)
|
||||
: nullptr;
|
||||
auto peerRemoved = false;
|
||||
if (checked) {
|
||||
now.push_back(id);
|
||||
} else {
|
||||
now.erase(ranges::remove(now, id), end(now));
|
||||
|
||||
peerRemoved = true;
|
||||
for (const auto left : now) {
|
||||
const auto i = ranges::find(_from, left, &TakenBoostSlot::id);
|
||||
Assert(i != end(_from));
|
||||
if (i->peerId == slot.peerId) {
|
||||
peerRemoved = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_selected = std::move(now);
|
||||
|
||||
if (peer) {
|
||||
auto selectedPeers = _selectedPeers.current();
|
||||
const auto i = ranges::find(selectedPeers, not_null(peer));
|
||||
if (peerRemoved) {
|
||||
Assert(i != end(selectedPeers));
|
||||
selectedPeers.erase(i);
|
||||
_selectedPeers = std::move(selectedPeers);
|
||||
} else if (i == end(selectedPeers) && checked) {
|
||||
selectedPeers.insert(begin(selectedPeers), peer);
|
||||
_selectedPeers = std::move(selectedPeers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object_ptr<Ui::BoxContent> ReassignBoostFloodBox(int seconds) {
|
||||
const auto days = seconds / 86400;
|
||||
const auto hours = seconds / 3600;
|
||||
const auto minutes = seconds / 60;
|
||||
return Ui::MakeInformBox({
|
||||
.text = tr::lng_boost_error_flood_text(
|
||||
lt_left,
|
||||
rpl::single(Ui::Text::Bold((days > 1)
|
||||
? tr::lng_days(tr::now, lt_count, days)
|
||||
: (hours > 1)
|
||||
? tr::lng_hours(tr::now, lt_count, hours)
|
||||
: (minutes > 1)
|
||||
? tr::lng_minutes(tr::now, lt_count, minutes)
|
||||
: tr::lng_seconds(tr::now, lt_count, seconds))),
|
||||
Ui::Text::RichLangValue),
|
||||
.title = tr::lng_boost_error_flood_title(),
|
||||
});
|
||||
}
|
||||
|
||||
object_ptr<Ui::BoxContent> ReassignBoostSingleBox(
|
||||
not_null<ChannelData*> to,
|
||||
TakenBoostSlot from,
|
||||
Fn<void(std::vector<int> slots, int sources)> reassign,
|
||||
Fn<void()> cancel) {
|
||||
const auto reassigned = std::make_shared<bool>();
|
||||
const auto slot = from.id;
|
||||
const auto peer = to->owner().peer(from.peerId);
|
||||
const auto confirmed = [=](Fn<void()> close) {
|
||||
*reassigned = true;
|
||||
reassign({ slot }, 1);
|
||||
close();
|
||||
};
|
||||
|
||||
auto result = Box([=](not_null<Ui::GenericBox*> box) {
|
||||
Ui::ConfirmBox(box, {
|
||||
.text = tr::lng_boost_now_instead(
|
||||
lt_channel,
|
||||
rpl::single(Ui::Text::Bold(peer->name())),
|
||||
lt_other,
|
||||
rpl::single(Ui::Text::Bold(to->name())),
|
||||
Ui::Text::WithEntities),
|
||||
.confirmed = confirmed,
|
||||
.confirmText = tr::lng_boost_now_replace(),
|
||||
.labelPadding = st::boxRowPadding,
|
||||
});
|
||||
box->verticalLayout()->insert(
|
||||
0,
|
||||
CreateBoostReplaceUserpics(
|
||||
box,
|
||||
rpl::single(std::vector{ peer }),
|
||||
to),
|
||||
st::boxRowPadding + st::boostReplaceUserpicsPadding);
|
||||
});
|
||||
|
||||
result->boxClosing() | rpl::filter([=] {
|
||||
return !*reassigned;
|
||||
}) | rpl::start_with_next(cancel, result->lifetime());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ForChannelBoostSlots ParseForChannelBoostSlots(
|
||||
not_null<ChannelData*> channel,
|
||||
const QVector<MTPMyBoost> &boosts) {
|
||||
auto result = ForChannelBoostSlots();
|
||||
const auto now = base::unixtime::now();
|
||||
for (const auto &my : boosts) {
|
||||
const auto &data = my.data();
|
||||
const auto id = data.vslot().v;
|
||||
const auto cooldown = data.vcooldown_until_date().value_or(0);
|
||||
const auto peerId = data.vpeer()
|
||||
? peerFromMTP(*data.vpeer())
|
||||
: PeerId();
|
||||
if (!peerId && cooldown <= now) {
|
||||
result.free.push_back(id);
|
||||
} else if (peerId == channel->id) {
|
||||
result.already.push_back(id);
|
||||
} else {
|
||||
result.other.push_back({
|
||||
.id = id,
|
||||
.expires = data.vexpires().v,
|
||||
.peerId = peerId,
|
||||
.cooldown = cooldown,
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Ui::BoostCounters ParseBoostCounters(
|
||||
const MTPpremium_BoostsStatus &status) {
|
||||
const auto &data = status.data();
|
||||
const auto slots = data.vmy_boost_slots();
|
||||
return {
|
||||
.level = data.vlevel().v,
|
||||
.boosts = data.vboosts().v,
|
||||
.thisLevelBoosts = data.vcurrent_level_boosts().v,
|
||||
.nextLevelBoosts = data.vnext_level_boosts().value_or_empty(),
|
||||
.mine = slots ? int(slots->v.size()) : 0,
|
||||
};
|
||||
}
|
||||
|
||||
int BoostsForGift(not_null<Main::Session*> session) {
|
||||
const auto key = u"boosts_per_sent_gift"_q;
|
||||
return session->account().appConfig().get<int>(key, 0);
|
||||
}
|
||||
|
||||
[[nodiscard]] int SourcesCount(
|
||||
const std::vector<TakenBoostSlot> &from,
|
||||
const std::vector<int> &slots) {
|
||||
auto checked = base::flat_set<PeerId>();
|
||||
checked.reserve(slots.size());
|
||||
for (const auto slot : slots) {
|
||||
const auto i = ranges::find(from, slot, &TakenBoostSlot::id);
|
||||
Assert(i != end(from));
|
||||
checked.emplace(i->peerId);
|
||||
}
|
||||
return checked.size();
|
||||
}
|
||||
|
||||
object_ptr<Ui::BoxContent> ReassignBoostsBox(
|
||||
not_null<ChannelData*> to,
|
||||
std::vector<TakenBoostSlot> from,
|
||||
Fn<void(std::vector<int> slots, int sources)> reassign,
|
||||
Fn<void()> cancel) {
|
||||
Expects(!from.empty());
|
||||
|
||||
const auto now = base::unixtime::now();
|
||||
if (from.size() == 1 && from.front().cooldown > now) {
|
||||
cancel();
|
||||
return ReassignBoostFloodBox(from.front().cooldown - now);
|
||||
} else if (from.size() == 1 && from.front().peerId) {
|
||||
return ReassignBoostSingleBox(to, from.front(), reassign, cancel);
|
||||
}
|
||||
const auto reassigned = std::make_shared<bool>();
|
||||
auto controller = std::make_unique<Controller>(to, from);
|
||||
const auto raw = controller.get();
|
||||
auto initBox = [=](not_null<Ui::BoxContent*> box) {
|
||||
raw->selectedValue(
|
||||
) | rpl::start_with_next([=](std::vector<int> slots) {
|
||||
box->clearButtons();
|
||||
if (!slots.empty()) {
|
||||
const auto sources = SourcesCount(from, slots);
|
||||
box->addButton(tr::lng_boost_reassign_button(), [=] {
|
||||
*reassigned = true;
|
||||
reassign(slots, sources);
|
||||
});
|
||||
}
|
||||
box->addButton(tr::lng_cancel(), [=] {
|
||||
box->closeBox();
|
||||
});
|
||||
}, box->lifetime());
|
||||
|
||||
box->boxClosing() | rpl::filter([=] {
|
||||
return !*reassigned;
|
||||
}) | rpl::start_with_next(cancel, box->lifetime());
|
||||
};
|
||||
return Box<PeerListBox>(std::move(controller), std::move(initBox));
|
||||
}
|
||||
|
||||
object_ptr<Ui::RpWidget> CreateBoostReplaceUserpics(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
rpl::producer<std::vector<not_null<PeerData*>>> from,
|
||||
not_null<PeerData*> to) {
|
||||
struct State {
|
||||
std::vector<not_null<PeerData*>> from;
|
||||
std::vector<std::unique_ptr<Ui::UserpicButton>> buttons;
|
||||
QImage layer;
|
||||
rpl::variable<int> count = 0;
|
||||
bool painting = false;
|
||||
};
|
||||
const auto full = st::boostReplaceUserpic.size.height()
|
||||
+ st::boostReplaceIconAdd.y()
|
||||
+ st::lineWidth;
|
||||
auto result = object_ptr<Ui::FixedHeightWidget>(parent, full);
|
||||
const auto raw = result.data();
|
||||
const auto &st = st::boostReplaceUserpic;
|
||||
const auto right = CreateChild<Ui::UserpicButton>(raw, to, st);
|
||||
const auto overlay = CreateChild<Ui::RpWidget>(raw);
|
||||
|
||||
const auto state = raw->lifetime().make_state<State>();
|
||||
std::move(
|
||||
from
|
||||
) | rpl::start_with_next([=](
|
||||
const std::vector<not_null<PeerData*>> &list) {
|
||||
const auto &st = st::boostReplaceUserpic;
|
||||
auto was = base::take(state->from);
|
||||
auto buttons = base::take(state->buttons);
|
||||
state->from.reserve(list.size());
|
||||
state->buttons.reserve(list.size());
|
||||
for (const auto &peer : list) {
|
||||
state->from.push_back(peer);
|
||||
const auto i = ranges::find(was, peer);
|
||||
if (i != end(was)) {
|
||||
const auto index = int(i - begin(was));
|
||||
Assert(buttons[index] != nullptr);
|
||||
state->buttons.push_back(std::move(buttons[index]));
|
||||
} else {
|
||||
state->buttons.push_back(
|
||||
std::make_unique<Ui::UserpicButton>(raw, peer, st));
|
||||
const auto raw = state->buttons.back().get();
|
||||
base::install_event_filter(raw, [=](not_null<QEvent*> e) {
|
||||
return (e->type() == QEvent::Paint && !state->painting)
|
||||
? base::EventFilterResult::Cancel
|
||||
: base::EventFilterResult::Continue;
|
||||
});
|
||||
}
|
||||
}
|
||||
state->count.force_assign(int(list.size()));
|
||||
overlay->update();
|
||||
}, raw->lifetime());
|
||||
|
||||
rpl::combine(
|
||||
raw->widthValue(),
|
||||
state->count.value()
|
||||
) | rpl::start_with_next([=](int width, int count) {
|
||||
const auto skip = st::boostReplaceUserpicsSkip;
|
||||
const auto left = width - 2 * right->width() - skip;
|
||||
const auto shift = std::min(
|
||||
st::boostReplaceUserpicsShift,
|
||||
(count > 1 ? (left / (count - 1)) : width));
|
||||
const auto total = right->width()
|
||||
+ (count ? (skip + right->width() + (count - 1) * shift) : 0);
|
||||
auto x = (width - total) / 2;
|
||||
for (const auto &single : state->buttons) {
|
||||
single->moveToLeft(x, 0);
|
||||
x += shift;
|
||||
}
|
||||
if (count) {
|
||||
x += right->width() - shift + skip;
|
||||
}
|
||||
right->moveToLeft(x, 0);
|
||||
overlay->setGeometry(QRect(0, 0, width, raw->height()));
|
||||
}, raw->lifetime());
|
||||
|
||||
overlay->paintRequest(
|
||||
) | rpl::filter([=] {
|
||||
return !state->buttons.empty();
|
||||
}) | rpl::start_with_next([=] {
|
||||
const auto outerw = overlay->width();
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
if (state->layer.size() != QSize(outerw, full) * ratio) {
|
||||
state->layer = QImage(
|
||||
QSize(outerw, full) * ratio,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
state->layer.setDevicePixelRatio(ratio);
|
||||
}
|
||||
state->layer.fill(Qt::transparent);
|
||||
|
||||
auto q = QPainter(&state->layer);
|
||||
auto hq = PainterHighQualityEnabler(q);
|
||||
const auto stroke = st::boostReplaceIconOutline;
|
||||
const auto half = stroke / 2.;
|
||||
auto pen = st::windowBg->p;
|
||||
pen.setWidthF(stroke * 2.);
|
||||
state->painting = true;
|
||||
for (const auto &button : state->buttons) {
|
||||
q.setPen(pen);
|
||||
q.setBrush(Qt::NoBrush);
|
||||
q.drawEllipse(button->geometry());
|
||||
const auto position = button->pos();
|
||||
button->render(&q, position, QRegion(), QWidget::DrawChildren);
|
||||
}
|
||||
state->painting = false;
|
||||
const auto last = state->buttons.back().get();
|
||||
const auto add = st::boostReplaceIconAdd;
|
||||
const auto skip = st::boostReplaceIconSkip;
|
||||
const auto w = st::boostReplaceIcon.width() + 2 * skip;
|
||||
const auto h = st::boostReplaceIcon.height() + 2 * skip;
|
||||
const auto x = last->x() + last->width() - w + add.x();
|
||||
const auto y = last->y() + last->height() - h + add.y();
|
||||
|
||||
auto brush = QLinearGradient(QPointF(x + w, y + h), QPointF(x, y));
|
||||
brush.setStops(Ui::Premium::ButtonGradientStops());
|
||||
q.setBrush(brush);
|
||||
pen.setWidthF(stroke);
|
||||
q.setPen(pen);
|
||||
q.drawEllipse(x - half, y - half, w + stroke, h + stroke);
|
||||
st::boostReplaceIcon.paint(q, x + skip, y + skip, outerw);
|
||||
|
||||
const auto size = st::boostReplaceArrow.size();
|
||||
st::boostReplaceArrow.paint(
|
||||
q,
|
||||
(last->x()
|
||||
+ last->width()
|
||||
+ (st::boostReplaceUserpicsSkip - size.width()) / 2),
|
||||
(last->height() - size.height()) / 2,
|
||||
outerw);
|
||||
|
||||
q.end();
|
||||
|
||||
auto p = QPainter(overlay);
|
||||
p.drawImage(0, 0, state->layer);
|
||||
}, overlay->lifetime());
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/object_ptr.h"
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Ui {
|
||||
struct BoostCounters;
|
||||
class BoxContent;
|
||||
class RpWidget;
|
||||
} // namespace Ui
|
||||
|
||||
struct TakenBoostSlot {
|
||||
int id = 0;
|
||||
TimeId expires = 0;
|
||||
PeerId peerId = 0;
|
||||
TimeId cooldown = 0;
|
||||
};
|
||||
|
||||
struct ForChannelBoostSlots {
|
||||
std::vector<int> free;
|
||||
std::vector<int> already;
|
||||
std::vector<TakenBoostSlot> other;
|
||||
};
|
||||
|
||||
[[nodiscard]] ForChannelBoostSlots ParseForChannelBoostSlots(
|
||||
not_null<ChannelData*> channel,
|
||||
const QVector<MTPMyBoost> &boosts);
|
||||
|
||||
[[nodiscard]] Ui::BoostCounters ParseBoostCounters(
|
||||
const MTPpremium_BoostsStatus &status);
|
||||
|
||||
[[nodiscard]] int BoostsForGift(not_null<Main::Session*> session);
|
||||
|
||||
object_ptr<Ui::BoxContent> ReassignBoostsBox(
|
||||
not_null<ChannelData*> to,
|
||||
std::vector<TakenBoostSlot> from,
|
||||
Fn<void(std::vector<int> slots, int sources)> reassign,
|
||||
Fn<void()> cancel);
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> CreateBoostReplaceUserpics(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
rpl::producer<std::vector<not_null<PeerData*>>> from,
|
||||
not_null<PeerData*> to);
|
||||
@@ -75,7 +75,7 @@ void PinMessageBox(
|
||||
auto object = object_ptr<Ui::Checkbox>(
|
||||
box,
|
||||
tr::lng_pinned_notify(tr::now),
|
||||
true,
|
||||
false,
|
||||
st::urlAuthCheckbox);
|
||||
object->setAllowTextLines();
|
||||
state->notify = Ui::MakeWeak(object.data());
|
||||
|
||||