Compare commits

..

53 Commits

Author SHA1 Message Date
John Preston
7e15722eab Beta version 6.0.3.
- Ctrl+Tab / Ctrl+Shift+Tab for last opened chats switching.
- New topic dividers for groups with topics and tabs.
- Adjust volume of notification sounds.
- Show stars required for the next rating level.
- IV support on Linux (in case WebView works).
2025-08-22 21:17:48 +04:00
John Preston
719c209c7b Fix build with Xcode. 2025-08-22 21:17:48 +04:00
John Preston
d05ad44b84 Fix build with GCC. 2025-08-22 21:15:53 +04:00
Ilya Fedin
b352c97479 Switch from custom URI scheme to local HTTP server for webview on Linux 2025-08-22 18:05:11 +04:00
Ilya Fedin
063085a6bb Format Linux webview socket path using std::format 2025-08-22 18:05:11 +04:00
Ilya Fedin
b4bca16109 Get rid of wayland-scanner downgrade 2025-08-22 17:48:43 +04:00
John Preston
03770c52fe Support floating topic bars in forums. 2025-08-22 13:13:20 +04:00
John Preston
10fe5cdd5d Keep recent peers userpics in memory. 2025-08-22 13:06:04 +04:00
John Preston
57d459b917 Show nice topic/sublist userpics. 2025-08-22 13:06:03 +04:00
John Preston
fe26594f12 Improve Ctrl+Tab switch design. 2025-08-22 13:05:27 +04:00
John Preston
0d8065fc1f First attempt of Ctrl+Tab/Ctrl+Shift+Tab UI. 2025-08-22 13:05:26 +04:00
John Preston
a3cdae1e94 Fix refreshing story file reference. 2025-08-22 13:05:26 +04:00
John Preston
29d77b649b Nice selection of gifts for a collection. 2025-08-22 13:05:26 +04:00
John Preston
9e190cee81 Show next-level stars in rating. 2025-08-22 13:05:26 +04:00
John Preston
687bfd0f17 Remove some FixedHeightWidget-s. 2025-08-22 13:01:49 +04:00
23rd
7d7df4f749 Fixed display of about text in low credits balance box in some cases. 2025-08-20 12:40:16 +03:00
23rd
958dede319 Fixed active color of recording voice button. 2025-08-19 20:51:32 +03:00
23rd
76e814944d Added shortcuts to start recording of voice or round message.
Fixed #29633.
2025-08-19 16:52:48 +03:00
23rd
fbc1d75e9a Added ability to start and lock voice recording immediately. 2025-08-19 16:43:47 +03:00
23rd
b3c7ce05dc Added ability to send recording voice with submit key. 2025-08-19 16:43:47 +03:00
23rd
feea881e09 Slightly improved position of tooltip for voice record bar. 2025-08-19 13:21:51 +03:00
23rd
b978bc4876 Added ability to bottom scroll in HistoryView::Chat section with submit.
Fixed #29662.
2025-08-19 12:34:46 +03:00
23rd
f84181e7a5 Added event for scrolling to bottom to compose controls. 2025-08-19 12:33:38 +03:00
John Preston
313ae0f86c Remove CenterWrap layout. 2025-08-18 17:55:20 +04:00
John Preston
a39c018359 Scroll to top on global posts search. 2025-08-18 17:25:31 +04:00
John Preston
a09f57d908 Fix natural width for LabelWithNumbers. 2025-08-18 17:25:31 +04:00
John Preston
596828cf78 Fix webview blocking popups. 2025-08-18 17:25:31 +04:00
23rd
44843aa9cd Added hint about archive and its features. 2025-08-18 13:26:26 +03:00
23rd
5f930cc4d1 Added initial ability to rate voice transcriptions. 2025-08-18 13:26:23 +03:00
23rd
7defad5d95 Added ability to clear history for channels and megagroups.
Fixed #28778.
2025-08-17 14:47:49 +03:00
23rd
707af8a295 Got rid Ui::CreateLabelWithCustomEmoji. 2025-08-17 14:47:49 +03:00
23rd
e0fb9ffbb0 Added support of setting up of login email from intro. 2025-08-17 14:47:49 +03:00
23rd
d614de6f5e Fixed display of error in cloud password section with new naturalWidth. 2025-08-17 14:47:49 +03:00
Ilya Fedin
86b94b4723 Change notification volume visibility on support change
Cross platform VolumeSupported wrapper
2025-08-17 14:47:49 +03:00
23rd
5070300050 Added ability to test on place volume of notifications. 2025-08-17 14:47:49 +03:00
23rd
1919546441 Added ability to change volume of specific notifications from settings. 2025-08-17 14:47:49 +03:00
23rd
1c3cd8d44b Added ability to change master volume of notifications from settings. 2025-08-17 14:47:49 +03:00
23rd
292296266f Added convenient utils to pass and change notifications volume from ui. 2025-08-17 14:47:49 +03:00
23rd
a3e8848bc8 Added ability to check if OS has support of notifications volume. 2025-08-17 14:47:49 +03:00
23rd
ced146fc63 Added session-specific settings for volume of notifications for peers. 2025-08-17 14:47:49 +03:00
23rd
6ed79f6a0d Added global settings for master volume of notifications. 2025-08-17 14:47:49 +03:00
23rd
15e4d86e92 Added ability to override volume in media audio tracks. 2025-08-17 14:47:49 +03:00
23rd
5a29a7d2a3 Moved out DefaultNotify to data_peer_notify_settings. 2025-08-17 14:47:49 +03:00
23rd
034740ce46 Removed unused PeerListWidget. 2025-08-17 14:47:49 +03:00
23rd
a2847246e6 Replaced custom GroupMembersWidget with PeerList. 2025-08-17 14:47:49 +03:00
23rd
04479ad660 Moved out GroupMembersWidget to correspond file and namespace. 2025-08-17 14:47:49 +03:00
23rd
35d2fff593 Added initial support of highlighting for found query in quotes. 2025-08-17 14:47:49 +03:00
23rd
2b93fe9a30 Added simple colorizing of query in found messages from compose search. 2025-08-17 14:47:49 +03:00
23rd
5c33a2bd5c Added simple colorizing of query in found messages. 2025-08-17 14:47:49 +03:00
23rd
a28f113105 Moved FindSearchQueryHighlight to use as util. 2025-08-17 14:47:49 +03:00
23rd
eb7976a2ef Replaced alignment of title bar with left side for macOS 26. 2025-08-17 14:47:49 +03:00
23rd
15db1c0c30 Added ability to withdrawal personal currency balance. 2025-08-17 14:47:49 +03:00
23rd
2b83c95869 Moved out ui util for channel earn list to static function. 2025-08-17 14:47:49 +03:00
192 changed files with 3999 additions and 2207 deletions

View File

@@ -523,6 +523,8 @@ PRIVATE
data/notify/data_notify_settings.h
data/notify/data_peer_notify_settings.cpp
data/notify/data_peer_notify_settings.h
data/notify/data_peer_notify_volume.cpp
data/notify/data_peer_notify_volume.h
data/stickers/data_custom_emoji.cpp
data/stickers/data_custom_emoji.h
data/stickers/data_stickers_set.cpp
@@ -876,6 +878,8 @@ PRIVATE
history/view/history_view_fake_items.h
history/view/history_view_group_call_bar.cpp
history/view/history_view_group_call_bar.h
history/view/history_view_group_members_widget.cpp
history/view/history_view_group_members_widget.h
history/view/history_view_item_preview.h
history/view/history_view_list_widget.cpp
history/view/history_view_list_widget.h
@@ -1119,6 +1123,8 @@ PRIVATE
inline_bots/inline_results_widget.h
intro/intro_code.cpp
intro/intro_code.h
intro/intro_email.cpp
intro/intro_email.h
intro/intro_password_check.cpp
intro/intro_password_check.h
intro/intro_phone.cpp
@@ -1274,6 +1280,8 @@ PRIVATE
menu/menu_antispam_validator.h
menu/menu_item_download_files.cpp
menu/menu_item_download_files.h
menu/menu_item_rate_transcribe_session.cpp
menu/menu_item_rate_transcribe_session.h
menu/menu_mute.cpp
menu/menu_mute.h
menu/menu_send.cpp
@@ -1307,6 +1315,8 @@ PRIVATE
mtproto/special_config_request.cpp
mtproto/special_config_request.h
mtproto/type_utils.h
overview/overview_checkbox.cpp
overview/overview_checkbox.h
overview/overview_layout.cpp
overview/overview_layout.h
overview/overview_layout_delegate.h
@@ -1427,10 +1437,6 @@ PRIVATE
platform/platform_window_title.h
profile/profile_back_button.cpp
profile/profile_back_button.h
profile/profile_block_group_members.cpp
profile/profile_block_group_members.h
profile/profile_block_peer_list.cpp
profile/profile_block_peer_list.h
profile/profile_block_widget.cpp
profile/profile_block_widget.h
profile/profile_cover_drop_area.cpp
@@ -1625,8 +1631,6 @@ PRIVATE
ui/text/format_song_document_name.h
ui/widgets/expandable_peer_list.cpp
ui/widgets/expandable_peer_list.h
ui/widgets/label_with_custom_emoji.cpp
ui/widgets/label_with_custom_emoji.h
ui/widgets/chat_filters_tabs_strip.cpp
ui/widgets/chat_filters_tabs_strip.h
ui/widgets/peer_bubble.cpp
@@ -1659,6 +1663,8 @@ PRIVATE
window/window_adaptive.h
window/window_chat_preview.cpp
window/window_chat_preview.h
window/window_chat_switch_process.cpp
window/window_chat_switch_process.h
window/window_connecting_widget.cpp
window/window_connecting_widget.h
window/window_controller.cpp

View File

@@ -377,6 +377,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_intro_fragment_about" = "Get the code for {phone_number} in the Anonymous Numbers section on Fragment.";
"lng_intro_fragment_button" = "Open Fragment";
"lng_intro_email_setup_title" = "Choose a login email";
"lng_intro_email_confirm_subtitle" = "Please check your email {email} (don't forget the spam folder) and enter the code we just sent you.";
"lng_phone_title" = "Your Phone Number";
"lng_phone_desc" = "Please confirm your country code\nand enter your phone number.";
"lng_phone_to_qr" = "Quick log in using QR code";
@@ -512,6 +515,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_notify_global" = "Global settings";
"lng_settings_notify_title" = "Notifications for chats";
"lng_settings_desktop_notify" = "Desktop notifications";
"lng_settings_master_volume_notifications" = "Volume";
"lng_settings_native_title" = "System integration";
"lng_settings_use_windows" = "Use Windows notifications";
"lng_settings_skip_in_focus" = "Respect system Focus mode";
@@ -552,12 +556,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"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_volume_private_chats" = "Notifications volume for private chats";
"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_volume_groups" = "Notifications volume for groups";
"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_volume_channel" = "Notifications volume for channels";
"lng_notification_exceptions_view" = "View exceptions";
"lng_notification_enable" = "Enable notifications";
"lng_notification_sound" = "Sound";
@@ -687,6 +694,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_shortcuts_media_fullscreen" = "Toggle video fullscreen";
"lng_shortcuts_show_chat_menu" = "Show chat menu";
"lng_shortcuts_show_chat_preview" = "Show chat preview";
"lng_shortcuts_record_voice_message" = "Record Voice Message";
"lng_shortcuts_record_round_message" = "Record Round Message";
"lng_settings_chat_reactions_title" = "Quick Reaction";
"lng_settings_chat_reactions_subtitle" = "Choose your favorite reaction";
@@ -1238,6 +1247,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_quick_dialog_action_toast_archive_success" = "The chat has been archived.";
"lng_quick_dialog_action_toast_unarchive_success" = "The chat has been unarchived.";
"lng_archive_hint_title" = "This is your Archive";
"lng_archive_hint_about" = "Archived chats will remain in the Archive when you receive a new message. {link}";
"lng_archive_hint_about_unmuted" = "When you receive a new message, muted chats will remain in the Archive, while unmuted chats will be moved to Chats. {link}";
"lng_archive_hint_about_link" = "Tap to change {emoji}";
"lng_archive_hint_section_1" = "Archived Chats";
"lng_archive_hint_section_1_info" = "Move any chat into your Archive and back by swiping on it.";
"lng_archive_hint_section_2" = "Hiding Archive";
"lng_archive_hint_section_2_info" = "Hide the Archive from your Main screen by swiping on it.";
"lng_archive_hint_section_3" = "Stories";
"lng_archive_hint_section_3_info" = "Archive Stories from your contacts separately from chats with them.";
"lng_archive_hint_button" = "Got it";
"lng_settings_generic_subscribe" = "Subscribe to {link} to use this setting.";
"lng_settings_generic_subscribe_link" = "Telegram Premium";
@@ -2055,6 +2076,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_sure_delete_contact" = "Are you sure you want to delete {contact} from your contact list?";
"lng_sure_delete_history" = "Are you sure you want to delete all message history with {contact}?\n\nThis action cannot be undone.";
"lng_sure_delete_group_history" = "Are you sure you want to delete all messages in \"{group}\"?\n\nThis action cannot be undone.";
"lng_sure_delete_channel_history" = "Are you sure you want to delete all messages in \"{channel}\"?\n\n**This action cannot be undone.**";
"lng_sure_delete_and_exit" = "Are you sure you want to delete all message history and leave «{group}»?\n\nThis action cannot be undone.";
"lng_sure_leave_channel" = "Are you sure you want to leave\nthis channel?";
"lng_sure_delete_channel" = "Are you sure you want to delete this channel? All subscribers will be removed and all messages will be lost.";
@@ -4291,6 +4313,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_archive_to_list" = "Move to chat list";
"lng_context_archive_to_menu_info" = "Archive moved to the main menu!\nRight click the archive button to return the Archive to your chat list.";
"lng_context_archive_settings" = "Archive settings";
"lng_context_archive_how_does_it_work" = "How does it work?";
"lng_context_mute" = "Mute notifications";
"lng_context_unmute" = "Unmute";
@@ -4302,6 +4325,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_remove_from_group" = "Remove from group";
"lng_context_add_to_group" = "Add to group";
"lng_context_rate_transcription" = "Rate transcription";
"lng_toast_sent_rate_transcription" = "Thank you for your feedback!";
"lng_context_copy_link" = "Copy Link";
"lng_context_copy_message_link" = "Copy Message Link";
"lng_context_copy_post_link" = "Copy Post Link";
@@ -6334,6 +6360,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_ringtones_box_title" = "Notification Sound";
"lng_ringtones_box_cloud_subtitle" = "Choose your tone";
"lng_ringtones_box_volume" = "Volume";
"lng_ringtones_box_upload_choose" = "Choose a tone";
"lng_ringtones_box_upload_button" = "Upload Sound";
"lng_ringtones_box_about" = "Right click on any short voice note or MP3 file in chat and select \"Save for Notifications\". It will appear here.";

View File

