Compare commits
121 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 59c2cbaabc | |||
| 6d39a5f1d2 | |||
| 4fd0b008ba | |||
| fccc93ca53 | |||
| 37d1940993 | |||
| f25638f492 | |||
| 6e6f15e711 | |||
| b38e72dcd9 | |||
| 9287af1752 | |||
| 254ca57bf3 | |||
| 52ef8e780a | |||
| 16a2d4ec96 | |||
| 3b50bc71b3 | |||
| bb31357c58 | |||
| 66afcbdae8 | |||
| 5a28e69f1a | |||
| ce78074df7 | |||
| 973f91b5e4 | |||
| d2246337a2 | |||
| c7f11eb05a | |||
| 758219265a | |||
| d8d9441731 | |||
| 098e797045 | |||
| 69f8cb5951 | |||
| ca86dce760 | |||
| 5cf0b6b50e | |||
| b6bbf3a560 | |||
| e0c2294d50 | |||
| 7426d285b4 | |||
| fba53e1ec8 | |||
| 74196732ac | |||
| 8c41a69e67 | |||
| b1e91ab746 | |||
| 7a139ecda7 | |||
| e52fe9ddb0 | |||
| 2e514a748c | |||
| cee2961632 | |||
| 50bc6a33f6 | |||
| 5933535c9b | |||
| a3b91da66c | |||
| 34d9a21aae | |||
| 233c6b18ed | |||
| 4274f9d3f3 | |||
| c8dd94601b | |||
| 382dab4ecb | |||
| bdf67645bb | |||
| 5c29cc59c8 | |||
| ca9caa36da | |||
| 0b5f05c7d4 | |||
| 676e85983d | |||
| 7638f4cc3d | |||
| e2e55312b8 | |||
| c51a8816eb | |||
| 175914f02b | |||
| fbd6b5b640 | |||
| 1b11731d6b | |||
| 8ecb49f132 | |||
| 2f069bcd02 | |||
| 74abaccab3 | |||
| 115517fae1 | |||
| 2aef563374 | |||
| 040794586b | |||
| c102f4c0e5 | |||
| 7cdbbc318a | |||
| 0ae537478f | |||
| 4aa432ecbe | |||
| d5a0f4890d | |||
| 5e255e56eb | |||
| a4d7309209 | |||
| a30d0eccda | |||
| a4f4e4564a | |||
| 3948341d6a | |||
| c408c5fb87 | |||
| fa8124cc1b | |||
| 8d93b21947 | |||
| bfe7683cdb | |||
| fbc600a978 | |||
| 70eb452a09 | |||
| 9f7c74ae72 | |||
| 65a3cf136b | |||
| 2d86ec1e84 | |||
| fdef19a009 | |||
| 26df482b54 | |||
| ee5b7a5100 | |||
| bd67bc4433 | |||
| 9d582040e6 | |||
| f3bda59019 | |||
| 0d72d47318 | |||
| 29646707a1 | |||
| e6b9a07163 | |||
| 4b297bfa09 | |||
| 00e785a3af | |||
| 78e6b3e13f | |||
| ad84750130 | |||
| 686310489b | |||
| 84c5310262 | |||
| f53397e26a | |||
| 2a8a74b5b1 | |||
| 9392550c01 | |||
| d2565dc944 | |||
| 22f68b430d | |||
| 3962e5a680 | |||
| a1c7a48958 | |||
| 85286684e3 | |||
| c2712b0104 | |||
| 1cb5ef7476 | |||
| 9f0b42bbbd | |||
| 634687881a | |||
| 878b4bb5af | |||
| 452257dcd5 | |||
| 4e6d8f06d9 | |||
| fd417024fb | |||
| 18c4d210e5 | |||
| ead40c759e | |||
| be9aa3a097 | |||
| bdcb146d06 | |||
| bf743836c1 | |||
| 70115a24bb | |||
| ea37e83b13 | |||
| 931c17418d | |||
| 5a47ed268c |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
@@ -94,6 +94,7 @@ jobs:
|
||||
-D CMAKE_CXX_FLAGS="-Werror" \
|
||||
-D CMAKE_EXE_LINKER_FLAGS="-s" \
|
||||
-D TDESKTOP_API_TEST=ON \
|
||||
-D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF \
|
||||
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \
|
||||
$DEFINE
|
||||
|
||||
@@ -115,7 +116,7 @@ jobs:
|
||||
run: |
|
||||
cd $REPO_NAME/out/Debug
|
||||
mkdir artifact
|
||||
mv Telegram artifact/
|
||||
mv {Telegram,Updater} artifact/
|
||||
- uses: actions/upload-artifact@master
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
name: Upload artifact.
|
||||
|
||||
@@ -115,6 +115,7 @@ jobs:
|
||||
-D CMAKE_CXX_FLAGS="-Werror" \
|
||||
-D CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=NO \
|
||||
-D TDESKTOP_API_TEST=ON \
|
||||
-D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF \
|
||||
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \
|
||||
$DEFINE
|
||||
|
||||
|
||||
@@ -169,6 +169,7 @@ jobs:
|
||||
%TDESKTOP_BUILD_GENERATOR% ^
|
||||
%TDESKTOP_BUILD_ARCH% ^
|
||||
%TDESKTOP_BUILD_API% ^
|
||||
-D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF ^
|
||||
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF ^
|
||||
-D DESKTOP_APP_NO_PDB=ON ^
|
||||
%TDESKTOP_BUILD_DEFINE%
|
||||
@@ -178,8 +179,10 @@ jobs:
|
||||
- name: Move artifact.
|
||||
if: (env.UPLOAD_ARTIFACT == 'true') || (github.ref == 'refs/heads/nightly')
|
||||
run: |
|
||||
set OUT=%TBUILD%\%REPO_NAME%\out\Debug
|
||||
mkdir artifact
|
||||
move %TBUILD%\%REPO_NAME%\out\Debug\Telegram.exe artifact/
|
||||
move %OUT%\Telegram.exe artifact/
|
||||
move %OUT%\Updater.exe artifact/
|
||||
- uses: actions/upload-artifact@master
|
||||
name: Upload artifact.
|
||||
if: (env.UPLOAD_ARTIFACT == 'true') || (github.ref == 'refs/heads/nightly')
|
||||
|
||||
@@ -60,9 +60,9 @@ include(cmake/options.cmake)
|
||||
|
||||
if (NOT DESKTOP_APP_USE_PACKAGED)
|
||||
if (WIN32)
|
||||
set(qt_version 5.15.11)
|
||||
set(qt_version 5.15.12)
|
||||
elseif (APPLE)
|
||||
set(qt_version 6.2.6)
|
||||
set(qt_version 6.2.7)
|
||||
endif()
|
||||
endif()
|
||||
include(cmake/external/qt/package.cmake)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
Copyright (c) 2014-2023 The Telegram Desktop Authors.
|
||||
Copyright (c) 2014-2024 The Telegram Desktop Authors.
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
||||
@@ -9,11 +9,17 @@
|
||||
- Anti-recall
|
||||
- Font customization
|
||||
- Streamer mode
|
||||
- Material Design switches
|
||||
- Local Telegram Premium
|
||||
- Sync read states and message history with AyuSync *(TBA)*
|
||||
- Enhanced appearance
|
||||
|
||||
<img src='.github/demos/demo1.png' width='250'> <img src='.github/demos/demo2.png' width='250'> <img src='.github/demos/demo3.png' width='250'>
|
||||
And many more. Check out our [Documentation](https://docs.ayugram.one/desktop/).
|
||||
|
||||
## Preferences screenshots
|
||||
|
||||
<img src='.github/demos/demo1.png' width='268'>
|
||||
<img src='.github/demos/demo2.png' width='268'>
|
||||
<img src='.github/demos/demo3.png' width='268'>
|
||||
<img src='.github/demos/demo4.png' width='268'>
|
||||
|
||||
## Downloads
|
||||
|
||||
@@ -27,12 +33,7 @@ build by yourself.
|
||||
|
||||
### Arch Linux
|
||||
|
||||
It is recommended to build AyuGram from source on any Linux distro.
|
||||
|
||||
But if you're brave enough, you can install `ayugram-desktop-git` or `ayugram-desktop-bin`
|
||||
from [AUR](https://aur.archlinux.org/packages?O=0&K=ayugram).
|
||||
|
||||
These packages maintained by [westpain](https://github.com/incycledream). *We're not responsible for them.*
|
||||
You can install `ayugram-desktop` or `ayugram-desktop-git` from [AUR](https://aur.archlinux.org/packages?O=0&K=ayugram).
|
||||
|
||||
### Any other Linux distro
|
||||
|
||||
@@ -48,23 +49,27 @@ Make sure you have these components installed with VS Build Tools:
|
||||
|
||||
## Donation
|
||||
|
||||
If you enjoy using **AyuGram** and want to send us a tip, here's how you can do it:
|
||||
Enjoy using **AyuGram**? Consider sending us a tip!
|
||||
|
||||
- Using [Boosty](https://boosty.to/alexeyzavar) - any card and PayPal
|
||||
- Using cryptocurrency - `TRpbajq38qU8joThgAfKJLyEPbNjzsdPJ1` (Tron + USDT)
|
||||
[Here's available methods.](https://docs.ayugram.one/donate/)
|
||||
|
||||
## Credits
|
||||
|
||||
### Telegram clients
|
||||
|
||||
- [Telegram Desktop](https://github.com/telegramdesktop/tdesktop)
|
||||
- [Kotatogram](https://github.com/kotatogram/kotatogram-desktop)
|
||||
- [64Gram](https://github.com/TDesktop-x64/tdesktop)
|
||||
- [Forkgram](https://github.com/forkgram/tdesktop)
|
||||
- [SQLite](https://github.com/sqlite/sqlite)
|
||||
- [sqlite_orm](https://github.com/fnc12/sqlite_orm)
|
||||
|
||||
### Very special thanks to
|
||||
### Libraries used
|
||||
|
||||
- [Solar Icon Set](https://solariconset.com/)
|
||||
- [JSON for Modern C++](https://github.com/nlohmann/json)
|
||||
- [BitConverter](https://github.com/YanjieHe/BitConverter)
|
||||
- [Not Enough Standards](https://github.com/Alairion/not-enough-standards)
|
||||
- [SQLite](https://github.com/sqlite/sqlite)
|
||||
- [sqlite_orm](https://github.com/fnc12/sqlite_orm)
|
||||
|
||||
### Icons
|
||||
|
||||
- [Solar Icon Set](https://www.figma.com/community/file/1166831539721848736)
|
||||
|
||||
@@ -96,6 +96,8 @@ nice_target_sources(Telegram ${src_loc}
|
||||
PRIVATE
|
||||
${style_files}
|
||||
|
||||
ayu/ayu_url_handlers.cpp
|
||||
ayu/ayu_url_handlers.h
|
||||
ayu/ayu_state.cpp
|
||||
ayu/ayu_state.h
|
||||
ayu/ayu_settings.cpp
|
||||
@@ -107,6 +109,7 @@ PRIVATE
|
||||
ayu/ayu_constants.h
|
||||
ayu/utils/ayu_mapper.cpp
|
||||
ayu/utils/ayu_mapper.h
|
||||
ayu/utils/qt_key_modifiers_extended.h
|
||||
ayu/utils/telegram_helpers.cpp
|
||||
ayu/utils/telegram_helpers.h
|
||||
ayu/utils/windows_utils.cpp
|
||||
@@ -123,6 +126,8 @@ PRIVATE
|
||||
ayu/ui/settings/settings_ayu.h
|
||||
ayu/ui/context_menu/context_menu.cpp
|
||||
ayu/ui/context_menu/context_menu.h
|
||||
ayu/ui/context_menu/menu_item_subtext.cpp
|
||||
ayu/ui/context_menu/menu_item_subtext.h
|
||||
ayu/ui/sections/edited/edited_log_inner.cpp
|
||||
ayu/ui/sections/edited/edited_log_inner.h
|
||||
ayu/ui/sections/edited/edited_log_item.cpp
|
||||
@@ -617,6 +622,10 @@ PRIVATE
|
||||
data/data_replies_list.h
|
||||
data/data_reply_preview.cpp
|
||||
data/data_reply_preview.h
|
||||
data/data_saved_messages.cpp
|
||||
data/data_saved_messages.h
|
||||
data/data_saved_sublist.cpp
|
||||
data/data_saved_sublist.h
|
||||
data/data_search_controller.cpp
|
||||
data/data_search_controller.h
|
||||
data/data_send_action.cpp
|
||||
@@ -863,6 +872,8 @@ PRIVATE
|
||||
history/view/history_view_sponsored_click_handler.h
|
||||
history/view/history_view_sticker_toast.cpp
|
||||
history/view/history_view_sticker_toast.h
|
||||
history/view/history_view_sublist_section.cpp
|
||||
history/view/history_view_sublist_section.h
|
||||
history/view/history_view_transcribe_button.cpp
|
||||
history/view/history_view_transcribe_button.h
|
||||
history/view/history_view_translate_bar.cpp
|
||||
@@ -964,6 +975,8 @@ PRIVATE
|
||||
info/profile/info_profile_values.h
|
||||
info/profile/info_profile_widget.cpp
|
||||
info/profile/info_profile_widget.h
|
||||
info/saved/info_saved_sublists_widget.cpp
|
||||
info/saved/info_saved_sublists_widget.h
|
||||
info/settings/info_settings_widget.cpp
|
||||
info/settings/info_settings_widget.h
|
||||
info/similar_channels/info_similar_channels_widget.cpp
|
||||
@@ -1934,9 +1947,9 @@ endif()
|
||||
|
||||
if (LINUX AND DESKTOP_APP_USE_PACKAGED)
|
||||
include(GNUInstallDirs)
|
||||
configure_file("../lib/xdg/ayugram.desktop.service" "${CMAKE_CURRENT_BINARY_DIR}/ayugram.desktop.service" @ONLY)
|
||||
configure_file("../lib/xdg/ayugram.desktop.metainfo.xml" "${CMAKE_CURRENT_BINARY_DIR}/ayugram.desktop.metainfo.xml" @ONLY)
|
||||
generate_appdata_changelog(Telegram "${CMAKE_SOURCE_DIR}/changelog.txt" "${CMAKE_CURRENT_BINARY_DIR}/ayugram.desktop.metainfo.xml")
|
||||
configure_file("../lib/xdg/com.ayugram.desktop.service" "${CMAKE_CURRENT_BINARY_DIR}/com.ayugram.desktop.service" @ONLY)
|
||||
configure_file("../lib/xdg/com.ayugram.desktop.metainfo.xml" "${CMAKE_CURRENT_BINARY_DIR}/com.ayugram.desktop.metainfo.xml" @ONLY)
|
||||
generate_appdata_changelog(Telegram "${CMAKE_SOURCE_DIR}/changelog.txt" "${CMAKE_CURRENT_BINARY_DIR}/com.ayugram.desktop.metainfo.xml")
|
||||
install(TARGETS Telegram RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" BUNDLE DESTINATION "${CMAKE_INSTALL_BINDIR}")
|
||||
install(FILES "Resources/art/icon16.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/16x16/apps" RENAME "ayugram.png")
|
||||
install(FILES "Resources/art/icon32.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/32x32/apps" RENAME "ayugram.png")
|
||||
@@ -1945,8 +1958,8 @@ if (LINUX AND DESKTOP_APP_USE_PACKAGED)
|
||||
install(FILES "Resources/art/icon128.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128/apps" RENAME "ayugram.png")
|
||||
install(FILES "Resources/art/icon256.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/256x256/apps" RENAME "ayugram.png")
|
||||
install(FILES "Resources/art/icon512.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/512x512/apps" RENAME "ayugram.png")
|
||||
install(FILES "Resources/icons/tray_monochrome.svg" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/symbolic/apps" RENAME "telegram-symbolic.svg")
|
||||
install(FILES "../lib/xdg/ayugram.desktop.desktop" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications")
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/ayugram.desktop.service" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/dbus-1/services")
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/ayugram.desktop.metainfo.xml" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo")
|
||||
install(FILES "Resources/icons/tray_monochrome.svg" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/symbolic/apps" RENAME "ayugram-symbolic.svg")
|
||||
install(FILES "../lib/xdg/com.ayugram.desktop.desktop" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications")
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.ayugram.desktop.service" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/dbus-1/services")
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.ayugram.desktop.metainfo.xml" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo")
|
||||
endif()
|
||||
|
||||
|
After Width: | Height: | Size: 919 B |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 753 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 380 B |
|
After Width: | Height: | Size: 664 B |
|
After Width: | Height: | Size: 954 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 686 B |
|
After Width: | Height: | Size: 952 B |
@@ -395,6 +395,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_dlg_new_bot_name" = "Bot name";
|
||||
"lng_no_chats" = "Your chats will be here";
|
||||
"lng_no_chats_filter" = "No chats currently belong to this folder.";
|
||||
"lng_no_saved_sublists" = "You can save messages from other chats here.";
|
||||
"lng_contacts_loading" = "Loading...";
|
||||
"lng_contacts_not_found" = "No contacts found";
|
||||
"lng_topics_not_found" = "No topics found.";
|
||||
@@ -1187,6 +1188,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_profile_common_groups#other" = "{count} groups in common";
|
||||
"lng_profile_similar_channels#one" = "{count} similar channel";
|
||||
"lng_profile_similar_channels#other" = "{count} similar channels";
|
||||
"lng_profile_saved_messages#one" = "{count} saved message";
|
||||
"lng_profile_saved_messages#other" = "{count} saved messages";
|
||||
"lng_profile_participants_section" = "Members";
|
||||
"lng_profile_subscribers_section" = "Subscribers";
|
||||
"lng_profile_add_contact" = "Add Contact";
|
||||
@@ -1712,6 +1715,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_ttl_video_received" = "{from} sent you a self-destructing video. Please view it on your mobile.";
|
||||
"lng_ttl_video_sent" = "You sent a self-destructing video.";
|
||||
"lng_ttl_video_expired" = "Video has expired";
|
||||
"lng_ttl_voice_sent" = "You sent a self-destructing voice messsage.";
|
||||
"lng_ttl_voice_expired" = "Voice message expired";
|
||||
"lng_ttl_round_sent" = "You sent a self-destructing video message.";
|
||||
"lng_ttl_round_expired" = "Round message expired";
|
||||
|
||||
"lng_profile_add_more_after_create" = "You will be able to add more members after you create the group.";
|
||||
"lng_profile_camera_title" = "Capture yourself";
|
||||
@@ -2080,6 +2087,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_premium_gifts_about_paid_below#one" = "They now have access to additional features.";
|
||||
"lng_premium_gifts_about_paid_below#other" = "They now have access to additional features.";
|
||||
"lng_premium_gifts_summary_subtitle" = "What's Included";
|
||||
"lng_premium_gifts_terms" = "By gifting Telegram Premium, you agree to the Telegram {link} and {policy}.";
|
||||
"lng_premium_gifts_terms_policy" = "Privacy Policy";
|
||||
|
||||
"lng_boost_channel_button" = "Boost Channel";
|
||||
"lng_boost_again_button" = "Boost Again";
|
||||
@@ -2492,6 +2501,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_saved_short" = "Save";
|
||||
"lng_saved_forward_here" = "Forward messages here for quick access";
|
||||
"lng_saved_quote_here" = "Quote here to save";
|
||||
"lng_saved_open_chat" = "Open Chat";
|
||||
"lng_saved_open_channel" = "Open Channel";
|
||||
"lng_saved_open_group" = "Open Group";
|
||||
"lng_saved_about_hidden" = "Senders of this messages restricted to link their name when forwarding.";
|
||||
|
||||
"lng_scheduled_messages" = "Scheduled Messages";
|
||||
"lng_scheduled_messages_empty" = "No scheduled messages here yet...";
|
||||
@@ -2518,6 +2531,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_comments_open_none" = "Leave a comment";
|
||||
"lng_replies_view_original" = "View in chat";
|
||||
"lng_replies_messages" = "Replies";
|
||||
"lng_hidden_author_messages" = "Author Hidden";
|
||||
"lng_my_notes" = "My Notes";
|
||||
"lng_replies_discussion_started" = "Discussion started";
|
||||
"lng_replies_no_comments" = "No comments here yet...";
|
||||
|
||||
@@ -4610,9 +4625,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"ayu_MaximumMediaSizeCellular" = "Media size limit (cellular data)";
|
||||
"ayu_MaximumMediaSizeWiFi" = "Media size limit (WiFi)";
|
||||
"ayu_MessageSavingOtherHeader" = "Other";
|
||||
"ayu_MessageSavingSaveFormatting" = "Save formatting";
|
||||
"ayu_MessageSavingSaveReactions" = "Save reactions";
|
||||
"ayu_MessageSavingSaveForBots" = "Save in bot dialogs";
|
||||
"ayu_MessageSavingSavePath" = "Attachments folder";
|
||||
"ayu_MessageSavingSavePathTitle" = "Choose folder";
|
||||
"ayu_SpyAllToggle" = "Spy on people";
|
||||
"ayu_SpySaveReadMarks" = "Save read date";
|
||||
"ayu_SpySaveLastActivity" = "Save last activity";
|
||||
@@ -4626,6 +4642,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"ayu_CustomizationHeader" = "Customization";
|
||||
"ayu_DeletedMarkText" = "Deleted mark";
|
||||
"ayu_EditedMarkText" = "Edited mark";
|
||||
"ayu_HideNotificationCounters" = "Hide notification counters";
|
||||
"ayu_HideAllChats" = "Hide \"All chats\" folder";
|
||||
"ayu_ShowGhostToggleInDrawer" = "Show ghost mode toggle";
|
||||
"ayu_ShowKllButtonInDrawer" = "Show kill app button";
|
||||
@@ -4633,12 +4650,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"ayu_SettingsShowID_Hide" = "Hide";
|
||||
"ayu_SettingsRecentStickersCount" = "Recent stickers count";
|
||||
"ayu_SettingsCustomizationHint" = "After making changes to the \"Customization\" section, you must restart the application.";
|
||||
"ayu_SettingsContextMenuTitle" = "Select item show type";
|
||||
"ayu_SettingsContextMenuDescription" = "Extended menu items will be displayed if you hold CTRL or SHIFT while right-clicking on the message.";
|
||||
"ayu_SettingsContextMenuItemHidden" = "Hidden";
|
||||
"ayu_SettingsContextMenuItemShown" = "Shown";
|
||||
"ayu_SettingsContextMenuItemExtended" = "Extended menu";
|
||||
"ayu_SettingsContextMenuReactionsPanel" = "Reactions Panel";
|
||||
"ayu_SettingsContextMenuViewsPanel" = "Views Panel";
|
||||
"ayu_ContextMenuElementsHeader" = "Context Menu Elements";
|
||||
"ayu_DrawerElementsHeader" = "Drawer Elements";
|
||||
"ayu_TrayElementsHeader" = "Tray Elements";
|
||||
"ayu_RegexFilters" = "Message Filters";
|
||||
"ayu_RegexFiltersAmount" = "filters";
|
||||
"ayu_RegexFiltersHeader" = "Filters";
|
||||
"ayu_RegexFiltersShared" = "Shared filters";
|
||||
"ayu_RegexFiltersExcluded" = "Excluded filters";
|
||||
"ayu_FiltersHideFromBlocked" = "Hide from blocked users";
|
||||
"ayu_FiltersHideFromBlockedNote" = "Message filters were enabled.";
|
||||
"ayu_RegexFiltersEnableSharedInChats" = "Enable shared filters in chats";
|
||||
@@ -4690,6 +4716,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"ayu_StickerConfirmation" = "For stickers";
|
||||
"ayu_GIFConfirmation" = "For GIFs";
|
||||
"ayu_VoiceConfirmation" = "For voice messages";
|
||||
"ayu_MessageDetailsPC" = "Details";
|
||||
"ayu_MessageDetailsViewsPC" = "Views";
|
||||
"ayu_MessageDetailsSharesPC" = "Shares";
|
||||
"ayu_MessageDetailsDatePC" = "Date";
|
||||
"ayu_MessageDetailsEditedDatePC" = "Edit Date";
|
||||
"ayu_MessageDetailsForwardedDatePC" = "Forward Date";
|
||||
"ayu_MessageDetailsFileSizePC" = "File Size";
|
||||
"ayu_MessageDetailsFileNamePC" = "File Name";
|
||||
"ayu_MessageDetailsFilePathPC" = "File Path";
|
||||
"ayu_MessageDetailsResolutionPC" = "Resolution";
|
||||
"ayu_MessageDetailsBitratePC" = "Bitrate";
|
||||
"ayu_MessageDetailsMimeTypePC" = "Mime Type";
|
||||
"ayu_MessageDetailsDatacenterPC" = "Datacenter";
|
||||
"ayu_MessageDetailsPackOwnerPC" = "Pack Owner";
|
||||
"ayu_MessageDetailsPackOwnerFetchingPC" = "fetching...";
|
||||
"ayu_MessageDetailsPackOwnerNotFoundPC" = "not found";
|
||||
"ayu_KillApp" = "Kill App";
|
||||
"ayu_LReadMessages" = "Read on Local";
|
||||
"ayu_SReadMessages" = "Read on Server";
|
||||
@@ -4710,6 +4752,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"ayu_ViewFiltersMenuText" = "View Filters";
|
||||
"ayu_PeekOnlineSuccess" = "Peeked via";
|
||||
"ayu_OneViewTTL" = "one view";
|
||||
"ayu_OnePlayTTL" = "one play";
|
||||
"ayu_UserMessagesMenuText" = "User Messages";
|
||||
"ayu_ReadUntilMenuText" = "Read Message";
|
||||
"ayu_DeleteKeepLocally" = "Keep locally";
|
||||
@@ -4730,6 +4773,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"ayu_DisableSimilarChannels" = "Disable similar channels";
|
||||
"ayu_CollapseSimilarChannels" = "Collapse similar channels";
|
||||
"ayu_HideSimilarChannelsTab" = "Hide similar channels tab";
|
||||
"ayu_UploadSpeedBoostPC" = "Upload speed boost";
|
||||
"ayu_MainFont" = "Application font";
|
||||
"ayu_MonospaceFont" = "Monospace font";
|
||||
"ayu_FontDefault" = "Default";
|
||||
@@ -4738,6 +4782,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"ayu_AyuForwardStatusLoadingMedia" = "Loading media…";
|
||||
"ayu_AyuForwardForwardingDescription" = "Please keep this window open while AyuGram is forwarding your messages.";
|
||||
"ayu_AyuForwardLoadingMediaDescription" = "Please keep this window open while AyuGram is downloading media from your messages.";
|
||||
"ayu_UserNotFoundMessage" = "User not found.";
|
||||
"ayu_DeleteDateMenuText" = "Delete Date";
|
||||
"ayu_ReadDateMenuText" = "Read Date";
|
||||
"ayu_ContentsReadDateMenuText" = "Contents Read Date";
|
||||
|
||||
@@ -11,5 +11,7 @@
|
||||
<file alias="ttl.tgs">../../animations/ttl.tgs</file>
|
||||
<file alias="discussion.tgs">../../animations/discussion.tgs</file>
|
||||
<file alias="stats.tgs">../../animations/stats.tgs</file>
|
||||
<file alias="voice_ttl_idle.tgs">../../animations/voice_ttl_idle.tgs</file>
|
||||
<file alias="voice_ttl_start.tgs">../../animations/voice_ttl_start.tgs</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -56,6 +56,6 @@
|
||||
</qresource>
|
||||
<qresource prefix="/misc">
|
||||
<file alias="default_shortcuts-custom.json">../../default_shortcuts-custom.json</file>
|
||||
<file alias="ayugram.desktop.desktop">../../../../lib/xdg/ayugram.desktop.desktop</file>
|
||||
<file alias="com.ayugram.desktop.desktop">../../../../lib/xdg/com.ayugram.desktop.desktop</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="4.13.1.0" />
|
||||
Version="4.14.3.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 4,13,1,0
|
||||
PRODUCTVERSION 4,13,1,0
|
||||
FILEVERSION 4,14,3,0
|
||||
PRODUCTVERSION 4,14,3,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Radolyn Labs"
|
||||
VALUE "FileDescription", "AyuGram Desktop"
|
||||
VALUE "FileVersion", "4.13.1.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
|
||||
VALUE "FileVersion", "4.14.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "AyuGram Desktop"
|
||||
VALUE "ProductVersion", "4.13.1.0"
|
||||
VALUE "ProductVersion", "4.14.3.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 4,13,1,0
|
||||
PRODUCTVERSION 4,13,1,0
|
||||
FILEVERSION 4,14,3,0
|
||||
PRODUCTVERSION 4,14,3,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Radolyn Labs"
|
||||
VALUE "FileDescription", "AyuGram Desktop Updater"
|
||||
VALUE "FileVersion", "4.13.1.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
|
||||
VALUE "FileVersion", "4.14.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "AyuGram Desktop"
|
||||
VALUE "ProductVersion", "4.13.1.0"
|
||||
VALUE "ProductVersion", "4.14.3.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -6,6 +6,7 @@ For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
@@ -25,6 +25,7 @@ struct SendOptions {
|
||||
bool silent = false;
|
||||
bool handleSupportSwitch = false;
|
||||
bool hideViaBot = false;
|
||||
crl::time ttlSeconds = 0;
|
||||
};
|
||||
[[nodiscard]] SendOptions DefaultSendWhenOnlineOptions();
|
||||
|
||||
|
||||
@@ -79,16 +79,19 @@ MTPInputMedia PrepareUploadedPhoto(
|
||||
not_null<HistoryItem*> item,
|
||||
RemoteFileInfo info) {
|
||||
using Flag = MTPDinputMediaUploadedPhoto::Flag;
|
||||
const auto spoiler = item->media()
|
||||
&& item->media()->hasSpoiler();
|
||||
const auto spoiler = item->media() && item->media()->hasSpoiler();
|
||||
const auto ttlSeconds = item->media()
|
||||
? item->media()->ttlSeconds()
|
||||
: 0;
|
||||
const auto flags = (spoiler ? Flag::f_spoiler : Flag())
|
||||
| (info.attachedStickers.empty() ? Flag() : Flag::f_stickers);
|
||||
| (info.attachedStickers.empty() ? Flag() : Flag::f_stickers)
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag());
|
||||
return MTP_inputMediaUploadedPhoto(
|
||||
MTP_flags(flags),
|
||||
info.file,
|
||||
MTP_vector<MTPInputDocument>(
|
||||
ranges::to<QVector<MTPInputDocument>>(info.attachedStickers)),
|
||||
MTP_int(0));
|
||||
MTP_int(ttlSeconds));
|
||||
}
|
||||
|
||||
MTPInputMedia PrepareUploadedDocument(
|
||||
@@ -98,12 +101,15 @@ MTPInputMedia PrepareUploadedDocument(
|
||||
return MTP_inputMediaEmpty();
|
||||
}
|
||||
using Flag = MTPDinputMediaUploadedDocument::Flag;
|
||||
const auto spoiler = item->media()
|
||||
&& item->media()->hasSpoiler();
|
||||
const auto spoiler = item->media() && item->media()->hasSpoiler();
|
||||
const auto ttlSeconds = item->media()
|
||||
? item->media()->ttlSeconds()
|
||||
: 0;
|
||||
const auto flags = (spoiler ? Flag::f_spoiler : Flag())
|
||||
| (info.thumb ? Flag::f_thumb : Flag())
|
||||
| (item->groupId() ? Flag::f_nosound_video : Flag())
|
||||
| (info.attachedStickers.empty() ? Flag::f_stickers : Flag());
|
||||
| (info.attachedStickers.empty() ? Flag::f_stickers : Flag())
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag());
|
||||
const auto document = item->media()->document();
|
||||
return MTP_inputMediaUploadedDocument(
|
||||
MTP_flags(flags),
|
||||
@@ -113,7 +119,7 @@ MTPInputMedia PrepareUploadedDocument(
|
||||
ComposeSendingDocumentAttributes(document),
|
||||
MTP_vector<MTPInputDocument>(
|
||||
ranges::to<QVector<MTPInputDocument>>(info.attachedStickers)),
|
||||
MTP_int(0));
|
||||
MTP_int(ttlSeconds));
|
||||
}
|
||||
|
||||
bool HasAttachedStickers(MTPInputMedia media) {
|
||||
|
||||
@@ -90,6 +90,7 @@ void MessagesSearch::searchRequest() {
|
||||
(_from
|
||||
? _from->input
|
||||
: MTP_inputPeerEmpty()),
|
||||
MTPInputPeer(), // saved_peer_id
|
||||
MTPint(), // top_msg_id
|
||||
MTP_inputMessagesFilterEmpty(),
|
||||
MTP_int(0), // min_date
|
||||
|
||||
@@ -42,6 +42,9 @@ void InnerFillMessagePostFlags(
|
||||
not_null<PeerData*> peer,
|
||||
MessageFlags &flags) {
|
||||
const auto anonymousPost = peer->amAnonymous();
|
||||
if (ShouldSendSilent(peer, options)) {
|
||||
flags |= MessageFlag::Silent;
|
||||
}
|
||||
if (!anonymousPost || options.sendAs) {
|
||||
flags |= MessageFlag::HasFromId;
|
||||
return;
|
||||
@@ -401,9 +404,6 @@ void SendConfirmedFile(
|
||||
const auto anonymousPost = peer->amAnonymous();
|
||||
const auto silentPost = ShouldSendSilent(peer, file->to.options);
|
||||
FillMessagePostFlags(action, peer, flags);
|
||||
if (silentPost) {
|
||||
flags |= MessageFlag::Silent;
|
||||
}
|
||||
if (file->to.options.scheduled) {
|
||||
flags |= MessageFlag::IsOrWasScheduled;
|
||||
|
||||
@@ -443,11 +443,30 @@ void SendConfirmedFile(
|
||||
MTPDocument(), // alt_document
|
||||
MTPint());
|
||||
} else if (file->type == SendMediaType::Audio) {
|
||||
const auto ttlSeconds = file->to.options.ttlSeconds;
|
||||
const auto isVoice = [&] {
|
||||
return file->document.match([](const MTPDdocumentEmpty &d) {
|
||||
return false;
|
||||
}, [](const MTPDdocument &d) {
|
||||
return ranges::any_of(d.vattributes().v, [&](
|
||||
const MTPDocumentAttribute &attribute) {
|
||||
using Att = MTPDdocumentAttributeAudio;
|
||||
return attribute.match([](const Att &data) -> bool {
|
||||
return data.vflags().v & Att::Flag::f_voice;
|
||||
}, [](const auto &) {
|
||||
return false;
|
||||
});
|
||||
});
|
||||
});
|
||||
}();
|
||||
using Flag = MTPDmessageMediaDocument::Flag;
|
||||
return MTP_messageMediaDocument(
|
||||
MTP_flags(MTPDmessageMediaDocument::Flag::f_document),
|
||||
MTP_flags(Flag::f_document
|
||||
| (isVoice ? Flag::f_voice : Flag())
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())),
|
||||
file->document,
|
||||
MTPDocument(), // alt_document
|
||||
MTPint());
|
||||
MTP_int(ttlSeconds));
|
||||
} else {
|
||||
Unexpected("Type in sendFilesConfirmed.");
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "mtproto/mtproto_dc_options.h"
|
||||
#include "data/notify/data_notify_settings.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "data/data_saved_messages.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_chat.h"
|
||||
@@ -44,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lang/lang_cloud_manager.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_item_helpers.h"
|
||||
#include "history/history_unread_things.h"
|
||||
#include "core/application.h"
|
||||
#include "storage/storage_account.h"
|
||||
@@ -1143,6 +1145,7 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
|
||||
? peerToMTP(_session->userPeerId())
|
||||
: MTP_peerUser(d.vuser_id())),
|
||||
MTP_peerUser(d.vuser_id()),
|
||||
MTPPeer(), // saved_peer_id
|
||||
d.vfwd_from() ? *d.vfwd_from() : MTPMessageFwdHeader(),
|
||||
MTP_long(d.vvia_bot_id().value_or_empty()),
|
||||
d.vreply_to() ? *d.vreply_to() : MTPMessageReplyHeader(),
|
||||
@@ -1174,6 +1177,7 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
|
||||
d.vid(),
|
||||
MTP_peerUser(d.vfrom_id()),
|
||||
MTP_peerChat(d.vchat_id()),
|
||||
MTPPeer(), // saved_peer_id
|
||||
d.vfwd_from() ? *d.vfwd_from() : MTPMessageFwdHeader(),
|
||||
MTP_long(d.vvia_bot_id().value_or_empty()),
|
||||
d.vreply_to() ? *d.vreply_to() : MTPMessageReplyHeader(),
|
||||
@@ -1234,11 +1238,12 @@ void Updates::applyUpdateNoPtsCheck(const MTPUpdate &update) {
|
||||
item->markMediaAndMentionRead();
|
||||
_session->data().requestItemRepaint(item);
|
||||
|
||||
if (item->out()
|
||||
&& item->history()->peer->isUser()
|
||||
&& !requestingDifference()) {
|
||||
item->history()->peer->asUser()->madeAction(
|
||||
base::unixtime::now());
|
||||
if (item->out()) {
|
||||
const auto user = item->history()->peer->asUser();
|
||||
if (user && !requestingDifference()) {
|
||||
user->madeAction(base::unixtime::now());
|
||||
}
|
||||
ClearMediaAsExpired(item);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -2236,6 +2241,16 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_updatePinnedSavedDialogs: {
|
||||
session().data().savedMessages().apply(
|
||||
update.c_updatePinnedSavedDialogs());
|
||||
} break;
|
||||
|
||||
case mtpc_updateSavedDialogPinned: {
|
||||
session().data().savedMessages().apply(
|
||||
update.c_updateSavedDialogPinned());
|
||||
} break;
|
||||
|
||||
case mtpc_updateChannel: {
|
||||
auto &d = update.c_updateChannel();
|
||||
if (const auto channel = session().data().channelLoaded(d.vchannel_id())) {
|
||||
|
||||
@@ -39,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_folder.h"
|
||||
#include "data/data_forum_topic.h"
|
||||
#include "data/data_forum.h"
|
||||
#include "data/data_saved_sublist.h"
|
||||
#include "data/data_search_controller.h"
|
||||
#include "data/data_scheduled_messages.h"
|
||||
#include "data/data_session.h"
|
||||
@@ -444,6 +445,26 @@ void ApiWrap::savePinnedOrder(not_null<Data::Forum*> forum) {
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ApiWrap::savePinnedOrder(not_null<Data::SavedMessages*> saved) {
|
||||
const auto &order = _session->data().pinnedChatsOrder(saved);
|
||||
const auto input = [](Dialogs::Key key) {
|
||||
if (const auto sublist = key.sublist()) {
|
||||
return MTP_inputDialogPeer(sublist->peer()->input);
|
||||
}
|
||||
Unexpected("Key type in pinnedDialogsOrder().");
|
||||
};
|
||||
auto peers = QVector<MTPInputDialogPeer>();
|
||||
peers.reserve(order.size());
|
||||
ranges::transform(
|
||||
order,
|
||||
ranges::back_inserter(peers),
|
||||
input);
|
||||
request(MTPmessages_ReorderPinnedSavedDialogs(
|
||||
MTP_flags(MTPmessages_ReorderPinnedSavedDialogs::Flag::f_force),
|
||||
MTP_vector(peers)
|
||||
)).send();
|
||||
}
|
||||
|
||||
void ApiWrap::toggleHistoryArchived(
|
||||
not_null<History*> history,
|
||||
bool archived,
|
||||
|
||||
@@ -34,6 +34,7 @@ class Forum;
|
||||
class ForumTopic;
|
||||
class Thread;
|
||||
class Story;
|
||||
class SavedMessages;
|
||||
} // namespace Data
|
||||
|
||||
namespace InlineBots {
|
||||
@@ -152,6 +153,7 @@ public:
|
||||
|
||||
void savePinnedOrder(Data::Folder *folder);
|
||||
void savePinnedOrder(not_null<Data::Forum*> forum);
|
||||
void savePinnedOrder(not_null<Data::SavedMessages*> saved);
|
||||
void toggleHistoryArchived(
|
||||
not_null<History*> history,
|
||||
bool archived,
|
||||
|
||||
@@ -59,54 +59,64 @@ void initialize()
|
||||
|
||||
settings = AyuGramSettings();
|
||||
|
||||
sendReadMessagesReactive.value() | rpl::filter([=](bool val)
|
||||
{
|
||||
return (val != settings->sendReadMessages);
|
||||
}) | start_with_next([=](bool val)
|
||||
{
|
||||
ghostModeEnabled =
|
||||
ghostModeEnabled_util(settings.value());
|
||||
}, lifetime);
|
||||
sendReadMessagesReactive.value() | rpl::filter(
|
||||
[=](bool val)
|
||||
{
|
||||
return (val != settings->sendReadMessages);
|
||||
}) | start_with_next(
|
||||
[=](bool val)
|
||||
{
|
||||
ghostModeEnabled =
|
||||
ghostModeEnabled_util(settings.value());
|
||||
}, lifetime);
|
||||
// ..
|
||||
sendReadStoriesReactive.value() | rpl::filter([=](bool val)
|
||||
{
|
||||
return (val != settings->sendReadStories);
|
||||
}) | start_with_next([=](bool val)
|
||||
{
|
||||
ghostModeEnabled =
|
||||
ghostModeEnabled_util(settings.value());
|
||||
}, lifetime);
|
||||
sendReadStoriesReactive.value() | rpl::filter(
|
||||
[=](bool val)
|
||||
{
|
||||
return (val != settings->sendReadStories);
|
||||
}) | start_with_next(
|
||||
[=](bool val)
|
||||
{
|
||||
ghostModeEnabled =
|
||||
ghostModeEnabled_util(settings.value());
|
||||
}, lifetime);
|
||||
// ..
|
||||
sendOnlinePacketsReactive.value() | rpl::filter([=](bool val)
|
||||
{
|
||||
return (val != settings->sendOnlinePackets);
|
||||
}) | start_with_next([=](bool val)
|
||||
{
|
||||
ghostModeEnabled =
|
||||
ghostModeEnabled_util(settings
|
||||
.value());
|
||||
}, lifetime);
|
||||
sendOnlinePacketsReactive.value() | rpl::filter(
|
||||
[=](bool val)
|
||||
{
|
||||
return (val != settings->sendOnlinePackets);
|
||||
}) | start_with_next(
|
||||
[=](bool val)
|
||||
{
|
||||
ghostModeEnabled =
|
||||
ghostModeEnabled_util(settings
|
||||
.value());
|
||||
}, lifetime);
|
||||
// ..
|
||||
sendUploadProgressReactive.value() | rpl::filter([=](bool val)
|
||||
{
|
||||
return (val != settings->sendUploadProgress);
|
||||
}) | start_with_next([=](bool val)
|
||||
{
|
||||
ghostModeEnabled =
|
||||
ghostModeEnabled_util(settings
|
||||
.value());
|
||||
}, lifetime);
|
||||
sendUploadProgressReactive.value() | rpl::filter(
|
||||
[=](bool val)
|
||||
{
|
||||
return (val != settings->sendUploadProgress);
|
||||
}) | start_with_next(
|
||||
[=](bool val)
|
||||
{
|
||||
ghostModeEnabled =
|
||||
ghostModeEnabled_util(settings
|
||||
.value());
|
||||
}, lifetime);
|
||||
// ..
|
||||
sendOfflinePacketAfterOnlineReactive.value() | rpl::filter([=](bool val)
|
||||
{
|
||||
return (val
|
||||
!= settings->sendOfflinePacketAfterOnline);
|
||||
}) | start_with_next([=](bool val)
|
||||
{
|
||||
ghostModeEnabled =
|
||||
ghostModeEnabled_util(
|
||||
settings.value());
|
||||
}, lifetime);
|
||||
sendOfflinePacketAfterOnlineReactive.value() | rpl::filter(
|
||||
[=](bool val)
|
||||
{
|
||||
return (val
|
||||
!= settings->sendOfflinePacketAfterOnline);
|
||||
}) | start_with_next(
|
||||
[=](bool val)
|
||||
{
|
||||
ghostModeEnabled =
|
||||
ghostModeEnabled_util(
|
||||
settings.value());
|
||||
}, lifetime);
|
||||
}
|
||||
|
||||
void postinitialize()
|
||||
@@ -150,7 +160,8 @@ void load()
|
||||
catch (...) {
|
||||
LOG(("AyuGramSettings: failed to parse settings file"));
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
catch (...) {
|
||||
LOG(("AyuGramSettings: failed to read settings file (not json-like)"));
|
||||
}
|
||||
postinitialize();
|
||||
@@ -259,6 +270,11 @@ void AyuGramSettings::set_hideSimilarChannels(bool val)
|
||||
hideSimilarChannels = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_uploadSpeedBoost(bool val)
|
||||
{
|
||||
uploadSpeedBoost = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_disableNotificationsDelay(bool val)
|
||||
{
|
||||
disableNotificationsDelay = val;
|
||||
@@ -279,7 +295,8 @@ void AyuGramSettings::set_appIcon(QString val)
|
||||
appIcon = std::move(val);
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_simpleQuotesAndReplies(bool val) {
|
||||
void AyuGramSettings::set_simpleQuotesAndReplies(bool val)
|
||||
{
|
||||
simpleQuotesAndReplies = val;
|
||||
}
|
||||
|
||||
@@ -300,6 +317,31 @@ void AyuGramSettings::set_recentStickersCount(int val)
|
||||
recentStickersCount = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_showReactionsPanelInContextMenu(int val)
|
||||
{
|
||||
showReactionsPanelInContextMenu = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_showViewsPanelInContextMenu(int val)
|
||||
{
|
||||
showViewsPanelInContextMenu = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_showHideMessageInContextMenu(int val)
|
||||
{
|
||||
showHideMessageInContextMenu = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_showUserMessagesInContextMenu(int val)
|
||||
{
|
||||
showUserMessagesInContextMenu = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_showMessageDetailsInContextMenu(int val)
|
||||
{
|
||||
showMessageDetailsInContextMenu = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_showLReadToggleInDrawer(bool val)
|
||||
{
|
||||
showLReadToggleInDrawer = val;
|
||||
@@ -346,9 +388,9 @@ void AyuGramSettings::set_showPeerId(int val)
|
||||
showPeerIdReactive = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_showMessageSeconds(bool val)
|
||||
void AyuGramSettings::set_hideNotificationCounters(bool val)
|
||||
{
|
||||
showMessageSeconds = val;
|
||||
hideNotificationCounters = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_hideAllChatsFolder(bool val)
|
||||
@@ -356,6 +398,11 @@ void AyuGramSettings::set_hideAllChatsFolder(bool val)
|
||||
hideAllChatsFolder = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_showMessageSeconds(bool val)
|
||||
{
|
||||
showMessageSeconds = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_stickerConfirmation(bool val)
|
||||
{
|
||||
stickerConfirmation = val;
|
||||
|
||||
@@ -9,27 +9,12 @@
|
||||
#include "lang_auto.h"
|
||||
#include "ayu/libs/json.hpp"
|
||||
#include "ayu/libs/json_ext.hpp"
|
||||
#include "ayu/ui/ayu_assets.h"
|
||||
#include "rpl/producer.h"
|
||||
|
||||
namespace AyuSettings
|
||||
{
|
||||
|
||||
const auto DEFAULT_ICON = QString("default");
|
||||
|
||||
const auto ALT_ICON = QString("alt");
|
||||
|
||||
const auto DISCORD_ICON = QString("discord");
|
||||
|
||||
const auto SPOTIFY_ICON = QString("spotify");
|
||||
|
||||
const auto EXTERA_ICON = QString("extera");
|
||||
|
||||
const auto NOTHING_ICON = QString("nothing");
|
||||
|
||||
const auto BARD_ICON = QString("bard");
|
||||
|
||||
const auto YAPLUS_ICON = QString("yaplus");
|
||||
|
||||
class AyuGramSettings
|
||||
{
|
||||
public:
|
||||
@@ -56,17 +41,30 @@ public:
|
||||
disableStories = false;
|
||||
collapseSimilarChannels = true;
|
||||
hideSimilarChannels = false;
|
||||
|
||||
uploadSpeedBoost = false;
|
||||
|
||||
disableNotificationsDelay = false;
|
||||
localPremium = false;
|
||||
copyUsernameAsLink = true;
|
||||
|
||||
// ~ Customization
|
||||
appIcon = DEFAULT_ICON;
|
||||
appIcon = AyuAssets::DEFAULT_ICON;
|
||||
simpleQuotesAndReplies = true;
|
||||
deletedMark = "🧹";
|
||||
editedMark = tr::lng_edited(tr::now);
|
||||
recentStickersCount = 50;
|
||||
|
||||
// context menu items
|
||||
// 0 - hide
|
||||
// 1 - show normally
|
||||
// 2 - show with SHIFT or CTRL pressed
|
||||
showReactionsPanelInContextMenu = 1;
|
||||
showViewsPanelInContextMenu = 1;
|
||||
showHideMessageInContextMenu = 0;
|
||||
showUserMessagesInContextMenu = 2;
|
||||
showMessageDetailsInContextMenu = 2;
|
||||
|
||||
showLReadToggleInDrawer = true;
|
||||
showSReadToggleInDrawer = true;
|
||||
showGhostToggleInDrawer = true;
|
||||
@@ -78,14 +76,15 @@ public:
|
||||
mainFont = "";
|
||||
monoFont = "";
|
||||
|
||||
hideNotificationCounters = false;
|
||||
hideAllChatsFolder = false;
|
||||
|
||||
/*
|
||||
* showPeerId = 0 means no ID shown
|
||||
* showPeerId = 1 means ID shown as for Telegram API devs
|
||||
* showPeerId = 2 means ID shown as for Bot API devs (-100)
|
||||
*/
|
||||
showPeerId = 2;
|
||||
|
||||
hideAllChatsFolder = false;
|
||||
showMessageSeconds = false;
|
||||
|
||||
// ~ Confirmations
|
||||
@@ -99,114 +98,118 @@ public:
|
||||
bool sendOnlinePackets;
|
||||
bool sendUploadProgress;
|
||||
bool sendOfflinePacketAfterOnline;
|
||||
|
||||
bool markReadAfterSend;
|
||||
bool markReadAfterReaction;
|
||||
bool markReadAfterPoll;
|
||||
bool useScheduledMessages;
|
||||
|
||||
bool saveDeletedMessages;
|
||||
bool saveMessagesHistory;
|
||||
|
||||
bool disableAds;
|
||||
bool disableStories;
|
||||
bool collapseSimilarChannels;
|
||||
bool hideSimilarChannels;
|
||||
|
||||
bool uploadSpeedBoost;
|
||||
|
||||
bool disableNotificationsDelay;
|
||||
bool localPremium;
|
||||
bool copyUsernameAsLink;
|
||||
|
||||
QString appIcon;
|
||||
bool simpleQuotesAndReplies;
|
||||
QString deletedMark;
|
||||
QString editedMark;
|
||||
int recentStickersCount;
|
||||
|
||||
int showReactionsPanelInContextMenu;
|
||||
int showViewsPanelInContextMenu;
|
||||
int showHideMessageInContextMenu;
|
||||
int showUserMessagesInContextMenu;
|
||||
int showMessageDetailsInContextMenu;
|
||||
|
||||
bool showLReadToggleInDrawer;
|
||||
bool showSReadToggleInDrawer;
|
||||
bool showGhostToggleInDrawer;
|
||||
bool showStreamerToggleInDrawer;
|
||||
|
||||
bool showGhostToggleInTray;
|
||||
bool showStreamerToggleInTray;
|
||||
|
||||
QString mainFont;
|
||||
QString monoFont;
|
||||
int showPeerId;
|
||||
|
||||
bool hideNotificationCounters;
|
||||
bool hideAllChatsFolder;
|
||||
|
||||
int showPeerId;
|
||||
bool showMessageSeconds;
|
||||
|
||||
bool stickerConfirmation;
|
||||
bool gifConfirmation;
|
||||
bool voiceConfirmation;
|
||||
|
||||
public:
|
||||
void set_sendReadMessages(bool val);
|
||||
|
||||
void set_sendReadStories(bool val);
|
||||
|
||||
void set_sendOnlinePackets(bool val);
|
||||
|
||||
void set_sendUploadProgress(bool val);
|
||||
|
||||
void set_sendOfflinePacketAfterOnline(bool val);
|
||||
|
||||
void set_ghostModeEnabled(bool val);
|
||||
|
||||
void set_markReadAfterSend(bool val);
|
||||
|
||||
void set_markReadAfterReaction(bool val);
|
||||
|
||||
void set_markReadAfterPoll(bool val);
|
||||
|
||||
void set_useScheduledMessages(bool val);
|
||||
|
||||
void set_saveDeletedMessages(bool val);
|
||||
|
||||
void set_saveMessagesHistory(bool val);
|
||||
|
||||
void set_disableAds(bool val);
|
||||
|
||||
void set_disableStories(bool val);
|
||||
|
||||
void set_collapseSimilarChannels(bool val);
|
||||
|
||||
void set_hideSimilarChannels(bool val);
|
||||
|
||||
void set_uploadSpeedBoost(bool val);
|
||||
|
||||
void set_disableNotificationsDelay(bool val);
|
||||
|
||||
void set_localPremium(bool val);
|
||||
|
||||
void set_copyUsernameAsLink(bool val);
|
||||
|
||||
void set_appIcon(QString val);
|
||||
|
||||
void set_simpleQuotesAndReplies(bool val);
|
||||
|
||||
void set_deletedMark(QString val);
|
||||
|
||||
void set_editedMark(QString val);
|
||||
|
||||
void set_recentStickersCount(int val);
|
||||
|
||||
void set_showReactionsPanelInContextMenu(int val);
|
||||
void set_showViewsPanelInContextMenu(int val);
|
||||
void set_showHideMessageInContextMenu(int val);
|
||||
void set_showUserMessagesInContextMenu(int val);
|
||||
void set_showMessageDetailsInContextMenu(int val);
|
||||
|
||||
void set_showLReadToggleInDrawer(bool val);
|
||||
|
||||
void set_showSReadToggleInDrawer(bool val);
|
||||
|
||||
void set_showGhostToggleInDrawer(bool val);
|
||||
|
||||
void set_showStreamerToggleInDrawer(bool val);
|
||||
|
||||
void set_showGhostToggleInTray(bool val);
|
||||
|
||||
void set_showStreamerToggleInTray(bool val);
|
||||
|
||||
void set_mainFont(QString val);
|
||||
|
||||
void set_monoFont(QString val);
|
||||
|
||||
void set_showPeerId(int val);
|
||||
|
||||
void set_showMessageSeconds(bool val);
|
||||
|
||||
void set_hideNotificationCounters(bool val);
|
||||
void set_hideAllChatsFolder(bool val);
|
||||
|
||||
void set_showPeerId(int val);
|
||||
void set_showMessageSeconds(bool val);
|
||||
|
||||
void set_stickerConfirmation(bool val);
|
||||
|
||||
void set_gifConfirmation(bool val);
|
||||
|
||||
void set_voiceConfirmation(bool val);
|
||||
};
|
||||
|
||||
@@ -227,6 +230,7 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(
|
||||
disableStories,
|
||||
collapseSimilarChannels,
|
||||
hideSimilarChannels,
|
||||
uploadSpeedBoost,
|
||||
disableNotificationsDelay,
|
||||
localPremium,
|
||||
copyUsernameAsLink,
|
||||
@@ -235,6 +239,11 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(
|
||||
deletedMark,
|
||||
editedMark,
|
||||
recentStickersCount,
|
||||
showReactionsPanelInContextMenu,
|
||||
showViewsPanelInContextMenu,
|
||||
showHideMessageInContextMenu,
|
||||
showUserMessagesInContextMenu,
|
||||
showMessageDetailsInContextMenu,
|
||||
showLReadToggleInDrawer,
|
||||
showSReadToggleInDrawer,
|
||||
showGhostToggleInDrawer,
|
||||
@@ -243,9 +252,10 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(
|
||||
showStreamerToggleInTray,
|
||||
mainFont,
|
||||
monoFont,
|
||||
hideNotificationCounters,
|
||||
hideAllChatsFolder,
|
||||
showPeerId,
|
||||
showMessageSeconds,
|
||||
hideAllChatsFolder,
|
||||
stickerConfirmation,
|
||||
gifConfirmation,
|
||||
voiceConfirmation
|
||||
@@ -254,17 +264,14 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(
|
||||
AyuGramSettings &getInstance();
|
||||
|
||||
void load();
|
||||
|
||||
void save();
|
||||
|
||||
rpl::producer<QString> get_deletedMarkReactive();
|
||||
|
||||
rpl::producer<QString> get_editedMarkReactive();
|
||||
|
||||
rpl::producer<int> get_showPeerIdReactive();
|
||||
|
||||
bool get_ghostModeEnabled();
|
||||
|
||||
rpl::producer<bool> get_ghostModeEnabledReactive();
|
||||
|
||||
}
|
||||
|
||||
@@ -8,4 +8,31 @@
|
||||
|
||||
namespace AyuState
|
||||
{
|
||||
|
||||
std::unordered_map<PeerId, std::unordered_set<MsgId>> hiddenMessages;
|
||||
|
||||
void hide(PeerId peerId, MsgId messageId)
|
||||
{
|
||||
hiddenMessages[peerId].insert(messageId);
|
||||
}
|
||||
|
||||
void hide(not_null<HistoryItem *> item)
|
||||
{
|
||||
hide(item->history()->peer->id, item->id);
|
||||
}
|
||||
|
||||
bool isHidden(PeerId peerId, MsgId messageId)
|
||||
{
|
||||
auto it = hiddenMessages.find(peerId);
|
||||
if (it != hiddenMessages.end()) {
|
||||
return it->second.find(messageId) != it->second.end();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isHidden(not_null<HistoryItem *> item)
|
||||
{
|
||||
return isHidden(item->history()->peer->id, item->id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,37 +8,15 @@
|
||||
|
||||
#include "ayu_settings.h"
|
||||
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
|
||||
namespace AyuState
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
class AyuStateVariable
|
||||
{
|
||||
public:
|
||||
bool val;
|
||||
int resetAfter;
|
||||
};
|
||||
|
||||
AyuStateVariable allowSendReadPacket;
|
||||
|
||||
bool processVariable(AyuStateVariable &variable)
|
||||
{
|
||||
if (variable.resetAfter == -1) {
|
||||
return variable.val;
|
||||
}
|
||||
|
||||
variable.resetAfter -= 1;
|
||||
auto val = variable.val;
|
||||
|
||||
if (variable.resetAfter == 0) {
|
||||
variable.val = false;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
}
|
||||
void hide(PeerId peerId, MsgId messageId);
|
||||
void hide(not_null<HistoryItem *> item);
|
||||
bool isHidden(PeerId peerId, MsgId messageId);
|
||||
bool isHidden(not_null<HistoryItem *> item);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
// This is the source code of AyuGram for Desktop.
|
||||
//
|
||||
// We do not and cannot prevent the use of our code,
|
||||
// but be respectful and credit the original author.
|
||||
//
|
||||
// Copyright @Radolyn, 2023
|
||||
#include "ayu/ayu_url_handlers.h"
|
||||
|
||||
#include "base/qthelp_url.h"
|
||||
|
||||
#include "lang_auto.h"
|
||||
#include "mainwindow.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ayu/utils/telegram_helpers.h"
|
||||
#include "boxes/abstract_box.h"
|
||||
#include "core/application.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
|
||||
namespace AyuUrlHandlers
|
||||
{
|
||||
|
||||
bool ResolveUser(
|
||||
Window::SessionController *controller,
|
||||
const Match &match,
|
||||
const QVariant &context)
|
||||
{
|
||||
if (!controller) {
|
||||
return false;
|
||||
}
|
||||
const auto params = url_parse_params(
|
||||
match->captured(1),
|
||||
qthelp::UrlParamNameTransform::ToLower);
|
||||
const auto userId = params.value(qsl("id")).toLongLong();
|
||||
if (!userId) {
|
||||
return false;
|
||||
}
|
||||
const auto peer = controller->session().data().peerLoaded(static_cast<PeerId>(userId));
|
||||
if (peer != nullptr) {
|
||||
controller->showPeerInfo(peer);
|
||||
return true;
|
||||
}
|
||||
|
||||
searchById(userId, &controller->session(), false, [=](const QString &title, UserData *data)
|
||||
{
|
||||
if (data) {
|
||||
controller->showPeerInfo(data);
|
||||
return;
|
||||
}
|
||||
|
||||
Core::App().hideMediaView();
|
||||
Ui::show(Ui::MakeInformBox(tr::ayu_UserNotFoundMessage()));
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HandleAyu(
|
||||
Window::SessionController *controller,
|
||||
const Match &match,
|
||||
const QVariant &context)
|
||||
{
|
||||
if (!controller) {
|
||||
return false;
|
||||
}
|
||||
controller->showToast(QString(":3"), 500);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// This is the source code of AyuGram for Desktop.
|
||||
//
|
||||
// We do not and cannot prevent the use of our code,
|
||||
// but be respectful and credit the original author.
|
||||
//
|
||||
// Copyright @Radolyn, 2023
|
||||
#pragma once
|
||||
|
||||
#include "window/window_session_controller.h"
|
||||
#include "base/qthelp_regex.h"
|
||||
|
||||
namespace AyuUrlHandlers
|
||||
{
|
||||
|
||||
using Match = qthelp::RegularExpressionMatch;
|
||||
|
||||
bool ResolveUser(
|
||||
Window::SessionController *controller,
|
||||
const Match &match,
|
||||
const QVariant &context);
|
||||
|
||||
bool HandleAyu(
|
||||
Window::SessionController *controller,
|
||||
const Match &match,
|
||||
const QVariant &context);
|
||||
|
||||
}
|
||||
@@ -13,6 +13,9 @@ static QImage LAST_LOADED;
|
||||
|
||||
static QImage LAST_LOADED_NO_MARGIN;
|
||||
|
||||
namespace AyuAssets
|
||||
{
|
||||
|
||||
void loadAppIco()
|
||||
{
|
||||
auto settings = &AyuSettings::getInstance();
|
||||
@@ -62,3 +65,5 @@ QImage currentAppLogoNoMargin()
|
||||
loadIcons();
|
||||
return LAST_LOADED_NO_MARGIN;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,27 @@
|
||||
// Copyright @Radolyn, 2023
|
||||
#pragma once
|
||||
|
||||
#define ICON(name, value) const auto name##_ICON = QStringLiteral(value)
|
||||
|
||||
namespace AyuAssets
|
||||
{
|
||||
|
||||
ICON(DEFAULT, "default");
|
||||
|
||||
ICON(ALT, "alt");
|
||||
|
||||
ICON(DISCORD, "discord");
|
||||
|
||||
ICON(SPOTIFY, "spotify");
|
||||
|
||||
ICON(EXTERA, "extera");
|
||||
|
||||
ICON(NOTHING, "nothing");
|
||||
|
||||
ICON(BARD, "bard");
|
||||
|
||||
ICON(YAPLUS, "yaplus");
|
||||
|
||||
void loadAppIco();
|
||||
|
||||
QImage loadPreview(QString name);
|
||||
@@ -13,3 +34,5 @@ QImage loadPreview(QString name);
|
||||
QString currentAppLogoName();
|
||||
QImage currentAppLogo();
|
||||
QImage currentAppLogoNoMargin();
|
||||
|
||||
}
|
||||
|
||||
@@ -5,13 +5,18 @@
|
||||
//
|
||||
// Copyright @Radolyn, 2023
|
||||
#include "ayu/ui/context_menu/context_menu.h"
|
||||
#include "ayu/utils/qt_key_modifiers_extended.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "lang_auto.h"
|
||||
#include "ayu/ayu_state.h"
|
||||
#include "ayu/messages/ayu_messages_controller.h"
|
||||
#include "ayu/ui/boxes/message_history_box.h"
|
||||
#include "ayu/ui/context_menu/menu_item_subtext.h"
|
||||
|
||||
#include "mainwidget.h"
|
||||
#include "core/mime_type.h"
|
||||
#include "styles/style_ayu_icons.h"
|
||||
#include "window/window_peer_menu.h"
|
||||
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
|
||||
#include "ayu/ui/sections/edited/edited_log_section.h"
|
||||
@@ -21,6 +26,11 @@
|
||||
namespace AyuUi
|
||||
{
|
||||
|
||||
bool needToShowItem(int state)
|
||||
{
|
||||
return state == 1 || state == 2 && base::IsExtendedContextMenuModifierPressed();
|
||||
}
|
||||
|
||||
void AddHistoryAction(not_null<Ui::PopupMenu *> menu, HistoryItem *item)
|
||||
{
|
||||
if (AyuMessages::getInstance().hasRevisions(item)) {
|
||||
@@ -34,21 +44,28 @@ void AddHistoryAction(not_null<Ui::PopupMenu *> menu, HistoryItem *item)
|
||||
|
||||
void AddHideMessageAction(not_null<Ui::PopupMenu *> menu, HistoryItem *item)
|
||||
{
|
||||
// todo: rework
|
||||
// const auto settings = &AyuSettings::getInstance();
|
||||
// const auto history = item->history();
|
||||
// menu->addAction(tr::ayu_ContextHideMessage(tr::now), [=]()
|
||||
// {
|
||||
// const auto initSaveDeleted = settings->saveDeletedMessages;
|
||||
//
|
||||
// settings->set_saveDeletedMessages(false);
|
||||
// history->destroyMessage(item);
|
||||
// settings->set_saveDeletedMessages(initSaveDeleted);
|
||||
// }, &st::menuIconClear);
|
||||
const auto settings = &AyuSettings::getInstance();
|
||||
if (!needToShowItem(settings->showHideMessageInContextMenu)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto history = item->history();
|
||||
menu->addAction(tr::ayu_ContextHideMessage(tr::now), [=]()
|
||||
{
|
||||
item->destroy();
|
||||
history->requestChatListMessage();
|
||||
|
||||
AyuState::hide(item);
|
||||
}, &st::menuIconClear);
|
||||
}
|
||||
|
||||
void AddUserMessagesAction(not_null<Ui::PopupMenu *> menu, HistoryItem *item)
|
||||
{
|
||||
const auto settings = &AyuSettings::getInstance();
|
||||
if (!needToShowItem(settings->showUserMessagesInContextMenu)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (item->history()->peer->isChat() || item->history()->peer->isMegagroup()) {
|
||||
menu->addAction(tr::ayu_UserMessagesMenuText(tr::now), [=]
|
||||
{
|
||||
@@ -60,15 +77,202 @@ void AddUserMessagesAction(not_null<Ui::PopupMenu *> menu, HistoryItem *item)
|
||||
? Dialogs::Key{item->topic()}
|
||||
: Dialogs::Key{item->history()}
|
||||
: Dialogs::Key();
|
||||
mainWidget->content()->searchMessages("", key, item->from()->asUser());
|
||||
mainWidget->content()->searchMessages(QString(), key, item->from()->asUser());
|
||||
}
|
||||
}
|
||||
}, &st::menuIconInfo);
|
||||
}, &st::menuIconTTL);
|
||||
}
|
||||
}
|
||||
|
||||
void AddMessageDetailsAction(not_null<Ui::PopupMenu *> menu, HistoryItem *item)
|
||||
{
|
||||
const auto settings = &AyuSettings::getInstance();
|
||||
if (!needToShowItem(settings->showMessageDetailsInContextMenu)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (item->isLocal()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto view = item->mainView();
|
||||
const auto forwarded = item->Get<HistoryMessageForwarded>();
|
||||
const auto views = item->Get<HistoryMessageViews>();
|
||||
const auto media = item->media();
|
||||
|
||||
const auto isSticker = media && media->document() && media->document()->sticker();
|
||||
const auto isForwarded = forwarded && !forwarded->story && forwarded->psaType.isEmpty();
|
||||
|
||||
const auto messageId = QString::number(item->id.bare);
|
||||
const auto messageDate = base::unixtime::parse(item->date());
|
||||
const auto messageEditDate = base::unixtime::parse(view ? view->displayedEditDate() : TimeId(0));
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma ide diagnostic ignored "NullDereference"
|
||||
const auto messageForwardedDate =
|
||||
isForwarded ? base::unixtime::parse(forwarded->originalDate)
|
||||
: QDateTime();
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
const auto
|
||||
messageViews = item->hasViews() && item->viewsCount() > 0 ? QString::number(item->viewsCount()) : QString();
|
||||
const auto messageForwards = views && views->forwardsCount > 0 ? QString::number(views->forwardsCount) : QString();
|
||||
|
||||
const auto mediaSize = media ? getMediaSize(item) : QString();
|
||||
const auto mediaMime = media ? getMediaMime(item) : QString();
|
||||
// todo: bitrate (?)
|
||||
const auto mediaName = media ? getMediaName(item) : QString();
|
||||
const auto mediaResolution = media ? getMediaResolution(item) : QString();
|
||||
const auto mediaDC = media ? getMediaDC(item) : QString();
|
||||
|
||||
const auto hasAnyPostField =
|
||||
!messageViews.isEmpty() ||
|
||||
!messageForwards.isEmpty();
|
||||
|
||||
const auto hasAnyMediaField =
|
||||
!mediaSize.isEmpty() ||
|
||||
!mediaMime.isEmpty() ||
|
||||
!mediaName.isEmpty() ||
|
||||
!mediaResolution.isEmpty() ||
|
||||
!mediaDC.isEmpty();
|
||||
|
||||
const auto callback = Ui::Menu::CreateAddActionCallback(menu);
|
||||
|
||||
callback(Window::PeerMenuCallback::Args{
|
||||
.text = tr::ayu_MessageDetailsPC(tr::now),
|
||||
.handler = nullptr,
|
||||
.icon = &st::menuIconInfo,
|
||||
.fillSubmenu = [&](not_null<Ui::PopupMenu *> menu2)
|
||||
{
|
||||
if (hasAnyPostField) {
|
||||
if (!messageViews.isEmpty()) {
|
||||
menu2->addAction(Ui::ContextActionWithSubText(
|
||||
menu2->menu(),
|
||||
st::menuIconShowInChat,
|
||||
tr::ayu_MessageDetailsViewsPC(tr::now),
|
||||
messageViews
|
||||
));
|
||||
}
|
||||
|
||||
if (!messageForwards.isEmpty()) {
|
||||
menu2->addAction(Ui::ContextActionWithSubText(
|
||||
menu2->menu(),
|
||||
st::menuIconViewReplies,
|
||||
tr::ayu_MessageDetailsSharesPC(tr::now),
|
||||
messageForwards
|
||||
));
|
||||
}
|
||||
|
||||
menu2->addSeparator();
|
||||
}
|
||||
|
||||
menu2->addAction(Ui::ContextActionWithSubText(
|
||||
menu2->menu(),
|
||||
st::menuIconInfo,
|
||||
QString("ID"),
|
||||
messageId
|
||||
));
|
||||
|
||||
menu2->addAction(Ui::ContextActionWithSubText(
|
||||
menu2->menu(),
|
||||
st::menuIconSchedule,
|
||||
tr::ayu_MessageDetailsDatePC(tr::now),
|
||||
formatDateTime(messageDate)
|
||||
));
|
||||
|
||||
if (view && view->displayedEditDate()) {
|
||||
menu2->addAction(Ui::ContextActionWithSubText(
|
||||
menu2->menu(),
|
||||
st::menuIconEdit,
|
||||
tr::ayu_MessageDetailsEditedDatePC(tr::now),
|
||||
formatDateTime(messageEditDate)
|
||||
));
|
||||
}
|
||||
|
||||
if (isForwarded) {
|
||||
menu2->addAction(Ui::ContextActionWithSubText(
|
||||
menu2->menu(),
|
||||
st::menuIconTTL,
|
||||
tr::ayu_MessageDetailsForwardedDatePC(tr::now),
|
||||
formatDateTime(messageForwardedDate)
|
||||
));
|
||||
}
|
||||
|
||||
if (media && hasAnyMediaField) {
|
||||
menu2->addSeparator();
|
||||
|
||||
if (!mediaSize.isEmpty()) {
|
||||
menu2->addAction(Ui::ContextActionWithSubText(
|
||||
menu2->menu(),
|
||||
st::menuIconDownload,
|
||||
tr::ayu_MessageDetailsFileSizePC(tr::now),
|
||||
mediaSize
|
||||
));
|
||||
}
|
||||
|
||||
if (!mediaMime.isEmpty()) {
|
||||
const auto mime = Core::MimeTypeForName(mediaMime);
|
||||
|
||||
menu2->addAction(Ui::ContextActionWithSubText(
|
||||
menu2->menu(),
|
||||
st::menuIconShowAll,
|
||||
tr::ayu_MessageDetailsMimeTypePC(tr::now),
|
||||
mime.name()
|
||||
));
|
||||
}
|
||||
|
||||
if (!mediaName.isEmpty()) {
|
||||
auto const shortified = mediaName.length() > 20 ? "…" + mediaName.right(20) : mediaName;
|
||||
|
||||
menu2->addAction(Ui::ContextActionWithSubText(
|
||||
menu2->menu(),
|
||||
st::ayuEditsHistoryIcon,
|
||||
tr::ayu_MessageDetailsFileNamePC(tr::now),
|
||||
shortified,
|
||||
[=]
|
||||
{
|
||||
QGuiApplication::clipboard()->setText(mediaName);
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
if (!mediaResolution.isEmpty()) {
|
||||
menu2->addAction(Ui::ContextActionWithSubText(
|
||||
menu2->menu(),
|
||||
st::menuIconStats,
|
||||
tr::ayu_MessageDetailsResolutionPC(tr::now),
|
||||
mediaResolution
|
||||
));
|
||||
}
|
||||
|
||||
if (!mediaDC.isEmpty()) {
|
||||
menu2->addAction(Ui::ContextActionWithSubText(
|
||||
menu2->menu(),
|
||||
st::menuIconBoosts,
|
||||
tr::ayu_MessageDetailsDatacenterPC(tr::now),
|
||||
mediaDC
|
||||
));
|
||||
}
|
||||
|
||||
if (isSticker) {
|
||||
const auto authorId = media->document()->sticker()->set.id >> 32;
|
||||
menu2->addAction(Ui::ContextActionStickerAuthor(
|
||||
menu2->menu(),
|
||||
&item->history()->session(),
|
||||
authorId
|
||||
));
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
void AddReadUntilAction(not_null<Ui::PopupMenu *> menu, HistoryItem *item)
|
||||
{
|
||||
if (item->isLocal()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto settings = &AyuSettings::getInstance();
|
||||
if (settings->sendReadMessages) {
|
||||
return;
|
||||
|
||||
@@ -78,17 +78,23 @@
|
||||
#include "history/view/history_view_context_menu.h"
|
||||
|
||||
#include "ayu/ayu_settings.h"
|
||||
#include "ayu/utils/qt_key_modifiers_extended.h"
|
||||
|
||||
#include "styles/style_info.h"
|
||||
|
||||
namespace AyuUi
|
||||
{
|
||||
|
||||
bool needToShowItem(int state);
|
||||
|
||||
void AddHistoryAction(not_null<Ui::PopupMenu *> menu, HistoryItem *item);
|
||||
|
||||
void AddHideMessageAction(not_null<Ui::PopupMenu *> menu, HistoryItem *item);
|
||||
|
||||
void AddUserMessagesAction(not_null<Ui::PopupMenu *> menu, HistoryItem *item);
|
||||
|
||||
void AddMessageDetailsAction(not_null<Ui::PopupMenu *> menu, HistoryItem *item);
|
||||
|
||||
void AddReadUntilAction(not_null<Ui::PopupMenu *> menu, HistoryItem *item);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,311 @@
|
||||
// This is the source code of AyuGram for Desktop.
|
||||
//
|
||||
// We do not and cannot prevent the use of our code,
|
||||
// but be respectful and credit the original author.
|
||||
//
|
||||
// Copyright @Radolyn, 2023
|
||||
#include <utility>
|
||||
|
||||
#include "ayu/ui/context_menu/menu_item_subtext.h"
|
||||
|
||||
#include "ayu/utils/telegram_helpers.h"
|
||||
#include "ayu/database/entities.h"
|
||||
#include "mainwindow.h"
|
||||
#include "qguiapplication.h"
|
||||
|
||||
#include "ui/widgets/menu/menu_action.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/painter.h"
|
||||
#include "data/data_user.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
#include "window/window_session_controller.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
namespace
|
||||
{
|
||||
|
||||
class ActionWithSubText : public Menu::ItemBase
|
||||
{
|
||||
public:
|
||||
ActionWithSubText(
|
||||
not_null<RpWidget *> parent,
|
||||
const style::Menu &st,
|
||||
const style::icon &icon,
|
||||
Fn<void()> callback,
|
||||
const QString &title,
|
||||
QString subtext);
|
||||
|
||||
bool isEnabled() const override;
|
||||
not_null<QAction *> action() const override;
|
||||
|
||||
void handleKeyPress(not_null<QKeyEvent *> e) override;
|
||||
|
||||
protected:
|
||||
QPoint prepareRippleStartPosition() const override;
|
||||
QImage prepareRippleMask() const override;
|
||||
|
||||
int contentHeight() const override;
|
||||
|
||||
void prepare(const QString &title);
|
||||
void paint(Painter &p);
|
||||
|
||||
const not_null<QAction *> _dummyAction;
|
||||
const style::Menu &_st;
|
||||
const style::icon &_icon;
|
||||
|
||||
Text::String _title;
|
||||
int _textWidth = 0;
|
||||
QString _subText;
|
||||
const int _height;
|
||||
|
||||
};
|
||||
|
||||
class ActionStickerPackAuthor final : public ActionWithSubText
|
||||
{
|
||||
public:
|
||||
ActionStickerPackAuthor(not_null<Menu::Menu *> menu, not_null<Main::Session *> session, ID authorId);
|
||||
|
||||
private:
|
||||
not_null<Main::Session *> _session;
|
||||
|
||||
void searchAuthor(ID authorId);
|
||||
};
|
||||
|
||||
TextParseOptions MenuTextOptions = {
|
||||
TextParseLinks, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
|
||||
ActionWithSubText::ActionWithSubText(
|
||||
not_null<RpWidget *> parent,
|
||||
const style::Menu &st,
|
||||
const style::icon &icon,
|
||||
Fn<void()> callback,
|
||||
const QString &title,
|
||||
QString subtext)
|
||||
: ItemBase(parent, st),
|
||||
_dummyAction(new QAction(parent)),
|
||||
_st(st),
|
||||
_icon(icon),
|
||||
_subText(std::move(subtext)),
|
||||
_height(st::ttlItemPadding.top()
|
||||
+ _st.itemStyle.font->height
|
||||
+ st::ttlItemTimerFont->height
|
||||
+ st::ttlItemPadding.bottom())
|
||||
{
|
||||
setAcceptBoth(true);
|
||||
initResizeHook(parent->sizeValue());
|
||||
setClickedCallback(std::move(callback));
|
||||
|
||||
paintRequest(
|
||||
) | rpl::start_with_next([=]
|
||||
{
|
||||
Painter p(this);
|
||||
paint(p);
|
||||
}, lifetime());
|
||||
|
||||
enableMouseSelecting();
|
||||
prepare(title);
|
||||
}
|
||||
|
||||
void ActionWithSubText::paint(Painter &p)
|
||||
{
|
||||
const auto selected = isSelected();
|
||||
if (selected && _st.itemBgOver->c.alpha() < 255) {
|
||||
p.fillRect(0, 0, width(), _height, _st.itemBg);
|
||||
}
|
||||
p.fillRect(0, 0, width(), _height, selected ? _st.itemBgOver : _st.itemBg);
|
||||
if (isEnabled()) {
|
||||
paintRipple(p, 0, 0);
|
||||
}
|
||||
|
||||
const auto normalHeight = _st.itemPadding.top()
|
||||
+ _st.itemStyle.font->height
|
||||
+ _st.itemPadding.bottom();
|
||||
const auto deltaHeight = _height - normalHeight;
|
||||
_icon.paint(
|
||||
p,
|
||||
_st.itemIconPosition + QPoint(0, deltaHeight / 2),
|
||||
width());
|
||||
|
||||
p.setPen(selected ? _st.itemFgOver : _st.itemFg);
|
||||
_title.drawLeftElided(
|
||||
p,
|
||||
_st.itemPadding.left(),
|
||||
st::ttlItemPadding.top(),
|
||||
_textWidth,
|
||||
width());
|
||||
|
||||
p.setFont(st::ttlItemTimerFont);
|
||||
p.setPen(selected ? _st.itemFgShortcutOver : _st.itemFgShortcut);
|
||||
p.drawTextLeft(
|
||||
_st.itemPadding.left(),
|
||||
st::ttlItemPadding.top() + _st.itemStyle.font->height,
|
||||
width(),
|
||||
_subText);
|
||||
}
|
||||
|
||||
void ActionWithSubText::prepare(const QString &title)
|
||||
{
|
||||
_title.setMarkedText(
|
||||
_st.itemStyle,
|
||||
{title},
|
||||
MenuTextOptions);
|
||||
const auto textWidth = _title.maxWidth();
|
||||
const auto &padding = _st.itemPadding;
|
||||
|
||||
const auto goodWidth = padding.left()
|
||||
+ textWidth
|
||||
+ padding.right();
|
||||
const auto subTextMaxWidth = padding.left()
|
||||
+ st::ttlItemTimerFont->width(_subText)
|
||||
+ padding.right();
|
||||
|
||||
const auto w = std::clamp(
|
||||
std::max({goodWidth, subTextMaxWidth}),
|
||||
_st.widthMin,
|
||||
_st.widthMax);
|
||||
_textWidth = w - (goodWidth - textWidth);
|
||||
setMinWidth(w);
|
||||
update();
|
||||
}
|
||||
|
||||
bool ActionWithSubText::isEnabled() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
not_null<QAction *> ActionWithSubText::action() const
|
||||
{
|
||||
return _dummyAction;
|
||||
}
|
||||
|
||||
QPoint ActionWithSubText::prepareRippleStartPosition() const
|
||||
{
|
||||
return mapFromGlobal(QCursor::pos());
|
||||
}
|
||||
|
||||
QImage ActionWithSubText::prepareRippleMask() const
|
||||
{
|
||||
return Ui::RippleAnimation::RectMask(size());
|
||||
}
|
||||
|
||||
int ActionWithSubText::contentHeight() const
|
||||
{
|
||||
return _height;
|
||||
}
|
||||
|
||||
void ActionWithSubText::handleKeyPress(not_null<QKeyEvent *> e)
|
||||
{
|
||||
if (!isSelected()) {
|
||||
return;
|
||||
}
|
||||
const auto key = e->key();
|
||||
if (key == Qt::Key_Enter || key == Qt::Key_Return) {
|
||||
setClicked(Menu::TriggeredSource::Keyboard);
|
||||
}
|
||||
}
|
||||
|
||||
ActionStickerPackAuthor::ActionStickerPackAuthor(not_null<Menu::Menu *> menu,
|
||||
not_null<Main::Session *> session,
|
||||
ID authorId)
|
||||
: ActionWithSubText(menu, menu->st(), st::menuIconStickers, [=]
|
||||
{ }, tr::ayu_MessageDetailsPackOwnerPC(tr::now), QString(tr::ayu_MessageDetailsPackOwnerFetchingPC(tr::now))),
|
||||
_session(session)
|
||||
{
|
||||
searchAuthor(authorId);
|
||||
}
|
||||
|
||||
void ActionStickerPackAuthor::searchAuthor(ID authorId)
|
||||
{
|
||||
const auto pointer = Ui::MakeWeak(this);
|
||||
searchById(authorId, _session, [=](const QString &username, UserData *user)
|
||||
{
|
||||
if (!pointer) {
|
||||
LOG(("ContextActionStickerAuthor: searchById callback after destruction"));
|
||||
return;
|
||||
}
|
||||
if (username.isEmpty() && !user) {
|
||||
_subText = QString(tr::ayu_MessageDetailsPackOwnerNotFoundPC(tr::now));
|
||||
setClickedCallback(
|
||||
[=]
|
||||
{
|
||||
const auto text =
|
||||
QString("int32: %1\nint64: %2").arg(authorId).arg(0x100000000L + authorId);
|
||||
QGuiApplication::clipboard()->setText(text);
|
||||
});
|
||||
|
||||
crl::on_main(
|
||||
[=]
|
||||
{
|
||||
update();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const auto title = username.isEmpty() ? user ? user->name() : QString() : username;
|
||||
const auto callback = [=]
|
||||
{
|
||||
if (user) {
|
||||
if (const auto window = _session->tryResolveWindow()) {
|
||||
if (const auto mainWidget = window->widget()->sessionController()) {
|
||||
mainWidget->showPeer(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
QGuiApplication::clipboard()->setText(title);
|
||||
}
|
||||
};
|
||||
|
||||
setClickedCallback(callback);
|
||||
|
||||
_subText = QString(title);
|
||||
crl::on_main(
|
||||
[=]
|
||||
{
|
||||
update();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
base::unique_qptr<Menu::ItemBase> ContextActionWithSubText(
|
||||
not_null<Menu::Menu *> menu,
|
||||
const style::icon &icon,
|
||||
const QString &title,
|
||||
const QString &subtext,
|
||||
Fn<void()> callback)
|
||||
{
|
||||
if (!callback) {
|
||||
callback = [=]()
|
||||
{
|
||||
QGuiApplication::clipboard()->setText(subtext);
|
||||
};
|
||||
}
|
||||
|
||||
return base::make_unique_q<ActionWithSubText>(
|
||||
menu,
|
||||
menu->st(),
|
||||
icon,
|
||||
std::move(callback),
|
||||
title,
|
||||
subtext);
|
||||
}
|
||||
|
||||
base::unique_qptr<Menu::ItemBase> ContextActionStickerAuthor(
|
||||
not_null<Menu::Menu *> menu,
|
||||
not_null<Main::Session *> session,
|
||||
ID authorId)
|
||||
{
|
||||
return base::make_unique_q<ActionStickerPackAuthor>(menu, session, authorId);
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
@@ -0,0 +1,36 @@
|
||||
// This is the source code of AyuGram for Desktop.
|
||||
//
|
||||
// We do not and cannot prevent the use of our code,
|
||||
// but be respectful and credit the original author.
|
||||
//
|
||||
// Copyright @Radolyn, 2023
|
||||
#pragma once
|
||||
|
||||
#include "ayu/database/entities.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
#include "base/unique_qptr.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
namespace Menu
|
||||
{
|
||||
class Menu;
|
||||
class ItemBase;
|
||||
} // namespace Menu
|
||||
|
||||
class PopupMenu;
|
||||
|
||||
[[nodiscard]] base::unique_qptr<Menu::ItemBase> ContextActionWithSubText(
|
||||
not_null<Menu::Menu *> menu,
|
||||
const style::icon &icon,
|
||||
const QString &title,
|
||||
const QString &subtext,
|
||||
Fn<void()> callback = nullptr);
|
||||
|
||||
[[nodiscard]] base::unique_qptr<Menu::ItemBase> ContextActionStickerAuthor(
|
||||
not_null<Menu::Menu *> menu,
|
||||
not_null<Main::Session *> session,
|
||||
ID authorId);
|
||||
|
||||
} // namespace Ui
|
||||
@@ -21,14 +21,14 @@
|
||||
#endif
|
||||
|
||||
const QVector<QString> icons{
|
||||
AyuSettings::DEFAULT_ICON,
|
||||
AyuSettings::ALT_ICON,
|
||||
AyuSettings::DISCORD_ICON,
|
||||
AyuSettings::SPOTIFY_ICON,
|
||||
AyuSettings::EXTERA_ICON,
|
||||
AyuSettings::NOTHING_ICON,
|
||||
AyuSettings::BARD_ICON,
|
||||
AyuSettings::YAPLUS_ICON
|
||||
AyuAssets::DEFAULT_ICON,
|
||||
AyuAssets::ALT_ICON,
|
||||
AyuAssets::DISCORD_ICON,
|
||||
AyuAssets::SPOTIFY_ICON,
|
||||
AyuAssets::EXTERA_ICON,
|
||||
AyuAssets::NOTHING_ICON,
|
||||
AyuAssets::BARD_ICON,
|
||||
AyuAssets::YAPLUS_ICON
|
||||
};
|
||||
|
||||
const auto rows = static_cast<int>(icons.size()) / 4 + std::min(1, static_cast<int>(icons.size()) % 4);
|
||||
@@ -51,6 +51,19 @@ void drawIcon(QPainter &p, const QImage &icon, int xOffset, int yOffset, bool se
|
||||
p.drawImage(rect, icon);
|
||||
}
|
||||
|
||||
void applyIcon()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
AyuAssets::loadAppIco();
|
||||
reloadAppIconFromTaskBar();
|
||||
#endif
|
||||
|
||||
Window::OverrideApplicationIcon(AyuAssets::currentAppLogo());
|
||||
Core::App().refreshApplicationIcon();
|
||||
Core::App().tray().updateIconCounters();
|
||||
Core::App().domain().notifyUnreadBadgeChanged();
|
||||
}
|
||||
|
||||
IconPicker::IconPicker(QWidget *parent)
|
||||
: RpWidget(parent)
|
||||
{
|
||||
@@ -70,13 +83,13 @@ void IconPicker::paintEvent(QPaintEvent *e)
|
||||
auto const idx = i + row * 4;
|
||||
|
||||
const auto &iconName = icons[idx];
|
||||
auto icon = loadPreview(iconName)
|
||||
auto icon = AyuAssets::loadPreview(iconName)
|
||||
.scaled(st::cpIconSize, st::cpIconSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
drawIcon(p,
|
||||
icon,
|
||||
(st::cpIconSize + st::cpSpacingX) * i + offset,
|
||||
row * (st::cpIconSize + st::cpSpacingY),
|
||||
currentAppLogoName() == iconName);
|
||||
AyuAssets::currentAppLogoName() == iconName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,16 +121,7 @@ void IconPicker::mousePressEvent(QMouseEvent *e)
|
||||
|
||||
if (changed) {
|
||||
AyuSettings::save();
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
loadAppIco();
|
||||
reloadAppIconFromTaskBar();
|
||||
#endif
|
||||
|
||||
Window::OverrideApplicationIcon(currentAppLogo());
|
||||
Core::App().refreshApplicationIcon();
|
||||
Core::App().tray().updateIconCounters();
|
||||
Core::App().domain().notifyUnreadBadgeChanged();
|
||||
applyIcon();
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
@@ -29,37 +29,6 @@ public:
|
||||
[[nodiscard]] rpl::producer<QString> title() override;
|
||||
|
||||
private:
|
||||
void SetupGhostModeToggle(not_null<Ui::VerticalLayout *> container);
|
||||
|
||||
void SetupReadAfterActionToggle(not_null<Ui::VerticalLayout *> container);
|
||||
|
||||
void SetupGhostEssentials(not_null<Ui::VerticalLayout *> container);
|
||||
|
||||
void SetupSpyEssentials(not_null<Ui::VerticalLayout *> container);
|
||||
|
||||
void SetupQoLToggles(not_null<Ui::VerticalLayout *> container);
|
||||
|
||||
void SetupAppIcon(not_null<Ui::VerticalLayout *> container);
|
||||
|
||||
void SetupCustomization(not_null<Ui::VerticalLayout *> container,
|
||||
not_null<Window::SessionController *> controller);
|
||||
|
||||
void SetupDrawerElements(not_null<Ui::VerticalLayout *> container);
|
||||
|
||||
void SetupTrayElements(not_null<Ui::VerticalLayout *> container);
|
||||
|
||||
void SetupShowPeerId(not_null<Ui::VerticalLayout *> container, not_null<Window::SessionController *> controller);
|
||||
|
||||
void SetupRecentStickersLimitSlider(not_null<Ui::VerticalLayout *> container);
|
||||
|
||||
void SetupFonts(not_null<Ui::VerticalLayout *> container, not_null<Window::SessionController *> controller);
|
||||
|
||||
void SetupAyuSync(not_null<Ui::VerticalLayout *> container);
|
||||
|
||||
void SetupSendConfirmations(not_null<Ui::VerticalLayout *> container);
|
||||
|
||||
void SetupAyuGramSettings(not_null<Ui::VerticalLayout *> container, not_null<Window::SessionController *> null);
|
||||
|
||||
void setupContent(not_null<Window::SessionController *> controller);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// This is the source code of AyuGram for Desktop.
|
||||
//
|
||||
// We do not and cannot prevent the use of our code,
|
||||
// but be respectful and credit the original author.
|
||||
//
|
||||
// Copyright @Radolyn, 2023
|
||||
#pragma once
|
||||
|
||||
#include "base/qt/qt_key_modifiers.h"
|
||||
|
||||
namespace base {
|
||||
|
||||
[[nodiscard]] inline bool IsShiftPressed() {
|
||||
return (QGuiApplication::keyboardModifiers() == Qt::ShiftModifier);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool IsExtendedContextMenuModifierPressed() {
|
||||
return IsShiftPressed() || IsCtrlPressed();
|
||||
}
|
||||
|
||||
} // namespace base
|
||||
@@ -10,10 +10,16 @@
|
||||
|
||||
#include "api/api_text_entities.h"
|
||||
|
||||
#include "core/mime_type.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_photo_media.h"
|
||||
#include "inline_bots/inline_bot_result.h"
|
||||
#include "lang_auto.h"
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_forum.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_forum_topic.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_peer_id.h"
|
||||
@@ -21,10 +27,12 @@
|
||||
#include "ayu/sync/models.h"
|
||||
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_document.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_unread_things.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "ui/text/format_values.h"
|
||||
|
||||
// https://github.com/AyuGram/AyuGram4AX/blob/rewrite/TMessagesProj/src/main/java/com/radolyn/ayugram/AyuConstants.java
|
||||
std::unordered_set<ID> ayugram_channels = {
|
||||
@@ -328,4 +336,382 @@ QString formatTTL(int time)
|
||||
}
|
||||
|
||||
return QString("🕓 %1s").arg(time);
|
||||
}
|
||||
}
|
||||
|
||||
QString getDCName(int dc)
|
||||
{
|
||||
const auto getName = [=](int dc)
|
||||
{
|
||||
switch (dc) {
|
||||
case 1:
|
||||
case 3: return "Miami FL, USA";
|
||||
case 2:
|
||||
case 4: return "Amsterdam, NL";
|
||||
case 5: return "Singapore, SG";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
};
|
||||
|
||||
if (dc < 1) {
|
||||
return {"DC_UNKNOWN"};
|
||||
}
|
||||
|
||||
return QString("DC%1, %2").arg(dc).arg(getName(dc));
|
||||
}
|
||||
|
||||
QString getLocalizedAt()
|
||||
{
|
||||
static const auto val = tr::lng_mediaview_date_time(
|
||||
tr::now,
|
||||
lt_date,
|
||||
"",
|
||||
lt_time,
|
||||
"");
|
||||
return val;
|
||||
}
|
||||
|
||||
QString formatDateTime(const QDateTime &date)
|
||||
{
|
||||
const auto locale = QLocale::system();
|
||||
const auto datePart = locale.toString(date.date(), QLocale::ShortFormat);
|
||||
const auto timePart = locale.toString(date, "HH:mm:ss");
|
||||
|
||||
return datePart + getLocalizedAt() + timePart;
|
||||
}
|
||||
|
||||
QString getMediaSize(not_null<HistoryItem *> message)
|
||||
{
|
||||
if (!message->media()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto media = message->media();
|
||||
|
||||
const auto document = media->document();
|
||||
const auto photo = media->photo();
|
||||
|
||||
int64 size = -1;
|
||||
if (document) { // any file
|
||||
size = document->size;
|
||||
}
|
||||
else if (photo && photo->hasVideo()) { // video
|
||||
size = photo->videoByteSize(Data::PhotoSize::Large);
|
||||
if (size == 0) {
|
||||
size = photo->videoByteSize(Data::PhotoSize::Small);
|
||||
}
|
||||
if (size == 0) {
|
||||
size = photo->videoByteSize(Data::PhotoSize::Thumbnail);
|
||||
}
|
||||
}
|
||||
else if (photo && !photo->hasVideo()) { // photo
|
||||
size = photo->imageByteSize(Data::PhotoSize::Large);
|
||||
if (size == 0) {
|
||||
size = photo->imageByteSize(Data::PhotoSize::Small);
|
||||
}
|
||||
if (size == 0) {
|
||||
size = photo->imageByteSize(Data::PhotoSize::Thumbnail);
|
||||
}
|
||||
}
|
||||
|
||||
if (size == -1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return Ui::FormatSizeText(size);
|
||||
}
|
||||
|
||||
QString getMediaMime(not_null<HistoryItem *> message)
|
||||
{
|
||||
if (!message->media()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto media = message->media();
|
||||
|
||||
const auto document = media->document();
|
||||
const auto photo = media->photo();
|
||||
|
||||
if (document) { // any file
|
||||
return document->mimeString();
|
||||
}
|
||||
else if (photo && photo->hasVideo()) { // video
|
||||
return "video/mp4";
|
||||
}
|
||||
else if (photo && !photo->hasVideo()) { // photo
|
||||
return "image/jpeg";
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
QString getMediaName(not_null<HistoryItem *> message)
|
||||
{
|
||||
if (!message->media()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto media = message->media();
|
||||
|
||||
const auto document = media->document();
|
||||
|
||||
if (document) {
|
||||
return document->filename();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
QString getMediaResolution(not_null<HistoryItem *> message)
|
||||
{
|
||||
if (!message->media()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto media = message->media();
|
||||
|
||||
const auto document = media->document();
|
||||
const auto photo = media->photo();
|
||||
|
||||
const auto formatQSize = [=](QSize size)
|
||||
{
|
||||
if (size.isNull() || size.isEmpty() || !size.isValid()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
return QString("%1x%2").arg(size.width()).arg(size.height());
|
||||
};
|
||||
|
||||
if (document) {
|
||||
return formatQSize(document->dimensions);
|
||||
}
|
||||
else if (photo) {
|
||||
auto result = photo->size(Data::PhotoSize::Large);
|
||||
if (!result.has_value()) {
|
||||
result = photo->size(Data::PhotoSize::Small);
|
||||
}
|
||||
if (!result.has_value()) {
|
||||
result = photo->size(Data::PhotoSize::Thumbnail);
|
||||
}
|
||||
return result.has_value() ? formatQSize(result.value()) : QString();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
QString getMediaDC(not_null<HistoryItem *> message)
|
||||
{
|
||||
if (!message->media()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto media = message->media();
|
||||
|
||||
const auto document = media->document();
|
||||
const auto photo = media->photo();
|
||||
|
||||
if (document) {
|
||||
return getDCName(document->getDC());
|
||||
}
|
||||
else if (photo) {
|
||||
return getDCName(photo->getDC());
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void resolveUser(ID userId, const QString &username, Main::Session *session, const Callback &callback)
|
||||
{
|
||||
auto normalized = username.trimmed().toLower();
|
||||
if (normalized.isEmpty()) {
|
||||
callback(QString(), nullptr);
|
||||
return;
|
||||
}
|
||||
normalized = normalized.startsWith("@") ? normalized.mid(1) : normalized;
|
||||
|
||||
if (normalized.isEmpty()) {
|
||||
callback(QString(), nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
session->api().request(MTPcontacts_ResolveUsername(
|
||||
MTP_string(normalized)
|
||||
)).done([=](const MTPcontacts_ResolvedPeer &result)
|
||||
{
|
||||
Expects(result.type() == mtpc_contacts_resolvedPeer);
|
||||
|
||||
auto &data = result.c_contacts_resolvedPeer();
|
||||
session->data().processUsers(data.vusers());
|
||||
session->data().processChats(data.vchats());
|
||||
const auto peer = session->data().peerLoaded(
|
||||
peerFromMTP(data.vpeer()));
|
||||
if (const auto user = peer ? peer->asUser() : nullptr) {
|
||||
if (user->id.value == userId) {
|
||||
callback(normalized, user);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
callback(normalized, nullptr);
|
||||
}).fail([=]
|
||||
{
|
||||
callback(QString(), nullptr);
|
||||
}).send();
|
||||
}
|
||||
|
||||
void searchUser(ID userId, Main::Session *session, bool searchUserFlag, bool cache, const Callback &callback)
|
||||
{
|
||||
const auto botId = 1696868284;
|
||||
const auto bot = session->data().userLoaded(botId);
|
||||
|
||||
if (!bot) {
|
||||
if (searchUserFlag) {
|
||||
resolveUser(botId, "tgdb_bot", session, [=](const QString &title, UserData *data)
|
||||
{
|
||||
searchUser(userId, session, false, false, callback);
|
||||
});
|
||||
}
|
||||
else {
|
||||
callback(QString(), nullptr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
session->api().request(MTPmessages_GetInlineBotResults(
|
||||
MTP_flags(0),
|
||||
bot->inputUser,
|
||||
MTP_inputPeerEmpty(),
|
||||
MTPInputGeoPoint(),
|
||||
MTP_string(QString::number(userId)),
|
||||
MTP_string("")
|
||||
)).done([=](const MTPmessages_BotResults &result)
|
||||
{
|
||||
if (result.type() != mtpc_messages_botResults) {
|
||||
callback(QString(), nullptr);
|
||||
return;
|
||||
}
|
||||
auto &d = result.c_messages_botResults();
|
||||
session->data().processUsers(d.vusers());
|
||||
|
||||
auto &v = d.vresults().v;
|
||||
auto queryId = d.vquery_id().v;
|
||||
|
||||
auto added = 0;
|
||||
for (const auto &res : v) {
|
||||
const auto message = res.match(
|
||||
[&](const MTPDbotInlineResult &data)
|
||||
{
|
||||
return &data.vsend_message();
|
||||
}, [&](const MTPDbotInlineMediaResult &data)
|
||||
{
|
||||
return &data.vsend_message();
|
||||
});
|
||||
|
||||
const auto text = message->match(
|
||||
[&](const MTPDbotInlineMessageMediaAuto &data)
|
||||
{
|
||||
return QString();
|
||||
}, [&](const MTPDbotInlineMessageText &data)
|
||||
{
|
||||
return qs(data.vmessage());
|
||||
}, [&](const MTPDbotInlineMessageMediaGeo &data)
|
||||
{
|
||||
return QString();
|
||||
}, [&](const MTPDbotInlineMessageMediaVenue &data)
|
||||
{
|
||||
return QString();
|
||||
}, [&](const MTPDbotInlineMessageMediaContact &data)
|
||||
{
|
||||
return QString();
|
||||
}, [&](const MTPDbotInlineMessageMediaInvoice &data)
|
||||
{
|
||||
return QString();
|
||||
}, [&](const MTPDbotInlineMessageMediaWebPage &data)
|
||||
{
|
||||
return QString();
|
||||
});
|
||||
|
||||
if (text.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ID id = 0; // 🆔
|
||||
QString title; // 🏷
|
||||
QString username; // 📧
|
||||
|
||||
for (const auto &line : text.split('\n')) {
|
||||
if (line.startsWith("🆔")) {
|
||||
id = line.mid(line.indexOf(':') + 1).toLongLong();
|
||||
}
|
||||
else if (line.startsWith("🏷")) {
|
||||
title = line.mid(line.indexOf(':') + 1);
|
||||
}
|
||||
else if (line.startsWith("📧")) {
|
||||
username = line.mid(line.indexOf(':') + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (id == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (id != userId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!username.isEmpty()) {
|
||||
resolveUser(id, username, session, [=](const QString &titleInner, UserData *data)
|
||||
{
|
||||
if (data) {
|
||||
callback(titleInner, data);
|
||||
}
|
||||
else {
|
||||
callback(title, nullptr);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!title.isEmpty()) {
|
||||
callback(title, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
callback(QString(), nullptr);
|
||||
}).fail([=]
|
||||
{
|
||||
callback(QString(), nullptr);
|
||||
}).handleAllErrors().send();
|
||||
}
|
||||
|
||||
void searchById(ID userId, Main::Session *session, bool retry, const Callback &callback)
|
||||
{
|
||||
if (userId == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto dataLoaded = session->data().userLoaded(userId);
|
||||
if (dataLoaded) {
|
||||
callback(dataLoaded->username(), dataLoaded);
|
||||
return;
|
||||
}
|
||||
|
||||
searchUser(userId, session, true, true, [=](const QString &title, UserData *data)
|
||||
{
|
||||
if (data && data->accessHash()) {
|
||||
callback(title, data);
|
||||
}
|
||||
else {
|
||||
if (retry) {
|
||||
searchById(0x100000000 + userId, session, false, callback);
|
||||
}
|
||||
else {
|
||||
callback(QString(), nullptr);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void searchById(ID userId, Main::Session *session, const Callback &callback)
|
||||
{
|
||||
searchById(userId, session, true, callback);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
#include "main/main_session.h"
|
||||
#include "dialogs/dialogs_main_list.h"
|
||||
|
||||
using Callback = Fn<void(const QString&, UserData *)>;
|
||||
|
||||
Main::Session *getSession(ID userId);
|
||||
bool accountExists(ID userId);
|
||||
void dispatchToMainThread(std::function<void()> callback, int delay = 0);
|
||||
@@ -32,3 +34,15 @@ void MarkAsReadThread(not_null<Data::Thread *> thread);
|
||||
void readHistory(not_null<HistoryItem *> message);
|
||||
|
||||
QString formatTTL(int time);
|
||||
QString formatDateTime(const QDateTime &date);
|
||||
|
||||
QString getDCName(int dc);
|
||||
|
||||
QString getMediaSize(not_null<HistoryItem *> message);
|
||||
QString getMediaMime(not_null<HistoryItem *> message);
|
||||
QString getMediaName(not_null<HistoryItem *> message);
|
||||
QString getMediaResolution(not_null<HistoryItem *> message);
|
||||
QString getMediaDC(not_null<HistoryItem *> message);
|
||||
|
||||
void searchById(ID userId, Main::Session *session, bool retry, const Callback &callback);
|
||||
void searchById(ID userId, Main::Session *session, const Callback& callback);
|
||||
|
||||
@@ -57,7 +57,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace {
|
||||
|
||||
constexpr auto kMaxWallPaperSlugLength = 255;
|
||||
constexpr auto kDefaultDimming = 50;
|
||||
|
||||
[[nodiscard]] bool IsValidWallPaperSlug(const QString &slug) {
|
||||
if (slug.isEmpty() || slug.size() > kMaxWallPaperSlugLength) {
|
||||
|
||||
@@ -113,7 +113,8 @@ Base64UrlInput::Base64UrlInput(
|
||||
rpl::producer<QString> placeholder,
|
||||
const QString &val)
|
||||
: MaskedInputField(parent, st, std::move(placeholder), val) {
|
||||
if (!QRegularExpression("^[a-zA-Z0-9_\\-]+$").match(val).hasMatch()) {
|
||||
static const auto RegExp = QRegularExpression("^[a-zA-Z0-9_\\-]+$");
|
||||
if (!RegExp.match(val).hasMatch()) {
|
||||
setText(QString());
|
||||
}
|
||||
}
|
||||
@@ -831,8 +832,9 @@ void ProxyBox::prepare() {
|
||||
connect(_host.data(), &HostInput::changed, [=] {
|
||||
Ui::PostponeCall(_host, [=] {
|
||||
const auto host = _host->getLastText().trimmed();
|
||||
static const auto mask = u"^\\d+\\.\\d+\\.\\d+\\.\\d+:(\\d*)$"_q;
|
||||
const auto match = QRegularExpression(mask).match(host);
|
||||
static const auto mask = QRegularExpression(
|
||||
u"^\\d+\\.\\d+\\.\\d+\\.\\d+:(\\d*)$"_q);
|
||||
const auto match = mask.match(host);
|
||||
if (_host->cursorPosition() == host.size()
|
||||
&& match.hasMatch()) {
|
||||
const auto port = match.captured(1);
|
||||
@@ -1107,6 +1109,10 @@ void ProxiesBoxController::ShowApplyConfirmation(
|
||||
proxy.password = fields.value(u"secret"_q);
|
||||
}
|
||||
if (proxy) {
|
||||
static const auto UrlStartRegExp = QRegularExpression(
|
||||
"^https://",
|
||||
QRegularExpression::CaseInsensitiveOption);
|
||||
static const auto UrlEndRegExp = QRegularExpression("/$");
|
||||
const auto displayed = "https://" + server + "/";
|
||||
const auto parsed = QUrl::fromUserInput(displayed);
|
||||
const auto displayUrl = !UrlClickHandler::IsSuspicious(displayed)
|
||||
@@ -1117,11 +1123,9 @@ void ProxiesBoxController::ShowApplyConfirmation(
|
||||
const auto displayServer = QString(
|
||||
displayUrl
|
||||
).replace(
|
||||
QRegularExpression(
|
||||
"^https://",
|
||||
QRegularExpression::CaseInsensitiveOption),
|
||||
UrlStartRegExp,
|
||||
QString()
|
||||
).replace(QRegularExpression("/$"), QString());
|
||||
).replace(UrlEndRegExp, QString());
|
||||
const auto text = tr::lng_sure_enable_socks(
|
||||
tr::now,
|
||||
lt_server,
|
||||
|
||||
@@ -62,7 +62,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kDiscountDivider = 5.;
|
||||
constexpr auto kUserpicsMax = size_t(3);
|
||||
|
||||
using GiftOption = Data::SubscriptionOption;
|
||||
@@ -383,7 +382,8 @@ void GiftsBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionController*> controller,
|
||||
std::vector<not_null<UserData*>> users,
|
||||
not_null<Api::PremiumGiftCodeOptions*> api) {
|
||||
not_null<Api::PremiumGiftCodeOptions*> api,
|
||||
const QString &ref) {
|
||||
Expects(!users.empty());
|
||||
|
||||
const auto boxWidth = st::boxWideWidth;
|
||||
@@ -592,7 +592,7 @@ void GiftsBox(
|
||||
Settings::AddSummaryPremium(
|
||||
content,
|
||||
controller,
|
||||
u"gift"_q,
|
||||
ref,
|
||||
std::move(buttonCallback));
|
||||
}
|
||||
|
||||
@@ -603,7 +603,20 @@ void GiftsBox(
|
||||
box,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
session->api().premium().statusTextValue(), // TODO.
|
||||
tr::lng_premium_gifts_terms(
|
||||
lt_link,
|
||||
tr::lng_payments_terms_link(
|
||||
) | rpl::map([](const QString &t) {
|
||||
using namespace Ui::Text;
|
||||
return Link(t, u"https://telegram.org/tos"_q);
|
||||
}),
|
||||
lt_policy,
|
||||
tr::lng_premium_gifts_terms_policy(
|
||||
) | rpl::map([](const QString &t) {
|
||||
using namespace Ui::Text;
|
||||
return Link(t, u"https://telegram.org/privacy"_q);
|
||||
}),
|
||||
Ui::Text::RichLangValue),
|
||||
st::premiumGiftTerms),
|
||||
st::defaultBoxDividerLabelPadding),
|
||||
{});
|
||||
@@ -615,7 +628,7 @@ void GiftsBox(
|
||||
auto raw = Settings::CreateSubscribeButton({
|
||||
controller,
|
||||
box,
|
||||
[] { return u"gift"_q; },
|
||||
[=] { return ref; },
|
||||
rpl::combine(
|
||||
state->buttonText.events(),
|
||||
state->confirmButtonBusy.value(),
|
||||
@@ -877,7 +890,7 @@ void GiftPremiumValidator::cancel() {
|
||||
_requestId = 0;
|
||||
}
|
||||
|
||||
void GiftPremiumValidator::showChoosePeerBox() {
|
||||
void GiftPremiumValidator::showChoosePeerBox(const QString &ref) {
|
||||
if (_manyGiftsLifetime) {
|
||||
return;
|
||||
}
|
||||
@@ -903,9 +916,13 @@ void GiftPremiumValidator::showChoosePeerBox() {
|
||||
protected:
|
||||
std::unique_ptr<PeerListRow> createRow(
|
||||
not_null<UserData*> user) override {
|
||||
return !user->isSelf()
|
||||
? ContactsBoxController::createRow(user)
|
||||
: nullptr;
|
||||
if (user->isSelf()
|
||||
|| user->isBot()
|
||||
|| user->isServiceUser()
|
||||
|| user->isInaccessible()) {
|
||||
return nullptr;
|
||||
}
|
||||
return ContactsBoxController::createRow(user);
|
||||
}
|
||||
|
||||
void rowClicked(not_null<PeerListRow*> row) override {
|
||||
@@ -937,7 +954,7 @@ void GiftPremiumValidator::showChoosePeerBox() {
|
||||
}) | ranges::to<std::vector<not_null<UserData*>>>();
|
||||
if (!users.empty()) {
|
||||
const auto giftBox = show->show(
|
||||
Box(GiftsBox, _controller, users, api));
|
||||
Box(GiftsBox, _controller, users, api, ref));
|
||||
giftBox->boxClosing(
|
||||
) | rpl::start_with_next([=] {
|
||||
_manyGiftsLifetime.destroy();
|
||||
@@ -1037,7 +1054,7 @@ void GiftCodeBox(
|
||||
state->data = session->api().premium().giftCodeValue(slug);
|
||||
state->used = state->data.value(
|
||||
) | rpl::map([=](const Api::GiftCode &data) {
|
||||
return data.used;
|
||||
return data.used != 0;
|
||||
});
|
||||
|
||||
box->setWidth(st::boxWideWidth);
|
||||
|
||||
@@ -34,7 +34,7 @@ public:
|
||||
GiftPremiumValidator(not_null<Window::SessionController*> controller);
|
||||
|
||||
void showBox(not_null<UserData*> user);
|
||||
void showChoosePeerBox();
|
||||
void showChoosePeerBox(const QString &ref);
|
||||
void cancel();
|
||||
|
||||
private:
|
||||
|
||||
@@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_forum.h"
|
||||
#include "data/data_saved_messages.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_folder.h"
|
||||
#include "data/data_premium_limits.h"
|
||||
@@ -67,6 +68,8 @@ public:
|
||||
void prepare() override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
|
||||
[[nodiscard]] rpl::producer<int> countValue() const;
|
||||
|
||||
private:
|
||||
void appendRow(not_null<PeerData*> peer, TimeId date);
|
||||
[[nodiscard]] std::unique_ptr<PeerListRow> createRow(
|
||||
@@ -74,6 +77,7 @@ private:
|
||||
TimeId date) const;
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
rpl::variable<int> _count;
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
};
|
||||
@@ -90,12 +94,15 @@ public:
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
void rowRightActionClicked(not_null<PeerListRow*> row) override;
|
||||
|
||||
[[nodiscard]] rpl::producer<int> countValue() const;
|
||||
|
||||
private:
|
||||
void appendRow(not_null<PeerData*> peer);
|
||||
[[nodiscard]] std::unique_ptr<PeerListRow> createRow(
|
||||
not_null<PeerData*> peer) const;
|
||||
|
||||
const not_null<Window::SessionNavigation*> _navigation;
|
||||
rpl::variable<int> _count;
|
||||
Fn<void()> _closeBox;
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
@@ -209,17 +216,17 @@ void InactiveController::prepare() {
|
||||
_requestId = _session->api().request(MTPchannels_GetInactiveChannels(
|
||||
)).done([=](const MTPmessages_InactiveChats &result) {
|
||||
_requestId = 0;
|
||||
result.match([&](const MTPDmessages_inactiveChats &data) {
|
||||
_session->data().processUsers(data.vusers());
|
||||
const auto &list = data.vchats().v;
|
||||
const auto &dates = data.vdates().v;
|
||||
for (auto i = 0, count = int(list.size()); i != count; ++i) {
|
||||
const auto peer = _session->data().processChat(list[i]);
|
||||
const auto date = (i < dates.size()) ? dates[i].v : TimeId();
|
||||
appendRow(peer, date);
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
});
|
||||
const auto &data = result.data();
|
||||
_session->data().processUsers(data.vusers());
|
||||
const auto &list = data.vchats().v;
|
||||
const auto &dates = data.vdates().v;
|
||||
for (auto i = 0, count = int(list.size()); i != count; ++i) {
|
||||
const auto peer = _session->data().processChat(list[i]);
|
||||
const auto date = (i < dates.size()) ? dates[i].v : TimeId();
|
||||
appendRow(peer, date);
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
_count = delegate()->peerListFullRowsCount();
|
||||
}).send();
|
||||
}
|
||||
|
||||
@@ -227,6 +234,10 @@ void InactiveController::rowClicked(not_null<PeerListRow*> row) {
|
||||
delegate()->peerListSetRowChecked(row, !row->checked());
|
||||
}
|
||||
|
||||
rpl::producer<int> InactiveController::countValue() const {
|
||||
return _count.value();
|
||||
}
|
||||
|
||||
void InactiveController::appendRow(
|
||||
not_null<PeerData*> participant,
|
||||
TimeId date) {
|
||||
@@ -295,6 +306,10 @@ Main::Session &PublicsController::session() const {
|
||||
return _navigation->session();
|
||||
}
|
||||
|
||||
rpl::producer<int> PublicsController::countValue() const {
|
||||
return _count.value();
|
||||
}
|
||||
|
||||
void PublicsController::prepare() {
|
||||
_requestId = _navigation->session().api().request(
|
||||
MTPchannels_GetAdminedPublicChannels(MTP_flags(0))
|
||||
@@ -314,6 +329,7 @@ void PublicsController::prepare() {
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
_count = delegate()->peerListFullRowsCount();
|
||||
}).send();
|
||||
}
|
||||
|
||||
@@ -570,7 +586,7 @@ void ChannelsLimitBox(
|
||||
{});
|
||||
|
||||
using namespace rpl::mappers;
|
||||
content->heightValue(
|
||||
controller->countValue(
|
||||
) | rpl::filter(_1 > 0) | rpl::start_with_next([=] {
|
||||
delete placeholder;
|
||||
}, placeholder->lifetime());
|
||||
@@ -661,7 +677,7 @@ void PublicLinksLimitBox(
|
||||
{});
|
||||
|
||||
using namespace rpl::mappers;
|
||||
content->heightValue(
|
||||
controller->countValue(
|
||||
) | rpl::filter(_1 > 0) | rpl::start_with_next([=] {
|
||||
delete placeholder;
|
||||
}, placeholder->lifetime());
|
||||
@@ -882,6 +898,18 @@ void PinsLimitBox(
|
||||
limits.dialogsPinnedPremium(),
|
||||
PinsCount(session->data().chatsList()));
|
||||
}
|
||||
void SublistsPinsLimitBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Main::Session*> session) {
|
||||
const auto limits = Data::PremiumLimits(session);
|
||||
SimplePinsLimitBox(
|
||||
box,
|
||||
session,
|
||||
"saved_dialog_pinned",
|
||||
limits.savedSublistsPinnedDefault(),
|
||||
limits.savedSublistsPinnedPremium(),
|
||||
PinsCount(session->data().savedMessages().chatsList()));
|
||||
}
|
||||
|
||||
void ForumPinsLimitBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
|
||||
@@ -60,6 +60,9 @@ void PinsLimitBox(
|
||||
void ForumPinsLimitBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Data::Forum*> forum);
|
||||
void SublistsPinsLimitBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Main::Session*> session);
|
||||
void CaptionLimitBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Main::Session*> session,
|
||||
|
||||
@@ -51,8 +51,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace {
|
||||
|
||||
constexpr auto kPremiumShift = 21. / 240;
|
||||
constexpr auto kReactionsPerRow = 5;
|
||||
constexpr auto kDisabledOpacity = 0.5;
|
||||
constexpr auto kToggleStickerTimeout = 2 * crl::time(1000);
|
||||
constexpr auto kStarOpacityOff = 0.1;
|
||||
constexpr auto kStarOpacityOn = 1.;
|
||||
|
||||
@@ -848,7 +848,7 @@ void ShareBox::Inner::loadProfilePhotos(int yFrom) {
|
||||
if (!_chatsIndexed->empty()) {
|
||||
const auto index = yFrom / _rowHeight;
|
||||
auto i = _chatsIndexed->begin()
|
||||
+ std::min(index, _chatsIndexed->size());;
|
||||
+ std::min(index, _chatsIndexed->size());
|
||||
for (auto end = _chatsIndexed->cend(); i != end; ++i) {
|
||||
if (((*i)->index() * _rowHeight) >= yTo) {
|
||||
break;
|
||||
|
||||
@@ -61,6 +61,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <QtGui/QClipboard>
|
||||
#include <QtSvg/QSvgRenderer>
|
||||
|
||||
// AyuGram includes
|
||||
#include "ayu/utils/telegram_helpers.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "data/data_user.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kStickersPerRow = 5;
|
||||
@@ -536,6 +542,26 @@ void StickerSetBox::updateButtons() {
|
||||
? tr::lng_stickers_copied_emoji(tr::now)
|
||||
: tr::lng_stickers_copied(tr::now));
|
||||
};
|
||||
const auto addAuthorPack = [=](const std::shared_ptr<base::unique_qptr<Ui::PopupMenu>>& menu) {
|
||||
if (type == Data::StickersType::Stickers) {
|
||||
(*menu)->addAction(tr::ayu_MessageDetailsPackOwnerPC(tr::now), [=]
|
||||
{
|
||||
searchById(_inner->setId() >> 32, _session, [=](const QString &username, UserData *user)
|
||||
{
|
||||
if (!user) {
|
||||
showToast(tr::ayu_UserNotFoundMessage(tr::now));
|
||||
return;
|
||||
}
|
||||
|
||||
if (const auto window = _session->tryResolveWindow()) {
|
||||
if (const auto mainWidget = window->widget()->sessionController()) {
|
||||
mainWidget->showPeer(user);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, &st::menuIconProfile);
|
||||
}
|
||||
};
|
||||
if (_inner->notInstalled()) {
|
||||
if (!_session->premium()
|
||||
&& _session->premiumPossible()
|
||||
@@ -582,6 +608,7 @@ void StickerSetBox::updateButtons() {
|
||||
: tr::lng_stickers_share_pack)(tr::now),
|
||||
[=] { share(); closeBox(); },
|
||||
&st::menuIconShare);
|
||||
addAuthorPack(menu);
|
||||
(*menu)->popup(QCursor::pos());
|
||||
return true;
|
||||
});
|
||||
@@ -631,6 +658,7 @@ void StickerSetBox::updateButtons() {
|
||||
archive,
|
||||
&st::menuIconArchive);
|
||||
}
|
||||
addAuthorPack(menu);
|
||||
(*menu)->popup(QCursor::pos());
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -520,6 +520,7 @@ void BoxController::loadMoreRows() {
|
||||
MTP_inputPeerEmpty(),
|
||||
MTP_string(), // q
|
||||
MTP_inputPeerEmpty(),
|
||||
MTPInputPeer(), // saved_peer_id
|
||||
MTPint(), // top_msg_id
|
||||
MTP_inputMessagesFilterPhoneCalls(MTP_flags(0)),
|
||||
MTP_int(0), // min_date
|
||||
|
||||
@@ -321,9 +321,6 @@ void Viewport::RendererGL::init(
|
||||
_frameBuffer->bind();
|
||||
_frameBuffer->allocate(kValues * sizeof(GLfloat));
|
||||
_downscaleProgram.yuv420.emplace();
|
||||
const auto downscaleVertexSource = VertexShader({
|
||||
VertexPassTextureCoord(),
|
||||
});
|
||||
_downscaleVertexShader = LinkProgram(
|
||||
&*_downscaleProgram.yuv420,
|
||||
VertexShader({
|
||||
|
||||
@@ -454,8 +454,10 @@ void ChooseSourceProcess::fillSources() {
|
||||
|
||||
auto screenIndex = 0;
|
||||
auto windowIndex = 0;
|
||||
auto firstScreenSelected = false;
|
||||
const auto active = _delegate->chooseSourceActiveDeviceId();
|
||||
const auto append = [&](const tgcalls::DesktopCaptureSource &source) {
|
||||
const auto firstScreen = !source.isWindow() && !screenIndex;
|
||||
const auto title = !source.isWindow()
|
||||
? tr::lng_group_call_screen_title(
|
||||
tr::now,
|
||||
@@ -471,6 +473,10 @@ void ChooseSourceProcess::fillSources() {
|
||||
if (!active.isEmpty() && active.toStdString() == id) {
|
||||
_selected = raw;
|
||||
raw->setActive(true);
|
||||
} else if (active.isEmpty() && firstScreen) {
|
||||
_selected = raw;
|
||||
raw->setActive(true);
|
||||
firstScreenSelected = true;
|
||||
}
|
||||
_sources.back()->activations(
|
||||
) | rpl::filter([=] {
|
||||
@@ -489,6 +495,9 @@ void ChooseSourceProcess::fillSources() {
|
||||
for (const auto &source : windowsManager.sources()) {
|
||||
append(source);
|
||||
}
|
||||
if (firstScreenSelected) {
|
||||
updateButtonsVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
void ChooseSourceProcess::updateButtonsVisibility() {
|
||||
|
||||
@@ -1072,6 +1072,7 @@ historyRecordCancelActive: windowActiveTextFg;
|
||||
historyRecordFont: font(13px);
|
||||
historyRecordDurationSkip: 12px;
|
||||
historyRecordDurationFg: historyComposeAreaFg;
|
||||
historyRecordTTLLineWidth: 2px;
|
||||
|
||||
historyRecordMainBlobMinRadius: 23px;
|
||||
historyRecordMainBlobMaxRadius: 37px;
|
||||
|
||||
@@ -683,7 +683,8 @@ bool Application::eventFilter(QObject *object, QEvent *e) {
|
||||
} break;
|
||||
|
||||
case QEvent::ThemeChange: {
|
||||
if (Platform::IsLinux() && object == QGuiApplication::allWindows().first()) {
|
||||
if (Platform::IsLinux()
|
||||
&& object == QGuiApplication::allWindows().constFirst()) {
|
||||
Core::App().refreshApplicationIcon();
|
||||
Core::App().tray().updateIconCounters();
|
||||
}
|
||||
|
||||
@@ -1265,7 +1265,7 @@ void Settings::resetOnLastLogout() {
|
||||
_tabbedReplacedWithInfo = false; // per-window
|
||||
_systemDarkModeEnabled = false;
|
||||
_hiddenGroupCallTooltips = 0;
|
||||
_storiesClickTooltipHidden = 0;
|
||||
_storiesClickTooltipHidden = false;
|
||||
|
||||
_recentEmojiPreload.clear();
|
||||
_recentEmoji.clear();
|
||||
|
||||
@@ -499,7 +499,7 @@ uint64 Launcher::installationTag() const {
|
||||
}
|
||||
|
||||
void Launcher::processArguments() {
|
||||
enum class KeyFormat {
|
||||
enum class KeyFormat {
|
||||
NoValues,
|
||||
OneValue,
|
||||
AllLeftValues,
|
||||
@@ -542,9 +542,13 @@ void Launcher::processArguments() {
|
||||
}
|
||||
}
|
||||
|
||||
static const auto RegExp = QRegularExpression("[^a-z0-9\\-_]");
|
||||
gDebugMode = parseResult.contains("-debug");
|
||||
gKeyFile = parseResult.value("-key", {}).join(QString()).toLower();
|
||||
gKeyFile = gKeyFile.replace(QRegularExpression("[^a-z0-9\\-_]"), {});
|
||||
gKeyFile = parseResult
|
||||
.value("-key", {})
|
||||
.join(QString())
|
||||
.toLower()
|
||||
.replace(RegExp, {});
|
||||
gLaunchMode = parseResult.contains("-autostart") ? LaunchModeAutoStart
|
||||
: parseResult.contains("-fixprevious") ? LaunchModeFixPrevious
|
||||
: parseResult.contains("-cleanup") ? LaunchModeCleanup
|
||||
|
||||
@@ -58,6 +58,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include <QtGui/QGuiApplication>
|
||||
|
||||
// AyuGram includes
|
||||
#include "ayu/ayu_url_handlers.h"
|
||||
|
||||
|
||||
namespace Core {
|
||||
namespace {
|
||||
|
||||
@@ -845,6 +849,21 @@ bool ResolvePremiumOffer(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResolvePremiumMultigift(
|
||||
Window::SessionController *controller,
|
||||
const Match &match,
|
||||
const QVariant &context) {
|
||||
if (!controller) {
|
||||
return false;
|
||||
}
|
||||
const auto params = url_parse_params(
|
||||
match->captured(1).mid(1),
|
||||
qthelp::UrlParamNameTransform::ToLower);
|
||||
controller->showGiftPremiumsBox(params.value(u"ref"_q, u"gift_url"_q));
|
||||
controller->window().activate();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResolveLoginCode(
|
||||
Window::SessionController *controller,
|
||||
const Match &match,
|
||||
@@ -968,6 +987,10 @@ const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
|
||||
u"premium_offer/?(\\?.+)?(#|$)"_q,
|
||||
ResolvePremiumOffer,
|
||||
},
|
||||
{
|
||||
u"^premium_multigift/?\\?(.+)(#|$)"_q,
|
||||
ResolvePremiumMultigift,
|
||||
},
|
||||
{
|
||||
u"^login/?(\\?code=([0-9]+))(&|$)"_q,
|
||||
ResolveLoginCode
|
||||
@@ -976,6 +999,14 @@ const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
|
||||
u"^boost/?\\?(.+)(#|$)"_q,
|
||||
ResolveBoost,
|
||||
},
|
||||
{
|
||||
u"^user\\?(.+)(#|$)"_q,
|
||||
AyuUrlHandlers::ResolveUser
|
||||
},
|
||||
{
|
||||
u"^ayu/?(.+)?(#|$)"_q,
|
||||
AyuUrlHandlers::HandleAyu
|
||||
},
|
||||
{
|
||||
u"^([^\\?]+)(\\?|#|$)"_q,
|
||||
HandleUnknown
|
||||
@@ -1092,7 +1123,7 @@ QString TryConvertUrlToLocal(QString url) {
|
||||
const auto base = u"tg://privatepost?channel="_q + channel;
|
||||
auto added = QString();
|
||||
if (const auto threadPostMatch = regex_match(u"^/(\\d+)/(\\d+)(/?\\?|/?$)"_q, privateMatch->captured(2))) {
|
||||
added = u"&topic=%1&post=%2"_q.arg(threadPostMatch->captured(1)).arg(threadPostMatch->captured(2));
|
||||
added = u"&topic=%1&post=%2"_q.arg(threadPostMatch->captured(1), threadPostMatch->captured(2));
|
||||
} else if (const auto postMatch = regex_match(u"^/(\\d+)(/?\\?|/?$)"_q, privateMatch->captured(2))) {
|
||||
added = u"&post="_q + postMatch->captured(1);
|
||||
}
|
||||
@@ -1122,7 +1153,7 @@ QString TryConvertUrlToLocal(QString url) {
|
||||
const auto base = u"tg://resolve?domain="_q + url_encode(usernameMatch->captured(1));
|
||||
auto added = QString();
|
||||
if (const auto threadPostMatch = regex_match(u"^/(\\d+)/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
|
||||
added = u"&topic=%1&post=%2"_q.arg(threadPostMatch->captured(1)).arg(threadPostMatch->captured(2));
|
||||
added = u"&topic=%1&post=%2"_q.arg(threadPostMatch->captured(1), threadPostMatch->captured(2));
|
||||
} else if (const auto postMatch = regex_match(u"^/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
|
||||
added = u"&post="_q + postMatch->captured(1);
|
||||
} else if (const auto storyMatch = regex_match(u"^/s/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
|
||||
|
||||
@@ -21,7 +21,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/local_url_handlers.h"
|
||||
#include "core/update_checker.h"
|
||||
#include "core/deadlock_detector.h"
|
||||
#include "base/options.h"
|
||||
#include "base/timer.h"
|
||||
#include "base/concurrent_timer.h"
|
||||
#include "base/invoke_queued.h"
|
||||
@@ -38,24 +37,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace Core {
|
||||
namespace {
|
||||
|
||||
base::options::toggle OptionForceWaylandFractionalScaling({
|
||||
.id = kOptionForceWaylandFractionalScaling,
|
||||
.name = "Enable xdg-output fractional scaling",
|
||||
.description = "Enable xdg-output based fractional scaling on Wayland. "
|
||||
"This works without fractional-scale-v1 and without "
|
||||
"precise High DPI scaling. "
|
||||
"Requires Qt with Desktop App Toolkit patches.",
|
||||
.defaultValue = true,
|
||||
.scope = [] {
|
||||
#ifdef DESKTOP_APP_QT_PATCHED
|
||||
return Platform::IsWayland();
|
||||
#else // DESKTOP_APP_QT_PATCHED
|
||||
return false;
|
||||
#endif // !DESKTOP_APP_QT_PATCHED
|
||||
},
|
||||
.restartRequired = true,
|
||||
});
|
||||
|
||||
QChar _toHex(ushort v) {
|
||||
v = v & 0x000F;
|
||||
return QChar::fromLatin1((v >= 10) ? ('a' + (v - 10)) : ('0' + v));
|
||||
@@ -96,17 +77,12 @@ QString _escapeFrom7bit(const QString &str) {
|
||||
|
||||
} // namespace
|
||||
|
||||
const char kOptionForceWaylandFractionalScaling[] = "force-wayland-fractional-scaling";
|
||||
|
||||
bool Sandbox::QuitOnStartRequested = false;
|
||||
|
||||
Sandbox::Sandbox(int &argc, char **argv)
|
||||
: QApplication(argc, argv)
|
||||
, _mainThreadId(QThread::currentThreadId()) {
|
||||
setQuitOnLastWindowClosed(false);
|
||||
if (OptionForceWaylandFractionalScaling.value()) {
|
||||
setProperty("_q_force_wayland_fractional_scale", true);
|
||||
}
|
||||
}
|
||||
|
||||
int Sandbox::start() {
|
||||
@@ -240,7 +216,7 @@ void Sandbox::setupScreenScale() {
|
||||
const auto logEnv = [](const char *name) {
|
||||
const auto value = qEnvironmentVariable(name);
|
||||
if (!value.isEmpty()) {
|
||||
LOG(("%1: %2").arg(name).arg(value));
|
||||
LOG(("%1: %2").arg(name, value));
|
||||
}
|
||||
};
|
||||
logEnv("QT_DEVICE_PIXEL_RATIO");
|
||||
|
||||
@@ -19,8 +19,6 @@ class QLockFile;
|
||||
|
||||
namespace Core {
|
||||
|
||||
extern const char kOptionForceWaylandFractionalScaling[];
|
||||
|
||||
class UpdateChecker;
|
||||
class Application;
|
||||
|
||||
|
||||
@@ -241,7 +241,7 @@ QString FindUpdateFile() {
|
||||
}
|
||||
const auto list = updates.entryInfoList(QDir::Files);
|
||||
for (const auto &info : list) {
|
||||
if (QRegularExpression(
|
||||
static const auto RegExp = QRegularExpression(
|
||||
"^("
|
||||
"tupdate|"
|
||||
"tx64upd|"
|
||||
@@ -250,7 +250,8 @@ QString FindUpdateFile() {
|
||||
"tlinuxupd|"
|
||||
")\\d+(_[a-z\\d]+)?$",
|
||||
QRegularExpression::CaseInsensitiveOption
|
||||
).match(info.fileName()).hasMatch()) {
|
||||
);
|
||||
if (RegExp.match(info.fileName()).hasMatch()) {
|
||||
return info.absoluteFilePath();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
|
||||
constexpr auto AppNameOld = "AyuGram for Windows"_cs;
|
||||
constexpr auto AppName = "AyuGram Desktop"_cs;
|
||||
constexpr auto AppFile = "AyuGram"_cs;
|
||||
constexpr auto AppVersion = 4013001;
|
||||
constexpr auto AppVersionStr = "4.13.1";
|
||||
constexpr auto AppVersion = 4014003;
|
||||
constexpr auto AppVersionStr = "4.14.3";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -48,6 +48,8 @@ namespace {
|
||||
|
||||
constexpr auto kDefaultCoverThumbnailSize = 100;
|
||||
constexpr auto kMaxAllowedPreloadPrefix = 6 * 1024 * 1024;
|
||||
constexpr auto kDefaultWebmEmojiSize = 100;
|
||||
constexpr auto kDefaultWebmStickerLargerSize = kStickerSideSize;
|
||||
|
||||
const auto kLottieStickerDimensions = QSize(
|
||||
kStickerSideSize,
|
||||
@@ -430,6 +432,42 @@ void DocumentData::setattributes(
|
||||
_flags |= Flag::HasAttachedStickers;
|
||||
});
|
||||
}
|
||||
|
||||
// Any "video/webm" file is treated as a video-sticker.
|
||||
if (hasMimeType(u"video/webm"_q)) {
|
||||
if (type == FileDocument) {
|
||||
type = StickerDocument;
|
||||
_additional = std::make_unique<StickerData>();
|
||||
}
|
||||
if (type == StickerDocument) {
|
||||
sticker()->type = StickerType::Webm;
|
||||
}
|
||||
}
|
||||
|
||||
// If "video/webm" sticker without dimensions we set them to default.
|
||||
if (const auto info = sticker(); info
|
||||
&& info->set
|
||||
&& info->type == StickerType::Webm
|
||||
&& dimensions.isEmpty()) {
|
||||
if (info->setType == Data::StickersType::Emoji) {
|
||||
// Always fixed.
|
||||
dimensions = { kDefaultWebmEmojiSize, kDefaultWebmEmojiSize };
|
||||
} else if (info->setType == Data::StickersType::Stickers) {
|
||||
// May have aspect != 1, so we count it from the thumbnail.
|
||||
const auto thumbnail = QSize(
|
||||
_thumbnail.location.width(),
|
||||
_thumbnail.location.height()
|
||||
).scaled(
|
||||
kDefaultWebmStickerLargerSize,
|
||||
kDefaultWebmStickerLargerSize,
|
||||
Qt::KeepAspectRatio);
|
||||
if (!thumbnail.isEmpty()) {
|
||||
dimensions = thumbnail;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check sticker size/dimensions properties (for sticker of any type).
|
||||
if (type == StickerDocument
|
||||
&& ((size > Storage::kMaxStickerBytesSize)
|
||||
|| (!sticker()->isLottie()
|
||||
@@ -438,14 +476,8 @@ void DocumentData::setattributes(
|
||||
dimensions.height())))) {
|
||||
type = FileDocument;
|
||||
_additional = nullptr;
|
||||
} else if (type == FileDocument
|
||||
&& hasMimeType(u"video/webm"_q)
|
||||
&& (size < Storage::kMaxStickerBytesSize)
|
||||
&& GoodStickerDimensions(dimensions.width(), dimensions.height())) {
|
||||
type = StickerDocument;
|
||||
_additional = std::make_unique<StickerData>();
|
||||
sticker()->type = StickerType::Webm;
|
||||
}
|
||||
|
||||
if (isAudioFile()
|
||||
|| isAnimation()
|
||||
|| isVoiceMessage()
|
||||
@@ -483,8 +515,7 @@ bool DocumentData::checkWallPaperProperties() {
|
||||
}
|
||||
if (type != FileDocument
|
||||
|| !hasThumbnail()
|
||||
|| !dimensions.width()
|
||||
|| !dimensions.height()
|
||||
|| dimensions.isEmpty()
|
||||
|| dimensions.width() > Storage::kMaxWallPaperDimension
|
||||
|| dimensions.height() > Storage::kMaxWallPaperDimension
|
||||
|| size > Storage::kMaxWallPaperInMemory) {
|
||||
|
||||
@@ -267,17 +267,18 @@ void ResolveDocument(
|
||||
return false;
|
||||
}
|
||||
const auto &location = document->location(true);
|
||||
const auto mime = u"image/"_q;
|
||||
if (!location.isEmpty() && location.accessEnable()) {
|
||||
const auto guard = gsl::finally([&] {
|
||||
location.accessDisable();
|
||||
});
|
||||
const auto path = location.name();
|
||||
if (Core::MimeTypeForFile(QFileInfo(path)).name().startsWith("image/")
|
||||
if (Core::MimeTypeForFile(QFileInfo(path)).name().startsWith(mime)
|
||||
&& QImageReader(path).canRead()) {
|
||||
showDocument();
|
||||
return true;
|
||||
}
|
||||
} else if (document->mimeString().startsWith("image/")
|
||||
} else if (document->mimeString().startsWith(mime)
|
||||
&& !media->bytes().isEmpty()) {
|
||||
auto bytes = media->bytes();
|
||||
auto buffer = QBuffer(&bytes);
|
||||
|
||||
@@ -889,7 +889,6 @@ not_null<HistoryItem*> DownloadManager::generateItem(
|
||||
const auto replyTo = FullReplyTo();
|
||||
const auto viaBotId = UserId();
|
||||
const auto date = base::unixtime::now();
|
||||
const auto postAuthor = QString();
|
||||
const auto caption = TextWithEntities();
|
||||
const auto make = [&](const auto media) {
|
||||
return history->makeMessage(
|
||||
|
||||
@@ -343,12 +343,6 @@ int Folder::storiesUnreadCount() const {
|
||||
return _storiesUnreadCount;
|
||||
}
|
||||
|
||||
void Folder::requestChatListMessage() {
|
||||
if (!chatListMessageKnown()) {
|
||||
owner().histories().requestDialogEntry(this);
|
||||
}
|
||||
}
|
||||
|
||||
TimeId Folder::adjustedChatListTimeId() const {
|
||||
return chatListTimeId();
|
||||
}
|
||||
|
||||
@@ -49,9 +49,9 @@ public:
|
||||
Dialogs::BadgesState chatListBadgesState() const override;
|
||||
HistoryItem *chatListMessage() const override;
|
||||
bool chatListMessageKnown() const override;
|
||||
void requestChatListMessage() override;
|
||||
const QString &chatListName() const override;
|
||||
const QString &chatListNameSortKey() const override;
|
||||
int chatListNameVersion() const override;
|
||||
const base::flat_set<QString> &chatListNameWords() const override;
|
||||
const base::flat_set<QChar> &chatListFirstLetters() const override;
|
||||
|
||||
@@ -82,8 +82,6 @@ public:
|
||||
private:
|
||||
void indexNameParts();
|
||||
|
||||
int chatListNameVersion() const override;
|
||||
|
||||
void reorderLastHistories();
|
||||
|
||||
void paintUserpic(
|
||||
|
||||
@@ -15,13 +15,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "apiwrap.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
constexpr auto kRefreshDefaultListEach = 60 * 60 * crl::time(1000);
|
||||
constexpr auto kRecentRequestTimeout = 10 * crl::time(1000);
|
||||
constexpr auto kMaxTimeout = 6 * 60 * 60 * crl::time(1000);
|
||||
|
||||
} // namespace
|
||||
|
||||
ForumIcons::ForumIcons(not_null<Session*> owner)
|
||||
: _owner(owner)
|
||||
|
||||
@@ -98,6 +98,7 @@ public:
|
||||
|
||||
void setRealRootId(MsgId realId);
|
||||
void readTillEnd();
|
||||
void requestChatListMessage();
|
||||
|
||||
void applyTopic(const MTPDforumTopic &data);
|
||||
|
||||
@@ -109,9 +110,9 @@ public:
|
||||
Dialogs::BadgesState chatListBadgesState() const override;
|
||||
HistoryItem *chatListMessage() const override;
|
||||
bool chatListMessageKnown() const override;
|
||||
void requestChatListMessage() override;
|
||||
const QString &chatListName() const override;
|
||||
const QString &chatListNameSortKey() const override;
|
||||
int chatListNameVersion() const override;
|
||||
const base::flat_set<QString> &chatListNameWords() const override;
|
||||
const base::flat_set<QChar> &chatListFirstLetters() const override;
|
||||
|
||||
@@ -187,8 +188,6 @@ private:
|
||||
void allowChatListMessageResolve();
|
||||
void resolveChatListMessageGroup();
|
||||
|
||||
int chatListNameVersion() const override;
|
||||
|
||||
void subscribeToUnreadChanges();
|
||||
[[nodiscard]] Dialogs::UnreadState unreadStateFor(
|
||||
int count,
|
||||
|
||||
@@ -555,6 +555,10 @@ bool Media::hasSpoiler() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
crl::time Media::ttlSeconds() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Media::consumeMessageText(const TextWithEntities &text) {
|
||||
return false;
|
||||
}
|
||||
@@ -849,12 +853,14 @@ MediaFile::MediaFile(
|
||||
not_null<HistoryItem*> parent,
|
||||
not_null<DocumentData*> document,
|
||||
bool skipPremiumEffect,
|
||||
bool spoiler)
|
||||
bool spoiler,
|
||||
crl::time ttlSeconds)
|
||||
: Media(parent)
|
||||
, _document(document)
|
||||
, _emoji(document->sticker() ? document->sticker()->alt : QString())
|
||||
, _skipPremiumEffect(skipPremiumEffect)
|
||||
, _spoiler(spoiler) {
|
||||
, _spoiler(spoiler)
|
||||
, _ttlSeconds(ttlSeconds) {
|
||||
parent->history()->owner().registerDocumentItem(_document, parent);
|
||||
|
||||
if (!_emoji.isEmpty()) {
|
||||
@@ -882,7 +888,8 @@ std::unique_ptr<Media> MediaFile::clone(not_null<HistoryItem*> parent) {
|
||||
parent,
|
||||
_document,
|
||||
!_document->session().premium(),
|
||||
_spoiler);
|
||||
_spoiler,
|
||||
_ttlSeconds);
|
||||
}
|
||||
|
||||
DocumentData *MediaFile::document() const {
|
||||
@@ -1129,6 +1136,14 @@ bool MediaFile::hasSpoiler() const {
|
||||
return _spoiler;
|
||||
}
|
||||
|
||||
crl::time MediaFile::ttlSeconds() const {
|
||||
return _ttlSeconds;
|
||||
}
|
||||
|
||||
bool MediaFile::allowsForward() const {
|
||||
return !ttlSeconds();
|
||||
}
|
||||
|
||||
bool MediaFile::updateInlineResultMedia(const MTPMessageMedia &media) {
|
||||
if (media.type() != mtpc_messageMediaDocument) {
|
||||
return false;
|
||||
|
||||
@@ -174,6 +174,7 @@ public:
|
||||
virtual bool dropForwardedInfo() const;
|
||||
virtual bool forceForwardedInfo() const;
|
||||
[[nodiscard]] virtual bool hasSpoiler() const;
|
||||
[[nodiscard]] virtual crl::time ttlSeconds() const;
|
||||
|
||||
[[nodiscard]] virtual bool consumeMessageText(
|
||||
const TextWithEntities &text);
|
||||
@@ -256,7 +257,8 @@ public:
|
||||
not_null<HistoryItem*> parent,
|
||||
not_null<DocumentData*> document,
|
||||
bool skipPremiumEffect,
|
||||
bool spoiler);
|
||||
bool spoiler,
|
||||
crl::time ttlSeconds);
|
||||
~MediaFile();
|
||||
|
||||
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
|
||||
@@ -278,6 +280,8 @@ public:
|
||||
bool forwardedBecomesUnread() const override;
|
||||
bool dropForwardedInfo() const override;
|
||||
bool hasSpoiler() const override;
|
||||
crl::time ttlSeconds() const override;
|
||||
bool allowsForward() const override;
|
||||
|
||||
bool updateInlineResultMedia(const MTPMessageMedia &media) override;
|
||||
bool updateSentMedia(const MTPMessageMedia &media) override;
|
||||
@@ -292,6 +296,9 @@ private:
|
||||
bool _skipPremiumEffect = false;
|
||||
bool _spoiler = false;
|
||||
|
||||
// Video (unsupported) / Voice / Round.
|
||||
crl::time _ttlSeconds = 0;
|
||||
|
||||
};
|
||||
|
||||
class MediaContact final : public Media {
|
||||
|
||||
@@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_folder.h"
|
||||
#include "data/data_forum.h"
|
||||
#include "data/data_forum_topic.h"
|
||||
#include "data/data_saved_messages.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_histories.h"
|
||||
@@ -1029,6 +1030,10 @@ bool PeerData::sharedMediaInfo() const {
|
||||
return isSelf() || isRepliesChat();
|
||||
}
|
||||
|
||||
bool PeerData::savedSublistsInfo() const {
|
||||
return isSelf() && owner().savedMessages().supported();
|
||||
}
|
||||
|
||||
bool PeerData::hasStoriesHidden() const {
|
||||
if (const auto user = asUser()) {
|
||||
return user->hasStoriesHidden();
|
||||
|
||||
@@ -165,6 +165,7 @@ public:
|
||||
virtual ~PeerData();
|
||||
|
||||
static constexpr auto kServiceNotificationsId = peerFromUser(777000);
|
||||
static constexpr auto kSavedHiddenAuthorId = peerFromUser(2666000);
|
||||
|
||||
[[nodiscard]] Data::Session &owner() const;
|
||||
[[nodiscard]] Main::Session &session() const;
|
||||
@@ -202,6 +203,7 @@ public:
|
||||
[[nodiscard]] bool isGigagroup() const;
|
||||
[[nodiscard]] bool isRepliesChat() const;
|
||||
[[nodiscard]] bool sharedMediaInfo() const;
|
||||
[[nodiscard]] bool savedSublistsInfo() const;
|
||||
[[nodiscard]] bool hasStoriesHidden() const;
|
||||
void setStoriesHidden(bool hidden);
|
||||
|
||||
@@ -212,6 +214,9 @@ public:
|
||||
[[nodiscard]] bool isServiceUser() const {
|
||||
return isUser() && !(id.value % 1000);
|
||||
}
|
||||
[[nodiscard]] bool isSavedHiddenAuthor() const {
|
||||
return (id == kSavedHiddenAuthorId);
|
||||
}
|
||||
|
||||
[[nodiscard]] Data::Forum *forum() const;
|
||||
[[nodiscard]] Data::ForumTopic *forumTopicFor(MsgId rootId) const;
|
||||
|
||||
@@ -226,7 +226,6 @@ bool PhotoMedia::setToClipboard() {
|
||||
if (fallback.isNull()) {
|
||||
return false;
|
||||
}
|
||||
const auto bytes = imageBytes(large);
|
||||
auto mime = std::make_unique<QMimeData>();
|
||||
mime->setImageData(std::move(fallback));
|
||||
if (auto bytes = imageBytes(large); !bytes.isEmpty()) {
|
||||
|
||||
@@ -141,6 +141,18 @@ int PremiumLimits::topicsPinnedCurrent() const {
|
||||
return appConfigLimit("topics_pinned_limit", 5);
|
||||
}
|
||||
|
||||
int PremiumLimits::savedSublistsPinnedDefault() const {
|
||||
return appConfigLimit("saved_dialogs_pinned_limit_default", 5);
|
||||
}
|
||||
int PremiumLimits::savedSublistsPinnedPremium() const {
|
||||
return appConfigLimit("saved_dialogs_pinned_limit_premium", 100);
|
||||
}
|
||||
int PremiumLimits::savedSublistsPinnedCurrent() const {
|
||||
return isPremium()
|
||||
? savedSublistsPinnedPremium()
|
||||
: savedSublistsPinnedDefault();
|
||||
}
|
||||
|
||||
int PremiumLimits::channelsPublicDefault() const {
|
||||
return appConfigLimit("channels_public_limit_default", 10);
|
||||
}
|
||||
|
||||
@@ -59,6 +59,10 @@ public:
|
||||
|
||||
[[nodiscard]] int topicsPinnedCurrent() const;
|
||||
|
||||
[[nodiscard]] int savedSublistsPinnedDefault() const;
|
||||
[[nodiscard]] int savedSublistsPinnedPremium() const;
|
||||
[[nodiscard]] int savedSublistsPinnedCurrent() const;
|
||||
|
||||
[[nodiscard]] int channelsPublicDefault() const;
|
||||
[[nodiscard]] int channelsPublicPremium() const;
|
||||
[[nodiscard]] int channelsPublicCurrent() const;
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
/*
|
||||
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 "data/data_saved_messages.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_saved_sublist.h"
|
||||
#include "data/data_session.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
constexpr auto kPerPage = 50;
|
||||
constexpr auto kFirstPerPage = 10;
|
||||
|
||||
} // namespace
|
||||
|
||||
SavedMessages::SavedMessages(not_null<Session*> owner)
|
||||
: _owner(owner)
|
||||
, _chatsList(
|
||||
&owner->session(),
|
||||
FilterId(),
|
||||
owner->maxPinnedChatsLimitValue(this))
|
||||
, _loadMore([=] { sendLoadMoreRequests(); }) {
|
||||
}
|
||||
|
||||
SavedMessages::~SavedMessages() = default;
|
||||
|
||||
bool SavedMessages::supported() const {
|
||||
return !_unsupported;
|
||||
}
|
||||
|
||||
Session &SavedMessages::owner() const {
|
||||
return *_owner;
|
||||
}
|
||||
|
||||
Main::Session &SavedMessages::session() const {
|
||||
return _owner->session();
|
||||
}
|
||||
|
||||
not_null<Dialogs::MainList*> SavedMessages::chatsList() {
|
||||
return &_chatsList;
|
||||
}
|
||||
|
||||
not_null<SavedSublist*> SavedMessages::sublist(not_null<PeerData*> peer) {
|
||||
const auto i = _sublists.find(peer);
|
||||
if (i != end(_sublists)) {
|
||||
return i->second.get();
|
||||
}
|
||||
return _sublists.emplace(
|
||||
peer,
|
||||
std::make_unique<SavedSublist>(peer)).first->second.get();
|
||||
}
|
||||
|
||||
void SavedMessages::loadMore() {
|
||||
_loadMoreScheduled = true;
|
||||
_loadMore.call();
|
||||
}
|
||||
|
||||
void SavedMessages::loadMore(not_null<SavedSublist*> sublist) {
|
||||
_loadMoreSublistsScheduled.emplace(sublist);
|
||||
_loadMore.call();
|
||||
}
|
||||
|
||||
void SavedMessages::sendLoadMore() {
|
||||
if (_loadMoreRequestId || _chatsList.loaded()) {
|
||||
return;
|
||||
} else if (!_pinnedLoaded) {
|
||||
loadPinned();
|
||||
}
|
||||
_loadMoreRequestId = _owner->session().api().request(
|
||||
MTPmessages_GetSavedDialogs(
|
||||
MTP_flags(MTPmessages_GetSavedDialogs::Flag::f_exclude_pinned),
|
||||
MTP_int(_offsetDate),
|
||||
MTP_int(_offsetId),
|
||||
_offsetPeer ? _offsetPeer->input : MTP_inputPeerEmpty(),
|
||||
MTP_int(kPerPage),
|
||||
MTP_long(0)) // hash
|
||||
).done([=](const MTPmessages_SavedDialogs &result) {
|
||||
apply(result, false);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
if (error.type() == u"SAVED_DIALOGS_UNSUPPORTED"_q) {
|
||||
_unsupported = true;
|
||||
}
|
||||
_chatsList.setLoaded();
|
||||
_loadMoreRequestId = 0;
|
||||
}).send();
|
||||
}
|
||||
|
||||
void SavedMessages::loadPinned() {
|
||||
if (_pinnedRequestId) {
|
||||
return;
|
||||
}
|
||||
_pinnedRequestId = _owner->session().api().request(
|
||||
MTPmessages_GetPinnedSavedDialogs()
|
||||
).done([=](const MTPmessages_SavedDialogs &result) {
|
||||
apply(result, true);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
if (error.type() == u"SAVED_DIALOGS_UNSUPPORTED"_q) {
|
||||
_unsupported = true;
|
||||
} else {
|
||||
_pinnedLoaded = true;
|
||||
}
|
||||
_pinnedRequestId = 0;
|
||||
}).send();
|
||||
}
|
||||
|
||||
void SavedMessages::sendLoadMore(not_null<SavedSublist*> sublist) {
|
||||
if (_loadMoreRequests.contains(sublist) || sublist->isFullLoaded()) {
|
||||
return;
|
||||
}
|
||||
const auto &list = sublist->messages();
|
||||
const auto offsetId = list.empty() ? MsgId(0) : list.back()->id;
|
||||
const auto offsetDate = list.empty() ? MsgId(0) : list.back()->date();
|
||||
const auto limit = offsetId ? kPerPage : kFirstPerPage;
|
||||
const auto requestId = _owner->session().api().request(
|
||||
MTPmessages_GetSavedHistory(
|
||||
sublist->peer()->input,
|
||||
MTP_int(offsetId),
|
||||
MTP_int(offsetDate),
|
||||
MTP_int(0), // add_offset
|
||||
MTP_int(limit),
|
||||
MTP_int(0), // max_id
|
||||
MTP_int(0), // min_id
|
||||
MTP_long(0)) // hash
|
||||
).done([=](const MTPmessages_Messages &result) {
|
||||
auto count = 0;
|
||||
auto list = (const QVector<MTPMessage>*)nullptr;
|
||||
result.match([](const MTPDmessages_channelMessages &) {
|
||||
LOG(("API Error: messages.channelMessages in sublist."));
|
||||
}, [](const MTPDmessages_messagesNotModified &) {
|
||||
LOG(("API Error: messages.messagesNotModified in sublist."));
|
||||
}, [&](const auto &data) {
|
||||
owner().processUsers(data.vusers());
|
||||
owner().processChats(data.vchats());
|
||||
list = &data.vmessages().v;
|
||||
if constexpr (MTPDmessages_messages::Is<decltype(data)>()) {
|
||||
count = int(list->size());
|
||||
} else {
|
||||
count = data.vcount().v;
|
||||
}
|
||||
});
|
||||
|
||||
_loadMoreRequests.remove(sublist);
|
||||
if (!list) {
|
||||
sublist->setFullLoaded();
|
||||
return;
|
||||
}
|
||||
auto items = std::vector<not_null<HistoryItem*>>();
|
||||
items.reserve(list->size());
|
||||
for (const auto &message : *list) {
|
||||
const auto item = owner().addNewMessage(
|
||||
message,
|
||||
{},
|
||||
NewMessageType::Existing);
|
||||
if (item) {
|
||||
items.push_back(item);
|
||||
}
|
||||
}
|
||||
sublist->append(std::move(items), count);
|
||||
if (result.type() == mtpc_messages_messages) {
|
||||
sublist->setFullLoaded();
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
if (error.type() == u"SAVED_DIALOGS_UNSUPPORTED"_q) {
|
||||
_unsupported = true;
|
||||
}
|
||||
sublist->setFullLoaded();
|
||||
_loadMoreRequests.remove(sublist);
|
||||
}).send();
|
||||
_loadMoreRequests[sublist] = requestId;
|
||||
}
|
||||
|
||||
void SavedMessages::apply(
|
||||
const MTPmessages_SavedDialogs &result,
|
||||
bool pinned) {
|
||||
auto list = (const QVector<MTPSavedDialog>*)nullptr;
|
||||
result.match([](const MTPDmessages_savedDialogsNotModified &) {
|
||||
LOG(("API Error: messages.savedDialogsNotModified."));
|
||||
}, [&](const auto &data) {
|
||||
_owner->processUsers(data.vusers());
|
||||
_owner->processChats(data.vchats());
|
||||
_owner->processMessages(
|
||||
data.vmessages(),
|
||||
NewMessageType::Existing);
|
||||
list = &data.vdialogs().v;
|
||||
});
|
||||
if (pinned) {
|
||||
_pinnedRequestId = 0;
|
||||
_pinnedLoaded = true;
|
||||
} else {
|
||||
_loadMoreRequestId = 0;
|
||||
}
|
||||
if (!list) {
|
||||
if (!pinned) {
|
||||
_chatsList.setLoaded();
|
||||
}
|
||||
return;
|
||||
}
|
||||
auto lastValid = false;
|
||||
auto offsetDate = TimeId();
|
||||
auto offsetId = MsgId();
|
||||
auto offsetPeer = (PeerData*)nullptr;
|
||||
const auto selfId = _owner->session().userPeerId();
|
||||
for (const auto &dialog : *list) {
|
||||
const auto &data = dialog.data();
|
||||
const auto peer = _owner->peer(peerFromMTP(data.vpeer()));
|
||||
const auto topId = MsgId(data.vtop_message().v);
|
||||
if (const auto item = _owner->message(selfId, topId)) {
|
||||
offsetPeer = peer;
|
||||
offsetDate = item->date();
|
||||
offsetId = topId;
|
||||
lastValid = true;
|
||||
const auto entry = sublist(peer);
|
||||
const auto entryPinned = pinned || data.is_pinned();
|
||||
entry->applyMaybeLast(item);
|
||||
_owner->setPinnedFromEntryList(entry, entryPinned);
|
||||
} else {
|
||||
lastValid = false;
|
||||
}
|
||||
}
|
||||
if (pinned) {
|
||||
} else if (!lastValid) {
|
||||
LOG(("API Error: Unknown message in the end of a slice."));
|
||||
_chatsList.setLoaded();
|
||||
} else if (result.type() == mtpc_messages_savedDialogs) {
|
||||
_chatsList.setLoaded();
|
||||
} else if ((_offsetDate > 0 && offsetDate > _offsetDate)
|
||||
|| (offsetDate == _offsetDate
|
||||
&& offsetId == _offsetId
|
||||
&& offsetPeer == _offsetPeer)) {
|
||||
LOG(("API Error: Bad order in messages.savedDialogs."));
|
||||
_chatsList.setLoaded();
|
||||
} else {
|
||||
_offsetDate = offsetDate;
|
||||
_offsetId = offsetId;
|
||||
_offsetPeer = offsetPeer;
|
||||
}
|
||||
}
|
||||
|
||||
void SavedMessages::sendLoadMoreRequests() {
|
||||
if (_loadMoreScheduled) {
|
||||
sendLoadMore();
|
||||
}
|
||||
for (const auto sublist : base::take(_loadMoreSublistsScheduled)) {
|
||||
sendLoadMore(sublist);
|
||||
}
|
||||
}
|
||||
|
||||
void SavedMessages::apply(const MTPDupdatePinnedSavedDialogs &update) {
|
||||
const auto list = update.vorder();
|
||||
if (!list) {
|
||||
loadPinned();
|
||||
return;
|
||||
}
|
||||
const auto &order = list->v;
|
||||
const auto notLoaded = [&](const MTPDialogPeer &peer) {
|
||||
return peer.match([&](const MTPDdialogPeer &data) {
|
||||
const auto peer = _owner->peer(peerFromMTP(data.vpeer()));
|
||||
return !_sublists.contains(peer);
|
||||
}, [&](const MTPDdialogPeerFolder &data) {
|
||||
LOG(("API Error: "
|
||||
"updatePinnedSavedDialogs has folders."));
|
||||
return false;
|
||||
});
|
||||
};
|
||||
if (!ranges::none_of(order, notLoaded)) {
|
||||
loadPinned();
|
||||
} else {
|
||||
_chatsList.pinned()->applyList(_owner, order);
|
||||
_owner->notifyPinnedDialogsOrderUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
void SavedMessages::apply(const MTPDupdateSavedDialogPinned &update) {
|
||||
update.vpeer().match([&](const MTPDdialogPeer &data) {
|
||||
const auto peer = _owner->peer(peerFromMTP(data.vpeer()));
|
||||
const auto i = _sublists.find(peer);
|
||||
if (i != end(_sublists)) {
|
||||
const auto entry = i->second.get();
|
||||
_owner->setChatPinned(entry, FilterId(), update.is_pinned());
|
||||
} else {
|
||||
loadPinned();
|
||||
}
|
||||
}, [&](const MTPDdialogPeerFolder &data) {
|
||||
DEBUG_LOG(("API Error: Folder in updateSavedDialogPinned."));
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
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 "dialogs/dialogs_main_list.h"
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Data {
|
||||
|
||||
class Session;
|
||||
class SavedSublist;
|
||||
|
||||
class SavedMessages final {
|
||||
public:
|
||||
explicit SavedMessages(not_null<Session*> owner);
|
||||
~SavedMessages();
|
||||
|
||||
[[nodiscard]] bool supported() const;
|
||||
|
||||
[[nodiscard]] Session &owner() const;
|
||||
[[nodiscard]] Main::Session &session() const;
|
||||
|
||||
[[nodiscard]] not_null<Dialogs::MainList*> chatsList();
|
||||
[[nodiscard]] not_null<SavedSublist*> sublist(not_null<PeerData*> peer);
|
||||
|
||||
void loadMore();
|
||||
void loadMore(not_null<SavedSublist*> sublist);
|
||||
|
||||
void apply(const MTPDupdatePinnedSavedDialogs &update);
|
||||
void apply(const MTPDupdateSavedDialogPinned &update);
|
||||
|
||||
private:
|
||||
void loadPinned();
|
||||
void apply(const MTPmessages_SavedDialogs &result, bool pinned);
|
||||
|
||||
void sendLoadMore();
|
||||
void sendLoadMore(not_null<SavedSublist*> sublist);
|
||||
void sendLoadMoreRequests();
|
||||
|
||||
const not_null<Session*> _owner;
|
||||
|
||||
Dialogs::MainList _chatsList;
|
||||
base::flat_map<
|
||||
not_null<PeerData*>,
|
||||
std::unique_ptr<SavedSublist>> _sublists;
|
||||
|
||||
base::flat_map<not_null<SavedSublist*>, mtpRequestId> _loadMoreRequests;
|
||||
mtpRequestId _loadMoreRequestId = 0;
|
||||
mtpRequestId _pinnedRequestId = 0;
|
||||
|
||||
TimeId _offsetDate = 0;
|
||||
MsgId _offsetId = 0;
|
||||
PeerData *_offsetPeer = nullptr;
|
||||
|
||||
SingleQueuedInvokation _loadMore;
|
||||
base::flat_set<not_null<SavedSublist*>> _loadMoreSublistsScheduled;
|
||||
bool _loadMoreScheduled = false;
|
||||
|
||||
bool _pinnedLoaded = false;
|
||||
bool _unsupported = false;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Data
|
||||
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
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 "data/data_saved_sublist.h"
|
||||
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_saved_messages.h"
|
||||
#include "data/data_session.h"
|
||||
#include "history/view/history_view_item_preview.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
|
||||
namespace Data {
|
||||
|
||||
SavedSublist::SavedSublist(not_null<PeerData*> peer)
|
||||
: Entry(&peer->owner(), Dialogs::Entry::Type::SavedSublist)
|
||||
, _history(peer->owner().history(peer)) {
|
||||
}
|
||||
|
||||
SavedSublist::~SavedSublist() = default;
|
||||
|
||||
not_null<History*> SavedSublist::history() const {
|
||||
return _history;
|
||||
}
|
||||
|
||||
not_null<PeerData*> SavedSublist::peer() const {
|
||||
return _history->peer;
|
||||
}
|
||||
|
||||
bool SavedSublist::isHiddenAuthor() const {
|
||||
return peer()->isSavedHiddenAuthor();
|
||||
}
|
||||
|
||||
bool SavedSublist::isFullLoaded() const {
|
||||
return (_flags & Flag::FullLoaded) != 0;
|
||||
}
|
||||
|
||||
auto SavedSublist::messages() const
|
||||
-> const std::vector<not_null<HistoryItem*>> & {
|
||||
return _items;
|
||||
}
|
||||
|
||||
void SavedSublist::applyMaybeLast(not_null<HistoryItem*> item, bool added) {
|
||||
const auto before = [](
|
||||
not_null<HistoryItem*> a,
|
||||
not_null<HistoryItem*> b) {
|
||||
return IsServerMsgId(a->id)
|
||||
? (IsServerMsgId(b->id) ? (a->id < b->id) : true)
|
||||
: (IsServerMsgId(b->id) ? false : (a->id < b->id));
|
||||
};
|
||||
|
||||
if (_items.empty()) {
|
||||
_items.push_back(item);
|
||||
} else if (_items.front() == item) {
|
||||
return;
|
||||
} else if (!isFullLoaded()
|
||||
&& _items.size() == 1
|
||||
&& before(_items.front(), item)) {
|
||||
_items[0] = item;
|
||||
} else if (before(_items.back(), item)) {
|
||||
for (auto i = begin(_items); i != end(_items); ++i) {
|
||||
if (item == *i) {
|
||||
return;
|
||||
} else if (before(*i, item)) {
|
||||
_items.insert(i, item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (added && _fullCount) {
|
||||
++*_fullCount;
|
||||
}
|
||||
if (_items.front() == item) {
|
||||
setChatListTimeId(item->date());
|
||||
resolveChatListMessageGroup();
|
||||
}
|
||||
_changed.fire({});
|
||||
}
|
||||
|
||||
void SavedSublist::removeOne(not_null<HistoryItem*> item) {
|
||||
if (_items.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto last = (_items.front() == item);
|
||||
const auto from = ranges::remove(_items, item);
|
||||
const auto removed = end(_items) - from;
|
||||
if (removed) {
|
||||
_items.erase(from, end(_items));
|
||||
}
|
||||
if (_fullCount) {
|
||||
--*_fullCount;
|
||||
}
|
||||
if (last) {
|
||||
if (_items.empty()) {
|
||||
if (isFullLoaded()) {
|
||||
updateChatListExistence();
|
||||
} else {
|
||||
updateChatListEntry();
|
||||
crl::on_main(this, [=] {
|
||||
owner().savedMessages().loadMore(this);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setChatListTimeId(_items.front()->date());
|
||||
}
|
||||
}
|
||||
if (removed || _fullCount) {
|
||||
_changed.fire({});
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<> SavedSublist::changes() const {
|
||||
return _changed.events();
|
||||
}
|
||||
|
||||
std::optional<int> SavedSublist::fullCount() const {
|
||||
return isFullLoaded() ? int(_items.size()) : _fullCount;
|
||||
}
|
||||
|
||||
rpl::producer<int> SavedSublist::fullCountValue() const {
|
||||
return _changed.events_starting_with({}) | rpl::map([=] {
|
||||
return fullCount();
|
||||
}) | rpl::filter_optional();
|
||||
}
|
||||
|
||||
void SavedSublist::append(
|
||||
std::vector<not_null<HistoryItem*>> &&items,
|
||||
int fullCount) {
|
||||
_fullCount = fullCount;
|
||||
if (items.empty()) {
|
||||
setFullLoaded();
|
||||
} else if (_items.empty()) {
|
||||
_items = std::move(items);
|
||||
setChatListTimeId(_items.front()->date());
|
||||
_changed.fire({});
|
||||
} else if (_items.back()->id > items.front()->id) {
|
||||
_items.insert(end(_items), begin(items), end(items));
|
||||
_changed.fire({});
|
||||
} else {
|
||||
_items.insert(end(_items), begin(items), end(items));
|
||||
ranges::stable_sort(
|
||||
_items,
|
||||
ranges::greater(),
|
||||
&HistoryItem::id);
|
||||
ranges::unique(_items, ranges::greater(), &HistoryItem::id);
|
||||
_changed.fire({});
|
||||
}
|
||||
}
|
||||
|
||||
void SavedSublist::setFullLoaded(bool loaded) {
|
||||
if (loaded != isFullLoaded()) {
|
||||
if (loaded) {
|
||||
_flags |= Flag::FullLoaded;
|
||||
if (_items.empty()) {
|
||||
updateChatListExistence();
|
||||
}
|
||||
} else {
|
||||
_flags &= ~Flag::FullLoaded;
|
||||
}
|
||||
_changed.fire({});
|
||||
}
|
||||
}
|
||||
|
||||
int SavedSublist::fixedOnTopIndex() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool SavedSublist::shouldBeInChatList() const {
|
||||
return isPinnedDialog(FilterId()) || !_items.empty();
|
||||
}
|
||||
|
||||
Dialogs::UnreadState SavedSublist::chatListUnreadState() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
Dialogs::BadgesState SavedSublist::chatListBadgesState() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
HistoryItem *SavedSublist::chatListMessage() const {
|
||||
return _items.empty() ? nullptr : _items.front().get();
|
||||
}
|
||||
|
||||
bool SavedSublist::chatListMessageKnown() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
const QString &SavedSublist::chatListName() const {
|
||||
return _history->chatListName();
|
||||
}
|
||||
|
||||
const base::flat_set<QString> &SavedSublist::chatListNameWords() const {
|
||||
return _history->chatListNameWords();
|
||||
}
|
||||
|
||||
const base::flat_set<QChar> &SavedSublist::chatListFirstLetters() const {
|
||||
return _history->chatListFirstLetters();
|
||||
}
|
||||
|
||||
const QString &SavedSublist::chatListNameSortKey() const {
|
||||
return _history->chatListNameSortKey();
|
||||
}
|
||||
|
||||
int SavedSublist::chatListNameVersion() const {
|
||||
return _history->chatListNameVersion();
|
||||
}
|
||||
|
||||
void SavedSublist::paintUserpic(
|
||||
Painter &p,
|
||||
Ui::PeerUserpicView &view,
|
||||
const Dialogs::Ui::PaintContext &context) const {
|
||||
_history->paintUserpic(p, view, context);
|
||||
}
|
||||
|
||||
void SavedSublist::chatListPreloadData() {
|
||||
peer()->loadUserpic();
|
||||
allowChatListMessageResolve();
|
||||
}
|
||||
|
||||
void SavedSublist::allowChatListMessageResolve() {
|
||||
if (_flags & Flag::ResolveChatListMessage) {
|
||||
return;
|
||||
}
|
||||
_flags |= Flag::ResolveChatListMessage;
|
||||
resolveChatListMessageGroup();
|
||||
}
|
||||
|
||||
bool SavedSublist::hasOrphanMediaGroupPart() const {
|
||||
if (isFullLoaded() || _items.size() != 1) {
|
||||
return false;
|
||||
}
|
||||
return (_items.front()->groupId() != MessageGroupId());
|
||||
}
|
||||
|
||||
void SavedSublist::resolveChatListMessageGroup() {
|
||||
const auto item = chatListMessage();
|
||||
if (!(_flags & Flag::ResolveChatListMessage)
|
||||
|| !item
|
||||
|| !hasOrphanMediaGroupPart()) {
|
||||
return;
|
||||
}
|
||||
// If we set a single album part, request the full album.
|
||||
const auto withImages = !item->toPreview({
|
||||
.hideSender = true,
|
||||
.hideCaption = true }).images.empty();
|
||||
if (withImages) {
|
||||
owner().histories().requestGroupAround(item);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
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 "dialogs/ui/dialogs_message_view.h"
|
||||
#include "dialogs/dialogs_entry.h"
|
||||
|
||||
class PeerData;
|
||||
class History;
|
||||
|
||||
namespace Data {
|
||||
|
||||
class Session;
|
||||
|
||||
class SavedSublist final : public Dialogs::Entry {
|
||||
public:
|
||||
explicit SavedSublist(not_null<PeerData*> peer);
|
||||
~SavedSublist();
|
||||
|
||||
[[nodiscard]] not_null<History*> history() const;
|
||||
[[nodiscard]] not_null<PeerData*> peer() const;
|
||||
[[nodiscard]] bool isHiddenAuthor() const;
|
||||
[[nodiscard]] bool isFullLoaded() const;
|
||||
|
||||
[[nodiscard]] auto messages() const
|
||||
-> const std::vector<not_null<HistoryItem*>> &;
|
||||
void applyMaybeLast(not_null<HistoryItem*> item, bool added = false);
|
||||
void removeOne(not_null<HistoryItem*> item);
|
||||
void append(std::vector<not_null<HistoryItem*>> &&items, int fullCount);
|
||||
void setFullLoaded(bool loaded = true);
|
||||
|
||||
[[nodiscard]] rpl::producer<> changes() const;
|
||||
[[nodiscard]] std::optional<int> fullCount() const;
|
||||
[[nodiscard]] rpl::producer<int> fullCountValue() const;
|
||||
|
||||
[[nodiscard]] Dialogs::Ui::MessageView &lastItemDialogsView() {
|
||||
return _lastItemDialogsView;
|
||||
}
|
||||
|
||||
int fixedOnTopIndex() const override;
|
||||
bool shouldBeInChatList() const override;
|
||||
Dialogs::UnreadState chatListUnreadState() const override;
|
||||
Dialogs::BadgesState chatListBadgesState() const override;
|
||||
HistoryItem *chatListMessage() const override;
|
||||
bool chatListMessageKnown() const override;
|
||||
const QString &chatListName() const override;
|
||||
const QString &chatListNameSortKey() const override;
|
||||
int chatListNameVersion() const override;
|
||||
const base::flat_set<QString> &chatListNameWords() const override;
|
||||
const base::flat_set<QChar> &chatListFirstLetters() const override;
|
||||
|
||||
void chatListPreloadData() override;
|
||||
void paintUserpic(
|
||||
Painter &p,
|
||||
Ui::PeerUserpicView &view,
|
||||
const Dialogs::Ui::PaintContext &context) const override;
|
||||
|
||||
private:
|
||||
enum class Flag : uchar {
|
||||
ResolveChatListMessage = (1 << 0),
|
||||
FullLoaded = (1 << 1),
|
||||
};
|
||||
friend inline constexpr bool is_flag_type(Flag) { return true; }
|
||||
using Flags = base::flags<Flag>;
|
||||
|
||||
bool hasOrphanMediaGroupPart() const;
|
||||
void allowChatListMessageResolve();
|
||||
void resolveChatListMessageGroup();
|
||||
|
||||
const not_null<History*> _history;
|
||||
|
||||
std::vector<not_null<HistoryItem*>> _items;
|
||||
std::optional<int> _fullCount;
|
||||
rpl::event_stream<> _changed;
|
||||
Dialogs::Ui::MessageView _lastItemDialogsView;
|
||||
Flags _flags;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Data
|
||||
@@ -68,6 +68,7 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000);
|
||||
data.vid(),
|
||||
data.vfrom_id() ? *data.vfrom_id() : MTPPeer(),
|
||||
data.vpeer_id(),
|
||||
data.vsaved_peer_id() ? *data.vsaved_peer_id() : MTPPeer(),
|
||||
data.vfwd_from() ? *data.vfwd_from() : MTPMessageFwdHeader(),
|
||||
MTP_long(data.vvia_bot_id().value_or_empty()),
|
||||
data.vreply_to() ? *data.vreply_to() : MTPMessageReplyHeader(),
|
||||
@@ -216,6 +217,7 @@ void ScheduledMessages::sendNowSimpleMessage(
|
||||
update.vid(),
|
||||
peerToMTP(local->from()->id),
|
||||
peerToMTP(history->peer->id),
|
||||
MTPPeer(), // saved_peer_id
|
||||
MTPMessageFwdHeader(),
|
||||
MTPlong(), // via_bot_id
|
||||
replyHeader,
|
||||
|
||||
@@ -97,6 +97,7 @@ std::optional<SearchRequest> PrepareSearchRequest(
|
||||
peer->input,
|
||||
MTP_string(query),
|
||||
MTP_inputPeerEmpty(),
|
||||
MTPInputPeer(), // saved_peer_id
|
||||
MTP_int(topicRootId),
|
||||
filter,
|
||||
MTP_int(0), // min_date
|
||||
|
||||
@@ -60,6 +60,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_emoji_statuses.h"
|
||||
#include "data/data_forum_icons.h"
|
||||
#include "data/data_cloud_themes.h"
|
||||
#include "data/data_saved_messages.h"
|
||||
#include "data/data_saved_sublist.h"
|
||||
#include "data/data_stories.h"
|
||||
#include "data/data_streaming.h"
|
||||
#include "data/data_media_rotation.h"
|
||||
@@ -78,6 +80,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ayu/ayu_settings.h"
|
||||
#include "ayu/database/ayu_database.h"
|
||||
#include "ayu/messages/ayu_messages_controller.h"
|
||||
#include "ayu/ayu_state.h"
|
||||
|
||||
|
||||
namespace Data {
|
||||
@@ -267,7 +270,8 @@ Session::Session(not_null<Main::Session*> session)
|
||||
, _forumIcons(std::make_unique<ForumIcons>(this))
|
||||
, _notifySettings(std::make_unique<NotifySettings>(this))
|
||||
, _customEmojiManager(std::make_unique<CustomEmojiManager>(this))
|
||||
, _stories(std::make_unique<Stories>(this)) {
|
||||
, _stories(std::make_unique<Stories>(this))
|
||||
, _savedMessages(std::make_unique<SavedMessages>(this)) {
|
||||
_cache->open(_session->local().cacheKey());
|
||||
_bigFileCache->open(_session->local().cacheBigFileKey());
|
||||
|
||||
@@ -1723,6 +1727,11 @@ void Session::requestItemRepaint(not_null<const HistoryItem*> item) {
|
||||
topic->updateChatListEntry();
|
||||
}
|
||||
}
|
||||
if (const auto sublist = item->savedSublist()) {
|
||||
if (sublist->lastItemDialogsView().dependsOn(item)) {
|
||||
sublist->updateChatListEntry();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<not_null<const HistoryItem*>> Session::itemRepaintRequest() const {
|
||||
@@ -2110,13 +2119,17 @@ void Session::applyDialog(
|
||||
setPinnedFromEntryList(folder, data.is_pinned());
|
||||
}
|
||||
|
||||
bool Session::pinnedCanPin(not_null<Data::Thread*> thread) const {
|
||||
if (const auto topic = thread->asTopic()) {
|
||||
bool Session::pinnedCanPin(not_null<Dialogs::Entry*> entry) const {
|
||||
if (const auto sublist = entry->asSublist()) {
|
||||
const auto saved = &savedMessages();
|
||||
return pinnedChatsOrder(saved).size() < pinnedChatsLimit(saved);
|
||||
} else if (const auto topic = entry->asTopic()) {
|
||||
const auto forum = topic->forum();
|
||||
return pinnedChatsOrder(forum).size() < pinnedChatsLimit(forum);
|
||||
} else {
|
||||
const auto folder = entry->folder();
|
||||
return pinnedChatsOrder(folder).size() < pinnedChatsLimit(folder);
|
||||
}
|
||||
const auto folder = thread->folder();
|
||||
return pinnedChatsOrder(folder).size() < pinnedChatsLimit(folder);
|
||||
}
|
||||
|
||||
bool Session::pinnedCanPin(
|
||||
@@ -2148,6 +2161,11 @@ int Session::pinnedChatsLimit(not_null<Data::Forum*> forum) const {
|
||||
return limits.topicsPinnedCurrent();
|
||||
}
|
||||
|
||||
int Session::pinnedChatsLimit(not_null<Data::SavedMessages*> saved) const {
|
||||
const auto limits = Data::PremiumLimits(_session);
|
||||
return limits.savedSublistsPinnedCurrent();
|
||||
}
|
||||
|
||||
rpl::producer<int> Session::maxPinnedChatsLimitValue(
|
||||
Data::Folder *folder) const {
|
||||
// Premium limit from appconfig.
|
||||
@@ -2188,6 +2206,20 @@ rpl::producer<int> Session::maxPinnedChatsLimitValue(
|
||||
});
|
||||
}
|
||||
|
||||
rpl::producer<int> Session::maxPinnedChatsLimitValue(
|
||||
not_null<SavedMessages*> saved) const {
|
||||
// Premium limit from appconfig.
|
||||
// We always use premium limit in the MainList limit producer,
|
||||
// because it slices the list to that limit. We don't want to slice
|
||||
// premium-ly added chats from the pinned list because of sync issues.
|
||||
return rpl::single(rpl::empty_value()) | rpl::then(
|
||||
_session->account().appConfig().refreshed()
|
||||
) | rpl::map([=] {
|
||||
const auto limits = Data::PremiumLimits(_session);
|
||||
return limits.savedSublistsPinnedPremium();
|
||||
});
|
||||
}
|
||||
|
||||
const std::vector<Dialogs::Key> &Session::pinnedChatsOrder(
|
||||
Data::Folder *folder) const {
|
||||
return chatsList(folder)->pinned()->order();
|
||||
@@ -2203,6 +2235,11 @@ const std::vector<Dialogs::Key> &Session::pinnedChatsOrder(
|
||||
return forum->topicsList()->pinned()->order();
|
||||
}
|
||||
|
||||
const std::vector<Dialogs::Key> &Session::pinnedChatsOrder(
|
||||
not_null<Data::SavedMessages*> saved) const {
|
||||
return saved->chatsList()->pinned()->order();
|
||||
}
|
||||
|
||||
void Session::clearPinnedChats(Data::Folder *folder) {
|
||||
chatsList(folder)->pinned()->clear();
|
||||
}
|
||||
@@ -2219,7 +2256,7 @@ void Session::reorderTwoPinnedChats(
|
||||
? topic->forum()->topicsList()
|
||||
: filterId
|
||||
? chatsFilters().chatsList(filterId)
|
||||
: chatsList(key1.entry()->folder());
|
||||
: chatsListFor(key1.entry());
|
||||
list->pinned()->reorder(key1, key2);
|
||||
notifyPinnedDialogsOrderUpdated();
|
||||
}
|
||||
@@ -2353,6 +2390,11 @@ void Session::registerMessage(not_null<HistoryItem*> item) {
|
||||
const auto peerId = item->history()->peer->id;
|
||||
const auto list = messagesListForInsert(peerId);
|
||||
const auto itemId = item->id;
|
||||
|
||||
if (AyuState::isHidden(item)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto i = list->find(itemId);
|
||||
if (i != list->end()) {
|
||||
LOG(("App Error: Trying to re-registerMessage()."));
|
||||
@@ -2408,10 +2450,17 @@ void Session::unregisterMessageTTL(
|
||||
}
|
||||
|
||||
void Session::checkTTLs() {
|
||||
const auto settings = &AyuSettings::getInstance();
|
||||
|
||||
_ttlCheckTimer.cancel();
|
||||
const auto now = base::unixtime::now();
|
||||
while (!_ttlMessages.empty() && _ttlMessages.begin()->first <= now) {
|
||||
_ttlMessages.begin()->second.front()->destroy();
|
||||
if (!settings->saveDeletedMessages) {
|
||||
_ttlMessages.begin()->second.front()->destroy();
|
||||
}
|
||||
else {
|
||||
_ttlMessages.begin()->second.front()->setAyuHint(settings->deletedMark);
|
||||
}
|
||||
}
|
||||
scheduleNextTTLs();
|
||||
}
|
||||
@@ -2430,7 +2479,15 @@ void Session::processMessagesDeleted(
|
||||
const auto i = list ? list->find(messageId.v) : Messages::iterator();
|
||||
if (list && i != list->end()) {
|
||||
const auto history = i->second->history();
|
||||
i->second->destroy();
|
||||
|
||||
const auto settings = &AyuSettings::getInstance();
|
||||
if (!settings->saveDeletedMessages) {
|
||||
i->second->destroy();
|
||||
}
|
||||
else {
|
||||
i->second->setAyuHint(settings->deletedMark);
|
||||
}
|
||||
|
||||
if (!history->chatListMessageKnown()) {
|
||||
historiesToCheck.emplace(history);
|
||||
}
|
||||
@@ -2448,7 +2505,15 @@ void Session::processNonChannelMessagesDeleted(const QVector<MTPint> &data) {
|
||||
for (const auto &messageId : data) {
|
||||
if (const auto item = nonChannelMessage(messageId.v)) {
|
||||
const auto history = item->history();
|
||||
item->destroy();
|
||||
|
||||
const auto settings = &AyuSettings::getInstance();
|
||||
if (!settings->saveDeletedMessages) {
|
||||
item->destroy();
|
||||
}
|
||||
else {
|
||||
item->setAyuHint(settings->deletedMark);
|
||||
}
|
||||
|
||||
if (!history->chatListMessageKnown()) {
|
||||
historiesToCheck.emplace(history);
|
||||
}
|
||||
@@ -4233,6 +4298,8 @@ not_null<Dialogs::MainList*> Session::chatsListFor(
|
||||
const auto topic = entry->asTopic();
|
||||
return topic
|
||||
? topic->forum()->topicsList()
|
||||
: entry->asSublist()
|
||||
? _savedMessages->chatsList()
|
||||
: chatsList(entry->folder());
|
||||
}
|
||||
|
||||
@@ -4429,6 +4496,7 @@ void Session::insertCheckedServiceNotification(
|
||||
MTP_int(0), // Not used (would've been trimmed to 32 bits).
|
||||
peerToMTP(PeerData::kServiceNotificationsId),
|
||||
peerToMTP(PeerData::kServiceNotificationsId),
|
||||
MTPPeer(), // saved_peer_id
|
||||
MTPMessageFwdHeader(),
|
||||
MTPlong(), // via_bot_id
|
||||
MTPMessageReplyHeader(),
|
||||
|
||||