Merge tag 'v4.9.4' into dev

# Conflicts:
#	Telegram/Resources/winrc/Telegram.rc
#	Telegram/Resources/winrc/Updater.rc
#	Telegram/SourceFiles/core/version.h
#	Telegram/lib_ui
#	cmake
This commit is contained in:
ZavaruKitsu 2023-09-01 17:20:58 +03:00
commit 96461b91c5
77 changed files with 1921 additions and 933 deletions

View file

@ -1314,6 +1314,8 @@ PRIVATE
settings/settings_main.h settings/settings_main.h
settings/settings_notifications.cpp settings/settings_notifications.cpp
settings/settings_notifications.h settings/settings_notifications.h
settings/settings_notifications_type.cpp
settings/settings_notifications_type.h
settings/settings_power_saving.cpp settings/settings_power_saving.cpp
settings/settings_power_saving.h settings/settings_power_saving.h
settings/settings_premium.cpp settings/settings_premium.cpp

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 772 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -451,6 +451,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_show_from" = "Show notifications from"; "lng_settings_show_from" = "Show notifications from";
"lng_settings_notify_all" = "All accounts"; "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_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_notify_title" = "Notifications for chats";
"lng_settings_desktop_notify" = "Desktop notifications"; "lng_settings_desktop_notify" = "Desktop notifications";
"lng_settings_native_title" = "Native 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_use_native_notifications" = "Use native notifications";
"lng_settings_notifications_position" = "Location on the screen"; "lng_settings_notifications_position" = "Location on the screen";
"lng_settings_notifications_count" = "Notifications count"; "lng_settings_notifications_count" = "Notifications count";
"lng_settings_sound_notify" = "Play sound"; "lng_settings_sound_allowed" = "Allow sound";
"lng_settings_sound_notify_off" = "Off";
"lng_settings_alert_windows" = "Flash the taskbar icon"; "lng_settings_alert_windows" = "Flash the taskbar icon";
"lng_settings_alert_mac" = "Bounce the dock icon"; "lng_settings_alert_mac" = "Bounce the dock icon";
"lng_settings_alert_linux" = "Draw attention to the window"; "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_hide_all" = "Hide all";
"lng_notification_sample" = "This is a sample notification"; "lng_notification_sample" = "This is a sample notification";
"lng_notification_reminder" = "Reminder"; "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_text" = "{reaction} to your «{text}»";
"lng_reaction_notext" = "{reaction} to your message"; "lng_reaction_notext" = "{reaction} to your message";

View file

@ -10,7 +10,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop" <Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE" ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A" Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="4.9.3.0" /> Version="4.9.4.0" />
<Properties> <Properties>
<DisplayName>Telegram Desktop</DisplayName> <DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName> <PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>

View file

@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 4,9,3,0 FILEVERSION 4,9,4,0
PRODUCTVERSION 4,9,3,0 PRODUCTVERSION 4,9,4,0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -62,10 +62,10 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "Radolyn Labs" VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop" VALUE "FileDescription", "AyuGram Desktop"
VALUE "FileVersion", "4.9.3.0" VALUE "FileVersion", "4.9.4.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2023" VALUE "LegalCopyright", "Copyright (C) 2014-2023"
VALUE "ProductName", "AyuGram Desktop" VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "4.9.3.0" VALUE "ProductVersion", "4.9.4.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 4,9,3,0 FILEVERSION 4,9,4,0
PRODUCTVERSION 4,9,3,0 PRODUCTVERSION 4,9,4,0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -53,10 +53,10 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "Radolyn Labs" VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop Updater" VALUE "FileDescription", "AyuGram Desktop Updater"
VALUE "FileVersion", "4.9.3.0" VALUE "FileVersion", "4.9.4.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2023" VALUE "LegalCopyright", "Copyright (C) 2014-2023"
VALUE "ProductName", "AyuGram Desktop" VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "4.9.3.0" VALUE "ProductVersion", "4.9.4.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -1889,17 +1889,8 @@ void ApiWrap::sendNotifySettingsUpdates() {
} }
const auto &settings = session().data().notifySettings(); const auto &settings = session().data().notifySettings();
for (const auto type : base::take(_updateNotifyDefaults)) { 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( request(MTPaccount_UpdateNotifySettings(
input, Data::DefaultNotifyToMTP(type),
settings.defaultSettings(type).serialize() settings.defaultSettings(type).serialize()
)).afterDelay(kSmallDelayMs).send(); )).afterDelay(kSmallDelayMs).send();
} }

View file

