diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index f83c3e3b1..81651cec4 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1314,6 +1314,8 @@ PRIVATE settings/settings_main.h settings/settings_notifications.cpp settings/settings_notifications.h + settings/settings_notifications_type.cpp + settings/settings_notifications_type.h settings/settings_power_saving.cpp settings/settings_power_saving.h settings/settings_premium.cpp diff --git a/Telegram/Resources/icons/mini_forward.png b/Telegram/Resources/icons/mini_forward.png new file mode 100644 index 000000000..0b12af15d Binary files /dev/null and b/Telegram/Resources/icons/mini_forward.png differ diff --git a/Telegram/Resources/icons/mini_forward@2x.png b/Telegram/Resources/icons/mini_forward@2x.png new file mode 100644 index 000000000..c8194ef00 Binary files /dev/null and b/Telegram/Resources/icons/mini_forward@2x.png differ diff --git a/Telegram/Resources/icons/mini_forward@3x.png b/Telegram/Resources/icons/mini_forward@3x.png new file mode 100644 index 000000000..19cac4700 Binary files /dev/null and b/Telegram/Resources/icons/mini_forward@3x.png differ diff --git a/Telegram/Resources/icons/mini_reply_story.png b/Telegram/Resources/icons/mini_reply_story.png new file mode 100644 index 000000000..bfcbc0c48 Binary files /dev/null and b/Telegram/Resources/icons/mini_reply_story.png differ diff --git a/Telegram/Resources/icons/mini_reply_story@2x.png b/Telegram/Resources/icons/mini_reply_story@2x.png new file mode 100644 index 000000000..a3c75e8bf Binary files /dev/null and b/Telegram/Resources/icons/mini_reply_story@2x.png differ diff --git a/Telegram/Resources/icons/mini_reply_story@3x.png b/Telegram/Resources/icons/mini_reply_story@3x.png new file mode 100644 index 000000000..471cfa57d Binary files /dev/null and b/Telegram/Resources/icons/mini_reply_story@3x.png differ diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 8f3ca7a71..626e9ae5b 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -451,6 +451,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_show_from" = "Show notifications from"; "lng_settings_notify_all" = "All accounts"; "lng_settings_notify_all_about" = "Turn this off if you want to receive notifications only from the account you are currently using."; +"lng_settings_notify_global" = "Global settings"; "lng_settings_notify_title" = "Notifications for chats"; "lng_settings_desktop_notify" = "Desktop notifications"; "lng_settings_native_title" = "Native notifications"; @@ -458,8 +459,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_use_native_notifications" = "Use native notifications"; "lng_settings_notifications_position" = "Location on the screen"; "lng_settings_notifications_count" = "Notifications count"; -"lng_settings_sound_notify" = "Play sound"; -"lng_settings_sound_notify_off" = "Off"; +"lng_settings_sound_allowed" = "Allow sound"; "lng_settings_alert_windows" = "Flash the taskbar icon"; "lng_settings_alert_mac" = "Bounce the dock icon"; "lng_settings_alert_linux" = "Draw attention to the window"; @@ -480,6 +480,36 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_notification_hide_all" = "Hide all"; "lng_notification_sample" = "This is a sample notification"; "lng_notification_reminder" = "Reminder"; +"lng_notification_private_chats" = "Private chats"; +"lng_notification_groups" = "Groups"; +"lng_notification_channels" = "Channels"; +"lng_notification_click_to_change" = "Click here to change"; +"lng_notification_on" = "On, {exceptions}"; +"lng_notification_off" = "Off, {exceptions}"; +"lng_notification_exceptions#one" = "{count} exception"; +"lng_notification_exceptions#other" = "{count} exceptions"; +"lng_notification_exceptions_title" = "Exceptions"; +"lng_notification_title_private_chats" = "Notifications for private chats"; +"lng_notification_about_private_chats#one" = "Please note that **{count} chat** is listed as an exception and won't be affected by this change."; +"lng_notification_about_private_chats#other" = "Please note that **{count} chats** are listed as exceptions and won't be affected by this change."; +"lng_notification_title_groups" = "Notifications for groups"; +"lng_notification_about_groups#one" = "Please note that **{count} group** is listed as an exception and won't be affected by this change."; +"lng_notification_about_groups#other" = "Please note that **{count} groups** are listed as exceptions and won't be affected by this change."; +"lng_notification_title_channels" = "Notifications for channels"; +"lng_notification_about_channels#one" = "Please note that **{count} channel** is listed as an exception and won't be affected by this change."; +"lng_notification_about_channels#other" = "Please note that **{count} channels** are listed as exceptions and won't be affected by this change."; +"lng_notification_exceptions_view" = "View exceptions"; +"lng_notification_enable" = "Enable notifications"; +"lng_notification_sound" = "Sound"; +"lng_notification_tone" = "Notification tone"; +"lng_notification_exceptions_muted" = "Muted"; +"lng_notification_exceptions_unmuted" = "Unmuted"; +"lng_notification_exceptions_add" = "Add an exception"; +"lng_notification_exceptions_clear" = "Delete all exceptions"; +"lng_notification_exceptions_clear_sure" = "Are you sure you want to delete all exceptions?"; +"lng_notification_exceptions_clear_button" = "Delete"; +"lng_notification_exceptions_remove" = "Remove"; +"lng_notification_context_remove" = "Remove exception"; "lng_reaction_text" = "{reaction} to your «{text}»"; "lng_reaction_notext" = "{reaction} to your message"; diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 15007bceb..3b5ae67a8 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="4.9.4.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index f13710dae..f0efceda4 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,9,3,0 - PRODUCTVERSION 4,9,3,0 + FILEVERSION 4,9,4,0 + PRODUCTVERSION 4,9,4,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Radolyn Labs" VALUE "FileDescription", "AyuGram Desktop" - VALUE "FileVersion", "4.9.3.0" + VALUE "FileVersion", "4.9.4.0" VALUE "LegalCopyright", "Copyright (C) 2014-2023" VALUE "ProductName", "AyuGram Desktop" - VALUE "ProductVersion", "4.9.3.0" + VALUE "ProductVersion", "4.9.4.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 197deffe1..5d8768e00 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,9,3,0 - PRODUCTVERSION 4,9,3,0 + FILEVERSION 4,9,4,0 + PRODUCTVERSION 4,9,4,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.9.3.0" + VALUE "FileVersion", "4.9.4.0" VALUE "LegalCopyright", "Copyright (C) 2014-2023" VALUE "ProductName", "AyuGram Desktop" - VALUE "ProductVersion", "4.9.3.0" + VALUE "ProductVersion", "4.9.4.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index fec04ea1d..4c551d0e9 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -1889,17 +1889,8 @@ void ApiWrap::sendNotifySettingsUpdates() { } const auto &settings = session().data().notifySettings(); for (const auto type : base::take(_updateNotifyDefaults)) { - const auto input = [&] { - switch (type) { - case Data::DefaultNotify::User: return MTP_inputNotifyUsers(); - case Data::DefaultNotify::Group: return MTP_inputNotifyChats(); - case Data::DefaultNotify::Broadcast: - return MTP_inputNotifyBroadcasts(); - } - Unexpected("Default notify type in sendNotifySettingsUpdates"); - }(); request(MTPaccount_UpdateNotifySettings( - input, + Data::DefaultNotifyToMTP(type), settings.defaultSettings(type).serialize() )).afterDelay(kSmallDelayMs).send(); } diff --git a/Telegram/SourceFiles/boxes/choose_filter_box.cpp b/Telegram/SourceFiles/boxes/choose_filter_box.cpp index 181dccb1e..12d58b538 100644 --- a/Telegram/SourceFiles/boxes/choose_filter_box.cpp +++ b/Telegram/SourceFiles/boxes/choose_filter_box.cpp @@ -15,14 +15,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "lang/lang_keys.h" #include "main/main_session.h" -#include "ui/filter_icons.h" #include "ui/text/text_utilities.h" // Ui::Text::Bold #include "ui/widgets/buttons.h" #include "ui/widgets/popup_menu.h" #include "window/window_controller.h" #include "window/window_session_controller.h" -#include "styles/style_settings.h" -#include "styles/style_payments.h" // paymentsSectionButton #include "styles/style_media_player.h" // mediaPlayerMenuCheck namespace { @@ -32,16 +29,30 @@ Data::ChatFilter ChangedFilter( not_null history, bool add) { auto always = base::duplicate(filter.always()); - if (add) { - always.insert(history); - } else { - always.remove(history); - } auto never = base::duplicate(filter.never()); if (add) { never.remove(history); + const auto result = Data::ChatFilter( + filter.id(), + filter.title(), + filter.iconEmoji(), + filter.flags(), + filter.always(), + filter.pinned(), + std::move(never)); + if (result.contains(history)) { + return result; + } else { + never = base::duplicate(result.never()); + always.insert(history); + } } else { - never.insert(history); + const auto alwaysIt = always.find(history); + if (alwaysIt != end(always)) { + always.erase(alwaysIt); + } else { + never.insert(history); + } } return Data::ChatFilter( filter.id(), diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp index 90a989ce7..4a2b53c5f 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp @@ -622,9 +622,10 @@ void EditCaptionBox::setupDragArea() { }; // Avoid both drag areas appearing at one time. auto computeState = [=](const QMimeData *data) { + using DragState = Storage::MimeDataState; const auto state = Storage::ComputeMimeDataState(data); - return (state == Storage::MimeDataState::PhotoFiles) - ? Storage::MimeDataState::Image + return (state == DragState::PhotoFiles || state == DragState::Image) + ? (_asFile ? DragState::Files : DragState::Image) : state; }; const auto areas = DragArea::SetupDragAreaToContainer( diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index 6f27553d0..28286f377 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/peer_list_box.h" +#include "history/history.h" // chatListNameSortKey. #include "main/session/session_show.h" #include "main/main_session.h" #include "mainwidget.h" @@ -396,6 +397,27 @@ void PeerListController::setSearchNoResultsText(const QString &text) { } } +void PeerListController::sortByName() { + auto keys = base::flat_map(); + keys.reserve(delegate()->peerListFullRowsCount()); + const auto key = [&](const PeerListRow &row) { + const auto id = row.id(); + const auto i = keys.find(id); + if (i != end(keys)) { + return i->second; + } + const auto peer = row.peer(); + const auto history = peer->owner().history(peer); + return keys.emplace( + id, + history->chatListNameSortKey()).first->second; + }; + const auto predicate = [&](const PeerListRow &a, const PeerListRow &b) { + return (key(a).compare(key(b)) < 0); + }; + delegate()->peerListSortRows(predicate); +} + base::unique_qptr PeerListController::rowContextMenu( QWidget *parent, not_null row) { @@ -741,8 +763,8 @@ int PeerListRow::paintNameIconGetWidth( ? st::dialogsVerifiedIconOver : st::dialogsVerifiedIcon), .premium = &(selected - ? st::dialogsPremiumIconOver - : st::dialogsPremiumIcon), + ? st::dialogsPremiumIcon.over + : st::dialogsPremiumIcon.icon), .scam = &(selected ? st::dialogsScamFgOver : st::dialogsScamFg), .premiumFg = &(selected ? st::dialogsVerifiedIconBgOver diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index ac8ab554b..837bb6e2c 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -560,6 +560,8 @@ protected: delegate()->peerListSetSearchNoResults(std::move(noResults)); } + void sortByName(); + private: PeerListDelegate *_delegate = nullptr; std::unique_ptr _searchController = nullptr; diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp index 335531fc9..874e61e23 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp @@ -594,25 +594,6 @@ void ContactsBoxController::sort() { } } -void ContactsBoxController::sortByName() { - auto keys = base::flat_map(); - keys.reserve(delegate()->peerListFullRowsCount()); - const auto key = [&](const PeerListRow &row) { - const auto id = row.id(); - const auto i = keys.find(id); - if (i != end(keys)) { - return i->second; - } - const auto peer = row.peer(); - const auto history = peer->owner().history(peer); - return keys.emplace(id, history->chatListNameSortKey()).first->second; - }; - const auto predicate = [&](const PeerListRow &a, const PeerListRow &b) { - return (key(a).compare(key(b)) < 0); - }; - delegate()->peerListSortRows(predicate); -} - void ContactsBoxController::sortByOnline() { const auto now = base::unixtime::now(); const auto key = [&](const PeerListRow &row) { diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.h b/Telegram/SourceFiles/boxes/peer_list_controllers.h index 0ba108f01..35b4e0f15 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.h +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.h @@ -192,7 +192,6 @@ protected: private: void sort(); - void sortByName(); void sortByOnline(); void rebuildRows(); void checkForEmptyRows(); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp index 4c33b38fc..97a86be3b 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp @@ -319,16 +319,15 @@ not_null AddInnerToggle( button->geometryValue( ) | rpl::start_with_next([=](const QRect &r) { const auto w = st::rightsButtonToggleWidth; - constexpr auto kLineWidth = int(1); toggleButton->setGeometry( r.x() + r.width() - w, r.y(), w, r.height()); separator->setGeometry( - toggleButton->x() - kLineWidth, + toggleButton->x() - st::lineWidth, r.y() + (r.height() - separatorHeight) / 2, - kLineWidth, + st::lineWidth, separatorHeight); }, toggleButton->lifetime()); diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index 7313afd21..50f8886d9 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -28,8 +28,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/call_delayed.h" #include "boxes/premium_limits_box.h" #include "boxes/premium_preview_box.h" -#include "ui/boxes/confirm_box.h" -#include "ui/effects/animations.h" #include "ui/effects/scroll_content_shadow.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/buttons.h" @@ -42,9 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/attach/attach_album_preview.h" #include "ui/chat/attach/attach_single_file_preview.h" #include "ui/chat/attach/attach_single_media_preview.h" -#include "ui/text/format_values.h" #include "ui/grouped_layout.h" -#include "ui/text/text_options.h" #include "ui/toast/toast.h" #include "ui/controls/emoji_button.h" #include "ui/painter.h" @@ -54,16 +50,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_premium_limits.h" #include "data/stickers/data_stickers.h" #include "data/stickers/data_custom_emoji.h" -#include "media/clip/media_clip_reader.h" #include "api/api_common.h" #include "window/window_session_controller.h" #include "core/application.h" #include "core/core_settings.h" -#include "styles/style_chat.h" #include "styles/style_layers.h" #include "styles/style_boxes.h" #include "styles/style_chat_helpers.h" -#include "styles/style_info.h" #include "styles/style_menu_icons.h" #include @@ -77,10 +70,14 @@ constexpr auto kMaxMessageLength = 4096; using Ui::SendFilesWay; -inline bool CanAddUrls(const QList &urls) { +[[nodiscard]] inline bool CanAddUrls(const QList &urls) { return !urls.isEmpty() && ranges::all_of(urls, &QUrl::isLocalFile); } +[[nodiscard]] bool CanAddFiles(not_null data) { + return data->hasImage() || CanAddUrls(Core::ReadMimeUrls(data)); +} + void FileDialogCallback( FileDialog::OpenResult &&result, Fn checkResult, @@ -448,13 +445,15 @@ void SendFilesBox::setupDragArea() { auto computeState = [=](const QMimeData *data) { using DragState = Storage::MimeDataState; const auto state = Storage::ComputeMimeDataState(data); - return (state == DragState::PhotoFiles) - ? DragState::Image + return (state == DragState::PhotoFiles || state == DragState::Image) + ? (_sendWay.current().sendImagesAsPhotos() + ? DragState::Image + : DragState::Files) : state; }; const auto areas = DragArea::SetupDragAreaToContainer( this, - [=](not_null d) { return canAddFiles(d); }, + CanAddFiles, [=](bool f) { _caption->setAcceptDrops(f); }, [=] { updateControlsGeometry(); }, std::move(computeState)); @@ -1049,7 +1048,7 @@ void SendFilesBox::setupCaption() { not_null data, Ui::InputField::MimeAction action) { if (action == Ui::InputField::MimeAction::Check) { - return canAddFiles(data); + return CanAddFiles(data); } else if (action == Ui::InputField::MimeAction::Insert) { return addFiles(data); } @@ -1145,10 +1144,6 @@ void SendFilesBox::captionResized() { update(); } -bool SendFilesBox::canAddFiles(not_null data) const { - return data->hasImage() || CanAddUrls(Core::ReadMimeUrls(data)); -} - bool SendFilesBox::addFiles(not_null data) { const auto premium = _show->session().premium(); auto list = [&] { diff --git a/Telegram/SourceFiles/boxes/send_files_box.h b/Telegram/SourceFiles/boxes/send_files_box.h index 6e6c6fa82..2325e911a 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.h +++ b/Telegram/SourceFiles/boxes/send_files_box.h @@ -213,7 +213,6 @@ private: void updateControlsGeometry(); void updateCaptionPlaceholder(); - bool canAddFiles(not_null data) const; bool addFiles(not_null data); bool addFiles(Ui::PreparedList list); void addFile(Ui::PreparedFile &&file); diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index 7cba7daba..154961016 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -18,7 +18,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/toast/toast.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/multi_select.h" -#include "ui/widgets/buttons.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/input_fields.h" #include "ui/widgets/popup_menu.h" @@ -602,7 +601,7 @@ void ShareBox::submitWhenOnline() { submit(Api::DefaultSendWhenOnlineOptions()); } -void ShareBox::copyLink() { +void ShareBox::copyLink() const { if (const auto onstack = _descriptor.copyCallback) { onstack(); } diff --git a/Telegram/SourceFiles/boxes/share_box.h b/Telegram/SourceFiles/boxes/share_box.h index b13638577..e793abb6e 100644 --- a/Telegram/SourceFiles/boxes/share_box.h +++ b/Telegram/SourceFiles/boxes/share_box.h @@ -119,7 +119,7 @@ private: void submitSilent(); void submitScheduled(); void submitWhenOnline(); - void copyLink(); + void copyLink() const; bool searchByUsername(bool useCache = false); SendMenu::Type sendMenuType() const; diff --git a/Telegram/SourceFiles/boxes/stickers_box.cpp b/Telegram/SourceFiles/boxes/stickers_box.cpp index 237306d84..b1eef8d91 100644 --- a/Telegram/SourceFiles/boxes/stickers_box.cpp +++ b/Telegram/SourceFiles/boxes/stickers_box.cpp @@ -53,6 +53,22 @@ constexpr auto kArchivedLimitFirstRequest = 10; constexpr auto kArchivedLimitPerPage = 30; constexpr auto kHandleMegagroupSetAddressChangeTimeout = crl::time(1000); +[[nodiscard]] QString FillSetTitle( + not_null set, + int maxNameWidth, + int *outTitleWidth) { + auto result = set->title; + auto titleWidth = st::contactsNameStyle.font->width(result); + if (titleWidth > maxNameWidth) { + result = st::contactsNameStyle.font->elided(result, maxNameWidth); + titleWidth = st::contactsNameStyle.font->width(result); + } + if (outTitleWidth) { + *outTitleWidth = titleWidth; + } + return result; +} + } // namespace class StickersBox::CounterWidget : public Ui::RpWidget { @@ -98,22 +114,25 @@ public: void updateRows(); // refresh only pack cover stickers bool appendSet(not_null set); - StickersSetsOrder getOrder() const; - StickersSetsOrder getFullOrder() const; - StickersSetsOrder getRemovedSets() const; + StickersSetsOrder order() const; + StickersSetsOrder fullOrder() const; + StickersSetsOrder removedSets() const; void setFullOrder(const StickersSetsOrder &order); void setRemovedSets(const StickersSetsOrder &removed); + void setRowRemovedBySetId(uint64 setId, bool removed); + void setInstallSetCallback(Fn callback) { _installSetCallback = std::move(callback); } + void setRemoveSetCallback(Fn callback) { + _removeSetCallback = std::move(callback); + } void setLoadMoreCallback(Fn callback) { _loadMoreCallback = std::move(callback); } - void setMinHeight(int newWidth, int minHeight); - int getVisibleTop() const { return _visibleTop; } @@ -151,13 +170,13 @@ private: int32 pixh); ~Row(); - bool isRecentSet() const; - bool isMasksSet() const; - bool isEmojiSet() const; - bool isWebm() const; - bool isInstalled() const; - bool isUnread() const; - bool isArchived() const; + [[nodiscard]] bool isRecentSet() const; + [[nodiscard]] bool isMasksSet() const; + [[nodiscard]] bool isEmojiSet() const; + [[nodiscard]] bool isWebm() const; + [[nodiscard]] bool isInstalled() const; + [[nodiscard]] bool isUnread() const; + [[nodiscard]] bool isArchived() const; const not_null set; DocumentData *sticker = nullptr; @@ -212,7 +231,11 @@ private: void setPressed(SelectedRow pressed); void setup(); QRect relativeButtonRect(bool removeButton, bool installedSet) const; - void ensureRipple(const style::RippleAnimation &st, QImage mask, bool removeButton); + void ensureRipple( + const style::RippleAnimation &st, + QImage mask, + bool removeButton, + bool installedSet); bool shiftingAnimationCallback(crl::time now); void paintRow(Painter &p, not_null row, int index); @@ -237,10 +260,6 @@ private: void rebuildAppendSet(not_null set); void fillSetCover(not_null set, DocumentData **outSticker, int *outWidth, int *outHeight) const; int fillSetCount(not_null set) const; - [[nodiscard]] QString fillSetTitle( - not_null set, - int maxNameWidth, - int *outTitleWidth) const; [[nodiscard]] Data::StickersSetFlags fillSetFlags( not_null set) const; void rebuildMegagroupSet(); @@ -270,6 +289,7 @@ private: Ui::Animations::Basic _shiftingAnimation; Fn _installSetCallback; + Fn _removeSetCallback; Fn _loadMoreCallback; int _visibleTop = 0; @@ -598,17 +618,43 @@ void StickersBox::prepare() { _attached.widget()->hide(); } - const auto installCallback = [=](uint64 setId) { installSet(setId); }; - if (_featured.widget()) { - _featured.widget()->setInstallSetCallback(installCallback); - } - if (_archived.widget()) { - _archived.widget()->setInstallSetCallback(installCallback); - _archived.widget()->setLoadMoreCallback([=] { loadMoreArchived(); }); - } - if (_attached.widget()) { - _attached.widget()->setInstallSetCallback(installCallback); - _attached.widget()->setLoadMoreCallback([=] { showAttachedStickers(); }); + { + const auto installCallback = [=](uint64 setId) { installSet(setId); }; + const auto markAsInstalledCallback = [=](uint64 setId) { + if (_installed.widget()) { + _installed.widget()->setRowRemovedBySetId(setId, false); + } + if (_featured.widget()) { + _featured.widget()->setRowRemovedBySetId(setId, false); + } + }; + const auto markAsRemovedCallback = [=](uint64 setId) { + if (_installed.widget()) { + _installed.widget()->setRowRemovedBySetId(setId, true); + } + if (_featured.widget()) { + _featured.widget()->setRowRemovedBySetId(setId, true); + } + }; + if (const auto installed = _installed.widget()) { + installed->setInstallSetCallback(markAsInstalledCallback); + installed->setRemoveSetCallback(markAsRemovedCallback); + } + if (const auto featured = _featured.widget()) { + featured->setInstallSetCallback([=](uint64 setId) { + markAsInstalledCallback(setId); + installCallback(setId); + }); + featured->setRemoveSetCallback(markAsRemovedCallback); + } + if (const auto archived = _archived.widget()) { + archived->setInstallSetCallback(installCallback); + archived->setLoadMoreCallback([=] { loadMoreArchived(); }); + } + if (const auto attached = _attached.widget()) { + attached->setInstallSetCallback(installCallback); + attached->setLoadMoreCallback([=] { showAttachedStickers(); }); + } } if (_megagroupSet) { @@ -634,7 +680,7 @@ void StickersBox::prepare() { } else { // _section == Section::Featured _tab = &_featured; } - setInnerWidget(_tab->takeWidget(), getTopSkip()); + setInnerWidget(_tab->takeWidget(), topSkip()); setDimensions(st::boxWideWidth, st::boxMaxListHeight); session().data().stickers().updated(_isEmoji @@ -757,7 +803,7 @@ void StickersBox::paintEvent(QPaintEvent *e) { Painter p(this); if (_slideAnimation) { - _slideAnimation->paintFrame(p, 0, getTopSkip(), width()); + _slideAnimation->paintFrame(p, 0, topSkip(), width()); if (!_slideAnimation->animating()) { _slideAnimation.reset(); setInnerVisible(true); @@ -774,7 +820,7 @@ void StickersBox::updateTabsGeometry() { _tabs->resizeToWidth(_tabIndices.size() * width() / maxTabs); _unreadBadge->setVisible(_tabIndices.contains(Section::Featured)); - setInnerTopSkip(getTopSkip()); + setInnerTopSkip(topSkip()); auto featuredLeft = width() / maxTabs; auto featuredRight = 2 * width() / maxTabs; @@ -790,7 +836,7 @@ void StickersBox::updateTabsGeometry() { _tabs->moveToLeft(0, 0); } -int StickersBox::getTopSkip() const { +int StickersBox::topSkip() const { return _tabs ? (_tabs->height() - st::lineWidth) : 0; } @@ -819,8 +865,8 @@ void StickersBox::switchTab() { } if (_tab == &_installed) { - _localOrder = _tab->widget()->getFullOrder(); - _localRemoved = _tab->widget()->getRemovedSets(); + _localOrder = _tab->widget()->fullOrder(); + _localRemoved = _tab->widget()->removedSets(); } auto wasCache = grabContentCache(); auto wasIndex = _tab->index(); @@ -831,12 +877,12 @@ void StickersBox::switchTab() { _tab->returnWidget(std::move(widget)); _tab = newTab; _section = newSection; - setInnerWidget(_tab->takeWidget(), getTopSkip()); + setInnerWidget(_tab->takeWidget(), topSkip()); _tabs->raise(); _unreadBadge->raise(); _tab->widget()->show(); rebuildList(); - scrollToY(_tab->getScrollTop()); + scrollToY(_tab->scrollTop()); setInnerVisible(true); auto nowCache = grabContentCache(); auto nowIndex = _tab->index(); @@ -901,7 +947,7 @@ void StickersBox::installSet(uint64 setId) { } void StickersBox::installDone( - const MTPmessages_StickerSetInstallResult &result) { + const MTPmessages_StickerSetInstallResult &result) const { if (result.type() == mtpc_messages_stickerSetInstallResultArchive) { session().data().stickers().applyArchivedResult( result.c_messages_stickerSetInstallResultArchive()); @@ -998,12 +1044,12 @@ void StickersBox::rebuildList(Tab *tab) { tab = _tab; } - if ((tab == &_installed) || (tab == &_masks)) { - _localOrder = tab->widget()->getFullOrder(); - _localRemoved = tab->widget()->getRemovedSets(); + if ((tab == &_installed) || (tab == &_masks) || (_tab == &_featured)) { + _localOrder = tab->widget()->fullOrder(); + _localRemoved = tab->widget()->removedSets(); } tab->widget()->rebuild(_isMasks); - if ((tab == &_installed) || (tab == &_masks)) { + if ((tab == &_installed) || (tab == &_masks) || (_tab == &_featured)) { tab->widget()->setFullOrder(_localOrder); } tab->widget()->setRemovedSets(_localRemoved); @@ -1030,14 +1076,14 @@ void StickersBox::saveChanges() { } if (installed) { session().api().saveStickerSets( - installed->getOrder(), - installed->getRemovedSets(), + installed->order(), + installed->removedSets(), Data::StickersType::Stickers); } if (masks) { session().api().saveStickerSets( - masks->getOrder(), - masks->getRemovedSets(), + masks->order(), + masks->removedSets(), Data::StickersType::Masks); } } @@ -1056,7 +1102,7 @@ const Data::StickersSetsOrder &StickersBox::archivedSetsOrder() const { : session().data().stickers().archivedMaskSetsOrder(); } -Data::StickersSetsOrder &StickersBox::archivedSetsOrderRef() { +Data::StickersSetsOrder &StickersBox::archivedSetsOrderRef() const { return !_isMasks ? session().data().stickers().archivedSetsOrderRef() : session().data().stickers().archivedMaskSetsOrderRef(); @@ -1594,6 +1640,12 @@ void StickersBox::Inner::paintFakeButton(Painter &p, not_null row, int ind const auto textWidth = _installedWidth; const auto &text = _installedText; _inactiveButtonBg.paint(p, myrtlrect(rect)); + if (row->ripple) { + row->ripple->paint(p, rect.x(), rect.y(), width()); + if (row->ripple->empty()) { + row->ripple.reset(); + } + } p.setFont(st.font); p.setPen(st.textFg); p.drawTextLeft(rect.x() - (st.width / 2), rect.y() + st.textTop, width(), text, textWidth); @@ -1673,16 +1725,27 @@ void StickersBox::Inner::setActionDown(int newActionDown) { if (row->removed) { auto rippleSize = QSize(_undoWidth - st::stickersUndoRemove.width, st::stickersUndoRemove.height); auto rippleMask = Ui::RippleAnimation::RoundRectMask(rippleSize, st::roundRadiusLarge); - ensureRipple(st::stickersUndoRemove.ripple, std::move(rippleMask), removeButton); + ensureRipple(st::stickersUndoRemove.ripple, std::move(rippleMask), removeButton, false); } else { auto rippleSize = st::stickersRemove.rippleAreaSize; auto rippleMask = Ui::RippleAnimation::EllipseMask(QSize(rippleSize, rippleSize)); - ensureRipple(st::stickersRemove.ripple, std::move(rippleMask), removeButton); + ensureRipple(st::stickersRemove.ripple, std::move(rippleMask), removeButton, false); } - } else if (!row->isInstalled() || row->isArchived() || row->removed) { - auto rippleSize = QSize(_addWidth - st::stickersTrendingAdd.width, st::stickersTrendingAdd.height); - auto rippleMask = Ui::RippleAnimation::RoundRectMask(rippleSize, st::roundRadiusLarge); - ensureRipple(st::stickersTrendingAdd.ripple, std::move(rippleMask), removeButton); + } else { + const auto installedSet = row->isInstalled() + && !row->isArchived() + && !row->removed; + const auto &st = installedSet + ? st::stickersTrendingInstalled + : st::stickersTrendingAdd; + auto rippleMask = Ui::RippleAnimation::RoundRectMask( + QSize(_addWidth - st.width, st.height), + st::roundRadiusLarge); + ensureRipple( + st.ripple, + std::move(rippleMask), + removeButton, + installedSet); } } if (row->ripple) { @@ -1747,9 +1810,14 @@ void StickersBox::Inner::setPressed(SelectedRow pressed) { } } -void StickersBox::Inner::ensureRipple(const style::RippleAnimation &st, QImage mask, bool removeButton) { - _rows[_actionDown]->ripple = std::make_unique(st, std::move(mask), [this, index = _actionDown, removeButton] { - update(myrtlrect(relativeButtonRect(removeButton, false).translated(0, _itemsTop + index * _rowHeight))); +void StickersBox::Inner::ensureRipple( + const style::RippleAnimation &st, + QImage mask, + bool removeButton, + bool installedSet) { + const auto dy = _itemsTop + _actionDown * _rowHeight; + _rows[_actionDown]->ripple = std::make_unique(st, std::move(mask), [=] { + update(myrtlrect(relativeButtonRect(removeButton, installedSet).translated(0, dy))); }); } @@ -1812,7 +1880,7 @@ void StickersBox::Inner::updateSelected() { selected = selectedIndex; local.setY(local.y() - _itemsTop - selectedIndex * _rowHeight); const auto row = _rows[selectedIndex].get(); - if (!_megagroupSet && (_isInstalledTab || !row->isInstalled() || row->isArchived() || row->removed)) { + if (!_megagroupSet && (_isInstalledTab || (_section == Section::Featured) || !row->isInstalled() || row->isArchived() || row->removed)) { auto removeButton = (_isInstalledTab && !row->removed); auto rect = myrtlrect(relativeButtonRect(removeButton, false)); actionSel = rect.contains(local) ? selectedIndex : -1; @@ -1857,7 +1925,7 @@ float64 StickersBox::Inner::aboveShadowOpacity() const { auto dx = 0; auto dy = qAbs(_above * _rowHeight + qRound(_rows[_above]->yadd.current()) - _started * _rowHeight); - return qMin((dx + dy) * 2. / _rowHeight, 1.); + return qMin((dx + dy) * 2. / _rowHeight, 1.); } void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) { @@ -1868,10 +1936,11 @@ void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) { _mouse = e->globalPos(); updateSelected(); if (_actionDown == _actionSel && _actionSel >= 0) { - if (_isInstalledTab) { - setRowRemoved(_actionDown, !_rows[_actionDown]->removed); - } else if (_installSetCallback) { - _installSetCallback(_rows[_actionDown]->set->id); + const auto callback = _rows[_actionDown]->removed + ? _installSetCallback + : _removeSetCallback; + if (callback) { + callback(_rows[_actionDown]->set->id); } } else if (_dragging >= 0) { _rows[_dragging]->yadd.start(0.); @@ -1921,6 +1990,13 @@ void StickersBox::Inner::saveGroupSet() { } } +void StickersBox::Inner::setRowRemovedBySetId(uint64 setId, bool removed) { + const auto index = getRowIndex(setId); + if (index >= 0) { + setRowRemoved(index, removed); + } +} + void StickersBox::Inner::setRowRemoved(int index, bool removed) { auto &row = _rows[index]; if (row->removed != removed) { @@ -2114,7 +2190,7 @@ void StickersBox::Inner::rebuildMegagroupSet() { auto removed = false; auto maxNameWidth = countMaxNameWidth(!_isInstalledTab); auto titleWidth = 0; - auto title = fillSetTitle(set, maxNameWidth, &titleWidth); + auto title = FillSetTitle(set, maxNameWidth, &titleWidth); if (!_megagroupSelectedSet || _megagroupSelectedSet->set->id != set->id) { _megagroupSetField->setText(set->shortName); @@ -2217,11 +2293,6 @@ void StickersBox::Inner::setMegagroupSelectedSet( updateSelected(); } -void StickersBox::Inner::setMinHeight(int newWidth, int minHeight) { - _minHeight = minHeight; - updateSize(newWidth); -} - void StickersBox::Inner::updateSize(int newWidth) { auto naturalHeight = _itemsTop + int(_rows.size()) * _rowHeight + st::membersMarginBottom; resize(newWidth ? newWidth : width(), qMax(_minHeight, naturalHeight)); @@ -2269,7 +2340,7 @@ void StickersBox::Inner::updateRows() { && row->isInstalled() && !row->isArchived() && !row->removed); - row->title = fillSetTitle( + row->title = FillSetTitle( set, installedSet ? maxNameWidthInstalled : maxNameWidth, &row->titleWidth); @@ -2331,7 +2402,7 @@ void StickersBox::Inner::rebuildAppendSet(not_null set) { && !(flagsOverride & SetFlag::Archived) && !removed); int titleWidth = 0; - QString title = fillSetTitle(set, maxNameWidth, &titleWidth); + QString title = FillSetTitle(set, maxNameWidth, &titleWidth); int count = fillSetCount(set); const auto existing = [&]{ @@ -2458,22 +2529,6 @@ int StickersBox::Inner::fillSetCount(not_null set) const { return result + added; } -QString StickersBox::Inner::fillSetTitle( - not_null set, - int maxNameWidth, - int *outTitleWidth) const { - auto result = set->title; - int titleWidth = st::contactsNameStyle.font->width(result); - if (titleWidth > maxNameWidth) { - result = st::contactsNameStyle.font->elided(result, maxNameWidth); - titleWidth = st::contactsNameStyle.font->width(result); - } - if (outTitleWidth) { - *outTitleWidth = titleWidth; - } - return result; -} - Data::StickersSetFlags StickersBox::Inner::fillSetFlags( not_null set) const { const auto result = set->flags; @@ -2494,19 +2549,19 @@ StickersSetsOrder StickersBox::Inner::collectSets(Check check) const { return result; } -StickersSetsOrder StickersBox::Inner::getOrder() const { +StickersSetsOrder StickersBox::Inner::order() const { return collectSets([](Row *row) { return !row->isArchived() && !row->removed && !row->isRecentSet(); }); } -StickersSetsOrder StickersBox::Inner::getFullOrder() const { +StickersSetsOrder StickersBox::Inner::fullOrder() const { return collectSets([](Row *row) { return !row->isRecentSet(); }); } -StickersSetsOrder StickersBox::Inner::getRemovedSets() const { +StickersSetsOrder StickersBox::Inner::removedSets() const { return collectSets([](Row *row) { return row->removed; }); diff --git a/Telegram/SourceFiles/boxes/stickers_box.h b/Telegram/SourceFiles/boxes/stickers_box.h index f0e54374a..e374ba0b4 100644 --- a/Telegram/SourceFiles/boxes/stickers_box.h +++ b/Telegram/SourceFiles/boxes/stickers_box.h @@ -104,7 +104,7 @@ private: [[nodiscard]] int index() const; void saveScrollTop(); - int getScrollTop() const { + int scrollTop() const { return _scrollTop; } @@ -122,12 +122,12 @@ private: void updateTabsGeometry(); void switchTab(); void installSet(uint64 setId); - int getTopSkip() const; + int topSkip() const; void saveChanges(); QPixmap grabContentCache(); - void installDone(const MTPmessages_StickerSetInstallResult &result); + void installDone(const MTPmessages_StickerSetInstallResult &result) const; void installFail(const MTP::Error &error, uint64 setId); void preloadArchivedSets(); @@ -139,7 +139,7 @@ private: void showAttachedStickers(); const Data::StickersSetsOrder &archivedSetsOrder() const; - Data::StickersSetsOrder &archivedSetsOrderRef(); + Data::StickersSetsOrder &archivedSetsOrderRef() const; std::array widgets() const; diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index fb85201eb..20b1d6287 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -294,8 +294,11 @@ stickersTrendingAdd: RoundButton(defaultActiveButton) { stickersTrendingInstalled: RoundButton(stickersTrendingAdd) { textFg: activeButtonBg; textFgOver: activeButtonBgOver; - textBg: activeButtonSecondaryFg; - textBgOver: activeButtonSecondaryFgOver; + textBg: lightButtonBgOver; + textBgOver: lightButtonBgOver; + ripple: RippleAnimation(defaultRippleAnimation) { + color: activeButtonSecondaryFg; + } } stickersRemove: IconButton(defaultIconButton) { width: 40px; diff --git a/Telegram/SourceFiles/core/utils.h b/Telegram/SourceFiles/core/utils.h index c8ab4422d..325f6ccc4 100644 --- a/Telegram/SourceFiles/core/utils.h +++ b/Telegram/SourceFiles/core/utils.h @@ -45,12 +45,9 @@ inline QString IconName() { inline bool CanReadDirectory(const QString &path) { #ifndef Q_OS_MAC // directory_iterator since 10.15 - try { - std::filesystem::directory_iterator(path.toStdString()); - return true; - } catch (...) { - return false; - } + std::error_code error; + std::filesystem::directory_iterator(path.toStdString(), error); + return !error; #else Unexpected("Not implemented."); #endif diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index e6f40049b..fec69bc64 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -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 = 4009003; -constexpr auto AppVersionStr = "4.9.3"; +constexpr auto AppVersion = 4009004; +constexpr auto AppVersionStr = "4.9.4"; constexpr auto AppBetaVersion = false; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/SourceFiles/data/notify/data_notify_settings.cpp b/Telegram/SourceFiles/data/notify/data_notify_settings.cpp index 1675d1e92..c522b8aa5 100644 --- a/Telegram/SourceFiles/data/notify/data_notify_settings.cpp +++ b/Telegram/SourceFiles/data/notify/data_notify_settings.cpp @@ -42,11 +42,39 @@ constexpr auto kMaxNotifyCheckDelay = 24 * 3600 * crl::time(1000); return (result > 0); } +[[nodiscard]] bool SkipAddException(not_null peer) { + if (const auto user = peer->asUser()) { + return user->isInaccessible(); + } else if (const auto chat = peer->asChat()) { + return chat->isDeactivated() || chat->isForbidden(); + } else if (const auto channel = peer->asChannel()) { + return channel->isForbidden(); + } + return false; +} + } // namespace +DefaultNotify DefaultNotifyType(not_null peer) { + return peer->isUser() + ? DefaultNotify::User + : (peer->isChat() || peer->isMegagroup()) + ? DefaultNotify::Group + : DefaultNotify::Broadcast; +} + +MTPInputNotifyPeer DefaultNotifyToMTP(DefaultNotify type) { + switch (type) { + case DefaultNotify::User: return MTP_inputNotifyUsers(); + case DefaultNotify::Group: return MTP_inputNotifyChats(); + case DefaultNotify::Broadcast: return MTP_inputNotifyBroadcasts(); + } + Unexpected("Default notify type in sendNotifySettingsUpdates"); +} + NotifySettings::NotifySettings(not_null owner) - : _owner(owner) - , _unmuteByFinishedTimer([=] { unmuteByFinished(); }) { +: _owner(owner) +, _unmuteByFinishedTimer([=] { unmuteByFinished(); }) { } void NotifySettings::request(not_null peer) { @@ -63,7 +91,7 @@ void NotifySettings::request(not_null peer) { } } -void NotifySettings::request(not_null thread) { +void NotifySettings::request(not_null thread) { if (const auto topic = thread->asTopic()) { if (topic->notify().settingsUnknown()) { topic->session().api().requestNotifySettings( @@ -145,6 +173,7 @@ void NotifySettings::apply( not_null peer, const MTPPeerNotifySettings &settings) { if (peer->notify().change(settings)) { + updateException(peer); updateLocal(peer); Core::App().notifications().checkDelayed(); } @@ -162,7 +191,7 @@ void NotifySettings::apply( } void NotifySettings::apply( - not_null topic, + not_null topic, const MTPPeerNotifySettings &settings) { if (topic->notify().change(settings)) { updateLocal(topic); @@ -171,8 +200,8 @@ void NotifySettings::apply( } void NotifySettings::update( - not_null thread, - Data::MuteValue muteForSeconds, + not_null thread, + MuteValue muteForSeconds, std::optional silentPosts, std::optional sound, std::optional storiesMuted) { @@ -181,34 +210,29 @@ void NotifySettings::update( silentPosts, sound, storiesMuted)) { + if (const auto history = thread->asHistory()) { + updateException(history->peer); + } updateLocal(thread); thread->session().api().updateNotifySettingsDelayed(thread); } } -void NotifySettings::resetToDefault(not_null thread) { - const auto empty = MTP_peerNotifySettings( - MTP_flags(0), - MTPBool(), - MTPBool(), - MTPint(), - MTPNotificationSound(), - MTPNotificationSound(), - MTPNotificationSound(), - MTPBool(), - MTPBool(), - MTPNotificationSound(), - MTPNotificationSound(), - MTPNotificationSound()); - if (thread->notify().change(empty)) { +void NotifySettings::resetToDefault(not_null thread) { + // Duplicated in clearExceptions(type) and resetToDefault(peer). + if (thread->notify().resetToDefault()) { + if (const auto history = thread->asHistory()) { + updateException(history->peer); + } updateLocal(thread); thread->session().api().updateNotifySettingsDelayed(thread); + Core::App().notifications().checkDelayed(); } } void NotifySettings::update( not_null peer, - Data::MuteValue muteForSeconds, + MuteValue muteForSeconds, std::optional silentPosts, std::optional sound, std::optional storiesMuted) { @@ -217,33 +241,24 @@ void NotifySettings::update( silentPosts, sound, storiesMuted)) { + updateException(peer); updateLocal(peer); peer->session().api().updateNotifySettingsDelayed(peer); } } void NotifySettings::resetToDefault(not_null peer) { - const auto empty = MTP_peerNotifySettings( - MTP_flags(0), - MTPBool(), - MTPBool(), - MTPint(), - MTPNotificationSound(), - MTPNotificationSound(), - MTPNotificationSound(), - MTPBool(), - MTPBool(), - MTPNotificationSound(), - MTPNotificationSound(), - MTPNotificationSound()); - if (peer->notify().change(empty)) { + // Duplicated in clearExceptions(type) and resetToDefault(thread). + if (peer->notify().resetToDefault()) { + updateException(peer); updateLocal(peer); peer->session().api().updateNotifySettingsDelayed(peer); + Core::App().notifications().checkDelayed(); } } -void NotifySettings::forumParentMuteUpdated(not_null forum) { - forum->enumerateTopics([&](not_null topic) { +void NotifySettings::forumParentMuteUpdated(not_null forum) { + forum->enumerateTopics([&](not_null topic) { if (!topic->notify().settingsUnknown()) { updateLocal(topic); } @@ -266,11 +281,7 @@ auto NotifySettings::defaultValue(DefaultNotify type) const const PeerNotifySettings &NotifySettings::defaultSettings( not_null peer) const { - return defaultSettings(peer->isUser() - ? DefaultNotify::User - : (peer->isChat() || peer->isMegagroup()) - ? DefaultNotify::Group - : DefaultNotify::Broadcast); + return defaultSettings(DefaultNotifyType(peer)); } const PeerNotifySettings &NotifySettings::defaultSettings( @@ -278,9 +289,16 @@ const PeerNotifySettings &NotifySettings::defaultSettings( return defaultValue(type).settings; } +bool NotifySettings::isMuted(DefaultNotify type) const { + if (const auto until = defaultSettings(type).muteUntil()) { + return MutedFromUntil(*until, nullptr); + } + return true; +} + void NotifySettings::defaultUpdate( DefaultNotify type, - Data::MuteValue muteForSeconds, + MuteValue muteForSeconds, std::optional silentPosts, std::optional sound, std::optional storiesMuted) { @@ -291,7 +309,7 @@ void NotifySettings::defaultUpdate( } } -void NotifySettings::updateLocal(not_null thread) { +void NotifySettings::updateLocal(not_null thread) { const auto topic = thread->asTopic(); if (!topic) { return updateLocal(thread->peer()); @@ -351,7 +369,7 @@ void NotifySettings::cacheSound(not_null document) { const auto view = document->createMediaView(); _ringtones.views.emplace(document->id, view); document->forceToCache(true); - document->save(Data::FileOriginRingtones(), QString()); + document->save(FileOriginRingtones(), QString()); } void NotifySettings::cacheSound(const std::optional &sound) { @@ -459,7 +477,7 @@ void NotifySettings::unmuteByFinished() { } bool NotifySettings::isMuted( - not_null thread, + not_null thread, crl::time *changesIn) const { const auto topic = thread->asTopic(); const auto until = topic ? topic->notify().muteUntil() : std::nullopt; @@ -468,27 +486,24 @@ bool NotifySettings::isMuted( : isMuted(thread->peer(), changesIn); } -bool NotifySettings::isMuted(not_null thread) const { +bool NotifySettings::isMuted(not_null thread) const { return isMuted(thread, nullptr); } -NotifySound NotifySettings::sound( - not_null thread) const { +NotifySound NotifySettings::sound(not_null thread) const { const auto topic = thread->asTopic(); const auto sound = topic ? topic->notify().sound() : std::nullopt; return sound ? *sound : this->sound(thread->peer()); } -bool NotifySettings::muteUnknown( - not_null thread) const { +bool NotifySettings::muteUnknown(not_null thread) const { const auto topic = thread->asTopic(); return (topic && topic->notify().settingsUnknown()) || ((!topic || !topic->notify().muteUntil().has_value()) && muteUnknown(thread->peer())); } -bool NotifySettings::soundUnknown( - not_null thread) const { +bool NotifySettings::soundUnknown(not_null thread) const { const auto topic = thread->asTopic(); return (topic && topic->notify().settingsUnknown()) || ((!topic || !topic->notify().sound().has_value()) @@ -543,8 +558,7 @@ bool NotifySettings::silentPostsUnknown( && defaultSettings(peer).settingsUnknown()); } -bool NotifySettings::soundUnknown( - not_null peer) const { +bool NotifySettings::soundUnknown(not_null peer) const { return peer->notify().settingsUnknown() || (!peer->notify().sound().has_value() && defaultSettings(peer).settingsUnknown()); @@ -556,8 +570,7 @@ bool NotifySettings::settingsUnknown(not_null peer) const { || soundUnknown(peer); } -bool NotifySettings::settingsUnknown( - not_null thread) const { +bool NotifySettings::settingsUnknown(not_null thread) const { const auto topic = thread->asTopic(); return muteUnknown(thread) || soundUnknown(thread) @@ -577,4 +590,85 @@ rpl::producer<> NotifySettings::defaultUpdates( : DefaultNotify::Broadcast); } +void NotifySettings::loadExceptions() { + for (auto i = 0; i != kDefaultNotifyTypes; ++i) { + if (_exceptionsRequestId[i]) { + continue; + } + const auto type = static_cast(i); + const auto api = &_owner->session().api(); + const auto requestId = api->request(MTPaccount_GetNotifyExceptions( + MTP_flags(MTPaccount_GetNotifyExceptions::Flag::f_peer), + DefaultNotifyToMTP(type) + )).done([=](const MTPUpdates &result) { + api->applyUpdates(result); + }).send(); + _exceptionsRequestId[i] = requestId; + } +} + +void NotifySettings::updateException(not_null peer) { + const auto type = DefaultNotifyType(peer); + const auto index = static_cast(type); + const auto exception = peer->notify().muteUntil().has_value(); + if (!exception) { + if (_exceptions[index].remove(peer)) { + exceptionsUpdated(type); + } + } else if (SkipAddException(peer)) { + return; + } else if (_exceptions[index].emplace(peer).second) { + exceptionsUpdated(type); + } +} + +void NotifySettings::exceptionsUpdated(DefaultNotify type) { + if (!ranges::contains(_exceptionsUpdatesScheduled, true)) { + crl::on_main(&_owner->session(), [=] { + const auto scheduled = base::take(_exceptionsUpdatesScheduled); + for (auto i = 0; i != kDefaultNotifyTypes; ++i) { + if (scheduled[i]) { + _exceptionsUpdates.fire(static_cast(i)); + } + } + }); + } + _exceptionsUpdatesScheduled[static_cast(type)] = true; + _exceptionsUpdatesRealtime.fire_copy(type); +} + +rpl::producer NotifySettings::exceptionsUpdates() const { + return _exceptionsUpdates.events(); +} + +auto NotifySettings::exceptionsUpdatesRealtime() const +-> rpl::producer { + return _exceptionsUpdatesRealtime.events(); +} + +const base::flat_set> &NotifySettings::exceptions( + DefaultNotify type) const { + const auto index = static_cast(type); + Assert(index >= 0 && index < kDefaultNotifyTypes); + + return _exceptions[index]; +} + +void NotifySettings::clearExceptions(DefaultNotify type) { + const auto index = static_cast(type); + const auto list = base::take(_exceptions[index]); + if (list.empty()) { + return; + } + for (const auto &peer : list) { + // Duplicated in resetToDefault(peer / thread). + if (peer->notify().resetToDefault()) { + updateLocal(peer); + peer->session().api().updateNotifySettingsDelayed(peer); + } + } + Core::App().notifications().checkDelayed(); + exceptionsUpdated(type); +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/notify/data_notify_settings.h b/Telegram/SourceFiles/data/notify/data_notify_settings.h index 6e51b87ac..b1930f45b 100644 --- a/Telegram/SourceFiles/data/notify/data_notify_settings.h +++ b/Telegram/SourceFiles/data/notify/data_notify_settings.h @@ -26,13 +26,17 @@ enum class DefaultNotify { Group, Broadcast, }; +[[nodiscard]] DefaultNotify DefaultNotifyType( + not_null peer); + +[[nodiscard]] MTPInputNotifyPeer DefaultNotifyToMTP(DefaultNotify type); class NotifySettings final { public: NotifySettings(not_null owner); void request(not_null peer); - void request(not_null thread); + void request(not_null thread); void apply( const MTPNotifyPeer ¬ifyPeer, @@ -50,25 +54,25 @@ public: MsgId topicRootId, const MTPPeerNotifySettings &settings); void apply( - not_null topic, + not_null topic, const MTPPeerNotifySettings &settings); void update( - not_null thread, - Data::MuteValue muteForSeconds, + not_null thread, + MuteValue muteForSeconds, std::optional silentPosts = std::nullopt, std::optional sound = std::nullopt, std::optional storiesMuted = std::nullopt); - void resetToDefault(not_null thread); + void resetToDefault(not_null thread); void update( not_null peer, - Data::MuteValue muteForSeconds, + MuteValue muteForSeconds, std::optional silentPosts = std::nullopt, std::optional sound = std::nullopt, std::optional storiesMuted = std::nullopt); void resetToDefault(not_null peer); - void forumParentMuteUpdated(not_null forum); + void forumParentMuteUpdated(not_null forum); void cacheSound(DocumentId id); void cacheSound(not_null document); @@ -81,21 +85,19 @@ public: [[nodiscard]] const PeerNotifySettings &defaultSettings( DefaultNotify type) const; + [[nodiscard]] bool isMuted(DefaultNotify type) const; void defaultUpdate( DefaultNotify type, - Data::MuteValue muteForSeconds, + MuteValue muteForSeconds, std::optional silentPosts = std::nullopt, std::optional sound = std::nullopt, std::optional storiesMuted = std::nullopt); - [[nodiscard]] bool isMuted(not_null thread) const; - [[nodiscard]] NotifySound sound( - not_null thread) const; - [[nodiscard]] bool muteUnknown( - not_null thread) const; - [[nodiscard]] bool soundUnknown( - not_null thread) const; + [[nodiscard]] bool isMuted(not_null thread) const; + [[nodiscard]] NotifySound sound(not_null thread) const; + [[nodiscard]] bool muteUnknown(not_null thread) const; + [[nodiscard]] bool soundUnknown(not_null thread) const; [[nodiscard]] bool isMuted(not_null peer) const; [[nodiscard]] bool silentPosts(not_null peer) const; @@ -105,7 +107,17 @@ public: not_null peer) const; [[nodiscard]] bool soundUnknown(not_null peer) const; + void loadExceptions(); + [[nodiscard]] rpl::producer exceptionsUpdates() const; + [[nodiscard]] auto exceptionsUpdatesRealtime() const + -> rpl::producer; + [[nodiscard]] const base::flat_set> &exceptions( + DefaultNotify type) const; + void clearExceptions(DefaultNotify type); + private: + static constexpr auto kDefaultNotifyTypes = 3; + struct DefaultValue { PeerNotifySettings settings; rpl::event_stream<> updates; @@ -114,7 +126,7 @@ private: void cacheSound(const std::optional &sound); [[nodiscard]] bool isMuted( - not_null thread, + not_null thread, crl::time *changesIn) const; [[nodiscard]] bool isMuted( not_null peer, @@ -126,21 +138,22 @@ private: not_null peer) const; [[nodiscard]] bool settingsUnknown(not_null peer) const; [[nodiscard]] bool settingsUnknown( - not_null thread) const; + not_null thread) const; void unmuteByFinished(); void unmuteByFinishedDelayed(crl::time delay); - void updateLocal(not_null thread); + void updateLocal(not_null thread); void updateLocal(not_null peer); void updateLocal(DefaultNotify type); + void updateException(not_null peer); + void exceptionsUpdated(DefaultNotify type); + const not_null _owner; DefaultValue _defaultValues[3]; std::unordered_set> _mutedPeers; - std::unordered_map< - not_null, - rpl::lifetime> _mutedTopics; + std::unordered_map, rpl::lifetime> _mutedTopics; base::Timer _unmuteByFinishedTimer; struct { @@ -151,6 +164,14 @@ private: rpl::lifetime pendingLifetime; } _ringtones; + rpl::event_stream _exceptionsUpdates; + rpl::event_stream _exceptionsUpdatesRealtime; + std::array< + base::flat_set>, + kDefaultNotifyTypes> _exceptions; + std::array _exceptionsRequestId = {}; + std::array _exceptionsUpdatesScheduled = {}; + }; } // namespace Data diff --git a/Telegram/SourceFiles/data/notify/data_peer_notify_settings.cpp b/Telegram/SourceFiles/data/notify/data_peer_notify_settings.cpp index 4b756a611..48d198d6e 100644 --- a/Telegram/SourceFiles/data/notify/data_peer_notify_settings.cpp +++ b/Telegram/SourceFiles/data/notify/data_peer_notify_settings.cpp @@ -256,6 +256,15 @@ bool PeerNotifySettings::change( SerializeSound(std::nullopt))); // stories_sound } +bool PeerNotifySettings::resetToDefault() { + if (_known && !_value) { + return false; + } + _known = true; + _value = nullptr; + return true; +} + std::optional PeerNotifySettings::muteUntil() const { return _value ? _value->muteUntil() diff --git a/Telegram/SourceFiles/data/notify/data_peer_notify_settings.h b/Telegram/SourceFiles/data/notify/data_peer_notify_settings.h index 76a8ecfd9..b5de9e119 100644 --- a/Telegram/SourceFiles/data/notify/data_peer_notify_settings.h +++ b/Telegram/SourceFiles/data/notify/data_peer_notify_settings.h @@ -46,6 +46,7 @@ public: std::optional silentPosts, std::optional sound, std::optional storiesMuted); + bool resetToDefault(); bool settingsUnknown() const; std::optional muteUntil() const; diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index 622016bb8..854faef97 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -23,6 +23,12 @@ DialogRow { unreadMarkDiameter: pixels; } +ThreeStateIcon { + icon: icon; + over: icon; + active: icon; +} + ForumTopicIcon { size: pixels; font: font; @@ -316,38 +322,56 @@ dialogSearchFrom: IconButton(dialogCalendar) { } dialogsChatTypeSkip: 3px; -dialogsChatIcon: icon {{ "dialogs/dialogs_chat", dialogsChatIconFg, point(1px, 4px) }}; -dialogsChatIconOver: icon {{ "dialogs/dialogs_chat", dialogsChatIconFgOver, point(1px, 4px) }}; -dialogsChatIconActive: icon {{ "dialogs/dialogs_chat", dialogsChatIconFgActive, point(1px, 4px) }}; -dialogsChannelIcon: icon {{ "dialogs/dialogs_channel", dialogsChatIconFg, point(3px, 4px) }}; -dialogsChannelIconOver: icon {{ "dialogs/dialogs_channel", dialogsChatIconFgOver, point(3px, 4px) }}; -dialogsChannelIconActive: icon {{ "dialogs/dialogs_channel", dialogsChatIconFgActive, point(3px, 4px) }}; -dialogsBotIcon: icon {{ "dialogs/dialogs_bot", dialogsChatIconFg, point(1px, 3px) }}; -dialogsBotIconOver: icon {{ "dialogs/dialogs_bot", dialogsChatIconFgOver, point(1px, 3px) }}; -dialogsBotIconActive: icon {{ "dialogs/dialogs_bot", dialogsChatIconFgActive, point(1px, 3px) }}; -dialogsForumIcon: icon {{ "dialogs/dialogs_forum", dialogsChatIconFg, point(1px, 4px) }}; -dialogsForumIconOver: icon {{ "dialogs/dialogs_forum", dialogsChatIconFgOver, point(1px, 4px) }}; -dialogsForumIconActive: icon {{ "dialogs/dialogs_forum", dialogsChatIconFgActive, point(1px, 4px) }}; +dialogsChatIcon: ThreeStateIcon { + icon: icon {{ "dialogs/dialogs_chat", dialogsChatIconFg, point(1px, 4px) }}; + over: icon {{ "dialogs/dialogs_chat", dialogsChatIconFgOver, point(1px, 4px) }}; + active: icon {{ "dialogs/dialogs_chat", dialogsChatIconFgActive, point(1px, 4px) }}; +} +dialogsChannelIcon: ThreeStateIcon { + icon: icon {{ "dialogs/dialogs_channel", dialogsChatIconFg, point(3px, 4px) }}; + over: icon {{ "dialogs/dialogs_channel", dialogsChatIconFgOver, point(3px, 4px) }}; + active: icon {{ "dialogs/dialogs_channel", dialogsChatIconFgActive, point(3px, 4px) }}; +} +dialogsBotIcon: ThreeStateIcon { + icon: icon {{ "dialogs/dialogs_bot", dialogsChatIconFg, point(1px, 3px) }}; + over: icon {{ "dialogs/dialogs_bot", dialogsChatIconFgOver, point(1px, 3px) }}; + active: icon {{ "dialogs/dialogs_bot", dialogsChatIconFgActive, point(1px, 3px) }}; +} +dialogsForumIcon: ThreeStateIcon { + icon: icon {{ "dialogs/dialogs_forum", dialogsChatIconFg, point(1px, 4px) }}; + over: icon {{ "dialogs/dialogs_forum", dialogsChatIconFgOver, point(1px, 4px) }}; + active: icon {{ "dialogs/dialogs_forum", dialogsChatIconFgActive, point(1px, 4px) }}; +} dialogsArchiveUserpic: icon {{ "archive_userpic", historyPeerUserpicFg }}; dialogsRepliesUserpic: icon {{ "replies_userpic", historyPeerUserpicFg }}; dialogsInaccessibleUserpic: icon {{ "dialogs/inaccessible_userpic", historyPeerUserpicFg }}; dialogsSendStateSkip: 20px; -dialogsSendingIcon: icon {{ "dialogs/dialogs_sending", dialogsSendingIconFg, point(8px, 4px) }}; -dialogsSendingIconOver: icon {{ "dialogs/dialogs_sending", dialogsSendingIconFgOver, point(8px, 4px) }}; -dialogsSendingIconActive: icon {{ "dialogs/dialogs_sending", dialogsSendingIconFgActive, point(8px, 4px) }}; -dialogsSentIcon: icon {{ "dialogs/dialogs_sent", dialogsSentIconFg, point(10px, 4px) }}; -dialogsSentIconOver: icon {{ "dialogs/dialogs_sent", dialogsSentIconFgOver, point(10px, 4px) }}; -dialogsSentIconActive: icon {{ "dialogs/dialogs_sent", dialogsSentIconFgActive, point(10px, 4px) }}; -dialogsReceivedIcon: icon {{ "dialogs/dialogs_received", dialogsSentIconFg, point(5px, 4px) }}; -dialogsReceivedIconOver: icon {{ "dialogs/dialogs_received", dialogsSentIconFgOver, point(5px, 4px) }}; -dialogsReceivedIconActive: icon {{ "dialogs/dialogs_received", dialogsSentIconFgActive, point(5px, 4px) }}; -dialogsPinnedIcon: icon {{ "dialogs/dialogs_pinned", dialogsUnreadBgMuted }}; -dialogsPinnedIconOver: icon {{ "dialogs/dialogs_pinned", dialogsUnreadBgMutedOver }}; -dialogsPinnedIconActive: icon {{ "dialogs/dialogs_pinned", dialogsUnreadBgMutedActive }}; -dialogsLockIcon: icon {{ "emoji/premium_lock", dialogsUnreadBgMuted, point(4px, 0px) }}; -dialogsLockIconOver: icon {{ "emoji/premium_lock", dialogsUnreadBgMutedOver, point(4px, 0px) }}; -dialogsLockIconActive: icon {{ "emoji/premium_lock", dialogsUnreadBgMutedActive, point(4px, 0px) }}; +dialogsSendingIcon: ThreeStateIcon { + icon: icon {{ "dialogs/dialogs_sending", dialogsSendingIconFg, point(8px, 4px) }}; + over: icon {{ "dialogs/dialogs_sending", dialogsSendingIconFgOver, point(8px, 4px) }}; + active: icon {{ "dialogs/dialogs_sending", dialogsSendingIconFgActive, point(8px, 4px) }}; +} +dialogsSentIcon: ThreeStateIcon { + icon: icon {{ "dialogs/dialogs_sent", dialogsSentIconFg, point(10px, 4px) }}; + over: icon {{ "dialogs/dialogs_sent", dialogsSentIconFgOver, point(10px, 4px) }}; + active: icon {{ "dialogs/dialogs_sent", dialogsSentIconFgActive, point(10px, 4px) }}; +} +dialogsReceivedIcon: ThreeStateIcon { + icon: icon {{ "dialogs/dialogs_received", dialogsSentIconFg, point(5px, 4px) }}; + over: icon {{ "dialogs/dialogs_received", dialogsSentIconFgOver, point(5px, 4px) }}; + active: icon {{ "dialogs/dialogs_received", dialogsSentIconFgActive, point(5px, 4px) }}; +} +dialogsPinnedIcon: ThreeStateIcon { + icon: icon {{ "dialogs/dialogs_pinned", dialogsUnreadBgMuted }}; + over: icon {{ "dialogs/dialogs_pinned", dialogsUnreadBgMutedOver }}; + active: icon {{ "dialogs/dialogs_pinned", dialogsUnreadBgMutedActive }}; +} +dialogsLockIcon: ThreeStateIcon { + icon: icon {{ "emoji/premium_lock", dialogsUnreadBgMuted, point(4px, 0px) }}; + over: icon {{ "emoji/premium_lock", dialogsUnreadBgMutedOver, point(4px, 0px) }}; + active: icon {{ "emoji/premium_lock", dialogsUnreadBgMutedActive, point(4px, 0px) }}; +} dialogsVerifiedIcon: icon { { "dialogs/dialogs_verified_star", dialogsVerifiedIconBg }, @@ -361,9 +385,11 @@ dialogsVerifiedIconActive: icon { { "dialogs/dialogs_verified_star", dialogsVerifiedIconBgActive }, { "dialogs/dialogs_verified_check", dialogsVerifiedIconFgActive }, }; -dialogsPremiumIcon: icon {{ "dialogs/dialogs_premium", dialogsVerifiedIconBg }}; -dialogsPremiumIconOver: icon {{ "dialogs/dialogs_premium", dialogsVerifiedIconBgOver }}; -dialogsPremiumIconActive: icon {{ "dialogs/dialogs_premium", dialogsVerifiedIconBgActive }}; +dialogsPremiumIcon: ThreeStateIcon { + icon: icon {{ "dialogs/dialogs_premium", dialogsVerifiedIconBg }}; + over: icon {{ "dialogs/dialogs_premium", dialogsVerifiedIconBgOver }}; + active: icon {{ "dialogs/dialogs_premium", dialogsVerifiedIconBgActive }}; +} historySendingIcon: icon {{ "dialogs/dialogs_sending", historySendingOutIconFg, point(5px, 5px) }}; historySendingInvertedIcon: icon {{ "dialogs/dialogs_sending", historySendingInvertedIconFg, point(5px, 5px) }}; @@ -436,12 +462,28 @@ dialogsMiniPreviewSkip: 2px; dialogsMiniPreviewRight: 3px; dialogsMiniPlay: icon{{ "dialogs/dialogs_mini_play", videoPlayIconFg }}; -dialogsUnreadMention: icon{{ "dialogs/dialogs_mention", dialogsUnreadFg }}; -dialogsUnreadMentionOver: icon{{ "dialogs/dialogs_mention", dialogsUnreadFgOver }}; -dialogsUnreadMentionActive: icon{{ "dialogs/dialogs_mention", dialogsUnreadFgActive }}; -dialogsUnreadReaction: icon{{ "dialogs/dialogs_reaction", dialogsUnreadFg }}; -dialogsUnreadReactionOver: icon{{ "dialogs/dialogs_reaction", dialogsUnreadFgOver }}; -dialogsUnreadReactionActive: icon{{ "dialogs/dialogs_reaction", dialogsUnreadFgActive }}; +dialogsMiniForwardIcon: ThreeStateIcon { + icon: icon {{ "mini_forward", dialogsTextFg, point(0px, 1px) }}; + over: icon {{ "mini_forward", dialogsTextFgOver, point(0px, 1px) }}; + active: icon {{ "mini_forward", dialogsTextFgActive, point(0px, 1px) }}; +} +dialogsMiniIconSkip: 2px; +dialogsMiniReplyStoryIcon: ThreeStateIcon { + icon: icon {{ "mini_reply_story", dialogsTextFg, point(0px, 1px) }}; + over: icon {{ "mini_reply_story", dialogsTextFgOver, point(0px, 1px) }}; + active: icon {{ "mini_reply_story", dialogsTextFgActive, point(0px, 1px) }}; +} + +dialogsUnreadMention: ThreeStateIcon { + icon: icon{{ "dialogs/dialogs_mention", dialogsUnreadFg }}; + over: icon{{ "dialogs/dialogs_mention", dialogsUnreadFgOver }}; + active: icon{{ "dialogs/dialogs_mention", dialogsUnreadFgActive }}; +} +dialogsUnreadReaction: ThreeStateIcon { + icon: icon{{ "dialogs/dialogs_reaction", dialogsUnreadFg }}; + over: icon{{ "dialogs/dialogs_reaction", dialogsUnreadFgOver }}; + active: icon{{ "dialogs/dialogs_reaction", dialogsUnreadFgActive }}; +} downloadBarHeight: 46px; downloadArrow: icon{{ "fast_to_original", menuIconFg }}; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 697862166..eadcc89ee 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "dialogs/dialogs_inner_widget.h" +#include "dialogs/dialogs_three_state_icon.h" #include "dialogs/ui/dialogs_layout.h" #include "dialogs/ui/dialogs_stories_content.h" #include "dialogs/ui/dialogs_stories_list.h" @@ -1010,11 +1011,10 @@ void InnerWidget::paintPeerSearchResult( : context.selected ? &st::dialogsVerifiedIconOver : &st::dialogsVerifiedIcon), - .premium = (context.active - ? &st::dialogsPremiumIconActive - : context.selected - ? &st::dialogsPremiumIconOver - : &st::dialogsPremiumIcon), + .premium = &ThreeStateIcon( + st::dialogsPremiumIcon, + context.active, + context.selected), .scam = (context.active ? &st::dialogsScamFgActive : context.selected diff --git a/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp index b9c7b2c7b..9e643555a 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_main_list.cpp @@ -194,6 +194,11 @@ UnreadState MainList::unreadState() const { result.chatsMuted = result.chats; result.marksMuted = result.marks; } + volatile auto touch = _unreadState.marks + _unreadState.marksMuted + + _unreadState.messages + _unreadState.messagesMuted + + _unreadState.chats + _unreadState.chatsMuted + + _unreadState.reactions + _unreadState.reactionsMuted + + _unreadState.mentions; return result; } diff --git a/Telegram/SourceFiles/dialogs/dialogs_three_state_icon.h b/Telegram/SourceFiles/dialogs/dialogs_three_state_icon.h new file mode 100644 index 000000000..e150fc669 --- /dev/null +++ b/Telegram/SourceFiles/dialogs/dialogs_three_state_icon.h @@ -0,0 +1,21 @@ +/* +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 "styles/style_dialogs.h" + +namespace Dialogs { + +[[nodiscard]] inline const style::icon &ThreeStateIcon( + const style::ThreeStateIcon &icons, + bool active, + bool over) { + return active ? icons.active : over ? icons.over : icons.icon; +} + +} // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp index 91674bfed..3ef60a4fe 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_forum_topic.h" #include "data/data_session.h" #include "dialogs/dialogs_list.h" +#include "dialogs/dialogs_three_state_icon.h" #include "dialogs/ui/dialogs_video_userpic.h" #include "styles/style_dialogs.h" #include "styles/style_window.h" @@ -147,11 +148,10 @@ int PaintBadges( const auto badge = PaintUnreadBadge(p, counter, right, top, st); right -= badge.width() + st.padding; } else if (displayPinnedIcon) { - const auto &icon = context.active - ? st::dialogsPinnedIconActive - : context.selected - ? st::dialogsPinnedIconOver - : st::dialogsPinnedIcon; + const auto &icon = ThreeStateIcon( + st::dialogsPinnedIcon, + context.active, + context.selected); icon.paint(p, right - icon.width(), pinnedIconTop, context.width); right -= icon.width() + st::dialogsUnreadPadding; } @@ -169,17 +169,12 @@ int PaintBadges( st.textTop = 0; const auto counter = QString(); const auto badge = PaintUnreadBadge(p, counter, right, top, st); - (badgesState.mention - ? (st.active - ? st::dialogsUnreadMentionActive - : st.selected - ? st::dialogsUnreadMentionOver - : st::dialogsUnreadMention) - : (st.active - ? st::dialogsUnreadReactionActive - : st.selected - ? st::dialogsUnreadReactionOver - : st::dialogsUnreadReaction)).paintInCenter(p, badge); + ThreeStateIcon( + badgesState.mention + ? st::dialogsUnreadMention + : st::dialogsUnreadReaction, + st.active, + st.selected).paintInCenter(p, badge); right -= badge.width() + st.padding + st::dialogsUnreadPadding; } return (initial - right); @@ -437,11 +432,10 @@ void PaintRow( auto availableWidth = namewidth; if (entry->isPinnedDialog(context.filter) && (context.filter || !entry->fixedOnTopIndex())) { - auto &icon = context.active - ? st::dialogsPinnedIconActive - : context.selected - ? st::dialogsPinnedIconOver - : st::dialogsPinnedIcon; + auto &icon = ThreeStateIcon( + st::dialogsPinnedIcon, + context.active, + context.selected); icon.paint( p, context.width - context.st->padding.right() - icon.width(), @@ -527,11 +521,10 @@ void PaintRow( auto availableWidth = namewidth; if (entry->isPinnedDialog(context.filter) && (context.filter || !entry->fixedOnTopIndex())) { - auto &icon = context.active - ? st::dialogsPinnedIconActive - : context.selected - ? st::dialogsPinnedIconOver - : st::dialogsPinnedIcon; + auto &icon = ThreeStateIcon( + st::dialogsPinnedIcon, + context.active, + context.selected); icon.paint(p, context.width - context.st->padding.right() - icon.width(), texttop, context.width); availableWidth -= icon.width() + st::dialogsUnreadPadding; } @@ -561,51 +554,49 @@ void PaintRow( paintItemCallback(nameleft, namewidth); } else if (entry->isPinnedDialog(context.filter) && (context.filter || !entry->fixedOnTopIndex())) { - auto &icon = context.active - ? st::dialogsPinnedIconActive - : context.selected - ? st::dialogsPinnedIconOver - : st::dialogsPinnedIcon; - icon.paint(p, context.width - context.st->padding.right() - icon.width(), texttop, context.width); + auto &icon = ThreeStateIcon( + st::dialogsPinnedIcon, + context.active, + context.selected); + icon.paint( + p, + context.width - context.st->padding.right() - icon.width(), + texttop, + context.width); } const auto sendStateIcon = [&]() -> const style::icon* { if (!thread) { return nullptr; } else if (const auto topic = thread->asTopic() ; !context.search && topic && topic->closed()) { - return &(context.active - ? st::dialogsLockIconActive - : context.selected - ? st::dialogsLockIconOver - : st::dialogsLockIcon); + return &ThreeStateIcon( + st::dialogsLockIcon, + context.active, + context.selected); } else if (draft) { if (draft->saveRequestId) { - return &(context.active - ? st::dialogsSendingIconActive - : context.selected - ? st::dialogsSendingIconOver - : st::dialogsSendingIcon); + return &ThreeStateIcon( + st::dialogsSendingIcon, + context.active, + context.selected); } } else if (item && !item->isEmpty() && item->needCheck()) { if (!item->isSending() && !item->hasFailed()) { if (item->unread(thread)) { - return &(context.active - ? st::dialogsSentIconActive - : context.selected - ? st::dialogsSentIconOver - : st::dialogsSentIcon); + return &ThreeStateIcon( + st::dialogsSentIcon, + context.active, + context.selected); } - return &(context.active - ? st::dialogsReceivedIconActive - : context.selected - ? st::dialogsReceivedIconOver - : st::dialogsReceivedIcon); + return &ThreeStateIcon( + st::dialogsReceivedIcon, + context.active, + context.selected); } - return &(context.active - ? st::dialogsSendingIconActive - : context.selected - ? st::dialogsSendingIconOver - : st::dialogsSendingIcon); + return &ThreeStateIcon( + st::dialogsSendingIcon, + context.active, + context.selected); } return nullptr; }(); @@ -643,11 +634,10 @@ void PaintRow( : context.selected ? &st::dialogsVerifiedIconOver : &st::dialogsVerifiedIcon), - .premium = (context.active - ? &st::dialogsPremiumIconActive - : context.selected - ? &st::dialogsPremiumIconOver - : &st::dialogsPremiumIcon), + .premium = &ThreeStateIcon( + st::dialogsPremiumIcon, + context.active, + context.selected), .scam = (context.active ? &st::dialogsScamFgActive : context.selected @@ -710,30 +700,26 @@ const style::icon *ChatTypeIcon( const PaintContext &context) { if (const auto user = peer->asUser()) { if (ShowUserBotIcon(user)) { - return &(context.active - ? st::dialogsBotIconActive - : context.selected - ? st::dialogsBotIconOver - : st::dialogsBotIcon); + return &ThreeStateIcon( + st::dialogsBotIcon, + context.active, + context.selected); } } else if (peer->isBroadcast()) { - return &(context.active - ? st::dialogsChannelIconActive - : context.selected - ? st::dialogsChannelIconOver - : st::dialogsChannelIcon); + return &ThreeStateIcon( + st::dialogsChannelIcon, + context.active, + context.selected); } else if (peer->isForum()) { - return &(context.active - ? st::dialogsForumIconActive - : context.selected - ? st::dialogsForumIconOver - : st::dialogsForumIcon); + return &ThreeStateIcon( + st::dialogsForumIcon, + context.active, + context.selected); } else { - return &(context.active - ? st::dialogsChatIconActive - : context.selected - ? st::dialogsChatIconOver - : st::dialogsChatIcon); + return &ThreeStateIcon( + st::dialogsChatIcon, + context.active, + context.selected); } return nullptr; } diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp index b1f59fb43..253bdc967 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp @@ -11,12 +11,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item.h" #include "history/view/history_view_item_preview.h" #include "main/main_session.h" +#include "dialogs/dialogs_three_state_icon.h" #include "dialogs/ui/dialogs_layout.h" #include "dialogs/ui/dialogs_topics_view.h" #include "ui/effects/spoiler_mess.h" #include "ui/text/text_options.h" #include "ui/text/text_utilities.h" -#include "ui/image/image.h" #include "ui/painter.h" #include "ui/power_saving.h" #include "core/ui_integration.h" @@ -159,6 +159,11 @@ void MessageView::prepare( options.ignoreTopic = true; options.spoilerLoginCode = true; auto preview = item->toPreview(options); + _leftIcon = (preview.icon == ItemPreview::Icon::ForwardedMessage) + ? &st::dialogsMiniForwardIcon + : (preview.icon == ItemPreview::Icon::ReplyToStory) + ? &st::dialogsMiniReplyStoryIcon + : nullptr; const auto hasImages = !preview.images.empty(); const auto history = item->history(); const auto context = Core::MarkedTextContext{ @@ -169,7 +174,7 @@ void MessageView::prepare( const auto senderTill = (preview.arrowInTextPosition > 0) ? preview.arrowInTextPosition : preview.imagesInTextPosition; - if (hasImages && senderTill > 0) { + if ((hasImages || _leftIcon) && senderTill > 0) { auto sender = Text::Mid(preview.text, 0, senderTill); TextUtilities::Trim(sender); _senderCache.setMarkedText( @@ -314,6 +319,15 @@ void MessageView::paint( rect.setLeft(rect.x() + skip); } } + + if (_leftIcon) { + const auto &icon = ThreeStateIcon( + *_leftIcon, + context.active, + context.selected); + icon.paint(p, rect.topLeft(), rect.width()); + rect.setLeft(rect.x() + icon.width() + st::dialogsMiniIconSkip); + } for (const auto &image : _imagesCache) { if (rect.width() < st::dialogsMiniPreview) { break; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h index 87b69b435..14f677536 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h @@ -15,6 +15,7 @@ enum class ImageRoundRadius; namespace style { struct DialogRow; +struct ThreeStateIcon; } // namespace style namespace Ui { @@ -92,6 +93,7 @@ private: mutable std::vector _imagesCache; mutable std::unique_ptr _spoiler; mutable std::unique_ptr _loadingContext; + mutable const style::ThreeStateIcon *_leftIcon = nullptr; }; diff --git a/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp b/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp index e105cd441..a2072b065 100644 --- a/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp +++ b/Telegram/SourceFiles/ffmpeg/ffmpeg_utility.cpp @@ -10,19 +10,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/algorithm.h" #include "logs.h" +#if !defined DESKTOP_APP_USE_PACKAGED && !defined Q_OS_WIN && !defined Q_OS_MAC +#include "base/platform/linux/base_linux_library.h" +#include +#endif // !DESKTOP_APP_USE_PACKAGED && !Q_OS_WIN && !Q_OS_MAC + #include #ifdef LIB_FFMPEG_USE_QT_PRIVATE_API #include #endif // LIB_FFMPEG_USE_QT_PRIVATE_API -#include - extern "C" { #include -#if !defined DESKTOP_APP_USE_PACKAGED && !defined Q_OS_WIN && !defined Q_OS_MAC -#include -#endif // !DESKTOP_APP_USE_PACKAGED && !Q_OS_WIN && !Q_OS_MAC } // extern "C" namespace FFmpeg { @@ -95,19 +95,10 @@ void PremultiplyLine(uchar *dst, const uchar *src, int intsCount) { auto list = std::deque{ AV_PIX_FMT_CUDA, }; - const auto vdpau = [&] { - if (const auto handle = dlopen("libvdpau.so.1", RTLD_LAZY)) { - dlclose(handle); - } - if (dlerror()) { - return false; - } - return true; - }(); - if (vdpau) { + if (base::Platform::LoadLibrary("libvdpau.so.1")) { list.push_front(AV_PIX_FMT_VDPAU); } - const auto va = [&] { + if ([&] { const auto list = std::array{ "libva-drm.so.1", "libva-x11.so.1", @@ -115,16 +106,12 @@ void PremultiplyLine(uchar *dst, const uchar *src, int intsCount) { "libdrm.so.2", }; for (const auto lib : list) { - if (const auto handle = dlopen(lib, RTLD_LAZY)) { - dlclose(handle); - } - if (dlerror()) { + if (!base::Platform::LoadLibrary(lib)) { return false; } } return true; - }(); - if (va) { + }()) { list.push_front(AV_PIX_FMT_VAAPI); } return list; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 595c253ce..918f10bfd 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_inner_widget.h" #include "core/file_utilities.h" -#include "core/crash_reports.h" #include "core/click_handler_types.h" #include "history/history.h" #include "history/admin_log/history_admin_log_item.h" @@ -32,7 +31,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/menu/menu_add_action_callback_factory.h" #include "ui/widgets/menu/menu_multiline_action.h" #include "ui/widgets/popup_menu.h" -#include "ui/image/image.h" #include "ui/effects/path_shift_gradient.h" #include "ui/effects/message_sending_animation_controller.h" #include "ui/effects/reaction_fly_animation.h" @@ -40,16 +38,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/boxes/report_box.h" #include "ui/layers/generic_box.h" #include "ui/controls/delete_message_context_action.h" -#include "ui/controls/who_reacted_context_action.h" #include "ui/painter.h" #include "ui/ui_utility.h" -#include "ui/cached_round_corners.h" #include "ui/inactive_press.h" -#include "window/window_adaptive.h" #include "window/window_session_controller.h" #include "window/window_controller.h" #include "window/window_peer_menu.h" -#include "window/window_controller.h" #include "window/notifications_manager.h" #include "boxes/about_sponsored_box.h" #include "boxes/delete_messages_box.h" @@ -94,12 +88,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_file_origin.h" #include "data/data_histories.h" #include "data/data_changes.h" -#include "data/stickers/data_stickers.h" #include "data/data_sponsored_messages.h" #include "dialogs/ui/dialogs_video_userpic.h" #include "settings/settings_premium.h" #include "styles/style_chat.h" -#include "styles/style_window.h" // st::windowMinWidth #include "styles/style_menu_icons.h" #include diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 8ed9b9cac..13e52a6f7 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -15,17 +15,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_message.h" #include "history/view/history_view_service_message.h" #include "history/view/media/history_view_media_grouped.h" -#include "history/history_item.h" #include "history/history_item_components.h" #include "history/history_item_helpers.h" #include "history/history_unread_things.h" #include "history/history.h" #include "mtproto/mtproto_config.h" #include "media/clip/media_clip_reader.h" -#include "ui/effects/ripple_animation.h" #include "ui/text/format_values.h" #include "ui/text/text_isolated_emoji.h" -#include "ui/text/text_options.h" #include "ui/text/text_utilities.h" #include "storage/file_upload.h" #include "storage/storage_facade.h" @@ -41,7 +38,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mainwindow.h" #include "window/window_controller.h" #include "window/window_session_controller.h" -#include "core/crash_reports.h" #include "core/click_handler_types.h" #include "base/unixtime.h" #include "base/timer_rpl.h" @@ -72,7 +68,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/stickers_gift_box_pack.h" #include "payments/payments_checkout_process.h" // CheckoutProcess::Start. #include "styles/style_dialogs.h" -#include "styles/style_chat.h" #include "ayu/ayu_settings.h" @@ -2972,6 +2967,11 @@ ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const { ? tr::lng_from_you(tr::now) : sender->shortName(); }; + result.icon = (Get() != nullptr) + ? ItemPreview::Icon::ForwardedMessage + : replyToStory().valid() + ? ItemPreview::Icon::ReplyToStory + : ItemPreview::Icon::None; const auto fromForwarded = [&]() -> std::optional { if (const auto forwarded = Get()) { return forwarded->originalSender diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 9e82f9d06..59ab3396c 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -43,6 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_bot.h" #include "styles/style_widgets.h" #include "styles/style_chat.h" +#include "styles/style_dialogs.h" // dialogsMiniReplyStoryIcon. #include @@ -460,7 +461,14 @@ void HistoryMessageReply::updateName( w += st::msgServiceFont->spacew + replyToVia->maxWidth; } - maxReplyWidth = previewSkip + qMax(w, qMin(replyToText.maxWidth(), int32(st::maxSignatureSize))); + maxReplyWidth = previewSkip + + std::max( + w, + std::min(replyToText.maxWidth(), st::maxSignatureSize)) + + (storyReply + ? (st::dialogsMiniIconSkip + + st::dialogsMiniReplyStoryIcon.icon.width()) + : 0); } else { maxReplyWidth = st::msgDateFont->width(statePhrase()); } @@ -596,14 +604,27 @@ void HistoryMessageReply::paint( ? stm->historyTextFg : st->msgImgReplyBarColor()); holder->prepareCustomEmojiPaint(p, context, replyToText); + auto replyToTextPosition = QPoint( + x + st::msgReplyBarSkip + previewSkip, + y + st::msgReplyPadding.top() + st::msgServiceNameFont->height); + const auto replyToTextPalette = &(inBubble + ? stm->replyTextPalette + : st->imgReplyTextPalette()); + if (storyReply) { + st::dialogsMiniReplyStoryIcon.icon.paint( + p, + replyToTextPosition, + w - st::msgReplyBarSkip - previewSkip, + replyToTextPalette->linkFg->c); + replyToTextPosition += QPoint( + st::dialogsMiniIconSkip + + st::dialogsMiniReplyStoryIcon.icon.width(), + 0); + } replyToText.draw(p, { - .position = QPoint( - x + st::msgReplyBarSkip + previewSkip, - y + st::msgReplyPadding.top() + st::msgServiceNameFont->height), + .position = replyToTextPosition, .availableWidth = w - st::msgReplyBarSkip - previewSkip, - .palette = &(inBubble - ? stm->replyTextPalette - : st->imgReplyTextPalette()), + .palette = replyToTextPalette, .spoiler = Ui::Text::DefaultSpoilerCache(), .now = context.now, .pausedEmoji = (context.paused diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index cf9466ba6..ea4b41222 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -12,7 +12,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_chat_participants.h" #include "api/api_report.h" #include "api/api_sending.h" -#include "api/api_text_entities.h" #include "api/api_send_progress.h" #include "api/api_unread_things.h" #include "ui/boxes/confirm_box.h" @@ -32,12 +31,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/inner_dropdown.h" #include "ui/widgets/dropdown_menu.h" #include "ui/widgets/labels.h" -#include "ui/widgets/shadow.h" #include "ui/effects/ripple_animation.h" #include "ui/effects/message_sending_animation_controller.h" #include "ui/text/text_utilities.h" // Ui::Text::ToUpper #include "ui/text/format_values.h" -#include "ui/chat/forward_options_box.h" #include "ui/chat/message_bar.h" #include "ui/chat/attach/attach_send_files_way.h" #include "ui/chat/choose_send_as.h" @@ -62,7 +59,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_document.h" #include "data/data_photo.h" #include "data/data_photo_media.h" -#include "data/data_media_types.h" #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_forum.h" @@ -107,7 +103,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_translate_bar.h" #include "history/view/media/history_view_media.h" #include "profile/profile_block_group_members.h" -#include "info/info_memento.h" #include "core/click_handler_types.h" #include "chat_helpers/tabbed_panel.h" #include "chat_helpers/tabbed_selector.h" @@ -138,7 +133,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/continuous_scroll.h" #include "ui/widgets/popup_menu.h" #include "ui/item_text_options.h" -#include "ui/unread_badge.h" #include "main/main_session.h" #include "main/main_session_settings.h" #include "main/session/send_as_peers.h" @@ -152,7 +146,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "inline_bots/bot_attach_web_view.h" #include "info/profile/info_profile_values.h" // SharedMediaCountValue. #include "chat_helpers/emoji_suggestions_widget.h" -#include "core/crash_reports.h" #include "core/shortcuts.h" #include "core/ui_integration.h" #include "support/support_common.h" @@ -160,12 +153,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "support/support_preload.h" #include "dialogs/dialogs_key.h" #include "calls/calls_instance.h" -#include "api/api_bot.h" #include "styles/style_chat.h" #include "styles/style_dialogs.h" #include "styles/style_window.h" #include "styles/style_boxes.h" -#include "styles/style_profile.h" #include "styles/style_chat_helpers.h" #include "styles/style_info.h" diff --git a/Telegram/SourceFiles/history/view/history_view_item_preview.h b/Telegram/SourceFiles/history/view/history_view_item_preview.h index 7ac0a2b18..f2304c49e 100644 --- a/Telegram/SourceFiles/history/view/history_view_item_preview.h +++ b/Telegram/SourceFiles/history/view/history_view_item_preview.h @@ -23,11 +23,17 @@ struct ItemPreviewImage { }; struct ItemPreview { + enum class Icon { + None, + ForwardedMessage, + ReplyToStory, + }; TextWithEntities text; std::vector images; int arrowInTextPosition = -1; int imagesInTextPosition = 0; std::any loadingContext; + Icon icon = Icon::None; }; struct ToPreviewOptions { diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index e6f7fbcad..aa453c225 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -759,7 +759,9 @@ QSize Message::performCountOptimalSize() { : item->hiddenSenderInfo()->nameText(); auto namew = st::msgPadding.left() + name.maxWidth() - + (_fromNameStatus ? st::dialogsPremiumIcon.width() : 0) + + (_fromNameStatus + ? st::dialogsPremiumIcon.icon.width() + : 0) + st::msgPadding.right(); if (via && !displayForwardedFrom()) { namew += st::msgServiceFont->spacew + via->maxWidth @@ -1358,7 +1360,7 @@ void Message::paintFromName( return &info->nameText(); }(); const auto statusWidth = _fromNameStatus - ? st::dialogsPremiumIcon.width() + ? st::dialogsPremiumIcon.icon.width() : 0; if (statusWidth && availableWidth > statusWidth) { const auto x = availableLeft @@ -1398,7 +1400,7 @@ void Message::paintFromName( .paused = context.paused || On(PowerSaving::kEmojiStatus), }); } else { - st::dialogsPremiumIcon.paint(p, x, y, width(), color); + st::dialogsPremiumIcon.icon.paint(p, x, y, width(), color); } availableWidth -= statusWidth; } @@ -1407,7 +1409,8 @@ void Message::paintFromName( nameText->drawElided(p, availableLeft, trect.top(), availableWidth); const auto skipWidth = nameText->maxWidth() + (_fromNameStatus - ? (st::dialogsPremiumIcon.width() + st::msgServiceFont->spacew) + ? (st::dialogsPremiumIcon.icon.width() + + st::msgServiceFont->spacew) : 0) + st::msgServiceFont->spacew; availableLeft += skipWidth; @@ -3525,7 +3528,7 @@ void Message::fromNameUpdated(int width) const { - st::msgPadding.right() - nameText->maxWidth() + (_fromNameStatus - ? (st::dialogsPremiumIcon.width() + ? (st::dialogsPremiumIcon.icon.width() + st::msgServiceFont->spacew) : 0) - st::msgServiceFont->spacew); diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp index 6f9275ae0..80b546b53 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -566,7 +566,7 @@ void TopBarWidget::paintTopBar(Painter &p) { { .peer = peer, .verified = &st::dialogsVerifiedIcon, - .premium = &st::dialogsPremiumIcon, + .premium = &st::dialogsPremiumIcon.icon, .scam = &st::attentionButtonFg, .premiumFg = &st::dialogsVerifiedIconBg, .customEmojiRepaint = [=] { update(); }, diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp index e24577962..c07fc9269 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp @@ -241,7 +241,7 @@ int Selector::countWidth(int desiredWidth, int maxWidth) { return std::max(2 * _skipx + _columns * _size, desiredWidth); } -QMargins Selector::extentsForShadow() const { +QMargins Selector::marginsForShadow() const { const auto line = st::lineWidth; return useTransparency() ? st::reactionCornerShadow @@ -264,26 +264,26 @@ void Selector::setSpecialExpandTopSkip(int skip) { } void Selector::initGeometry(int innerTop) { - const auto extents = extentsForShadow(); + const auto margins = marginsForShadow(); const auto parent = parentWidget()->rect(); const auto innerWidth = 2 * _skipx + _columns * _size; const auto innerHeight = st::reactStripHeight; const auto width = _useTransparency - ? (innerWidth + extents.left() + extents.right()) + ? (innerWidth + margins.left() + margins.right()) : parent.width(); - const auto height = innerHeight + extents.top() + extents.bottom(); + const auto height = innerHeight + margins.top() + margins.bottom(); const auto left = style::RightToLeft() ? 0 : (parent.width() - width); _collapsedTopSkip = _useTransparency ? (extendTopForCategories() + _specialExpandTopSkip) : 0; - const auto top = innerTop - extents.top() - _collapsedTopSkip; - const auto add = _st.icons.stripBubble.height() - extents.bottom(); + const auto top = innerTop - margins.top() - _collapsedTopSkip; + const auto add = _st.icons.stripBubble.height() - margins.bottom(); _outer = QRect(0, _collapsedTopSkip, width, height); _outerWithBubble = _outer.marginsAdded({ 0, 0, 0, add }); setGeometry(_outerWithBubble.marginsAdded( { 0, _collapsedTopSkip, 0, 0 } ).translated(left, top)); - _inner = _outer.marginsRemoved(extents); + _inner = _outer.marginsRemoved(margins); if (!_strip) { expand(); @@ -343,9 +343,9 @@ void Selector::paintAppearing(QPainter &p) { } _paintBuffer.fill(_st.bg->c); auto q = QPainter(&_paintBuffer); - const auto extents = extentsForShadow(); + const auto margins = marginsForShadow(); const auto appearedWidth = countAppearedWidth(_appearProgress); - const auto fullWidth = _inner.x() + appearedWidth + extents.right(); + const auto fullWidth = _inner.x() + appearedWidth + margins.right(); const auto size = QSize(fullWidth, _outer.height()); q.translate(_inner.topLeft() - QPoint(0, _collapsedTopSkip)); @@ -455,7 +455,7 @@ auto Selector::paintExpandingBg(QPainter &p, float64 progress) const auto radius = _reactions.customAllowed ? (radiusStart + progress * (radiusEnd - radiusStart)) : radiusStart; - const auto extents = extentsForShadow(); + const auto margins = marginsForShadow(); const auto expanding = anim::easeOutCirc(1., progress); const auto expandUp = anim::interpolate(0, _collapsedTopSkip, expanding); const auto expandDown = anim::interpolate( @@ -470,7 +470,7 @@ auto Selector::paintExpandingBg(QPainter &p, float64 progress) p.fillRect(fill, _st.bg); } } else { - const auto inner = outer.marginsRemoved(extentsForShadow()); + const auto inner = outer.marginsRemoved(marginsForShadow()); p.fillRect(inner, _st.bg); p.fillRect( inner.x(), @@ -483,7 +483,7 @@ auto Selector::paintExpandingBg(QPainter &p, float64 progress) 0, extendTopForCategories(), expanding); - const auto inner = outer.marginsRemoved(extents); + const auto inner = outer.marginsRemoved(margins); _shadowTop = inner.y() + categories; _shadowSkip = (_useTransparency && categories < radius) ? int(base::SafeRound( @@ -494,7 +494,7 @@ auto Selector::paintExpandingBg(QPainter &p, float64 progress) .list = inner.marginsRemoved({ 0, categories, 0, 0 }), .radius = radius, .expanding = expanding, - .finalBottom = height() - extents.bottom(), + .finalBottom = height() - margins.bottom(), }; } @@ -521,7 +521,7 @@ void Selector::paintExpanded(QPainter &p) { if (_useTransparency) { p.drawImage(0, 0, _paintBuffer); } else { - const auto inner = rect().marginsRemoved(extentsForShadow()); + const auto inner = rect().marginsRemoved(marginsForShadow()); p.fillRect(inner, _st.bg); p.fillRect( inner.x(), @@ -694,13 +694,13 @@ void Selector::expand() { _willExpand.fire({}); preloadAllRecentsAnimations(); const auto parent = parentWidget()->geometry(); - const auto extents = extentsForShadow(); + const auto margins = marginsForShadow(); const auto heightLimit = _reactions.customAllowed ? st::emojiPanMaxHeight : minimalHeight(); const auto willBeHeight = std::min( parent.height() - y(), - extents.top() + heightLimit + extents.bottom()); + margins.top() + heightLimit + margins.bottom()); const auto additionalBottom = willBeHeight - height(); const auto additional = _specialExpandTopSkip + additionalBottom; if (additionalBottom < 0 || additional <= 0) { @@ -834,7 +834,7 @@ void Selector::createList() { _list->jumpedToPremium( ) | rpl::start_with_next(_jumpedToPremium, _list->lifetime()); - const auto inner = rect().marginsRemoved(extentsForShadow()); + const auto inner = rect().marginsRemoved(marginsForShadow()); const auto footer = _reactions.customAllowed ? _list->createFooter().data() : nullptr; @@ -904,16 +904,16 @@ bool AdjustMenuGeometryForSelector( const auto desiredWidth = menu->menu()->width() + added; const auto maxWidth = menu->st().menu.widthMax + added; const auto width = selector->countWidth(desiredWidth, maxWidth); - const auto extents = selector->extentsForShadow(); + const auto margins = selector->marginsForShadow(); const auto categoriesTop = selector->useTransparency() ? selector->extendTopForCategories() : 0; menu->setForceWidth(width - added); const auto height = menu->height(); - const auto fullTop = extents.top() + categoriesTop + extend.top(); - const auto minimalHeight = extents.top() + const auto fullTop = margins.top() + categoriesTop + extend.top(); + const auto minimalHeight = margins.top() + selector->minimalHeight() - + extents.bottom(); + + margins.bottom(); const auto willBeHeightWithoutBottomPadding = fullTop + height - menu->st().shadow.extend.top(); @@ -924,15 +924,15 @@ bool AdjustMenuGeometryForSelector( ? (minimalHeight - willBeHeightWithoutBottomPadding) : 0); menu->setAdditionalMenuPadding(QMargins( - extents.left() + extend.left(), + margins.left() + extend.left(), fullTop, - extents.right() + extend.right(), + margins.right() + extend.right(), additionalPaddingBottom ), QMargins( - extents.left(), - extents.top(), - extents.right(), - std::min(additionalPaddingBottom, extents.bottom()) + margins.left(), + margins.top(), + margins.right(), + std::min(additionalPaddingBottom, margins.bottom()) )); if (!menu->prepareGeometryFor(desiredPosition)) { return false; @@ -944,14 +944,14 @@ bool AdjustMenuGeometryForSelector( return true; } menu->setAdditionalMenuPadding(QMargins( - extents.left() + extend.left(), + margins.left() + extend.left(), fullTop + additionalPaddingBottom, - extents.right() + extend.right(), + margins.right() + extend.right(), 0 ), QMargins( - extents.left(), - extents.top(), - extents.right(), + margins.left(), + margins.top(), + margins.right(), 0 )); selector->setSpecialExpandTopSkip(additionalPaddingBottom); diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h index 8d3b34a03..bf9ddb72b 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h @@ -61,7 +61,7 @@ public: [[nodiscard]] bool useTransparency() const; int countWidth(int desiredWidth, int maxWidth); - [[nodiscard]] QMargins extentsForShadow() const; + [[nodiscard]] QMargins marginsForShadow() const; [[nodiscard]] int extendTopForCategories() const; [[nodiscard]] int minimalHeight() const; [[nodiscard]] int countAppearedWidth(float64 progress) const; diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp index 02b42d593..8057829e3 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp @@ -243,18 +243,18 @@ void Reactions::Panel::create() { const auto desiredWidth = st::storiesReactionsWidth; const auto maxWidth = desiredWidth * 2; const auto width = _selector->countWidth(desiredWidth, maxWidth); - const auto extents = _selector->extentsForShadow(); + const auto margins = _selector->marginsForShadow(); const auto categoriesTop = _selector->extendTopForCategories(); - const auto full = extents.left() + width + extents.right(); + const auto full = margins.left() + width + margins.right(); _shownValue = 0.; rpl::combine( _controller->layoutValue(), _shownValue.value() ) | rpl::start_with_next([=](const Layout &layout, float64 shown) { - const auto width = extents.left() + const auto width = margins.left() + _selector->countAppearedWidth(shown) - + extents.right(); + + margins.right(); const auto height = layout.reactions.height(); const auto shift = (width / 2); const auto right = (mode == Mode::Message) @@ -271,7 +271,7 @@ void Reactions::Panel::create() { const auto innerTop = height - st::storiesReactionsBottomSkip - st::reactStripHeight; - const auto maxAdded = innerTop - extents.top() - categoriesTop; + const auto maxAdded = innerTop - margins.top() - categoriesTop; const auto added = std::min(maxAdded, st::storiesReactionsAddedTop); _selector->setSpecialExpandTopSkip(added); _selector->initGeometry(innerTop); diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index fbf5e385a..8062ccc9f 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -88,7 +88,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session_settings.h" #include "layout/layout_document_generic_preview.h" #include "platform/platform_overlay_widget.h" -#include "settings/settings_premium.h" #include "storage/file_download.h" #include "storage/storage_account.h" #include "calls/calls_instance.h" @@ -104,7 +103,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include #include -#include #include #include diff --git a/Telegram/SourceFiles/menu/menu_mute.cpp b/Telegram/SourceFiles/menu/menu_mute.cpp index 444747584..dd58f6fc0 100644 --- a/Telegram/SourceFiles/menu/menu_mute.cpp +++ b/Telegram/SourceFiles/menu/menu_mute.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_thread.h" #include "data/notify/data_notify_settings.h" +#include "data/notify/data_peer_notify_settings.h" #include "info/profile/info_profile_values.h" #include "lang/lang_keys.h" #include "main/main_session.h" @@ -31,10 +32,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_menu_icons.h" namespace MuteMenu { - namespace { constexpr auto kMuteDurSecondsDefault = crl::time(8) * 3600; +constexpr auto kMuteForeverValue = std::numeric_limits::max(); class IconWithText final : public Ui::Menu::Action { public: @@ -70,7 +71,7 @@ public: MuteItem( not_null parent, const style::Menu &st, - not_null thread); + Descriptor descriptor); protected: void paintEvent(QPaintEvent *e) override; @@ -79,31 +80,30 @@ private: const QPoint _itemIconPosition; Ui::Animations::Simple _animation; bool _isMuted = false; + bool _inited; }; MuteItem::MuteItem( not_null parent, const style::Menu &st, - not_null thread) + Descriptor descriptor) : Ui::Menu::Action( parent, st, Ui::CreateChild(parent.get()), nullptr, nullptr) -, _itemIconPosition(st.itemIconPosition) -, _isMuted(thread->owner().notifySettings().isMuted(thread)) { - Info::Profile::NotificationsEnabledValue( - thread - ) | rpl::start_with_next([=](bool isUnmuted) { - const auto isMuted = !isUnmuted; +, _itemIconPosition(st.itemIconPosition) { + descriptor.isMutedValue( + ) | rpl::start_with_next([=](bool isMuted) { action()->setText(isMuted ? tr::lng_mute_menu_duration_unmute(tr::now) : tr::lng_mute_menu_duration_forever(tr::now)); - if (isMuted == _isMuted) { + if (_inited && isMuted == _isMuted) { return; } + _inited = true; _isMuted = isMuted; _animation.start( [=] { update(); }, @@ -112,13 +112,8 @@ MuteItem::MuteItem( st::defaultPopupMenu.showDuration); }, lifetime()); - const auto weak = base::make_weak(thread); setClickedCallback([=] { - if (const auto strong = weak.get()) { - strong->owner().notifySettings().update( - strong, - { .unmute = _isMuted, .forever = !_isMuted }); - } + descriptor.updateMutePeriod(_isMuted ? 0 : kMuteForeverValue); }); } @@ -140,7 +135,7 @@ void MuteItem::paintEvent(QPaintEvent *e) { icon.paint(p, _itemIconPosition, width(), color); } -void MuteBox(not_null box, not_null thread) { +void MuteBox(not_null box, Descriptor descriptor) { struct State { int lastSeconds = 0; }; @@ -161,14 +156,9 @@ void MuteBox(not_null box, not_null thread) { : tr::lng_mute_menu_mute(); }) | rpl::flatten_latest(); - const auto weak = base::make_weak(thread); Ui::ConfirmBox(box, { .confirmed = [=] { - if (const auto strong = weak.get()) { - strong->owner().notifySettings().update( - strong, - { .period = state->lastSeconds }); - } + descriptor.updateMutePeriod(state->lastSeconds); box->getDelegate()->hideLayer(); }, .confirmText = std::move(confirmText), @@ -178,7 +168,7 @@ void MuteBox(not_null box, not_null thread) { void PickMuteBox( not_null box, - not_null thread) { + Descriptor descriptor) { struct State { base::unique_qptr menu; }; @@ -191,17 +181,12 @@ void PickMuteBox( const auto pickerCallback = TimePickerBox(box, seconds, phrases, 0); - const auto weak = base::make_weak(thread); Ui::ConfirmBox(box, { .confirmed = [=] { const auto muteFor = pickerCallback(); - if (const auto strong = weak.get()) { - strong->owner().notifySettings().update( - strong, - { .period = muteFor }); - strong->session().settings().addMutePeriod(muteFor); - strong->session().saveSettings(); - } + descriptor.updateMutePeriod(muteFor); + descriptor.session->settings().addMutePeriod(muteFor); + descriptor.session->saveSettings(); box->closeBox(); }, .confirmText = tr::lng_mute_menu_mute(), @@ -220,11 +205,7 @@ void PickMuteBox( st::popupMenuWithIcons); state->menu->addAction( tr::lng_manage_messages_ttl_after_custom(tr::now), - [=] { - if (const auto strong = weak.get()) { - box->getDelegate()->show(Box(MuteBox, strong)); - } - }, + [=] { box->getDelegate()->show(Box(MuteBox, descriptor)); }, &st::menuIconCustomize); state->menu->setDestroyedCallback(crl::guard(top, [=] { top->setForceRippled(false); @@ -236,46 +217,123 @@ void PickMuteBox( } // namespace +Descriptor ThreadDescriptor(not_null thread) { + const auto weak = base::make_weak(thread); + const auto isMutedValue = [=]() -> rpl::producer { + if (const auto strong = weak.get()) { + return Info::Profile::NotificationsEnabledValue( + strong + ) | rpl::map(!rpl::mappers::_1); + } + return rpl::single(false); + }; + const auto currentSound = [=] { + const auto strong = weak.get(); + return strong + ? strong->owner().notifySettings().sound(strong) + : std::optional(); + }; + const auto updateSound = crl::guard(weak, [=](Data::NotifySound sound) { + thread->owner().notifySettings().update(thread, {}, {}, sound); + }); + const auto updateMutePeriod = crl::guard(weak, [=](TimeId mute) { + const auto settings = &thread->owner().notifySettings(); + if (!mute) { + settings->update(thread, { .unmute = true }); + } else if (mute == kMuteForeverValue) { + settings->update(thread, { .forever = true }); + } else { + settings->update(thread, { .period = mute }); + } + }); + return { + .session = &thread->session(), + .isMutedValue = isMutedValue, + .currentSound = currentSound, + .updateSound = updateSound, + .updateMutePeriod = updateMutePeriod, + }; +} + +Descriptor DefaultDescriptor( + not_null session, + Data::DefaultNotify type) { + const auto settings = &session->data().notifySettings(); + const auto isMutedValue = [=]() -> rpl::producer { + return rpl::single( + rpl::empty + ) | rpl::then( + settings->defaultUpdates(type) + ) | rpl::map([=] { + return settings->isMuted(type); + }); + }; + const auto currentSound = [=] { + return settings->defaultSettings(type).sound(); + }; + const auto updateSound = [=](Data::NotifySound sound) { + settings->defaultUpdate(type, {}, {}, sound); + }; + const auto updateMutePeriod = [=](TimeId mute) { + if (!mute) { + settings->defaultUpdate(type, { .unmute = true }); + } else if (mute == kMuteForeverValue) { + settings->defaultUpdate(type, { .forever = true }); + } else { + settings->defaultUpdate(type, { .period = mute }); + } + }; + return { + .session = session, + .isMutedValue = isMutedValue, + .currentSound = currentSound, + .updateSound = updateSound, + .updateMutePeriod = updateMutePeriod, + }; +} + void FillMuteMenu( not_null menu, - not_null thread, + Descriptor descriptor, std::shared_ptr show) { - const auto weak = base::make_weak(thread); - const auto with = [=](Fn thread)> handler) { - return [=] { - if (const auto strong = weak.get()) { - handler(strong); - } - }; + const auto session = descriptor.session; + const auto soundSelect = [=] { + if (const auto currentSound = descriptor.currentSound()) { + show->showBox(Box( + RingtonesBox, + session, + *currentSound, + descriptor.updateSound)); + } }; - menu->addAction( tr::lng_mute_menu_sound_select(tr::now), - with([=](not_null thread) { - show->showBox(Box(ThreadRingtonesBox, thread)); - }), + soundSelect, &st::menuIconSoundSelect); - const auto notifySettings = &thread->owner().notifySettings(); - const auto soundIsNone = notifySettings->sound(thread).none; + const auto soundIsNone = descriptor.currentSound().value_or( + Data::NotifySound() + ).none; + const auto toggleSound = [=] { + if (auto sound = descriptor.currentSound()) { + sound->none = !soundIsNone; + descriptor.updateSound(*sound); + } + }; menu->addAction( - soundIsNone + (soundIsNone ? tr::lng_mute_menu_sound_on(tr::now) - : tr::lng_mute_menu_sound_off(tr::now), - with([=](not_null thread) { - auto sound = notifySettings->sound(thread); - sound.none = !sound.none; - notifySettings->update(thread, {}, {}, sound); - }), + : tr::lng_mute_menu_sound_off(tr::now)), + toggleSound, soundIsNone ? &st::menuIconSoundOn : &st::menuIconSoundOff); const auto &st = menu->st().menu; const auto iconTextPosition = st.itemIconPosition + st::menuIconMuteForAnyTextPosition; - for (const auto muteFor : thread->session().settings().mutePeriods()) { - const auto callback = with([=](not_null thread) { - notifySettings->update(thread, { .period = muteFor }); - }); + for (const auto muteFor : session->settings().mutePeriods()) { + const auto callback = [=, update = descriptor.updateMutePeriod] { + update(muteFor); + }; auto item = base::make_unique_q( menu, @@ -295,20 +353,17 @@ void FillMuteMenu( menu->addAction( tr::lng_mute_menu_duration(tr::now), - with([=](not_null thread) { - DEBUG_LOG(("Mute Info: PickMuteBox called.")); - show->showBox(Box(PickMuteBox, thread)); - }), + [=] { show->showBox(Box(PickMuteBox, descriptor)); }, &st::menuIconMuteFor); menu->addAction( - base::make_unique_q(menu, menu->st().menu, thread)); + base::make_unique_q(menu, menu->st().menu, descriptor)); } void SetupMuteMenu( not_null parent, rpl::producer<> triggers, - Fn makeThread, + Fn()> makeDescriptor, std::shared_ptr show) { struct State { base::unique_qptr menu; @@ -319,11 +374,11 @@ void SetupMuteMenu( ) | rpl::start_with_next([=] { if (state->menu) { return; - } else if (const auto thread = makeThread()) { + } else if (const auto descriptor = makeDescriptor()) { state->menu = base::make_unique_q( parent, st::popupMenuWithIcons); - FillMuteMenu(state->menu.get(), thread, show); + FillMuteMenu(state->menu.get(), *descriptor, show); state->menu->popup(QCursor::pos()); } }, parent->lifetime()); diff --git a/Telegram/SourceFiles/menu/menu_mute.h b/Telegram/SourceFiles/menu/menu_mute.h index 045cd4d11..a6d818668 100644 --- a/Telegram/SourceFiles/menu/menu_mute.h +++ b/Telegram/SourceFiles/menu/menu_mute.h @@ -9,8 +9,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Data { class Thread; +struct NotifySound; +enum class DefaultNotify; } // namespace Data +namespace Main { +class Session; +} // namespace Main + namespace Ui { class PopupMenu; class RpWidget; @@ -19,15 +25,48 @@ class Show; namespace MuteMenu { +struct Descriptor { + not_null session; + Fn()> isMutedValue; + Fn()> currentSound; + Fn updateSound; + Fn updateMutePeriod; +}; + +[[nodiscard]] Descriptor ThreadDescriptor(not_null thread); +[[nodiscard]] Descriptor DefaultDescriptor( + not_null session, + Data::DefaultNotify type); + void FillMuteMenu( not_null menu, - not_null thread, + Descriptor descriptor, std::shared_ptr show); void SetupMuteMenu( not_null parent, rpl::producer<> triggers, - Fn makeThread, + Fn()> makeDescriptor, std::shared_ptr show); +inline void FillMuteMenu( + not_null menu, + not_null thread, + std::shared_ptr show) { + FillMuteMenu(menu, ThreadDescriptor(thread), std::move(show)); +} + +inline void SetupMuteMenu( + not_null parent, + rpl::producer<> triggers, + Fn makeThread, + std::shared_ptr show) { + SetupMuteMenu(parent, std::move(triggers), [=] { + const auto thread = makeThread(); + return thread + ? ThreadDescriptor(thread) + : std::optional(); + }, std::move(show)); +} + } // namespace MuteMenu diff --git a/Telegram/SourceFiles/platform/linux/linux_xdp_open_with_dialog.cpp b/Telegram/SourceFiles/platform/linux/linux_xdp_open_with_dialog.cpp index 9efcb0b68..a425ff59f 100644 --- a/Telegram/SourceFiles/platform/linux/linux_xdp_open_with_dialog.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_xdp_open_with_dialog.cpp @@ -10,8 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/platform/base_platform_info.h" #include "base/platform/linux/base_linux_xdp_utilities.h" #include "base/platform/linux/base_linux_wayland_integration.h" -#include "core/application.h" -#include "window/window_controller.h" #include "base/random.h" #include @@ -60,26 +58,13 @@ bool ShowXDPOpenWithDialog(const QString &filepath) { const auto fdGuard = gsl::finally([&] { ::close(fd); }); - const auto parentWindowId = [&]() -> Glib::ustring { - const auto activeWindow = Core::App().activeWindow(); - if (!activeWindow) { - return {}; - } - - return base::Platform::XDP::ParentWindowID( - activeWindow->widget()->windowHandle()); - }(); - const auto handleToken = Glib::ustring("tdesktop") + std::to_string(base::RandomValue()); const auto activationToken = []() -> Glib::ustring { using base::Platform::WaylandIntegration; if (const auto integration = WaylandIntegration::Instance()) { - if (const auto token = integration->activationToken() - ; !token.isNull()) { - return token.toStdString(); - } + return integration->activationToken().toStdString(); } return {}; }(); @@ -124,7 +109,7 @@ bool ShowXDPOpenWithDialog(const QString &filepath) { kXDPOpenURIInterface, "OpenFile", Glib::create_variant(std::tuple{ - parentWindowId, + base::Platform::XDP::ParentWindowID(), Glib::DBusHandle(), std::map{ { diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index 93bd9420b..ac74fd5c5 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -139,11 +139,13 @@ void XCBSetDesktopFileName(QWindow *window) { void SkipTaskbar(QWindow *window, bool skip) { if (const auto integration = WaylandIntegration::Instance()) { integration->skipTaskbar(window, skip); + return; } #ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION if (IsX11()) { XCBSkipTaskbar(window, skip); + return; } #endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION } diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 84ceb77c2..b3b7a50e5 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -17,10 +17,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mainwindow.h" #include "storage/localstorage.h" #include "core/launcher.h" -#include "core/application.h" #include "core/core_settings.h" #include "core/update_checker.h" -#include "window/window_controller.h" #include "webview/platform/linux/webview_linux_webkitgtk.h" #ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION @@ -65,16 +63,6 @@ bool PortalAutostart(bool start, bool silent) { const auto connection = Gio::DBus::Connection::get_sync( Gio::DBus::BusType::SESSION); - const auto parentWindowId = [&]() -> Glib::ustring { - const auto activeWindow = Core::App().activeWindow(); - if (!activeWindow) { - return {}; - } - - return base::Platform::XDP::ParentWindowID( - activeWindow->widget()->windowHandle()); - }(); - const auto handleToken = Glib::ustring("tdesktop") + std::to_string(base::RandomValue()); @@ -152,7 +140,7 @@ bool PortalAutostart(bool start, bool silent) { "org.freedesktop.portal.Background", "RequestBackground", Glib::create_variant(std::tuple{ - parentWindowId, + base::Platform::XDP::ParentWindowID(), options, }), base::Platform::XDP::kService); diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index 4f53c9a33..45173b569 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -523,10 +523,19 @@ settingsPremiumLock: icon{{ "emoji/premium_lock", windowActiveTextFg, point(0px, settingsPremiumLockSkip: 3px; settingsBlockedListSubtitleAddPadding: margins(0px, 1px, 0px, -4px); -settingsBlockedListIconPadding: margins(0px, 34px, 0px, 5px); +settingsBlockedListIconPadding: margins(0px, 24px, 0px, 5px); settingsBlockedList: PeerList(peerListBox) { padding: margins(0px, 0px, 0px, membersMarginBottom); } +settingsBlockedHeightMin: 240px; + +settingsNotificationType: SettingsButton(settingsButton) { + height: 40px; + padding: margins(60px, 4px, 22px, 4px); +} +settingsNotificationTypeDetails: FlatLabel(defaultFlatLabel) { + textFg: windowSubTextFg; +} requestPeerRestriction: FlatLabel(defaultFlatLabel) { minWidth: 240px; diff --git a/Telegram/SourceFiles/settings/settings_blocked_peers.cpp b/Telegram/SourceFiles/settings/settings_blocked_peers.cpp index 45f2376df..e21666d81 100644 --- a/Telegram/SourceFiles/settings/settings_blocked_peers.cpp +++ b/Telegram/SourceFiles/settings/settings_blocked_peers.cpp @@ -47,7 +47,10 @@ Blocked::Blocked( tr::lng_contacts_loading(), st::changePhoneDescription), std::move(padding))); - Ui::ResizeFitChild(this, _loading.get()); + Ui::ResizeFitChild( + this, + _loading.get(), + st::settingsBlockedHeightMin); } _controller->session().api().blockedPeers().slice( @@ -77,7 +80,7 @@ QPointer Blocked::createPinnedToTop(not_null parent) { content, tr::lng_blocked_list_add(), st::settingsButtonActive, - { &st::menuIconBlockSettings, IconType::Round, &st::transparent } + { &st::menuIconBlockSettings } )->addClickHandler([=] { BlockedBoxController::BlockNewPeer(_controller); }); @@ -201,7 +204,30 @@ void Blocked::setupContent() { AddSkip(content, st::settingsBlockedListIconPadding.top()); } - Ui::ResizeFitChild(this, _container); + // We want minimal height to be the same no matter if subtitle + // is visible or not, so minimal height isn't a constant here. +// Ui::ResizeFitChild(this, _container, st::settingsBlockedHeightMin); + + widthValue( + ) | rpl::start_with_next([=](int width) { + _container->resizeToWidth(width); + }, _container->lifetime()); + + rpl::combine( + _container->heightValue(), + _emptinessChanges.events_starting_with(true) + ) | rpl::start_with_next([=](int height, bool empty) { + const auto subtitled = !empty || (_countBlocked.current() > 0); + const auto total = st::settingsBlockedHeightMin; + const auto padding = st::settingsSubsectionTitlePadding + + st::settingsBlockedListSubtitleAddPadding; + const auto subtitle = st::settingsSectionSkip + + padding.top() + + st::settingsSubsectionTitle.style.font->height + + padding.bottom(); + const auto min = total - (subtitled ? subtitle : 0); + resize(width(), std::max(height, min)); + }, _container->lifetime()); } void Blocked::checkTotal(int total) { diff --git a/Telegram/SourceFiles/settings/settings_notifications.cpp b/Telegram/SourceFiles/settings/settings_notifications.cpp index 9d4e1ed94..02240041f 100644 --- a/Telegram/SourceFiles/settings/settings_notifications.cpp +++ b/Telegram/SourceFiles/settings/settings_notifications.cpp @@ -8,8 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_notifications.h" #include "settings/settings_common.h" +#include "settings/settings_notifications_type.h" +#include "ui/boxes/confirm_box.h" #include "ui/controls/chat_service_checkbox.h" #include "ui/effects/animations.h" +#include "ui/text/text_utilities.h" #include "ui/wrap/vertical_layout.h" #include "ui/wrap/slide_wrap.h" #include "ui/widgets/box_content_divider.h" @@ -140,6 +143,160 @@ private: }; +void AddTypeButton( + not_null container, + not_null controller, + Data::DefaultNotify type, + Fn showOther) { + using Type = Data::DefaultNotify; + auto label = [&] { + switch (type) { + case Type::User: return tr::lng_notification_private_chats(); + case Type::Group: return tr::lng_notification_groups(); + case Type::Broadcast: return tr::lng_notification_channels(); + } + Unexpected("Type value in AddTypeButton."); + }(); + const auto icon = [&] { + switch (type) { + case Type::User: return &st::menuIconProfile; + case Type::Group: return &st::menuIconGroups; + case Type::Broadcast: return &st::menuIconChannel; + } + Unexpected("Type value in AddTypeButton."); + }(); + const auto button = AddButton( + container, + std::move(label), + st::settingsNotificationType, + { icon }); + button->setClickedCallback([=] { + showOther(NotificationsTypeId(type)); + }); + + const auto session = &controller->session(); + const auto settings = &session->data().notifySettings(); + const auto &st = st::settingsNotificationType; + auto status = rpl::combine( + NotificationsEnabledForTypeValue(session, type), + rpl::single( + type + ) | rpl::then(settings->exceptionsUpdates( + ) | rpl::filter(rpl::mappers::_1 == type)) + ) | rpl::map([=](bool enabled, const auto &) { + const auto count = int(settings->exceptions(type).size()); + return !count + ? tr::lng_notification_click_to_change() + : (enabled + ? tr::lng_notification_on + : tr::lng_notification_off)( + lt_exceptions, + tr::lng_notification_exceptions( + lt_count, + rpl::single(float64(count)))); + }) | rpl::flatten_latest(); + const auto details = Ui::CreateChild( + button.get(), + std::move(status), + st::settingsNotificationTypeDetails); + details->show(); + details->moveToLeft( + st.padding.left(), + st.padding.top() + st.height - details->height()); + details->setAttribute(Qt::WA_TransparentForMouseEvents); + + const auto toggleButton = Ui::CreateChild( + container.get(), + nullptr, + st); + const auto checkView = button->lifetime().make_state( + st.toggle, + NotificationsEnabledForType(session, type), + [=] { toggleButton->update(); }); + + const auto separator = Ui::CreateChild(container.get()); + separator->paintRequest( + ) | rpl::start_with_next([=, bg = st.textBgOver] { + auto p = QPainter(separator); + p.fillRect(separator->rect(), bg); + }, separator->lifetime()); + const auto separatorHeight = st.height - 2 * st.toggle.border; + button->geometryValue( + ) | rpl::start_with_next([=](const QRect &r) { + const auto w = st::rightsButtonToggleWidth; + toggleButton->setGeometry( + r.x() + r.width() - w, + r.y(), + w, + r.height()); + separator->setGeometry( + toggleButton->x() - st::lineWidth, + r.y() + (r.height() - separatorHeight) / 2, + st::lineWidth, + separatorHeight); + }, toggleButton->lifetime()); + + const auto checkWidget = Ui::CreateChild(toggleButton); + checkWidget->resize(checkView->getSize()); + checkWidget->paintRequest( + ) | rpl::start_with_next([=] { + auto p = QPainter(checkWidget); + checkView->paint(p, 0, 0, checkWidget->width()); + }, checkWidget->lifetime()); + toggleButton->sizeValue( + ) | rpl::start_with_next([=](const QSize &s) { + checkWidget->moveToRight( + st.toggleSkip, + (s.height() - checkWidget->height()) / 2); + }, toggleButton->lifetime()); + + const auto toggle = crl::guard(toggleButton, [=] { + const auto enabled = !checkView->checked(); + checkView->setChecked(enabled, anim::type::normal); + settings->defaultUpdate(type, Data::MuteValue{ + .unmute = enabled, + .forever = !enabled, + }); + }); + toggleButton->clicks( + ) | rpl::start_with_next([=] { + const auto count = int(settings->exceptions(type).size()); + if (!count) { + toggle(); + } else { + controller->show(Box([=](not_null box) { + const auto phrase = [&] { + switch (type) { + case Type::User: + return tr::lng_notification_about_private_chats; + case Type::Group: + return tr::lng_notification_about_groups; + case Type::Broadcast: + return tr::lng_notification_about_channels; + } + Unexpected("Type in AddTypeButton."); + }(); + Ui::ConfirmBox(box, { + .text = phrase( + lt_count, + rpl::single(float64(count)), + Ui::Text::RichLangValue), + .confirmed = [=](auto close) { toggle(); close(); }, + .confirmText = tr::lng_box_ok(), + .title = tr::lng_notification_exceptions_title(), + .inform = true, + }); + box->addLeftButton( + tr::lng_notification_exceptions_view(), + [=] { + box->closeBox(); + showOther(NotificationsTypeId(type)); + }); + })); + } + }, toggleButton->lifetime()); +} + NotificationsCount::NotificationsCount( QWidget *parent, not_null controller) @@ -817,15 +974,16 @@ void SetupMultiAccountNotifications( void SetupNotificationsContent( not_null controller, - not_null container) { + not_null container, + Fn showOther) { using namespace rpl::mappers; - AddSkip(container); + AddSkip(container, st::settingsPrivacySkip); using NotifyView = Core::Settings::NotifyView; SetupMultiAccountNotifications(controller, container); - AddSubsectionTitle(container, tr::lng_settings_notify_title()); + AddSubsectionTitle(container, tr::lng_settings_notify_global()); const auto session = &controller->session(); const auto checkbox = [&]( @@ -871,41 +1029,15 @@ void SetupNotificationsContent( flashbounceToggles->events_starting_with( settings.flashBounceNotify())); - const auto soundLabel = container->lifetime( - ).make_state>(); - const auto soundValue = [=] { - const auto owner = &controller->session().data(); - const auto &settings = owner->notifySettings().defaultSettings( - Data::DefaultNotify::User); - return !Core::App().settings().soundNotify() - ? Data::NotifySound{ .none = true } - : settings.sound().value_or(Data::NotifySound()); + const auto soundAllowed = container->lifetime( + ).make_state>(); + const auto allowed = [=] { + return Core::App().settings().soundNotify(); }; - const auto label = [=] { - const auto now = soundValue(); - const auto owner = &controller->session().data(); - return now.none - ? tr::lng_settings_sound_notify_off(tr::now) - : !now.id - ? tr::lng_ringtones_box_default(tr::now) - : ExtractRingtoneName(owner->document(now.id)); - }; - controller->session().data().notifySettings().defaultUpdates( - Data::DefaultNotify::User - ) | rpl::start_with_next([=] { - soundLabel->fire(label()); - }, container->lifetime()); - controller->session().api().ringtones().listUpdates( - ) | rpl::start_with_next([=] { - soundLabel->fire(label()); - }, container->lifetime()); - - const auto sound = AddButtonWithLabel( - container, - tr::lng_settings_sound_notify(), - soundLabel->events_starting_with(label()), - st::settingsButton, - { &st::menuIconSoundOn }); + const auto sound = addCheckbox( + tr::lng_settings_sound_allowed(), + { &st::menuIconUnmute }, + soundAllowed->events_starting_with(allowed())); AddSkip(container); @@ -924,7 +1056,20 @@ void SetupNotificationsContent( previewWrap->toggle(settings.desktopNotify(), anim::type::instant); previewDivider->toggle(!settings.desktopNotify(), anim::type::instant); + controller->session().data().notifySettings().loadExceptions(); + AddSkip(container, st::notifyPreviewBottomSkip); + AddSubsectionTitle(container, tr::lng_settings_notify_title()); + const auto addType = [&](Data::DefaultNotify type) { + AddTypeButton(container, controller, type, showOther); + }; + addType(Data::DefaultNotify::User); + addType(Data::DefaultNotify::Group); + addType(Data::DefaultNotify::Broadcast); + + AddSkip(container, st::settingsCheckboxesSkip); + AddDivider(container); + AddSkip(container, st::settingsCheckboxesSkip); AddSubsectionTitle(container, tr::lng_settings_events_title()); auto joinSilent = rpl::single( @@ -1045,6 +1190,14 @@ void SetupNotificationsContent( changed(Change::DesktopEnabled); }, desktop->lifetime()); + sound->toggledChanges( + ) | rpl::filter([](bool checked) { + return (checked != Core::App().settings().soundNotify()); + }) | rpl::start_with_next([=](bool checked) { + Core::App().settings().setSoundNotify(checked); + changed(Change::SoundEnabled); + }, sound->lifetime()); + name->checkedChanges( ) | rpl::map([=](bool checked) { if (!checked) { @@ -1077,25 +1230,6 @@ void SetupNotificationsContent( changed(Change::ViewParams); }, preview->lifetime()); - sound->setClickedCallback([=] { - controller->show(Box(RingtonesBox, session, soundValue(), [=]( - Data::NotifySound sound) { - Core::App().settings().setSoundNotify(!sound.none); - if (!sound.none) { - using Type = Data::DefaultNotify; - const auto owner = &controller->session().data(); - auto &settings = owner->notifySettings(); - const auto updateType = [&](Type type) { - settings.defaultUpdate(type, {}, {}, sound); - }; - updateType(Type::User); - updateType(Type::Group); - updateType(Type::Broadcast); - } - changed(Change::SoundEnabled); - })); - }); - flashbounce->toggledChanges( ) | rpl::filter([](bool checked) { return (checked != Core::App().settings().flashBounceNotify()); @@ -1133,7 +1267,7 @@ void SetupNotificationsContent( } else if (change == Change::ViewParams) { // } else if (change == Change::SoundEnabled) { - soundLabel->fire(label()); + soundAllowed->fire(allowed()); } else if (change == Change::FlashBounceEnabled) { flashbounceToggles->fire( Core::App().settings().flashBounceNotify()); @@ -1160,8 +1294,9 @@ void SetupNotificationsContent( void SetupNotifications( not_null controller, - not_null container) { - SetupNotificationsContent(controller, container); + not_null container, + Fn showOther) { + SetupNotificationsContent(controller, container, std::move(showOther)); } } // namespace @@ -1177,11 +1312,17 @@ rpl::producer Notifications::title() { return tr::lng_settings_section_notify(); } +rpl::producer Notifications::sectionShowOther() { + return _showOther.events(); +} + void Notifications::setupContent( not_null controller) { const auto content = Ui::CreateChild(this); - SetupNotifications(controller, content); + SetupNotifications(controller, content, [=](Type type) { + _showOther.fire_copy(type); + }); Ui::ResizeFitChild(this, content); } diff --git a/Telegram/SourceFiles/settings/settings_notifications.h b/Telegram/SourceFiles/settings/settings_notifications.h index d895e20f2..d75318230 100644 --- a/Telegram/SourceFiles/settings/settings_notifications.h +++ b/Telegram/SourceFiles/settings/settings_notifications.h @@ -19,9 +19,13 @@ public: [[nodiscard]] rpl::producer title() override; + rpl::producer sectionShowOther() override; + private: void setupContent(not_null controller); + rpl::event_stream _showOther; + }; } // namespace Settings diff --git a/Telegram/SourceFiles/settings/settings_notifications_type.cpp b/Telegram/SourceFiles/settings/settings_notifications_type.cpp new file mode 100644 index 000000000..5f394face --- /dev/null +++ b/Telegram/SourceFiles/settings/settings_notifications_type.cpp @@ -0,0 +1,633 @@ +/* +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 "settings/settings_notifications_type.h" + +#include "api/api_ringtones.h" +#include "apiwrap.h" +#include "base/unixtime.h" +#include "boxes/ringtones_box.h" +#include "boxes/peer_list_box.h" +#include "boxes/peer_list_controllers.h" +#include "data/notify/data_notify_settings.h" +#include "data/data_changes.h" +#include "data/data_peer.h" +#include "data/data_session.h" +#include "history/history.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "menu/menu_mute.h" +#include "ui/boxes/confirm_box.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/popup_menu.h" +#include "ui/wrap/slide_wrap.h" +#include "ui/wrap/vertical_layout.h" +#include "window/window_session_controller.h" +#include "styles/style_layers.h" +#include "styles/style_menu_icons.h" +#include "styles/style_settings.h" + +namespace Settings { +namespace { + +using Notify = Data::DefaultNotify; + +class AddExceptionBoxController final + : public ChatsListBoxController + , public base::has_weak_ptr { +public: + AddExceptionBoxController( + not_null session, + Notify type, + Fn)> done); + + Main::Session &session() const override; + void rowClicked(not_null row) override; + base::unique_qptr rowContextMenu( + QWidget *parent, + not_null row) override; + +private: + void prepareViewHook() override; + std::unique_ptr createRow(not_null history) override; + + const not_null _session; + const Notify _type; + const Fn)> _done; + + base::unique_qptr _menu; + PeerData *_lastClickedPeer = nullptr; + + rpl::lifetime _lifetime; + +}; + +class ExceptionsController final : public PeerListController { +public: + ExceptionsController( + not_null window, + Notify type); + + Main::Session &session() const override; + void prepare() override; + void rowClicked(not_null row) override; + base::unique_qptr rowContextMenu( + QWidget *parent, + not_null row) override; + void rowRightActionClicked(not_null row) override; + void loadMoreRows() override; + + void bringToTop(not_null peer); + + [[nodiscard]] rpl::producer countValue() const; + +private: + void refreshRows(); + bool appendRow(not_null peer); + std::unique_ptr createRow(not_null peer) const; + void refreshStatus(not_null row) const; + + void sort(); + + const not_null _window; + const Notify _type; + + base::unique_qptr _menu; + + base::flat_map, int> _topOrdered; + int _topOrder = 0; + + rpl::variable _count; + + rpl::lifetime _lifetime; + +}; + +AddExceptionBoxController::AddExceptionBoxController( + not_null session, + Notify type, + Fn)> done) +: ChatsListBoxController(session) +, _session(session) +, _type(type) +, _done(std::move(done)) { +} + +Main::Session &AddExceptionBoxController::session() const { + return *_session; +} + +void AddExceptionBoxController::prepareViewHook() { + delegate()->peerListSetTitle(tr::lng_notification_exceptions_add()); + + _session->changes().peerUpdates( + Data::PeerUpdate::Flag::Notifications + ) | rpl::filter([=](const Data::PeerUpdate &update) { + return update.peer == _lastClickedPeer; + }) | rpl::start_with_next([=] { + if (const auto onstack = _done) { + onstack(_lastClickedPeer); + } + }, _lifetime); +} + +void AddExceptionBoxController::rowClicked(not_null row) { + delegate()->peerListShowRowMenu(row, true); +} + +base::unique_qptr AddExceptionBoxController::rowContextMenu( + QWidget *parent, + not_null row) { + const auto peer = row->peer(); + auto result = base::make_unique_q( + parent, + st::popupMenuWithIcons); + + MuteMenu::FillMuteMenu( + result.get(), + peer->owner().history(peer), + delegate()->peerListUiShow()); + + // First clear _menu value, so that we don't check row positions yet. + base::take(_menu); + + // Here unique_qptr is used like a shared pointer, where + // not the last destroyed pointer destroys the object, but the first. + _menu = base::unique_qptr(result.get()); + _menu->setDestroyedCallback(crl::guard(this, [=] { + _lastClickedPeer = nullptr; + })); + _lastClickedPeer = peer; + + return result; +} + +auto AddExceptionBoxController::createRow(not_null history) +-> std::unique_ptr { + if (Data::DefaultNotifyType(history->peer) != _type + || history->peer->isSelf() + || history->peer->isRepliesChat()) { + return nullptr; + } + return std::make_unique(history); +} + +ExceptionsController::ExceptionsController( + not_null window, + Notify type) +: _window(window) +, _type(type) { +} + +Main::Session &ExceptionsController::session() const { + return _window->session(); +} + +void ExceptionsController::prepare() { + refreshRows(); + + session().data().notifySettings().exceptionsUpdates( + ) | rpl::filter(rpl::mappers::_1 == _type) | rpl::start_with_next([=] { + refreshRows(); + }, lifetime()); + + session().changes().peerUpdates( + Data::PeerUpdate::Flag::Notifications + ) | rpl::start_with_next([=](const Data::PeerUpdate &update) { + const auto peer = update.peer; + if (const auto row = delegate()->peerListFindRow(peer->id.value)) { + if (peer->notify().muteUntil().has_value()) { + refreshStatus(row); + } else { + delegate()->peerListRemoveRow(row); + delegate()->peerListRefreshRows(); + _count = delegate()->peerListFullRowsCount(); + } + } + }, _lifetime); +} + +void ExceptionsController::loadMoreRows() { +} + +void ExceptionsController::bringToTop(not_null peer) { + _topOrdered[peer] = ++_topOrder; + if (delegate()->peerListFindRow(peer->id.value)) { + sort(); + } +} + +rpl::producer ExceptionsController::countValue() const { + return _count.value(); +} + +void ExceptionsController::rowClicked(not_null row) { + delegate()->peerListShowRowMenu(row, true); +} + +void ExceptionsController::rowRightActionClicked( + not_null row) { + session().data().notifySettings().resetToDefault(row->peer()); +} + +void ExceptionsController::refreshRows() { + auto seen = base::flat_set>(); + const auto &list = session().data().notifySettings().exceptions(_type); + auto removed = false, added = false; + auto already = delegate()->peerListFullRowsCount(); + seen.reserve(std::min(int(list.size()), already)); + for (auto i = 0; i != already;) { + const auto row = delegate()->peerListRowAt(i); + if (list.contains(row->peer())) { + seen.emplace(row->peer()); + ++i; + } else { + delegate()->peerListRemoveRow(row); + --already; + removed = true; + } + } + for (const auto &peer : list) { + if (!seen.contains(peer)) { + appendRow(peer); + added = true; + } + } + if (added || removed) { + if (added) { + sort(); + } + delegate()->peerListRefreshRows(); + _count = delegate()->peerListFullRowsCount(); + } +} + +base::unique_qptr ExceptionsController::rowContextMenu( + QWidget *parent, + not_null row) { + const auto peer = row->peer(); + auto result = base::make_unique_q( + parent, + st::popupMenuWithIcons); + + result->addAction( + (peer->isUser() + ? tr::lng_context_view_profile + : peer->isBroadcast() + ? tr::lng_context_view_channel + : tr::lng_context_view_group)(tr::now), + crl::guard(_window, [window = _window.get(), peer] { + window->showPeerInfo(peer); + }), + (peer->isUser() ? &st::menuIconProfile : &st::menuIconInfo)); + result->addSeparator(); + + MuteMenu::FillMuteMenu( + result.get(), + peer->owner().history(peer), + _window->uiShow()); + + // First clear _menu value, so that we don't check row positions yet. + base::take(_menu); + + // Here unique_qptr is used like a shared pointer, where + // not the last destroyed pointer destroys the object, but the first. + _menu = base::unique_qptr(result.get()); + + return result; +} + +bool ExceptionsController::appendRow(not_null peer) { + delegate()->peerListAppendRow(createRow(peer)); + return true; +} + +std::unique_ptr ExceptionsController::createRow( + not_null peer) const { + auto row = std::make_unique(peer); + row->setActionLink(tr::lng_notification_exceptions_remove(tr::now)); + refreshStatus(row.get()); + return row; +} + +void ExceptionsController::refreshStatus(not_null row) const { + const auto peer = row->peer(); + const auto status = peer->owner().notifySettings().isMuted(peer) + ? tr::lng_notification_exceptions_muted(tr::now) + : tr::lng_notification_exceptions_unmuted(tr::now); + row->setCustomStatus(status); +} + +void ExceptionsController::sort() { + auto keys = base::flat_map(); + keys.reserve(delegate()->peerListFullRowsCount()); + const auto length = QString::number(_topOrder).size(); + const auto key = [&](const PeerListRow &row) { + const auto id = row.id(); + const auto i = keys.find(id); + if (i != end(keys)) { + return i->second; + } + const auto peer = row.peer(); + const auto top = _topOrdered.find(peer); + if (top != end(_topOrdered)) { + const auto order = _topOrder - top->second; + return keys.emplace( + id, + u"0%1"_q.arg(order, length, 10, QChar('0'))).first->second; + } + const auto history = peer->owner().history(peer); + return keys.emplace( + id, + '1' + history->chatListNameSortKey()).first->second; + }; + const auto predicate = [&](const PeerListRow &a, const PeerListRow &b) { + return (key(a).compare(key(b)) < 0); + }; + delegate()->peerListSortRows(predicate); +} + +template +[[nodiscard]] Type Id() { + return &NotificationsTypeMetaImplementation::Meta; +} + +[[nodiscard]] rpl::producer Title(Notify type) { + switch (type) { + case Notify::User: return tr::lng_notification_title_private_chats(); + case Notify::Group: return tr::lng_notification_title_groups(); + case Notify::Broadcast: return tr::lng_notification_title_channels(); + } + Unexpected("Type in Title."); +} + +void SetupChecks( + not_null container, + not_null controller, + Notify type) { + AddSubsectionTitle(container, Title(type)); + + const auto session = &controller->session(); + const auto settings = &session->data().notifySettings(); + + const auto enabled = container->add( + CreateButton( + container, + tr::lng_notification_enable(), + st::settingsButton, + { &st::menuIconNotifications })); + enabled->toggleOn( + NotificationsEnabledForTypeValue(session, type), + true); + + enabled->setAcceptBoth(); + MuteMenu::SetupMuteMenu( + enabled, + enabled->clicks( + ) | rpl::filter([=](Qt::MouseButton button) { + if (button == Qt::RightButton) { + return true; + } else if (settings->isMuted(type)) { + settings->defaultUpdate(type, { .unmute = true }); + return false; + } else { + return true; + } + }) | rpl::to_empty, + [=] { return MuteMenu::DefaultDescriptor(session, type); }, + controller->uiShow()); + + const auto soundWrap = container->add( + object_ptr>( + container, + object_ptr(container))); + soundWrap->toggleOn(enabled->toggledValue()); + soundWrap->finishAnimating(); + + const auto soundInner = soundWrap->entity(); + const auto soundValue = [=] { + const auto sound = settings->defaultSettings(type).sound(); + return !sound || !sound->none; + }; + const auto sound = soundInner->add( + CreateButton( + soundInner, + tr::lng_notification_sound(), + st::settingsButton, + { &st::menuIconUnmute })); + sound->toggleOn(rpl::single( + soundValue() + ) | rpl::then(settings->defaultUpdates( + type + ) | rpl::map([=] { return soundValue(); }))); + + const auto toneWrap = soundInner->add( + object_ptr>( + container, + object_ptr(container))); + toneWrap->toggleOn(sound->toggledValue()); + toneWrap->finishAnimating(); + + const auto toneInner = toneWrap->entity(); + const auto toneLabel = toneInner->lifetime( + ).make_state>(); + const auto toneValue = [=] { + const auto sound = settings->defaultSettings(type).sound(); + return sound.value_or(Data::NotifySound()); + }; + const auto label = [=] { + const auto now = toneValue(); + return !now.id + ? tr::lng_ringtones_box_default(tr::now) + : ExtractRingtoneName(session->data().document(now.id)); + }; + settings->defaultUpdates( + Notify::User + ) | rpl::start_with_next([=] { + toneLabel->fire(label()); + }, toneInner->lifetime()); + session->api().ringtones().listUpdates( + ) | rpl::start_with_next([=] { + toneLabel->fire(label()); + }, toneInner->lifetime()); + + const auto tone = AddButtonWithLabel( + toneInner, + tr::lng_notification_tone(), + toneLabel->events_starting_with(label()), + st::settingsButton, + { &st::menuIconSoundOn }); + + enabled->toggledValue( + ) | rpl::filter([=](bool value) { + return (value != NotificationsEnabledForType(session, type)); + }) | rpl::start_with_next([=](bool value) { + settings->defaultUpdate(type, Data::MuteValue{ + .unmute = value, + .forever = !value, + }); + }, sound->lifetime()); + + sound->toggledValue( + ) | rpl::filter([=](bool enabled) { + const auto sound = settings->defaultSettings(type).sound(); + return (!sound || !sound->none) != enabled; + }) | rpl::start_with_next([=](bool enabled) { + const auto value = Data::NotifySound{ .none = !enabled }; + settings->defaultUpdate(type, {}, {}, value); + }, sound->lifetime()); + + tone->setClickedCallback([=] { + controller->show(Box(RingtonesBox, session, toneValue(), [=]( + Data::NotifySound sound) { + settings->defaultUpdate(type, {}, {}, sound); + })); + }); +} + +void SetupExceptions( + not_null container, + not_null window, + Notify type) { + const auto add = AddButton( + container, + tr::lng_notification_exceptions_add(), + st::settingsButtonActive, + { &st::menuIconInviteSettings }); + + auto controller = std::make_unique(window, type); + controller->setStyleOverrides(&st::settingsBlockedList); + const auto content = container->add( + object_ptr(container, controller.get())); + + struct State { + std::unique_ptr controller; + std::unique_ptr delegate; + }; + const auto state = content->lifetime().make_state(); + state->controller = std::move(controller); + state->delegate = std::make_unique(); + + state->delegate->setContent(content); + state->controller->setDelegate(state->delegate.get()); + + add->setClickedCallback([=] { + const auto box = std::make_shared>(); + const auto done = [=](not_null peer) { + state->controller->bringToTop(peer); + if (*box) { + (*box)->closeBox(); + } + }; + auto controller = std::make_unique( + &window->session(), + type, + crl::guard(content, done)); + auto initBox = [=](not_null box) { + box->addButton(tr::lng_cancel(), [box] { box->closeBox(); }); + }; + *box = window->show( + Box(std::move(controller), std::move(initBox))); + }); + + const auto wrap = container->add( + object_ptr>( + container, + CreateButton( + container, + tr::lng_notification_exceptions_clear(), + st::settingsAttentionButtonWithIcon, + { &st::menuIconDeleteAttention }))); + wrap->entity()->setClickedCallback([=] { + const auto clear = [=](Fn close) { + window->session().data().notifySettings().clearExceptions(type); + close(); + }; + window->show(Ui::MakeConfirmBox({ + .text = tr::lng_notification_exceptions_clear_sure(), + .confirmed = clear, + .confirmText = tr::lng_notification_exceptions_clear_button(), + .confirmStyle = &st::attentionBoxButton, + .title = tr::lng_notification_exceptions_clear(), + })); + }); + wrap->toggleOn( + state->controller->countValue() | rpl::map(rpl::mappers::_1 > 1), + anim::type::instant); +} + +} // namespace + +Type NotificationsTypeId(Notify type) { + switch (type) { + case Notify::User: return Id(); + case Notify::Group: return Id(); + case Notify::Broadcast: return Id(); + } + Unexpected("Type in NotificationTypeId."); +} + +NotificationsType::NotificationsType( + QWidget *parent, + not_null controller, + Notify type) +: AbstractSection(parent) +, _type(type) { + setupContent(controller); +} + +rpl::producer NotificationsType::title() { + switch (_type) { + case Notify::User: return tr::lng_notification_private_chats(); + case Notify::Group: return tr::lng_notification_groups(); + case Notify::Broadcast: return tr::lng_notification_channels(); + } + Unexpected("Type in NotificationsType."); +} + +Type NotificationsType::id() const { + return NotificationsTypeId(_type); +} + +void NotificationsType::setupContent( + not_null controller) { + const auto container = Ui::CreateChild(this); + + AddSkip(container, st::settingsPrivacySkip); + SetupChecks(container, controller, _type); + + AddSkip(container); + AddDivider(container); + AddSkip(container); + + SetupExceptions(container, controller, _type); + + Ui::ResizeFitChild(this, container); +} + +bool NotificationsEnabledForType( + not_null session, + Notify type) { + const auto settings = &session->data().notifySettings(); + const auto until = settings->defaultSettings(type).muteUntil(); + return until && (*until <= base::unixtime::now()); +} + +rpl::producer NotificationsEnabledForTypeValue( + not_null session, + Notify type) { + const auto settings = &session->data().notifySettings(); + return rpl::single( + rpl::empty + ) | rpl::then( + settings->defaultUpdates(type) + ) | rpl::map([=] { + return NotificationsEnabledForType(session, type); + }); +} + +} // namespace Settings diff --git a/Telegram/SourceFiles/settings/settings_notifications_type.h b/Telegram/SourceFiles/settings/settings_notifications_type.h new file mode 100644 index 000000000..59014f0d5 --- /dev/null +++ b/Telegram/SourceFiles/settings/settings_notifications_type.h @@ -0,0 +1,62 @@ +/* +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 "settings/settings_common.h" +#include "data/notify/data_notify_settings.h" + +namespace Data { +enum class DefaultNotify; +} // namespace Data + +namespace Settings { + +class NotificationsType : public AbstractSection { +public: + NotificationsType( + QWidget *parent, + not_null controller, + Data::DefaultNotify type); + + [[nodiscard]] rpl::producer title() override; + + [[nodiscard]] Type id() const final override; + +private: + void setupContent(not_null controller); + + Data::DefaultNotify _type; + +}; + +template +struct NotificationsTypeMetaImplementation : SectionMeta { + object_ptr create( + not_null parent, + not_null controller + ) const final override { + return object_ptr(parent, controller, kType); + } + + [[nodiscard]] static not_null Meta() { + static NotificationsTypeMetaImplementation result; + return &result; + } +}; + +[[nodiscard]] Type NotificationsTypeId(Data::DefaultNotify type); + +[[nodiscard]] bool NotificationsEnabledForType( + not_null session, + Data::DefaultNotify type); + +[[nodiscard]] rpl::producer NotificationsEnabledForTypeValue( + not_null session, + Data::DefaultNotify type); + +} // namespace Settings diff --git a/Telegram/SourceFiles/ui/image/image.cpp b/Telegram/SourceFiles/ui/image/image.cpp index 17f95e354..14cefdb5a 100644 --- a/Telegram/SourceFiles/ui/image/image.cpp +++ b/Telegram/SourceFiles/ui/image/image.cpp @@ -41,260 +41,6 @@ namespace { } // namespace -QByteArray ExpandInlineBytes(const QByteArray &bytes) { - if (bytes.size() < 3 || bytes[0] != '\x01') { - return QByteArray(); - } - const char header[] = "\xff\xd8\xff\xe0\x00\x10\x4a\x46\x49" - "\x46\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00\xff\xdb\x00\x43\x00\x28\x1c" - "\x1e\x23\x1e\x19\x28\x23\x21\x23\x2d\x2b\x28\x30\x3c\x64\x41\x3c\x37\x37" - "\x3c\x7b\x58\x5d\x49\x64\x91\x80\x99\x96\x8f\x80\x8c\x8a\xa0\xb4\xe6\xc3" - "\xa0\xaa\xda\xad\x8a\x8c\xc8\xff\xcb\xda\xee\xf5\xff\xff\xff\x9b\xc1\xff" - "\xff\xff\xfa\xff\xe6\xfd\xff\xf8\xff\xdb\x00\x43\x01\x2b\x2d\x2d\x3c\x35" - "\x3c\x76\x41\x41\x76\xf8\xa5\x8c\xa5\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8" - "\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8" - "\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8" - "\xf8\xf8\xf8\xf8\xf8\xff\xc0\x00\x11\x08\x00\x00\x00\x00\x03\x01\x22\x00" - "\x02\x11\x01\x03\x11\x01\xff\xc4\x00\x1f\x00\x00\x01\x05\x01\x01\x01\x01" - "\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08" - "\x09\x0a\x0b\xff\xc4\x00\xb5\x10\x00\x02\x01\x03\x03\x02\x04\x03\x05\x05" - "\x04\x04\x00\x00\x01\x7d\x01\x02\x03\x00\x04\x11\x05\x12\x21\x31\x41\x06" - "\x13\x51\x61\x07\x22\x71\x14\x32\x81\x91\xa1\x08\x23\x42\xb1\xc1\x15\x52" - "\xd1\xf0\x24\x33\x62\x72\x82\x09\x0a\x16\x17\x18\x19\x1a\x25\x26\x27\x28" - "\x29\x2a\x34\x35\x36\x37\x38\x39\x3a\x43\x44\x45\x46\x47\x48\x49\x4a\x53" - "\x54\x55\x56\x57\x58\x59\x5a\x63\x64\x65\x66\x67\x68\x69\x6a\x73\x74\x75" - "\x76\x77\x78\x79\x7a\x83\x84\x85\x86\x87\x88\x89\x8a\x92\x93\x94\x95\x96" - "\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xb2\xb3\xb4\xb5\xb6" - "\xb7\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xd2\xd3\xd4\xd5\xd6" - "\xd7\xd8\xd9\xda\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf1\xf2\xf3\xf4" - "\xf5\xf6\xf7\xf8\xf9\xfa\xff\xc4\x00\x1f\x01\x00\x03\x01\x01\x01\x01\x01" - "\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08" - "\x09\x0a\x0b\xff\xc4\x00\xb5\x11\x00\x02\x01\x02\x04\x04\x03\x04\x07\x05" - "\x04\x04\x00\x01\x02\x77\x00\x01\x02\x03\x11\x04\x05\x21\x31\x06\x12\x41" - "\x51\x07\x61\x71\x13\x22\x32\x81\x08\x14\x42\x91\xa1\xb1\xc1\x09\x23\x33" - "\x52\xf0\x15\x62\x72\xd1\x0a\x16\x24\x34\xe1\x25\xf1\x17\x18\x19\x1a\x26" - "\x27\x28\x29\x2a\x35\x36\x37\x38\x39\x3a\x43\x44\x45\x46\x47\x48\x49\x4a" - "\x53\x54\x55\x56\x57\x58\x59\x5a\x63\x64\x65\x66\x67\x68\x69\x6a\x73\x74" - "\x75\x76\x77\x78\x79\x7a\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x92\x93\x94" - "\x95\x96\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xb2\xb3\xb4" - "\xb5\xb6\xb7\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xd2\xd3\xd4" - "\xd5\xd6\xd7\xd8\xd9\xda\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf2\xf3\xf4" - "\xf5\xf6\xf7\xf8\xf9\xfa\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00" - "\x3f\x00"; - const char footer[] = "\xff\xd9"; - auto real = QByteArray(header, sizeof(header) - 1); - real[164] = bytes[1]; - real[166] = bytes[2]; - return real - + bytes.mid(3) - + QByteArray::fromRawData(footer, sizeof(footer) - 1); -} - -QImage FromInlineBytes(const QByteArray &bytes) { - return Read({ .content = ExpandInlineBytes(bytes) }).image; -} - -// Thanks TDLib for code. -QByteArray ExpandPathInlineBytes(const QByteArray &bytes) { - auto result = QByteArray(); - result.reserve(3 * (bytes.size() + 1)); - result.append('M'); - for (unsigned char c : bytes) { - if (c >= 128 + 64) { - result.append("AACAAAAHAAALMAAAQASTAVAAAZ" - "aacaaaahaaalmaaaqastava.az0123456789-,"[c - 128 - 64]); - } else { - if (c >= 128) { - result.append(','); - } else if (c >= 64) { - result.append('-'); - } - //char buffer[3] = { 0 }; // Unavailable on macOS < 10.15. - //std::to_chars(buffer, buffer + 3, (c & 63)); - //result.append(buffer); - result.append(QByteArray::number(c & 63)); - } - } - result.append('z'); - return result; -} - -QPainterPath PathFromInlineBytes(const QByteArray &bytes) { - if (bytes.isEmpty()) { - return QPainterPath(); - } - const auto expanded = ExpandPathInlineBytes(bytes); - const auto path = expanded.data(); // Allows checking for '\0' by index. - auto position = 0; - - const auto isAlpha = [](char c) { - c |= 0x20; - return 'a' <= c && c <= 'z'; - }; - const auto isDigit = [](char c) { - return '0' <= c && c <= '9'; - }; - const auto skipCommas = [&] { - while (path[position] == ',') { - ++position; - } - }; - const auto getNumber = [&] { - skipCommas(); - auto sign = 1; - if (path[position] == '-') { - sign = -1; - ++position; - } - double res = 0; - while (isDigit(path[position])) { - res = res * 10 + path[position++] - '0'; - } - if (path[position] == '.') { - ++position; - double mul = 0.1; - while (isDigit(path[position])) { - res += (path[position] - '0') * mul; - mul *= 0.1; - ++position; - } - } - return sign * res; - }; - - auto result = QPainterPath(); - auto x = 0.; - auto y = 0.; - while (path[position] != '\0') { - skipCommas(); - if (path[position] == '\0') { - break; - } - - while (path[position] == 'm' || path[position] == 'M') { - auto command = path[position++]; - do { - if (command == 'm') { - x += getNumber(); - y += getNumber(); - } else { - x = getNumber(); - y = getNumber(); - } - skipCommas(); - } while (path[position] != '\0' && !isAlpha(path[position])); - } - - auto xStart = x; - auto yStart = y; - result.moveTo(xStart, yStart); - auto haveLastEndControlPoint = false; - auto xLastEndControlPoint = 0.; - auto yLastEndControlPoint = 0.; - auto isClosed = false; - auto command = '-'; - while (!isClosed) { - skipCommas(); - if (path[position] == '\0') { - LOG(("SVG Error: Receive unclosed path: %1" - ).arg(QString::fromLatin1(path))); - return QPainterPath(); - } - if (isAlpha(path[position])) { - command = path[position++]; - } - switch (command) { - case 'l': - case 'L': - case 'h': - case 'H': - case 'v': - case 'V': - if (command == 'l' || command == 'h') { - x += getNumber(); - } else if (command == 'L' || command == 'H') { - x = getNumber(); - } - if (command == 'l' || command == 'v') { - y += getNumber(); - } else if (command == 'L' || command == 'V') { - y = getNumber(); - } - result.lineTo(x, y); - haveLastEndControlPoint = false; - break; - case 'C': - case 'c': - case 'S': - case 's': { - auto xStartControlPoint = 0.; - auto yStartControlPoint = 0.; - if (command == 'S' || command == 's') { - if (haveLastEndControlPoint) { - xStartControlPoint = 2 * x - xLastEndControlPoint; - yStartControlPoint = 2 * y - yLastEndControlPoint; - } else { - xStartControlPoint = x; - yStartControlPoint = y; - } - } else { - xStartControlPoint = getNumber(); - yStartControlPoint = getNumber(); - if (command == 'c') { - xStartControlPoint += x; - yStartControlPoint += y; - } - } - - xLastEndControlPoint = getNumber(); - yLastEndControlPoint = getNumber(); - if (command == 'c' || command == 's') { - xLastEndControlPoint += x; - yLastEndControlPoint += y; - } - haveLastEndControlPoint = true; - - if (command == 'c' || command == 's') { - x += getNumber(); - y += getNumber(); - } else { - x = getNumber(); - y = getNumber(); - } - result.cubicTo( - xStartControlPoint, - yStartControlPoint, - xLastEndControlPoint, - yLastEndControlPoint, - x, - y); - break; - } - case 'm': - case 'M': - --position; - [[fallthrough]]; - case 'z': - case 'Z': - if (x != xStart || y != yStart) { - x = xStart; - y = yStart; - result.lineTo(x, y); - } - isClosed = true; - break; - default: - LOG(("SVG Error: Receive invalid command %1 at pos %2: %3" - ).arg(command - ).arg(position - ).arg(QString::fromLatin1(path))); - return QPainterPath(); - } - } - } - return result; -} - } // namespace Images Image::Image(const QString &path) diff --git a/Telegram/SourceFiles/ui/image/image.h b/Telegram/SourceFiles/ui/image/image.h index 1735a87a2..61095ce3c 100644 --- a/Telegram/SourceFiles/ui/image/image.h +++ b/Telegram/SourceFiles/ui/image/image.h @@ -11,14 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class QPainterPath; -namespace Images { - -[[nodiscard]] QByteArray ExpandInlineBytes(const QByteArray &bytes); -[[nodiscard]] QImage FromInlineBytes(const QByteArray &bytes); -[[nodiscard]] QPainterPath PathFromInlineBytes(const QByteArray &bytes); - -} // namespace Images - class Image final { public: explicit Image(const QString &path); diff --git a/Telegram/SourceFiles/ui/menu_icons.style b/Telegram/SourceFiles/ui/menu_icons.style index bfb508773..715c6a2fb 100644 --- a/Telegram/SourceFiles/ui/menu_icons.style +++ b/Telegram/SourceFiles/ui/menu_icons.style @@ -173,6 +173,7 @@ menuIconReportAttention: icon {{ "menu/report", menuIconAttentionColor }}; menuIconRestoreAttention: icon {{ "menu/restore", menuIconAttentionColor }}; menuIconBlockSettings: icon {{ "menu/block", windowBgActive }}; +menuIconInviteSettings: icon {{ "menu/invite", windowBgActive }}; playerSpeedSlow: icon {{ "player/speed/audiospeed_menu_0.5", menuIconColor }}; playerSpeedSlowActive: icon {{ "player/speed/audiospeed_menu_0.5", mediaPlayerActiveFg }}; diff --git a/Telegram/SourceFiles/window/notifications_manager.cpp b/Telegram/SourceFiles/window/notifications_manager.cpp index 5a52f4480..c03080c7e 100644 --- a/Telegram/SourceFiles/window/notifications_manager.cpp +++ b/Telegram/SourceFiles/window/notifications_manager.cpp @@ -66,7 +66,15 @@ constexpr auto kSystemAlertDuration = crl::time(0); return result; } -QString TextWithPermanentSpoiler(const TextWithEntities &textWithEntities) { +[[nodiscard]] QString TextWithForwardedChar( + const QString &text, + bool forwarded) { + static const auto result = QString::fromUtf8("\xE2\x9E\xA1\xEF\xB8\x8F"); + return forwarded ? result + text : text; +} + +[[nodiscard]] QString TextWithPermanentSpoiler( + const TextWithEntities &textWithEntities) { auto text = textWithEntities.text; for (const auto &e : textWithEntities.entities) { if (e.type() == EntityType::Spoiler) { @@ -1175,9 +1183,11 @@ void NativeManager::doShowNotification(NotificationFields &&fields) { ? tr::lng_forward_messages(tr::now, lt_count, fields.forwardedCount) : item->groupId() ? tr::lng_in_dlg_album(tr::now) - : TextWithPermanentSpoiler(item->notificationText({ - .spoilerLoginCode = options.spoilerLoginCode, - })); + : TextWithForwardedChar( + TextWithPermanentSpoiler(item->notificationText({ + .spoilerLoginCode = options.spoilerLoginCode, + })), + (fields.forwardedCount == 1)); // #TODO optimize auto userpicView = item->history()->peer->createUserpicView(); diff --git a/Telegram/SourceFiles/window/themes/window_theme_preview.cpp b/Telegram/SourceFiles/window/themes/window_theme_preview.cpp index 8db74f0f2..11878fd98 100644 --- a/Telegram/SourceFiles/window/themes/window_theme_preview.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme_preview.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "window/themes/window_theme_preview.h" +#include "dialogs/dialogs_three_state_icon.h" #include "lang/lang_keys.h" #include "platform/platform_window_title.h" #include "ui/text/text_options.h" @@ -695,9 +696,15 @@ void Generator::paintRow(const Row &row) { auto chatTypeIcon = ([&row]() -> const style::icon * { if (row.type == Row::Type::Group) { - return &(row.active ? st::dialogsChatIconActive : (row.selected ? st::dialogsChatIconOver : st::dialogsChatIcon)); + return &Dialogs::ThreeStateIcon( + st::dialogsChatIcon, + row.active, + row.selected); } else if (row.type == Row::Type::Channel) { - return &(row.active ? st::dialogsChannelIconActive : (row.selected ? st::dialogsChannelIconOver : st::dialogsChannelIcon)); + return &Dialogs::ThreeStateIcon( + st::dialogsChannelIcon, + row.active, + row.selected); } return nullptr; })(); @@ -750,7 +757,10 @@ void Generator::paintRow(const Row &row) { _p->setPen(row.active ? st::dialogsUnreadFgActive[_palette] : (row.selected ? st::dialogsUnreadFgOver[_palette] : st::dialogsUnreadFg[_palette])); _p->drawText(unreadRectLeft + (unreadRectWidth - unreadWidth) / 2, unreadRectTop + textTop + st::dialogsUnreadFont->ascent, counter); } else if (row.pinned) { - auto icon = (row.active ? st::dialogsPinnedIconActive[_palette] : (row.selected ? st::dialogsPinnedIconOver[_palette] : st::dialogsPinnedIcon[_palette])); + auto icon = Dialogs::ThreeStateIcon( + st::dialogsPinnedIcon, + row.active, + row.selected)[_palette]; icon.paint(*_p, x + fullWidth - st.padding.right() - icon.width(), texttop, fullWidth); availableWidth -= icon.width() + st::dialogsUnreadPadding; } @@ -763,9 +773,15 @@ void Generator::paintRow(const Row &row) { auto sendStateIcon = ([&row]() -> const style::icon* { if (row.status == Status::Sent) { - return &(row.active ? st::dialogsSentIconActive : (row.selected ? st::dialogsSentIconOver : st::dialogsSentIcon)); + return &Dialogs::ThreeStateIcon( + st::dialogsSentIcon, + row.active, + row.selected); } else if (row.status == Status::Received) { - return &(row.active ? st::dialogsReceivedIconActive : (row.selected ? st::dialogsReceivedIconOver : st::dialogsReceivedIcon)); + return &Dialogs::ThreeStateIcon( + st::dialogsReceivedIcon, + row.active, + row.selected); } return nullptr; })(); diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 70f7fb8e3..03f4f0ffe 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -10,11 +10,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/add_contact_box.h" #include "boxes/peers/add_bot_to_chat_box.h" #include "boxes/peers/edit_peer_info_box.h" -#include "boxes/peer_list_controllers.h" #include "boxes/delete_messages_box.h" #include "window/window_adaptive.h" #include "window/window_controller.h" -#include "window/main_window.h" #include "window/window_filters_menu.h" #include "info/info_memento.h" #include "info/info_controller.h" @@ -37,7 +35,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "data/data_document.h" #include "data/data_document_media.h" -#include "data/data_document_resolver.h" #include "data/data_changes.h" #include "data/data_group_call.h" #include "data/data_forum.h" @@ -54,13 +51,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/core_settings.h" #include "core/click_handler_types.h" #include "base/unixtime.h" -#include "base/random.h" #include "ui/layers/generic_box.h" #include "ui/text/text_utilities.h" #include "ui/text/format_values.h" // Ui::FormatPhone. #include "ui/delayed_activation.h" #include "ui/chat/attach/attach_bot_webview.h" -#include "ui/chat/message_bubble.h" #include "ui/chat/chat_style.h" #include "ui/chat/chat_theme.h" #include "ui/effects/message_sending_animation_controller.h" diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 9536894ee..7f541cf5e 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -16,7 +16,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_chat_participant_status.h" #include "dialogs/dialogs_key.h" #include "ui/layers/layer_widget.h" -#include "ui/layers/show.h" #include "settings/settings_type.h" #include "window/window_adaptive.h" #include "mtproto/sender.h" @@ -89,7 +88,6 @@ namespace Window { using GifPauseReason = ChatHelpers::PauseReason; using GifPauseReasons = ChatHelpers::PauseReasons; -class MainWindow; class SectionMemento; class Controller; class FiltersMenu; diff --git a/Telegram/ThirdParty/kimageformats b/Telegram/ThirdParty/kimageformats index 63056c52f..b2b677b8a 160000 --- a/Telegram/ThirdParty/kimageformats +++ b/Telegram/ThirdParty/kimageformats @@ -1 +1 @@ -Subproject commit 63056c52f91aa4a34bb0637f1723848dc25a5f87 +Subproject commit b2b677b8a5e4c3cf34790eb990218217bf867c18 diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index 912fa52bf..2c2ac3de2 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -59,7 +59,7 @@ FROM builder AS patches RUN git init patches \ && cd patches \ && git remote add origin {{ GIT }}/desktop-app/patches.git \ - && git fetch --depth=1 origin dbd8a5b00f5a0e7e09b11878b9e46afeb4b593c0 \ + && git fetch --depth=1 origin 2cf975200b9baac520b9b298776e0a20b24e397a \ && git reset --hard FETCH_HEAD \ && rm -rf .git diff --git a/Telegram/build/version b/Telegram/build/version index 112140bad..1b6e6f9ec 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 4009003 +AppVersion 4009004 AppVersionStrMajor 4.9 -AppVersionStrSmall 4.9.3 -AppVersionStr 4.9.3 +AppVersionStrSmall 4.9.4 +AppVersionStr 4.9.4 BetaChannel 0 AlphaVersion 0 -AppVersionOriginal 4.9.3 +AppVersionOriginal 4.9.4 diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 66bac9c85..e446f4b08 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -73,6 +73,7 @@ PRIVATE data/data_subscription_option.h + dialogs/dialogs_three_state_icon.h dialogs/ui/dialogs_stories_list.cpp dialogs/ui/dialogs_stories_list.h diff --git a/Telegram/lib_base b/Telegram/lib_base index 0919789bf..764b55db1 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 0919789bf555c651e32bdbcabc397dbacb6a2545 +Subproject commit 764b55db1a4a8b9d8ba966d7f0fa46c9f384737e diff --git a/Telegram/lib_spellcheck b/Telegram/lib_spellcheck index b94ec0107..c989a9f41 160000 --- a/Telegram/lib_spellcheck +++ b/Telegram/lib_spellcheck @@ -1 +1 @@ -Subproject commit b94ec0107b03f13c28f336642411c6df21107a63 +Subproject commit c989a9f41e5bd8268587af2256efa89327cb3ae0 diff --git a/Telegram/lib_webview b/Telegram/lib_webview index 585056693..5b9092bcb 160000 --- a/Telegram/lib_webview +++ b/Telegram/lib_webview @@ -1 +1 @@ -Subproject commit 5850566934f2f6cae56646c36cb95f85c8a9c752 +Subproject commit 5b9092bcb27a207fed3cb2155bb98db46d7cedfa diff --git a/changelog.txt b/changelog.txt index 0fc4f1217..0c1ef6979 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,9 @@ +4.9.4 (30.08.23) + +- Default private chats / groups / channels notification settings. +- Forwarded / reply-to-a-story icon in chats list message preview. +- Bug fixes and other minor improvements. + 4.9.3 (22.08.23) - Fix audio output on macOS.