@@ -51,8 +51,8 @@ var LocationPicker = {
},
init: function (params) {
mapboxgl.accessToken = params.token;
if (params.protocol) {
mapboxgl.config.API_URL = params.protocol + '://domain/api.mapbox.com';
if (location.hostname != 'desktop-app-resource') {
mapboxgl.config.API_URL = location.protocol + '//' + location.host + '/api.mapbox.com';
}
var options = { container: 'map', config: {

View File

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

View File

@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 6,0,2,0
PRODUCTVERSION 6,0,2,0
FILEVERSION 6,0,3,0
PRODUCTVERSION 6,0,3,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop"
VALUE "FileVersion", "6.0.2.0"
VALUE "FileVersion", "6.0.3.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "6.0.2.0"
VALUE "ProductVersion", "6.0.3.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 6,0,2,0
PRODUCTVERSION 6,0,2,0
FILEVERSION 6,0,3,0
PRODUCTVERSION 6,0,3,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -53,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop Updater"
VALUE "FileVersion", "6.0.2.0"
VALUE "FileVersion", "6.0.3.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "6.0.2.0"
VALUE "ProductVersion", "6.0.3.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -142,13 +142,12 @@ void ConfirmSubscriptionBox(
const auto content = box->verticalLayout();
Ui::AddSkip(content, st::confirmInvitePhotoTop);
const auto userpicWrap = content->add(
object_ptr<Ui::CenterWrap<>>(
content,
object_ptr<Ui::RpWidget>(content)));
const auto userpic = userpicWrap->entity();
const auto userpic = content->add(
object_ptr<Ui::RpWidget>(content),
style::al_top);
const auto photoSize = st::confirmInvitePhotoSize;
userpic->resize(Size(photoSize));
userpic->setNaturalWidth(photoSize);
const auto creditsIconSize = photoSize / 3;
const auto creditsIconCallback =
Ui::PaintOutlinedColoredCreditsIconCallback(
@@ -188,8 +187,8 @@ void ConfirmSubscriptionBox(
}
auto p = QPainter(userpic);
p.drawImage(0, 0, state->frame);
}, userpicWrap->lifetime());
userpicWrap->setAttribute(Qt::WA_TransparentForMouseEvents);
}, userpic->lifetime());
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
if (photo) {
state->photoMedia = photo->createMediaView();
state->photoMedia->wanted(Data::PhotoSize::Small, Data::FileOrigin());
@@ -197,7 +196,7 @@ void ConfirmSubscriptionBox(
session->downloaderTaskFinished(
) | rpl::start_with_next([=] {
userpic->update();
}, userpicWrap->entity()->lifetime());
}, userpic->lifetime());
}
} else {
state->photoEmpty = std::make_unique<Ui::EmptyUserpic>(
@@ -219,7 +218,6 @@ void ConfirmSubscriptionBox(
box,
tr::lng_channel_invite_subscription_title(),
st::inviteLinkSubscribeBoxTitle),
st::boxRowPadding,
style::al_top);
box->addRow(
object_ptr<Ui::FlatLabel>(
@@ -234,7 +232,6 @@ void ConfirmSubscriptionBox(
Ui::Text::Bold),
Ui::Text::WithEntities),
st::inviteLinkSubscribeBoxAbout),
st::boxRowPadding,
style::al_top);
Ui::AddSkip(content);
box->addRow(
@@ -250,7 +247,6 @@ void ConfirmSubscriptionBox(
}),
Ui::Text::RichLangValue),
st::inviteLinkSubscribeBoxTerms),
st::boxRowPadding,
style::al_top);
{

View File

@@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item_helpers.h"
#include "main/main_app_config.h"
#include "main/main_session.h"
#include "main/main_session_settings.h"
namespace Api {
@@ -25,6 +26,32 @@ Transcribes::Transcribes(not_null<ApiWrap*> api)
, _api(&api->instance()) {
}
bool Transcribes::isRated(not_null<HistoryItem*> item) const {
const auto fullId = item->fullId();
for (const auto &[transcribeId, id] : _ids) {
if (id == fullId) {
return _session->settings().isTranscriptionRated(transcribeId);
}
}
return false;
}
void Transcribes::rate(not_null<HistoryItem*> item, bool isGood) {
const auto fullId = item->fullId();
for (const auto &[transcribeId, id] : _ids) {
if (id == fullId) {
_api.request(MTPmessages_RateTranscribedAudio(
item->history()->peer->input,
MTP_int(item->id),
MTP_long(transcribeId),
MTP_bool(isGood))).send();
_session->settings().markTranscriptionAsRated(transcribeId);
_session->saveSettings();
return;
}
}
}
bool Transcribes::freeFor(not_null<HistoryItem*> item) const {
if (const auto channel = item->history()->peer->asMegagroup()) {
const auto owner = &channel->owner();

View File

@@ -37,6 +37,8 @@ public:
void apply(const MTPDupdateTranscribedAudio &update);
[[nodiscard]] bool freeFor(not_null<HistoryItem*> item) const;
[[nodiscard]] bool isRated(not_null<HistoryItem*> item) const;
void rate(not_null<HistoryItem*> item, bool isGood);
[[nodiscard]] bool trialsSupport();
[[nodiscard]] TimeId trialsRefreshAt();

View File

@@ -2458,7 +2458,16 @@ void ApiWrap::refreshFileReference(
v::match(origin.data, [&](Data::FileOriginMessage data) {
if (const auto item = _session->data().message(data)) {
const auto media = item->media();
const auto storyId = media ? media->storyId() : FullStoryId();
const auto mediaStory = media ? media->storyId() : FullStoryId();
const auto storyId = mediaStory
? mediaStory
: FullStoryId(
(IsStoryMsgId(item->id)
? item->history()->peer->id
: PeerId()),
(IsStoryMsgId(item->id)
? StoryIdFromMsgId(item->id)
: StoryId()));
if (storyId) {
request(MTPstories_GetStoriesByID(
_session->data().peer(storyId.peer)->input,

View File

@@ -28,7 +28,7 @@ namespace Data {
struct UpdatedFileReferences;
class WallPaper;
struct ResolvedForwardDraft;
enum class DefaultNotify;
enum class DefaultNotify : uint8_t;
enum class StickersType : uchar;
class Forum;
class ForumTopic;

View File

@@ -13,12 +13,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/update_checker.h"
#include "lang/lang_keys.h"
#include "ui/boxes/confirm_box.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/text/text_utilities.h"
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h"
#include "ui/wrap/vertical_layout.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
#include "styles/style_channel_earn.h"
#include "styles/style_chat.h"
#include "styles/style_dialogs.h"
#include "styles/style_menu_icons.h"
#include "styles/style_premium.h"
#include "styles/style_settings.h"
#include <QtGui/QGuiApplication>
#include <QtGui/QClipboard>
@@ -158,3 +166,151 @@ QString currentVersionText() {
#endif
return result;
}
void ArchiveHintBox(
not_null<Ui::GenericBox*> box,
bool unarchiveOnNewMessage,
Fn<void()> onUnarchive) {
box->setNoContentMargin(true);
const auto content = box->verticalLayout().get();
Ui::AddSkip(content);
Ui::AddSkip(content);
Ui::AddSkip(content);
{
const auto &icon = st::dialogsArchiveUserpic;
const auto rect = Rect(icon.size() * 2);
auto owned = object_ptr<Ui::RpWidget>(content);
owned->resize(rect.size());
owned->setNaturalWidth(rect.width());
const auto widget = box->addRow(std::move(owned), style::al_top);
widget->paintRequest(
) | rpl::start_with_next([=] {
auto p = Painter(widget);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(st::activeButtonBg);
p.drawEllipse(rect);
icon.paintInCenter(p, rect);
}, widget->lifetime());
}
Ui::AddSkip(content);
Ui::AddSkip(content);
box->addRow(
object_ptr<Ui::FlatLabel>(
content,
tr::lng_archive_hint_title(),
st::boxTitle),
style::al_top);
Ui::AddSkip(content);
Ui::AddSkip(content);
{
const auto label = box->addRow(
object_ptr<Ui::FlatLabel>(
content,
(unarchiveOnNewMessage
? tr::lng_archive_hint_about_unmuted
: tr::lng_archive_hint_about)(
lt_link,
tr::lng_archive_hint_about_link(
lt_emoji,
rpl::single(
Ui::Text::IconEmoji(&st::textMoreIconEmoji)),
Ui::Text::RichLangValue
) | rpl::map([](TextWithEntities text) {
return Ui::Text::Link(std::move(text), 1);
}),
Ui::Text::RichLangValue),
st::channelEarnHistoryRecipientLabel));
label->resizeToWidth(box->width()
- rect::m::sum::h(st::boxRowPadding));
label->setLink(
1,
std::make_shared<GenericClickHandler>([=](ClickContext context) {
if (context.button == Qt::LeftButton) {
onUnarchive();
}
}));
}
Ui::AddSkip(content);
Ui::AddSkip(content);
Ui::AddSkip(content);
Ui::AddSkip(content);
{
const auto padding = QMargins(
st::settingsButton.padding.left(),
st::boxRowPadding.top(),
st::boxRowPadding.right(),
st::boxRowPadding.bottom());
const auto addEntry = [&](
rpl::producer<QString> title,
rpl::producer<QString> about,
const style::icon &icon) {
const auto top = content->add(
object_ptr<Ui::FlatLabel>(
content,
std::move(title),
st::channelEarnSemiboldLabel),
padding);
Ui::AddSkip(content, st::channelEarnHistoryThreeSkip);
content->add(
object_ptr<Ui::FlatLabel>(
content,
std::move(about),
st::channelEarnHistoryRecipientLabel),
padding);
const auto left = Ui::CreateChild<Ui::RpWidget>(
box->verticalLayout().get());
left->paintRequest(
) | rpl::start_with_next([=] {
auto p = Painter(left);
icon.paint(p, 0, 0, left->width());
}, left->lifetime());
left->resize(icon.size());
top->geometryValue(
) | rpl::start_with_next([=](const QRect &g) {
left->moveToLeft(
(g.left() - left->width()) / 2,
g.top() + st::channelEarnHistoryThreeSkip);
}, left->lifetime());
};
addEntry(
tr::lng_archive_hint_section_1(),
tr::lng_archive_hint_section_1_info(),
st::menuIconArchive);
Ui::AddSkip(content);
Ui::AddSkip(content);
addEntry(
tr::lng_archive_hint_section_2(),
tr::lng_archive_hint_section_2_info(),
st::menuIconStealth);
Ui::AddSkip(content);
Ui::AddSkip(content);
addEntry(
tr::lng_archive_hint_section_3(),
tr::lng_archive_hint_section_3_info(),
st::menuIconStoriesSavedSection);
Ui::AddSkip(content);
Ui::AddSkip(content);
}
Ui::AddSkip(content);
Ui::AddSkip(content);
Ui::AddSkip(content);
{
const auto &st = st::premiumPreviewDoubledLimitsBox;
box->setStyle(st);
auto button = object_ptr<Ui::RoundButton>(
box,
tr::lng_archive_hint_button(),
st::defaultActiveButton);
button->setTextTransform(
Ui::RoundButton::TextTransform::NoTransform);
button->resizeToWidth(box->width()
- st.buttonPadding.left()
- st.buttonPadding.left());
button->setClickedCallback([=] { box->closeBox(); });
box->addButton(std::move(button));
}
}

View File

@@ -10,6 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/layers/generic_box.h"
void AboutBox(not_null<Ui::GenericBox*> box);
void ArchiveHintBox(
not_null<Ui::GenericBox*> box,
bool unarchiveOnNewMessage,
Fn<void()> onUnarchive);
QString telegramFaqLink();
QString currentVersionText();

View File

@@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/buttons.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/labels.h"
#include "ui/rect.h"
#include "ui/wrap/slide_wrap.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
@@ -111,16 +112,12 @@ void DeleteMessagesBox::prepare() {
Ui::Text::RichLangValue);
deleteStyle = &st::attentionBoxButton;
} else if (_wipeHistoryJustClear) {
const auto isChannel = peer->isBroadcast();
const auto isPublicGroup = peer->isMegagroup()
&& peer->asChannel()->isPublic();
if (isChannel || isPublicGroup) {
canDelete = false;
}
details.text = isChannel
? tr::lng_no_clear_history_channel(tr::now)
: isPublicGroup
? tr::lng_no_clear_history_group(tr::now)
_revokeJustClearForChannel = true;
details.text = (peer->isChannel() && !peer->isMegagroup())
? tr::lng_sure_delete_channel_history(
tr::now,
lt_channel,
peer->name())
: peer->isSelf()
? tr::lng_sure_delete_saved_messages(tr::now)
: peer->isUser()
@@ -156,7 +153,8 @@ void DeleteMessagesBox::prepare() {
}
deleteStyle = &st::attentionBoxButton;
}
if (auto revoke = revokeText(peer)) {
if (_revokeJustClearForChannel) {
} else if (auto revoke = revokeText(peer)) {
_revoke.create(
this,
revoke->checkbox,
@@ -285,6 +283,7 @@ void DeleteMessagesBox::prepare() {
}
}
_text.create(this, rpl::single(std::move(details)), st::boxLabel);
_text->resizeToWidth(st::boxWidth - rect::m::sum::h(st::boxPadding));
if (_wipeHistoryJustClear && _wipeHistoryPeer) {
const auto validator = TTLMenu::TTLValidator(
@@ -554,7 +553,9 @@ void DeleteMessagesBox::deleteAndClear() {
!_revoke->checked());
Core::App().saveSettingsDelayed();
}
const auto revoke = _revoke ? _revoke->checked() : _revokeForBot;
const auto revoke = _revoke
? _revoke->checked()
: (_revokeForBot || _revokeJustClearForChannel);
const auto session = _session;
const auto invokeCallbackAndClose = [&] {
// deleteMessages can initiate closing of the current section,

View File

@@ -75,6 +75,7 @@ private:
bool _moderateDeleteAll = false;
bool _revokeForBot = false;
bool _revokeJustClearForChannel = false;
object_ptr<Ui::FlatLabel> _text = { nullptr };
object_ptr<Ui::Checkbox> _revoke = { nullptr };

View File

@@ -1293,7 +1293,7 @@ void EditDirectMessagesPriceBox(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
box,
object_ptr<Ui::VerticalLayout>(box)),
{});
style::margins());
wrap->toggle(savedValue.has_value(), anim::type::instant);
wrap->toggleOn(toggle->toggledChanges());

View File

@@ -24,7 +24,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/rect.h"
#include "ui/text/text_utilities.h"
#include "ui/vertical_list.h"
#include "ui/widgets/label_with_custom_emoji.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
#include "styles/style_channel_earn.h"
@@ -53,9 +52,8 @@ void GiftCreditsBox(
Ui::AddSkip(content);
const auto &stUser = st::premiumGiftsUserpicButton;
const auto userpicWrap = content->add(
object_ptr<Ui::CenterWrap<>>(
content,
object_ptr<Ui::UserpicButton>(content, peer, stUser)));
object_ptr<Ui::UserpicButton>(content, peer, stUser),
style::al_top);
userpicWrap->setAttribute(Qt::WA_TransparentForMouseEvents);
Ui::AddSkip(content);
Ui::AddSkip(content);
@@ -78,7 +76,7 @@ void GiftCreditsBox(
u"internal:stars_examples"_q);
});
content->add(
Ui::CreateLabelWithCustomEmoji(
object_ptr<Ui::FlatLabel>(
content,
tr::lng_credits_box_history_entry_gift_out_about(
lt_user,
@@ -86,7 +84,6 @@ void GiftCreditsBox(
lt_link,
std::move(link),
Ui::Text::RichLangValue),
Core::TextContext({ .session = &peer->session() }),
st::creditsBoxAbout),
st::boxRowPadding,
style::al_top);

View File

@@ -990,12 +990,11 @@ void GiveawayInfoBox(
label->setTextColorOverride(st::windowActiveTextFg->c);
}
const auto result = box->addRow(
object_ptr<Ui::PaddingWrap<Ui::CenterWrap<Ui::FlatLabel>>>(
object_ptr<Ui::PaddingWrap<Ui::FlatLabel>>(
box.get(),
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
box.get(),
std::move(label)),
QMargins(0, skip, 0, skip)));
std::move(label),
QMargins(0, skip, 0, skip)),
style::al_justify);
result->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(result);
p.setPen(Qt::NoPen);

View File

@@ -513,7 +513,7 @@ void InviteForbiddenController::setComplexCover() {
container->add(
MakeShowOrLabel(container, tr::lng_invite_upgrade_or()),
st::inviteForbiddenOrLabelPadding,
style::al_top);
style::al_justify);
}
container->add(
object_ptr<Ui::FlatLabel>(

View File

@@ -77,7 +77,9 @@ DefaultIconEmoji::DefaultIconEmoji(
std::move(value) | rpl::start_with_next([=](DefaultIcon value) {
_icon = value;
_image = QImage();
repaint();
if (repaint) {
repaint();
}
}, _lifetime);
}

View File

@@ -1200,7 +1200,7 @@ void EditPeerColorBox(
peer,
state->index.value(),
state->emojiId.value()
), {});
), style::margins());
auto indices = peer->session().api().peerColors().suggestedValue();
const auto margin = st::settingsColorRadioMargin;

View File

@@ -969,9 +969,9 @@ void Controller::rowClicked(not_null<PeerListRow*> row) {
const auto photoSize = st::boostReplaceUserpic.photoSize;
const auto session = &row->peer()->session();
content->add(object_ptr<Ui::CenterWrap<>>(
content,
Settings::SubscriptionUserpic(content, channel, photoSize)));
content->add(
Settings::SubscriptionUserpic(content, channel, photoSize),
style::al_top);
Ui::AddSkip(content);
Ui::AddSkip(content);
@@ -981,7 +981,6 @@ void Controller::rowClicked(not_null<PeerListRow*> row) {
box,
tr::lng_credits_box_subscription_title(),
st::creditsBoxAboutTitle),
st::boxRowPadding,
style::al_top);
Ui::AddSkip(content);
@@ -990,7 +989,6 @@ void Controller::rowClicked(not_null<PeerListRow*> row) {
object_ptr<Ui::FlatLabel>(
box,
st::creditsTopupPrice),
st::boxRowPadding,
style::al_top);
subtitle1->setMarkedText(
tr::lng_credits_subscription_subtitle(
@@ -1002,11 +1000,10 @@ void Controller::rowClicked(not_null<PeerListRow*> row) {
Ui::Text::WithEntities),
_emojiHelper.context());
const auto subtitle2 = box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
object_ptr<Ui::FlatLabel>(
box,
object_ptr<Ui::FlatLabel>(
box,
st::creditsTopupPrice)))->entity();
st::creditsTopupPrice),
style::al_top);
session->credits().rateValue(
channel
) | rpl::start_with_next([=, currency = u"USD"_q](float64 rate) {
@@ -1038,7 +1035,6 @@ void Controller::rowClicked(not_null<PeerListRow*> row) {
tr::lng_credits_box_out_about_link(tr::now)),
Ui::Text::WithEntities),
st::creditsBoxAboutDivider),
st::boxRowPadding,
style::al_top);
const auto button = box->addButton(tr::lng_box_ok(), [=] {

View File

@@ -585,14 +585,14 @@ void ChannelsLimitBox(
const auto content = box->addRow(
object_ptr<PeerListContent>(box, controller),
{});
style::margins());
delegate->setContent(content);
controller->setDelegate(delegate);
const auto count = 100;
const auto placeholder = box->addRow(
object_ptr<PeerListDummy>(box, count, st::defaultPeerList),
{});
style::margins());
using namespace rpl::mappers;
controller->countValue(
@@ -676,14 +676,14 @@ void PublicLinksLimitBox(
const auto content = box->addRow(
object_ptr<PeerListContent>(box, controller),
{});
style::margins());
delegate->setContent(content);
controller->setDelegate(delegate);
const auto count = defaultLimit;
const auto placeholder = box->addRow(
object_ptr<PeerListDummy>(box, count, st::defaultPeerList),
{});
style::margins());
using namespace rpl::mappers;
controller->countValue(

View File

@@ -902,7 +902,7 @@ void PreviewBox(
const auto outer = box->addRow(
ChatBackPreview(box, size.height(), back),
{});
style::margins());
struct Hiding {
not_null<Ui::RpWidget*> widget;

View File

@@ -7,8 +7,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/ringtones_box.h"
#include "data/notify/data_peer_notify_volume.h"
#include "data/notify/data_peer_notify_settings.h"
#include "api/api_ringtones.h"
#include "apiwrap.h"
#include "ui/widgets/continuous_sliders.h"
#include "base/call_delayed.h"
#include "base/event_filter.h"
#include "base/timer_rpl.h"
@@ -21,11 +24,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_document_media.h"
#include "data/data_document_resolver.h"
#include "data/data_thread.h"
#include "data/data_peer.h"
#include "data/data_session.h"
#include "data/notify/data_notify_settings.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "main/main_session_settings.h"
#include "media/audio/media_audio.h"
#include "ui/wrap/slide_wrap.h"
#include "platform/platform_notifications_manager.h"
#include "settings/settings_common.h"
#include "ui/boxes/confirm_box.h"
@@ -111,7 +117,8 @@ void RingtonesBox(
not_null<Ui::GenericBox*> box,
not_null<Main::Session*> session,
Data::NotifySound selected,
Fn<void(Data::NotifySound)> save) {
Fn<void(Data::NotifySound)> save,
Data::VolumeController volumeController) {
box->setTitle(tr::lng_ringtones_box_title());
const auto container = box->verticalLayout();
@@ -128,12 +135,17 @@ void RingtonesBox(
QPointer<Ui::Radiobutton> defaultButton;
QPointer<Ui::Radiobutton> chosenButton;
std::vector<QPointer<Ui::Radiobutton>> buttons;
ushort presavedVolume = 0;
};
const auto state = container->lifetime().make_state<State>(State{
.group = std::make_shared<Ui::RadiobuttonGroup>(),
.chosen = selected,
});
const auto volumeOverride = [volume = volumeController.volume] {
return volume ? (0.01 * volume()) : -1;
};
const auto addToGroup = [=](
not_null<Ui::VerticalLayout*> verticalLayout,
int value,
@@ -156,7 +168,10 @@ void RingtonesBox(
if (value == kDefaultValue) {
state->defaultButton = button;
button->setClickedCallback([=] {
Core::App().notifications().playSound(session, 0);
Core::App().notifications().playSound(
session,
0,
volumeOverride());
});
}
if (value < 0) {
@@ -170,7 +185,8 @@ void RingtonesBox(
if (media->loaded()) {
Core::App().notifications().playSound(
session,
media->owner()->id);
media->owner()->id,
volumeOverride());
}
});
base::install_event_filter(button, [=](not_null<QEvent*> e) {
@@ -320,6 +336,31 @@ void RingtonesBox(
}));
});
if (volumeController.volume && volumeController.saveVolume) {
auto saveAndTestVolume = [=](ushort currentVolume) {
state->presavedVolume = currentVolume;
const auto value = state->group->current();
if (value != kNoSoundValue) {
Core::App().notifications().playSound(
session,
(value == kDefaultValue)
? 0
: state->medias[value]->owner()->id,
0.01 * currentVolume);
}
};
Ui::AddRingtonesVolumeSlider(
container,
state->group->value() | rpl::map([=](int value) {
return value != kNoSoundValue;
}),
tr::lng_ringtones_box_volume(),
Data::VolumeController{
base::take(volumeController.volume),
std::move(saveAndTestVolume),
});
}
box->addSkip(st::ringtonesBoxSkip);
Ui::AddDividerText(container, tr::lng_ringtones_box_about());
@@ -333,6 +374,9 @@ void RingtonesBox(
: (value == kNoSoundValue)
? Data::NotifySound{ .none = true }
: Data::NotifySound{ .id = state->medias[value]->owner()->id };
if (state->presavedVolume) {
volumeController.saveVolume(state->presavedVolume);
}
save(sound);
box->closeBox();
});
@@ -345,5 +389,5 @@ void ThreadRingtonesBox(
const auto now = thread->owner().notifySettings().sound(thread);
RingtonesBox(box, &thread->session(), now, [=](Data::NotifySound sound) {
thread->owner().notifySettings().update(thread, {}, {}, sound);
});
}, Data::ThreadRingtonesVolumeController(thread));
}

View File

@@ -12,6 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Data {
struct NotifySound;
class Thread;
enum class DefaultNotify : uint8_t;
struct VolumeController;
} // namespace Data
namespace Main {
@@ -28,7 +30,8 @@ void RingtonesBox(
not_null<Ui::GenericBox*> box,
not_null<Main::Session*> session,
Data::NotifySound selected,
Fn<void(Data::NotifySound)> save);
Fn<void(Data::NotifySound)> save,
Data::VolumeController volumeController);
void ThreadRingtonesBox(
not_null<Ui::GenericBox*> box,

View File

@@ -348,9 +348,9 @@ void SendCreditsBox(
}, ministarsContainer->lifetime());
}
const auto thumb = box->addRow(object_ptr<Ui::CenterWrap<>>(
content,
SendCreditsThumbnail(content, session, form.get(), photoSize)));
const auto thumb = box->addRow(
SendCreditsThumbnail(content, session, form.get(), photoSize),
style::al_top);
thumb->setAttribute(Qt::WA_TransparentForMouseEvents);
if (form->invoice.subscriptionPeriod) {
const auto badge = SendCreditsBadge(content, form->invoice.amount);
@@ -371,16 +371,14 @@ void SendCreditsBox(
? rpl::single(form->title)
: tr::lng_credits_box_out_title(),
st::settingsPremiumUserTitle),
st::boxRowPadding,
style::al_top);
if (form->invoice.subscriptionPeriod && form->botId && form->photo) {
Ui::AddSkip(content);
Ui::AddSkip(content);
const auto bot = session->data().user(form->botId);
box->addRow(
object_ptr<Ui::CenterWrap<>>(
box,
Ui::CreatePeerBubble(box, bot)));
Ui::CreatePeerBubble(box, bot),
style::al_top);
Ui::AddSkip(content);
}
Ui::AddSkip(content);
@@ -389,7 +387,6 @@ void SendCreditsBox(
box,
SendCreditsConfirmText(session, form.get()),
st::creditsBoxAbout),
st::boxRowPadding,
style::al_top);
Ui::AddSkip(content);
Ui::AddSkip(content);

View File

@@ -3151,10 +3151,9 @@ void GiftBox(
&& uniqueDisallowed;
content->add(
object_ptr<CenterWrap<UserpicButton>>(
content,
object_ptr<UserpicButton>(content, peer, stUser))
)->entity()->setClickedCallback([=] { window->showPeerInfo(peer); });
object_ptr<UserpicButton>(content, peer, stUser),
style::al_top
)->setClickedCallback([=] { window->showPeerInfo(peer); });
AddSkip(content);
AddSkip(content);
@@ -4919,19 +4918,18 @@ void UpgradeBox(
object_ptr<PlainShadow>(container),
st::boxRowPadding + QMargins(0, skip, 0, skip));
const auto checkbox = container->add(
object_ptr<CenterWrap<Checkbox>>(
object_ptr<Checkbox>(
container,
object_ptr<Checkbox>(
container,
(args.canAddComment
? tr::lng_gift_upgrade_add_comment(tr::now)
: args.canAddSender
? tr::lng_gift_upgrade_add_sender(tr::now)
: args.canAddMyComment
? tr::lng_gift_upgrade_add_my_comment(tr::now)
: tr::lng_gift_upgrade_add_my(tr::now)),
args.addDetailsDefault)),
st::defaultCheckbox.margin)->entity();
(args.canAddComment
? tr::lng_gift_upgrade_add_comment(tr::now)
: args.canAddSender
? tr::lng_gift_upgrade_add_sender(tr::now)
: args.canAddMyComment
? tr::lng_gift_upgrade_add_my_comment(tr::now)
: tr::lng_gift_upgrade_add_my(tr::now)),
args.addDetailsDefault),
st::defaultCheckbox.margin,
style::al_top);
checkbox->checkedChanges() | rpl::start_with_next([=](bool checked) {
state->preserveDetails = checked;
}, checkbox->lifetime());

View File

@@ -641,20 +641,11 @@ void ChangeSetNameBox(
const auto it = sets.find(input.id);
return (it == sets.end()) ? QString() : it->second->title;
}();
const auto wrap = box->addRow(object_ptr<Ui::FixedHeightWidget>(
const auto field = box->addRow(object_ptr<Ui::InputField>(
box,
st::editStickerSetNameField.heightMin));
auto owned = object_ptr<Ui::InputField>(
wrap,
st::editStickerSetNameField,
tr::lng_stickers_context_edit_name(),
wasName);
const auto field = owned.data();
wrap->widthValue() | rpl::start_with_next([=](int width) {
field->move(0, 0);
field->resize(width, field->height());
wrap->resize(width, field->height());
}, wrap->lifetime());
wasName));
field->selectAll();
constexpr auto kMaxSetNameLength = 50;
field->setMaxLength(kMaxSetNameLength);

View File

@@ -324,7 +324,7 @@ void UsernamesBox(
const auto editor = box->addRow(
object_ptr<UsernameEditor>(box, peer),
{});
style::margins());
editor->setEnabled(!isBot);
box->setFocusCallback([=] { editor->setInnerFocus(); });
@@ -366,7 +366,7 @@ void UsernamesBox(
!isBot
? [=] { box->scrollToY(0); editor->setInnerFocus(); }
: Fn<void()>(nullptr)),
{});
style::margins());
const auto finish = [=] {
list->save(

View File

@@ -825,7 +825,7 @@ void ShowCallsBox(not_null<::Window::SessionController*> window) {
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
box,
object_ptr<Ui::VerticalLayout>(box)),
{});
style::margins());
groupCalls->hide(anim::type::instant);
groupCalls->toggleOn(state->groupCallsController.shownValue());
@@ -833,8 +833,7 @@ void ShowCallsBox(not_null<::Window::SessionController*> window) {
groupCalls->entity(),
tr::lng_call_box_groupcalls_subtitle());
state->groupCallsDelegate.setContent(groupCalls->entity()->add(
object_ptr<PeerListContent>(box, &state->groupCallsController),
{}));
object_ptr<PeerListContent>(box, &state->groupCallsController)));
state->groupCallsController.setDelegate(&state->groupCallsDelegate);
Ui::AddSkip(groupCalls->entity());
Ui::AddDivider(groupCalls->entity());
@@ -853,7 +852,7 @@ void ShowCallsBox(not_null<::Window::SessionController*> window) {
const auto content = box->addRow(
object_ptr<PeerListContent>(box, &state->callsController),
{});
style::margins());
state->callsDelegate.setContent(content);
state->callsController.setDelegate(&state->callsDelegate);