@ -15,14 +15,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h" #include "history/history.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "ui/filter_icons.h"
#include "ui/text/text_utilities.h" // Ui::Text::Bold #include "ui/text/text_utilities.h" // Ui::Text::Bold
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/popup_menu.h" #include "ui/widgets/popup_menu.h"
#include "window/window_controller.h" #include "window/window_controller.h"
#include "window/window_session_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 #include "styles/style_media_player.h" // mediaPlayerMenuCheck
namespace { namespace {
@ -32,16 +29,30 @@ Data::ChatFilter ChangedFilter(
not_null<History*> history, not_null<History*> history,
bool add) { bool add) {
auto always = base::duplicate(filter.always()); auto always = base::duplicate(filter.always());
if (add) {
always.insert(history);
} else {
always.remove(history);
}
auto never = base::duplicate(filter.never()); auto never = base::duplicate(filter.never());
if (add) { if (add) {
never.remove(history); 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 { } else {
never.insert(history); const auto alwaysIt = always.find(history);
if (alwaysIt != end(always)) {
always.erase(alwaysIt);
} else {
never.insert(history);
}
} }
return Data::ChatFilter( return Data::ChatFilter(
filter.id(), filter.id(),

View file

@ -622,9 +622,10 @@ void EditCaptionBox::setupDragArea() {
}; };
// Avoid both drag areas appearing at one time. // Avoid both drag areas appearing at one time.
auto computeState = [=](const QMimeData *data) { auto computeState = [=](const QMimeData *data) {
using DragState = Storage::MimeDataState;
const auto state = Storage::ComputeMimeDataState(data); const auto state = Storage::ComputeMimeDataState(data);
return (state == Storage::MimeDataState::PhotoFiles) return (state == DragState::PhotoFiles || state == DragState::Image)
? Storage::MimeDataState::Image ? (_asFile ? DragState::Files : DragState::Image)
: state; : state;
}; };
const auto areas = DragArea::SetupDragAreaToContainer( const auto areas = DragArea::SetupDragAreaToContainer(

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "boxes/peer_list_box.h" #include "boxes/peer_list_box.h"
#include "history/history.h" // chatListNameSortKey.
#include "main/session/session_show.h" #include "main/session/session_show.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "mainwidget.h" #include "mainwidget.h"
@ -396,6 +397,27 @@ void PeerListController::setSearchNoResultsText(const QString &text) {
} }
} }
void PeerListController::sortByName() {
auto keys = base::flat_map<PeerListRowId, QString>();
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<Ui::PopupMenu> PeerListController::rowContextMenu( base::unique_qptr<Ui::PopupMenu> PeerListController::rowContextMenu(
QWidget *parent, QWidget *parent,
not_null<PeerListRow*> row) { not_null<PeerListRow*> row) {
@ -741,8 +763,8 @@ int PeerListRow::paintNameIconGetWidth(
? st::dialogsVerifiedIconOver ? st::dialogsVerifiedIconOver
: st::dialogsVerifiedIcon), : st::dialogsVerifiedIcon),
.premium = &(selected .premium = &(selected
? st::dialogsPremiumIconOver ? st::dialogsPremiumIcon.over
: st::dialogsPremiumIcon), : st::dialogsPremiumIcon.icon),
.scam = &(selected ? st::dialogsScamFgOver : st::dialogsScamFg), .scam = &(selected ? st::dialogsScamFgOver : st::dialogsScamFg),
.premiumFg = &(selected .premiumFg = &(selected
? st::dialogsVerifiedIconBgOver ? st::dialogsVerifiedIconBgOver

View file

@ -560,6 +560,8 @@ protected:
delegate()->peerListSetSearchNoResults(std::move(noResults)); delegate()->peerListSetSearchNoResults(std::move(noResults));
} }
void sortByName();
private: private:
PeerListDelegate *_delegate = nullptr; PeerListDelegate *_delegate = nullptr;
std::unique_ptr<PeerListSearchController> _searchController = nullptr; std::unique_ptr<PeerListSearchController> _searchController = nullptr;

View file

@ -594,25 +594,6 @@ void ContactsBoxController::sort() {
} }
} }
void ContactsBoxController::sortByName() {
auto keys = base::flat_map<PeerListRowId, QString>();
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() { void ContactsBoxController::sortByOnline() {
const auto now = base::unixtime::now(); const auto now = base::unixtime::now();
const auto key = [&](const PeerListRow &row) { const auto key = [&](const PeerListRow &row) {

View file

@ -192,7 +192,6 @@ protected:
private: private:
void sort(); void sort();
void sortByName();
void sortByOnline(); void sortByOnline();
void rebuildRows(); void rebuildRows();
void checkForEmptyRows(); void checkForEmptyRows();

View file

@ -319,16 +319,15 @@ not_null<Ui::RpWidget*> AddInnerToggle(
button->geometryValue( button->geometryValue(
) | rpl::start_with_next([=](const QRect &r) { ) | rpl::start_with_next([=](const QRect &r) {
const auto w = st::rightsButtonToggleWidth; const auto w = st::rightsButtonToggleWidth;
constexpr auto kLineWidth = int(1);
toggleButton->setGeometry( toggleButton->setGeometry(
r.x() + r.width() - w, r.x() + r.width() - w,
r.y(), r.y(),
w, w,
r.height()); r.height());
separator->setGeometry( separator->setGeometry(
toggleButton->x() - kLineWidth, toggleButton->x() - st::lineWidth,
r.y() + (r.height() - separatorHeight) / 2, r.y() + (r.height() - separatorHeight) / 2,
kLineWidth, st::lineWidth,
separatorHeight); separatorHeight);
}, toggleButton->lifetime()); }, toggleButton->lifetime());

View file

@ -28,8 +28,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/call_delayed.h" #include "base/call_delayed.h"
#include "boxes/premium_limits_box.h" #include "boxes/premium_limits_box.h"
#include "boxes/premium_preview_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/effects/scroll_content_shadow.h"
#include "ui/widgets/checkbox.h" #include "ui/widgets/checkbox.h"
#include "ui/widgets/buttons.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_album_preview.h"
#include "ui/chat/attach/attach_single_file_preview.h" #include "ui/chat/attach/attach_single_file_preview.h"
#include "ui/chat/attach/attach_single_media_preview.h" #include "ui/chat/attach/attach_single_media_preview.h"
#include "ui/text/format_values.h"
#include "ui/grouped_layout.h" #include "ui/grouped_layout.h"
#include "ui/text/text_options.h"
#include "ui/toast/toast.h" #include "ui/toast/toast.h"
#include "ui/controls/emoji_button.h" #include "ui/controls/emoji_button.h"
#include "ui/painter.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/data_premium_limits.h"
#include "data/stickers/data_stickers.h" #include "data/stickers/data_stickers.h"
#include "data/stickers/data_custom_emoji.h" #include "data/stickers/data_custom_emoji.h"
#include "media/clip/media_clip_reader.h"
#include "api/api_common.h" #include "api/api_common.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "core/application.h" #include "core/application.h"
#include "core/core_settings.h" #include "core/core_settings.h"
#include "styles/style_chat.h"
#include "styles/style_layers.h" #include "styles/style_layers.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "styles/style_chat_helpers.h" #include "styles/style_chat_helpers.h"
#include "styles/style_info.h"
#include "styles/style_menu_icons.h" #include "styles/style_menu_icons.h"
#include <QtCore/QMimeData> #include <QtCore/QMimeData>
@ -77,10 +70,14 @@ constexpr auto kMaxMessageLength = 4096;
using Ui::SendFilesWay; using Ui::SendFilesWay;
inline bool CanAddUrls(const QList<QUrl> &urls) { [[nodiscard]] inline bool CanAddUrls(const QList<QUrl> &urls) {
return !urls.isEmpty() && ranges::all_of(urls, &QUrl::isLocalFile); return !urls.isEmpty() && ranges::all_of(urls, &QUrl::isLocalFile);
} }
[[nodiscard]] bool CanAddFiles(not_null<const QMimeData*> data) {
return data->hasImage() || CanAddUrls(Core::ReadMimeUrls(data));
}
void FileDialogCallback( void FileDialogCallback(
FileDialog::OpenResult &&result, FileDialog::OpenResult &&result,
Fn<bool(const Ui::PreparedList&)> checkResult, Fn<bool(const Ui::PreparedList&)> checkResult,
@ -448,13 +445,15 @@ void SendFilesBox::setupDragArea() {
auto computeState = [=](const QMimeData *data) { auto computeState = [=](const QMimeData *data) {
using DragState = Storage::MimeDataState; using DragState = Storage::MimeDataState;
const auto state = Storage::ComputeMimeDataState(data); const auto state = Storage::ComputeMimeDataState(data);
return (state == DragState::PhotoFiles) return (state == DragState::PhotoFiles || state == DragState::Image)
? DragState::Image ? (_sendWay.current().sendImagesAsPhotos()
? DragState::Image
: DragState::Files)
: state; : state;
}; };
const auto areas = DragArea::SetupDragAreaToContainer( const auto areas = DragArea::SetupDragAreaToContainer(
this, this,
[=](not_null<const QMimeData*> d) { return canAddFiles(d); }, CanAddFiles,
[=](bool f) { _caption->setAcceptDrops(f); }, [=](bool f) { _caption->setAcceptDrops(f); },
[=] { updateControlsGeometry(); }, [=] { updateControlsGeometry(); },
std::move(computeState)); std::move(computeState));
@ -1049,7 +1048,7 @@ void SendFilesBox::setupCaption() {
not_null<const QMimeData*> data, not_null<const QMimeData*> data,
Ui::InputField::MimeAction action) { Ui::InputField::MimeAction action) {
if (action == Ui::InputField::MimeAction::Check) { if (action == Ui::InputField::MimeAction::Check) {
return canAddFiles(data); return CanAddFiles(data);
} else if (action == Ui::InputField::MimeAction::Insert) { } else if (action == Ui::InputField::MimeAction::Insert) {
return addFiles(data); return addFiles(data);
} }
@ -1145,10 +1144,6 @@ void SendFilesBox::captionResized() {
update(); update();
} }
bool SendFilesBox::canAddFiles(not_null<const QMimeData*> data) const {
return data->hasImage() || CanAddUrls(Core::ReadMimeUrls(data));
}
bool SendFilesBox::addFiles(not_null<const QMimeData*> data) { bool SendFilesBox::addFiles(not_null<const QMimeData*> data) {
const auto premium = _show->session().premium(); const auto premium = _show->session().premium();
auto list = [&] { auto list = [&] {

View file

@ -213,7 +213,6 @@ private:
void updateControlsGeometry(); void updateControlsGeometry();
void updateCaptionPlaceholder(); void updateCaptionPlaceholder();
bool canAddFiles(not_null<const QMimeData*> data) const;
bool addFiles(not_null<const QMimeData*> data); bool addFiles(not_null<const QMimeData*> data);
bool addFiles(Ui::PreparedList list); bool addFiles(Ui::PreparedList list);
void addFile(Ui::PreparedFile &&file); void addFile(Ui::PreparedFile &&file);

View file

@ -18,7 +18,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/toast/toast.h" #include "ui/toast/toast.h"
#include "ui/widgets/checkbox.h" #include "ui/widgets/checkbox.h"
#include "ui/widgets/multi_select.h" #include "ui/widgets/multi_select.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/scroll_area.h" #include "ui/widgets/scroll_area.h"
#include "ui/widgets/input_fields.h" #include "ui/widgets/input_fields.h"
#include "ui/widgets/popup_menu.h" #include "ui/widgets/popup_menu.h"
@ -602,7 +601,7 @@ void ShareBox::submitWhenOnline() {
submit(Api::DefaultSendWhenOnlineOptions()); submit(Api::DefaultSendWhenOnlineOptions());
} }
void ShareBox::copyLink() { void ShareBox::copyLink() const {
if (const auto onstack = _descriptor.copyCallback) { if (const auto onstack = _descriptor.copyCallback) {
onstack(); onstack();
} }

View file

@ -119,7 +119,7 @@ private:
void submitSilent(); void submitSilent();
void submitScheduled(); void submitScheduled();
void submitWhenOnline(); void submitWhenOnline();
void copyLink(); void copyLink() const;
bool searchByUsername(bool useCache = false); bool searchByUsername(bool useCache = false);
SendMenu::Type sendMenuType() const; SendMenu::Type sendMenuType() const;

View file

@ -53,6 +53,22 @@ constexpr auto kArchivedLimitFirstRequest = 10;
constexpr auto kArchivedLimitPerPage = 30; constexpr auto kArchivedLimitPerPage = 30;
constexpr auto kHandleMegagroupSetAddressChangeTimeout = crl::time(1000); constexpr auto kHandleMegagroupSetAddressChangeTimeout = crl::time(1000);
[[nodiscard]] QString FillSetTitle(
not_null<StickersSet*> 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 } // namespace
class StickersBox::CounterWidget : public Ui::RpWidget { class StickersBox::CounterWidget : public Ui::RpWidget {
@ -98,22 +114,25 @@ public:
void updateRows(); // refresh only pack cover stickers void updateRows(); // refresh only pack cover stickers
bool appendSet(not_null<StickersSet*> set); bool appendSet(not_null<StickersSet*> set);
StickersSetsOrder getOrder() const; StickersSetsOrder order() const;
StickersSetsOrder getFullOrder() const; StickersSetsOrder fullOrder() const;
StickersSetsOrder getRemovedSets() const; StickersSetsOrder removedSets() const;
void setFullOrder(const StickersSetsOrder &order); void setFullOrder(const StickersSetsOrder &order);
void setRemovedSets(const StickersSetsOrder &removed); void setRemovedSets(const StickersSetsOrder &removed);
void setRowRemovedBySetId(uint64 setId, bool removed);
void setInstallSetCallback(Fn<void(uint64 setId)> callback) { void setInstallSetCallback(Fn<void(uint64 setId)> callback) {
_installSetCallback = std::move(callback); _installSetCallback = std::move(callback);
} }
void setRemoveSetCallback(Fn<void(uint64 setId)> callback) {
_removeSetCallback = std::move(callback);
}
void setLoadMoreCallback(Fn<void()> callback) { void setLoadMoreCallback(Fn<void()> callback) {
_loadMoreCallback = std::move(callback); _loadMoreCallback = std::move(callback);
} }
void setMinHeight(int newWidth, int minHeight);
int getVisibleTop() const { int getVisibleTop() const {
return _visibleTop; return _visibleTop;
} }
@ -151,13 +170,13 @@ private:
int32 pixh); int32 pixh);
~Row(); ~Row();
bool isRecentSet() const; [[nodiscard]] bool isRecentSet() const;
bool isMasksSet() const; [[nodiscard]] bool isMasksSet() const;
bool isEmojiSet() const; [[nodiscard]] bool isEmojiSet() const;
bool isWebm() const; [[nodiscard]] bool isWebm() const;
bool isInstalled() const; [[nodiscard]] bool isInstalled() const;
bool isUnread() const; [[nodiscard]] bool isUnread() const;
bool isArchived() const; [[nodiscard]] bool isArchived() const;
const not_null<StickersSet*> set; const not_null<StickersSet*> set;
DocumentData *sticker = nullptr; DocumentData *sticker = nullptr;
@ -212,7 +231,11 @@ private:
void setPressed(SelectedRow pressed); void setPressed(SelectedRow pressed);
void setup(); void setup();
QRect relativeButtonRect(bool removeButton, bool installedSet) const; 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); bool shiftingAnimationCallback(crl::time now);
void paintRow(Painter &p, not_null<Row*> row, int index); void paintRow(Painter &p, not_null<Row*> row, int index);
@ -237,10 +260,6 @@ private:
void rebuildAppendSet(not_null<StickersSet*> set); void rebuildAppendSet(not_null<StickersSet*> set);
void fillSetCover(not_null<StickersSet*> set, DocumentData **outSticker, int *outWidth, int *outHeight) const; void fillSetCover(not_null<StickersSet*> set, DocumentData **outSticker, int *outWidth, int *outHeight) const;
int fillSetCount(not_null<StickersSet*> set) const; int fillSetCount(not_null<StickersSet*> set) const;
[[nodiscard]] QString fillSetTitle(
not_null<StickersSet*> set,
int maxNameWidth,
int *outTitleWidth) const;
[[nodiscard]] Data::StickersSetFlags fillSetFlags( [[nodiscard]] Data::StickersSetFlags fillSetFlags(
not_null<StickersSet*> set) const; not_null<StickersSet*> set) const;
void rebuildMegagroupSet(); void rebuildMegagroupSet();
@ -270,6 +289,7 @@ private:
Ui::Animations::Basic _shiftingAnimation; Ui::Animations::Basic _shiftingAnimation;
Fn<void(uint64 setId)> _installSetCallback; Fn<void(uint64 setId)> _installSetCallback;
Fn<void(uint64 setId)> _removeSetCallback;
Fn<void()> _loadMoreCallback; Fn<void()> _loadMoreCallback;
int _visibleTop = 0; int _visibleTop = 0;
@ -598,17 +618,43 @@ void StickersBox::prepare() {
_attached.widget()->hide(); _attached.widget()->hide();
} }
const auto installCallback = [=](uint64 setId) { installSet(setId); }; {
if (_featured.widget()) { const auto installCallback = [=](uint64 setId) { installSet(setId); };
_featured.widget()->setInstallSetCallback(installCallback); const auto markAsInstalledCallback = [=](uint64 setId) {
} if (_installed.widget()) {
if (_archived.widget()) { _installed.widget()->setRowRemovedBySetId(setId, false);
_archived.widget()->setInstallSetCallback(installCallback); }
_archived.widget()->setLoadMoreCallback([=] { loadMoreArchived(); }); if (_featured.widget()) {
} _featured.widget()->setRowRemovedBySetId(setId, false);
if (_attached.widget()) { }
_attached.widget()->setInstallSetCallback(installCallback); };
_attached.widget()->setLoadMoreCallback([=] { showAttachedStickers(); }); 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) { if (_megagroupSet) {
@ -634,7 +680,7 @@ void StickersBox::prepare() {
} else { // _section == Section::Featured } else { // _section == Section::Featured
_tab = &_featured; _tab = &_featured;
} }
setInnerWidget(_tab->takeWidget(), getTopSkip()); setInnerWidget(_tab->takeWidget(), topSkip());
setDimensions(st::boxWideWidth, st::boxMaxListHeight); setDimensions(st::boxWideWidth, st::boxMaxListHeight);
session().data().stickers().updated(_isEmoji session().data().stickers().updated(_isEmoji
@ -757,7 +803,7 @@ void StickersBox::paintEvent(QPaintEvent *e) {
Painter p(this); Painter p(this);
if (_slideAnimation) { if (_slideAnimation) {
_slideAnimation->paintFrame(p, 0, getTopSkip(), width()); _slideAnimation->paintFrame(p, 0, topSkip(), width());
if (!_slideAnimation->animating()) { if (!_slideAnimation->animating()) {
_slideAnimation.reset(); _slideAnimation.reset();
setInnerVisible(true); setInnerVisible(true);
@ -774,7 +820,7 @@ void StickersBox::updateTabsGeometry() {
_tabs->resizeToWidth(_tabIndices.size() * width() / maxTabs); _tabs->resizeToWidth(_tabIndices.size() * width() / maxTabs);
_unreadBadge->setVisible(_tabIndices.contains(Section::Featured)); _unreadBadge->setVisible(_tabIndices.contains(Section::Featured));
setInnerTopSkip(getTopSkip()); setInnerTopSkip(topSkip());
auto featuredLeft = width() / maxTabs; auto featuredLeft = width() / maxTabs;
auto featuredRight = 2 * width() / maxTabs; auto featuredRight = 2 * width() / maxTabs;
@ -790,7 +836,7 @@ void StickersBox::updateTabsGeometry() {
_tabs->moveToLeft(0, 0); _tabs->moveToLeft(0, 0);
} }
int StickersBox::getTopSkip() const { int StickersBox::topSkip() const {
return _tabs ? (_tabs->height() - st::lineWidth) : 0; return _tabs ? (_tabs->height() - st::lineWidth) : 0;
} }
@ -819,8 +865,8 @@ void StickersBox::switchTab() {
} }
if (_tab == &_installed) { if (_tab == &_installed) {
_localOrder = _tab->widget()->getFullOrder(); _localOrder = _tab->widget()->fullOrder();
_localRemoved = _tab->widget()->getRemovedSets(); _localRemoved = _tab->widget()->removedSets();
} }
auto wasCache = grabContentCache(); auto wasCache = grabContentCache();
auto wasIndex = _tab->index(); auto wasIndex = _tab->index();
@ -831,12 +877,12 @@ void StickersBox::switchTab() {
_tab->returnWidget(std::move(widget)); _tab->returnWidget(std::move(widget));
_tab = newTab; _tab = newTab;
_section = newSection; _section = newSection;
setInnerWidget(_tab->takeWidget(), getTopSkip()); setInnerWidget(_tab->takeWidget(), topSkip());
_tabs->raise(); _tabs->raise();
_unreadBadge->raise(); _unreadBadge->raise();
_tab->widget()->show(); _tab->widget()->show();
rebuildList(); rebuildList();
scrollToY(_tab->getScrollTop()); scrollToY(_tab->scrollTop());
setInnerVisible(true); setInnerVisible(true);
auto nowCache = grabContentCache(); auto nowCache = grabContentCache();
auto nowIndex = _tab->index(); auto nowIndex = _tab->index();
@ -901,7 +947,7 @@ void StickersBox::installSet(uint64 setId) {
} }
void StickersBox::installDone( void StickersBox::installDone(
const MTPmessages_StickerSetInstallResult &result) { const MTPmessages_StickerSetInstallResult &result) const {
if (result.type() == mtpc_messages_stickerSetInstallResultArchive) { if (result.type() == mtpc_messages_stickerSetInstallResultArchive) {
session().data().stickers().applyArchivedResult( session().data().stickers().applyArchivedResult(
result.c_messages_stickerSetInstallResultArchive()); result.c_messages_stickerSetInstallResultArchive());
@ -998,12 +1044,12 @@ void StickersBox::rebuildList(Tab *tab) {
tab = _tab; tab = _tab;
} }
if ((tab == &_installed) || (tab == &_masks)) { if ((tab == &_installed) || (tab == &_masks) || (_tab == &_featured)) {
_localOrder = tab->widget()->getFullOrder(); _localOrder = tab->widget()->fullOrder();
_localRemoved = tab->widget()->getRemovedSets(); _localRemoved = tab->widget()->removedSets();
} }
tab->widget()->rebuild(_isMasks); tab->widget()->rebuild(_isMasks);
if ((tab == &_installed) || (tab == &_masks)) { if ((tab == &_installed) || (tab == &_masks) || (_tab == &_featured)) {
tab->widget()->setFullOrder(_localOrder); tab->widget()->setFullOrder(_localOrder);
} }
tab->widget()->setRemovedSets(_localRemoved); tab->widget()->setRemovedSets(_localRemoved);
@ -1030,14 +1076,14 @@ void StickersBox::saveChanges() {
} }
if (installed) { if (installed) {
session().api().saveStickerSets( session().api().saveStickerSets(
installed->getOrder(), installed->order(),
installed->getRemovedSets(), installed->removedSets(),
Data::StickersType::Stickers); Data::StickersType::Stickers);
} }
if (masks) { if (masks) {
session().api().saveStickerSets( session().api().saveStickerSets(
masks->getOrder(), masks->order(),
masks->getRemovedSets(), masks->removedSets(),
Data::StickersType::Masks); Data::StickersType::Masks);
} }
} }
@ -1056,7 +1102,7 @@ const Data::StickersSetsOrder &StickersBox::archivedSetsOrder() const {
: session().data().stickers().archivedMaskSetsOrder(); : session().data().stickers().archivedMaskSetsOrder();
} }
Data::StickersSetsOrder &StickersBox::archivedSetsOrderRef() { Data::StickersSetsOrder &StickersBox::archivedSetsOrderRef() const {
return !_isMasks return !_isMasks
? session().data().stickers().archivedSetsOrderRef() ? session().data().stickers().archivedSetsOrderRef()
: session().data().stickers().archivedMaskSetsOrderRef(); : session().data().stickers().archivedMaskSetsOrderRef();
@ -1594,6 +1640,12 @@ void StickersBox::Inner::paintFakeButton(Painter &p, not_null<Row*> row, int ind
const auto textWidth = _installedWidth; const auto textWidth = _installedWidth;
const auto &text = _installedText; const auto &text = _installedText;
_inactiveButtonBg.paint(p, myrtlrect(rect)); _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.setFont(st.font);
p.setPen(st.textFg); p.setPen(st.textFg);
p.drawTextLeft(rect.x() - (st.width / 2), rect.y() + st.textTop, width(), text, textWidth); 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) { if (row->removed) {
auto rippleSize = QSize(_undoWidth - st::stickersUndoRemove.width, st::stickersUndoRemove.height); auto rippleSize = QSize(_undoWidth - st::stickersUndoRemove.width, st::stickersUndoRemove.height);
auto rippleMask = Ui::RippleAnimation::RoundRectMask(rippleSize, st::roundRadiusLarge); 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 { } else {
auto rippleSize = st::stickersRemove.rippleAreaSize; auto rippleSize = st::stickersRemove.rippleAreaSize;
auto rippleMask = Ui::RippleAnimation::EllipseMask(QSize(rippleSize, rippleSize)); 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) { } else {
auto rippleSize = QSize(_addWidth - st::stickersTrendingAdd.width, st::stickersTrendingAdd.height); const auto installedSet = row->isInstalled()
auto rippleMask = Ui::RippleAnimation::RoundRectMask(rippleSize, st::roundRadiusLarge); && !row->isArchived()
ensureRipple(st::stickersTrendingAdd.ripple, std::move(rippleMask), removeButton); && !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) { 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) { void StickersBox::Inner::ensureRipple(
_rows[_actionDown]->ripple = std::make_unique<Ui::RippleAnimation>(st, std::move(mask), [this, index = _actionDown, removeButton] { const style::RippleAnimation &st,
update(myrtlrect(relativeButtonRect(removeButton, false).translated(0, _itemsTop + index * _rowHeight))); QImage mask,
bool removeButton,
bool installedSet) {
const auto dy = _itemsTop + _actionDown * _rowHeight;
_rows[_actionDown]->ripple = std::make_unique<Ui::RippleAnimation>(st, std::move(mask), [=] {
update(myrtlrect(relativeButtonRect(removeButton, installedSet).translated(0, dy)));
}); });
} }
@ -1812,7 +1880,7 @@ void StickersBox::Inner::updateSelected() {
selected = selectedIndex; selected = selectedIndex;
local.setY(local.y() - _itemsTop - selectedIndex * _rowHeight); local.setY(local.y() - _itemsTop - selectedIndex * _rowHeight);
const auto row = _rows[selectedIndex].get(); 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 removeButton = (_isInstalledTab && !row->removed);
auto rect = myrtlrect(relativeButtonRect(removeButton, false)); auto rect = myrtlrect(relativeButtonRect(removeButton, false));
actionSel = rect.contains(local) ? selectedIndex : -1; actionSel = rect.contains(local) ? selectedIndex : -1;
@ -1857,7 +1925,7 @@ float64 StickersBox::Inner::aboveShadowOpacity() const {
auto dx = 0; auto dx = 0;
auto dy = qAbs(_above * _rowHeight + qRound(_rows[_above]->yadd.current()) - _started * _rowHeight); 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) { void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
@ -1868,10 +1936,11 @@ void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
_mouse = e->globalPos(); _mouse = e->globalPos();
updateSelected(); updateSelected();
if (_actionDown == _actionSel && _actionSel >= 0) { if (_actionDown == _actionSel && _actionSel >= 0) {
if (_isInstalledTab) { const auto callback = _rows[_actionDown]->removed
setRowRemoved(_actionDown, !_rows[_actionDown]->removed); ? _installSetCallback
} else if (_installSetCallback) { : _removeSetCallback;
_installSetCallback(_rows[_actionDown]->set->id); if (callback) {
callback(_rows[_actionDown]->set->id);
} }
} else if (_dragging >= 0) { } else if (_dragging >= 0) {
_rows[_dragging]->yadd.start(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) { void StickersBox::Inner::setRowRemoved(int index, bool removed) {
auto &row = _rows[index]; auto &row = _rows[index];
if (row->removed != removed) { if (row->removed != removed) {
@ -2114,7 +2190,7 @@ void StickersBox::Inner::rebuildMegagroupSet() {
auto removed = false; auto removed = false;
auto maxNameWidth = countMaxNameWidth(!_isInstalledTab); auto maxNameWidth = countMaxNameWidth(!_isInstalledTab);
auto titleWidth = 0; auto titleWidth = 0;
auto title = fillSetTitle(set, maxNameWidth, &titleWidth); auto title = FillSetTitle(set, maxNameWidth, &titleWidth);
if (!_megagroupSelectedSet if (!_megagroupSelectedSet
|| _megagroupSelectedSet->set->id != set->id) { || _megagroupSelectedSet->set->id != set->id) {
_megagroupSetField->setText(set->shortName); _megagroupSetField->setText(set->shortName);
@ -2217,11 +2293,6 @@ void StickersBox::Inner::setMegagroupSelectedSet(
updateSelected(); updateSelected();
} }
void StickersBox::Inner::setMinHeight(int newWidth, int minHeight) {
_minHeight = minHeight;
updateSize(newWidth);
}
void StickersBox::Inner::updateSize(int newWidth) { void StickersBox::Inner::updateSize(int newWidth) {
auto naturalHeight = _itemsTop + int(_rows.size()) * _rowHeight + st::membersMarginBottom; auto naturalHeight = _itemsTop + int(_rows.size()) * _rowHeight + st::membersMarginBottom;
resize(newWidth ? newWidth : width(), qMax(_minHeight, naturalHeight)); resize(newWidth ? newWidth : width(), qMax(_minHeight, naturalHeight));
@ -2269,7 +2340,7 @@ void StickersBox::Inner::updateRows() {
&& row->isInstalled() && row->isInstalled()
&& !row->isArchived() && !row->isArchived()
&& !row->removed); && !row->removed);
row->title = fillSetTitle( row->title = FillSetTitle(
set, set,
installedSet ? maxNameWidthInstalled : maxNameWidth, installedSet ? maxNameWidthInstalled : maxNameWidth,
&row->titleWidth); &row->titleWidth);
@ -2331,7 +2402,7 @@ void StickersBox::Inner::rebuildAppendSet(not_null<StickersSet*> set) {
&& !(flagsOverride & SetFlag::Archived) && !(flagsOverride & SetFlag::Archived)
&& !removed); && !removed);
int titleWidth = 0; int titleWidth = 0;
QString title = fillSetTitle(set, maxNameWidth, &titleWidth); QString title = FillSetTitle(set, maxNameWidth, &titleWidth);
int count = fillSetCount(set); int count = fillSetCount(set);
const auto existing = [&]{ const auto existing = [&]{
@ -2458,22 +2529,6 @@ int StickersBox::Inner::fillSetCount(not_null<StickersSet*> set) const {
return result + added; return result + added;
} }
QString StickersBox::Inner::fillSetTitle(
not_null<StickersSet*> 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( Data::StickersSetFlags StickersBox::Inner::fillSetFlags(
not_null<StickersSet*> set) const { not_null<StickersSet*> set) const {
const auto result = set->flags; const auto result = set->flags;
@ -2494,19 +2549,19 @@ StickersSetsOrder StickersBox::Inner::collectSets(Check check) const {
return result; return result;
} }
StickersSetsOrder StickersBox::Inner::getOrder() const { StickersSetsOrder StickersBox::Inner::order() const {
return collectSets([](Row *row) { return collectSets([](Row *row) {
return !row->isArchived() && !row->removed && !row->isRecentSet(); return !row->isArchived() && !row->removed && !row->isRecentSet();
}); });
} }
StickersSetsOrder StickersBox::Inner::getFullOrder() const { StickersSetsOrder StickersBox::Inner::fullOrder() const {
return collectSets([](Row *row) { return collectSets([](Row *row) {
return !row->isRecentSet(); return !row->isRecentSet();
}); });
} }
StickersSetsOrder StickersBox::Inner::getRemovedSets() const { StickersSetsOrder StickersBox::Inner::removedSets() const {
return collectSets([](Row *row) { return collectSets([](Row *row) {
return row->removed; return row->removed;
}); });

View file

@ -104,7 +104,7 @@ private:
[[nodiscard]] int index() const; [[nodiscard]] int index() const;
void saveScrollTop(); void saveScrollTop();
int getScrollTop() const { int scrollTop() const {
return _scrollTop; return _scrollTop;
} }
@ -122,12 +122,12 @@ private:
void updateTabsGeometry(); void updateTabsGeometry();
void switchTab(); void switchTab();
void installSet(uint64 setId); void installSet(uint64 setId);
int getTopSkip() const; int topSkip() const;
void saveChanges(); void saveChanges();
QPixmap grabContentCache(); QPixmap grabContentCache();
void installDone(const MTPmessages_StickerSetInstallResult &result); void installDone(const MTPmessages_StickerSetInstallResult &result) const;
void installFail(const MTP::Error &error, uint64 setId); void installFail(const MTP::Error &error, uint64 setId);
void preloadArchivedSets(); void preloadArchivedSets();
@ -139,7 +139,7 @@ private:
void showAttachedStickers(); void showAttachedStickers();
const Data::StickersSetsOrder &archivedSetsOrder() const; const Data::StickersSetsOrder &archivedSetsOrder() const;
Data::StickersSetsOrder &archivedSetsOrderRef(); Data::StickersSetsOrder &archivedSetsOrderRef() const;
std::array<Inner*, 5> widgets() const; std::array<Inner*, 5> widgets() const;

View file

@ -294,8 +294,11 @@ stickersTrendingAdd: RoundButton(defaultActiveButton) {
stickersTrendingInstalled: RoundButton(stickersTrendingAdd) { stickersTrendingInstalled: RoundButton(stickersTrendingAdd) {
textFg: activeButtonBg; textFg: activeButtonBg;
textFgOver: activeButtonBgOver; textFgOver: activeButtonBgOver;
textBg: activeButtonSecondaryFg; textBg: lightButtonBgOver;
textBgOver: activeButtonSecondaryFgOver; textBgOver: lightButtonBgOver;
ripple: RippleAnimation(defaultRippleAnimation) {
color: activeButtonSecondaryFg;
}
} }
stickersRemove: IconButton(defaultIconButton) { stickersRemove: IconButton(defaultIconButton) {
width: 40px; width: 40px;

View file

@ -45,12 +45,9 @@ inline QString IconName() {
inline bool CanReadDirectory(const QString &path) { inline bool CanReadDirectory(const QString &path) {
#ifndef Q_OS_MAC // directory_iterator since 10.15 #ifndef Q_OS_MAC // directory_iterator since 10.15
try { std::error_code error;
std::filesystem::directory_iterator(path.toStdString()); std::filesystem::directory_iterator(path.toStdString(), error);
return true; return !error;
} catch (...) {
return false;
}
#else #else
Unexpected("Not implemented."); Unexpected("Not implemented.");
#endif #endif

View file

@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
constexpr auto AppNameOld = "AyuGram for Windows"_cs; constexpr auto AppNameOld = "AyuGram for Windows"_cs;
constexpr auto AppName = "AyuGram Desktop"_cs; constexpr auto AppName = "AyuGram Desktop"_cs;
constexpr auto AppFile = "AyuGram"_cs; constexpr auto AppFile = "AyuGram"_cs;
constexpr auto AppVersion = 4009003; constexpr auto AppVersion = 4009004;
constexpr auto AppVersionStr = "4.9.3"; constexpr auto AppVersionStr = "4.9.4";
constexpr auto AppBetaVersion = false; constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View file

@ -42,11 +42,39 @@ constexpr auto kMaxNotifyCheckDelay = 24 * 3600 * crl::time(1000);
return (result > 0); return (result > 0);
} }
[[nodiscard]] bool SkipAddException(not_null<PeerData*> 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 } // namespace
DefaultNotify DefaultNotifyType(not_null<const PeerData*> 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<Session*> owner) NotifySettings::NotifySettings(not_null<Session*> owner)
: _owner(owner) : _owner(owner)
, _unmuteByFinishedTimer([=] { unmuteByFinished(); }) { , _unmuteByFinishedTimer([=] { unmuteByFinished(); }) {
} }
void NotifySettings::request(not_null<PeerData*> peer) { void NotifySettings::request(not_null<PeerData*> peer) {
@ -63,7 +91,7 @@ void NotifySettings::request(not_null<PeerData*> peer) {
} }
} }
void NotifySettings::request(not_null<Data::Thread*> thread) { void NotifySettings::request(not_null<Thread*> thread) {
if (const auto topic = thread->asTopic()) { if (const auto topic = thread->asTopic()) {
if (topic->notify().settingsUnknown()) { if (topic->notify().settingsUnknown()) {
topic->session().api().requestNotifySettings( topic->session().api().requestNotifySettings(
@ -145,6 +173,7 @@ void NotifySettings::apply(
not_null<PeerData*> peer, not_null<PeerData*> peer,
const MTPPeerNotifySettings &settings) { const MTPPeerNotifySettings &settings) {
if (peer->notify().change(settings)) { if (peer->notify().change(settings)) {
updateException(peer);
updateLocal(peer); updateLocal(peer);
Core::App().notifications().checkDelayed(); Core::App().notifications().checkDelayed();
} }
@ -162,7 +191,7 @@ void NotifySettings::apply(
} }
void NotifySettings::apply( void NotifySettings::apply(
not_null<Data::ForumTopic*> topic, not_null<ForumTopic*> topic,
const MTPPeerNotifySettings &settings) { const MTPPeerNotifySettings &settings) {
if (topic->notify().change(settings)) { if (topic->notify().change(settings)) {
updateLocal(topic); updateLocal(topic);
@ -171,8 +200,8 @@ void NotifySettings::apply(
} }
void NotifySettings::update( void NotifySettings::update(
not_null<Data::Thread*> thread, not_null<Thread*> thread,
Data::MuteValue muteForSeconds, MuteValue muteForSeconds,
std::optional<bool> silentPosts, std::optional<bool> silentPosts,
std::optional<NotifySound> sound, std::optional<NotifySound> sound,
std::optional<bool> storiesMuted) { std::optional<bool> storiesMuted) {
@ -181,34 +210,29 @@ void NotifySettings::update(
silentPosts, silentPosts,
sound, sound,
storiesMuted)) { storiesMuted)) {
if (const auto history = thread->asHistory()) {
updateException(history->peer);
}
updateLocal(thread); updateLocal(thread);
thread->session().api().updateNotifySettingsDelayed(thread); thread->session().api().updateNotifySettingsDelayed(thread);
} }
} }
void NotifySettings::resetToDefault(not_null<Data::Thread*> thread) { void NotifySettings::resetToDefault(not_null<Thread*> thread) {
const auto empty = MTP_peerNotifySettings( // Duplicated in clearExceptions(type) and resetToDefault(peer).
MTP_flags(0), if (thread->notify().resetToDefault()) {
MTPBool(), if (const auto history = thread->asHistory()) {
MTPBool(), updateException(history->peer);
MTPint(), }
MTPNotificationSound(),
MTPNotificationSound(),
MTPNotificationSound(),
MTPBool(),
MTPBool(),
MTPNotificationSound(),
MTPNotificationSound(),
MTPNotificationSound());
if (thread->notify().change(empty)) {
updateLocal(thread); updateLocal(thread);
thread->session().api().updateNotifySettingsDelayed(thread); thread->session().api().updateNotifySettingsDelayed(thread);
Core::App().notifications().checkDelayed();
} }
} }
void NotifySettings::update( void NotifySettings::update(
not_null<PeerData*> peer, not_null<PeerData*> peer,
Data::MuteValue muteForSeconds, MuteValue muteForSeconds,
std::optional<bool> silentPosts, std::optional<bool> silentPosts,
std::optional<NotifySound> sound, std::optional<NotifySound> sound,
std::optional<bool> storiesMuted) { std::optional<bool> storiesMuted) {
@ -217,33 +241,24 @@ void NotifySettings::update(
silentPosts, silentPosts,
sound, sound,
storiesMuted)) { storiesMuted)) {
updateException(peer);
updateLocal(peer); updateLocal(peer);
peer->session().api().updateNotifySettingsDelayed(peer); peer->session().api().updateNotifySettingsDelayed(peer);
} }
} }
void NotifySettings::resetToDefault(not_null<PeerData*> peer) { void NotifySettings::resetToDefault(not_null<PeerData*> peer) {
const auto empty = MTP_peerNotifySettings( // Duplicated in clearExceptions(type) and resetToDefault(thread).
MTP_flags(0), if (peer->notify().resetToDefault()) {
MTPBool(), updateException(peer);
MTPBool(),
MTPint(),
MTPNotificationSound(),
MTPNotificationSound(),
MTPNotificationSound(),
MTPBool(),
MTPBool(),
MTPNotificationSound(),
MTPNotificationSound(),
MTPNotificationSound());
if (peer->notify().change(empty)) {
updateLocal(peer); updateLocal(peer);
peer->session().api().updateNotifySettingsDelayed(peer); peer->session().api().updateNotifySettingsDelayed(peer);
Core::App().notifications().checkDelayed();
} }
} }
void NotifySettings::forumParentMuteUpdated(not_null<Data::Forum*> forum) { void NotifySettings::forumParentMuteUpdated(not_null<Forum*> forum) {
forum->enumerateTopics([&](not_null<Data::ForumTopic*> topic) { forum->enumerateTopics([&](not_null<ForumTopic*> topic) {
if (!topic->notify().settingsUnknown()) { if (!topic->notify().settingsUnknown()) {
updateLocal(topic); updateLocal(topic);
} }
@ -266,11 +281,7 @@ auto NotifySettings::defaultValue(DefaultNotify type) const
const PeerNotifySettings &NotifySettings::defaultSettings( const PeerNotifySettings &NotifySettings::defaultSettings(
not_null<const PeerData*> peer) const { not_null<const PeerData*> peer) const {
return defaultSettings(peer->isUser() return defaultSettings(DefaultNotifyType(peer));
? DefaultNotify::User
: (peer->isChat() || peer->isMegagroup())
? DefaultNotify::Group
: DefaultNotify::Broadcast);
} }
const PeerNotifySettings &NotifySettings::defaultSettings( const PeerNotifySettings &NotifySettings::defaultSettings(
@ -278,9 +289,16 @@ const PeerNotifySettings &NotifySettings::defaultSettings(
return defaultValue(type).settings; 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( void NotifySettings::defaultUpdate(
DefaultNotify type, DefaultNotify type,
Data::MuteValue muteForSeconds, MuteValue muteForSeconds,
std::optional<bool> silentPosts, std::optional<bool> silentPosts,
std::optional<NotifySound> sound, std::optional<NotifySound> sound,
std::optional<bool> storiesMuted) { std::optional<bool> storiesMuted) {
@ -291,7 +309,7 @@ void NotifySettings::defaultUpdate(
} }
} }
void NotifySettings::updateLocal(not_null<Data::Thread*> thread) { void NotifySettings::updateLocal(not_null<Thread*> thread) {
const auto topic = thread->asTopic(); const auto topic = thread->asTopic();
if (!topic) { if (!topic) {
return updateLocal(thread->peer()); return updateLocal(thread->peer());
@ -351,7 +369,7 @@ void NotifySettings::cacheSound(not_null<DocumentData*> document) {
const auto view = document->createMediaView(); const auto view = document->createMediaView();
_ringtones.views.emplace(document->id, view); _ringtones.views.emplace(document->id, view);
document->forceToCache(true); document->forceToCache(true);
document->save(Data::FileOriginRingtones(), QString()); document->save(FileOriginRingtones(), QString());
} }
void NotifySettings::cacheSound(const std::optional<NotifySound> &sound) { void NotifySettings::cacheSound(const std::optional<NotifySound> &sound) {
@ -459,7 +477,7 @@ void NotifySettings::unmuteByFinished() {
} }
bool NotifySettings::isMuted( bool NotifySettings::isMuted(
not_null<const Data::Thread*> thread, not_null<const Thread*> thread,
crl::time *changesIn) const { crl::time *changesIn) const {
const auto topic = thread->asTopic(); const auto topic = thread->asTopic();
const auto until = topic ? topic->notify().muteUntil() : std::nullopt; const auto until = topic ? topic->notify().muteUntil() : std::nullopt;
@ -468,27 +486,24 @@ bool NotifySettings::isMuted(
: isMuted(thread->peer(), changesIn); : isMuted(thread->peer(), changesIn);
} }
bool NotifySettings::isMuted(not_null<const Data::Thread*> thread) const { bool NotifySettings::isMuted(not_null<const Thread*> thread) const {
return isMuted(thread, nullptr); return isMuted(thread, nullptr);
} }
NotifySound NotifySettings::sound( NotifySound NotifySettings::sound(not_null<const Thread*> thread) const {
not_null<const Data::Thread*> thread) const {
const auto topic = thread->asTopic(); const auto topic = thread->asTopic();
const auto sound = topic ? topic->notify().sound() : std::nullopt; const auto sound = topic ? topic->notify().sound() : std::nullopt;
return sound ? *sound : this->sound(thread->peer()); return sound ? *sound : this->sound(thread->peer());
} }
bool NotifySettings::muteUnknown( bool NotifySettings::muteUnknown(not_null<const Thread*> thread) const {
not_null<const Data::Thread*> thread) const {
const auto topic = thread->asTopic(); const auto topic = thread->asTopic();
return (topic && topic->notify().settingsUnknown()) return (topic && topic->notify().settingsUnknown())
|| ((!topic || !topic->notify().muteUntil().has_value()) || ((!topic || !topic->notify().muteUntil().has_value())
&& muteUnknown(thread->peer())); && muteUnknown(thread->peer()));
} }
bool NotifySettings::soundUnknown( bool NotifySettings::soundUnknown(not_null<const Thread*> thread) const {
not_null<const Data::Thread*> thread) const {
const auto topic = thread->asTopic(); const auto topic = thread->asTopic();
return (topic && topic->notify().settingsUnknown()) return (topic && topic->notify().settingsUnknown())
|| ((!topic || !topic->notify().sound().has_value()) || ((!topic || !topic->notify().sound().has_value())
@ -543,8 +558,7 @@ bool NotifySettings::silentPostsUnknown(
&& defaultSettings(peer).settingsUnknown()); && defaultSettings(peer).settingsUnknown());
} }
bool NotifySettings::soundUnknown( bool NotifySettings::soundUnknown(not_null<const PeerData*> peer) const {
not_null<const PeerData*> peer) const {
return peer->notify().settingsUnknown() return peer->notify().settingsUnknown()
|| (!peer->notify().sound().has_value() || (!peer->notify().sound().has_value()
&& defaultSettings(peer).settingsUnknown()); && defaultSettings(peer).settingsUnknown());
@ -556,8 +570,7 @@ bool NotifySettings::settingsUnknown(not_null<const PeerData*> peer) const {
|| soundUnknown(peer); || soundUnknown(peer);
} }
bool NotifySettings::settingsUnknown( bool NotifySettings::settingsUnknown(not_null<const Thread*> thread) const {
not_null<const Data::Thread*> thread) const {
const auto topic = thread->asTopic(); const auto topic = thread->asTopic();
return muteUnknown(thread) return muteUnknown(thread)
|| soundUnknown(thread) || soundUnknown(thread)
@ -577,4 +590,85 @@ rpl::producer<> NotifySettings::defaultUpdates(
: DefaultNotify::Broadcast); : DefaultNotify::Broadcast);
} }
void NotifySettings::loadExceptions() {
for (auto i = 0; i != kDefaultNotifyTypes; ++i) {
if (_exceptionsRequestId[i]) {
continue;
}
const auto type = static_cast<DefaultNotify>(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<PeerData*> peer) {
const auto type = DefaultNotifyType(peer);
const auto index = static_cast<int>(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<DefaultNotify>(i));
}
}
});
}
_exceptionsUpdatesScheduled[static_cast<int>(type)] = true;
_exceptionsUpdatesRealtime.fire_copy(type);
}
rpl::producer<DefaultNotify> NotifySettings::exceptionsUpdates() const {
return _exceptionsUpdates.events();
}
auto NotifySettings::exceptionsUpdatesRealtime() const
-> rpl::producer<DefaultNotify> {
return _exceptionsUpdatesRealtime.events();
}
const base::flat_set<not_null<PeerData*>> &NotifySettings::exceptions(
DefaultNotify type) const {
const auto index = static_cast<int>(type);
Assert(index >= 0 && index < kDefaultNotifyTypes);
return _exceptions[index];
}
void NotifySettings::clearExceptions(DefaultNotify type) {
const auto index = static_cast<int>(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 } // namespace Data

View file

@ -26,13 +26,17 @@ enum class DefaultNotify {
Group, Group,
Broadcast, Broadcast,
}; };
[[nodiscard]] DefaultNotify DefaultNotifyType(
not_null<const PeerData*> peer);
[[nodiscard]] MTPInputNotifyPeer DefaultNotifyToMTP(DefaultNotify type);
class NotifySettings final { class NotifySettings final {
public: public:
NotifySettings(not_null<Session*> owner); NotifySettings(not_null<Session*> owner);
void request(not_null<PeerData*> peer); void request(not_null<PeerData*> peer);
void request(not_null<Data::Thread*> thread); void request(not_null<Thread*> thread);
void apply( void apply(
const MTPNotifyPeer &notifyPeer, const MTPNotifyPeer &notifyPeer,
@ -50,25 +54,25 @@ public:
MsgId topicRootId, MsgId topicRootId,
const MTPPeerNotifySettings &settings); const MTPPeerNotifySettings &settings);
void apply( void apply(
not_null<Data::ForumTopic*> topic, not_null<ForumTopic*> topic,
const MTPPeerNotifySettings &settings); const MTPPeerNotifySettings &settings);
void update( void update(
not_null<Data::Thread*> thread, not_null<Thread*> thread,
Data::MuteValue muteForSeconds, MuteValue muteForSeconds,
std::optional<bool> silentPosts = std::nullopt, std::optional<bool> silentPosts = std::nullopt,
std::optional<NotifySound> sound = std::nullopt, std::optional<NotifySound> sound = std::nullopt,
std::optional<bool> storiesMuted = std::nullopt); std::optional<bool> storiesMuted = std::nullopt);
void resetToDefault(not_null<Data::Thread*> thread); void resetToDefault(not_null<Thread*> thread);
void update( void update(
not_null<PeerData*> peer, not_null<PeerData*> peer,
Data::MuteValue muteForSeconds, MuteValue muteForSeconds,
std::optional<bool> silentPosts = std::nullopt, std::optional<bool> silentPosts = std::nullopt,
std::optional<NotifySound> sound = std::nullopt, std::optional<NotifySound> sound = std::nullopt,
std::optional<bool> storiesMuted = std::nullopt); std::optional<bool> storiesMuted = std::nullopt);
void resetToDefault(not_null<PeerData*> peer); void resetToDefault(not_null<PeerData*> peer);
void forumParentMuteUpdated(not_null<Data::Forum*> forum); void forumParentMuteUpdated(not_null<Forum*> forum);
void cacheSound(DocumentId id); void cacheSound(DocumentId id);
void cacheSound(not_null<DocumentData*> document); void cacheSound(not_null<DocumentData*> document);
@ -81,21 +85,19 @@ public:
[[nodiscard]] const PeerNotifySettings &defaultSettings( [[nodiscard]] const PeerNotifySettings &defaultSettings(
DefaultNotify type) const; DefaultNotify type) const;
[[nodiscard]] bool isMuted(DefaultNotify type) const;
void defaultUpdate( void defaultUpdate(
DefaultNotify type, DefaultNotify type,
Data::MuteValue muteForSeconds, MuteValue muteForSeconds,
std::optional<bool> silentPosts = std::nullopt, std::optional<bool> silentPosts = std::nullopt,
std::optional<NotifySound> sound = std::nullopt, std::optional<NotifySound> sound = std::nullopt,
std::optional<bool> storiesMuted = std::nullopt); std::optional<bool> storiesMuted = std::nullopt);
[[nodiscard]] bool isMuted(not_null<const Data::Thread*> thread) const; [[nodiscard]] bool isMuted(not_null<const Thread*> thread) const;
[[nodiscard]] NotifySound sound( [[nodiscard]] NotifySound sound(not_null<const Thread*> thread) const;
not_null<const Data::Thread*> thread) const; [[nodiscard]] bool muteUnknown(not_null<const Thread*> thread) const;
[[nodiscard]] bool muteUnknown( [[nodiscard]] bool soundUnknown(not_null<const Thread*> thread) const;
not_null<const Data::Thread*> thread) const;
[[nodiscard]] bool soundUnknown(
not_null<const Data::Thread*> thread) const;
[[nodiscard]] bool isMuted(not_null<const PeerData*> peer) const; [[nodiscard]] bool isMuted(not_null<const PeerData*> peer) const;
[[nodiscard]] bool silentPosts(not_null<const PeerData*> peer) const; [[nodiscard]] bool silentPosts(not_null<const PeerData*> peer) const;
@ -105,7 +107,17 @@ public:
not_null<const PeerData*> peer) const; not_null<const PeerData*> peer) const;
[[nodiscard]] bool soundUnknown(not_null<const PeerData*> peer) const; [[nodiscard]] bool soundUnknown(not_null<const PeerData*> peer) const;
void loadExceptions();
[[nodiscard]] rpl::producer<DefaultNotify> exceptionsUpdates() const;
[[nodiscard]] auto exceptionsUpdatesRealtime() const
-> rpl::producer<DefaultNotify>;
[[nodiscard]] const base::flat_set<not_null<PeerData*>> &exceptions(
DefaultNotify type) const;
void clearExceptions(DefaultNotify type);
private: private:
static constexpr auto kDefaultNotifyTypes = 3;
struct DefaultValue { struct DefaultValue {
PeerNotifySettings settings; PeerNotifySettings settings;
rpl::event_stream<> updates; rpl::event_stream<> updates;
@ -114,7 +126,7 @@ private:
void cacheSound(const std::optional<NotifySound> &sound); void cacheSound(const std::optional<NotifySound> &sound);
[[nodiscard]] bool isMuted( [[nodiscard]] bool isMuted(
not_null<const Data::Thread*> thread, not_null<const Thread*> thread,
crl::time *changesIn) const; crl::time *changesIn) const;
[[nodiscard]] bool isMuted( [[nodiscard]] bool isMuted(
not_null<const PeerData*> peer, not_null<const PeerData*> peer,
@ -126,21 +138,22 @@ private:
not_null<const PeerData*> peer) const; not_null<const PeerData*> peer) const;
[[nodiscard]] bool settingsUnknown(not_null<const PeerData*> peer) const; [[nodiscard]] bool settingsUnknown(not_null<const PeerData*> peer) const;
[[nodiscard]] bool settingsUnknown( [[nodiscard]] bool settingsUnknown(
not_null<const Data::Thread*> thread) const; not_null<const Thread*> thread) const;
void unmuteByFinished(); void unmuteByFinished();
void unmuteByFinishedDelayed(crl::time delay); void unmuteByFinishedDelayed(crl::time delay);
void updateLocal(not_null<Data::Thread*> thread); void updateLocal(not_null<Thread*> thread);
void updateLocal(not_null<PeerData*> peer); void updateLocal(not_null<PeerData*> peer);
void updateLocal(DefaultNotify type); void updateLocal(DefaultNotify type);
void updateException(not_null<PeerData*> peer);
void exceptionsUpdated(DefaultNotify type);
const not_null<Session*> _owner; const not_null<Session*> _owner;
DefaultValue _defaultValues[3]; DefaultValue _defaultValues[3];
std::unordered_set<not_null<const PeerData*>> _mutedPeers; std::unordered_set<not_null<const PeerData*>> _mutedPeers;
std::unordered_map< std::unordered_map<not_null<ForumTopic*>, rpl::lifetime> _mutedTopics;
not_null<Data::ForumTopic*>,
rpl::lifetime> _mutedTopics;
base::Timer _unmuteByFinishedTimer; base::Timer _unmuteByFinishedTimer;
struct { struct {
@ -151,6 +164,14 @@ private:
rpl::lifetime pendingLifetime; rpl::lifetime pendingLifetime;
} _ringtones; } _ringtones;
rpl::event_stream<DefaultNotify> _exceptionsUpdates;
rpl::event_stream<DefaultNotify> _exceptionsUpdatesRealtime;
std::array<
base::flat_set<not_null<PeerData*>>,
kDefaultNotifyTypes> _exceptions;
std::array<mtpRequestId, kDefaultNotifyTypes> _exceptionsRequestId = {};
std::array<bool, kDefaultNotifyTypes> _exceptionsUpdatesScheduled = {};
}; };
} // namespace Data } // namespace Data

View file

@ -256,6 +256,15 @@ bool PeerNotifySettings::change(
SerializeSound(std::nullopt))); // stories_sound SerializeSound(std::nullopt))); // stories_sound
} }
bool PeerNotifySettings::resetToDefault() {
if (_known && !_value) {
return false;
}
_known = true;
_value = nullptr;
return true;
}
std::optional<TimeId> PeerNotifySettings::muteUntil() const { std::optional<TimeId> PeerNotifySettings::muteUntil() const {
return _value return _value
? _value->muteUntil() ? _value->muteUntil()

View file

@ -46,6 +46,7 @@ public:
std::optional<bool> silentPosts, std::optional<bool> silentPosts,
std::optional<NotifySound> sound, std::optional<NotifySound> sound,
std::optional<bool> storiesMuted); std::optional<bool> storiesMuted);
bool resetToDefault();
bool settingsUnknown() const; bool settingsUnknown() const;
std::optional<TimeId> muteUntil() const; std::optional<TimeId> muteUntil() const;

View file

@ -23,6 +23,12 @@ DialogRow {
unreadMarkDiameter: pixels; unreadMarkDiameter: pixels;
} }
ThreeStateIcon {
icon: icon;
over: icon;
active: icon;
}
ForumTopicIcon { ForumTopicIcon {
size: pixels; size: pixels;
font: font; font: font;
@ -316,38 +322,56 @@ dialogSearchFrom: IconButton(dialogCalendar) {
} }
dialogsChatTypeSkip: 3px; dialogsChatTypeSkip: 3px;
dialogsChatIcon: icon {{ "dialogs/dialogs_chat", dialogsChatIconFg, point(1px, 4px) }}; dialogsChatIcon: ThreeStateIcon {
dialogsChatIconOver: icon {{ "dialogs/dialogs_chat", dialogsChatIconFgOver, point(1px, 4px) }}; icon: icon {{ "dialogs/dialogs_chat", dialogsChatIconFg, point(1px, 4px) }};
dialogsChatIconActive: icon {{ "dialogs/dialogs_chat", dialogsChatIconFgActive, point(1px, 4px) }}; over: icon {{ "dialogs/dialogs_chat", dialogsChatIconFgOver, point(1px, 4px) }};
dialogsChannelIcon: icon {{ "dialogs/dialogs_channel", dialogsChatIconFg, point(3px, 4px) }}; active: icon {{ "dialogs/dialogs_chat", dialogsChatIconFgActive, point(1px, 4px) }};
dialogsChannelIconOver: icon {{ "dialogs/dialogs_channel", dialogsChatIconFgOver, point(3px, 4px) }}; }
dialogsChannelIconActive: icon {{ "dialogs/dialogs_channel", dialogsChatIconFgActive, point(3px, 4px) }}; dialogsChannelIcon: ThreeStateIcon {
dialogsBotIcon: icon {{ "dialogs/dialogs_bot", dialogsChatIconFg, point(1px, 3px) }}; icon: icon {{ "dialogs/dialogs_channel", dialogsChatIconFg, point(3px, 4px) }};
dialogsBotIconOver: icon {{ "dialogs/dialogs_bot", dialogsChatIconFgOver, point(1px, 3px) }}; over: icon {{ "dialogs/dialogs_channel", dialogsChatIconFgOver, point(3px, 4px) }};
dialogsBotIconActive: icon {{ "dialogs/dialogs_bot", dialogsChatIconFgActive, point(1px, 3px) }}; active: icon {{ "dialogs/dialogs_channel", dialogsChatIconFgActive, point(3px, 4px) }};
dialogsForumIcon: icon {{ "dialogs/dialogs_forum", dialogsChatIconFg, point(1px, 4px) }}; }
dialogsForumIconOver: icon {{ "dialogs/dialogs_forum", dialogsChatIconFgOver, point(1px, 4px) }}; dialogsBotIcon: ThreeStateIcon {
dialogsForumIconActive: icon {{ "dialogs/dialogs_forum", dialogsChatIconFgActive, point(1px, 4px) }}; 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 }}; dialogsArchiveUserpic: icon {{ "archive_userpic", historyPeerUserpicFg }};
dialogsRepliesUserpic: icon {{ "replies_userpic", historyPeerUserpicFg }}; dialogsRepliesUserpic: icon {{ "replies_userpic", historyPeerUserpicFg }};
dialogsInaccessibleUserpic: icon {{ "dialogs/inaccessible_userpic", historyPeerUserpicFg }}; dialogsInaccessibleUserpic: icon {{ "dialogs/inaccessible_userpic", historyPeerUserpicFg }};
dialogsSendStateSkip: 20px; dialogsSendStateSkip: 20px;
dialogsSendingIcon: icon {{ "dialogs/dialogs_sending", dialogsSendingIconFg, point(8px, 4px) }}; dialogsSendingIcon: ThreeStateIcon {
dialogsSendingIconOver: icon {{ "dialogs/dialogs_sending", dialogsSendingIconFgOver, point(8px, 4px) }}; icon: icon {{ "dialogs/dialogs_sending", dialogsSendingIconFg, point(8px, 4px) }};
dialogsSendingIconActive: icon {{ "dialogs/dialogs_sending", dialogsSendingIconFgActive, point(8px, 4px) }}; over: icon {{ "dialogs/dialogs_sending", dialogsSendingIconFgOver, point(8px, 4px) }};
dialogsSentIcon: icon {{ "dialogs/dialogs_sent", dialogsSentIconFg, point(10px, 4px) }}; active: icon {{ "dialogs/dialogs_sending", dialogsSendingIconFgActive, point(8px, 4px) }};
dialogsSentIconOver: icon {{ "dialogs/dialogs_sent", dialogsSentIconFgOver, point(10px, 4px) }}; }
dialogsSentIconActive: icon {{ "dialogs/dialogs_sent", dialogsSentIconFgActive, point(10px, 4px) }}; dialogsSentIcon: ThreeStateIcon {
dialogsReceivedIcon: icon {{ "dialogs/dialogs_received", dialogsSentIconFg, point(5px, 4px) }}; icon: icon {{ "dialogs/dialogs_sent", dialogsSentIconFg, point(10px, 4px) }};
dialogsReceivedIconOver: icon {{ "dialogs/dialogs_received", dialogsSentIconFgOver, point(5px, 4px) }}; over: icon {{ "dialogs/dialogs_sent", dialogsSentIconFgOver, point(10px, 4px) }};
dialogsReceivedIconActive: icon {{ "dialogs/dialogs_received", dialogsSentIconFgActive, point(5px, 4px) }}; active: icon {{ "dialogs/dialogs_sent", dialogsSentIconFgActive, point(10px, 4px) }};
dialogsPinnedIcon: icon {{ "dialogs/dialogs_pinned", dialogsUnreadBgMuted }}; }
dialogsPinnedIconOver: icon {{ "dialogs/dialogs_pinned", dialogsUnreadBgMutedOver }}; dialogsReceivedIcon: ThreeStateIcon {
dialogsPinnedIconActive: icon {{ "dialogs/dialogs_pinned", dialogsUnreadBgMutedActive }}; icon: icon {{ "dialogs/dialogs_received", dialogsSentIconFg, point(5px, 4px) }};
dialogsLockIcon: icon {{ "emoji/premium_lock", dialogsUnreadBgMuted, point(4px, 0px) }}; over: icon {{ "dialogs/dialogs_received", dialogsSentIconFgOver, point(5px, 4px) }};
dialogsLockIconOver: icon {{ "emoji/premium_lock", dialogsUnreadBgMutedOver, point(4px, 0px) }}; active: icon {{ "dialogs/dialogs_received", dialogsSentIconFgActive, point(5px, 4px) }};
dialogsLockIconActive: icon {{ "emoji/premium_lock", dialogsUnreadBgMutedActive, point(4px, 0px) }}; }
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 { dialogsVerifiedIcon: icon {
{ "dialogs/dialogs_verified_star", dialogsVerifiedIconBg }, { "dialogs/dialogs_verified_star", dialogsVerifiedIconBg },
@ -361,9 +385,11 @@ dialogsVerifiedIconActive: icon {
{ "dialogs/dialogs_verified_star", dialogsVerifiedIconBgActive }, { "dialogs/dialogs_verified_star", dialogsVerifiedIconBgActive },
{ "dialogs/dialogs_verified_check", dialogsVerifiedIconFgActive }, { "dialogs/dialogs_verified_check", dialogsVerifiedIconFgActive },
}; };
dialogsPremiumIcon: icon {{ "dialogs/dialogs_premium", dialogsVerifiedIconBg }}; dialogsPremiumIcon: ThreeStateIcon {
dialogsPremiumIconOver: icon {{ "dialogs/dialogs_premium", dialogsVerifiedIconBgOver }}; icon: icon {{ "dialogs/dialogs_premium", dialogsVerifiedIconBg }};
dialogsPremiumIconActive: icon {{ "dialogs/dialogs_premium", dialogsVerifiedIconBgActive }}; over: icon {{ "dialogs/dialogs_premium", dialogsVerifiedIconBgOver }};
active: icon {{ "dialogs/dialogs_premium", dialogsVerifiedIconBgActive }};
}
historySendingIcon: icon {{ "dialogs/dialogs_sending", historySendingOutIconFg, point(5px, 5px) }}; historySendingIcon: icon {{ "dialogs/dialogs_sending", historySendingOutIconFg, point(5px, 5px) }};
historySendingInvertedIcon: icon {{ "dialogs/dialogs_sending", historySendingInvertedIconFg, point(5px, 5px) }}; historySendingInvertedIcon: icon {{ "dialogs/dialogs_sending", historySendingInvertedIconFg, point(5px, 5px) }};
@ -436,12 +462,28 @@ dialogsMiniPreviewSkip: 2px;
dialogsMiniPreviewRight: 3px; dialogsMiniPreviewRight: 3px;
dialogsMiniPlay: icon{{ "dialogs/dialogs_mini_play", videoPlayIconFg }}; dialogsMiniPlay: icon{{ "dialogs/dialogs_mini_play", videoPlayIconFg }};
dialogsUnreadMention: icon{{ "dialogs/dialogs_mention", dialogsUnreadFg }}; dialogsMiniForwardIcon: ThreeStateIcon {
dialogsUnreadMentionOver: icon{{ "dialogs/dialogs_mention", dialogsUnreadFgOver }}; icon: icon {{ "mini_forward", dialogsTextFg, point(0px, 1px) }};
dialogsUnreadMentionActive: icon{{ "dialogs/dialogs_mention", dialogsUnreadFgActive }}; over: icon {{ "mini_forward", dialogsTextFgOver, point(0px, 1px) }};
dialogsUnreadReaction: icon{{ "dialogs/dialogs_reaction", dialogsUnreadFg }}; active: icon {{ "mini_forward", dialogsTextFgActive, point(0px, 1px) }};
dialogsUnreadReactionOver: icon{{ "dialogs/dialogs_reaction", dialogsUnreadFgOver }}; }
dialogsUnreadReactionActive: icon{{ "dialogs/dialogs_reaction", dialogsUnreadFgActive }}; 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; downloadBarHeight: 46px;
downloadArrow: icon{{ "fast_to_original", menuIconFg }}; downloadArrow: icon{{ "fast_to_original", menuIconFg }};

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "dialogs/dialogs_inner_widget.h" #include "dialogs/dialogs_inner_widget.h"
#include "dialogs/dialogs_three_state_icon.h"
#include "dialogs/ui/dialogs_layout.h" #include "dialogs/ui/dialogs_layout.h"
#include "dialogs/ui/dialogs_stories_content.h" #include "dialogs/ui/dialogs_stories_content.h"
#include "dialogs/ui/dialogs_stories_list.h" #include "dialogs/ui/dialogs_stories_list.h"
@ -1010,11 +1011,10 @@ void InnerWidget::paintPeerSearchResult(
: context.selected : context.selected
? &st::dialogsVerifiedIconOver ? &st::dialogsVerifiedIconOver
: &st::dialogsVerifiedIcon), : &st::dialogsVerifiedIcon),
.premium = (context.active .premium = &ThreeStateIcon(
? &st::dialogsPremiumIconActive st::dialogsPremiumIcon,
: context.selected context.active,
? &st::dialogsPremiumIconOver context.selected),
: &st::dialogsPremiumIcon),
.scam = (context.active .scam = (context.active
? &st::dialogsScamFgActive ? &st::dialogsScamFgActive
: context.selected : context.selected

View file

@ -194,6 +194,11 @@ UnreadState MainList::unreadState() const {
result.chatsMuted = result.chats; result.chatsMuted = result.chats;
result.marksMuted = result.marks; 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; return result;
} }

View file

@ -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

View file

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_forum_topic.h" #include "data/data_forum_topic.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "dialogs/dialogs_list.h" #include "dialogs/dialogs_list.h"
#include "dialogs/dialogs_three_state_icon.h"
#include "dialogs/ui/dialogs_video_userpic.h" #include "dialogs/ui/dialogs_video_userpic.h"
#include "styles/style_dialogs.h" #include "styles/style_dialogs.h"
#include "styles/style_window.h" #include "styles/style_window.h"
@ -147,11 +148,10 @@ int PaintBadges(
const auto badge = PaintUnreadBadge(p, counter, right, top, st); const auto badge = PaintUnreadBadge(p, counter, right, top, st);
right -= badge.width() + st.padding; right -= badge.width() + st.padding;
} else if (displayPinnedIcon) { } else if (displayPinnedIcon) {
const auto &icon = context.active const auto &icon = ThreeStateIcon(
? st::dialogsPinnedIconActive st::dialogsPinnedIcon,
: context.selected context.active,
? st::dialogsPinnedIconOver context.selected);
: st::dialogsPinnedIcon;
icon.paint(p, right - icon.width(), pinnedIconTop, context.width); icon.paint(p, right - icon.width(), pinnedIconTop, context.width);
right -= icon.width() + st::dialogsUnreadPadding; right -= icon.width() + st::dialogsUnreadPadding;
} }
@ -169,17 +169,12 @@ int PaintBadges(
st.textTop = 0; st.textTop = 0;
const auto counter = QString(); const auto counter = QString();
const auto badge = PaintUnreadBadge(p, counter, right, top, st); const auto badge = PaintUnreadBadge(p, counter, right, top, st);
(badgesState.mention ThreeStateIcon(
? (st.active badgesState.mention
? st::dialogsUnreadMentionActive ? st::dialogsUnreadMention
: st.selected : st::dialogsUnreadReaction,
? st::dialogsUnreadMentionOver st.active,
: st::dialogsUnreadMention) st.selected).paintInCenter(p, badge);
: (st.active
? st::dialogsUnreadReactionActive
: st.selected
? st::dialogsUnreadReactionOver
: st::dialogsUnreadReaction)).paintInCenter(p, badge);
right -= badge.width() + st.padding + st::dialogsUnreadPadding; right -= badge.width() + st.padding + st::dialogsUnreadPadding;
} }
return (initial - right); return (initial - right);
@ -437,11 +432,10 @@ void PaintRow(
auto availableWidth = namewidth; auto availableWidth = namewidth;
if (entry->isPinnedDialog(context.filter) if (entry->isPinnedDialog(context.filter)
&& (context.filter || !entry->fixedOnTopIndex())) { && (context.filter || !entry->fixedOnTopIndex())) {
auto &icon = context.active auto &icon = ThreeStateIcon(
? st::dialogsPinnedIconActive st::dialogsPinnedIcon,
: context.selected context.active,
? st::dialogsPinnedIconOver context.selected);
: st::dialogsPinnedIcon;
icon.paint( icon.paint(
p, p,
context.width - context.st->padding.right() - icon.width(), context.width - context.st->padding.right() - icon.width(),
@ -527,11 +521,10 @@ void PaintRow(
auto availableWidth = namewidth; auto availableWidth = namewidth;
if (entry->isPinnedDialog(context.filter) if (entry->isPinnedDialog(context.filter)
&& (context.filter || !entry->fixedOnTopIndex())) { && (context.filter || !entry->fixedOnTopIndex())) {
auto &icon = context.active auto &icon = ThreeStateIcon(
? st::dialogsPinnedIconActive st::dialogsPinnedIcon,
: context.selected context.active,
? st::dialogsPinnedIconOver context.selected);
: st::dialogsPinnedIcon;
icon.paint(p, context.width - context.st->padding.right() - icon.width(), texttop, context.width); icon.paint(p, context.width - context.st->padding.right() - icon.width(), texttop, context.width);
availableWidth -= icon.width() + st::dialogsUnreadPadding; availableWidth -= icon.width() + st::dialogsUnreadPadding;
} }
@ -561,51 +554,49 @@ void PaintRow(
paintItemCallback(nameleft, namewidth); paintItemCallback(nameleft, namewidth);
} else if (entry->isPinnedDialog(context.filter) } else if (entry->isPinnedDialog(context.filter)
&& (context.filter || !entry->fixedOnTopIndex())) { && (context.filter || !entry->fixedOnTopIndex())) {
auto &icon = context.active auto &icon = ThreeStateIcon(
? st::dialogsPinnedIconActive st::dialogsPinnedIcon,
: context.selected context.active,
? st::dialogsPinnedIconOver context.selected);
: st::dialogsPinnedIcon; icon.paint(
icon.paint(p, context.width - context.st->padding.right() - icon.width(), texttop, context.width); p,
context.width - context.st->padding.right() - icon.width(),
texttop,
context.width);
} }
const auto sendStateIcon = [&]() -> const style::icon* { const auto sendStateIcon = [&]() -> const style::icon* {
if (!thread) { if (!thread) {
return nullptr; return nullptr;
} else if (const auto topic = thread->asTopic() } else if (const auto topic = thread->asTopic()
; !context.search && topic && topic->closed()) { ; !context.search && topic && topic->closed()) {
return &(context.active return &ThreeStateIcon(
? st::dialogsLockIconActive st::dialogsLockIcon,
: context.selected context.active,
? st::dialogsLockIconOver context.selected);
: st::dialogsLockIcon);
} else if (draft) { } else if (draft) {
if (draft->saveRequestId) { if (draft->saveRequestId) {
return &(context.active return &ThreeStateIcon(
? st::dialogsSendingIconActive st::dialogsSendingIcon,
: context.selected context.active,
? st::dialogsSendingIconOver context.selected);
: st::dialogsSendingIcon);
} }
} else if (item && !item->isEmpty() && item->needCheck()) { } else if (item && !item->isEmpty() && item->needCheck()) {
if (!item->isSending() && !item->hasFailed()) { if (!item->isSending() && !item->hasFailed()) {
if (item->unread(thread)) { if (item->unread(thread)) {
return &(context.active return &ThreeStateIcon(
? st::dialogsSentIconActive st::dialogsSentIcon,
: context.selected context.active,
? st::dialogsSentIconOver context.selected);
: st::dialogsSentIcon);
} }
return &(context.active return &ThreeStateIcon(
? st::dialogsReceivedIconActive st::dialogsReceivedIcon,
: context.selected context.active,
? st::dialogsReceivedIconOver context.selected);
: st::dialogsReceivedIcon);
} }
return &(context.active return &ThreeStateIcon(
? st::dialogsSendingIconActive st::dialogsSendingIcon,
: context.selected context.active,
? st::dialogsSendingIconOver context.selected);
: st::dialogsSendingIcon);
} }
return nullptr; return nullptr;
}(); }();
@ -643,11 +634,10 @@ void PaintRow(
: context.selected : context.selected
? &st::dialogsVerifiedIconOver ? &st::dialogsVerifiedIconOver
: &st::dialogsVerifiedIcon), : &st::dialogsVerifiedIcon),
.premium = (context.active .premium = &ThreeStateIcon(
? &st::dialogsPremiumIconActive st::dialogsPremiumIcon,
: context.selected context.active,
? &st::dialogsPremiumIconOver context.selected),
: &st::dialogsPremiumIcon),
.scam = (context.active .scam = (context.active
? &st::dialogsScamFgActive ? &st::dialogsScamFgActive
: context.selected : context.selected
@ -710,30 +700,26 @@ const style::icon *ChatTypeIcon(
const PaintContext &context) { const PaintContext &context) {
if (const auto user = peer->asUser()) { if (const auto user = peer->asUser()) {
if (ShowUserBotIcon(user)) { if (ShowUserBotIcon(user)) {
return &(context.active return &ThreeStateIcon(
? st::dialogsBotIconActive st::dialogsBotIcon,
: context.selected context.active,
? st::dialogsBotIconOver context.selected);
: st::dialogsBotIcon);
} }
} else if (peer->isBroadcast()) { } else if (peer->isBroadcast()) {
return &(context.active return &ThreeStateIcon(
? st::dialogsChannelIconActive st::dialogsChannelIcon,
: context.selected context.active,
? st::dialogsChannelIconOver context.selected);
: st::dialogsChannelIcon);
} else if (peer->isForum()) { } else if (peer->isForum()) {
return &(context.active return &ThreeStateIcon(
? st::dialogsForumIconActive st::dialogsForumIcon,
: context.selected context.active,
? st::dialogsForumIconOver context.selected);
: st::dialogsForumIcon);
} else { } else {
return &(context.active return &ThreeStateIcon(
? st::dialogsChatIconActive st::dialogsChatIcon,
: context.selected context.active,
? st::dialogsChatIconOver context.selected);
: st::dialogsChatIcon);
} }
return nullptr; return nullptr;
} }

View file

@ -11,12 +11,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item.h" #include "history/history_item.h"
#include "history/view/history_view_item_preview.h" #include "history/view/history_view_item_preview.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "dialogs/dialogs_three_state_icon.h"
#include "dialogs/ui/dialogs_layout.h" #include "dialogs/ui/dialogs_layout.h"
#include "dialogs/ui/dialogs_topics_view.h" #include "dialogs/ui/dialogs_topics_view.h"
#include "ui/effects/spoiler_mess.h" #include "ui/effects/spoiler_mess.h"
#include "ui/text/text_options.h" #include "ui/text/text_options.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/image/image.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "ui/power_saving.h" #include "ui/power_saving.h"
#include "core/ui_integration.h" #include "core/ui_integration.h"
@ -159,6 +159,11 @@ void MessageView::prepare(
options.ignoreTopic = true; options.ignoreTopic = true;
options.spoilerLoginCode = true; options.spoilerLoginCode = true;
auto preview = item->toPreview(options); 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 hasImages = !preview.images.empty();
const auto history = item->history(); const auto history = item->history();
const auto context = Core::MarkedTextContext{ const auto context = Core::MarkedTextContext{
@ -169,7 +174,7 @@ void MessageView::prepare(
const auto senderTill = (preview.arrowInTextPosition > 0) const auto senderTill = (preview.arrowInTextPosition > 0)
? preview.arrowInTextPosition ? preview.arrowInTextPosition
: preview.imagesInTextPosition; : preview.imagesInTextPosition;
if (hasImages && senderTill > 0) { if ((hasImages || _leftIcon) && senderTill > 0) {
auto sender = Text::Mid(preview.text, 0, senderTill); auto sender = Text::Mid(preview.text, 0, senderTill);
TextUtilities::Trim(sender); TextUtilities::Trim(sender);
_senderCache.setMarkedText( _senderCache.setMarkedText(
@ -314,6 +319,15 @@ void MessageView::paint(
rect.setLeft(rect.x() + skip); 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) { for (const auto &image : _imagesCache) {
if (rect.width() < st::dialogsMiniPreview) { if (rect.width() < st::dialogsMiniPreview) {
break; break;

View file

@ -15,6 +15,7 @@ enum class ImageRoundRadius;
namespace style { namespace style {
struct DialogRow; struct DialogRow;
struct ThreeStateIcon;
} // namespace style } // namespace style
namespace Ui { namespace Ui {
@ -92,6 +93,7 @@ private:
mutable std::vector<ItemPreviewImage> _imagesCache; mutable std::vector<ItemPreviewImage> _imagesCache;
mutable std::unique_ptr<SpoilerAnimation> _spoiler; mutable std::unique_ptr<SpoilerAnimation> _spoiler;
mutable std::unique_ptr<LoadingContext> _loadingContext; mutable std::unique_ptr<LoadingContext> _loadingContext;
mutable const style::ThreeStateIcon *_leftIcon = nullptr;
}; };

View file

@ -10,19 +10,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/algorithm.h" #include "base/algorithm.h"
#include "logs.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 <deque>
#endif // !DESKTOP_APP_USE_PACKAGED && !Q_OS_WIN && !Q_OS_MAC
#include <QImage> #include <QImage>
#ifdef LIB_FFMPEG_USE_QT_PRIVATE_API #ifdef LIB_FFMPEG_USE_QT_PRIVATE_API
#include <private/qdrawhelper_p.h> #include <private/qdrawhelper_p.h>
#endif // LIB_FFMPEG_USE_QT_PRIVATE_API #endif // LIB_FFMPEG_USE_QT_PRIVATE_API
#include <deque>
extern "C" { extern "C" {
#include <libavutil/opt.h> #include <libavutil/opt.h>
#if !defined DESKTOP_APP_USE_PACKAGED && !defined Q_OS_WIN && !defined Q_OS_MAC
#include <dlfcn.h>
#endif // !DESKTOP_APP_USE_PACKAGED && !Q_OS_WIN && !Q_OS_MAC
} // extern "C" } // extern "C"
namespace FFmpeg { namespace FFmpeg {
@ -95,19 +95,10 @@ void PremultiplyLine(uchar *dst, const uchar *src, int intsCount) {
auto list = std::deque{ auto list = std::deque{
AV_PIX_FMT_CUDA, AV_PIX_FMT_CUDA,
}; };
const auto vdpau = [&] { if (base::Platform::LoadLibrary("libvdpau.so.1")) {
if (const auto handle = dlopen("libvdpau.so.1", RTLD_LAZY)) {
dlclose(handle);
}
if (dlerror()) {
return false;
}
return true;
}();
if (vdpau) {
list.push_front(AV_PIX_FMT_VDPAU); list.push_front(AV_PIX_FMT_VDPAU);
} }
const auto va = [&] { if ([&] {
const auto list = std::array{ const auto list = std::array{
"libva-drm.so.1", "libva-drm.so.1",
"libva-x11.so.1", "libva-x11.so.1",
@ -115,16 +106,12 @@ void PremultiplyLine(uchar *dst, const uchar *src, int intsCount) {
"libdrm.so.2", "libdrm.so.2",
}; };
for (const auto lib : list) { for (const auto lib : list) {
if (const auto handle = dlopen(lib, RTLD_LAZY)) { if (!base::Platform::LoadLibrary(lib)) {
dlclose(handle);
}
if (dlerror()) {
return false; return false;
} }
} }
return true; return true;
}(); }()) {
if (va) {
list.push_front(AV_PIX_FMT_VAAPI); list.push_front(AV_PIX_FMT_VAAPI);
} }
return list; return list;

View file

@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_inner_widget.h" #include "history/history_inner_widget.h"
#include "core/file_utilities.h" #include "core/file_utilities.h"
#include "core/crash_reports.h"
#include "core/click_handler_types.h" #include "core/click_handler_types.h"
#include "history/history.h" #include "history/history.h"
#include "history/admin_log/history_admin_log_item.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_add_action_callback_factory.h"
#include "ui/widgets/menu/menu_multiline_action.h" #include "ui/widgets/menu/menu_multiline_action.h"
#include "ui/widgets/popup_menu.h" #include "ui/widgets/popup_menu.h"
#include "ui/image/image.h"
#include "ui/effects/path_shift_gradient.h" #include "ui/effects/path_shift_gradient.h"
#include "ui/effects/message_sending_animation_controller.h" #include "ui/effects/message_sending_animation_controller.h"
#include "ui/effects/reaction_fly_animation.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/boxes/report_box.h"
#include "ui/layers/generic_box.h" #include "ui/layers/generic_box.h"
#include "ui/controls/delete_message_context_action.h" #include "ui/controls/delete_message_context_action.h"
#include "ui/controls/who_reacted_context_action.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "ui/ui_utility.h" #include "ui/ui_utility.h"
#include "ui/cached_round_corners.h"
#include "ui/inactive_press.h" #include "ui/inactive_press.h"
#include "window/window_adaptive.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "window/window_controller.h" #include "window/window_controller.h"
#include "window/window_peer_menu.h" #include "window/window_peer_menu.h"
#include "window/window_controller.h"
#include "window/notifications_manager.h" #include "window/notifications_manager.h"
#include "boxes/about_sponsored_box.h" #include "boxes/about_sponsored_box.h"
#include "boxes/delete_messages_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_file_origin.h"
#include "data/data_histories.h" #include "data/data_histories.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/stickers/data_stickers.h"
#include "data/data_sponsored_messages.h" #include "data/data_sponsored_messages.h"
#include "dialogs/ui/dialogs_video_userpic.h" #include "dialogs/ui/dialogs_video_userpic.h"
#include "settings/settings_premium.h" #include "settings/settings_premium.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
#include "styles/style_window.h" // st::windowMinWidth
#include "styles/style_menu_icons.h" #include "styles/style_menu_icons.h"
#include <QtGui/QClipboard> #include <QtGui/QClipboard>

View file

@ -15,17 +15,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_message.h" #include "history/view/history_view_message.h"
#include "history/view/history_view_service_message.h" #include "history/view/history_view_service_message.h"
#include "history/view/media/history_view_media_grouped.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_components.h"
#include "history/history_item_helpers.h" #include "history/history_item_helpers.h"
#include "history/history_unread_things.h" #include "history/history_unread_things.h"
#include "history/history.h" #include "history/history.h"
#include "mtproto/mtproto_config.h" #include "mtproto/mtproto_config.h"
#include "media/clip/media_clip_reader.h" #include "media/clip/media_clip_reader.h"
#include "ui/effects/ripple_animation.h"
#include "ui/text/format_values.h" #include "ui/text/format_values.h"
#include "ui/text/text_isolated_emoji.h" #include "ui/text/text_isolated_emoji.h"
#include "ui/text/text_options.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "storage/file_upload.h" #include "storage/file_upload.h"
#include "storage/storage_facade.h" #include "storage/storage_facade.h"
@ -41,7 +38,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mainwindow.h" #include "mainwindow.h"
#include "window/window_controller.h" #include "window/window_controller.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "core/crash_reports.h"
#include "core/click_handler_types.h" #include "core/click_handler_types.h"
#include "base/unixtime.h" #include "base/unixtime.h"
#include "base/timer_rpl.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 "chat_helpers/stickers_gift_box_pack.h"
#include "payments/payments_checkout_process.h" // CheckoutProcess::Start. #include "payments/payments_checkout_process.h" // CheckoutProcess::Start.
#include "styles/style_dialogs.h" #include "styles/style_dialogs.h"
#include "styles/style_chat.h"
#include "ayu/ayu_settings.h" #include "ayu/ayu_settings.h"
@ -2972,6 +2967,11 @@ ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const {
? tr::lng_from_you(tr::now) ? tr::lng_from_you(tr::now)
: sender->shortName(); : sender->shortName();
}; };
result.icon = (Get<HistoryMessageForwarded>() != nullptr)
? ItemPreview::Icon::ForwardedMessage
: replyToStory().valid()
? ItemPreview::Icon::ReplyToStory
: ItemPreview::Icon::None;
const auto fromForwarded = [&]() -> std::optional<QString> { const auto fromForwarded = [&]() -> std::optional<QString> {
if (const auto forwarded = Get<HistoryMessageForwarded>()) { if (const auto forwarded = Get<HistoryMessageForwarded>()) {
return forwarded->originalSender return forwarded->originalSender

View file

@ -43,6 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_bot.h" #include "api/api_bot.h"
#include "styles/style_widgets.h" #include "styles/style_widgets.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
#include "styles/style_dialogs.h" // dialogsMiniReplyStoryIcon.
#include <QtGui/QGuiApplication> #include <QtGui/QGuiApplication>
@ -460,7 +461,14 @@ void HistoryMessageReply::updateName(
w += st::msgServiceFont->spacew + replyToVia->maxWidth; 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 { } else {
maxReplyWidth = st::msgDateFont->width(statePhrase()); maxReplyWidth = st::msgDateFont->width(statePhrase());
} }
@ -596,14 +604,27 @@ void HistoryMessageReply::paint(
? stm->historyTextFg ? stm->historyTextFg
: st->msgImgReplyBarColor()); : st->msgImgReplyBarColor());
holder->prepareCustomEmojiPaint(p, context, replyToText); 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, { replyToText.draw(p, {
.position = QPoint( .position = replyToTextPosition,
x + st::msgReplyBarSkip + previewSkip,
y + st::msgReplyPadding.top() + st::msgServiceNameFont->height),
.availableWidth = w - st::msgReplyBarSkip - previewSkip, .availableWidth = w - st::msgReplyBarSkip - previewSkip,
.palette = &(inBubble .palette = replyToTextPalette,
? stm->replyTextPalette
: st->imgReplyTextPalette()),
.spoiler = Ui::Text::DefaultSpoilerCache(), .spoiler = Ui::Text::DefaultSpoilerCache(),
.now = context.now, .now = context.now,
.pausedEmoji = (context.paused .pausedEmoji = (context.paused

View file

@ -12,7 +12,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_chat_participants.h" #include "api/api_chat_participants.h"
#include "api/api_report.h" #include "api/api_report.h"
#include "api/api_sending.h" #include "api/api_sending.h"
#include "api/api_text_entities.h"
#include "api/api_send_progress.h" #include "api/api_send_progress.h"
#include "api/api_unread_things.h" #include "api/api_unread_things.h"
#include "ui/boxes/confirm_box.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/inner_dropdown.h"
#include "ui/widgets/dropdown_menu.h" #include "ui/widgets/dropdown_menu.h"
#include "ui/widgets/labels.h" #include "ui/widgets/labels.h"
#include "ui/widgets/shadow.h"
#include "ui/effects/ripple_animation.h" #include "ui/effects/ripple_animation.h"
#include "ui/effects/message_sending_animation_controller.h" #include "ui/effects/message_sending_animation_controller.h"
#include "ui/text/text_utilities.h" // Ui::Text::ToUpper #include "ui/text/text_utilities.h" // Ui::Text::ToUpper
#include "ui/text/format_values.h" #include "ui/text/format_values.h"
#include "ui/chat/forward_options_box.h"
#include "ui/chat/message_bar.h" #include "ui/chat/message_bar.h"
#include "ui/chat/attach/attach_send_files_way.h" #include "ui/chat/attach/attach_send_files_way.h"
#include "ui/chat/choose_send_as.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_document.h"
#include "data/data_photo.h" #include "data/data_photo.h"
#include "data/data_photo_media.h" #include "data/data_photo_media.h"
#include "data/data_media_types.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_chat.h" #include "data/data_chat.h"
#include "data/data_forum.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/history_view_translate_bar.h"
#include "history/view/media/history_view_media.h" #include "history/view/media/history_view_media.h"
#include "profile/profile_block_group_members.h" #include "profile/profile_block_group_members.h"
#include "info/info_memento.h"
#include "core/click_handler_types.h" #include "core/click_handler_types.h"
#include "chat_helpers/tabbed_panel.h" #include "chat_helpers/tabbed_panel.h"
#include "chat_helpers/tabbed_selector.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/chat/continuous_scroll.h"
#include "ui/widgets/popup_menu.h" #include "ui/widgets/popup_menu.h"
#include "ui/item_text_options.h" #include "ui/item_text_options.h"
#include "ui/unread_badge.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "main/main_session_settings.h" #include "main/main_session_settings.h"
#include "main/session/send_as_peers.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 "inline_bots/bot_attach_web_view.h"
#include "info/profile/info_profile_values.h" // SharedMediaCountValue. #include "info/profile/info_profile_values.h" // SharedMediaCountValue.
#include "chat_helpers/emoji_suggestions_widget.h" #include "chat_helpers/emoji_suggestions_widget.h"
#include "core/crash_reports.h"
#include "core/shortcuts.h" #include "core/shortcuts.h"
#include "core/ui_integration.h" #include "core/ui_integration.h"
#include "support/support_common.h" #include "support/support_common.h"
@ -160,12 +153,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "support/support_preload.h" #include "support/support_preload.h"
#include "dialogs/dialogs_key.h" #include "dialogs/dialogs_key.h"
#include "calls/calls_instance.h" #include "calls/calls_instance.h"
#include "api/api_bot.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
#include "styles/style_dialogs.h" #include "styles/style_dialogs.h"
#include "styles/style_window.h" #include "styles/style_window.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "styles/style_profile.h"
#include "styles/style_chat_helpers.h" #include "styles/style_chat_helpers.h"
#include "styles/style_info.h" #include "styles/style_info.h"

View file

@ -23,11 +23,17 @@ struct ItemPreviewImage {
}; };
struct ItemPreview { struct ItemPreview {
enum class Icon {
None,
ForwardedMessage,
ReplyToStory,
};
TextWithEntities text; TextWithEntities text;
std::vector<ItemPreviewImage> images; std::vector<ItemPreviewImage> images;
int arrowInTextPosition = -1; int arrowInTextPosition = -1;
int imagesInTextPosition = 0; int imagesInTextPosition = 0;
std::any loadingContext; std::any loadingContext;
Icon icon = Icon::None;
}; };
struct ToPreviewOptions { struct ToPreviewOptions {

View file

@ -759,7 +759,9 @@ QSize Message::performCountOptimalSize() {
: item->hiddenSenderInfo()->nameText(); : item->hiddenSenderInfo()->nameText();
auto namew = st::msgPadding.left() auto namew = st::msgPadding.left()
+ name.maxWidth() + name.maxWidth()
+ (_fromNameStatus ? st::dialogsPremiumIcon.width() : 0) + (_fromNameStatus
? st::dialogsPremiumIcon.icon.width()
: 0)
+ st::msgPadding.right(); + st::msgPadding.right();
if (via && !displayForwardedFrom()) { if (via && !displayForwardedFrom()) {
namew += st::msgServiceFont->spacew + via->maxWidth namew += st::msgServiceFont->spacew + via->maxWidth
@ -1358,7 +1360,7 @@ void Message::paintFromName(
return &info->nameText(); return &info->nameText();
}(); }();
const auto statusWidth = _fromNameStatus const auto statusWidth = _fromNameStatus
? st::dialogsPremiumIcon.width() ? st::dialogsPremiumIcon.icon.width()
: 0; : 0;
if (statusWidth && availableWidth > statusWidth) { if (statusWidth && availableWidth > statusWidth) {
const auto x = availableLeft const auto x = availableLeft
@ -1398,7 +1400,7 @@ void Message::paintFromName(
.paused = context.paused || On(PowerSaving::kEmojiStatus), .paused = context.paused || On(PowerSaving::kEmojiStatus),
}); });
} else { } else {
st::dialogsPremiumIcon.paint(p, x, y, width(), color); st::dialogsPremiumIcon.icon.paint(p, x, y, width(), color);
} }
availableWidth -= statusWidth; availableWidth -= statusWidth;
} }
@ -1407,7 +1409,8 @@ void Message::paintFromName(
nameText->drawElided(p, availableLeft, trect.top(), availableWidth); nameText->drawElided(p, availableLeft, trect.top(), availableWidth);
const auto skipWidth = nameText->maxWidth() const auto skipWidth = nameText->maxWidth()
+ (_fromNameStatus + (_fromNameStatus
? (st::dialogsPremiumIcon.width() + st::msgServiceFont->spacew) ? (st::dialogsPremiumIcon.icon.width()
+ st::msgServiceFont->spacew)
: 0) : 0)
+ st::msgServiceFont->spacew; + st::msgServiceFont->spacew;
availableLeft += skipWidth; availableLeft += skipWidth;
@ -3525,7 +3528,7 @@ void Message::fromNameUpdated(int width) const {
- st::msgPadding.right() - st::msgPadding.right()
- nameText->maxWidth() - nameText->maxWidth()
+ (_fromNameStatus + (_fromNameStatus
? (st::dialogsPremiumIcon.width() ? (st::dialogsPremiumIcon.icon.width()
+ st::msgServiceFont->spacew) + st::msgServiceFont->spacew)
: 0) : 0)
- st::msgServiceFont->spacew); - st::msgServiceFont->spacew);

View file

@ -566,7 +566,7 @@ void TopBarWidget::paintTopBar(Painter &p) {
{ {
.peer = peer, .peer = peer,
.verified = &st::dialogsVerifiedIcon, .verified = &st::dialogsVerifiedIcon,
.premium = &st::dialogsPremiumIcon, .premium = &st::dialogsPremiumIcon.icon,
.scam = &st::attentionButtonFg, .scam = &st::attentionButtonFg,
.premiumFg = &st::dialogsVerifiedIconBg, .premiumFg = &st::dialogsVerifiedIconBg,
.customEmojiRepaint = [=] { update(); }, .customEmojiRepaint = [=] { update(); },

View file

@ -241,7 +241,7 @@ int Selector::countWidth(int desiredWidth, int maxWidth) {
return std::max(2 * _skipx + _columns * _size, desiredWidth); return std::max(2 * _skipx + _columns * _size, desiredWidth);
} }
QMargins Selector::extentsForShadow() const { QMargins Selector::marginsForShadow() const {
const auto line = st::lineWidth; const auto line = st::lineWidth;
return useTransparency() return useTransparency()
? st::reactionCornerShadow ? st::reactionCornerShadow
@ -264,26 +264,26 @@ void Selector::setSpecialExpandTopSkip(int skip) {
} }
void Selector::initGeometry(int innerTop) { void Selector::initGeometry(int innerTop) {
const auto extents = extentsForShadow(); const auto margins = marginsForShadow();
const auto parent = parentWidget()->rect(); const auto parent = parentWidget()->rect();
const auto innerWidth = 2 * _skipx + _columns * _size; const auto innerWidth = 2 * _skipx + _columns * _size;
const auto innerHeight = st::reactStripHeight; const auto innerHeight = st::reactStripHeight;
const auto width = _useTransparency const auto width = _useTransparency
? (innerWidth + extents.left() + extents.right()) ? (innerWidth + margins.left() + margins.right())
: parent.width(); : 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); const auto left = style::RightToLeft() ? 0 : (parent.width() - width);
_collapsedTopSkip = _useTransparency _collapsedTopSkip = _useTransparency
? (extendTopForCategories() + _specialExpandTopSkip) ? (extendTopForCategories() + _specialExpandTopSkip)
: 0; : 0;
const auto top = innerTop - extents.top() - _collapsedTopSkip; const auto top = innerTop - margins.top() - _collapsedTopSkip;
const auto add = _st.icons.stripBubble.height() - extents.bottom(); const auto add = _st.icons.stripBubble.height() - margins.bottom();
_outer = QRect(0, _collapsedTopSkip, width, height); _outer = QRect(0, _collapsedTopSkip, width, height);
_outerWithBubble = _outer.marginsAdded({ 0, 0, 0, add }); _outerWithBubble = _outer.marginsAdded({ 0, 0, 0, add });
setGeometry(_outerWithBubble.marginsAdded( setGeometry(_outerWithBubble.marginsAdded(
{ 0, _collapsedTopSkip, 0, 0 } { 0, _collapsedTopSkip, 0, 0 }
).translated(left, top)); ).translated(left, top));
_inner = _outer.marginsRemoved(extents); _inner = _outer.marginsRemoved(margins);
if (!_strip) { if (!_strip) {
expand(); expand();
@ -343,9 +343,9 @@ void Selector::paintAppearing(QPainter &p) {
} }
_paintBuffer.fill(_st.bg->c); _paintBuffer.fill(_st.bg->c);
auto q = QPainter(&_paintBuffer); auto q = QPainter(&_paintBuffer);
const auto extents = extentsForShadow(); const auto margins = marginsForShadow();
const auto appearedWidth = countAppearedWidth(_appearProgress); 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()); const auto size = QSize(fullWidth, _outer.height());
q.translate(_inner.topLeft() - QPoint(0, _collapsedTopSkip)); q.translate(_inner.topLeft() - QPoint(0, _collapsedTopSkip));
@ -455,7 +455,7 @@ auto Selector::paintExpandingBg(QPainter &p, float64 progress)
const auto radius = _reactions.customAllowed const auto radius = _reactions.customAllowed
? (radiusStart + progress * (radiusEnd - radiusStart)) ? (radiusStart + progress * (radiusEnd - radiusStart))
: radiusStart; : radiusStart;
const auto extents = extentsForShadow(); const auto margins = marginsForShadow();
const auto expanding = anim::easeOutCirc(1., progress); const auto expanding = anim::easeOutCirc(1., progress);
const auto expandUp = anim::interpolate(0, _collapsedTopSkip, expanding); const auto expandUp = anim::interpolate(0, _collapsedTopSkip, expanding);
const auto expandDown = anim::interpolate( const auto expandDown = anim::interpolate(
@ -470,7 +470,7 @@ auto Selector::paintExpandingBg(QPainter &p, float64 progress)
p.fillRect(fill, _st.bg); p.fillRect(fill, _st.bg);
} }
} else { } else {
const auto inner = outer.marginsRemoved(extentsForShadow()); const auto inner = outer.marginsRemoved(marginsForShadow());
p.fillRect(inner, _st.bg); p.fillRect(inner, _st.bg);
p.fillRect( p.fillRect(
inner.x(), inner.x(),
@ -483,7 +483,7 @@ auto Selector::paintExpandingBg(QPainter &p, float64 progress)
0, 0,
extendTopForCategories(), extendTopForCategories(),
expanding); expanding);
const auto inner = outer.marginsRemoved(extents); const auto inner = outer.marginsRemoved(margins);
_shadowTop = inner.y() + categories; _shadowTop = inner.y() + categories;
_shadowSkip = (_useTransparency && categories < radius) _shadowSkip = (_useTransparency && categories < radius)
? int(base::SafeRound( ? int(base::SafeRound(
@ -494,7 +494,7 @@ auto Selector::paintExpandingBg(QPainter &p, float64 progress)
.list = inner.marginsRemoved({ 0, categories, 0, 0 }), .list = inner.marginsRemoved({ 0, categories, 0, 0 }),
.radius = radius, .radius = radius,
.expanding = expanding, .expanding = expanding,
.finalBottom = height() - extents.bottom(), .finalBottom = height() - margins.bottom(),
}; };
} }
@ -521,7 +521,7 @@ void Selector::paintExpanded(QPainter &p) {
if (_useTransparency) { if (_useTransparency) {
p.drawImage(0, 0, _paintBuffer); p.drawImage(0, 0, _paintBuffer);
} else { } else {
const auto inner = rect().marginsRemoved(extentsForShadow()); const auto inner = rect().marginsRemoved(marginsForShadow());
p.fillRect(inner, _st.bg); p.fillRect(inner, _st.bg);
p.fillRect( p.fillRect(
inner.x(), inner.x(),
@ -694,13 +694,13 @@ void Selector::expand() {
_willExpand.fire({}); _willExpand.fire({});
preloadAllRecentsAnimations(); preloadAllRecentsAnimations();
const auto parent = parentWidget()->geometry(); const auto parent = parentWidget()->geometry();
const auto extents = extentsForShadow(); const auto margins = marginsForShadow();
const auto heightLimit = _reactions.customAllowed const auto heightLimit = _reactions.customAllowed
? st::emojiPanMaxHeight ? st::emojiPanMaxHeight
: minimalHeight(); : minimalHeight();
const auto willBeHeight = std::min( const auto willBeHeight = std::min(
parent.height() - y(), parent.height() - y(),
extents.top() + heightLimit + extents.bottom()); margins.top() + heightLimit + margins.bottom());
const auto additionalBottom = willBeHeight - height(); const auto additionalBottom = willBeHeight - height();
const auto additional = _specialExpandTopSkip + additionalBottom; const auto additional = _specialExpandTopSkip + additionalBottom;
if (additionalBottom < 0 || additional <= 0) { if (additionalBottom < 0 || additional <= 0) {
@ -834,7 +834,7 @@ void Selector::createList() {
_list->jumpedToPremium( _list->jumpedToPremium(
) | rpl::start_with_next(_jumpedToPremium, _list->lifetime()); ) | rpl::start_with_next(_jumpedToPremium, _list->lifetime());
const auto inner = rect().marginsRemoved(extentsForShadow()); const auto inner = rect().marginsRemoved(marginsForShadow());
const auto footer = _reactions.customAllowed const auto footer = _reactions.customAllowed
? _list->createFooter().data() ? _list->createFooter().data()
: nullptr; : nullptr;
@ -904,16 +904,16 @@ bool AdjustMenuGeometryForSelector(
const auto desiredWidth = menu->menu()->width() + added; const auto desiredWidth = menu->menu()->width() + added;
const auto maxWidth = menu->st().menu.widthMax + added; const auto maxWidth = menu->st().menu.widthMax + added;
const auto width = selector->countWidth(desiredWidth, maxWidth); const auto width = selector->countWidth(desiredWidth, maxWidth);
const auto extents = selector->extentsForShadow(); const auto margins = selector->marginsForShadow();
const auto categoriesTop = selector->useTransparency() const auto categoriesTop = selector->useTransparency()
? selector->extendTopForCategories() ? selector->extendTopForCategories()
: 0; : 0;
menu->setForceWidth(width - added); menu->setForceWidth(width - added);
const auto height = menu->height(); const auto height = menu->height();
const auto fullTop = extents.top() + categoriesTop + extend.top(); const auto fullTop = margins.top() + categoriesTop + extend.top();
const auto minimalHeight = extents.top() const auto minimalHeight = margins.top()
+ selector->minimalHeight() + selector->minimalHeight()
+ extents.bottom(); + margins.bottom();
const auto willBeHeightWithoutBottomPadding = fullTop const auto willBeHeightWithoutBottomPadding = fullTop
+ height + height
- menu->st().shadow.extend.top(); - menu->st().shadow.extend.top();
@ -924,15 +924,15 @@ bool AdjustMenuGeometryForSelector(
? (minimalHeight - willBeHeightWithoutBottomPadding) ? (minimalHeight - willBeHeightWithoutBottomPadding)
: 0); : 0);
menu->setAdditionalMenuPadding(QMargins( menu->setAdditionalMenuPadding(QMargins(
extents.left() + extend.left(), margins.left() + extend.left(),
fullTop, fullTop,
extents.right() + extend.right(), margins.right() + extend.right(),
additionalPaddingBottom additionalPaddingBottom
), QMargins( ), QMargins(
extents.left(), margins.left(),
extents.top(), margins.top(),
extents.right(), margins.right(),
std::min(additionalPaddingBottom, extents.bottom()) std::min(additionalPaddingBottom, margins.bottom())
)); ));
if (!menu->prepareGeometryFor(desiredPosition)) { if (!menu->prepareGeometryFor(desiredPosition)) {
return false; return false;
@ -944,14 +944,14 @@ bool AdjustMenuGeometryForSelector(
return true; return true;
} }
menu->setAdditionalMenuPadding(QMargins( menu->setAdditionalMenuPadding(QMargins(
extents.left() + extend.left(), margins.left() + extend.left(),
fullTop + additionalPaddingBottom, fullTop + additionalPaddingBottom,
extents.right() + extend.right(), margins.right() + extend.right(),
0 0
), QMargins( ), QMargins(
extents.left(), margins.left(),
extents.top(), margins.top(),
extents.right(), margins.right(),
0 0
)); ));
selector->setSpecialExpandTopSkip(additionalPaddingBottom); selector->setSpecialExpandTopSkip(additionalPaddingBottom);

View file

@ -61,7 +61,7 @@ public:
[[nodiscard]] bool useTransparency() const; [[nodiscard]] bool useTransparency() const;
int countWidth(int desiredWidth, int maxWidth); int countWidth(int desiredWidth, int maxWidth);
[[nodiscard]] QMargins extentsForShadow() const; [[nodiscard]] QMargins marginsForShadow() const;
[[nodiscard]] int extendTopForCategories() const; [[nodiscard]] int extendTopForCategories() const;
[[nodiscard]] int minimalHeight() const; [[nodiscard]] int minimalHeight() const;
[[nodiscard]] int countAppearedWidth(float64 progress) const; [[nodiscard]] int countAppearedWidth(float64 progress) const;

View file

@ -243,18 +243,18 @@ void Reactions::Panel::create() {
const auto desiredWidth = st::storiesReactionsWidth; const auto desiredWidth = st::storiesReactionsWidth;
const auto maxWidth = desiredWidth * 2; const auto maxWidth = desiredWidth * 2;
const auto width = _selector->countWidth(desiredWidth, maxWidth); const auto width = _selector->countWidth(desiredWidth, maxWidth);
const auto extents = _selector->extentsForShadow(); const auto margins = _selector->marginsForShadow();
const auto categoriesTop = _selector->extendTopForCategories(); const auto categoriesTop = _selector->extendTopForCategories();
const auto full = extents.left() + width + extents.right(); const auto full = margins.left() + width + margins.right();
_shownValue = 0.; _shownValue = 0.;
rpl::combine( rpl::combine(
_controller->layoutValue(), _controller->layoutValue(),
_shownValue.value() _shownValue.value()
) | rpl::start_with_next([=](const Layout &layout, float64 shown) { ) | rpl::start_with_next([=](const Layout &layout, float64 shown) {
const auto width = extents.left() const auto width = margins.left()
+ _selector->countAppearedWidth(shown) + _selector->countAppearedWidth(shown)
+ extents.right(); + margins.right();
const auto height = layout.reactions.height(); const auto height = layout.reactions.height();
const auto shift = (width / 2); const auto shift = (width / 2);
const auto right = (mode == Mode::Message) const auto right = (mode == Mode::Message)
@ -271,7 +271,7 @@ void Reactions::Panel::create() {
const auto innerTop = height const auto innerTop = height
- st::storiesReactionsBottomSkip - st::storiesReactionsBottomSkip
- st::reactStripHeight; - st::reactStripHeight;
const auto maxAdded = innerTop - extents.top() - categoriesTop; const auto maxAdded = innerTop - margins.top() - categoriesTop;
const auto added = std::min(maxAdded, st::storiesReactionsAddedTop); const auto added = std::min(maxAdded, st::storiesReactionsAddedTop);
_selector->setSpecialExpandTopSkip(added); _selector->setSpecialExpandTopSkip(added);
_selector->initGeometry(innerTop); _selector->initGeometry(innerTop);

View file

@ -88,7 +88,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session_settings.h" #include "main/main_session_settings.h"
#include "layout/layout_document_generic_preview.h" #include "layout/layout_document_generic_preview.h"
#include "platform/platform_overlay_widget.h" #include "platform/platform_overlay_widget.h"
#include "settings/settings_premium.h"
#include "storage/file_download.h" #include "storage/file_download.h"
#include "storage/storage_account.h" #include "storage/storage_account.h"
#include "calls/calls_instance.h" #include "calls/calls_instance.h"
@ -104,7 +103,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtWidgets/QApplication> #include <QtWidgets/QApplication>
#include <QtCore/QBuffer> #include <QtCore/QBuffer>
#include <QtGui/QGuiApplication> #include <QtGui/QGuiApplication>
#include <QtGui/QClipboard>
#include <QtGui/QWindow> #include <QtGui/QWindow>
#include <QtGui/QScreen> #include <QtGui/QScreen>

View file

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_thread.h" #include "data/data_thread.h"
#include "data/notify/data_notify_settings.h" #include "data/notify/data_notify_settings.h"
#include "data/notify/data_peer_notify_settings.h"
#include "info/profile/info_profile_values.h" #include "info/profile/info_profile_values.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "main/main_session.h" #include "main/main_session.h"
@ -31,10 +32,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_menu_icons.h" #include "styles/style_menu_icons.h"
namespace MuteMenu { namespace MuteMenu {
namespace { namespace {
constexpr auto kMuteDurSecondsDefault = crl::time(8) * 3600; constexpr auto kMuteDurSecondsDefault = crl::time(8) * 3600;
constexpr auto kMuteForeverValue = std::numeric_limits<TimeId>::max();
class IconWithText final : public Ui::Menu::Action { class IconWithText final : public Ui::Menu::Action {
public: public:
@ -70,7 +71,7 @@ public:
MuteItem( MuteItem(
not_null<RpWidget*> parent, not_null<RpWidget*> parent,
const style::Menu &st, const style::Menu &st,
not_null<Data::Thread*> thread); Descriptor descriptor);
protected: protected:
void paintEvent(QPaintEvent *e) override; void paintEvent(QPaintEvent *e) override;
@ -79,31 +80,30 @@ private:
const QPoint _itemIconPosition; const QPoint _itemIconPosition;
Ui::Animations::Simple _animation; Ui::Animations::Simple _animation;
bool _isMuted = false; bool _isMuted = false;
bool _inited;
}; };
MuteItem::MuteItem( MuteItem::MuteItem(
not_null<RpWidget*> parent, not_null<RpWidget*> parent,
const style::Menu &st, const style::Menu &st,
not_null<Data::Thread*> thread) Descriptor descriptor)
: Ui::Menu::Action( : Ui::Menu::Action(
parent, parent,
st, st,
Ui::CreateChild<QAction>(parent.get()), Ui::CreateChild<QAction>(parent.get()),
nullptr, nullptr,
nullptr) nullptr)
, _itemIconPosition(st.itemIconPosition) , _itemIconPosition(st.itemIconPosition) {
, _isMuted(thread->owner().notifySettings().isMuted(thread)) { descriptor.isMutedValue(
Info::Profile::NotificationsEnabledValue( ) | rpl::start_with_next([=](bool isMuted) {
thread
) | rpl::start_with_next([=](bool isUnmuted) {
const auto isMuted = !isUnmuted;
action()->setText(isMuted action()->setText(isMuted
? tr::lng_mute_menu_duration_unmute(tr::now) ? tr::lng_mute_menu_duration_unmute(tr::now)
: tr::lng_mute_menu_duration_forever(tr::now)); : tr::lng_mute_menu_duration_forever(tr::now));
if (isMuted == _isMuted) { if (_inited && isMuted == _isMuted) {
return; return;
} }
_inited = true;
_isMuted = isMuted; _isMuted = isMuted;
_animation.start( _animation.start(
[=] { update(); }, [=] { update(); },
@ -112,13 +112,8 @@ MuteItem::MuteItem(
st::defaultPopupMenu.showDuration); st::defaultPopupMenu.showDuration);
}, lifetime()); }, lifetime());
const auto weak = base::make_weak(thread);
setClickedCallback([=] { setClickedCallback([=] {
if (const auto strong = weak.get()) { descriptor.updateMutePeriod(_isMuted ? 0 : kMuteForeverValue);
strong->owner().notifySettings().update(
strong,
{ .unmute = _isMuted, .forever = !_isMuted });
}
}); });
} }
@ -140,7 +135,7 @@ void MuteItem::paintEvent(QPaintEvent *e) {
icon.paint(p, _itemIconPosition, width(), color); icon.paint(p, _itemIconPosition, width(), color);
} }
void MuteBox(not_null<Ui::GenericBox*> box, not_null<Data::Thread*> thread) { void MuteBox(not_null<Ui::GenericBox*> box, Descriptor descriptor) {
struct State { struct State {
int lastSeconds = 0; int lastSeconds = 0;
}; };
@ -161,14 +156,9 @@ void MuteBox(not_null<Ui::GenericBox*> box, not_null<Data::Thread*> thread) {
: tr::lng_mute_menu_mute(); : tr::lng_mute_menu_mute();
}) | rpl::flatten_latest(); }) | rpl::flatten_latest();
const auto weak = base::make_weak(thread);
Ui::ConfirmBox(box, { Ui::ConfirmBox(box, {
.confirmed = [=] { .confirmed = [=] {
if (const auto strong = weak.get()) { descriptor.updateMutePeriod(state->lastSeconds);
strong->owner().notifySettings().update(
strong,
{ .period = state->lastSeconds });
}
box->getDelegate()->hideLayer(); box->getDelegate()->hideLayer();
}, },
.confirmText = std::move(confirmText), .confirmText = std::move(confirmText),
@ -178,7 +168,7 @@ void MuteBox(not_null<Ui::GenericBox*> box, not_null<Data::Thread*> thread) {
void PickMuteBox( void PickMuteBox(
not_null<Ui::GenericBox*> box, not_null<Ui::GenericBox*> box,
not_null<Data::Thread*> thread) { Descriptor descriptor) {
struct State { struct State {
base::unique_qptr<Ui::PopupMenu> menu; base::unique_qptr<Ui::PopupMenu> menu;
}; };
@ -191,17 +181,12 @@ void PickMuteBox(
const auto pickerCallback = TimePickerBox(box, seconds, phrases, 0); const auto pickerCallback = TimePickerBox(box, seconds, phrases, 0);
const auto weak = base::make_weak(thread);
Ui::ConfirmBox(box, { Ui::ConfirmBox(box, {
.confirmed = [=] { .confirmed = [=] {
const auto muteFor = pickerCallback(); const auto muteFor = pickerCallback();
if (const auto strong = weak.get()) { descriptor.updateMutePeriod(muteFor);
strong->owner().notifySettings().update( descriptor.session->settings().addMutePeriod(muteFor);
strong, descriptor.session->saveSettings();
{ .period = muteFor });
strong->session().settings().addMutePeriod(muteFor);
strong->session().saveSettings();
}
box->closeBox(); box->closeBox();
}, },
.confirmText = tr::lng_mute_menu_mute(), .confirmText = tr::lng_mute_menu_mute(),
@ -220,11 +205,7 @@ void PickMuteBox(
st::popupMenuWithIcons); st::popupMenuWithIcons);
state->menu->addAction( state->menu->addAction(
tr::lng_manage_messages_ttl_after_custom(tr::now), tr::lng_manage_messages_ttl_after_custom(tr::now),
[=] { [=] { box->getDelegate()->show(Box(MuteBox, descriptor)); },
if (const auto strong = weak.get()) {
box->getDelegate()->show(Box(MuteBox, strong));
}
},
&st::menuIconCustomize); &st::menuIconCustomize);
state->menu->setDestroyedCallback(crl::guard(top, [=] { state->menu->setDestroyedCallback(crl::guard(top, [=] {
top->setForceRippled(false); top->setForceRippled(false);
@ -236,46 +217,123 @@ void PickMuteBox(
} // namespace } // namespace
Descriptor ThreadDescriptor(not_null<Data::Thread*> thread) {
const auto weak = base::make_weak(thread);
const auto isMutedValue = [=]() -> rpl::producer<bool> {
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<Data::NotifySound>();
};
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<Main::Session*> session,
Data::DefaultNotify type) {
const auto settings = &session->data().notifySettings();
const auto isMutedValue = [=]() -> rpl::producer<bool> {
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( void FillMuteMenu(
not_null<Ui::PopupMenu*> menu, not_null<Ui::PopupMenu*> menu,
not_null<Data::Thread*> thread, Descriptor descriptor,
std::shared_ptr<Ui::Show> show) { std::shared_ptr<Ui::Show> show) {
const auto weak = base::make_weak(thread); const auto session = descriptor.session;
const auto with = [=](Fn<void(not_null<Data::Thread*> thread)> handler) { const auto soundSelect = [=] {
return [=] { if (const auto currentSound = descriptor.currentSound()) {
if (const auto strong = weak.get()) { show->showBox(Box(
handler(strong); RingtonesBox,
} session,
}; *currentSound,
descriptor.updateSound));
}
}; };
menu->addAction( menu->addAction(
tr::lng_mute_menu_sound_select(tr::now), tr::lng_mute_menu_sound_select(tr::now),
with([=](not_null<Data::Thread*> thread) { soundSelect,
show->showBox(Box(ThreadRingtonesBox, thread));
}),
&st::menuIconSoundSelect); &st::menuIconSoundSelect);
const auto notifySettings = &thread->owner().notifySettings(); const auto soundIsNone = descriptor.currentSound().value_or(
const auto soundIsNone = notifySettings->sound(thread).none; Data::NotifySound()
).none;
const auto toggleSound = [=] {
if (auto sound = descriptor.currentSound()) {
sound->none = !soundIsNone;
descriptor.updateSound(*sound);
}
};
menu->addAction( menu->addAction(
soundIsNone (soundIsNone
? tr::lng_mute_menu_sound_on(tr::now) ? tr::lng_mute_menu_sound_on(tr::now)
: tr::lng_mute_menu_sound_off(tr::now), : tr::lng_mute_menu_sound_off(tr::now)),
with([=](not_null<Data::Thread*> thread) { toggleSound,
auto sound = notifySettings->sound(thread);
sound.none = !sound.none;
notifySettings->update(thread, {}, {}, sound);
}),
soundIsNone ? &st::menuIconSoundOn : &st::menuIconSoundOff); soundIsNone ? &st::menuIconSoundOn : &st::menuIconSoundOff);
const auto &st = menu->st().menu; const auto &st = menu->st().menu;
const auto iconTextPosition = st.itemIconPosition const auto iconTextPosition = st.itemIconPosition
+ st::menuIconMuteForAnyTextPosition; + st::menuIconMuteForAnyTextPosition;
for (const auto muteFor : thread->session().settings().mutePeriods()) { for (const auto muteFor : session->settings().mutePeriods()) {
const auto callback = with([=](not_null<Data::Thread*> thread) { const auto callback = [=, update = descriptor.updateMutePeriod] {
notifySettings->update(thread, { .period = muteFor }); update(muteFor);
}); };
auto item = base::make_unique_q<IconWithText>( auto item = base::make_unique_q<IconWithText>(
menu, menu,
@ -295,20 +353,17 @@ void FillMuteMenu(
menu->addAction( menu->addAction(
tr::lng_mute_menu_duration(tr::now), tr::lng_mute_menu_duration(tr::now),
with([=](not_null<Data::Thread*> thread) { [=] { show->showBox(Box(PickMuteBox, descriptor)); },
DEBUG_LOG(("Mute Info: PickMuteBox called."));
show->showBox(Box(PickMuteBox, thread));
}),
&st::menuIconMuteFor); &st::menuIconMuteFor);
menu->addAction( menu->addAction(
base::make_unique_q<MuteItem>(menu, menu->st().menu, thread)); base::make_unique_q<MuteItem>(menu, menu->st().menu, descriptor));
} }
void SetupMuteMenu( void SetupMuteMenu(
not_null<Ui::RpWidget*> parent, not_null<Ui::RpWidget*> parent,
rpl::producer<> triggers, rpl::producer<> triggers,
Fn<Data::Thread*()> makeThread, Fn<std::optional<Descriptor>()> makeDescriptor,
std::shared_ptr<Ui::Show> show) { std::shared_ptr<Ui::Show> show) {
struct State { struct State {
base::unique_qptr<Ui::PopupMenu> menu; base::unique_qptr<Ui::PopupMenu> menu;
@ -319,11 +374,11 @@ void SetupMuteMenu(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
if (state->menu) { if (state->menu) {
return; return;
} else if (const auto thread = makeThread()) { } else if (const auto descriptor = makeDescriptor()) {
state->menu = base::make_unique_q<Ui::PopupMenu>( state->menu = base::make_unique_q<Ui::PopupMenu>(
parent, parent,
st::popupMenuWithIcons); st::popupMenuWithIcons);
FillMuteMenu(state->menu.get(), thread, show); FillMuteMenu(state->menu.get(), *descriptor, show);
state->menu->popup(QCursor::pos()); state->menu->popup(QCursor::pos());
} }
}, parent->lifetime()); }, parent->lifetime());

View file

@ -9,8 +9,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Data { namespace Data {
class Thread; class Thread;
struct NotifySound;
enum class DefaultNotify;
} // namespace Data } // namespace Data
namespace Main {
class Session;
} // namespace Main
namespace Ui { namespace Ui {
class PopupMenu; class PopupMenu;
class RpWidget; class RpWidget;
@ -19,15 +25,48 @@ class Show;
namespace MuteMenu { namespace MuteMenu {
struct Descriptor {
not_null<Main::Session*> session;
Fn<rpl::producer<bool>()> isMutedValue;
Fn<std::optional<Data::NotifySound>()> currentSound;
Fn<void(Data::NotifySound)> updateSound;
Fn<void(TimeId)> updateMutePeriod;
};
[[nodiscard]] Descriptor ThreadDescriptor(not_null<Data::Thread*> thread);
[[nodiscard]] Descriptor DefaultDescriptor(
not_null<Main::Session*> session,
Data::DefaultNotify type);
void FillMuteMenu( void FillMuteMenu(
not_null<Ui::PopupMenu*> menu, not_null<Ui::PopupMenu*> menu,
not_null<Data::Thread*> thread, Descriptor descriptor,
std::shared_ptr<Ui::Show> show); std::shared_ptr<Ui::Show> show);
void SetupMuteMenu( void SetupMuteMenu(
not_null<Ui::RpWidget*> parent, not_null<Ui::RpWidget*> parent,
rpl::producer<> triggers, rpl::producer<> triggers,
Fn<Data::Thread*()> makeThread, Fn<std::optional<Descriptor>()> makeDescriptor,
std::shared_ptr<Ui::Show> show); std::shared_ptr<Ui::Show> show);
inline void FillMuteMenu(
not_null<Ui::PopupMenu*> menu,
not_null<Data::Thread*> thread,
std::shared_ptr<Ui::Show> show) {
FillMuteMenu(menu, ThreadDescriptor(thread), std::move(show));
}
inline void SetupMuteMenu(
not_null<Ui::RpWidget*> parent,
rpl::producer<> triggers,
Fn<Data::Thread*()> makeThread,
std::shared_ptr<Ui::Show> show) {
SetupMuteMenu(parent, std::move(triggers), [=] {
const auto thread = makeThread();
return thread
? ThreadDescriptor(thread)
: std::optional<Descriptor>();
}, std::move(show));
}
} // namespace MuteMenu } // namespace MuteMenu

View file

@ -10,8 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/platform/base_platform_info.h" #include "base/platform/base_platform_info.h"
#include "base/platform/linux/base_linux_xdp_utilities.h" #include "base/platform/linux/base_linux_xdp_utilities.h"
#include "base/platform/linux/base_linux_wayland_integration.h" #include "base/platform/linux/base_linux_wayland_integration.h"
#include "core/application.h"
#include "window/window_controller.h"
#include "base/random.h" #include "base/random.h"
#include <fcntl.h> #include <fcntl.h>
@ -60,26 +58,13 @@ bool ShowXDPOpenWithDialog(const QString &filepath) {
const auto fdGuard = gsl::finally([&] { ::close(fd); }); 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") const auto handleToken = Glib::ustring("tdesktop")
+ std::to_string(base::RandomValue<uint>()); + std::to_string(base::RandomValue<uint>());
const auto activationToken = []() -> Glib::ustring { const auto activationToken = []() -> Glib::ustring {
using base::Platform::WaylandIntegration; using base::Platform::WaylandIntegration;
if (const auto integration = WaylandIntegration::Instance()) { if (const auto integration = WaylandIntegration::Instance()) {
if (const auto token = integration->activationToken() return integration->activationToken().toStdString();
; !token.isNull()) {
return token.toStdString();
}
} }
return {}; return {};
}(); }();
@ -124,7 +109,7 @@ bool ShowXDPOpenWithDialog(const QString &filepath) {
kXDPOpenURIInterface, kXDPOpenURIInterface,
"OpenFile", "OpenFile",
Glib::create_variant(std::tuple{ Glib::create_variant(std::tuple{
parentWindowId, base::Platform::XDP::ParentWindowID(),
Glib::DBusHandle(), Glib::DBusHandle(),
std::map<Glib::ustring, Glib::VariantBase>{ std::map<Glib::ustring, Glib::VariantBase>{
{ {

View file

@ -139,11 +139,13 @@ void XCBSetDesktopFileName(QWindow *window) {
void SkipTaskbar(QWindow *window, bool skip) { void SkipTaskbar(QWindow *window, bool skip) {
if (const auto integration = WaylandIntegration::Instance()) { if (const auto integration = WaylandIntegration::Instance()) {
integration->skipTaskbar(window, skip); integration->skipTaskbar(window, skip);
return;
} }
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION #ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
if (IsX11()) { if (IsX11()) {
XCBSkipTaskbar(window, skip); XCBSkipTaskbar(window, skip);
return;
} }
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION #endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
} }

View file

@ -17,10 +17,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mainwindow.h" #include "mainwindow.h"
#include "storage/localstorage.h" #include "storage/localstorage.h"
#include "core/launcher.h" #include "core/launcher.h"
#include "core/application.h"
#include "core/core_settings.h" #include "core/core_settings.h"
#include "core/update_checker.h" #include "core/update_checker.h"
#include "window/window_controller.h"
#include "webview/platform/linux/webview_linux_webkitgtk.h" #include "webview/platform/linux/webview_linux_webkitgtk.h"
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION #ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
@ -65,16 +63,6 @@ bool PortalAutostart(bool start, bool silent) {
const auto connection = Gio::DBus::Connection::get_sync( const auto connection = Gio::DBus::Connection::get_sync(
Gio::DBus::BusType::SESSION); 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") const auto handleToken = Glib::ustring("tdesktop")
+ std::to_string(base::RandomValue<uint>()); + std::to_string(base::RandomValue<uint>());
@ -152,7 +140,7 @@ bool PortalAutostart(bool start, bool silent) {
"org.freedesktop.portal.Background", "org.freedesktop.portal.Background",
"RequestBackground", "RequestBackground",
Glib::create_variant(std::tuple{ Glib::create_variant(std::tuple{
parentWindowId, base::Platform::XDP::ParentWindowID(),
options, options,
}), }),
base::Platform::XDP::kService); base::Platform::XDP::kService);

View file

@ -523,10 +523,19 @@ settingsPremiumLock: icon{{ "emoji/premium_lock", windowActiveTextFg, point(0px,
settingsPremiumLockSkip: 3px; settingsPremiumLockSkip: 3px;
settingsBlockedListSubtitleAddPadding: margins(0px, 1px, 0px, -4px); settingsBlockedListSubtitleAddPadding: margins(0px, 1px, 0px, -4px);
settingsBlockedListIconPadding: margins(0px, 34px, 0px, 5px); settingsBlockedListIconPadding: margins(0px, 24px, 0px, 5px);
settingsBlockedList: PeerList(peerListBox) { settingsBlockedList: PeerList(peerListBox) {
padding: margins(0px, 0px, 0px, membersMarginBottom); 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) { requestPeerRestriction: FlatLabel(defaultFlatLabel) {
minWidth: 240px; minWidth: 240px;

View file

@ -47,7 +47,10 @@ Blocked::Blocked(
tr::lng_contacts_loading(), tr::lng_contacts_loading(),
st::changePhoneDescription), st::changePhoneDescription),
std::move(padding))); std::move(padding)));
Ui::ResizeFitChild(this, _loading.get()); Ui::ResizeFitChild(
this,
_loading.get(),
st::settingsBlockedHeightMin);
} }
_controller->session().api().blockedPeers().slice( _controller->session().api().blockedPeers().slice(
@ -77,7 +80,7 @@ QPointer<Ui::RpWidget> Blocked::createPinnedToTop(not_null<QWidget*> parent) {
content, content,
tr::lng_blocked_list_add(), tr::lng_blocked_list_add(),
st::settingsButtonActive, st::settingsButtonActive,
{ &st::menuIconBlockSettings, IconType::Round, &st::transparent } { &st::menuIconBlockSettings }
)->addClickHandler([=] { )->addClickHandler([=] {
BlockedBoxController::BlockNewPeer(_controller); BlockedBoxController::BlockNewPeer(_controller);
}); });
@ -201,7 +204,30 @@ void Blocked::setupContent() {
AddSkip(content, st::settingsBlockedListIconPadding.top()); 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) { void Blocked::checkTotal(int total) {

View file

@ -8,8 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_notifications.h" #include "settings/settings_notifications.h"
#include "settings/settings_common.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/controls/chat_service_checkbox.h"
#include "ui/effects/animations.h" #include "ui/effects/animations.h"
#include "ui/text/text_utilities.h"
#include "ui/wrap/vertical_layout.h" #include "ui/wrap/vertical_layout.h"
#include "ui/wrap/slide_wrap.h" #include "ui/wrap/slide_wrap.h"
#include "ui/widgets/box_content_divider.h" #include "ui/widgets/box_content_divider.h"
@ -140,6 +143,160 @@ private:
}; };
void AddTypeButton(
not_null<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> controller,
Data::DefaultNotify type,
Fn<void(Type)> 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<Ui::FlatLabel>(
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<Ui::SettingsButton>(
container.get(),
nullptr,
st);
const auto checkView = button->lifetime().make_state<Ui::ToggleView>(
st.toggle,
NotificationsEnabledForType(session, type),
[=] { toggleButton->update(); });
const auto separator = Ui::CreateChild<Ui::RpWidget>(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<Ui::RpWidget>(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<Ui::GenericBox*> 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( NotificationsCount::NotificationsCount(
QWidget *parent, QWidget *parent,
not_null<Window::SessionController*> controller) not_null<Window::SessionController*> controller)
@ -817,15 +974,16 @@ void SetupMultiAccountNotifications(
void SetupNotificationsContent( void SetupNotificationsContent(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container) { not_null<Ui::VerticalLayout*> container,
Fn<void(Type)> showOther) {
using namespace rpl::mappers; using namespace rpl::mappers;
AddSkip(container); AddSkip(container, st::settingsPrivacySkip);
using NotifyView = Core::Settings::NotifyView; using NotifyView = Core::Settings::NotifyView;
SetupMultiAccountNotifications(controller, container); SetupMultiAccountNotifications(controller, container);
AddSubsectionTitle(container, tr::lng_settings_notify_title()); AddSubsectionTitle(container, tr::lng_settings_notify_global());
const auto session = &controller->session(); const auto session = &controller->session();
const auto checkbox = [&]( const auto checkbox = [&](
@ -871,41 +1029,15 @@ void SetupNotificationsContent(
flashbounceToggles->events_starting_with( flashbounceToggles->events_starting_with(
settings.flashBounceNotify())); settings.flashBounceNotify()));
const auto soundLabel = container->lifetime( const auto soundAllowed = container->lifetime(
).make_state<rpl::event_stream<QString>>(); ).make_state<rpl::event_stream<bool>>();
const auto soundValue = [=] { const auto allowed = [=] {
const auto owner = &controller->session().data(); return Core::App().settings().soundNotify();
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 label = [=] { const auto sound = addCheckbox(
const auto now = soundValue(); tr::lng_settings_sound_allowed(),
const auto owner = &controller->session().data(); { &st::menuIconUnmute },
return now.none soundAllowed->events_starting_with(allowed()));
? 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 });
AddSkip(container); AddSkip(container);
@ -924,7 +1056,20 @@ void SetupNotificationsContent(
previewWrap->toggle(settings.desktopNotify(), anim::type::instant); previewWrap->toggle(settings.desktopNotify(), anim::type::instant);
previewDivider->toggle(!settings.desktopNotify(), anim::type::instant); previewDivider->toggle(!settings.desktopNotify(), anim::type::instant);
controller->session().data().notifySettings().loadExceptions();
AddSkip(container, st::notifyPreviewBottomSkip); 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()); AddSubsectionTitle(container, tr::lng_settings_events_title());
auto joinSilent = rpl::single( auto joinSilent = rpl::single(
@ -1045,6 +1190,14 @@ void SetupNotificationsContent(
changed(Change::DesktopEnabled); changed(Change::DesktopEnabled);
}, desktop->lifetime()); }, 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( name->checkedChanges(
) | rpl::map([=](bool checked) { ) | rpl::map([=](bool checked) {
if (!checked) { if (!checked) {
@ -1077,25 +1230,6 @@ void SetupNotificationsContent(
changed(Change::ViewParams); changed(Change::ViewParams);
}, preview->lifetime()); }, 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( flashbounce->toggledChanges(
) | rpl::filter([](bool checked) { ) | rpl::filter([](bool checked) {
return (checked != Core::App().settings().flashBounceNotify()); return (checked != Core::App().settings().flashBounceNotify());
@ -1133,7 +1267,7 @@ void SetupNotificationsContent(
} else if (change == Change::ViewParams) { } else if (change == Change::ViewParams) {
// //
} else if (change == Change::SoundEnabled) { } else if (change == Change::SoundEnabled) {
soundLabel->fire(label()); soundAllowed->fire(allowed());
} else if (change == Change::FlashBounceEnabled) { } else if (change == Change::FlashBounceEnabled) {
flashbounceToggles->fire( flashbounceToggles->fire(
Core::App().settings().flashBounceNotify()); Core::App().settings().flashBounceNotify());
@ -1160,8 +1294,9 @@ void SetupNotificationsContent(
void SetupNotifications( void SetupNotifications(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container) { not_null<Ui::VerticalLayout*> container,
SetupNotificationsContent(controller, container); Fn<void(Type)> showOther) {
SetupNotificationsContent(controller, container, std::move(showOther));
} }
} // namespace } // namespace
@ -1177,11 +1312,17 @@ rpl::producer<QString> Notifications::title() {
return tr::lng_settings_section_notify(); return tr::lng_settings_section_notify();
} }
rpl::producer<Type> Notifications::sectionShowOther() {
return _showOther.events();
}
void Notifications::setupContent( void Notifications::setupContent(
not_null<Window::SessionController*> controller) { not_null<Window::SessionController*> controller) {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this); const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
SetupNotifications(controller, content); SetupNotifications(controller, content, [=](Type type) {
_showOther.fire_copy(type);
});
Ui::ResizeFitChild(this, content); Ui::ResizeFitChild(this, content);
} }

View file

@ -19,9 +19,13 @@ public:
[[nodiscard]] rpl::producer<QString> title() override; [[nodiscard]] rpl::producer<QString> title() override;
rpl::producer<Type> sectionShowOther() override;
private: private:
void setupContent(not_null<Window::SessionController*> controller); void setupContent(not_null<Window::SessionController*> controller);
rpl::event_stream<Type> _showOther;
}; };
} // namespace Settings } // namespace Settings

View file

@ -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<Main::Session*> session,
Notify type,
Fn<void(not_null<PeerData*>)> done);
Main::Session &session() const override;
void rowClicked(not_null<PeerListRow*> row) override;
base::unique_qptr<Ui::PopupMenu> rowContextMenu(
QWidget *parent,
not_null<PeerListRow*> row) override;
private:
void prepareViewHook() override;
std::unique_ptr<Row> createRow(not_null<History*> history) override;
const not_null<Main::Session*> _session;
const Notify _type;
const Fn<void(not_null<PeerData*>)> _done;
base::unique_qptr<Ui::PopupMenu> _menu;
PeerData *_lastClickedPeer = nullptr;
rpl::lifetime _lifetime;
};
class ExceptionsController final : public PeerListController {
public:
ExceptionsController(
not_null<Window::SessionController*> window,
Notify type);
Main::Session &session() const override;
void prepare() override;
void rowClicked(not_null<PeerListRow*> row) override;
base::unique_qptr<Ui::PopupMenu> rowContextMenu(
QWidget *parent,
not_null<PeerListRow*> row) override;
void rowRightActionClicked(not_null<PeerListRow*> row) override;
void loadMoreRows() override;
void bringToTop(not_null<PeerData*> peer);
[[nodiscard]] rpl::producer<int> countValue() const;
private:
void refreshRows();
bool appendRow(not_null<PeerData*> peer);
std::unique_ptr<PeerListRow> createRow(not_null<PeerData*> peer) const;
void refreshStatus(not_null<PeerListRow*> row) const;
void sort();
const not_null<Window::SessionController*> _window;
const Notify _type;
base::unique_qptr<Ui::PopupMenu> _menu;
base::flat_map<not_null<PeerData*>, int> _topOrdered;
int _topOrder = 0;
rpl::variable<int> _count;
rpl::lifetime _lifetime;
};
AddExceptionBoxController::AddExceptionBoxController(
not_null<Main::Session*> session,
Notify type,
Fn<void(not_null<PeerData*>)> 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<PeerListRow*> row) {
delegate()->peerListShowRowMenu(row, true);
}
base::unique_qptr<Ui::PopupMenu> AddExceptionBoxController::rowContextMenu(
QWidget *parent,
not_null<PeerListRow*> row) {
const auto peer = row->peer();
auto result = base::make_unique_q<Ui::PopupMenu>(
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<Ui::PopupMenu>(result.get());
_menu->setDestroyedCallback(crl::guard(this, [=] {
_lastClickedPeer = nullptr;
}));
_lastClickedPeer = peer;
return result;
}
auto AddExceptionBoxController::createRow(not_null<History*> history)
-> std::unique_ptr<AddExceptionBoxController::Row> {
if (Data::DefaultNotifyType(history->peer) != _type
|| history->peer->isSelf()
|| history->peer->isRepliesChat()) {
return nullptr;
}
return std::make_unique<Row>(history);
}
ExceptionsController::ExceptionsController(
not_null<Window::SessionController*> 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<PeerData*> peer) {
_topOrdered[peer] = ++_topOrder;
if (delegate()->peerListFindRow(peer->id.value)) {
sort();
}
}
rpl::producer<int> ExceptionsController::countValue() const {
return _count.value();
}
void ExceptionsController::rowClicked(not_null<PeerListRow*> row) {
delegate()->peerListShowRowMenu(row, true);
}
void ExceptionsController::rowRightActionClicked(
not_null<PeerListRow*> row) {
session().data().notifySettings().resetToDefault(row->peer());
}
void ExceptionsController::refreshRows() {
auto seen = base::flat_set<not_null<PeerData*>>();
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<Ui::PopupMenu> ExceptionsController::rowContextMenu(
QWidget *parent,
not_null<PeerListRow*> row) {
const auto peer = row->peer();
auto result = base::make_unique_q<Ui::PopupMenu>(
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<Ui::PopupMenu>(result.get());
return result;
}
bool ExceptionsController::appendRow(not_null<PeerData*> peer) {
delegate()->peerListAppendRow(createRow(peer));
return true;
}
std::unique_ptr<PeerListRow> ExceptionsController::createRow(
not_null<PeerData*> peer) const {
auto row = std::make_unique<PeerListRowWithLink>(peer);
row->setActionLink(tr::lng_notification_exceptions_remove(tr::now));
refreshStatus(row.get());
return row;
}
void ExceptionsController::refreshStatus(not_null<PeerListRow*> 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<PeerListRowId, QString>();
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 <Notify kType>
[[nodiscard]] Type Id() {
return &NotificationsTypeMetaImplementation<kType>::Meta;
}
[[nodiscard]] rpl::producer<QString> 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<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> 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<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(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<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(container)));
toneWrap->toggleOn(sound->toggledValue());
toneWrap->finishAnimating();
const auto toneInner = toneWrap->entity();
const auto toneLabel = toneInner->lifetime(
).make_state<rpl::event_stream<QString>>();
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<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> window,
Notify type) {
const auto add = AddButton(
container,
tr::lng_notification_exceptions_add(),
st::settingsButtonActive,
{ &st::menuIconInviteSettings });
auto controller = std::make_unique<ExceptionsController>(window, type);
controller->setStyleOverrides(&st::settingsBlockedList);
const auto content = container->add(
object_ptr<PeerListContent>(container, controller.get()));
struct State {
std::unique_ptr<ExceptionsController> controller;
std::unique_ptr<PeerListContentDelegateSimple> delegate;
};
const auto state = content->lifetime().make_state<State>();
state->controller = std::move(controller);
state->delegate = std::make_unique<PeerListContentDelegateSimple>();
state->delegate->setContent(content);
state->controller->setDelegate(state->delegate.get());
add->setClickedCallback([=] {
const auto box = std::make_shared<QPointer<Ui::BoxContent>>();
const auto done = [=](not_null<PeerData*> peer) {
state->controller->bringToTop(peer);
if (*box) {
(*box)->closeBox();
}
};
auto controller = std::make_unique<AddExceptionBoxController>(
&window->session(),
type,
crl::guard(content, done));
auto initBox = [=](not_null<PeerListBox*> box) {
box->addButton(tr::lng_cancel(), [box] { box->closeBox(); });
};
*box = window->show(
Box<PeerListBox>(std::move(controller), std::move(initBox)));
});
const auto wrap = container->add(
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
container,
CreateButton(
container,
tr::lng_notification_exceptions_clear(),
st::settingsAttentionButtonWithIcon,
{ &st::menuIconDeleteAttention })));
wrap->entity()->setClickedCallback([=] {
const auto clear = [=](Fn<void()> 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<Notify::User>();
case Notify::Group: return Id<Notify::Group>();
case Notify::Broadcast: return Id<Notify::Broadcast>();
}
Unexpected("Type in NotificationTypeId.");
}
NotificationsType::NotificationsType(
QWidget *parent,
not_null<Window::SessionController*> controller,
Notify type)
: AbstractSection(parent)
, _type(type) {
setupContent(controller);
}
rpl::producer<QString> 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<Window::SessionController*> controller) {
const auto container = Ui::CreateChild<Ui::VerticalLayout>(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<Main::Session*> session,
Notify type) {
const auto settings = &session->data().notifySettings();
const auto until = settings->defaultSettings(type).muteUntil();
return until && (*until <= base::unixtime::now());
}
rpl::producer<bool> NotificationsEnabledForTypeValue(
not_null<Main::Session*> 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

View file

@ -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<Window::SessionController*> controller,
Data::DefaultNotify type);
[[nodiscard]] rpl::producer<QString> title() override;
[[nodiscard]] Type id() const final override;
private:
void setupContent(not_null<Window::SessionController*> controller);
Data::DefaultNotify _type;
};
template <Data::DefaultNotify kType>
struct NotificationsTypeMetaImplementation : SectionMeta {
object_ptr<AbstractSection> create(
not_null<QWidget*> parent,
not_null<Window::SessionController*> controller
) const final override {
return object_ptr<NotificationsType>(parent, controller, kType);
}
[[nodiscard]] static not_null<SectionMeta*> Meta() {
static NotificationsTypeMetaImplementation result;
return &result;
}
};
[[nodiscard]] Type NotificationsTypeId(Data::DefaultNotify type);
[[nodiscard]] bool NotificationsEnabledForType(
not_null<Main::Session*> session,
Data::DefaultNotify type);
[[nodiscard]] rpl::producer<bool> NotificationsEnabledForTypeValue(
not_null<Main::Session*> session,
Data::DefaultNotify type);
} // namespace Settings

View file

@ -41,260 +41,6 @@ namespace {
} // 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 } // namespace Images
Image::Image(const QString &path) Image::Image(const QString &path)

View file

@ -11,14 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class QPainterPath; 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 { class Image final {
public: public:
explicit Image(const QString &path); explicit Image(const QString &path);

View file

@ -173,6 +173,7 @@ menuIconReportAttention: icon {{ "menu/report", menuIconAttentionColor }};
menuIconRestoreAttention: icon {{ "menu/restore", menuIconAttentionColor }}; menuIconRestoreAttention: icon {{ "menu/restore", menuIconAttentionColor }};
menuIconBlockSettings: icon {{ "menu/block", windowBgActive }}; menuIconBlockSettings: icon {{ "menu/block", windowBgActive }};
menuIconInviteSettings: icon {{ "menu/invite", windowBgActive }};
playerSpeedSlow: icon {{ "player/speed/audiospeed_menu_0.5", menuIconColor }}; playerSpeedSlow: icon {{ "player/speed/audiospeed_menu_0.5", menuIconColor }};
playerSpeedSlowActive: icon {{ "player/speed/audiospeed_menu_0.5", mediaPlayerActiveFg }}; playerSpeedSlowActive: icon {{ "player/speed/audiospeed_menu_0.5", mediaPlayerActiveFg }};

View file

@ -66,7 +66,15 @@ constexpr auto kSystemAlertDuration = crl::time(0);
return result; 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; auto text = textWithEntities.text;
for (const auto &e : textWithEntities.entities) { for (const auto &e : textWithEntities.entities) {
if (e.type() == EntityType::Spoiler) { if (e.type() == EntityType::Spoiler) {
@ -1175,9 +1183,11 @@ void NativeManager::doShowNotification(NotificationFields &&fields) {
? tr::lng_forward_messages(tr::now, lt_count, fields.forwardedCount) ? tr::lng_forward_messages(tr::now, lt_count, fields.forwardedCount)
: item->groupId() : item->groupId()
? tr::lng_in_dlg_album(tr::now) ? tr::lng_in_dlg_album(tr::now)
: TextWithPermanentSpoiler(item->notificationText({ : TextWithForwardedChar(
.spoilerLoginCode = options.spoilerLoginCode, TextWithPermanentSpoiler(item->notificationText({
})); .spoilerLoginCode = options.spoilerLoginCode,
})),
(fields.forwardedCount == 1));
// #TODO optimize // #TODO optimize
auto userpicView = item->history()->peer->createUserpicView(); auto userpicView = item->history()->peer->createUserpicView();

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "window/themes/window_theme_preview.h" #include "window/themes/window_theme_preview.h"
#include "dialogs/dialogs_three_state_icon.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "platform/platform_window_title.h" #include "platform/platform_window_title.h"
#include "ui/text/text_options.h" #include "ui/text/text_options.h"
@ -695,9 +696,15 @@ void Generator::paintRow(const Row &row) {
auto chatTypeIcon = ([&row]() -> const style::icon * { auto chatTypeIcon = ([&row]() -> const style::icon * {
if (row.type == Row::Type::Group) { 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) { } 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; 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->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); _p->drawText(unreadRectLeft + (unreadRectWidth - unreadWidth) / 2, unreadRectTop + textTop + st::dialogsUnreadFont->ascent, counter);
} else if (row.pinned) { } 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); icon.paint(*_p, x + fullWidth - st.padding.right() - icon.width(), texttop, fullWidth);
availableWidth -= icon.width() + st::dialogsUnreadPadding; availableWidth -= icon.width() + st::dialogsUnreadPadding;
} }
@ -763,9 +773,15 @@ void Generator::paintRow(const Row &row) {
auto sendStateIcon = ([&row]() -> const style::icon* { auto sendStateIcon = ([&row]() -> const style::icon* {
if (row.status == Status::Sent) { 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) { } 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; return nullptr;
})(); })();

View file

@ -10,11 +10,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/add_contact_box.h" #include "boxes/add_contact_box.h"
#include "boxes/peers/add_bot_to_chat_box.h" #include "boxes/peers/add_bot_to_chat_box.h"
#include "boxes/peers/edit_peer_info_box.h" #include "boxes/peers/edit_peer_info_box.h"
#include "boxes/peer_list_controllers.h"
#include "boxes/delete_messages_box.h" #include "boxes/delete_messages_box.h"
#include "window/window_adaptive.h" #include "window/window_adaptive.h"
#include "window/window_controller.h" #include "window/window_controller.h"
#include "window/main_window.h"
#include "window/window_filters_menu.h" #include "window/window_filters_menu.h"
#include "info/info_memento.h" #include "info/info_memento.h"
#include "info/info_controller.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_user.h"
#include "data/data_document.h" #include "data/data_document.h"
#include "data/data_document_media.h" #include "data/data_document_media.h"
#include "data/data_document_resolver.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_group_call.h" #include "data/data_group_call.h"
#include "data/data_forum.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/core_settings.h"
#include "core/click_handler_types.h" #include "core/click_handler_types.h"
#include "base/unixtime.h" #include "base/unixtime.h"
#include "base/random.h"
#include "ui/layers/generic_box.h" #include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/text/format_values.h" // Ui::FormatPhone. #include "ui/text/format_values.h" // Ui::FormatPhone.
#include "ui/delayed_activation.h" #include "ui/delayed_activation.h"
#include "ui/chat/attach/attach_bot_webview.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_style.h"
#include "ui/chat/chat_theme.h" #include "ui/chat/chat_theme.h"
#include "ui/effects/message_sending_animation_controller.h" #include "ui/effects/message_sending_animation_controller.h"

View file

@ -16,7 +16,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_chat_participant_status.h" #include "data/data_chat_participant_status.h"
#include "dialogs/dialogs_key.h" #include "dialogs/dialogs_key.h"
#include "ui/layers/layer_widget.h" #include "ui/layers/layer_widget.h"
#include "ui/layers/show.h"
#include "settings/settings_type.h" #include "settings/settings_type.h"
#include "window/window_adaptive.h" #include "window/window_adaptive.h"
#include "mtproto/sender.h" #include "mtproto/sender.h"
@ -89,7 +88,6 @@ namespace Window {
using GifPauseReason = ChatHelpers::PauseReason; using GifPauseReason = ChatHelpers::PauseReason;
using GifPauseReasons = ChatHelpers::PauseReasons; using GifPauseReasons = ChatHelpers::PauseReasons;
class MainWindow;
class SectionMemento; class SectionMemento;
class Controller; class Controller;
class FiltersMenu; class FiltersMenu;

@ -1 +1 @@
Subproject commit 63056c52f91aa4a34bb0637f1723848dc25a5f87 Subproject commit b2b677b8a5e4c3cf34790eb990218217bf867c18

View file

@ -59,7 +59,7 @@ FROM builder AS patches
RUN git init patches \ RUN git init patches \
&& cd patches \ && cd patches \
&& git remote add origin {{ GIT }}/desktop-app/patches.git \ && 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 \ && git reset --hard FETCH_HEAD \
&& rm -rf .git && rm -rf .git

View file

@ -1,7 +1,7 @@
AppVersion 4009003 AppVersion 4009004
AppVersionStrMajor 4.9 AppVersionStrMajor 4.9
AppVersionStrSmall 4.9.3 AppVersionStrSmall 4.9.4
AppVersionStr 4.9.3 AppVersionStr 4.9.4
BetaChannel 0 BetaChannel 0
AlphaVersion 0 AlphaVersion 0
AppVersionOriginal 4.9.3 AppVersionOriginal 4.9.4

View file

@ -73,6 +73,7 @@ PRIVATE
data/data_subscription_option.h data/data_subscription_option.h
dialogs/dialogs_three_state_icon.h
dialogs/ui/dialogs_stories_list.cpp dialogs/ui/dialogs_stories_list.cpp
dialogs/ui/dialogs_stories_list.h dialogs/ui/dialogs_stories_list.h

@ -1 +1 @@
Subproject commit 0919789bf555c651e32bdbcabc397dbacb6a2545 Subproject commit 764b55db1a4a8b9d8ba966d7f0fa46c9f384737e

@ -1 +1 @@
Subproject commit b94ec0107b03f13c28f336642411c6df21107a63 Subproject commit c989a9f41e5bd8268587af2256efa89327cb3ae0

@ -1 +1 @@
Subproject commit 5850566934f2f6cae56646c36cb95f85c8a9c752 Subproject commit 5b9092bcb27a207fed3cb2155bb98db46d7cedfa

View file

@ -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) 4.9.3 (22.08.23)
- Fix audio output on macOS. - Fix audio output on macOS.