View File

@@ -116,13 +116,12 @@ void ConferenceCallJoinConfirm(
st::boxRowPadding + st::confcallLinkHeaderIconPadding);
box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
object_ptr<Ui::FlatLabel>(
box,
object_ptr<Ui::FlatLabel>(
box,
tr::lng_confcall_join_title(),
st::boxTitle)),
st::boxRowPadding + st::confcallLinkTitlePadding);
tr::lng_confcall_join_title(),
st::boxTitle),
st::boxRowPadding + st::confcallLinkTitlePadding,
style::al_top);
const auto wrapName = [&](not_null<PeerData*> peer) {
return rpl::single(Ui::Text::Bold(peer->shortName()));
};
@@ -323,13 +322,12 @@ void ShowConferenceCallLinkBox(
Info::BotStarRef::CreateLinkHeaderIcon(box, &call->session()),
st::boxRowPadding + st::confcallLinkHeaderIconPadding);
box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
object_ptr<Ui::FlatLabel>(
box,
object_ptr<Ui::FlatLabel>(
box,
tr::lng_confcall_link_title(),
st.box ? st.box->title : st::boxTitle)),
st::boxRowPadding + st::confcallLinkTitlePadding);
tr::lng_confcall_link_title(),
st.box ? st.box->title : st::boxTitle),
st::boxRowPadding + st::confcallLinkTitlePadding,
style::al_top);
box->addRow(
object_ptr<Ui::FlatLabel>(
box,

View File

@@ -1042,13 +1042,12 @@ not_null<Ui::RpWidget*> CreateReActivateHeader(not_null<QWidget*> parent) {
st::boxRowPadding + st::confcallLinkHeaderIconPadding);
result->add(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
object_ptr<Ui::FlatLabel>(
result,
object_ptr<Ui::FlatLabel>(
result,
tr::lng_confcall_inactive_title(),
st::boxTitle)),
st::boxRowPadding + st::confcallLinkTitlePadding);
tr::lng_confcall_inactive_title(),
st::boxTitle),
st::boxRowPadding + st::confcallLinkTitlePadding,
style::al_top);
result->add(
object_ptr<Ui::FlatLabel>(
result,

View File

@@ -1295,6 +1295,7 @@ historyRecordLockPosition: point(1px, 22px);
historyRecordCancelButtonWidth: 100px;
historyRecordCancelButtonFg: lightButtonFg;
historyRecordTooltipSkip: 8px;
historyRecordTooltip: ImportantTooltip(defaultImportantTooltip) {
padding: margins(4px, 4px, 4px, 4px);
radius: 11px;
@@ -1626,4 +1627,17 @@ frozenInfoBox: Box(defaultBox) {
shadowIgnoreTopSkip: true;
}
menuTranscribeItemPadding: margins(0px, 10px, 0px, 10px);
menuTranscribeDummyButton: IconButton(defaultIconButton) {
width: 40px;
height: 40px;
icon: icon {{ "chat/input_attach", historyComposeIconFgOver }};
iconOver: icon {{ "chat/input_attach", historyComposeIconFgOver }};
rippleAreaPosition: point(3px, 3px);
rippleAreaSize: 34px;
ripple: defaultRippleAnimationBgOver;
}
roundVideoFont: font(14px semibold);

View File

@@ -656,8 +656,20 @@ bool Application::eventFilter(QObject *object, QEvent *e) {
updateNonIdle();
} break;
case QEvent::KeyRelease: {
const auto event = static_cast<QKeyEvent*>(e);
if (Shortcuts::HandlePossibleChatSwitch(event)) {
return true;
}
} break;
case QEvent::ShortcutOverride: {
// handle shortcuts ourselves
// Ctrl+Tab/Ctrl+Shift+Tab chat switch is a special shortcut case,
// because it not only does an action on the shortcut activation,
// but also keeps the UI visible until you release the Ctrl key.
Shortcuts::HandlePossibleChatSwitch(static_cast<QKeyEvent*>(e));
// Handle all the shortcut management manually.
return true;
} break;

View File

@@ -240,7 +240,8 @@ QByteArray Settings::serialize() const {
+ Serialize::stringSize(_customFontFamily)
+ sizeof(qint32) * 3
+ Serialize::bytearraySize(_tonsiteStorageToken)
+ sizeof(qint32) * 8;
+ sizeof(qint32) * 8
+ sizeof(ushort);
auto result = QByteArray();
result.reserve(size);
@@ -402,7 +403,8 @@ QByteArray Settings::serialize() const {
<< SerializeVideoQuality(_videoQuality)
<< qint32(_ivZoom.current())
<< qint32(_systemDarkModeEnabled.current() ? 1 : 0)
<< qint32(_quickDialogAction);
<< qint32(_quickDialogAction)
<< _notificationsVolume;
}
Ensures(result.size() == size);
@@ -532,6 +534,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
quint32 videoQuality = SerializeVideoQuality(_videoQuality);
quint32 chatFiltersHorizontal = _chatFiltersHorizontal.current() ? 1 : 0;
quint32 quickDialogAction = quint32(_quickDialogAction);
ushort notificationsVolume = _notificationsVolume;
stream >> themesAccentColors;
if (!stream.atEnd()) {
@@ -863,6 +866,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
if (!stream.atEnd()) {
stream >> quickDialogAction;
}
if (!stream.atEnd()) {
stream >> notificationsVolume;
}
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: "
"Bad data for Core::Settings::constructFromSerialized()"));
@@ -1085,6 +1091,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
_videoQuality = DeserializeVideoQuality(videoQuality);
_chatFiltersHorizontal = (chatFiltersHorizontal == 1);
_quickDialogAction = Dialogs::Ui::QuickDialogAction(quickDialogAction);
_notificationsVolume = notificationsVolume;
}
QString Settings::getSoundPath(const QString &key) const {
@@ -1477,6 +1484,7 @@ void Settings::resetOnLastLogout() {
_videoQuality = {};
_chatFiltersHorizontal = false;
_quickDialogAction = Dialogs::Ui::QuickDialogAction::Disabled;
_notificationsVolume = 100;
_recentEmojiPreload.clear();
_recentEmoji.clear();

View File

@@ -950,6 +950,13 @@ public:
[[nodiscard]] Dialogs::Ui::QuickDialogAction quickDialogAction() const;
void setQuickDialogAction(Dialogs::Ui::QuickDialogAction);
[[nodiscard]] ushort notificationsVolume() const {
return _notificationsVolume;
}
void setNotificationsVolume(ushort value) {
_notificationsVolume = value;
}
void resetOnLastLogout();
private:
@@ -1093,6 +1100,8 @@ private:
Dialogs::Ui::QuickDialogAction _quickDialogAction
= Dialogs::Ui::QuickDialogAction::Disabled;
ushort _notificationsVolume = 100;
QByteArray _photoEditorBrush;
};

View File

@@ -657,11 +657,7 @@ bool ResolveUsernameOrPhone(
startToken = params.value(u"startapp"_q);
}
}
const auto historyInNewWindow =
(params.value(u"tdesktop_target"_q) == u"blank"_q);
if (!historyInNewWindow) {
controller->window().activate();
}
controller->window().activate();
controller->showPeerByLink(Window::PeerByLinkInfo{
.usernameOrId = domain,
.phone = phone,
@@ -712,7 +708,8 @@ bool ResolveUsernameOrPhone(
: std::nullopt),
.clickFromMessageId = myContext.itemId,
.clickFromBotWebviewContext = myContext.botWebviewContext,
.historyInNewWindow = historyInNewWindow,
.historyInNewWindow =
(params.value(u"tdesktop_target"_q) == u"blank"_q),
});
return true;
}
@@ -1920,8 +1917,6 @@ bool InternalPassportLink(const QString &url) {
bool StartUrlRequiresActivate(const QString &url) {
return Core::App().passcodeLocked()
? true
: url.contains(u"tdesktop_target"_q)
? false
: !InternalPassportLink(url);
}

View File

@@ -30,6 +30,10 @@ constexpr auto kCountLimit = 256; // How many shortcuts can be in json file.
rpl::event_stream<not_null<Request*>> RequestsStream;
bool Paused/* = false*/;
Qt::Key ChatSwitchModifier/* = Qt::Key()*/;
bool ChatSwitchStarted/* = false*/;
rpl::event_stream<ChatSwitchRequest> ChatSwitchStream;
const auto AutoRepeatCommands = base::flat_set<Command>{
Command::MediaPrevious,
Command::MediaNext,
@@ -112,6 +116,8 @@ const auto CommandByName = base::flat_map<QString, Command>{
{ u"show_chat_menu"_q , Command::ShowChatMenu },
{ u"show_chat_preview"_q , Command::ShowChatPreview },
{ u"record_voice"_q , Command::RecordVoice },
// Shortcuts that have no default values.
{ u"message"_q , Command::JustSendMessage },
{ u"message_silently"_q , Command::SendSilentMessage },
@@ -119,6 +125,7 @@ const auto CommandByName = base::flat_map<QString, Command>{
{ u"media_viewer_video_fullscreen"_q , Command::MediaViewerFullscreen },
{ u"show_scheduled"_q , Command::ShowScheduled },
{ u"archive_chat"_q , Command::ArchiveChat },
{ u"record_round"_q , Command::RecordRound },
//
};
@@ -140,6 +147,7 @@ const base::flat_map<Command, QString> &CommandNames() {
Command::MediaViewerFullscreen,
Command::ShowScheduled,
Command::ArchiveChat,
Command::RecordRound,
};
class Manager {
@@ -152,6 +160,7 @@ public:
void toggleMedia(bool toggled);
void toggleSupport(bool toggled);
void listen(not_null<QWidget*> widget);
[[nodiscard]] bool handles(const QKeySequence &sequence) const;
[[nodiscard]] const QStringList &errors() const;
@@ -357,6 +366,10 @@ void Manager::listen(not_null<QWidget*> widget) {
}
}
bool Manager::handles(const QKeySequence &sequence) const {
return _shortcuts.contains(sequence);
}
void Manager::pruneListened() {
for (auto i = begin(_listened); i != end(_listened);) {
if (i->data()) {
@@ -466,10 +479,6 @@ void Manager::fillDefaults() {
set(u"ctrl+pgup"_q, Command::ChatPrevious);
set(u"alt+up"_q, Command::ChatPrevious);
set(u"%1+tab"_q.arg(ctrl), Command::ChatNext);
set(u"%1+shift+tab"_q.arg(ctrl), Command::ChatPrevious);
set(u"%1+backtab"_q.arg(ctrl), Command::ChatPrevious);
set(u"ctrl+alt+home"_q, Command::ChatFirst);
set(u"ctrl+alt+end"_q, Command::ChatLast);
@@ -509,6 +518,8 @@ void Manager::fillDefaults() {
set(u"ctrl+\\"_q, Command::ShowChatMenu);
set(u"ctrl+]"_q, Command::ShowChatPreview);
set(u"ctrl+r"_q, Command::RecordVoice);
_defaults = keysCurrents();
}
@@ -794,6 +805,72 @@ bool HandleEvent(
return Launch(Data.lookup(object));
}
void CancelChatSwitch(Qt::Key result) {
ChatSwitchModifier = Qt::Key();
if (ChatSwitchStarted) {
ChatSwitchStarted = false;
ChatSwitchStream.fire({ .action = result });
}
}
rpl::producer<ChatSwitchRequest> ChatSwitchRequests() {
return ChatSwitchStream.events();
}
bool HandlePossibleChatSwitch(not_null<QKeyEvent*> event) {
const auto type = event->type();
if (Paused) {
return false;
} else if (type == QEvent::ShortcutOverride) {
const auto key = Qt::Key(event->key());
if (key == Qt::Key_Escape) {
CancelChatSwitch(Qt::Key_Escape);
return false;
} else if (key == Qt::Key_Return || key == Qt::Key_Enter) {
CancelChatSwitch(Qt::Key_Enter);
return false;
}
const auto ctrl = Platform::IsMac()
? Qt::MetaModifier
: Qt::ControlModifier;
if (Data.handles(ctrl | Qt::ShiftModifier | Qt::Key_Tab)
&& Data.handles(QKeySequence(ctrl | Qt::Key_Tab))
&& Data.handles(QKeySequence(ctrl | Qt::Key_Backtab))) {
return false;
} else if (key == Qt::Key_Control || key == Qt::Key_Meta) {
ChatSwitchModifier = key;
} else if (key == Qt::Key_Tab || key == Qt::Key_Backtab) {
const auto modifiers = event->modifiers();
if (modifiers & ctrl) {
if (Data.handles(modifiers | key)) {
return false;
}
if (ChatSwitchModifier == Qt::Key()) {
ChatSwitchModifier = Platform::IsMac()
? Qt::Key_Meta
: Qt::Key_Control;
}
const auto action = (modifiers & Qt::ShiftModifier)
? Qt::Key_Backtab
: key;
const auto started = !std::exchange(ChatSwitchStarted, true);
ChatSwitchStream.fire({
.action = action,
.started = started,
});
return true;
}
}
} else if (type == QEvent::KeyRelease) {
const auto key = Qt::Key(event->key());
if (key == ChatSwitchModifier) {
CancelChatSwitch(Qt::Key_Enter);
}
}
return false;
}
void ToggleMediaShortcuts(bool toggled) {
Data.toggleMedia(toggled);
}

View File

@@ -7,6 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
class QKeyEvent;
class QShortcutEvent;
namespace Shortcuts {
enum class Command {
@@ -66,6 +69,9 @@ enum class Command {
SendSilentMessage,
ScheduleMessage,
RecordVoice,
RecordRound,
ReadChat,
ArchiveChat,
@@ -119,7 +125,7 @@ private:
};
rpl::producer<not_null<Request*>> Requests();
[[nodiscard]] rpl::producer<not_null<Request*>> Requests();
void Start();
void Finish();
@@ -129,7 +135,15 @@ void Listen(not_null<QWidget*> widget);
bool Launch(Command command);
bool HandleEvent(not_null<QObject*> object, not_null<QShortcutEvent*> event);
const QStringList &Errors();
bool HandlePossibleChatSwitch(not_null<QKeyEvent*> event);
struct ChatSwitchRequest {
Qt::Key action = Qt::Key_Tab; // Key_Tab, Key_Backtab or Key_Escape.
bool started = false;
};
[[nodiscard]] rpl::producer<ChatSwitchRequest> ChatSwitchRequests();
[[nodiscard]] const QStringList &Errors();
// Media shortcuts are not enabled by default, because other
// applications also use them. They are enabled only when

View File

@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
constexpr auto AppName = "Telegram Desktop"_cs;
constexpr auto AppFile = "Telegram"_cs;
constexpr auto AppVersion = 6000002;
constexpr auto AppVersionStr = "6.0.2";
constexpr auto AppBetaVersion = false;
constexpr auto AppVersion = 6000003;
constexpr auto AppVersionStr = "6.0.3";
constexpr auto AppBetaVersion = true;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View File

@@ -7,6 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/components/recent_peers.h"
#include "data/data_peer.h"
#include "data/data_session.h"
#include "history/history.h"
#include "main/main_session.h"
#include "storage/serialize_common.h"
#include "storage/serialize_peer.h"
@@ -16,6 +19,7 @@ namespace Data {
namespace {
constexpr auto kLimit = 48;
constexpr auto kMaxRememberedOpenChats = 32;
} // namespace
@@ -133,4 +137,40 @@ void RecentPeers::applyLocal(QByteArray serialized) {
("Suggestions: RecentPeers read OK, count: %1").arg(_list.size()));
}
std::vector<not_null<Thread*>> RecentPeers::collectChatOpenHistory() const {
_session->local().readSearchSuggestions();
auto result = _opens;
result.reserve(result.size() + _list.size());
for (const auto &peer : _list) {
const auto history = peer->owner().history(peer);
const auto thread = not_null<Data::Thread*>(history);
if (!ranges::contains(result, thread)) {
result.push_back(thread);
}
}
return result;
}
void RecentPeers::chatOpenPush(not_null<Thread*> thread) {
const auto i = ranges::find(_opens, thread);
if (i == end(_opens)) {
while (_opens.size() >= kMaxRememberedOpenChats) {
_opens.pop_back();
}
_opens.insert(begin(_opens), thread);
} else if (i != begin(_opens)) {
ranges::rotate(begin(_opens), i, i + 1);
}
}
void RecentPeers::chatOpenDestroyed(not_null<Thread*> thread) {
_opens.erase(ranges::remove(_opens, thread), end(_opens));
}
void RecentPeers::chatOpenKeepUserpics(
base::flat_map<not_null<PeerData*>, Ui::PeerUserpicView> userpics) {
_chatOpenUserpicsCache = std::move(userpics);
}
} // namespace Data

View File

@@ -7,12 +7,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "ui/userpic_view.h"
namespace Main {
class Session;
} // namespace Main
namespace Data {
class Thread;
class RecentPeers final {
public:
explicit RecentPeers(not_null<Main::Session*> session);
@@ -28,10 +32,22 @@ public:
[[nodiscard]] QByteArray serialize() const;
void applyLocal(QByteArray serialized);
[[nodiscard]] auto collectChatOpenHistory() const
-> std::vector<not_null<Thread*>>;
void chatOpenPush(not_null<Thread*> thread);
void chatOpenDestroyed(not_null<Thread*> thread);
void chatOpenKeepUserpics(
base::flat_map<not_null<PeerData*>, Ui::PeerUserpicView> userpics);
private:
const not_null<Main::Session*> _session;
std::vector<not_null<PeerData*>> _list;
std::vector<not_null<Thread*>> _opens;
base::flat_map<
not_null<PeerData*>,
Ui::PeerUserpicView> _chatOpenUserpicsCache;
rpl::event_stream<> _updates;
};

View File

@@ -235,7 +235,8 @@ void ChannelData::setFlags(ChannelDataFlags which) {
| Flag::CallNotEmpty
| Flag::SimilarExpanded
| Flag::Signatures
| Flag::SignatureProfiles)) {
| Flag::SignatureProfiles
| Flag::ForumTabs)) {
if (const auto history = this->owner().historyLoaded(this)) {
if (diff & Flag::CallNotEmpty) {
history->updateChatListEntry();
@@ -262,6 +263,9 @@ void ChannelData::setFlags(ChannelDataFlags which) {
if (diff & (Flag::Signatures | Flag::SignatureProfiles)) {
session().changes().peerUpdated(this, UpdateFlag::Rights);
}
if (diff & Flag::ForumTabs) {
history->forumTabsChanged(which & Flag::ForumTabs);
}
}
}
if (const auto raw = takenForum.get()) {

View File

@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/data_forum.h"
#include "data/components/recent_peers.h"
#include "data/data_channel.h"
#include "data/data_histories.h"
#include "data/data_changes.h"
@@ -194,6 +195,7 @@ void Forum::applyTopicDeleted(MsgId rootId) {
_activeSubsectionTopic = nullptr;
}
_topicDestroyed.fire(raw);
_history->session().recentPeers().chatOpenDestroyed(raw);
session().changes().topicUpdated(
raw,
Data::TopicUpdate::Flag::Destroyed);

View File

@@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/painter.h"
#include "ui/color_int_conversion.h"
#include "ui/text/text_custom_emoji.h"
#include "ui/text/text_utilities.h"
#include "styles/style_dialogs.h"
#include "styles/style_chat_helpers.h"
@@ -756,6 +757,16 @@ TextWithEntities ForumTopic::titleWithIcon() const {
return ForumTopicIconWithTitle(_rootId, _iconId, _title);
}
TextWithEntities ForumTopic::titleWithIconOrLogo() const {
if (_iconId || isGeneral()) {
return titleWithIcon();
}
return Ui::Text::SingleCustomEmoji(Data::TopicIconEmojiEntity({
.title = _title,
.colorId = _colorId,
})).append(' ').append(_title);
}
int ForumTopic::titleVersion() const {
return _titleVersion;
}

View File

@@ -148,6 +148,7 @@ public:
[[nodiscard]] QString title() const;
[[nodiscard]] TextWithEntities titleWithIcon() const;
[[nodiscard]] TextWithEntities titleWithIconOrLogo() const;
[[nodiscard]] int titleVersion() const;
void applyTitle(const QString &title);
[[nodiscard]] DocumentId iconId() const;

View File

@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "core/application.h"
#include "data/components/recent_peers.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/data_histories.h"
@@ -452,6 +453,7 @@ void SavedMessages::applySublistDeleted(not_null<PeerData*> sublistPeer) {
}
_sublistDestroyed.fire(raw);
_owner->session().recentPeers().chatOpenDestroyed(raw);
session().changes().sublistUpdated(
raw,
Data::SublistUpdate::Flag::Destroyed);

View File

@@ -21,11 +21,6 @@ class Thread;
class Forum;
class ForumTopic;
enum class DefaultNotify {
User,
Group,
Broadcast,
};
[[nodiscard]] DefaultNotify DefaultNotifyType(
not_null<const PeerData*> peer);

View File

@@ -11,6 +11,12 @@ namespace Data {
class NotifyPeerSettingsValue;
enum class DefaultNotify : uint8_t {
User,
Group,
Broadcast,
};
struct NotifySound {
QString title;
QString data;

View File

@@ -0,0 +1,117 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/notify/data_peer_notify_volume.h"
#include "data/data_peer.h"
#include "data/data_thread.h"
#include "data/notify/data_peer_notify_settings.h"
#include "main/main_session.h"
#include "main/main_session_settings.h"
#include "core/application.h"
#include "window/notifications_manager.h"
#include "settings/settings_common.h"
#include "ui/vertical_list.h"
#include "ui/widgets/continuous_sliders.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "styles/style_settings.h"
namespace Data {
Data::VolumeController DefaultRingtonesVolumeController(
not_null<Main::Session*> session,
Data::DefaultNotify defaultNotify) {
return Data::VolumeController{
.volume = [=]() -> ushort {
const auto volume = session->settings().ringtoneVolume(
defaultNotify);
return volume ? volume : 100;
},
.saveVolume = [=](ushort volume) {
session->settings().setRingtoneVolume(defaultNotify, volume);
session->saveSettingsDelayed();
}};
}
Data::VolumeController ThreadRingtonesVolumeController(
not_null<Data::Thread*> thread) {
return Data::VolumeController{
.volume = [=]() -> ushort {
const auto volume = thread->session().settings().ringtoneVolume(
thread->peer()->id,
thread->topicRootId(),
thread->monoforumPeerId());
return volume ? volume : 100;
},
.saveVolume = [=](ushort volume) {
thread->session().settings().setRingtoneVolume(
thread->peer()->id,
thread->topicRootId(),
thread->monoforumPeerId(),
volume);
thread->session().saveSettingsDelayed();
}};
}
} // namespace Data
namespace Ui {
void AddRingtonesVolumeSlider(
not_null<Ui::VerticalLayout*> container,
rpl::producer<bool> toggleOn,
rpl::producer<QString> subtitle,
Data::VolumeController volumeController) {
Expects(volumeController.volume && volumeController.saveVolume);
const auto volumeWrap = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(container)));
volumeWrap->toggleOn(
rpl::combine(
Core::App().notifications().volumeSupportedValue(),
std::move(toggleOn)
) | rpl::map(
rpl::mappers::_1 && rpl::mappers::_2
) | rpl::distinct_until_changed(),
anim::type::normal);
volumeWrap->finishAnimating();
Ui::AddSubsectionTitle(volumeWrap->entity(), std::move(subtitle));
auto sliderWithLabel = Settings::MakeSliderWithLabel(
volumeWrap->entity(),
st::settingsScale,
st::settingsScaleLabel,
st::normalFont->spacew * 2,
st::settingsScaleLabel.style.font->width("100%"),
true);
const auto slider = sliderWithLabel.slider;
const auto label = sliderWithLabel.label;
volumeWrap->entity()->add(
std::move(sliderWithLabel.widget),
st::settingsBigScalePadding);
const auto updateLabel = [=](int volume) {
label->setText(QString::number(volume) + '%');
};
slider->setPseudoDiscrete(
100,
[=](int index) { return index + 1; },
int(volumeController.volume()),
updateLabel,
[saveVolume = volumeController.saveVolume](int volume) {
saveVolume(volume);
});
updateLabel(volumeController.volume());
}
} // namespace Ui

View File

@@ -0,0 +1,45 @@
/*
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
namespace Main {
class Session;
} // namespace Main
namespace Ui {
class VerticalLayout;
} // namespace Ui
namespace Data {
enum class DefaultNotify : uint8_t;
class Thread;
struct VolumeController {
Fn<ushort()> volume = nullptr;
Fn<void(ushort)> saveVolume = nullptr;
};
[[nodiscard]] VolumeController DefaultRingtonesVolumeController(
not_null<Main::Session*> session,
Data::DefaultNotify defaultNotify);
[[nodiscard]] VolumeController ThreadRingtonesVolumeController(
not_null<Data::Thread*> thread);
} // namespace Data
namespace Ui {
void AddRingtonesVolumeSlider(
not_null<Ui::VerticalLayout*> container,
rpl::producer<bool> toggleOn,
rpl::producer<QString> subtitle,
Data::VolumeController volumeController);
} // namespace Ui

View File

@@ -838,6 +838,8 @@ dialogsTopBarSuggestionAboutStyle: TextStyle(defaultTextStyle) {
font: font(11px);
}
dialogsMiniQuoteIcon: icon{{ "chat/mini_quote", dialogsTextFg }};
postsSearchIntroTitle: FlatLabel(defaultFlatLabel) {
textFg: windowBoldFg;
minWidth: 64px;

View File

@@ -1240,6 +1240,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
auto to = ceilclamp(r.y() + r.height() - skip, _st->height, 0, _previewResults.size());
p.translate(0, from * _st->height);
if (from < _previewResults.size()) {
const auto searchLowerText = _searchState.query.toLower();
for (; from < to; ++from) {
const auto &result = _previewResults[from];
const auto active = isSearchResultActive(result.get(), activeEntry);
@@ -1257,6 +1258,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
.currentBg = currentBg(),
.filter = _filterId,
.now = ms,
.searchLowerText = QStringView(searchLowerText),
.width = fullWidth,
.active = active,
.selected = selected,
@@ -1284,6 +1286,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
tr::now,
lt_count,
_searchedMigratedCount + _searchedCount);
const auto searchLowerText = _searchState.query.toLower();
p.fillRect(0, 0, fullWidth, st::searchedBarHeight, st::searchedBarBg);
p.setFont(st::searchedBarFont);
p.setPen(st::searchedBarFg);
@@ -1331,6 +1334,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
.currentBg = currentBg(),
.filter = _filterId,
.now = ms,
.searchLowerText = QStringView(searchLowerText),
.width = fullWidth,
.active = active,
.selected = selected,
@@ -4147,14 +4151,13 @@ void InnerWidget::refreshEmpty() {
.name = u"no_chats"_q,
.sizeOverride = Size(st::changePhoneIconSize),
});
_emptyList->add(std::move(icon.widget), {}, style::al_top);
_emptyList->add(std::move(icon.widget), style::al_top);
Ui::AddSkip(_emptyList);
_emptyList->add(
object_ptr<Ui::FlatLabel>(
_emptyList,
tr::lng_no_conversations(),
st::dialogEmptyButtonLabel),
{},
style::al_top);
if (_state == WidgetState::Default) {
icon.animate(anim::repeat::once);

View File

@@ -1137,6 +1137,7 @@ void RowPainter::Paint(
return {};
}();
previewOptions.ignoreGroup = true;
previewOptions.searchLowerText = context.searchLowerText;
const auto badgesState = context.displayUnreadInfo
? entry->chatListBadgesState()

View File

@@ -64,6 +64,7 @@ struct PaintContext {
FilterId filter = 0;
float64 topicsExpanded = 0.;
crl::time now = 0;
QStringView searchLowerText;
int width = 0;
bool active = false;
bool selected = false;

View File

@@ -9,15 +9,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h"
#include "history/history_item.h"
#include "history/view/history_view_element.h"
#include "history/view/history_view_item_preview.h"
#include "main/main_session.h"
#include "dialogs/dialogs_three_state_icon.h"
#include "dialogs/ui/dialogs_layout.h"
#include "dialogs/ui/dialogs_topics_view.h"
#include "ui/effects/spoiler_mess.h"
#include "ui/text/custom_emoji_helper.h"
#include "ui/text/text_options.h"
#include "ui/text/text_utilities.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/power_saving.h"
#include "core/ui_integration.h"
#include "lang/lang_keys.h"
@@ -186,7 +189,7 @@ void MessageView::prepare(
: nullptr;
const auto hasImages = !preview.images.empty();
const auto history = item->history();
const auto context = Core::TextContext({
auto context = Core::TextContext({
.session = &history->session(),
.repaint = customEmojiRepaint,
.customEmojiLoopLimit = kEmojiLoopCount,
@@ -207,13 +210,105 @@ void MessageView::prepare(
}
TextUtilities::Trim(preview.text);
auto textToCache = DialogsPreviewText(std::move(preview.text));
if (!options.searchLowerText.isEmpty()) {
static constexpr auto kLeftShift = 15;
auto minFrom = std::numeric_limits<uint16>::max();
const auto words = Ui::Text::Words(options.searchLowerText);
textToCache.entities.reserve(textToCache.entities.size()
+ words.size());
for (const auto &word : words) {
const auto selection = HistoryView::FindSearchQueryHighlight(
textToCache.text,
word);
if (!selection.empty()) {
minFrom = std::min(minFrom, selection.from);
textToCache.entities.push_back(EntityInText{
EntityType::Colorized,
selection.from,
selection.to - selection.from
});
}
}
if (minFrom == std::numeric_limits<uint16>::max()
&& !item->replyTo().quote.empty()) {
auto textQuote = TextWithEntities();
for (const auto &word : words) {
const auto selection = HistoryView::FindSearchQueryHighlight(
item->replyTo().quote.text,
word);
if (!selection.empty()) {
minFrom = 0;
if (textQuote.empty()) {
textQuote = item->replyTo().quote;
}
textQuote.entities.push_back(EntityInText{
EntityType::Colorized,
selection.from,
selection.to - selection.from
});
}
}
if (!textQuote.empty()) {
auto helper = Ui::Text::CustomEmojiHelper(context);
const auto factory = Ui::Text::PaletteDependentEmoji{
.factory = [=] {
const auto &icon = st::dialogsMiniQuoteIcon;
auto image = QImage(
icon.size() * style::DevicePixelRatio(),
QImage::Format_ARGB32_Premultiplied);
image.setDevicePixelRatio(style::DevicePixelRatio());
image.fill(Qt::transparent);
{
auto p = Painter(&image);
icon.paintInCenter(
p,
Rect(icon.size()),
st::dialogsTextFg->c);
}
return image;
},
.margin = QMargins(
st::lineWidth * 2,
0,
st::lineWidth * 2,
0),
};
textToCache = textQuote
.append(helper.paletteDependent(factory))
.append(std::move(textToCache));
context = helper.context(customEmojiRepaint);
}
}
if (!words.empty() && minFrom != std::numeric_limits<uint16>::max()) {
std::sort(
textToCache.entities.begin(),
textToCache.entities.end(),
[](const auto &a, const auto &b) {
return a.offset() < b.offset();
});
const auto textSize = textToCache.text.size();
minFrom = (minFrom > textSize || minFrom < kLeftShift)
? 0
: minFrom - kLeftShift;
textToCache = TextWithEntities(
minFrom > 0 ? kQEllipsis : QString())
.append(Text::Mid(std::move(textToCache), minFrom));
}
}
_hasPlainLinkAtBegin = !textToCache.entities.empty()
&& (textToCache.entities.front().type() == EntityType::Colorized);
_textCache.setMarkedText(
st::dialogsTextStyle,
std::move(textToCache),
DialogTextOptions(),
context);
std::move(context));
_textCachedFor = item;
_imagesCache = std::move(preview.images);
if (!ranges::any_of(_imagesCache, &ItemPreviewImage::hasSpoiler)) {

View File

@@ -1886,6 +1886,7 @@ void Suggestions::setupPostsSearch() {
nullptr,
{ .posts = true, .start = true },
state.totalCount);
_postsScroll->scrollToY(0);
updatePostsSearchVisibleRange();
}
}, _postsWrap->lifetime());

View File

@@ -186,7 +186,6 @@ void PostsSearchIntro::setup() {
_content.get(),
rpl::single(QString()),
st::postsSearchIntroButton),
{},
style::al_top);
_button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
_footer = _content->add(

View File

@@ -3464,6 +3464,26 @@ bool History::suggestDraftAllowed() const {
return peer->isMonoforum() && !peer->amMonoforumAdmin();
}
bool History::hasForumThreadBars() const {
if (amMonoforumAdmin()) {
return true;
} else if (const auto channel = peer->asChannel()) {
return channel->forum() && channel->useSubsectionTabs();
}
return false;
}
void History::forumTabsChanged(bool forumTabs) {
for (auto &block : blocks) {
for (auto &view : block->messages) {
view->setPendingResize();
if (forumTabs || view->Has<HistoryView::ForumThreadBar>()) {
view->previousInBlocksChanged();
}
}
}
}
not_null<History*> History::migrateToOrMe() const {
if (const auto to = peer->migrateTo()) {
return owner().history(to);

View File

@@ -80,6 +80,8 @@ public:
void monoforumChanged(Data::SavedMessages *old);
[[nodiscard]] bool amMonoforumAdmin() const;
[[nodiscard]] bool suggestDraftAllowed() const;
[[nodiscard]] bool hasForumThreadBars() const;
void forumTabsChanged(bool forumTabs);
[[nodiscard]] not_null<History*> migrateToOrMe() const;
[[nodiscard]] History *migrateFrom() const;

View File

@@ -73,6 +73,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session_settings.h"
#include "mainwidget.h"
#include "menu/menu_item_download_files.h"
#include "menu/menu_item_rate_transcribe.h"
#include "menu/menu_item_rate_transcribe_session.h"
#include "menu/menu_sponsored.h"
#include "core/application.h"
#include "apiwrap.h"
@@ -926,8 +928,8 @@ void HistoryInner::enumerateDates(Method method) {
}
template <typename Method>
void HistoryInner::enumerateMonoforumSenders(Method method) {
if (!_history->amMonoforumAdmin()) {
void HistoryInner::enumerateForumThreadBars(Method method) {
if (!_history->hasForumThreadBars()) {
return;
}
@@ -944,28 +946,28 @@ void HistoryInner::enumerateMonoforumSenders(Method method) {
// -1 means we didn't find a same-day with previous message yet.
auto lowestInOneBunchItemBottom = -1;
auto senderCallback = [&](not_null<Element*> view, int itemtop, int itembottom) {
auto barCallback = [&](not_null<Element*> view, int itemtop, int itembottom) {
const auto item = view->data();
if (lowestInOneBunchItemBottom < 0 && view->isInOneBunchWithPrevious()) {
lowestInOneBunchItemBottom = itembottom - view->marginBottom();
}
// Call method on a sender for all messages that have it and for those who are not showing it
// Call method on a bar for all messages that have it and for those who are not showing it
// because they are in a one day together with the previous message if they are top-most visible.
if (view->displayMonoforumSender() || (!item->isEmpty() && itemtop <= _visibleAreaTop)) {
if (view->displayForumThreadBar() || (!item->isEmpty() && itemtop <= _visibleAreaTop)) {
if (lowestInOneBunchItemBottom < 0) {
lowestInOneBunchItemBottom = itembottom - view->marginBottom();
}
// Attach sender to the top of the visible area with the same margin as it has in service message.
int senderTop = qMax(itemtop + view->displayedDateHeight(), _visibleAreaTop + skip) + st::msgServiceMargin.top();
// Attach bar to the top of the visible area with the same margin as it has in service message.
int barTop = qMax(itemtop + view->displayedDateHeight(), _visibleAreaTop + skip) + st::msgServiceMargin.top();
// Do not let the sender go below the single-sender messages pack bottom line.
int senderHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top();
senderTop = qMin(senderTop, lowestInOneBunchItemBottom - senderHeight);
// Do not let the bar go below the single-bar messages pack bottom line.
int barHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top();
barTop = qMin(barTop, lowestInOneBunchItemBottom - barHeight);
// Call the template callback function that was passed
// and return if it finished everything it needed.
if (!method(view, itemtop, senderTop)) {
if (!method(view, itemtop, barTop)) {
return false;
}
}
@@ -978,7 +980,7 @@ void HistoryInner::enumerateMonoforumSenders(Method method) {
return true;
};
enumerateItems<EnumItemsDirection::BottomToTop>(senderCallback);
enumerateItems<EnumItemsDirection::BottomToTop>(barCallback);
}
TextSelection HistoryInner::computeRenderSelection(
@@ -1393,31 +1395,31 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
});
p.setOpacity(1.);
enumerateMonoforumSenders([&](not_null<Element*> view, int itemtop, int senderTop) {
// stop the enumeration if the sender is above the painted rect
if (senderTop + dateHeight <= clip.top()) {
enumerateForumThreadBars([&](not_null<Element*> view, int itemtop, int barTop) {
// stop the enumeration if the bar is above the painted rect
if (barTop + dateHeight <= clip.top()) {
return false;
}
const auto displaySender = view->displayMonoforumSender();
auto senderInPlace = displaySender;
if (senderInPlace) {
const auto correctSenderTop = itemtop + view->displayedDateHeight() + st::msgServiceMargin.top();
senderInPlace = (senderTop < correctSenderTop + st::msgServiceMargin.top());
const auto displayBar = view->displayForumThreadBar();
auto barInPlace = displayBar;
if (barInPlace) {
const auto correctBarTop = itemtop + view->displayedDateHeight() + st::msgServiceMargin.top();
barInPlace = (barTop < correctBarTop + st::msgServiceMargin.top());
}
// paint the sender if it intersects the painted rect
if (senderTop < clip.top() + clip.height()) {
const auto senderY = senderTop - st::msgServiceMargin.top();
if (const auto sender = view->Get<HistoryView::MonoforumSenderBar>()) {
sender->paint(p, context.st, senderY, _contentWidth, _isChatWide, !senderInPlace);
// paint the bar if it intersects the painted rect
if (barTop < clip.top() + clip.height()) {
const auto barY = barTop - st::msgServiceMargin.top();
if (const auto bar = view->Get<HistoryView::ForumThreadBar>()) {
bar->paint(p, context.st, barY, _contentWidth, _isChatWide, !barInPlace);
} else {
HistoryView::MonoforumSenderBar::PaintFor(
_forumThreadBarWidth = HistoryView::ForumThreadBar::PaintForGetWidth(
p,
context.st,
view,
_monoforumSenderUserpicView,
senderY,
_forumThreadBarUserpicView,
barY,
_contentWidth,
_isChatWide);
}
@@ -2500,6 +2502,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
}, &st::menuIconStickers);
}
};
auto rateTranscriptionItem = (HistoryItem*)(nullptr);
const auto addDocumentActions = [&](not_null<DocumentData*> document, HistoryItem *item) {
if (document->loading()) {
_menu->addAction(tr::lng_context_cancel_download(tr::now), [=] {
@@ -2559,6 +2562,11 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
document);
}, &st::menuIconStickers);
}
if (item
&& (lnkIsVoice || document->isVideoMessage())
&& Menu::HasRateTranscribeItem(item)) {
rateTranscriptionItem = item;
}
};
#ifdef _DEBUG // Sometimes we need to save emoji to files.
@@ -2972,6 +2980,12 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
&st::menuIconGiftPremium);
}
}
} else if (!rateTranscriptionItem && media->document()) {
if ((media->document()->isVoiceMessage()
|| media->document()->isVideoMessage())
&& Menu::HasRateTranscribeItem(item)) {
rateTranscriptionItem = item;
}
}
}
if (!item->isService() && view && actionText.isEmpty()) {
@@ -3139,6 +3153,13 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
_controller);
}
if (!_menu->empty() && rateTranscriptionItem) {
_menu->insertAction(0, base::make_unique_q<Menu::RateTranscribe>(
_menu,
_menu->st().menu,
Menu::RateTranscribeCallbackFactory(rateTranscriptionItem)));
}
if (_menu->empty()) {
_menu = nullptr;
return;
@@ -3677,7 +3698,7 @@ void HistoryInner::toggleScrollDateShown() {
void HistoryInner::repaintScrollDateCallback() {
int updateTop = _visibleAreaTop;
int updateHeight = st::msgServiceMargin.top() + st::msgServicePadding.top() + st::msgServiceFont->height + st::msgServicePadding.bottom();
if (_history->amMonoforumAdmin()) {
if (_history->hasForumThreadBars()) {
updateHeight *= 2;
}
update(0, updateTop, width(), updateHeight);
@@ -4240,6 +4261,54 @@ void HistoryInner::mouseActionUpdate() {
}
return true;
});
if (!dragState.link) {
enumerateForumThreadBars([&](not_null<Element*> view, int itemtop, int barTop) {
// stop the enumeration if the bar is above our point
if (barTop + dateHeight <= point.y()) {
return false;
}
const auto displayBar = view->displayForumThreadBar();
auto barInPlace = displayBar;
if (barInPlace) {
const auto correctBarTop = itemtop + view->displayedDateHeight() + st::msgServiceMargin.top();
barInPlace = (barTop < correctBarTop + st::msgServiceMargin.top());
}
// stop enumeration if we've found a bar under the cursor
if (barTop <= point.y()) {
const auto item = view->data();
auto barWidth = 0;
if (const auto bar = view->Get<HistoryView::ForumThreadBar>()) {
barWidth = bar->width;
} else {
barWidth = _forumThreadBarWidth;
}
auto barLeft = st::msgServiceMargin.left();
auto maxwidth = _contentWidth;
if (_isChatWide) {
maxwidth = qMin(maxwidth, int32(st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left()));
}
auto widthForBar = maxwidth - st::msgServiceMargin.left() - st::msgServiceMargin.left();
barLeft += (widthForBar - barWidth) / 2;
if (point.x() >= barLeft && point.x() < barLeft + barWidth) {
if (!_forumThreadBarLink) {
_forumThreadBarLink = std::make_shared<Window::ForumThreadClickHandler>(item);
} else {
static_cast<Window::ForumThreadClickHandler*>(_forumThreadBarLink.get())->update(item);
}
dragState = TextState(
nullptr,
_forumThreadBarLink);
_dragStateItem = session().data().message(dragState.itemId);
lnkhost = view;
}
}
return true;
});
}
if (!dragState.link) {
StateRequest request;
if (_mouseAction == MouseAction::Selecting) {

View File

@@ -315,13 +315,13 @@ private:
template <typename Method>
void enumerateDates(Method method);
// This function finds all monoforum sender elements that are displayed and calls template method
// This function finds all forum thread bar elements that are displayed and calls template method
// for each found date element (from the bottom to the top) using enumerateItems() method.
//
// Method has "bool (*Method)(not_null<Element*> view, int itemtop, int dateTop)" signature
// if it returns false the enumeration stops immediately.
template <typename Method>
void enumerateMonoforumSenders(Method method);
void enumerateForumThreadBars(Method method);
void scrollDateCheck();
void scrollDateHideByTimer();
@@ -470,7 +470,8 @@ private:
int _contentWidth = 0;
int _historyPaddingTop = 0;
int _revealHeight = 0;
Ui::PeerUserpicView _monoforumSenderUserpicView;
int _forumThreadBarWidth = 0;
Ui::PeerUserpicView _forumThreadBarUserpicView;
// Save visible area coords for painting / pressing userpics.
int _visibleAreaTop = 0;
@@ -572,6 +573,7 @@ private:
Element *_scrollDateLastItem = nullptr;
int _scrollDateLastItemTop = 0;
ClickHandlerPtr _scrollDateLink;
ClickHandlerPtr _forumThreadBarLink;
};

View File

@@ -115,6 +115,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_pinned_section.h"
#include "history/view/history_view_pinned_bar.h"
#include "history/view/history_view_group_call_bar.h"
#include "history/view/history_view_group_members_widget.h"
#include "history/view/history_view_item_preview.h"
#include "history/view/history_view_reply.h"
#include "history/view/history_view_requests_bar.h"
@@ -122,7 +123,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_subsection_tabs.h"
#include "history/view/history_view_translate_bar.h"
#include "history/view/media/history_view_media.h"
#include "profile/profile_block_group_members.h"
#include "core/click_handler_types.h"
#include "chat_helpers/field_autocomplete.h"
#include "chat_helpers/tabbed_panel.h"
@@ -2105,6 +2105,18 @@ void HistoryWidget::setupShortcuts() {
std::make_shared<Scheduled>(_history));
return true;
});
if (showRecordButton()) {
const auto isVoice = request->check(Command::RecordVoice, 1);
const auto isRound = !isVoice
&& request->check(Command::RecordRound, 1);
(isVoice || isRound) && request->handle([=] {
if (_voiceRecordBar) {
_voiceRecordBar->startRecordingAndLock(isRound);
return true;
}
return false;
});
}
if (session().supportMode()) {
request->check(
Command::SupportToggleMuted
@@ -5806,11 +5818,7 @@ void HistoryWidget::showMembersDropdown() {
if (!_membersDropdown) {
_membersDropdown.create(this, st::membersInnerDropdown);
_membersDropdown->setOwnedWidget(
object_ptr<Profile::GroupMembersWidget>(
this,
controller(),
_peer,
st::membersInnerItem));
object_ptr<HistoryView::GroupMembersWidget>(this, controller(), _peer));
_membersDropdown->resizeToWidth(st::membersInnerWidth);
_membersDropdown->setMaxHeight(countMembersDropdownHeightMax());

View File

@@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/field_autocomplete.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "core/shortcuts.h"
#include "core/ui_integration.h"
#include "data/notify/data_notify_settings.h"
#include "data/data_changes.h"
@@ -1180,6 +1181,17 @@ auto ComposeControls::sendContentRequests(SendRequestType requestType) const {
_sendCustomRequests.events());
}
rpl::producer<> ComposeControls::scrollToMaxRequests() const {
return _field->submits() | rpl::filter([=]{
if (_mode == Mode::Normal
&& !_voiceRecordBar->isListenState()
&& getTextWithAppliedMarkdown().text.isEmpty()) {
return true;
}
return false;
}) | rpl::to_empty;
}
rpl::producer<Api::SendOptions> ComposeControls::sendRequests() const {
return sendContentRequests(SendRequestType::Text);
}
@@ -2593,6 +2605,23 @@ void ComposeControls::initVoiceRecordBar() {
) | rpl::start_with_next([=] {
updateSendButtonType();
}, _wrap->lifetime());
Shortcuts::Requests(
) | rpl::filter([=] {
return Ui::AppInFocus();
}) | rpl::start_with_next([=](not_null<Shortcuts::Request*> request) {
using Command = Shortcuts::Command;
const auto isVoice = request->check(Command::RecordVoice, 1);
const auto isRound = !isVoice
&& request->check(Command::RecordRound, 1);
(isVoice || isRound) && request->handle([=] {
if (_voiceRecordBar) {
_voiceRecordBar->startRecordingAndLock(isRound);
return true;
}
return false;
});
}, _voiceRecordBar->lifetime());
}
void ComposeControls::updateWrappingVisibility() {

View File

@@ -183,6 +183,7 @@ public:
-> rpl::producer<ReplyNextRequest>;
[[nodiscard]] rpl::producer<> focusRequests() const;
[[nodiscard]] rpl::producer<> showScheduledRequests() const;
[[nodiscard]] rpl::producer<> scrollToMaxRequests() const;
using MimeDataHook = Fn<bool(
not_null<const QMimeData*> data,

View File

@@ -54,7 +54,9 @@ using SearchRequest = Api::MessagesSearchMerged::Request;
class Row final : public PeerListRow {
public:
explicit Row(std::unique_ptr<Dialogs::FakeRow> fakeRow);
explicit Row(
std::unique_ptr<Dialogs::FakeRow> fakeRow,
not_null<QString*> query);
[[nodiscard]] FullMsgId fullId() const;
@@ -73,15 +75,17 @@ public:
private:
const std::unique_ptr<Dialogs::FakeRow> _fakeRow;
not_null<QString*> _query;
int _outerWidth = 0;
};
Row::Row(std::unique_ptr<Dialogs::FakeRow> fakeRow)
Row::Row(std::unique_ptr<Dialogs::FakeRow> fakeRow, not_null<QString*> query)
: PeerListRow(
fakeRow->searchInChat().history()->peer,
fakeRow->item()->fullId().msg.bare)
, _fakeRow(std::move(fakeRow)) {
, _fakeRow(std::move(fakeRow))
, _query(query) {
}
FullMsgId Row::fullId() const {
@@ -116,9 +120,11 @@ void Row::elementsPaint(
.st = &st::defaultDialogRow,
.currentBg = st::dialogsBg,
.now = crl::now(),
.searchLowerText = QStringView(*_query),
.width = outerWidth,
.selected = selected,
.paused = p.inactive(),
.search = true,
});
}
@@ -134,6 +140,7 @@ public:
void loadMoreRows() override;
void addItems(const MessageIdsList &ids, bool clear);
void setQuery(const QString &query);
[[nodiscard]] rpl::producer<FullMsgId> showItemRequests() const;
[[nodiscard]] rpl::producer<> searchMoreRequests() const;
@@ -145,6 +152,8 @@ private:
rpl::event_stream<> _searchMoreRequests;
rpl::event_stream<> _resetScrollRequests;
QString _query;
};
ListController::ListController(not_null<History*> history)
@@ -201,7 +210,8 @@ void ListController::addItems(const MessageIdsList &ids, bool clear) {
std::make_unique<Dialogs::FakeRow>(
key,
item,
[=] { delegate()->peerListUpdateRow(*shared); }));
[=] { delegate()->peerListUpdateRow(*shared); }),
&_query);
*shared = row.get();
delegate()->peerListAppendRow(std::move(row));
}
@@ -214,6 +224,10 @@ void ListController::addItems(const MessageIdsList &ids, bool clear) {
}
}
void ListController::setQuery(const QString &query) {
_query = query;
}
struct List {
base::unique_qptr<Ui::RpWidget> container;
std::unique_ptr<ListController> controller;
@@ -913,6 +927,9 @@ ComposeSearch::Inner::Inner(
search.topMsgId = _topMsgId;
_apiSearch.clear();
_apiSearch.search(search);
_list.controller->addItems({}, true);
_list.controller->setQuery(_apiSearch.request().query);
}, _topBar->lifetime());
_topBar->queryChanges(

View File

@@ -1075,7 +1075,7 @@ void DraftOptionsBox(
state->wrap = box->addRow(
object_ptr<PreviewWrap>(box, args.history),
{});
style::margins());
state->wrap->draggingScrollDelta(
) | rpl::start_with_next([=](int delta) {
box->scrollByDraggingDelta(delta);

View File

@@ -394,7 +394,6 @@ void ChooseSuggestPriceBox(
box,
std::move(title),
st::settingsPremiumUserTitle),
st::boxRowPadding,
style::al_top);
}

View File

@@ -40,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/text_utilities.h"
#include "ui/dynamic_image.h"
#include "ui/painter.h"
#include "ui/widgets/fields/input_field.h" // ShouldSubmit.
#include "ui/widgets/tooltip.h"
#include "ui/rect.h"
#include "ui/ui_utility.h"
@@ -130,10 +131,6 @@ void SoundedPreview::subscribeToUpdates(Fn<void()> callback) {
_repaint = std::move(callback);
}
[[nodiscard]] auto InactiveColor(const QColor &c) {
return QColor(c.red(), c.green(), c.blue(), kInactiveWaveformBarAlpha);
}
[[nodiscard]] auto Progress(int low, int high) {
return std::clamp(float64(low) / high, 0., 1.);
}
@@ -442,6 +439,7 @@ TTLButton::TTLButton(
r.top()
+ r.height()
- size.height()
- st::historyRecordTooltipSkip
+ st::historyRecordTooltip.padding.top());
});
}, _tooltip->lifetime());
@@ -606,7 +604,8 @@ ListenWrap::ListenWrap(
, _playPauseSt(st::mediaPlayerButton)
, _playPauseButton(base::make_unique_q<Ui::AbstractButton>(parent))
, _activeWaveformBar(st::historyRecordVoiceFgActiveIcon->c)
, _inactiveWaveformBar(InactiveColor(_activeWaveformBar))
, _inactiveWaveformBar(
anim::with_alpha(_activeWaveformBar, kInactiveWaveformBarAlpha))
, _playPause(_playPauseSt, [=] { _playPauseButton->update(); }) {
init();
}
@@ -1655,6 +1654,7 @@ void VoiceRecordBar::init() {
auto callback = [=](float64 value) {
_lock->requestPaintLockToStopProgress(value);
_level->requestPaintColor(activeAnimationRatio());
update();
updateTTLGeometry(TTLAnimationType::RightLeft, value);
};
@@ -1678,11 +1678,7 @@ void VoiceRecordBar::init() {
if (_startRecordingFilter && _startRecordingFilter()) {
return;
}
_recordingTipRequire = crl::now();
_recordingVideo = (_send->type() == Ui::SendButton::Type::Round);
_fullRecord = false;
_ttlButton = nullptr;
_lock->setRecordingVideo(_recordingVideo);
prepareOnSendPress();
_startTimer.callOnce(st::universalDuration);
} else if (e->type() == QEvent::MouseButtonRelease) {
checkTipRequired();
@@ -1712,6 +1708,14 @@ void VoiceRecordBar::init() {
initLevelGeometry();
}
void VoiceRecordBar::prepareOnSendPress() {
_recordingTipRequire = crl::now();
_recordingVideo = (_send->type() == Ui::SendButton::Type::Round);
_fullRecord = false;
_ttlButton = nullptr;
_lock->setRecordingVideo(_recordingVideo);
}
void VoiceRecordBar::activeAnimate(bool active) {
const auto to = active ? 1. : 0.;
if (_activeAnimation.animating()) {
@@ -1802,6 +1806,24 @@ void VoiceRecordBar::initLevelGeometry() {
}, lifetime());
}
void VoiceRecordBar::startRecordingAndLock(bool round) {
{
auto sendState = _send->state();
sendState.type = round
? Ui::SendButton::Type::Round
: Ui::SendButton::Type::Record;
_send->setState(std::move(sendState));
}
if (_startRecordingFilter && _startRecordingFilter()) {
return;
}
prepareOnSendPress();
_lock->show();
_lock->requestPaintProgress(1.);
startRecording();
}
void VoiceRecordBar::startRecording() {
if (isRecording()) {
return;
@@ -1942,6 +1964,40 @@ void VoiceRecordBar::startRecording() {
stop(_inField.current());
}
}, _recordingLifetime);
_listenChanges.events_starting_with(
rpl::empty_value()
) | rpl::filter([=] {
return _listen == nullptr;
}) | rpl::start_with_next([=] {
auto keyFilterCallback = [=](not_null<QEvent*> e) {
using Result = base::EventFilterResult;
if (_send->type() != Ui::SendButton::Type::Record
&& _send->type() != Ui::SendButton::Type::Round) {
return Result::Continue;
}
switch(e->type()) {
case QEvent::KeyPress: {
if (!_warningShown
&& isRecordingLocked()
&& Ui::ShouldSubmit(
static_cast<QKeyEvent*>(e.get()),
Core::App().settings().sendSubmitWay())) {
stop(true);
return Result::Cancel;
}
return Result::Continue;
}
default: return Result::Continue;
}
};
_keyFilterInRecordingState = base::unique_qptr{
base::install_event_filter(
QCoreApplication::instance(),
std::move(keyFilterCallback)).get()
};
}, lifetime());
}
void VoiceRecordBar::checkTipRequired() {
@@ -2015,6 +2071,7 @@ void VoiceRecordBar::hideFast() {
_lock->hide();
_level->hide();
[[maybe_unused]] const auto s = takeTTLState();
_keyFilterInRecordingState = nullptr;
}
void VoiceRecordBar::stopRecording(StopType type, bool ttlBeforeHide) {
@@ -2145,8 +2202,8 @@ void VoiceRecordBar::startRedCircleAnimation() {
if (anim::Disabled()) {
return;
}
const auto animation = _recordingLifetime
.make_state<Ui::Animations::Basic>();
const auto animation
= _recordingLifetime.make_state<Ui::Animations::Basic>();
animation->init([=](crl::time now) {
const auto diffTime = now - animation->started();
_redCircleProgress = std::abs(std::sin(diffTime / 400.));
@@ -2298,6 +2355,9 @@ bool VoiceRecordBar::hasDuration() const {
}
float64 VoiceRecordBar::activeAnimationRatio() const {
if (isRecordingLocked()) {
return 1.;
}
return _activeAnimation.value(_inField.current() ? 1. : 0.);
}
@@ -2352,6 +2412,7 @@ void VoiceRecordBar::orderControls() {
}
void VoiceRecordBar::installListenStateFilter() {
_keyFilterInRecordingState = nullptr;
auto keyFilterCallback = [=](not_null<QEvent*> e) {
using Result = base::EventFilterResult;
if (!(_send->type() == Ui::SendButton::Type::Send

View File

@@ -78,7 +78,8 @@ public:
Fn<void()> &&callback,
anim::type animated = anim::type::instant);
void startRecording();
void startRecordingAndLock(bool round);
void finishAnimating();
void hideAnimated();
void hideFast();
@@ -145,6 +146,9 @@ private:
void startRedCircleAnimation();
void installListenStateFilter();
void startRecording();
void prepareOnSendPress();
[[nodiscard]] bool isTypeRecord() const;
[[nodiscard]] bool hasDuration() const;
@@ -194,6 +198,8 @@ private:
FilterCallback _startRecordingFilter;
FilterCallback _hasTTLFilter;
base::unique_qptr<QObject> _keyFilterInRecordingState;
bool _warningShown = false;
bool _pauseInsteadSend = false;

View File

@@ -810,6 +810,11 @@ void ChatWidget::setupComposeControls() {
send(options);
}, lifetime());
_composeControls->scrollToMaxRequests(
) | rpl::start_with_next([=] {
listScrollTo(_scroll->scrollTopMax());
}, lifetime());
_composeControls->sendVoiceRequests(
) | rpl::start_with_next([=](const ComposeControls::VoiceToSend &data) {
sendVoice(data);

View File

@@ -44,6 +44,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/boxes/report_box_graphics.h"
#include "ui/ui_utility.h"
#include "menu/menu_item_download_files.h"
#include "menu/menu_item_rate_transcribe.h"
#include "menu/menu_item_rate_transcribe_session.h"
#include "menu/menu_send.h"
#include "ui/boxes/confirm_box.h"
#include "ui/boxes/show_or_premium_box.h"
@@ -338,6 +340,16 @@ void AddDocumentActions(
const auto controller = list->controller();
AddSaveSoundForNotifications(menu, item, document, controller);
}
if ((document->isVoiceMessage()
|| document->isVideoMessage())
&& Menu::HasRateTranscribeItem(item)) {
if (!menu->empty()) {
menu->insertAction(0, base::make_unique_q<Menu::RateTranscribe>(
menu,
menu->st().menu,
Menu::RateTranscribeCallbackFactory(item)));
}
}
AddSaveDocumentAction(menu, item, document, list);
AddCopyFilename(
menu,

View File

@@ -101,59 +101,6 @@ Element *MousedElement/* = nullptr*/;
return session->tryResolveWindow();
}
[[nodiscard]] TextSelection FindSearchQueryHighlight(
const QString &text,
const QString &query) {
const auto lower = query.toLower();
const auto inside = text.toLower();
const auto find = [&](QStringView part) {
auto skip = 0;
if (const auto from = inside.indexOf(part, skip); from >= 0) {
if (!from || !inside[from - 1].isLetterOrNumber()) {
return int(from);
}
skip = from + 1;
}
return -1;
};
if (const auto from = find(lower); from >= 0) {
const auto till = from + query.size();
if (till >= inside.size() || !inside[till].isLetterOrNumber()) {
return { uint16(from), uint16(till) };
}
}
const auto tillEndOfWord = [&](int from) {
for (auto till = from + 1; till != inside.size(); ++till) {
if (!inside[till].isLetterOrNumber()) {
return TextSelection{ uint16(from), uint16(till) };
}
}
return TextSelection{ uint16(from), uint16(inside.size()) };
};
const auto words = QStringView(lower).split(
QRegularExpression(
u"[\\W]"_q,
QRegularExpression::UseUnicodePropertiesOption),
Qt::SkipEmptyParts);
for (const auto &word : words) {
const auto length = int(word.size());
const auto cut = length / 2;
const auto part = word.mid(0, length - cut);
const auto offset = find(part);
if (offset < 0) {
continue;
}
for (auto i = 0; i != cut; ++i) {
const auto part = word.mid(0, length - i);
if (const auto from = find(part); from >= 0) {
return tillEndOfWord(from);
}
}
return tillEndOfWord(offset);
}
return {};
}
[[nodiscard]] TextSelection ApplyModificationsFrom(
TextSelection result,
const Ui::Text::String &text) {
@@ -480,20 +427,37 @@ void DateBadge::paint(
ServiceMessagePainter::PaintDate(p, st, text, width, y, w, chatWide);
}
void MonoforumSenderBar::init(
void ForumThreadBar::init(
not_null<PeerData*> parentChat,
not_null<PeerData*> peer) {
sender = peer;
text.setText(st::semiboldTextStyle, peer->name());
not_null<Data::Thread*> thread) {
this->thread = thread;
const auto sublist = thread->asSublist();
if (sublist) {
text.setText(st::semiboldTextStyle, sublist->sublistPeer()->name());
} else if (const auto topic = thread->asTopic()) {
text.setMarkedText(
st::semiboldTextStyle,
topic->titleWithIconOrLogo(),
kMarkupTextOptions,
Core::TextContext({ .session = &topic->session() }));
}
const auto skip = st::monoforumBarUserpicSkip;
const auto userpic = st::msgServicePadding.top()
+ st::msgServiceFont->height
+ st::msgServicePadding.bottom()
- 2 * skip;
width = skip + userpic + skip * 2 + text.maxWidth() + st::msgServicePadding.right();
const auto userpic = sublist
? (st::msgServicePadding.top()
+ st::msgServiceFont->height
+ st::msgServicePadding.bottom()
- 2 * skip)
: (st::msgServicePadding.left() - 3 * skip);
width = skip
+ userpic
+ skip * 2
+ text.maxWidth()
+ st::topicButtonArrowSkip
+ st::msgServicePadding.right();
}
int MonoforumSenderBar::height() const {
int ForumThreadBar::height() const {
return st::msgServiceMargin.top()
+ st::msgServicePadding.top()
+ st::msgServiceFont->height
@@ -501,17 +465,29 @@ int MonoforumSenderBar::height() const {
+ st::msgServiceMargin.bottom();
}
void MonoforumSenderBar::paint(
void ForumThreadBar::paint(
Painter &p,
not_null<const Ui::ChatStyle*> st,
int y,
int w,
bool chatWide,
bool skipPatternLine) const {
Paint(p, st, sender, text, width, view, y, w, chatWide, skipPatternLine);
if (const auto strong = thread.get()) {
Paint(
p,
st,
strong,
text,
width,
view,
y,
w,
chatWide,
skipPatternLine);
}
}
void MonoforumSenderBar::PaintFor(
int ForumThreadBar::PaintForGetWidth(
Painter &p,
not_null<const Ui::ChatStyle*> st,
not_null<Element*> itemView,
@@ -519,31 +495,49 @@ void MonoforumSenderBar::PaintFor(
int y,
int w,
bool chatWide) {
const auto sublist = itemView->data()->savedSublist();
const auto sender = (sublist && sublist->parentChat())
? sublist->sublistPeer().get()
const auto item = itemView->data();
const auto topic = item->topic();
const auto sublist = item->savedSublist();
const auto sender = topic
? (Data::Thread*)topic
: (sublist && sublist->parentChat())
? (Data::Thread*)sublist
: nullptr;
if (!sender || sender->isMonoforum()) {
return;
auto text = Ui::Text::String();
if (!sender
|| !topic
|| (sublist && sublist->sublistPeer()->isMonoforum())) {
return 0;
} else if (topic) {
text.setMarkedText(
st::semiboldTextStyle,
topic->titleWithIconOrLogo(),
kMarkupTextOptions,
Core::TextContext({ .session = &topic->session() }));
} else {
text.setText(st::semiboldTextStyle, sublist->sublistPeer()->name());
}
auto text = Ui::Text::String(st::semiboldTextStyle, sender->name());
const auto skip = st::monoforumBarUserpicSkip;
const auto userpic = st::msgServicePadding.top()
+ st::msgServiceFont->height
+ st::msgServicePadding.bottom()
- 2 * skip;
const auto userpic = sublist
? (st::msgServicePadding.top()
+ st::msgServiceFont->height
+ st::msgServicePadding.bottom()
- 2 * skip)
: (st::msgServicePadding.left() - 3 * skip);
const auto width = skip
+ userpic
+ skip * 2
+ text.maxWidth()
+ st::topicButtonArrowSkip
+ st::msgServicePadding.right();
Paint(p, st, sender, text, width, userpicView, y, w, chatWide, true);
return width;
}
void MonoforumSenderBar::Paint(
void ForumThreadBar::Paint(
Painter &p,
not_null<const Ui::ChatStyle*> st,
not_null<PeerData*> sender,
not_null<Data::Thread*> thread,
const Ui::Text::String &text,
int width,
Ui::PeerUserpicView &view,
@@ -551,8 +545,6 @@ void MonoforumSenderBar::Paint(
int w,
bool chatWide,
bool skipPatternLine) {
Expects(sender != nullptr);
int left = st::msgServiceMargin.left();
const auto maxwidth = chatWide
? std::min(w, WideChatWidth())
@@ -581,24 +573,47 @@ void MonoforumSenderBar::Paint(
p.drawLine(left + use, top, 2 * w, top);
}
const auto userpic = st::msgServicePadding.top()
+ st::msgServiceFont->height
+ st::msgServicePadding.bottom()
- 2 * skip;
const auto available = use - (skip + userpic + skip * 2 + st::msgServicePadding.right());
const auto sublist = thread->asSublist();
const auto userpic = sublist
? (st::msgServicePadding.top()
+ st::msgServiceFont->height
+ st::msgServicePadding.bottom()
- 2 * skip)
: (st::msgServicePadding.left() - 3 * skip);
const auto available = use
- (skip
+ userpic
+ skip * 2
+ st::topicButtonArrowSkip
+ st::msgServicePadding.right());
sender->paintUserpic(p, view, left + skip, y + st::msgServiceMargin.top() + skip, userpic);
if (sublist) {
sublist->sublistPeer()->paintUserpic(
p,
view,
left + skip,
y + st::msgServiceMargin.top() + skip,
userpic);
}
const auto textLeft = left + skip + userpic + skip * 2;
const auto textTop = y
+ st::msgServiceMargin.top()
+ st::msgServicePadding.top();
p.setFont(st::msgServiceFont);
p.setPen(st->msgServiceFg());
text.draw(p, {
.position = {
left + skip + userpic + skip * 2,
y + st::msgServiceMargin.top() + st::msgServicePadding.top(),
},
.position = { textLeft, textTop },
.availableWidth = available,
.elisionLines = 1,
});
st::topicButtonArrow.paint(
p,
textLeft + available + st::topicButtonArrowPosition.x(),
textTop + st::topicButtonArrowPosition.y(),
w,
st->msgServiceFg()->c);
}
void ServicePreMessage::init(
@@ -1420,7 +1435,7 @@ void Element::validateTextSkipBlock(bool has, int width, int height) {
}
void Element::previousInBlocksChanged() {
recountMonoforumSenderBarInBlocks();
recountThreadBarInBlocks();
recountDisplayDateInBlocks();
recountAttachToPreviousInBlocks();
}
@@ -1457,7 +1472,7 @@ bool Element::computeIsAttachToPrevious(not_null<Element*> previous) {
if (!Has<DateBadge>()
&& !Has<UnreadBar>()
&& !Has<ServicePreMessage>()
&& !Has<MonoforumSenderBar>()) {
&& !Has<ForumThreadBar>()) {
const auto prev = previous->data();
const auto previousMarkup = prev->inlineReplyMarkup();
const auto possible = (std::abs(prev->date() - item->date())
@@ -1569,12 +1584,12 @@ bool Element::isInOneDayWithPrevious() const {
return !data()->isEmpty() && !displayDate();
}
bool Element::displayMonoforumSender() const {
return Has<MonoforumSenderBar>();
bool Element::displayForumThreadBar() const {
return Has<ForumThreadBar>();
}
bool Element::isInOneBunchWithPrevious() const {
return !data()->isEmpty() && !displayMonoforumSender();
return !data()->isEmpty() && !displayForumThreadBar();
}
void Element::recountAttachToPreviousInBlocks() {
@@ -1595,34 +1610,49 @@ void Element::recountAttachToPreviousInBlocks() {
setAttachToPrevious(attachToPrevious, previous);
}
void Element::recountMonoforumSenderBarInBlocks() {
void Element::recountThreadBarInBlocks() {
const auto item = data();
const auto topic = item->topic();
const auto sublist = item->savedSublist();
const auto parentChat = sublist ? sublist->parentChat() : nullptr;
const auto barPeer = [&]() -> PeerData* {
const auto parentChat = (topic && topic->channel()->useSubsectionTabs())
? topic->channel().get()
: sublist
? sublist->parentChat()
: nullptr;
const auto barThread = [&]() -> Data::Thread* {
if (!parentChat
|| isHidden()
|| item->isEmpty()
|| item->isSponsored()) {
return nullptr;
}
const auto sublistPeer = sublist->sublistPeer();
if (const auto previous = previousDisplayedInBlocks()) {
const auto prev = previous->data();
if (const auto prevSublist = prev->savedSublist()) {
if (const auto prevTopic = prev->topic()) {
Assert(prevTopic->channel() == parentChat);
const auto topicRootId = topic->rootId();
if (prevTopic->rootId() == topicRootId) {
return nullptr;
}
} else if (const auto prevSublist = prev->savedSublist()) {
Assert(prevSublist->parentChat() == parentChat);
const auto sublistPeer = sublist->sublistPeer();
if (prevSublist->sublistPeer() == sublistPeer) {
return nullptr;
}
}
}
return (sublistPeer == parentChat) ? nullptr : sublistPeer.get();
return topic
? (Data::Thread*)topic
: (sublist && sublist->sublistPeer() != parentChat)
? (Data::Thread*)sublist
: nullptr;
}();
if (barPeer && !Has<MonoforumSenderBar>()) {
AddComponents(MonoforumSenderBar::Bit());
Get<MonoforumSenderBar>()->init(parentChat, barPeer);
} else if (!barPeer && Has<MonoforumSenderBar>()) {
RemoveComponents(MonoforumSenderBar::Bit());
if (barThread && !Has<ForumThreadBar>()) {
AddComponents(ForumThreadBar::Bit());
Get<ForumThreadBar>()->init(parentChat, barThread);
} else if (!barThread && Has<ForumThreadBar>()) {
RemoveComponents(ForumThreadBar::Bit());
}
}
@@ -2528,4 +2558,60 @@ Window::SessionController *ExtractController(const ClickContext &context) {
return nullptr;
}
TextSelection FindSearchQueryHighlight(
const QString &text,
const QString &query) {
const auto lower = query.toLower();
return FindSearchQueryHighlight(text, QStringView(lower));
}
TextSelection FindSearchQueryHighlight(
const QString &text,
QStringView lower) {
const auto inside = text.toLower();
const auto find = [&](QStringView part) {
auto skip = 0;
if (const auto from = inside.indexOf(part, skip); from >= 0) {
if (!from || !inside[from - 1].isLetterOrNumber()) {
return int(from);
}
skip = from + 1;
}
return -1;
};
if (const auto from = find(lower); from >= 0) {
const auto till = from + lower.size();
if (till >= inside.size()
|| !(inside.begin() + till)->isLetterOrNumber()) {
return { uint16(from), uint16(till) };
}
}
const auto tillEndOfWord = [&](int from) {
for (auto till = from + 1; till != inside.size(); ++till) {
if (!inside[till].isLetterOrNumber()) {
return TextSelection{ uint16(from), uint16(till) };
}
}
return TextSelection{ uint16(from), uint16(inside.size()) };
};
const auto words = Ui::Text::Words(lower);
for (const auto &word : words) {
const auto length = int(word.size());
const auto cut = length / 2;
const auto part = word.mid(0, length - cut);
const auto offset = find(part);
if (offset < 0) {
continue;
}
for (auto i = 0; i != cut; ++i) {
const auto part = word.mid(0, length - i);
if (const auto from = find(part); from >= 0) {
return tillEndOfWord(from);
}
}
return tillEndOfWord(offset);
}
return {};
}
} // namespace HistoryView

View File

@@ -20,6 +20,7 @@ struct HistoryMessageReply;
struct PreparedServiceText;
namespace Data {
class Thread;
struct Reaction;
struct ReactionId;
} // namespace Data
@@ -265,8 +266,10 @@ struct DateBadge : RuntimeComponent<DateBadge, Element> {
};
struct MonoforumSenderBar : RuntimeComponent<MonoforumSenderBar, Element> {
void init(not_null<PeerData*> parentChat, not_null<PeerData*> peer);
struct ForumThreadBar : RuntimeComponent<ForumThreadBar, Element> {
void init(
not_null<PeerData*> parentChat,
not_null<Data::Thread*> thread);
int height() const;
void paint(
@@ -276,7 +279,7 @@ struct MonoforumSenderBar : RuntimeComponent<MonoforumSenderBar, Element> {
int w,
bool chatWide,
bool skipPatternLine) const;
static void PaintFor(
static int PaintForGetWidth(
Painter &p,
not_null<const Ui::ChatStyle*> st,
not_null<Element*> itemView,
@@ -285,9 +288,8 @@ struct MonoforumSenderBar : RuntimeComponent<MonoforumSenderBar, Element> {
int w,
bool chatWide);
PeerData *sender = nullptr;
base::weak_ptr<Data::Thread> thread;
Ui::Text::String text;
ClickHandlerPtr link;
mutable Ui::PeerUserpicView view;
int width = 0;
@@ -295,7 +297,7 @@ private:
static void Paint(
Painter &p,
not_null<const Ui::ChatStyle*> st,
not_null<PeerData*> sender,
not_null<Data::Thread*> thread,
const Ui::Text::String &text,
int width,
Ui::PeerUserpicView &view,
@@ -475,7 +477,7 @@ public:
[[nodiscard]] bool displayDate() const;
[[nodiscard]] bool isInOneDayWithPrevious() const;
[[nodiscard]] bool displayMonoforumSender() const;
[[nodiscard]] bool displayForumThreadBar() const;
[[nodiscard]] bool isInOneBunchWithPrevious() const;
virtual void draw(Painter &p, const PaintContext &context) const = 0;
@@ -688,7 +690,7 @@ protected:
std::unique_ptr<Reactions::InlineList> _reactions;
private:
void recountMonoforumSenderBarInBlocks();
void recountThreadBarInBlocks();
// This should be called only from previousInBlocksChanged()
// to add required bits to the Composer mask
@@ -755,4 +757,12 @@ private:
[[nodiscard]] Window::SessionController *ExtractController(
const ClickContext &context);
[[nodiscard]] TextSelection FindSearchQueryHighlight(
const QString &text,
const QString &query);
[[nodiscard]] TextSelection FindSearchQueryHighlight(
const QString &text,
QStringView lower);
} // namespace HistoryView

View File

@@ -0,0 +1,113 @@
/*
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 "history/view/history_view_group_members_widget.h"
#include "boxes/peers/edit_participants_box.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
#include "styles/style_chat.h"
namespace HistoryView {
namespace {
class GroupMembersWidgetController : public ParticipantsBoxController {
public:
using ParticipantsBoxController::ParticipantsBoxController;
protected:
base::unique_qptr<Ui::PopupMenu> rowContextMenu(
QWidget *parent,
not_null<PeerListRow*> row) override {
return nullptr;
}
};
} // namespace
GroupMembersWidget::GroupMembersWidget(
not_null<Ui::RpWidget*> parent,
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer)
: Ui::RpWidget(parent)
, _show(navigation->uiShow())
, _listController(std::make_unique<GroupMembersWidgetController>(
navigation,
peer,
ParticipantsBoxController::Role::Profile)) {
_listController->setStoriesShown(true);
setupList();
setContent(_list.data());
_listController->setDelegate(static_cast<PeerListDelegate*>(this));
}
void GroupMembersWidget::setupList() {
const auto topSkip = 0;
_listController->setStyleOverrides(&st::groupMembersWidgetList);
_listController->setStoriesShown(true);
_list = object_ptr<PeerListContent>(this, _listController.get());
widthValue() | rpl::start_with_next([this](int newWidth) {
if (newWidth > 0) {
_list->resizeToWidth(newWidth);
}
}, _list->lifetime());
_list->heightValue() | rpl::start_with_next([=](int listHeight) {
if (const auto newHeight = topSkip + listHeight; newHeight > 0) {
resize(width(), newHeight);
}
}, _list->lifetime());
_list->moveToLeft(0, topSkip);
}
void GroupMembersWidget::peerListSetTitle(rpl::producer<QString> title) {
}
void GroupMembersWidget::peerListSetAdditionalTitle(
rpl::producer<QString> title) {
}
bool GroupMembersWidget::peerListIsRowChecked(not_null<PeerListRow*> row) {
return false;
}
int GroupMembersWidget::peerListSelectedRowsCount() {
return 0;
}
void GroupMembersWidget::peerListScrollToTop() {
}
void GroupMembersWidget::peerListAddSelectedPeerInBunch(
not_null<PeerData*> peer) {
Unexpected("Item selection in Info::Profile::Members.");
}
void GroupMembersWidget::peerListAddSelectedRowInBunch(
not_null<PeerListRow*> row) {
Unexpected("Item selection in Info::Profile::Members.");
}
void GroupMembersWidget::peerListFinishSelectedRowsBunch() {
}
std::shared_ptr<Main::SessionShow> GroupMembersWidget::peerListUiShow() {
return _show;
}
void GroupMembersWidget::peerListSetDescription(
object_ptr<Ui::FlatLabel> description) {
description.destroy();
}
void GroupMembersWidget::peerListShowRowMenu(
not_null<PeerListRow*> row,
bool highlightRow,
Fn<void(not_null<Ui::PopupMenu*>)> destroyed) {
}
} // namespace HistoryView

View File

@@ -0,0 +1,58 @@
/*
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 "boxes/peer_list_box.h"
namespace Window {
class SessionNavigation;
} // namespace Window
class ParticipantsBoxController;
namespace HistoryView {
class GroupMembersWidget
: public Ui::RpWidget
, public PeerListContentDelegate {
public:
GroupMembersWidget(
not_null<Ui::RpWidget*> parent,
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer);
private:
void setupList();
// PeerListContentDelegate interface.
void peerListSetTitle(rpl::producer<QString> title) override;
void peerListSetAdditionalTitle(rpl::producer<QString> title) override;
bool peerListIsRowChecked(not_null<PeerListRow*> row) override;
int peerListSelectedRowsCount() override;
void peerListScrollToTop() override;
void peerListAddSelectedPeerInBunch(
not_null<PeerData*> peer) override;
void peerListAddSelectedRowInBunch(
not_null<PeerListRow*> row) override;
void peerListFinishSelectedRowsBunch() override;
void peerListSetDescription(
object_ptr<Ui::FlatLabel> description) override;
std::shared_ptr<Main::SessionShow> peerListUiShow() override;
void peerListShowRowMenu(
not_null<PeerListRow*> row,
bool highlightRow,
Fn<void(not_null<Ui::PopupMenu*>)> destroyed = nullptr) override;
// PeerListContentDelegate interface.
std::shared_ptr<Main::SessionShow> _show;
object_ptr<PeerListContent> _list = { nullptr };
std::unique_ptr<ParticipantsBoxController> _listController;
};
} // namespace HistoryView

View File

@@ -41,6 +41,7 @@ struct ItemPreview {
struct ToPreviewOptions {
const std::vector<ItemPreviewImage> *existing = nullptr;
QStringView searchLowerText;
bool hideSender = false;
bool hideCaption = false;
bool ignoreMessageText = false;

View File

@@ -1092,10 +1092,13 @@ QSize Message::performCountOptimalSize() {
void Message::refreshTopicButton() {
const auto item = data();
if (isAttachedToPrevious()
|| delegate()->elementHideTopicButton(this)) {
if (isAttachedToPrevious() || delegate()->elementHideTopicButton(this)) {
_topicButton = nullptr;
} else if (const auto topic = item->topic()) {
if (topic->channel()->useSubsectionTabs()) {
_topicButton = nullptr;
return;
}
if (!_topicButton) {
_topicButton = std::make_unique<TopicButton>();
}
@@ -1132,8 +1135,8 @@ int Message::marginTop() const {
if (const auto bar = Get<UnreadBar>()) {
result += bar->height();
}
if (const auto monoforumBar = Get<MonoforumSenderBar>()) {
result += monoforumBar->height();
if (const auto bar = Get<ForumThreadBar>()) {
result += bar->height();
}
if (const auto service = Get<ServicePreMessage>()) {
result += service->height;
@@ -1181,8 +1184,8 @@ void Message::draw(Painter &p, const PaintContext &context) const {
if (const auto date = Get<DateBadge>()) {
aboveh += date->height();
}
if (const auto sender = Get<MonoforumSenderBar>()) {
aboveh += sender->height();
if (const auto bar = Get<ForumThreadBar>()) {
aboveh += bar->height();
}
if (context.clip.intersects(QRect(0, aboveh, width(), unreadbarh))) {
p.translate(0, aboveh);

View File

@@ -527,8 +527,8 @@ int Service::marginTop() const {
if (const auto bar = Get<UnreadBar>()) {
result += bar->height();
}
if (const auto monoforumBar = Get<MonoforumSenderBar>()) {
result += monoforumBar->height();
if (const auto bar = Get<ForumThreadBar>()) {
result += bar->height();
}
if (const auto service = Get<ServicePreMessage>()) {
result += service->height;
@@ -553,8 +553,8 @@ void Service::draw(Painter &p, const PaintContext &context) const {
if (const auto date = Get<DateBadge>()) {
aboveh += date->height();
}
if (const auto sender = Get<MonoforumSenderBar>()) {
aboveh += sender->height();
if (const auto bar = Get<ForumThreadBar>()) {
aboveh += bar->height();
}
if (context.clip.intersects(QRect(0, aboveh, width(), unreadbarh))) {
p.translate(0, aboveh);

View File

@@ -35,7 +35,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/toast/toast.h"
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/label_with_custom_emoji.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/slider_natural_width.h"
#include "ui/wrap/slide_wrap.h"

View File

@@ -446,13 +446,12 @@ object_ptr<Ui::BoxContent> StarRefLinkBox(
CreateLinkIcon(box, &bot->session(), row.state.users),
st::boxRowPadding + st::starrefJoinUserpicsPadding);
box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
object_ptr<Ui::FlatLabel>(
box,
object_ptr<Ui::FlatLabel>(
box,
tr::lng_star_ref_link_title(),
st::boxTitle)),
st::boxRowPadding + st::starrefJoinTitlePadding);
tr::lng_star_ref_link_title(),
st::boxTitle),
st::boxRowPadding + st::starrefJoinTitlePadding,
style::al_top);
box->addRow(
object_ptr<Ui::FlatLabel>(
box,
@@ -470,7 +469,6 @@ object_ptr<Ui::BoxContent> StarRefLinkBox(
FormatForProgramDuration(program.durationMonths),
Ui::Text::WithEntities),
st::starrefCenteredText),
st::boxRowPadding,
style::al_top);
Ui::AddSkip(box->verticalLayout(), st::defaultVerticalListSkip * 3);
@@ -480,7 +478,6 @@ object_ptr<Ui::BoxContent> StarRefLinkBox(
box,
tr::lng_star_ref_link_recipient(),
st::starrefCenteredText),
st::boxRowPadding,
style::al_top);
Ui::AddSkip(box->verticalLayout());
box->addRow(object_ptr<Ui::AbstractButton>::fromRaw(
@@ -579,13 +576,12 @@ object_ptr<Ui::BoxContent> JoinStarRefBox(
}, box->lifetime());
box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
object_ptr<Ui::FlatLabel>(
box,
object_ptr<Ui::FlatLabel>(
box,
tr::lng_star_ref_title(),
st::boxTitle)),
st::boxRowPadding + st::starrefJoinTitlePadding);
tr::lng_star_ref_title(),
st::boxTitle),
st::boxRowPadding + st::starrefJoinTitlePadding,
style::al_top);
box->addRow(
object_ptr<Ui::FlatLabel>(
box,
@@ -599,7 +595,6 @@ object_ptr<Ui::BoxContent> JoinStarRefBox(
FormatForProgramDuration(program.durationMonths),
Ui::Text::WithEntities),
st::starrefCenteredText),
st::boxRowPadding,
style::al_top);
Ui::AddSkip(box->verticalLayout(), st::defaultVerticalListSkip * 3);
@@ -641,7 +636,6 @@ object_ptr<Ui::BoxContent> JoinStarRefBox(
box,
tr::lng_star_ref_link_recipient(),
st::starrefCenteredText),
st::boxRowPadding,
style::al_top);
Ui::AddSkip(box->verticalLayout());
const auto recipientWrap = box->addRow(

View File

@@ -215,7 +215,7 @@ void AddPremiumTopBarWithDefaultTitleBar(
+ st::defaultVerticalListSkip,
st::boxDividerBg,
RectPart::Bottom),
{});
style::margins());
bar->setPaused(true);
bar->setRoundEdges(false);
bar->setMaximumHeight(st::giveawayGiftCodeTopHeight);
@@ -348,12 +348,11 @@ void CreateGiveawayBox(
Ui::AddSkip(container);
Ui::AddSkip(container);
container->add(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
object_ptr<Ui::FlatLabel>(
box,
object_ptr<Ui::FlatLabel>(
box,
tr::lng_contacts_loading(),
st::giveawayLoadingLabel)));
tr::lng_contacts_loading(),
st::giveawayLoadingLabel),
style::al_top);
Ui::AddSkip(container);
Ui::AddSkip(container);
}

View File

@@ -177,12 +177,11 @@ void SelectCountriesBox(
Ui::AddSkip(container);
Ui::AddSkip(container);
container->add(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
object_ptr<Ui::FlatLabel>(
container,
object_ptr<Ui::FlatLabel>(
container,
tr::lng_search_messages_none(),
st::membersAbout)));
tr::lng_search_messages_none(),
st::membersAbout),
style::al_top);
Ui::AddSkip(container);
Ui::AddSkip(container);
}

View File

@@ -58,7 +58,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/text_utilities.h"
#include "ui/vertical_list.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/label_with_custom_emoji.h"
#include "ui/widgets/peer_bubble.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/slider_natural_width.h"
@@ -141,14 +140,12 @@ void AddHeader(
}
void AddRecipient(not_null<Ui::GenericBox*> box, const TextWithEntities &t) {
const auto wrap = box->addRow(
object_ptr<Ui::CenterWrap<Ui::RoundButton>>(
const auto container = box->addRow(
object_ptr<Ui::RoundButton>(
box,
object_ptr<Ui::RoundButton>(
box,
rpl::single(QString()),
st::channelEarnHistoryRecipientButton)));
const auto container = wrap->entity();
rpl::single(QString()),
st::channelEarnHistoryRecipientButton),
style::al_top);
const auto label = Ui::CreateChild<Ui::FlatLabel>(
container,
rpl::single(t),
@@ -200,6 +197,8 @@ void AddRecipient(not_null<Ui::GenericBox*> box, const TextWithEntities &t) {
QLocale().toString(date.time(), QLocale::ShortFormat));
}
constexpr auto kMinus = QChar(0x2212);
} // namespace
InnerWidget::InnerWidget(
@@ -401,7 +400,6 @@ void InnerWidget::fill() {
);
constexpr auto kMinorLength = 3;
constexpr auto kMinus = QChar(0x2212);
//constexpr auto kApproximately = QChar(0x2248);
const auto multiplier = data.usdRate;
@@ -412,51 +410,20 @@ void InnerWidget::fill() {
const auto session = &_peer->session();
const auto withdrawalEnabled = WithdrawalEnabled(session);
const auto addEmojiToMajor = [=](
not_null<Ui::FlatLabel*> label,
rpl::producer<CreditsAmount> value,
std::optional<bool> isIn,
std::optional<QMargins> margins) {
const auto &st = label->st();
const auto prepended = !isIn
? TextWithEntities()
: TextWithEntities::Simple((*isIn) ? QChar('+') : kMinus);
std::move(
value
) | rpl::start_with_next([=](CreditsAmount v) {
auto helper = Ui::Text::CustomEmojiHelper();
auto icon = helper.paletteDependent({ .factory = [=] {
return Ui::Earn::IconCurrencyColored(
st.style.font,
!isIn
? st::currencyFg->c
: (*isIn)
? st::boxTextFgGood->c
: st::menuIconAttentionColor->c);
}, .margin = margins
? *margins
: st::channelEarnCurrencyCommonMargins });
label->setMarkedText(
base::duplicate(prepended).append(icon).append(MajorPart(v)),
helper.context());
}, label->lifetime());
};
const auto arrow = Ui::Text::IconEmoji(&st::textMoreIconEmoji);
const auto addAboutWithLearn = [&](const tr::phrase<lngtag_link> &text) {
auto label = Ui::CreateLabelWithCustomEmoji(
auto label = object_ptr<Ui::FlatLabel>(
container,
text(
lt_link,
tr::lng_channel_earn_about_link(
lt_emoji,
rpl::single(arrow),
rpl::single(Ui::Text::IconEmoji(&st::textMoreIconEmoji)),
Ui::Text::RichLangValue
) | rpl::map([](TextWithEntities text) {
return Ui::Text::Link(std::move(text), 1);
}),
Ui::Text::RichLangValue),
Core::TextContext({ .session = session }),
st::boxDividerLabel);
label->setLink(1, std::make_shared<LambdaClickHandler>([=] {
_show->showBox(Box([=](not_null<Ui::GenericBox*> box) {
@@ -480,10 +447,10 @@ void InnerWidget::fill() {
const auto rect = Rect(icon.size() * 1.4);
auto owned = object_ptr<Ui::RpWidget>(content);
owned->resize(rect.size());
owned->setNaturalWidth(rect.width());
const auto widget = box->addRow(
object_ptr<Ui::CenterWrap<>>(
content,
std::move(owned)))->entity();
std::move(owned),
style::al_top);
widget->paintRequest(
) | rpl::start_with_next([=] {
auto p = Painter(widget);
@@ -496,14 +463,13 @@ void InnerWidget::fill() {
}
Ui::AddSkip(content);
Ui::AddSkip(content);
box->addRow(object_ptr<Ui::CenterWrap<>>(
box->addRow(object_ptr<Ui::FlatLabel>(
content,
object_ptr<Ui::FlatLabel>(
content,
bot
? tr::lng_channel_earn_bot_learn_title()
: tr::lng_channel_earn_learn_title(),
st::boxTitle)));
bot
? tr::lng_channel_earn_bot_learn_title()
: tr::lng_channel_earn_learn_title(),
st::boxTitle),
style::al_top);
Ui::AddSkip(content);
Ui::AddSkip(content);
Ui::AddSkip(content);
@@ -571,18 +537,17 @@ void InnerWidget::fill() {
Ui::AddSkip(content);
{
const auto l = box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
object_ptr<Ui::FlatLabel>(
content,
Ui::CreateLabelWithCustomEmoji(
content,
tr::lng_channel_earn_learn_coin_title(
lt_emoji,
rpl::single(
Ui::Text::Link(bigCurrencyIcon, 1)),
Ui::Text::RichLangValue
),
emojiHelper.context(),
st::boxTitle)))->entity();
tr::lng_channel_earn_learn_coin_title(
lt_emoji,
rpl::single(
Ui::Text::Link(bigCurrencyIcon, 1)),
Ui::Text::RichLangValue),
st::boxTitle,
st::defaultPopupMenu,
emojiHelper.context()),
style::al_top);
const auto diamonds = l->lifetime().make_state<int>(0);
l->setLink(1, std::make_shared<LambdaClickHandler>([=] {
const auto count = (*diamonds);
@@ -598,20 +563,19 @@ void InnerWidget::fill() {
Ui::AddSkip(content);
{
const auto label = box->addRow(
Ui::CreateLabelWithCustomEmoji(
object_ptr<Ui::FlatLabel>(
content,
tr::lng_channel_earn_learn_coin_about(
lt_link,
tr::lng_channel_earn_about_link(
lt_emoji,
rpl::single(arrow),
rpl::single(Ui::Text::IconEmoji(
&st::textMoreIconEmoji)),
Ui::Text::RichLangValue
) | rpl::map([](TextWithEntities text) {
return Ui::Text::Link(std::move(text), 1);
}),
Ui::Text::RichLangValue
),
Core::TextContext({ .session = session }),
Ui::Text::RichLangValue),
st::channelEarnLearnDescription));
label->resizeToWidth(box->width()
- rect::m::sum::h(st::boxRowPadding));
@@ -727,7 +691,7 @@ void InnerWidget::fill() {
const auto majorLabel = Ui::CreateChild<Ui::FlatLabel>(
line,
st::channelEarnOverviewMajorLabel);
addEmojiToMajor(
AddEmojiToMajor(
majorLabel,
rpl::duplicate(currencyValue),
{},
@@ -870,9 +834,8 @@ void InnerWidget::fill() {
Ui::AddSkip(container);
const auto labels = container->add(
object_ptr<Ui::CenterWrap<Ui::RpWidget>>(
container,
object_ptr<Ui::RpWidget>(container)))->entity();
object_ptr<Ui::RpWidget>(container),
style::al_top);
const auto majorLabel = Ui::CreateChild<Ui::FlatLabel>(
labels,
@@ -880,7 +843,7 @@ void InnerWidget::fill() {
{
const auto &m = st::channelEarnCurrencyCommonMargins;
const auto p = QMargins(m.left(), 0, m.right(), m.bottom());
addEmojiToMajor(majorLabel, rpl::single(value), {}, p);
AddEmojiToMajor(majorLabel, rpl::single(value), {}, p);
}
majorLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
const auto minorLabel = Ui::CreateChild<Ui::FlatLabel>(
@@ -897,6 +860,8 @@ void InnerWidget::fill() {
labels->resize(
majorSize.width() + minorSize.width(),
majorSize.height());
labels->setNaturalWidth(
majorSize.width() + minorSize.width());
majorLabel->moveToLeft(0, 0);
minorLabel->moveToRight(
0,
@@ -906,12 +871,11 @@ void InnerWidget::fill() {
Ui::AddSkip(container);
container->add(
object_ptr<Ui::CenterWrap<>>(
object_ptr<Ui::FlatLabel>(
container,
object_ptr<Ui::FlatLabel>(
container,
ToUsd(value, multiplier, 0),
st::channelEarnOverviewSubMinorLabel)));
ToUsd(value, multiplier, 0),
st::channelEarnOverviewSubMinorLabel),
style::al_top);
Ui::AddSkip(container);
@@ -921,7 +885,8 @@ void InnerWidget::fill() {
container,
rpl::never<QString>(),
stButton),
st::boxRowPadding);
st::boxRowPadding,
style::al_justify);
const auto label = Ui::CreateChild<Ui::FlatLabel>(
button,
@@ -1162,7 +1127,7 @@ void InnerWidget::fill() {
const auto majorLabel = Ui::CreateChild<Ui::FlatLabel>(
wrap,
st::channelEarnHistoryMajorLabel);
addEmojiToMajor(
AddEmojiToMajor(
majorLabel,
rpl::single(entry.credits),
isIn,
@@ -1189,14 +1154,13 @@ void InnerWidget::fill() {
Ui::AddSkip(box->verticalLayout());
Ui::AddSkip(box->verticalLayout());
const auto labels = box->addRow(
object_ptr<Ui::CenterWrap<Ui::RpWidget>>(
box,
object_ptr<Ui::RpWidget>(box)))->entity();
object_ptr<Ui::RpWidget>(box),
style::al_top);
const auto majorLabel = Ui::CreateChild<Ui::FlatLabel>(
labels,
st::channelEarnOverviewMajorLabel);
addEmojiToMajor(
AddEmojiToMajor(
majorLabel,
rpl::single(entry.credits),
isIn,
@@ -1220,6 +1184,8 @@ void InnerWidget::fill() {
labels->resize(
majorSize.width() + minorSize.width(),
majorSize.height());
labels->setNaturalWidth(
majorSize.width() + minorSize.width());
majorLabel->moveToLeft(0, 0);
minorLabel->moveToRight(
0,
@@ -1227,12 +1193,12 @@ void InnerWidget::fill() {
}, box->lifetime());
Ui::AddSkip(box->verticalLayout());
box->addRow(object_ptr<Ui::CenterWrap<>>(
box,
box->addRow(
object_ptr<Ui::FlatLabel>(
box,
dateText,
st::channelEarnHistorySubLabel)));
st::channelEarnHistorySubLabel),
style::al_top);
Ui::AddSkip(box->verticalLayout());
Ui::AddSkip(box->verticalLayout());
AddChannelEarnTable(
@@ -1241,14 +1207,14 @@ void InnerWidget::fill() {
entry);
Ui::AddSkip(box->verticalLayout());
Ui::AddSkip(box->verticalLayout());
box->addRow(object_ptr<Ui::CenterWrap<>>(
box,
box->addRow(
object_ptr<Ui::FlatLabel>(
box,
isIn
? tr::lng_channel_earn_history_in_about()
: tr::lng_channel_earn_history_out(),
st::channelEarnHistoryDescriptionLabel)));
st::channelEarnHistoryDescriptionLabel),
style::al_top);
Ui::AddSkip(box->verticalLayout());
if (isIn) {
Ui::AddSkip(box->verticalLayout());
@@ -1259,9 +1225,8 @@ void InnerWidget::fill() {
}
if (isIn) {
box->addRow(
object_ptr<Ui::CenterWrap<>>(
box,
Ui::CreatePeerBubble(box, peer)));
Ui::CreatePeerBubble(box, peer),
style::al_top);
}
const auto closeBox = [=] { box->closeBox(); };
{
@@ -1530,4 +1495,34 @@ not_null<PeerData*> InnerWidget::peer() const {
return _peer;
}
void AddEmojiToMajor(
not_null<Ui::FlatLabel*> label,
rpl::producer<CreditsAmount> value,
std::optional<bool> isIn,
std::optional<QMargins> margins) {
const auto &st = label->st();
const auto prepended = !isIn
? TextWithEntities()
: TextWithEntities::Simple((*isIn) ? QChar('+') : kMinus);
std::move(
value
) | rpl::start_with_next([=](CreditsAmount v) {
auto helper = Ui::Text::CustomEmojiHelper();
auto icon = helper.paletteDependent({ .factory = [=] {
return Ui::Earn::IconCurrencyColored(
st.style.font,
!isIn
? st::currencyFg->c
: (*isIn)
? st::boxTextFgGood->c
: st::menuIconAttentionColor->c);
}, .margin = margins
? *margins
: st::channelEarnCurrencyCommonMargins });
label->setMarkedText(
base::duplicate(prepended).append(icon).append(MajorPart(v)),
helper.context());
}, label->lifetime());
}
} // namespace Info::ChannelEarn

View File

@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Ui {
class Show;
class FlatLabel;
} // namespace Ui
namespace Info {
@@ -63,4 +64,10 @@ private:
};
void AddEmojiToMajor(
not_null<Ui::FlatLabel*> label,
rpl::producer<CreditsAmount> value,
std::optional<bool> isIn,
std::optional<QMargins> margins);
} // namespace Info::ChannelEarn

View File

@@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "info/channel_statistics/earn/earn_icons.h"
#include "main/main_session.h"
#include "overview/overview_checkbox.h"
#include "settings/settings_credits_graphics.h"
#include "ui/layers/generic_box.h"
#include "ui/text/format_values.h"
@@ -34,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_session_controller.h"
#include "styles/style_credits.h"
#include "styles/style_layers.h"
#include "styles/style_overview.h"
#include "styles/style_premium.h"
namespace Info::PeerGifts {
@@ -78,6 +80,18 @@ GiftButton::~GiftButton() {
unsubscribe();
}
void GiftButton::onStateChanged(State was, StateChangeSource source) {
if (_check) {
const auto diff = state() ^ was;
if (diff & State::Enum::Over) {
_check->setActive(state() & State::Enum::Over);
}
if (diff & State::Enum::Down) {
_check->setPressed(state() & State::Enum::Down);
}
}
}
void GiftButton::unsubscribe() {
if (base::take(_subscribed)) {
_userpic->subscribeToUpdates(nullptr);
@@ -85,6 +99,16 @@ void GiftButton::unsubscribe() {
}
void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) {
_mode = mode;
if (_mode != GiftButtonMode::Selection) {
_check = nullptr;
} else if (!_check) {
_check = std::make_unique<Overview::Layout::Checkbox>(
[=] { update(); },
st::overviewSmallCheck);
}
const auto unique = v::is<GiftTypeStars>(descriptor)
? v::get<GiftTypeStars>(descriptor).info.unique.get()
: nullptr;
@@ -106,7 +130,6 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) {
_descriptor = descriptor;
_resalePrice = resalePrice;
const auto resale = (_resalePrice > 0);
_small = (mode != Mode::Full);
v::match(descriptor, [&](const GiftTypePremium &data) {
const auto months = data.months;
_text = Ui::Text::String(st::giftBoxGiftHeight / 4);
@@ -147,12 +170,12 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) {
const auto soldOut = data.info.limitedCount
&& !data.userpic
&& !data.info.limitedLeft;
_userpic = !data.userpic
_userpic = (!data.userpic || _mode == GiftButtonMode::Selection)
? nullptr
: data.from
? Ui::MakeUserpicThumbnail(data.from)
: Ui::MakeHiddenAuthorThumbnail();
if (_small && !resale) {
if (small() && !resale) {
_price = {};
_stars.reset();
return;
@@ -169,7 +192,7 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) {
? unique->starsForResale
: data.info.starsResellMin)
).append(data.info.resellCount > 1 ? "+" : ""))
: (_small && unique && unique->starsForResale)
: (small() && unique && unique->starsForResale)
? Data::FormatGiftResaleAsked(*unique)
: unique
? tr::lng_gift_transfer_button(
@@ -214,7 +237,7 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) {
_uniquePatternEmoji = nullptr;
_uniquePatternCache.clear();
if (_small && !resale) {
if (small() && !resale) {
_button = QRect();
return;
}
@@ -225,7 +248,7 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor, Mode mode) {
QSize(buttonw, buttonh)
).marginsAdded(st::giftBoxButtonPadding);
const auto skipy = _delegate->buttonSize().height()
- (_small
- (small()
? st::giftBoxButtonBottomSmall
: _byStars.isEmpty()
? st::giftBoxButtonBottom
@@ -295,11 +318,17 @@ void GiftButton::setGeometry(QRect inner, QMargins extend) {
}
QMargins GiftButton::currentExtend() const {
const auto progress = _selectedAnimation.value(_selected ? 1. : 0.);
const auto progress = (_mode == Mode::Selection)
? 0.
: _selectedAnimation.value(_selected ? 1. : 0.);
const auto added = anim::interpolate(0, st::giftBoxSelectSkip, progress);
return _extend + QMargins(added, added, added, added);
}
bool GiftButton::small() const {
return _mode != GiftButtonMode::Full;
}
void GiftButton::toggleSelected(bool selected, anim::type animated) {
if (_selected == selected) {
if (animated == anim::type::instant) {
@@ -310,8 +339,14 @@ void GiftButton::toggleSelected(bool selected, anim::type animated) {
const auto duration = st::defaultRoundCheckbox.duration;
_selected = selected;
if (animated == anim::type::instant) {
if (_check) {
_check->finishAnimating();
}
_selectedAnimation.stop();
return;
} else if (_check) {
_check->setChecked(selected, animated);
return;
}
_selectedAnimation.start([=] {
update();
@@ -358,7 +393,9 @@ void GiftButton::paintBackground(QPainter &p, const QImage &background) {
}
auto hq = PainterHighQualityEnabler(p);
const auto progress = _selectedAnimation.value(_selected ? 1. : 0.);
const auto progress = (_mode == Mode::Selection)
? 0.
: _selectedAnimation.value(_selected ? 1. : 0.);
if (progress < 0.01) {
return;
}
@@ -446,7 +483,7 @@ void GiftButton::paintEvent(QPaintEvent *e) {
const auto unique = v::is<GiftTypeStars>(_descriptor)
? v::get<GiftTypeStars>(_descriptor).info.unique.get()
: nullptr;
const auto onsale = (unique && unique->starsForResale && _small);
const auto onsale = unique && unique->starsForResale && small();
const auto requirePremium = v::is<GiftTypeStars>(_descriptor)
&& !v::get<GiftTypeStars>(_descriptor).userpic
&& !v::get<GiftTypeStars>(_descriptor).info.unique
@@ -482,6 +519,14 @@ void GiftButton::paintEvent(QPaintEvent *e) {
const auto image = _userpic->image(st::giftBoxUserpicSize);
const auto skip = st::giftBoxUserpicSkip;
p.drawImage(extend.left() + skip, extend.top() + skip, image);
} else if (_check) {
const auto skip = st::giftBoxUserpicSkip;
_check->paint(
p,
QPoint(extend.left() + skip, extend.top() + skip),
width,
_selected,
true);
}
auto frame = QImage();
@@ -502,7 +547,7 @@ void GiftButton::paintEvent(QPaintEvent *e) {
p.drawImage(
QRect(
(width - size.width()) / 2,
(_small
(small()
? st::giftBoxSmallStickerTop
: _text.isEmpty()
? st::giftBoxStickerStarTop
@@ -516,7 +561,7 @@ void GiftButton::paintEvent(QPaintEvent *e) {
if (hidden) {
const auto topleft = QPoint(
(width - st::giftBoxStickerSize.width()) / 2,
(_small
(small()
? st::giftBoxSmallStickerTop
: _text.isEmpty()
? st::giftBoxStickerStarTop
@@ -781,7 +826,7 @@ QSize Delegate::buttonSize() {
const auto available = width - padding.left() - padding.right();
const auto singlew = (available - 2 * st::giftBoxGiftSkip.x())
/ kGiftsPerRow;
const auto minimal = (_mode == GiftButtonMode::Minimal);
const auto minimal = (_mode != GiftButtonMode::Full);
_single = QSize(
singlew,
minimal ? st::giftBoxGiftSmall : st::giftBoxGiftHeight);

View File

@@ -34,6 +34,10 @@ namespace Main {
class Session;
} // namespace Main
namespace Overview::Layout {
class Checkbox;
} // namespace Overview::Layout
namespace Ui {
class DynamicImage;
} // namespace Ui
@@ -118,9 +122,10 @@ struct GiftBadge {
const GiftBadge &) = default;
};
enum class GiftButtonMode {
enum class GiftButtonMode : uint8 {
Full,
Minimal,
Selection,
};
class GiftButtonDelegate {
@@ -173,7 +178,9 @@ private:
void setDocument(not_null<DocumentData*> document);
[[nodiscard]] QMargins currentExtend() const;
[[nodiscard]] bool small() const;
void onStateChanged(State was, StateChangeSource source) override;
void unsubscribe();
const not_null<GiftButtonDelegate*> _delegate;
@@ -190,11 +197,12 @@ private:
base::flat_map<float64, QImage> _uniquePatternCache;
std::optional<Ui::Premium::ColoredMiniStars> _stars;
Ui::Animations::Simple _selectedAnimation;
std::unique_ptr<Overview::Layout::Checkbox> _check;
int _resalePrice = 0;
GiftButtonMode _mode = GiftButtonMode::Full;
bool _subscribed = false;
bool _patterned = false;
bool _selected = false;
bool _small = false;
QRect _button;
QMargins _extend;

View File

@@ -201,6 +201,7 @@ private:
const not_null<Window::SessionController*> _window;
const not_null<PeerData*> _peer;
const int _addingToCollectionId = 0;
const GiftButtonMode _mode;
rpl::variable<Descriptor> _descriptor;
Delegate _delegate;
@@ -267,8 +268,11 @@ InnerWidget::InnerWidget(
, _window(window)
, _peer(peer)
, _addingToCollectionId(addingToCollectionId)
, _mode(_addingToCollectionId
? GiftButtonMode::Selection
: GiftButtonMode::Minimal)
, _descriptor(std::move(descriptor))
, _delegate(&_window->session(), GiftButtonMode::Minimal)
, _delegate(&_window->session(), _mode)
, _all(std::move(all))
, _entries(&_all)
, _list(&_entries->list)
@@ -750,7 +754,7 @@ void InnerWidget::validateButtons() {
view.button->toggleSelected(
_addingToCollectionId && _inCollection.contains(manageId),
anim::type::instant);
view.button->setDescriptor(descriptor, GiftButton::Mode::Minimal);
view.button->setDescriptor(descriptor, _mode);
view.button->setClickedCallback(callback);
return true;
};
@@ -979,30 +983,27 @@ void InnerWidget::refreshAbout() {
} else if (collectionCanAdd) {
auto about = std::make_unique<Ui::VerticalLayout>(this);
about->add(
object_ptr<Ui::CenterWrap<>>(
object_ptr<Ui::FlatLabel>(
about.get(),
object_ptr<Ui::FlatLabel>(
about.get(),
tr::lng_gift_collection_empty_title(),
st::collectionEmptyTitle)),
st::collectionEmptyTitleMargin);
tr::lng_gift_collection_empty_title(),
st::collectionEmptyTitle),
st::collectionEmptyTitleMargin,
style::al_top);
about->add(
object_ptr<Ui::CenterWrap<>>(
object_ptr<Ui::FlatLabel>(
about.get(),
object_ptr<Ui::FlatLabel>(
about.get(),
tr::lng_gift_collection_empty_text(),
st::collectionEmptyText)),
st::collectionEmptyTextMargin);
tr::lng_gift_collection_empty_text(),
st::collectionEmptyText),
st::collectionEmptyTextMargin,
style::al_top);
const auto button = about->add(
object_ptr<Ui::CenterWrap<Ui::RoundButton>>(
object_ptr<Ui::RoundButton>(
about.get(),
object_ptr<Ui::RoundButton>(
about.get(),
rpl::single(QString()),
st::collectionEmptyButton)),
st::collectionEmptyAddMargin)->entity();
rpl::single(QString()),
st::collectionEmptyButton),
st::collectionEmptyAddMargin,
style::al_top);
button->setText(tr::lng_gift_collection_add_button(
) | rpl::map([](const QString &text) {
return Ui::Text::IconEmoji(&st::collectionAddIcon).append(text);
@@ -1081,7 +1082,7 @@ void InnerWidget::editCollectionGifts(int id) {
state->descriptor.value(),
id,
(_all.filter == Filter()) ? _all : Entries()),
{});
style::margins());
state->changes = content->changes();
content->descriptorChanges(
@@ -1505,6 +1506,7 @@ Widget::Widget(QWidget *parent, not_null<Controller*> controller)
controller->parentController(),
controller->giftsPeer(),
_descriptor.value()));
_emptyCollectionShown = _inner->collectionEmptyValue();
_inner->notifyEnabled(
) | rpl::take(1) | rpl::start_with_next([=](bool enabled) {
_notifyEnabled = enabled;
@@ -1518,7 +1520,10 @@ Widget::Widget(QWidget *parent, not_null<Controller*> controller)
scrollTo({ 0, 0 });
}, _inner->lifetime());
_descriptor.value() | rpl::start_with_next([=] {
rpl::combine(
_descriptor.value(),
_emptyCollectionShown.value()
) | rpl::start_with_next([=] {
refreshBottom();
}, _inner->lifetime());
}
@@ -1527,22 +1532,22 @@ void Widget::refreshBottom() {
const auto notify = _notifyEnabled.has_value();
const auto descriptor = _descriptor.current();
const auto shownId = descriptor.collectionId;
const auto withButton = shownId && peer()->canManageGifts();
const auto withButton = shownId
&& peer()->canManageGifts()
&& !_emptyCollectionShown.current();
const auto wasBottom = _pinnedToBottom ? _pinnedToBottom->height() : 0;
delete _pinnedToBottom.data();
if (!notify && !withButton) {
setScrollBottomSkip(0);
_hasPinnedToBottom = false;
} else if (withButton) {
setupBottomButton(wasBottom, _inner->collectionEmptyValue());
setupBottomButton(wasBottom);
} else {
setupNotifyCheckbox(wasBottom, *_notifyEnabled);
}
}
void Widget::setupBottomButton(
int wasBottomHeight,
rpl::producer<bool> hidden) {
void Widget::setupBottomButton(int wasBottomHeight) {
_pinnedToBottom = Ui::CreateChild<Ui::SlideWrap<Ui::RpWidget>>(
this,
object_ptr<Ui::RpWidget>(this));
@@ -1561,10 +1566,8 @@ void Widget::setupBottomButton(
) | rpl::map([](const QString &text) {
return Ui::Text::IconEmoji(&st::collectionAddIcon).append(text);
}));
std::move(hidden) | rpl::start_with_next([=](bool hidden) {
button->setVisible(!hidden);
_hasPinnedToBottom = !hidden;
}, button->lifetime());
button->show();
_hasPinnedToBottom = true;
button->setClickedCallback([=] {
if (const auto id = _descriptor.current().collectionId) {

View File

@@ -105,12 +105,13 @@ private:
std::shared_ptr<ContentMemento> doCreateMemento() override;
void setupNotifyCheckbox(int wasBottomHeight, bool enabled);
void setupBottomButton(int wasBottomHeight, rpl::producer<bool> hidden);
void setupBottomButton(int wasBottomHeight);
void refreshBottom();
InnerWidget *_inner = nullptr;
QPointer<Ui::SlideWrap<Ui::RpWidget>> _pinnedToBottom;
rpl::variable<bool> _hasPinnedToBottom;
rpl::variable<bool> _emptyCollectionShown;
rpl::variable<Descriptor> _descriptor;
std::optional<bool> _notifyEnabled;
bool _shown = false;

View File

@@ -1985,7 +1985,8 @@ void DetailsFiller::setupMainApp() {
_wrap,
tr::lng_profile_open_app(),
st::infoOpenApp),
st::infoOpenAppMargin);
st::infoOpenAppMargin,
style::al_justify);
button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
const auto user = _peer->asUser();

View File

@@ -590,30 +590,29 @@ void FillLoading(
content->add(std::move(icon.widget));
content->add(
object_ptr<Ui::CenterWrap<>>(
object_ptr<Ui::FlatLabel>(
content,
object_ptr<Ui::FlatLabel>(
content,
(type == LoadingType::Boosts)
? tr::lng_stats_boosts_loading()
: (type == LoadingType::Earn)
? tr::lng_stats_earn_loading()
: tr::lng_stats_loading(),
st::changePhoneTitle)),
st::changePhoneTitlePadding + st::boxRowPadding);
(type == LoadingType::Boosts)
? tr::lng_stats_boosts_loading()
: (type == LoadingType::Earn)
? tr::lng_stats_earn_loading()
: tr::lng_stats_loading(),
st::changePhoneTitle),
st::changePhoneTitlePadding + st::boxRowPadding,
style::al_top);
content->add(
object_ptr<Ui::CenterWrap<>>(
object_ptr<Ui::FlatLabel>(
content,
object_ptr<Ui::FlatLabel>(
content,
(type == LoadingType::Boosts)
? tr::lng_stats_boosts_loading_subtext()
: (type == LoadingType::Earn)
? tr::lng_stats_earn_loading_subtext()
: tr::lng_stats_loading_subtext(),
st::statisticsLoadingSubtext)),
st::changePhoneDescriptionPadding + st::boxRowPadding);
(type == LoadingType::Boosts)
? tr::lng_stats_boosts_loading_subtext()
: (type == LoadingType::Earn)
? tr::lng_stats_earn_loading_subtext()
: tr::lng_stats_loading_subtext(),
st::statisticsLoadingSubtext),
st::changePhoneDescriptionPadding + st::boxRowPadding,
style::al_top
)->setTryMakeSimilarLines(true);
Ui::AddSkip(content, st::settingsBlockedListIconPadding.top());
}

View File

@@ -586,30 +586,27 @@ void InnerWidget::refreshEmpty() {
if (albumCanAdd) {
auto empty = object_ptr<Ui::VerticalLayout>(this);
empty->add(
object_ptr<Ui::CenterWrap<>>(
object_ptr<Ui::FlatLabel>(
empty.get(),
object_ptr<Ui::FlatLabel>(
empty.get(),
tr::lng_stories_album_empty_title(),
st::collectionEmptyTitle)),
st::collectionEmptyTitleMargin);
tr::lng_stories_album_empty_title(),
st::collectionEmptyTitle),
st::collectionEmptyTitleMargin,
style::al_top);
empty->add(
object_ptr<Ui::CenterWrap<>>(
object_ptr<Ui::FlatLabel>(
empty.get(),
object_ptr<Ui::FlatLabel>(
empty.get(),
tr::lng_stories_album_empty_text(),
st::collectionEmptyText)),
st::collectionEmptyTextMargin);
tr::lng_stories_album_empty_text(),
st::collectionEmptyText),
st::collectionEmptyTextMargin,
style::al_top);
const auto button = empty->add(
object_ptr<Ui::CenterWrap<Ui::RoundButton>>(
object_ptr<Ui::RoundButton>(
empty.get(),
object_ptr<Ui::RoundButton>(
empty.get(),
rpl::single(QString()),
st::collectionEmptyButton)),
st::collectionEmptyAddMargin)->entity();
rpl::single(QString()),
st::collectionEmptyButton),
st::collectionEmptyAddMargin,
style::al_top);
button->setText(tr::lng_stories_album_add_button(
) | rpl::map([](const QString &text) {
return Ui::Text::IconEmoji(&st::collectionAddIcon).append(text);

View File

@@ -65,6 +65,7 @@ Widget::Widget(
controller,
_albumId.value(),
controller->key().storiesAddToAlbumId()));
_emptyAlbumShown = _inner->albumEmptyValue();
_inner->albumIdChanges() | rpl::start_with_next([=](int id) {
controller->showSection(
Make(controller->storiesPeer(), id),
@@ -76,7 +77,10 @@ Widget::Widget(
scrollTo(request);
}, _inner->lifetime());
_albumId.value() | rpl::start_with_next([=] {
rpl::combine(
_albumId.value(),
_emptyAlbumShown.value()
) | rpl::start_with_next([=] {
refreshBottom();
}, _inner->lifetime());
}
@@ -133,20 +137,19 @@ void Widget::refreshBottom() {
const auto albumId = _albumId.current();
const auto withButton = (albumId != Data::kStoriesAlbumIdSaved)
&& (albumId != Data::kStoriesAlbumIdArchive)
&& controller()->storiesPeer()->canEditStories();
&& controller()->storiesPeer()->canEditStories()
&& !_emptyAlbumShown.current();
const auto wasBottom = _pinnedToBottom ? _pinnedToBottom->height() : 0;
delete _pinnedToBottom.data();
if (!withButton) {
setScrollBottomSkip(0);
_hasPinnedToBottom = false;
} else {
setupBottomButton(wasBottom, _inner->albumEmptyValue());
setupBottomButton(wasBottom);
}
}
void Widget::setupBottomButton(
int wasBottomHeight,
rpl::producer<bool> hidden) {
void Widget::setupBottomButton(int wasBottomHeight) {
_pinnedToBottom = Ui::CreateChild<Ui::SlideWrap<Ui::RpWidget>>(
this,
object_ptr<Ui::RpWidget>(this));
@@ -166,10 +169,7 @@ void Widget::setupBottomButton(
return Ui::Text::IconEmoji(&st::collectionAddIcon).append(text);
}));
button->show();
std::move(hidden) | rpl::start_with_next([=](bool hidden) {
button->setVisible(!hidden);
_hasPinnedToBottom = !hidden;
}, button->lifetime());
_hasPinnedToBottom = true;
button->setClickedCallback([=] {
if (const auto id = _albumId.current()) {

View File

@@ -72,7 +72,7 @@ private:
void saveState(not_null<Memento*> memento);
void restoreState(not_null<Memento*> memento);
void setupBottomButton(int wasBottomHeight, rpl::producer<bool> hidden);
void setupBottomButton(int wasBottomHeight);
void refreshBottom();
std::shared_ptr<ContentMemento> doCreateMemento() override;
@@ -81,6 +81,7 @@ private:
InnerWidget *_inner = nullptr;
QPointer<Ui::SlideWrap<Ui::RpWidget>> _pinnedToBottom;
rpl::variable<bool> _hasPinnedToBottom;
rpl::variable<bool> _emptyAlbumShown;
bool _shown = false;
};

View File

@@ -52,10 +52,11 @@ QRect BubbleWrapInnerRect(const QRect &r) {
not_null<Ui::RpWidget*> AddBubbleWrap(
not_null<Ui::VerticalLayout*> container,
const QSize &size) {
const auto bubble = container->add(object_ptr<Ui::CenterWrap<RpWidget>>(
container,
object_ptr<Ui::RpWidget>(container)))->entity();
const auto bubble = container->add(
object_ptr<Ui::RpWidget>(container),
style::al_top);
bubble->resize(size);
bubble->setNaturalWidth(size.width());
auto cached = QImage(
size * style::DevicePixelRatio(),

View File

@@ -245,12 +245,11 @@ object_ptr<Ui::RpWidget> CreateGradientEditor(
std::vector<QColor> colors;
};
const auto preview = container->add(
object_ptr<Ui::CenterWrap<EmojiUserpic>>(
object_ptr<EmojiUserpic>(
container,
object_ptr<EmojiUserpic>(
container,
Size(st::defaultUserpicButton.photoSize),
false)))->entity();
Size(st::defaultUserpicButton.photoSize),
false),
style::al_top);
preview->setDuration(0);
if (document) {
preview->setDocument(document);

View File

@@ -166,6 +166,7 @@ EmojiUserpic::EmojiUserpic(
, _painter(size.width())
, _duration(st::slideWrapDuration) {
resize(size);
setNaturalWidth(size.width());
}
void EmojiUserpic::setDocument(not_null<DocumentData*> document) {

View File

@@ -178,7 +178,7 @@ void ShowGradientEditor(
},
});
box->setWidth(content->width());
box->addRow(std::move(content), {});
box->addRow(std::move(content), style::margins());
}));
}
@@ -408,13 +408,12 @@ not_null<Ui::VerticalLayout*> CreateUserpicBuilder(
const auto state = container->lifetime().make_state<State>();
const auto preview = container->add(
object_ptr<Ui::CenterWrap<EmojiUserpic>>(
object_ptr<EmojiUserpic>(
container,
object_ptr<EmojiUserpic>(
container,
Size(st::settingsInfoPhotoSize),
data.isForum)),
st::userpicBuilderEmojiPreviewPadding)->entity();
Size(st::settingsInfoPhotoSize),
data.isForum),
st::userpicBuilderEmojiPreviewPadding,
style::al_top);
if (const auto id = data.documentId) {
const auto document = controller->session().data().document(id);
if (document && document->sticker()) {
@@ -423,13 +422,12 @@ not_null<Ui::VerticalLayout*> CreateUserpicBuilder(
}
container->add(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
object_ptr<Ui::FlatLabel>(
container,
object_ptr<Ui::FlatLabel>(
container,
tr::lng_userpic_builder_color_subtitle(),
st::userpicBuilderEmojiSubtitle)),
st::userpicBuilderEmojiSubtitlePadding);
tr::lng_userpic_builder_color_subtitle(),
st::userpicBuilderEmojiSubtitle),
st::userpicBuilderEmojiSubtitlePadding,
style::al_top);
const auto paletteBg = Ui::AddBubbleWrap(
container,
@@ -515,13 +513,12 @@ not_null<Ui::VerticalLayout*> CreateUserpicBuilder(
}, palette->lifetime());
container->add(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
object_ptr<Ui::FlatLabel>(
container,
object_ptr<Ui::FlatLabel>(
container,
tr::lng_userpic_builder_emoji_subtitle(),
st::userpicBuilderEmojiSubtitle)),
st::userpicBuilderEmojiSubtitlePadding);
tr::lng_userpic_builder_emoji_subtitle(),
st::userpicBuilderEmojiSubtitle),
st::userpicBuilderEmojiSubtitlePadding,
style::al_top);
const auto selectorBg = Ui::AddBubbleWrap(
container,

View File

@@ -72,7 +72,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/vertical_list.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/dropdown_menu.h"
#include "ui/widgets/label_with_custom_emoji.h"
#include "ui/widgets/menu/menu_item_base.h"
#include "ui/widgets/popup_menu.h"
#include "webview/webview_interface.h"
@@ -384,7 +383,7 @@ void FillBotUsepic(
not_null<Ui::GenericBox*> box,
not_null<PeerData*> bot,
base::weak_ptr<Window::SessionController> weak) {
auto aboutLabel = Ui::CreateLabelWithCustomEmoji(
auto aboutLabel = object_ptr<Ui::FlatLabel>(
box->verticalLayout(),
tr::lng_allow_bot_webview_details(
lt_emoji,
@@ -393,7 +392,6 @@ void FillBotUsepic(
) | rpl::map([](TextWithEntities text) {
return Ui::Text::Link(std::move(text), u"internal:"_q);
}),
Core::TextContext({ .session = &bot->session() }),
st::defaultFlatLabel);
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
box->verticalLayout(),
@@ -521,7 +519,6 @@ void ConfirmEmojiStatusAccessBox(
rpl::single(name),
Ui::Text::RichLangValue),
st::botEmojiStatusText),
st::boxRowPadding,
style::al_top);
box->addButton(tr::lng_bot_emoji_status_access_allow(), [=] {
@@ -565,7 +562,6 @@ void ConfirmEmojiStatusBox(
box,
tr::lng_bot_emoji_status_title(),
st::botEmojiStatusTitle),
st::boxRowPadding,
style::al_top);
AddSkip(box->verticalLayout());
@@ -577,7 +573,6 @@ void ConfirmEmojiStatusBox(
rpl::single(Ui::Text::Bold(bot->name())),
Ui::Text::RichLangValue),
st::botEmojiStatusText),
st::boxRowPadding,
style::al_top);
AddSkip(box->verticalLayout(), 2 * st::defaultVerticalListSkip);

Some files were not shown because too many files have changed in this diff Show More