Compare commits
107 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1f9b5a96f | ||
|
|
2887c0b564 | ||
|
|
9b7826ea0d | ||
|
|
241be89e5c | ||
|
|
39075538fb | ||
|
|
1592f70a7c | ||
|
|
732bb25666 | ||
|
|
5cba1cdc64 | ||
|
|
d346925b9d | ||
|
|
02f3985125 | ||
|
|
f3db43abc9 | ||
|
|
ecf61712cd | ||
|
|
b47c66155d | ||
|
|
2fda96a375 | ||
|
|
12c2e42917 | ||
|
|
94a956ce19 | ||
|
|
704f64a0c9 | ||
|
|
f9ca7f4505 | ||
|
|
0a3d31a91f | ||
|
|
3c17fab15a | ||
|
|
2efe409c60 | ||
|
|
1176421bf2 | ||
|
|
05911a7172 | ||
|
|
1326359745 | ||
|
|
fc26457218 | ||
|
|
173108a9cb | ||
|
|
7307f0b1a5 | ||
|
|
c49dac57b7 | ||
|
|
6288da2f3d | ||
|
|
ce37c6ef08 | ||
|
|
5f93725431 | ||
|
|
90dfdb0e1f | ||
|
|
7cd330db9a | ||
|
|
b14ac5cafe | ||
|
|
1fc929b78f | ||
|
|
fd47fd4d9e | ||
|
|
9b74958fab | ||
|
|
7091fb9448 | ||
|
|
876cdcf26a | ||
|
|
36eca970f2 | ||
|
|
21232e09a4 | ||
|
|
2d9d373c7f | ||
|
|
23387d6625 | ||
|
|
6137c64444 | ||
|
|
43a830f0af | ||
|
|
ff331c040a | ||
|
|
3532e187fd | ||
|
|
6467ba7739 | ||
|
|
8de3b2c0d3 | ||
|
|
deeb022e0b | ||
|
|
9e0e28dc45 | ||
|
|
c99ac0a264 | ||
|
|
991fafb30e | ||
|
|
5cf5d4b4c4 | ||
|
|
38e42f9a95 | ||
|
|
9b7689993f | ||
|
|
0e3eddcb77 | ||
|
|
3f829ef3b9 | ||
|
|
de8d93ba73 | ||
|
|
dad9f4b87d | ||
|
|
0f538e2606 | ||
|
|
08fa6a9815 | ||
|
|
a7cf4027ea | ||
|
|
646c7ecceb | ||
|
|
3cbbe3d3c2 | ||
|
|
0af26dd353 | ||
|
|
159e366122 | ||
|
|
b9081c26ba | ||
|
|
9933c6ba59 | ||
|
|
eb0642f569 | ||
|
|
1cce35a5a5 | ||
|
|
aeb71e089a | ||
|
|
b962efeca3 | ||
|
|
eb6c350e72 | ||
|
|
d496d41e7e | ||
|
|
19aa4f4acc | ||
|
|
19350e3846 | ||
|
|
741b524d71 | ||
|
|
84288112fc | ||
|
|
7c537cd787 | ||
|
|
c56977cbc1 | ||
|
|
2afa2cd9ab | ||
|
|
442d0da5c1 | ||
|
|
db6bdf36af | ||
|
|
b246328dcf | ||
|
|
a27ea35edd | ||
|
|
a7c4aea9ff | ||
|
|
1ba870a655 | ||
|
|
5bc3cf56fd | ||
|
|
3c4cf2862b | ||
|
|
af69a7a01f | ||
|
|
b9f7a501f5 | ||
|
|
322a085b70 | ||
|
|
6c4dc34441 | ||
|
|
efa287b786 | ||
|
|
23e1c6128b | ||
|
|
bc71a2619a | ||
|
|
4f3510c47c | ||
|
|
2adc20f07f | ||
|
|
b6ade7ce19 | ||
|
|
cabed9587b | ||
|
|
0ce01410a1 | ||
|
|
d02819db13 | ||
|
|
46bae9ed74 | ||
|
|
693ff3398e | ||
|
|
567216f41f | ||
|
|
1ef0791bc6 |
@@ -56,11 +56,11 @@ include(cmake/options.cmake)
|
||||
|
||||
if (NOT DESKTOP_APP_USE_PACKAGED)
|
||||
if (WIN32)
|
||||
set(qt_version 5.15.7)
|
||||
set(qt_version 5.15.8)
|
||||
elseif (APPLE)
|
||||
set(qt_version 6.3.2)
|
||||
else()
|
||||
set(qt_version 6.4.1)
|
||||
set(qt_version 6.4.2)
|
||||
endif()
|
||||
endif()
|
||||
include(cmake/external/qt/package.cmake)
|
||||
|
||||
2
LEGAL
2
LEGAL
@@ -1,7 +1,7 @@
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
Copyright (c) 2014-2022 The Telegram Desktop Authors.
|
||||
Copyright (c) 2014-2023 The Telegram Desktop Authors.
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
||||
@@ -841,8 +841,6 @@ PRIVATE
|
||||
info/profile/info_profile_cover.h
|
||||
info/profile/info_profile_emoji_status_panel.cpp
|
||||
info/profile/info_profile_emoji_status_panel.h
|
||||
info/profile/info_profile_icon.cpp
|
||||
info/profile/info_profile_icon.h
|
||||
info/profile/info_profile_inner_widget.cpp
|
||||
info/profile/info_profile_inner_widget.h
|
||||
info/profile/info_profile_members.cpp
|
||||
@@ -1139,7 +1137,6 @@ PRIVATE
|
||||
platform/platform_integration.h
|
||||
platform/platform_main_window.h
|
||||
platform/platform_notifications_manager.h
|
||||
platform/platform_specific.cpp
|
||||
platform/platform_specific.h
|
||||
platform/platform_tray.h
|
||||
platform/platform_window_title.h
|
||||
@@ -1201,6 +1198,8 @@ PRIVATE
|
||||
settings/settings_privacy_controllers.h
|
||||
settings/settings_privacy_security.cpp
|
||||
settings/settings_privacy_security.h
|
||||
settings/settings_scale_preview.cpp
|
||||
settings/settings_scale_preview.h
|
||||
settings/settings_type.h
|
||||
storage/details/storage_file_utilities.cpp
|
||||
storage/details/storage_file_utilities.h
|
||||
|
||||
BIN
Telegram/Resources/animations/discussion.tgs
Normal file
BIN
Telegram/Resources/animations/discussion.tgs
Normal file
Binary file not shown.
@@ -755,7 +755,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_download_path" = "Download path";
|
||||
"lng_download_path_temp" = "Temp folder";
|
||||
"lng_download_path_default" = "Default folder";
|
||||
"lng_download_path_unset" = "Unset";
|
||||
"lng_download_path_clear" = "Clear all";
|
||||
"lng_download_path_header" = "Choose download path";
|
||||
"lng_download_path_default_radio" = "Telegram folder in system «Downloads»";
|
||||
@@ -763,7 +762,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_download_path_dir_radio" = "Custom folder, cleared only manually";
|
||||
"lng_download_path_choose" = "Choose download path";
|
||||
"lng_sure_clear_downloads" = "Do you want to remove all downloaded files from temp folder? It is done automatically on logout or program uninstall.";
|
||||
"lng_download_path_failed" = "File download could not be started.\n\nThis might be because the download location you've selected is invalid. Try changing the \"Download path\" in Settings.";
|
||||
"lng_download_path_failed" = "File download could not be started.\n\nThe default download location will be used now. You can always change it in Settings > Advanced > Download Path.\n\nPlease try once again.";
|
||||
"lng_download_path_settings" = "Settings";
|
||||
"lng_download_finish_failed" = "File download could not be finished.\n\nWould you like to try again?";
|
||||
"lng_download_path_clearing" = "Clearing...";
|
||||
@@ -1332,8 +1331,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_manage_discussion_group_add" = "Add a group";
|
||||
"lng_manage_linked_channel" = "Linked channel";
|
||||
"lng_manage_linked_channel_restore" = "Restore linked channel";
|
||||
"lng_manage_discussion_group_about" = "Select a group chat for discussion that will be displayed in your channel.";
|
||||
"lng_manage_discussion_group_about_chosen" = "A link to {group} is shown to all subscribers in the bottom panel.";
|
||||
"lng_manage_discussion_group_about" = "Select a group chat that will host comments from your channel.";
|
||||
"lng_manage_discussion_group_about_chosen" = "{group} is selected as the group that hosts comments for your channel.";
|
||||
"lng_manage_discussion_group_create" = "Create a new group";
|
||||
"lng_manage_discussion_group_unlink" = "Unlink group";
|
||||
"lng_manage_discussion_group_posted" = "Everything you post in the channel is forwarded to this group.";
|
||||
@@ -1529,6 +1528,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_action_ttl_changed_you" = "You set messages to auto-delete in {duration}";
|
||||
"lng_action_ttl_changed_channel" = "New messages will auto-delete in {duration}";
|
||||
"lng_action_ttl_global" = "{from} uses a self-destruct timer for all chats. All new messages in this chat will be automatically deleted after {duration} they've been sent.";
|
||||
"lng_action_ttl_global_me" = "You set a self-destruct timer for all chats. All new messages in this chat will be automatically deleted after {duration} they’ve been sent.";
|
||||
"lng_action_ttl_removed" = "{from} has set messages not to auto-delete";
|
||||
"lng_action_ttl_removed_you" = "You disabled the auto-delete timer";
|
||||
"lng_action_ttl_removed_channel" = "New messages will not auto-delete";
|
||||
@@ -2498,6 +2498,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_mediaview_channel_photo" = "Channel Photo";
|
||||
"lng_mediaview_profile_photo" = "Profile Photo";
|
||||
"lng_mediaview_profile_public_photo" = "Public Photo";
|
||||
"lng_mediaview_profile_photo_by_you" = "Photo set by you";
|
||||
"lng_mediaview_file_n_of_amount" = "{file} {n} of {amount}";
|
||||
"lng_mediaview_n_of_amount" = "Photo {n} of {amount}";
|
||||
"lng_mediaview_doc_image" = "File";
|
||||
|
||||
@@ -9,5 +9,6 @@
|
||||
<file alias="cloud_password/hint.tgs">../../animations/cloud_password/hint.tgs</file>
|
||||
<file alias="cloud_password/email.tgs">../../animations/cloud_password/email.tgs</file>
|
||||
<file alias="ttl.tgs">../../animations/ttl.tgs</file>
|
||||
<file alias="discussion.tgs">../../animations/discussion.tgs</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="4.4.3.0" />
|
||||
Version="4.5.6.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 4,4,3,0
|
||||
PRODUCTVERSION 4,4,3,0
|
||||
FILEVERSION 4,5,6,0
|
||||
PRODUCTVERSION 4,5,6,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "4.4.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
|
||||
VALUE "FileVersion", "4.5.6.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "4.4.3.0"
|
||||
VALUE "ProductVersion", "4.5.6.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 4,4,3,0
|
||||
PRODUCTVERSION 4,4,3,0
|
||||
FILEVERSION 4,5,6,0
|
||||
PRODUCTVERSION 4,5,6,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", "4.4.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
|
||||
VALUE "FileVersion", "4.5.6.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "4.4.3.0"
|
||||
VALUE "ProductVersion", "4.5.6.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -148,6 +148,7 @@ void EditMessageWithUploadedMedia(
|
||||
MTPInputMedia media) {
|
||||
const auto done = [=](Fn<void()> applyUpdates) {
|
||||
if (item) {
|
||||
item->removeFromSharedMediaIndex();
|
||||
item->clearSavedMedia();
|
||||
item->setIsLocalUpdateMedia(true);
|
||||
applyUpdates();
|
||||
|
||||
@@ -36,7 +36,7 @@ bool UnreadThings::trackMentions(Data::Thread *thread) const {
|
||||
|
||||
bool UnreadThings::trackReactions(Data::Thread *thread) const {
|
||||
const auto peer = thread ? thread->peer().get() : nullptr;
|
||||
return peer && (peer->isChat() || peer->isMegagroup());
|
||||
return peer && (peer->isUser() || peer->isChat() || peer->isMegagroup());
|
||||
}
|
||||
|
||||
void UnreadThings::preloadEnough(Data::Thread *thread) {
|
||||
|
||||
@@ -2137,7 +2137,6 @@ void ApiWrap::saveDraftsToCloud() {
|
||||
if (const auto cloudDraft = history->cloudDraft(topicRootId)) {
|
||||
if (cloudDraft->saveRequestId == requestId) {
|
||||
history->clearCloudDraft(topicRootId);
|
||||
history->applyCloudDraft(topicRootId);
|
||||
}
|
||||
}
|
||||
const auto i = _draftsSaveRequestIds.find(weak);
|
||||
|
||||
@@ -147,7 +147,9 @@ void DownloadPathBox::setPathText(const QString &text) {
|
||||
DownloadPathBox::Directory DownloadPathBox::typeFromPath(
|
||||
const QString &path) {
|
||||
if (path.isEmpty()) {
|
||||
return Directory::Downloads;
|
||||
return Core::App().canReadDefaultDownloadPath(true)
|
||||
? Directory::Downloads
|
||||
: Directory::Temp;
|
||||
} else if (path == FileDialog::Tmp()) {
|
||||
return Directory::Temp;
|
||||
}
|
||||
|
||||
@@ -451,7 +451,8 @@ void EditCaptionBox::setupPhotoEditorEventHandler() {
|
||||
&file.information->media);
|
||||
|
||||
image->modifications = mods;
|
||||
Storage::UpdateImageDetails(file, previewWidth);
|
||||
const auto sideLimit = PhotoSideLimit();
|
||||
Storage::UpdateImageDetails(file, previewWidth, sideLimit);
|
||||
rebuildPreview();
|
||||
};
|
||||
const auto fileImage = std::make_shared<Image>(*large);
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lang/lang_keys.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "settings/settings_common.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
@@ -25,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_info.h"
|
||||
#include "styles/style_settings.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -208,88 +210,24 @@ void Controller::choose(not_null<ChatData*> chat) {
|
||||
Ui::LayerOption::KeepOther);
|
||||
}
|
||||
|
||||
object_ptr<Ui::RpWidget> SetupAbout(
|
||||
not_null<QWidget*> parent,
|
||||
[[nodiscard]] rpl::producer<TextWithEntities> About(
|
||||
not_null<ChannelData*> channel,
|
||||
ChannelData *chat) {
|
||||
auto about = object_ptr<Ui::FlatLabel>(
|
||||
parent,
|
||||
QString(),
|
||||
st::linkedChatAbout);
|
||||
about->setMarkedText([&] {
|
||||
if (!channel->isBroadcast()) {
|
||||
return tr::lng_manage_linked_channel_about(
|
||||
tr::now,
|
||||
lt_channel,
|
||||
Ui::Text::Bold(chat->name()),
|
||||
Ui::Text::WithEntities);
|
||||
} else if (chat != nullptr) {
|
||||
return tr::lng_manage_discussion_group_about_chosen(
|
||||
tr::now,
|
||||
lt_group,
|
||||
Ui::Text::Bold(chat->name()),
|
||||
Ui::Text::WithEntities);
|
||||
}
|
||||
return tr::lng_manage_discussion_group_about(
|
||||
tr::now,
|
||||
if (!channel->isBroadcast()) {
|
||||
return tr::lng_manage_linked_channel_about(
|
||||
lt_channel,
|
||||
rpl::single(Ui::Text::Bold(chat->name())),
|
||||
Ui::Text::WithEntities);
|
||||
}());
|
||||
return about;
|
||||
} else if (chat != nullptr) {
|
||||
return tr::lng_manage_discussion_group_about_chosen(
|
||||
lt_group,
|
||||
rpl::single(Ui::Text::Bold(chat->name())),
|
||||
Ui::Text::WithEntities);
|
||||
}
|
||||
return tr::lng_manage_discussion_group_about(Ui::Text::WithEntities);
|
||||
}
|
||||
|
||||
object_ptr<Ui::RpWidget> SetupFooter(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<ChannelData*> channel) {
|
||||
return object_ptr<Ui::FlatLabel>(
|
||||
parent,
|
||||
(channel->isBroadcast()
|
||||
? tr::lng_manage_discussion_group_posted
|
||||
: tr::lng_manage_linked_channel_posted)(),
|
||||
st::linkedChatAbout);
|
||||
}
|
||||
|
||||
object_ptr<Ui::RpWidget> SetupCreateGroup(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<ChannelData*> channel,
|
||||
Fn<void(ChannelData*)> callback) {
|
||||
Expects(channel->isBroadcast());
|
||||
|
||||
auto result = object_ptr<Ui::SettingsButton>(
|
||||
parent,
|
||||
tr::lng_manage_discussion_group_create(
|
||||
) | Ui::Text::ToUpper(),
|
||||
st::infoCreateLinkedChatButton);
|
||||
result->addClickHandler([=] {
|
||||
const auto guarded = crl::guard(parent, callback);
|
||||
Window::Show(navigation).showBox(
|
||||
Box<GroupInfoBox>(
|
||||
navigation,
|
||||
GroupInfoBox::Type::Megagroup,
|
||||
channel->name() + " Chat",
|
||||
guarded),
|
||||
Ui::LayerOption::KeepOther);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
object_ptr<Ui::RpWidget> SetupUnlink(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<ChannelData*> channel,
|
||||
Fn<void(ChannelData*)> callback) {
|
||||
auto result = object_ptr<Ui::SettingsButton>(
|
||||
parent,
|
||||
(channel->isBroadcast()
|
||||
? tr::lng_manage_discussion_group_unlink
|
||||
: tr::lng_manage_linked_channel_unlink)() | Ui::Text::ToUpper(),
|
||||
st::infoUnlinkChatButton);
|
||||
result->addClickHandler([=] {
|
||||
callback(nullptr);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
object_ptr<Ui::BoxContent> EditLinkedChatBox(
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> EditLinkedChatBox(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<ChannelData*> channel,
|
||||
ChannelData *chat,
|
||||
@@ -298,27 +236,77 @@ object_ptr<Ui::BoxContent> EditLinkedChatBox(
|
||||
Fn<void(ChannelData*)> callback) {
|
||||
Expects((channel->isBroadcast() && canEdit) || (chat != nullptr));
|
||||
|
||||
const auto init = [=](not_null<PeerListBox*> box) {
|
||||
class ListBox final : public PeerListBox {
|
||||
public:
|
||||
ListBox(
|
||||
QWidget *parent,
|
||||
std::unique_ptr<PeerListController> controller,
|
||||
Fn<void(not_null<ListBox*>)> init)
|
||||
: PeerListBox(
|
||||
parent,
|
||||
std::move(controller),
|
||||
[=](not_null<PeerListBox*>) { init(this); }) {
|
||||
}
|
||||
|
||||
void showFinished() override {
|
||||
_showFinished.fire({});
|
||||
}
|
||||
|
||||
rpl::producer<> showFinishes() const {
|
||||
return _showFinished.events();
|
||||
}
|
||||
|
||||
private:
|
||||
rpl::event_stream<> _showFinished;
|
||||
|
||||
};
|
||||
|
||||
const auto init = [=](not_null<ListBox*> box) {
|
||||
auto above = object_ptr<Ui::VerticalLayout>(box);
|
||||
above->add(
|
||||
SetupAbout(above, channel, chat),
|
||||
st::linkedChatAboutPadding);
|
||||
Settings::AddDividerTextWithLottie(
|
||||
above,
|
||||
box->showFinishes(),
|
||||
About(channel, chat),
|
||||
u"discussion"_q);
|
||||
if (!chat) {
|
||||
above->add(SetupCreateGroup(
|
||||
Assert(channel->isBroadcast());
|
||||
|
||||
Settings::AddSkip(above);
|
||||
Settings::AddButton(
|
||||
above,
|
||||
navigation,
|
||||
channel,
|
||||
callback));
|
||||
tr::lng_manage_discussion_group_create(),
|
||||
st::infoCreateLinkedChatButton,
|
||||
{ &st::settingsIconChat, Settings::kIconLightBlue }
|
||||
)->addClickHandler([=, parent = above.data()] {
|
||||
const auto guarded = crl::guard(parent, callback);
|
||||
Window::Show(navigation).showBox(
|
||||
Box<GroupInfoBox>(
|
||||
navigation,
|
||||
GroupInfoBox::Type::Megagroup,
|
||||
channel->name() + " Chat",
|
||||
guarded),
|
||||
Ui::LayerOption::KeepOther);
|
||||
});
|
||||
}
|
||||
box->peerListSetAboveWidget(std::move(above));
|
||||
|
||||
auto below = object_ptr<Ui::VerticalLayout>(box);
|
||||
if (chat && canEdit) {
|
||||
below->add(SetupUnlink(below, channel, callback));
|
||||
Settings::AddButton(
|
||||
below,
|
||||
(channel->isBroadcast()
|
||||
? tr::lng_manage_discussion_group_unlink
|
||||
: tr::lng_manage_linked_channel_unlink)(),
|
||||
st::infoUnlinkChatButton,
|
||||
{ &st::settingsIconMinus, Settings::kIconRed }
|
||||
)->addClickHandler([=] { callback(nullptr); });
|
||||
Settings::AddSkip(below);
|
||||
}
|
||||
below->add(
|
||||
SetupFooter(below, channel),
|
||||
st::linkedChatAboutPadding);
|
||||
Settings::AddDividerText(
|
||||
below,
|
||||
(channel->isBroadcast()
|
||||
? tr::lng_manage_discussion_group_posted
|
||||
: tr::lng_manage_linked_channel_posted)());
|
||||
box->peerListSetBelowWidget(std::move(below));
|
||||
|
||||
box->setTitle(channel->isBroadcast()
|
||||
@@ -339,7 +327,7 @@ object_ptr<Ui::BoxContent> EditLinkedChatBox(
|
||||
std::move(chats),
|
||||
std::move(callback),
|
||||
std::move(showHistoryCallback));
|
||||
return Box<PeerListBox>(std::move(controller), init);
|
||||
return Box<ListBox>(std::move(controller), init);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -17,14 +17,14 @@ namespace Window {
|
||||
class SessionNavigation;
|
||||
} // namespace Window
|
||||
|
||||
object_ptr<Ui::BoxContent> EditLinkedChatBox(
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> EditLinkedChatBox(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<ChannelData*> chat,
|
||||
bool canEdit,
|
||||
Fn<void(ChannelData*)> callback);
|
||||
|
||||
object_ptr<Ui::BoxContent> EditLinkedChatBox(
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> EditLinkedChatBox(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<ChannelData*> channel,
|
||||
std::vector<not_null<PeerData*>> &&chats,
|
||||
|
||||
@@ -1913,7 +1913,7 @@ void Controller::deleteChannel() {
|
||||
const auto session = &_peer->session();
|
||||
|
||||
_navigation->parentController()->hideLayer();
|
||||
Core::App().closeChatFromWindows(_peer);
|
||||
Core::App().closeChatFromWindows(channel);
|
||||
if (chat) {
|
||||
session->api().deleteConversation(chat, false);
|
||||
}
|
||||
|
||||
@@ -348,8 +348,9 @@ void SendFilesBox::enqueueNextPrepare() {
|
||||
_list.filesToProcess.pop_front();
|
||||
const auto weak = Ui::MakeWeak(this);
|
||||
_preparing = true;
|
||||
crl::async([weak, file = std::move(file)]() mutable {
|
||||
Storage::PrepareDetails(file, st::sendMediaPreviewSize);
|
||||
const auto sideLimit = PhotoSideLimit(); // Get on main thread.
|
||||
crl::async([weak, sideLimit, file = std::move(file)]() mutable {
|
||||
Storage::PrepareDetails(file, st::sendMediaPreviewSize, sideLimit);
|
||||
crl::on_main([weak, file = std::move(file)]() mutable {
|
||||
if (weak) {
|
||||
weak->addPreparedAsyncFile(std::move(file));
|
||||
|
||||
@@ -252,15 +252,6 @@ void Application::run() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (KSandbox::isInside()) {
|
||||
const auto path = settings().downloadPath();
|
||||
if (!path.isEmpty()
|
||||
&& path != FileDialog::Tmp()
|
||||
&& !base::CanReadDirectory(path)) {
|
||||
settings().setDownloadPath(QString());
|
||||
}
|
||||
}
|
||||
|
||||
_translator = std::make_unique<Lang::Translator>();
|
||||
QCoreApplication::instance()->installTranslator(_translator.get());
|
||||
|
||||
@@ -606,8 +597,7 @@ bool Application::canReadDefaultDownloadPath(bool always) const {
|
||||
}
|
||||
|
||||
bool Application::canSaveFileWithoutAskingForPath() const {
|
||||
return !Core::App().settings().askDownloadPath()
|
||||
&& canReadDefaultDownloadPath();
|
||||
return !Core::App().settings().askDownloadPath();
|
||||
}
|
||||
|
||||
MTP::Config &Application::fallbackProductionConfig() const {
|
||||
|
||||
@@ -22,61 +22,6 @@ namespace {
|
||||
|
||||
std::map<int, const char*> BetaLogs() {
|
||||
return {
|
||||
{
|
||||
4000003,
|
||||
"- Animated emoji for messages.\n"
|
||||
|
||||
"- Premium: Privacy settings for voice messages.\n"
|
||||
|
||||
"- Premium: Gifting Telegram Premium "
|
||||
"to any user from their profile page.\n"
|
||||
},
|
||||
{
|
||||
4000004,
|
||||
"- Allow sending animated emoji to Saved Messages "
|
||||
"even without Telegram Premium.\n"
|
||||
|
||||
"- Premium: Suggest animated emoji by regular emoji "
|
||||
"(can be disabled in Settings).\n"
|
||||
|
||||
"- Premium: Show all suggested premium stickers "
|
||||
"in a special section of the stickers panel.\n"
|
||||
|
||||
"- Premium: Allow hiding premium stickers special section "
|
||||
"of the stickers panel.\n"
|
||||
|
||||
"- Fix a memory leak in RTMP livestreams.\n"
|
||||
|
||||
"- Fix some bot webview bugs on macOS.\n"
|
||||
|
||||
"- Fix forwarding of voice messages.\n"
|
||||
},
|
||||
{
|
||||
4001002,
|
||||
"- New reaction selector above the right click menu.\n"
|
||||
|
||||
"- Premium: Set any custom emoji reactions in private chats.\n"
|
||||
|
||||
"- Premium: Set any custom emoji as your profile status.\n"
|
||||
|
||||
"- Insert or copy custom emoji from pack preview.\n"
|
||||
},
|
||||
{
|
||||
4002001,
|
||||
"- Improve scaling / cropping for photos / video files.\n"
|
||||
|
||||
"- Improve touch support in channel comments.\n"
|
||||
|
||||
"- Nice animation for spoilers.\n"
|
||||
},
|
||||
{
|
||||
4002002,
|
||||
"- Fix crash in spoiler revealing in media captions.\n"
|
||||
|
||||
"- Fix spoiler revealing in media viewer captions.\n"
|
||||
|
||||
"- Fix crash in folder editing on Linux.\n"
|
||||
},
|
||||
{
|
||||
4004002,
|
||||
"- Send photos and video files hidden by a spoiler effect.\n"
|
||||
@@ -93,6 +38,32 @@ std::map<int, const char*> BetaLogs() {
|
||||
"- Fix a crash in own profile photo updating.\n"
|
||||
|
||||
"- Bug fixes and other minor improvements.\n"
|
||||
},
|
||||
{
|
||||
4005004,
|
||||
"- Allow wide range of interface scale options.\n"
|
||||
|
||||
"- Show opened chat name in the window title.\n"
|
||||
|
||||
"- Bug fixes and other minor improvements.\n"
|
||||
|
||||
"- Fix updating on macOS older than 10.14.\n"
|
||||
},
|
||||
{
|
||||
4005006,
|
||||
"- Try enabling non-fractional scale "
|
||||
"High DPI support on Windows and Linux.\n"
|
||||
|
||||
"- Experimental setting for fractional scale "
|
||||
"High DPI support on Windows and Linux.\n"
|
||||
|
||||
"- Fix navigation to bottom problems in groups you didn't join.\n"
|
||||
|
||||
"- Fix a crash in chat export settings changes.\n"
|
||||
|
||||
"- Fix a crash in sending some of JPEG images.\n"
|
||||
|
||||
"- Fix CJK fonts on Windows.\n"
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -93,7 +93,8 @@ QString HiddenUrlClickHandler::copyToClipboardContextItemText() const {
|
||||
}
|
||||
|
||||
QString HiddenUrlClickHandler::dragText() const {
|
||||
return HiddenUrlClickHandler::copyToClipboardText();
|
||||
const auto result = HiddenUrlClickHandler::copyToClipboardText();
|
||||
return result.startsWith(u"internal:"_q) ? QString() : result;
|
||||
}
|
||||
|
||||
void HiddenUrlClickHandler::Open(QString url, QVariant context) {
|
||||
|
||||
@@ -48,7 +48,32 @@ PreLaunchWindow::PreLaunchWindow(QString title) {
|
||||
int paddingVertical = (_size / 2);
|
||||
int paddingHorizontal = _size;
|
||||
int borderRadius = (_size / 5);
|
||||
setStyleSheet(u"QPushButton { padding: %1px %2px; background-color: #ffffff; border-radius: %3px; }\nQPushButton#confirm:hover, QPushButton#cancel:hover { background-color: #e3f1fa; color: #2f9fea; }\nQPushButton#confirm { color: #2f9fea; }\nQPushButton#cancel { color: #aeaeae; }\nQLineEdit { border: 1px solid #e0e0e0; padding: 5px; }\nQLineEdit:focus { border: 2px solid #37a1de; padding: 4px; }"_q.arg(paddingVertical).arg(paddingHorizontal).arg(borderRadius));
|
||||
setStyleSheet(uR"(
|
||||
QPushButton {
|
||||
padding: %1px %2px;
|
||||
background-color: #ffffff;
|
||||
border-radius: %3px;
|
||||
}
|
||||
QPushButton#confirm:hover,
|
||||
QPushButton#cancel:hover {
|
||||
background-color: #e3f1fa;
|
||||
color: #2f9fea;
|
||||
}
|
||||
QPushButton#confirm {
|
||||
color: #2f9fea;
|
||||
}
|
||||
QPushButton#cancel {
|
||||
color: #aeaeae;
|
||||
}
|
||||
QLineEdit {
|
||||
border: 1px solid #e0e0e0;
|
||||
padding: 5px;
|
||||
}
|
||||
QLineEdit:focus {
|
||||
border: 2px solid #37a1de;
|
||||
padding: 4px;
|
||||
}
|
||||
)"_q.arg(paddingVertical).arg(paddingHorizontal).arg(borderRadius));
|
||||
if (!PreLaunchWindowInstance) {
|
||||
PreLaunchWindowInstance = this;
|
||||
}
|
||||
@@ -57,7 +82,7 @@ PreLaunchWindow::PreLaunchWindow(QString title) {
|
||||
void PreLaunchWindow::activate() {
|
||||
setWindowState(windowState() & ~Qt::WindowMinimized);
|
||||
setVisible(true);
|
||||
psActivateProcess();
|
||||
Platform::ActivateThisProcess();
|
||||
raise();
|
||||
activateWindow();
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "boxes/abstract_box.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/platform/base_platform_file_utilities.h"
|
||||
#include "platform/platform_file_utilities.h"
|
||||
@@ -75,7 +76,7 @@ QString filedialogDefaultName(
|
||||
QString base;
|
||||
if (fileTime) {
|
||||
const auto date = base::unixtime::parse(fileTime);
|
||||
base = prefix + QLocale().toString(date, "_yyyy-MM-dd_HH-mm-ss");
|
||||
base = prefix + date.toString("_yyyy-MM-dd_HH-mm-ss");
|
||||
} else {
|
||||
struct tm tm;
|
||||
time_t t = time(NULL);
|
||||
@@ -171,31 +172,14 @@ QString DefaultDownloadPathFolder(not_null<Main::Session*> session) {
|
||||
}
|
||||
|
||||
QString DefaultDownloadPath(not_null<Main::Session*> session) {
|
||||
const auto realDefaultPath = QStandardPaths::writableLocation(
|
||||
if (!Core::App().canReadDefaultDownloadPath()) {
|
||||
return session->local().tempDirectory();
|
||||
}
|
||||
return QStandardPaths::writableLocation(
|
||||
QStandardPaths::DownloadLocation)
|
||||
+ '/'
|
||||
+ DefaultDownloadPathFolder(session)
|
||||
+ '/';
|
||||
if (!Core::App().canReadDefaultDownloadPath()) {
|
||||
QStringList files;
|
||||
QByteArray remoteContent;
|
||||
const auto success = Platform::FileDialog::Get(
|
||||
nullptr,
|
||||
files,
|
||||
remoteContent,
|
||||
tr::lng_download_path_choose(tr::now),
|
||||
QString(),
|
||||
FileDialog::internal::Type::ReadFolder,
|
||||
realDefaultPath);
|
||||
if (success && !files.isEmpty() && !files[0].isEmpty()) {
|
||||
const auto result = files[0].endsWith('/') ? files[0] : (files[0] + '/');
|
||||
Core::App().settings().setDownloadPath(result);
|
||||
Core::App().saveSettings();
|
||||
return result;
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
return realDefaultPath;
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "platform/platform_launcher.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "base/options.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/platform/base_platform_file_utilities.h"
|
||||
#include "ui/main_queue_processor.h"
|
||||
@@ -272,8 +273,18 @@ bool CheckPortableVersionFolder() {
|
||||
return true;
|
||||
}
|
||||
|
||||
base::options::toggle OptionFractionalScalingEnabled({
|
||||
.id = kOptionFractionalScalingEnabled,
|
||||
.name = "Enable precise High DPI scaling",
|
||||
.description = "Follow system interface scale settings exactly.",
|
||||
.scope = base::options::windows | base::options::linux,
|
||||
.restartRequired = true,
|
||||
});
|
||||
|
||||
} // namespace
|
||||
|
||||
const char kOptionFractionalScalingEnabled[] = "fractional-scaling-enabled";
|
||||
|
||||
std::unique_ptr<Launcher> Launcher::Create(int argc, char *argv[]) {
|
||||
return std::make_unique<Platform::Launcher>(argc, argv);
|
||||
}
|
||||
@@ -294,9 +305,6 @@ void Launcher::init() {
|
||||
initQtMessageLogging();
|
||||
|
||||
QApplication::setApplicationName(u"TelegramDesktop"_q);
|
||||
QApplication::setAttribute(Qt::AA_DisableHighDpiScaling, true);
|
||||
QApplication::setHighDpiScaleFactorRoundingPolicy(
|
||||
Qt::HighDpiScaleFactorRoundingPolicy::Floor);
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
// fallback session management is useless for tdesktop since it doesn't have
|
||||
@@ -311,6 +319,17 @@ void Launcher::init() {
|
||||
initHook();
|
||||
}
|
||||
|
||||
void Launcher::initHighDpi() {
|
||||
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true);
|
||||
if (OptionFractionalScalingEnabled.value()) {
|
||||
QApplication::setHighDpiScaleFactorRoundingPolicy(
|
||||
Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
|
||||
} else {
|
||||
QApplication::setHighDpiScaleFactorRoundingPolicy(
|
||||
Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor);
|
||||
}
|
||||
}
|
||||
|
||||
int Launcher::exec() {
|
||||
init();
|
||||
|
||||
@@ -324,6 +343,9 @@ int Launcher::exec() {
|
||||
Logs::start(this);
|
||||
base::options::init(cWorkingDir() + "tdata/experimental_options.json");
|
||||
|
||||
// Must be called after options are inited.
|
||||
initHighDpi();
|
||||
|
||||
if (Logs::DebugEnabled()) {
|
||||
const auto openalLogPath = QDir::toNativeSeparators(
|
||||
cWorkingDir() + u"DebugLogs/last_openal_log.txt"_q);
|
||||
|
||||
@@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace Core {
|
||||
|
||||
extern const char kOptionFractionalScalingEnabled[];
|
||||
|
||||
class Launcher {
|
||||
public:
|
||||
Launcher(int argc, char *argv[]);
|
||||
@@ -52,6 +54,7 @@ private:
|
||||
void init();
|
||||
virtual void initHook() {
|
||||
}
|
||||
virtual void initHighDpi();
|
||||
|
||||
virtual bool launchUpdater(UpdaterLaunch action) = 0;
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "settings/settings_premium.h"
|
||||
#include "mainwidget.h"
|
||||
#include "main/main_account.h"
|
||||
#include "main/main_domain.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_session_settings.h"
|
||||
#include "inline_bots/bot_attach_web_view.h"
|
||||
@@ -798,12 +799,13 @@ bool ResolveLoginCode(
|
||||
const Match &match,
|
||||
const QVariant &context) {
|
||||
const auto loginCode = match->captured(2);
|
||||
if (loginCode.isEmpty()) {
|
||||
const auto &domain = Core::App().domain();
|
||||
if (loginCode.isEmpty() || (!controller && !domain.started())) {
|
||||
return false;
|
||||
};
|
||||
(controller
|
||||
? controller->session().account()
|
||||
: Core::App().activeAccount()).handleLoginCode(loginCode);
|
||||
: domain.active()).handleLoginCode(loginCode);
|
||||
if (controller) {
|
||||
controller->window().activate();
|
||||
} else if (const auto window = Core::App().activeWindow()) {
|
||||
@@ -869,7 +871,7 @@ const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
|
||||
ResolvePrivatePost
|
||||
},
|
||||
{
|
||||
u"^settings(/language|/devices|/folders|/privacy|/themes|/change_number|/auto_delete|/information)?$"_q,
|
||||
u"^settings(/language|/devices|/folders|/privacy|/themes|/change_number|/auto_delete|/information|/edit_profile)?$"_q,
|
||||
ResolveSettings
|
||||
},
|
||||
{
|
||||
|
||||
@@ -25,19 +25,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/invoke_queued.h"
|
||||
#include "base/qthelp_url.h"
|
||||
#include "base/qthelp_regex.h"
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "ui/effects/animations.h"
|
||||
|
||||
#include <QtCore/QLockFile>
|
||||
#include <QtGui/QSessionManager>
|
||||
#include <QtGui/QScreen>
|
||||
#include <QtGui/qpa/qplatformscreen.h>
|
||||
|
||||
namespace Core {
|
||||
namespace {
|
||||
|
||||
constexpr auto kEmptyPidForCommandResponse = 0ULL;
|
||||
|
||||
QChar _toHex(ushort v) {
|
||||
v = v & 0x000F;
|
||||
return QChar::fromLatin1((v >= 10) ? ('a' + (v - 10)) : ('0' + v));
|
||||
@@ -134,7 +132,7 @@ int Sandbox::start() {
|
||||
[=] { socketDisconnected(); });
|
||||
connect(
|
||||
&_localSocket,
|
||||
base::QLocalSocket_error,
|
||||
&QLocalSocket::errorOccurred,
|
||||
[=](QLocalSocket::LocalSocketError error) { socketError(error); });
|
||||
connect(
|
||||
&_localSocket,
|
||||
@@ -210,38 +208,52 @@ void Sandbox::launchApplication() {
|
||||
}
|
||||
|
||||
void Sandbox::setupScreenScale() {
|
||||
const auto dpi = Sandbox::primaryScreen()->logicalDotsPerInch();
|
||||
LOG(("Primary screen DPI: %1").arg(dpi));
|
||||
if (dpi <= 108) {
|
||||
cSetScreenScale(100); // 100%: 96 DPI (0-108)
|
||||
} else if (dpi <= 132) {
|
||||
cSetScreenScale(125); // 125%: 120 DPI (108-132)
|
||||
} else if (dpi <= 168) {
|
||||
cSetScreenScale(150); // 150%: 144 DPI (132-168)
|
||||
} else if (dpi <= 216) {
|
||||
cSetScreenScale(200); // 200%: 192 DPI (168-216)
|
||||
} else if (dpi <= 264) {
|
||||
cSetScreenScale(250); // 250%: 240 DPI (216-264)
|
||||
} else {
|
||||
cSetScreenScale(300); // 300%: 288 DPI (264-inf)
|
||||
}
|
||||
|
||||
const auto ratio = devicePixelRatio();
|
||||
if (ratio > 1.) {
|
||||
if (!Platform::IsMac() || (ratio != 2.)) {
|
||||
LOG(("Found non-trivial Device Pixel Ratio: %1").arg(ratio));
|
||||
LOG(("Environmental variables: QT_DEVICE_PIXEL_RATIO='%1'").arg(qEnvironmentVariable("QT_DEVICE_PIXEL_RATIO")));
|
||||
LOG(("Environmental variables: QT_SCALE_FACTOR='%1'").arg(qEnvironmentVariable("QT_SCALE_FACTOR")));
|
||||
LOG(("Environmental variables: QT_AUTO_SCREEN_SCALE_FACTOR='%1'").arg(qEnvironmentVariable("QT_AUTO_SCREEN_SCALE_FACTOR")));
|
||||
LOG(("Environmental variables: QT_SCREEN_SCALE_FACTORS='%1'").arg(qEnvironmentVariable("QT_SCREEN_SCALE_FACTORS")));
|
||||
}
|
||||
style::SetDevicePixelRatio(std::ceil(ratio));
|
||||
if (Platform::IsMac() && ratio == 2.) {
|
||||
cSetScreenScale(110); // 110% for Retina screens by default.
|
||||
} else {
|
||||
cSetScreenScale(style::kScaleDefault);
|
||||
LOG(("Global devicePixelRatio: %1").arg(ratio));
|
||||
const auto logEnv = [](const char *name) {
|
||||
const auto value = qEnvironmentVariable(name);
|
||||
if (!value.isEmpty()) {
|
||||
LOG(("%1: %2").arg(name).arg(value));
|
||||
}
|
||||
};
|
||||
logEnv("QT_DEVICE_PIXEL_RATIO");
|
||||
logEnv("QT_AUTO_SCREEN_SCALE_FACTOR");
|
||||
logEnv("QT_ENABLE_HIGHDPI_SCALING");
|
||||
logEnv("QT_SCALE_FACTOR");
|
||||
logEnv("QT_SCREEN_SCALE_FACTORS");
|
||||
logEnv("QT_SCALE_FACTOR_ROUNDING_POLICY");
|
||||
logEnv("QT_DPI_ADJUSTMENT_POLICY");
|
||||
logEnv("QT_USE_PHYSICAL_DPI");
|
||||
logEnv("QT_FONT_DPI");
|
||||
|
||||
// Like Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor.
|
||||
// Round up for .75 and higher. This favors "small UI" over "large UI".
|
||||
const auto roundedRatio = ((ratio - qFloor(ratio)) < 0.75)
|
||||
? qFloor(ratio)
|
||||
: qCeil(ratio);
|
||||
const auto useRatio = std::clamp(roundedRatio, 1, 3);
|
||||
style::SetDevicePixelRatio(useRatio);
|
||||
|
||||
const auto screen = Sandbox::primaryScreen();
|
||||
const auto dpi = screen->logicalDotsPerInch();
|
||||
const auto basePair = screen->handle()->logicalBaseDpi();
|
||||
const auto base = (basePair.first + basePair.second) * 0.5;
|
||||
const auto screenScaleExact = dpi / base;
|
||||
const auto screenScale = int(base::SafeRound(screenScaleExact * 4)) * 25;
|
||||
LOG(("Primary screen DPI: %1, Base: %2.").arg(dpi).arg(base));
|
||||
LOG(("Computed screen scale: %1").arg(screenScale));
|
||||
if (Platform::IsMac()) {
|
||||
// 110% for Retina screens by default.
|
||||
cSetScreenScale((useRatio == 2) ? 110 : 100);
|
||||
} else {
|
||||
const auto clamped = std::clamp(
|
||||
screenScale * useRatio,
|
||||
50 * useRatio,
|
||||
300);
|
||||
cSetScreenScale(int(base::SafeRound(clamped * 1. / useRatio)));
|
||||
}
|
||||
LOG(("DevicePixelRatio: %1").arg(useRatio));
|
||||
LOG(("ScreenScale: %1").arg(cScreenScale()));
|
||||
}
|
||||
|
||||
Sandbox::~Sandbox() = default;
|
||||
@@ -298,17 +310,21 @@ void Sandbox::socketReading() {
|
||||
return;
|
||||
}
|
||||
_localSocketReadData.append(_localSocket.readAll());
|
||||
if (QRegularExpression("RES:(\\d+);").match(_localSocketReadData).hasMatch()) {
|
||||
uint64 pid = base::StringViewMid(
|
||||
_localSocketReadData,
|
||||
4,
|
||||
_localSocketReadData.length() - 5).toULongLong();
|
||||
if (pid != kEmptyPidForCommandResponse) {
|
||||
psActivateProcess(pid);
|
||||
}
|
||||
LOG(("Show command response received, pid = %1, activating and quitting...").arg(pid));
|
||||
return Quit();
|
||||
const auto m = QRegularExpression(u"RES:(\\d+)_(\\d+);"_q).match(
|
||||
_localSocketReadData);
|
||||
if (!m.hasMatch()) {
|
||||
return;
|
||||
}
|
||||
const auto processId = m.capturedView(1).toULongLong();
|
||||
const auto windowId = m.capturedView(2).toULongLong();
|
||||
if (windowId) {
|
||||
Platform::ActivateOtherProcess(processId, windowId);
|
||||
}
|
||||
LOG(("Show command response received, processId = %1, windowId = %2, "
|
||||
"activating and quitting..."
|
||||
).arg(processId
|
||||
).arg(windowId));
|
||||
return Quit();
|
||||
}
|
||||
|
||||
void Sandbox::socketError(QLocalSocket::LocalSocketError e) {
|
||||
@@ -426,8 +442,9 @@ void Sandbox::readClients() {
|
||||
for (int32 to = cmds.indexOf(QChar(';'), from); to >= from; to = (from < l) ? cmds.indexOf(QChar(';'), from) : -1) {
|
||||
auto cmd = base::StringViewMid(cmds, from, to - from);
|
||||
if (cmd.startsWith(u"CMD:"_q)) {
|
||||
execExternal(cmds.mid(from + 4, to - from - 4));
|
||||
const auto response = u"RES:%1;"_q.arg(QApplication::applicationPid()).toLatin1();
|
||||
const auto processId = QApplication::applicationPid();
|
||||
const auto windowId = execExternal(cmds.mid(from + 4, to - from - 4));
|
||||
const auto response = u"RES:%1_%2;"_q.arg(processId).arg(windowId).toLatin1();
|
||||
i->first->write(response.data(), response.size());
|
||||
} else if (cmd.startsWith(u"SEND:"_q)) {
|
||||
if (cSendPaths().isEmpty()) {
|
||||
@@ -437,14 +454,12 @@ void Sandbox::readClients() {
|
||||
qputenv("XDG_ACTIVATION_TOKEN", _escapeFrom7bit(cmds.mid(from + 21, to - from - 21)).toUtf8());
|
||||
} else if (cmd.startsWith(u"OPEN:"_q)) {
|
||||
startUrl = _escapeFrom7bit(cmds.mid(from + 5, to - from - 5)).mid(0, 8192);
|
||||
auto activateRequired = StartUrlRequiresActivate(startUrl);
|
||||
if (activateRequired) {
|
||||
execExternal("show");
|
||||
}
|
||||
const auto responsePid = activateRequired
|
||||
? QApplication::applicationPid()
|
||||
: kEmptyPidForCommandResponse;
|
||||
const auto response = u"RES:%1;"_q.arg(responsePid).toLatin1();
|
||||
const auto activationRequired = StartUrlRequiresActivate(startUrl);
|
||||
const auto processId = QApplication::applicationPid();
|
||||
const auto windowId = activationRequired
|
||||
? execExternal("show")
|
||||
: 0;
|
||||
const auto response = u"RES:%1_%2;"_q.arg(processId).arg(windowId).toLatin1();
|
||||
i->first->write(response.data(), response.size());
|
||||
} else {
|
||||
LOG(("Sandbox Error: unknown command %1 passed in local socket").arg(cmd.toString()));
|
||||
@@ -643,17 +658,21 @@ void Sandbox::closeApplication() {
|
||||
_updateChecker = nullptr;
|
||||
}
|
||||
|
||||
void Sandbox::execExternal(const QString &cmd) {
|
||||
uint64 Sandbox::execExternal(const QString &cmd) {
|
||||
DEBUG_LOG(("Sandbox Info: executing external command '%1'").arg(cmd));
|
||||
if (cmd == "show") {
|
||||
if (Core::IsAppLaunched() && Core::App().primaryWindow()) {
|
||||
Core::App().primaryWindow()->activate();
|
||||
} else if (PreLaunchWindow::instance()) {
|
||||
PreLaunchWindow::instance()->activate();
|
||||
const auto window = Core::App().primaryWindow();
|
||||
window->activate();
|
||||
return Platform::ActivationWindowId(window->widget());
|
||||
} else if (const auto window = PreLaunchWindow::instance()) {
|
||||
window->activate();
|
||||
return Platform::ActivationWindowId(window);
|
||||
}
|
||||
} else if (cmd == "quit") {
|
||||
Quit();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -95,7 +95,9 @@ private:
|
||||
void singleInstanceChecked();
|
||||
void launchApplication();
|
||||
void setupScreenScale();
|
||||
void execExternal(const QString &cmd);
|
||||
|
||||
// Return window id for activation.
|
||||
uint64 execExternal(const QString &cmd);
|
||||
|
||||
// Single instance application
|
||||
void socketConnected();
|
||||
|
||||
@@ -370,6 +370,9 @@ void Manager::fillDefaults() {
|
||||
set(u"ctrl+3"_q, Command::ChatPinned3);
|
||||
set(u"ctrl+4"_q, Command::ChatPinned4);
|
||||
set(u"ctrl+5"_q, Command::ChatPinned5);
|
||||
set(u"ctrl+6"_q, Command::ChatPinned6);
|
||||
set(u"ctrl+7"_q, Command::ChatPinned7);
|
||||
set(u"ctrl+8"_q, Command::ChatPinned8);
|
||||
|
||||
auto &&folders = ranges::views::zip(
|
||||
kShowFolder,
|
||||
|
||||
@@ -34,6 +34,9 @@ enum class Command {
|
||||
ChatPinned3,
|
||||
ChatPinned4,
|
||||
ChatPinned5,
|
||||
ChatPinned6,
|
||||
ChatPinned7,
|
||||
ChatPinned8,
|
||||
|
||||
ShowAllChats,
|
||||
ShowFolder1,
|
||||
|
||||
@@ -13,7 +13,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/timer.h"
|
||||
#include "base/bytes.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "core/application.h"
|
||||
#include "core/changelogs.h"
|
||||
@@ -664,7 +663,7 @@ void HttpChecker::start() {
|
||||
_reply->connect(_reply, &QNetworkReply::finished, [=] {
|
||||
gotResponse();
|
||||
});
|
||||
_reply->connect(_reply, base::QNetworkReply_error, [=](auto e) {
|
||||
_reply->connect(_reply, &QNetworkReply::errorOccurred, [=](auto e) {
|
||||
gotFailure(e);
|
||||
});
|
||||
}
|
||||
@@ -703,7 +702,7 @@ void HttpChecker::clearSentRequest() {
|
||||
return;
|
||||
}
|
||||
reply->disconnect(reply, &QNetworkReply::finished, nullptr, nullptr);
|
||||
reply->disconnect(reply, base::QNetworkReply_error, nullptr, nullptr);
|
||||
reply->disconnect(reply, &QNetworkReply::errorOccurred, nullptr, nullptr);
|
||||
reply->abort();
|
||||
reply->deleteLater();
|
||||
_manager = nullptr;
|
||||
@@ -857,7 +856,7 @@ void HttpLoaderActor::sendRequest() {
|
||||
&HttpLoaderActor::partFinished);
|
||||
connect(
|
||||
_reply.get(),
|
||||
base::QNetworkReply_error,
|
||||
&QNetworkReply::errorOccurred,
|
||||
this,
|
||||
&HttpLoaderActor::partFailed);
|
||||
connect(
|
||||
|
||||
@@ -7,11 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "logs.h"
|
||||
#include "base/basic_types.h"
|
||||
#include "base/flags.h"
|
||||
#include "base/algorithm.h"
|
||||
#include "base/assertion.h"
|
||||
#include "base/bytes.h"
|
||||
|
||||
#include <crl/crl_time.h>
|
||||
@@ -182,52 +180,3 @@ static int32 FullArcLength = 360 * 16;
|
||||
static int32 QuarterArcLength = (FullArcLength / 4);
|
||||
static int32 MinArcLength = (FullArcLength / 360);
|
||||
static int32 AlmostFullArcLength = (FullArcLength - MinArcLength);
|
||||
|
||||
// This pointer is used for global non-POD variables that are allocated
|
||||
// on demand by createIfNull(lambda) and are never automatically freed.
|
||||
template <typename T>
|
||||
class NeverFreedPointer {
|
||||
public:
|
||||
NeverFreedPointer() = default;
|
||||
NeverFreedPointer(const NeverFreedPointer<T> &other) = delete;
|
||||
NeverFreedPointer &operator=(const NeverFreedPointer<T> &other) = delete;
|
||||
|
||||
template <typename... Args>
|
||||
void createIfNull(Args&&... args) {
|
||||
if (isNull()) {
|
||||
reset(new T(std::forward<Args>(args)...));
|
||||
}
|
||||
};
|
||||
|
||||
T *data() const {
|
||||
return _p;
|
||||
}
|
||||
T *release() {
|
||||
return base::take(_p);
|
||||
}
|
||||
void reset(T *p = nullptr) {
|
||||
delete _p;
|
||||
_p = p;
|
||||
}
|
||||
bool isNull() const {
|
||||
return data() == nullptr;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
reset();
|
||||
}
|
||||
T *operator->() const {
|
||||
return data();
|
||||
}
|
||||
T &operator*() const {
|
||||
Assert(!isNull());
|
||||
return *data();
|
||||
}
|
||||
explicit operator bool() const {
|
||||
return !isNull();
|
||||
}
|
||||
|
||||
private:
|
||||
T *_p;
|
||||
|
||||
};
|
||||
|
||||
@@ -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 = 4004003;
|
||||
constexpr auto AppVersionStr = "4.4.3";
|
||||
constexpr auto AppVersion = 4005006;
|
||||
constexpr auto AppVersionStr = "4.5.6";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -7,11 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/data_abstract_structure.h"
|
||||
|
||||
#include "base/never_freed_pointer.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
using DataStructures = OrderedSet<AbstractStructure**>;
|
||||
NeverFreedPointer<DataStructures> structures;
|
||||
base::NeverFreedPointer<DataStructures> structures;
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -279,11 +279,11 @@ void LoadCloudFile(
|
||||
if (const auto onstack = progress) {
|
||||
onstack();
|
||||
}
|
||||
}, [=, &file](bool started) {
|
||||
}, [=, &file](FileLoader::Error error) {
|
||||
finish(file);
|
||||
file.flags |= CloudFile::Flag::Failed;
|
||||
if (const auto onstack = fail) {
|
||||
onstack(started);
|
||||
onstack(error.started);
|
||||
}
|
||||
}, [=, &file] {
|
||||
finish(file);
|
||||
|
||||
@@ -988,8 +988,9 @@ void DocumentData::handleLoaderUpdates() {
|
||||
_loader->updates(
|
||||
) | rpl::start_with_next_error_done([=] {
|
||||
_owner->documentLoadProgress(this);
|
||||
}, [=](bool started) {
|
||||
if (started && _loader) {
|
||||
}, [=](FileLoader::Error error) {
|
||||
using FailureReason = FileLoader::FailureReason;
|
||||
if (error.started && _loader) {
|
||||
const auto origin = _loader->fileOrigin();
|
||||
const auto failedFileName = _loader->fileName();
|
||||
const auto retry = [=] {
|
||||
@@ -1000,23 +1001,21 @@ void DocumentData::handleLoaderUpdates() {
|
||||
tr::lng_download_finish_failed(),
|
||||
crl::guard(&session(), retry)
|
||||
}));
|
||||
} else {
|
||||
// Sometimes we have LOCATION_INVALID error in documents / stickers.
|
||||
// Sometimes FILE_REFERENCE_EXPIRED could not be handled.
|
||||
//
|
||||
//const auto openSettings = [=] {
|
||||
// Core::App().settings().etDownloadPathBookmark(QByteArray());
|
||||
// Core::App().settings().setDownloadPath(QString());
|
||||
// Ui::show(Box<DownloadPathBox>());
|
||||
//};
|
||||
//Ui::show(Box<Ui::ConfirmBox>(
|
||||
// tr::lng_download_path_failed(tr::now),
|
||||
// tr::lng_download_path_settings(tr::now),
|
||||
// crl::guard(&session(), openSettings)));
|
||||
} else if (error.failureReason == FailureReason::FileWriteFailure) {
|
||||
if (!Core::App().settings().downloadPath().isEmpty()) {
|
||||
Core::App().settings().setDownloadPathBookmark(QByteArray());
|
||||
Core::App().settings().setDownloadPath(QString());
|
||||
Core::App().saveSettingsDelayed();
|
||||
InvokeQueued(qApp, [] {
|
||||
Ui::show(
|
||||
Ui::MakeInformBox(
|
||||
tr::lng_download_path_failed(tr::now)));
|
||||
});
|
||||
}
|
||||
}
|
||||
finishLoad();
|
||||
status = FileDownloadFailed;
|
||||
_owner->documentLoadFail(this, started);
|
||||
_owner->documentLoadFail(this, error.started);
|
||||
}, [=] {
|
||||
finishLoad();
|
||||
_owner->documentLoadDone(this);
|
||||
@@ -1198,26 +1197,29 @@ bool DocumentData::isStickerSetInstalled() const {
|
||||
|
||||
Image *DocumentData::getReplyPreview(
|
||||
Data::FileOrigin origin,
|
||||
not_null<PeerData*> context) {
|
||||
not_null<PeerData*> context,
|
||||
bool spoiler) {
|
||||
if (!hasThumbnail()) {
|
||||
return nullptr;
|
||||
} else if (!_replyPreview) {
|
||||
_replyPreview = std::make_unique<Data::ReplyPreview>(this);
|
||||
}
|
||||
return _replyPreview->image(origin, context);
|
||||
return _replyPreview->image(origin, context, spoiler);
|
||||
}
|
||||
|
||||
Image *DocumentData::getReplyPreview(not_null<HistoryItem*> item) {
|
||||
return getReplyPreview(item->fullId(), item->history()->peer);
|
||||
const auto media = item->media();
|
||||
const auto spoiler = media && media->hasSpoiler();
|
||||
return getReplyPreview(item->fullId(), item->history()->peer, spoiler);
|
||||
}
|
||||
|
||||
bool DocumentData::replyPreviewLoaded() const {
|
||||
bool DocumentData::replyPreviewLoaded(bool spoiler) const {
|
||||
if (!hasThumbnail()) {
|
||||
return true;
|
||||
} else if (!_replyPreview) {
|
||||
return false;
|
||||
}
|
||||
return _replyPreview->loaded();
|
||||
return _replyPreview->loaded(spoiler);
|
||||
}
|
||||
|
||||
StickerData *DocumentData::sticker() const {
|
||||
|
||||
@@ -142,9 +142,10 @@ public:
|
||||
|
||||
[[nodiscard]] Image *getReplyPreview(
|
||||
Data::FileOrigin origin,
|
||||
not_null<PeerData*> context);
|
||||
not_null<PeerData*> context,
|
||||
bool spoiler);
|
||||
[[nodiscard]] Image *getReplyPreview(not_null<HistoryItem*> item);
|
||||
[[nodiscard]] bool replyPreviewLoaded() const;
|
||||
[[nodiscard]] bool replyPreviewLoaded(bool spoiler) const;
|
||||
|
||||
[[nodiscard]] StickerData *sticker() const;
|
||||
[[nodiscard]] Data::FileOrigin stickerSetOrigin() const;
|
||||
|
||||
@@ -618,7 +618,7 @@ Image *MediaPhoto::replyPreview() const {
|
||||
}
|
||||
|
||||
bool MediaPhoto::replyPreviewLoaded() const {
|
||||
return _photo->replyPreviewLoaded();
|
||||
return _photo->replyPreviewLoaded(_spoiler);
|
||||
}
|
||||
|
||||
TextWithEntities MediaPhoto::notificationText() const {
|
||||
@@ -854,7 +854,7 @@ Image *MediaFile::replyPreview() const {
|
||||
}
|
||||
|
||||
bool MediaFile::replyPreviewLoaded() const {
|
||||
return _document->replyPreviewLoaded();
|
||||
return _document->replyPreviewLoaded(_spoiler);
|
||||
}
|
||||
|
||||
ItemPreview MediaFile::toPreview(ToPreviewOptions options) const {
|
||||
@@ -1323,7 +1323,7 @@ TextForMimeData MediaLocation::clipboardText() const {
|
||||
if (!descriptionResult.text.isEmpty()) {
|
||||
result.append(std::move(descriptionResult));
|
||||
}
|
||||
result.append(LocationClickHandler(_point).dragText());
|
||||
result.append(LocationClickHandler(_point).url());
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1479,10 +1479,11 @@ Image *MediaWebPage::replyPreview() const {
|
||||
}
|
||||
|
||||
bool MediaWebPage::replyPreviewLoaded() const {
|
||||
const auto spoiler = false;
|
||||
if (const auto document = MediaWebPage::document()) {
|
||||
return document->replyPreviewLoaded();
|
||||
return document->replyPreviewLoaded(spoiler);
|
||||
} else if (const auto photo = MediaWebPage::photo()) {
|
||||
return photo->replyPreviewLoaded();
|
||||
return photo->replyPreviewLoaded(spoiler);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1552,10 +1553,11 @@ Image *MediaGame::replyPreview() const {
|
||||
}
|
||||
|
||||
bool MediaGame::replyPreviewLoaded() const {
|
||||
const auto spoiler = false;
|
||||
if (const auto document = _game->document) {
|
||||
return document->replyPreviewLoaded();
|
||||
return document->replyPreviewLoaded(spoiler);
|
||||
} else if (const auto photo = _game->photo) {
|
||||
return photo->replyPreviewLoaded();
|
||||
return photo->replyPreviewLoaded(spoiler);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1675,8 +1677,9 @@ Image *MediaInvoice::replyPreview() const {
|
||||
}
|
||||
|
||||
bool MediaInvoice::replyPreviewLoaded() const {
|
||||
const auto spoiler = false;
|
||||
if (const auto photo = _invoice.photo) {
|
||||
return photo->replyPreviewLoaded();
|
||||
return photo->replyPreviewLoaded(spoiler);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -514,7 +514,8 @@ bool ChannelHasActiveCall(not_null<ChannelData*> channel) {
|
||||
|
||||
rpl::producer<QImage> PeerUserpicImageValue(
|
||||
not_null<PeerData*> peer,
|
||||
int size) {
|
||||
int size,
|
||||
std::optional<int> radius) {
|
||||
return [=](auto consumer) {
|
||||
auto result = rpl::lifetime();
|
||||
struct State {
|
||||
@@ -541,7 +542,10 @@ rpl::producer<QImage> PeerUserpicImageValue(
|
||||
}
|
||||
state->key = key;
|
||||
state->empty = false;
|
||||
consumer.put_next(peer->generateUserpicImage(state->view, size));
|
||||
consumer.put_next(peer->generateUserpicImage(
|
||||
state->view,
|
||||
size,
|
||||
radius));
|
||||
};
|
||||
peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
|
||||
@@ -133,7 +133,8 @@ inline auto PeerFullFlagValue(
|
||||
|
||||
[[nodiscard]] rpl::producer<QImage> PeerUserpicImageValue(
|
||||
not_null<PeerData*> peer,
|
||||
int size);
|
||||
int size,
|
||||
std::optional<int> radius = {});
|
||||
|
||||
[[nodiscard]] const AllowedReactions &PeerAllowedReactions(
|
||||
not_null<PeerData*> peer);
|
||||
|
||||
@@ -23,7 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kPhotoSideLimit = 1280;
|
||||
constexpr auto kPhotoSideLimit = 2560;
|
||||
|
||||
using Data::PhotoMedia;
|
||||
using Data::PhotoSize;
|
||||
@@ -209,22 +209,25 @@ bool PhotoData::uploading() const {
|
||||
|
||||
Image *PhotoData::getReplyPreview(
|
||||
Data::FileOrigin origin,
|
||||
not_null<PeerData*> context) {
|
||||
not_null<PeerData*> context,
|
||||
bool spoiler) {
|
||||
if (!_replyPreview) {
|
||||
_replyPreview = std::make_unique<Data::ReplyPreview>(this);
|
||||
}
|
||||
return _replyPreview->image(origin, context);
|
||||
return _replyPreview->image(origin, context, spoiler);
|
||||
}
|
||||
|
||||
Image *PhotoData::getReplyPreview(not_null<HistoryItem*> item) {
|
||||
return getReplyPreview(item->fullId(), item->history()->peer);
|
||||
const auto media = item->media();
|
||||
const auto spoiler = media && media->hasSpoiler();
|
||||
return getReplyPreview(item->fullId(), item->history()->peer, spoiler);
|
||||
}
|
||||
|
||||
bool PhotoData::replyPreviewLoaded() const {
|
||||
bool PhotoData::replyPreviewLoaded(bool spoiler) const {
|
||||
if (!_replyPreview) {
|
||||
return false;
|
||||
}
|
||||
return _replyPreview->loaded();
|
||||
return _replyPreview->loaded(spoiler);
|
||||
}
|
||||
|
||||
void PhotoData::setRemoteLocation(
|
||||
|
||||
@@ -66,9 +66,10 @@ public:
|
||||
|
||||
[[nodiscard]] Image *getReplyPreview(
|
||||
Data::FileOrigin origin,
|
||||
not_null<PeerData*> context);
|
||||
not_null<PeerData*> context,
|
||||
bool spoiler);
|
||||
[[nodiscard]] Image *getReplyPreview(not_null<HistoryItem*> item);
|
||||
[[nodiscard]] bool replyPreviewLoaded() const;
|
||||
[[nodiscard]] bool replyPreviewLoaded(bool spoiler) const;
|
||||
|
||||
void setRemoteLocation(
|
||||
int32 dc,
|
||||
|
||||
@@ -101,6 +101,14 @@ void PhotoMedia::set(
|
||||
QImage image,
|
||||
QByteArray bytes) {
|
||||
const auto index = PhotoSizeIndex(size);
|
||||
const auto limit = PhotoData::SideLimit();
|
||||
if (image.width() > limit || image.height() > limit) {
|
||||
image = image.scaled(
|
||||
limit,
|
||||
limit,
|
||||
Qt::KeepAspectRatio,
|
||||
Qt::SmoothTransformation);
|
||||
}
|
||||
_images[index] = PhotoImage{
|
||||
.data = std::make_unique<Image>(std::move(image)),
|
||||
.bytes = std::move(bytes),
|
||||
|
||||
@@ -27,7 +27,11 @@ ReplyPreview::ReplyPreview(not_null<PhotoData*> photo)
|
||||
|
||||
ReplyPreview::~ReplyPreview() = default;
|
||||
|
||||
void ReplyPreview::prepare(not_null<Image*> image, Images::Options options) {
|
||||
void ReplyPreview::prepare(
|
||||
not_null<Image*> image,
|
||||
Images::Options options,
|
||||
bool spoiler) {
|
||||
using namespace Images;
|
||||
if (image->isNull()) {
|
||||
return;
|
||||
}
|
||||
@@ -41,24 +45,34 @@ void ReplyPreview::prepare(not_null<Image*> image, Images::Options options) {
|
||||
: QSize(
|
||||
st::msgReplyBarSize.height(),
|
||||
h * st::msgReplyBarSize.height() / w);
|
||||
thumbSize *= cIntRetinaFactor();
|
||||
options |= Images::Option::TransparentBackground;
|
||||
thumbSize *= style::DevicePixelRatio();
|
||||
options |= Option::TransparentBackground;
|
||||
auto outerSize = st::msgReplyBarSize.height();
|
||||
auto bitmap = image->pixNoCache(
|
||||
thumbSize,
|
||||
{ .options = options, .outer = { outerSize, outerSize } });
|
||||
_image = std::make_unique<Image>(bitmap.toImage());
|
||||
_good = ((options & Images::Option::Blur) == 0);
|
||||
auto original = spoiler
|
||||
? image->original().scaled(
|
||||
{ 40, 40 },
|
||||
Qt::KeepAspectRatio,
|
||||
Qt::SmoothTransformation)
|
||||
: image->original();
|
||||
auto prepared = Prepare(std::move(original), thumbSize, {
|
||||
.options = options | (spoiler ? Option::Blur : Option()),
|
||||
.outer = { outerSize, outerSize },
|
||||
});
|
||||
(spoiler ? _spoilered : _regular) = std::make_unique<Image>(
|
||||
std::move(prepared));
|
||||
_good = spoiler || ((options & Option::Blur) == 0);
|
||||
}
|
||||
|
||||
Image *ReplyPreview::image(
|
||||
Data::FileOrigin origin,
|
||||
not_null<PeerData*> context) {
|
||||
if (_checked) {
|
||||
return _image.get();
|
||||
}
|
||||
if (_document) {
|
||||
if (!_image || (!_good && _document->hasThumbnail())) {
|
||||
not_null<PeerData*> context,
|
||||
bool spoiler) {
|
||||
auto &image = spoiler ? _spoilered : _regular;
|
||||
auto &checked = spoiler ? _checkedSpoilered : _checkedRegular;
|
||||
if (checked) {
|
||||
return image.get();
|
||||
} else if (_document) {
|
||||
if (!image || (!_good && _document->hasThumbnail())) {
|
||||
if (!_documentMedia) {
|
||||
_documentMedia = _document->createMediaView();
|
||||
_documentMedia->thumbnailWanted(origin);
|
||||
@@ -67,51 +81,66 @@ Image *ReplyPreview::image(
|
||||
const auto option = _document->isVideoMessage()
|
||||
? Images::Option::RoundCircle
|
||||
: Images::Option::None;
|
||||
if (thumbnail) {
|
||||
if (spoiler) {
|
||||
if (const auto image = _documentMedia->thumbnailInline()) {
|
||||
prepare(image, option, true);
|
||||
} else if (thumbnail) {
|
||||
prepare(thumbnail, option, true);
|
||||
}
|
||||
} else if (thumbnail) {
|
||||
prepare(thumbnail, option);
|
||||
} else if (!_image) {
|
||||
} else if (!image) {
|
||||
if (const auto image = _documentMedia->thumbnailInline()) {
|
||||
prepare(image, option | Images::Option::Blur);
|
||||
}
|
||||
}
|
||||
if (_good || !_document->hasThumbnail()) {
|
||||
_checked = true;
|
||||
checked = true;
|
||||
_documentMedia = nullptr;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Assert(_photo != nullptr);
|
||||
if (!_image || !_good) {
|
||||
if (!image || !_good) {
|
||||
const auto inlineThumbnailBytes = _photo->inlineThumbnailBytes();
|
||||
if (!_photoMedia) {
|
||||
_photoMedia = _photo->createMediaView();
|
||||
}
|
||||
using Size = PhotoSize;
|
||||
const auto loadThumbnail = inlineThumbnailBytes.isEmpty()
|
||||
|| _photoMedia->autoLoadThumbnailAllowed(context);
|
||||
|| (!spoiler
|
||||
&& _photoMedia->autoLoadThumbnailAllowed(context));
|
||||
if (loadThumbnail) {
|
||||
_photoMedia->wanted(PhotoSize::Small, origin);
|
||||
_photoMedia->wanted(Size::Small, origin);
|
||||
}
|
||||
if (const auto small = _photoMedia->image(PhotoSize::Small)) {
|
||||
prepare(small, Images::Option(0));
|
||||
} else if (const auto large = _photoMedia->image(
|
||||
PhotoSize::Large)) {
|
||||
prepare(large, Images::Option(0));
|
||||
} else if (!_image) {
|
||||
if (spoiler) {
|
||||
if (const auto blurred = _photoMedia->thumbnailInline()) {
|
||||
prepare(blurred, {}, true);
|
||||
} else if (const auto small = _photoMedia->image(Size::Small)) {
|
||||
prepare(small, {}, true);
|
||||
} else if (const auto large = _photoMedia->image(Size::Large)) {
|
||||
prepare(large, {}, true);
|
||||
}
|
||||
} else if (const auto small = _photoMedia->image(Size::Small)) {
|
||||
prepare(small, {});
|
||||
} else if (const auto large = _photoMedia->image(Size::Large)) {
|
||||
prepare(large, {});
|
||||
} else if (!image) {
|
||||
if (const auto blurred = _photoMedia->thumbnailInline()) {
|
||||
prepare(blurred, Images::Option::Blur);
|
||||
}
|
||||
}
|
||||
if (_good) {
|
||||
_checked = true;
|
||||
checked = true;
|
||||
_photoMedia = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
return _image.get();
|
||||
return image.get();
|
||||
}
|
||||
|
||||
bool ReplyPreview::loaded() const {
|
||||
return _checked;
|
||||
bool ReplyPreview::loaded(bool spoiler) const {
|
||||
return spoiler ? _checkedSpoilered : _checkedRegular;
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -25,19 +25,25 @@ public:
|
||||
|
||||
[[nodiscard]] Image *image(
|
||||
Data::FileOrigin origin,
|
||||
not_null<PeerData*> context);
|
||||
[[nodiscard]] bool loaded() const;
|
||||
not_null<PeerData*> context,
|
||||
bool spoiler);
|
||||
[[nodiscard]] bool loaded(bool spoiler) const;
|
||||
|
||||
private:
|
||||
void prepare(not_null<Image*> image, Images::Options options);
|
||||
void prepare(
|
||||
not_null<Image*> image,
|
||||
Images::Options options,
|
||||
bool spoiler = false);
|
||||
|
||||
std::unique_ptr<Image> _image;
|
||||
std::unique_ptr<Image> _regular;
|
||||
std::unique_ptr<Image> _spoilered;
|
||||
PhotoData *_photo = nullptr;
|
||||
DocumentData *_document = nullptr;
|
||||
std::shared_ptr<PhotoMedia> _photoMedia;
|
||||
std::shared_ptr<DocumentMedia> _documentMedia;
|
||||
bool _good = false;
|
||||
bool _checked = false;
|
||||
bool _checkedRegular = false;
|
||||
bool _checkedSpoilered = false;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -144,6 +144,8 @@ WebPageType ParseWebPageType(
|
||||
return WebPageType::Video;
|
||||
} else if (type == u"photo"_q) {
|
||||
return WebPageType::Photo;
|
||||
} else if (type == u"document"_q) {
|
||||
return WebPageType::Document;
|
||||
} else if (type == u"profile"_q) {
|
||||
return WebPageType::Profile;
|
||||
} else if (type == u"telegram_background"_q) {
|
||||
|
||||
@@ -26,6 +26,7 @@ enum class WebPageType {
|
||||
|
||||
Photo,
|
||||
Video,
|
||||
Document,
|
||||
|
||||
User,
|
||||
Bot,
|
||||
|
||||
@@ -926,7 +926,8 @@ void Stickers::specialSetReceived(
|
||||
LOG(("API Error: "
|
||||
"received recent attached stickers hash %1 "
|
||||
"while counted hash is %2"
|
||||
).arg(hash, counted));
|
||||
).arg(hash
|
||||
).arg(counted));
|
||||
}
|
||||
session().local().writeRecentMasks();
|
||||
} break;
|
||||
|
||||
@@ -2851,7 +2851,6 @@ void InnerWidget::searchInChat(Key key, PeerData *from) {
|
||||
_searchInChat = key;
|
||||
_searchFromPeer = from;
|
||||
if (_searchInChat) {
|
||||
_controller->closeFolder();
|
||||
onHashtagFilterUpdate(QStringView());
|
||||
_cancelSearchInChat->show();
|
||||
} else {
|
||||
@@ -3739,6 +3738,9 @@ void InnerWidget::setupShortcuts() {
|
||||
Command::ChatPinned3,
|
||||
Command::ChatPinned4,
|
||||
Command::ChatPinned5,
|
||||
Command::ChatPinned6,
|
||||
Command::ChatPinned7,
|
||||
Command::ChatPinned8,
|
||||
};
|
||||
auto &&pinned = ranges::views::zip(
|
||||
kPinned,
|
||||
@@ -3791,14 +3793,14 @@ void InnerWidget::setupShortcuts() {
|
||||
});
|
||||
|
||||
request->check(Command::ReadChat) && request->handle([=] {
|
||||
const auto history = _selected ? _selected->history() : nullptr;
|
||||
if (history) {
|
||||
if (history->chatListBadgesState().unread) {
|
||||
session().data().histories().readInbox(history);
|
||||
}
|
||||
return true;
|
||||
const auto thread = _selected ? _selected->thread() : nullptr;
|
||||
if (!thread) {
|
||||
return false;
|
||||
}
|
||||
return (history != nullptr);
|
||||
if (Window::IsUnreadThread(thread)) {
|
||||
Window::MarkAsReadThread(thread);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
request->check(Command::ShowContacts) && request->handle([=] {
|
||||
|
||||
@@ -94,16 +94,18 @@ constexpr auto kNoneLayer = 0;
|
||||
constexpr auto kAngleStart = 90 * 16;
|
||||
constexpr auto kAngleSpan = 180 * 16;
|
||||
|
||||
const auto penWidth = style::ConvertScaleExact(kPenWidth);
|
||||
auto pen = QPen(st::premiumButtonFg);
|
||||
pen.setJoinStyle(Qt::RoundJoin);
|
||||
pen.setCapStyle(Qt::RoundCap);
|
||||
pen.setWidthF(style::ConvertScaleExact(kPenWidth));
|
||||
pen.setWidthF(penWidth);
|
||||
|
||||
q.setPen(pen);
|
||||
q.setBrush(Qt::NoBrush);
|
||||
q.drawArc(innerRect, kAngleStart, kAngleSpan);
|
||||
|
||||
q.setClipRect(innerRect - QMargins(innerRect.width() / 2, 0, 0, 0));
|
||||
q.setClipRect(innerRect
|
||||
- QMargins(innerRect.width() / 2, 0, -penWidth, -penWidth));
|
||||
pen.setStyle(Qt::DotLine);
|
||||
q.setPen(pen);
|
||||
q.drawEllipse(innerRect);
|
||||
@@ -252,6 +254,10 @@ Row::Row(Key key, int index, int top) : _id(key), _top(top), _index(index) {
|
||||
}
|
||||
}
|
||||
|
||||
Row::~Row() {
|
||||
clearTopicJumpRipple();
|
||||
}
|
||||
|
||||
void Row::recountHeight(float64 narrowRatio) {
|
||||
if (const auto history = _id.history()) {
|
||||
_height = history->isForum()
|
||||
@@ -487,6 +493,11 @@ void Row::stopLastRipple() {
|
||||
}
|
||||
}
|
||||
|
||||
void Row::clearRipple() {
|
||||
BasicRow::clearRipple();
|
||||
clearTopicJumpRipple();
|
||||
}
|
||||
|
||||
void Row::addTopicJumpRipple(
|
||||
QPoint origin,
|
||||
not_null<Ui::TopicJumpCache*> topicJumpCache,
|
||||
@@ -503,10 +514,15 @@ void Row::addTopicJumpRipple(
|
||||
}
|
||||
|
||||
void Row::clearTopicJumpRipple() {
|
||||
if (_topicJumpRipple) {
|
||||
clearRipple();
|
||||
_topicJumpRipple = 0;
|
||||
if (!_topicJumpRipple) {
|
||||
return;
|
||||
}
|
||||
const auto history = this->history();
|
||||
const auto view = history ? &history->lastItemDialogsView() : nullptr;
|
||||
if (view) {
|
||||
view->clearRipple();
|
||||
}
|
||||
_topicJumpRipple = 0;
|
||||
}
|
||||
|
||||
bool Row::topicJumpRipple() const {
|
||||
|
||||
@@ -53,11 +53,11 @@ public:
|
||||
|
||||
void addRipple(QPoint origin, QSize size, Fn<void()> updateCallback);
|
||||
virtual void stopLastRipple();
|
||||
virtual void clearRipple();
|
||||
void addRippleWithMask(
|
||||
QPoint origin,
|
||||
QImage mask,
|
||||
Fn<void()> updateCallback);
|
||||
void clearRipple();
|
||||
|
||||
void paintRipple(
|
||||
QPainter &p,
|
||||
@@ -82,6 +82,7 @@ public:
|
||||
explicit Row(std::nullptr_t) {
|
||||
}
|
||||
Row(Key key, int index, int top);
|
||||
~Row();
|
||||
|
||||
[[nodiscard]] int top() const {
|
||||
return _top;
|
||||
@@ -105,6 +106,7 @@ public:
|
||||
|
||||
[[nodiscard]] bool lookupIsInTopicJump(int x, int y) const;
|
||||
void stopLastRipple() override;
|
||||
void clearRipple() override;
|
||||
void addTopicJumpRipple(
|
||||
QPoint origin,
|
||||
not_null<Ui::TopicJumpCache*> topicJumpCache,
|
||||
|
||||
@@ -188,7 +188,12 @@ Widget::Widget(
|
||||
+ st::defaultDialogRow.photoSize
|
||||
+ st::defaultDialogRow.padding.left())
|
||||
, _searchControls(this)
|
||||
, _mainMenuToggle(_searchControls, st::dialogsMenuToggle)
|
||||
, _mainMenu({
|
||||
.toggle = object_ptr<Ui::IconButton>(
|
||||
_searchControls,
|
||||
st::dialogsMenuToggle),
|
||||
.under = object_ptr<Ui::AbstractButton>(_searchControls),
|
||||
})
|
||||
, _searchForNarrowFilters(_searchControls, st::dialogsSearchForNarrowFilters)
|
||||
, _filter(_searchControls, st::dialogsFilter, tr::lng_dlg_filter())
|
||||
, _chooseFromUser(
|
||||
@@ -427,6 +432,14 @@ Widget::Widget(
|
||||
updateControlsGeometry();
|
||||
}, lifetime());
|
||||
|
||||
_childListShown.changes(
|
||||
) | rpl::filter((rpl::mappers::_1 == 0.) || (rpl::mappers::_1 == 1.)
|
||||
) | rpl::start_with_next([=](float64 shown) {
|
||||
const auto color = (shown > 0.) ? &st::dialogsRippleBg : nullptr;
|
||||
_mainMenu.toggle->setRippleColorOverride(color);
|
||||
_searchForNarrowFilters->setRippleColorOverride(color);
|
||||
}, lifetime());
|
||||
|
||||
setupDownloadBar();
|
||||
}
|
||||
}
|
||||
@@ -662,13 +675,18 @@ void Widget::setupSupportMode() {
|
||||
}
|
||||
|
||||
void Widget::setupMainMenuToggle() {
|
||||
_mainMenuToggle->setClickedCallback([=] { showMainMenu(); });
|
||||
_mainMenu.under->setClickedCallback([=] {
|
||||
_mainMenu.toggle->clicked({}, Qt::LeftButton);
|
||||
});
|
||||
_mainMenu.under->stackUnder(_mainMenu.toggle);
|
||||
_mainMenu.toggle->setClickedCallback([=] { showMainMenu(); });
|
||||
|
||||
rpl::single(rpl::empty) | rpl::then(
|
||||
controller()->filtersMenuChanged()
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto filtersHidden = !controller()->filtersWidth();
|
||||
_mainMenuToggle->setVisible(filtersHidden);
|
||||
_mainMenu.toggle->setVisible(filtersHidden);
|
||||
_mainMenu.under->setVisible(filtersHidden);
|
||||
_searchForNarrowFilters->setVisible(!filtersHidden);
|
||||
updateControlsGeometry();
|
||||
}, lifetime());
|
||||
@@ -680,8 +698,8 @@ void Widget::setupMainMenuToggle() {
|
||||
: !state.allMuted
|
||||
? &st::dialogsMenuToggleUnread
|
||||
: &st::dialogsMenuToggleUnreadMuted;
|
||||
_mainMenuToggle->setIconOverride(icon, icon);
|
||||
}, _mainMenuToggle->lifetime());
|
||||
_mainMenu.toggle->setIconOverride(icon, icon);
|
||||
}, _mainMenu.toggle->lifetime());
|
||||
}
|
||||
|
||||
void Widget::setupShortcuts() {
|
||||
@@ -2160,15 +2178,13 @@ bool Widget::setSearchInChat(Key chat, PeerData *from) {
|
||||
}
|
||||
}
|
||||
_searchInMigrated = nullptr;
|
||||
if (peer) {
|
||||
if (peer && !forum) {
|
||||
if (_layout != Layout::Main) {
|
||||
return false;
|
||||
} else if (const auto migrateTo = peer->migrateTo()) {
|
||||
return setSearchInChat(peer->owner().history(migrateTo), from);
|
||||
} else if (const auto migrateFrom = peer->migrateFrom()) {
|
||||
if (!forum) {
|
||||
_searchInMigrated = peer->owner().history(migrateFrom);
|
||||
}
|
||||
_searchInMigrated = peer->owner().history(migrateFrom);
|
||||
}
|
||||
}
|
||||
if (searchInPeerUpdated) {
|
||||
@@ -2180,6 +2196,9 @@ bool Widget::setSearchInChat(Key chat, PeerData *from) {
|
||||
updateSearchFromVisibility();
|
||||
clearSearchCache();
|
||||
}
|
||||
if (_searchInChat && _layout == Layout::Main) {
|
||||
controller()->closeFolder();
|
||||
}
|
||||
_inner->searchInChat(_searchInChat, _searchFromAuthor);
|
||||
if (_subsectionTopBar) {
|
||||
_subsectionTopBar->searchEnableJumpToDate(
|
||||
@@ -2368,7 +2387,7 @@ void Widget::updateControlsGeometry() {
|
||||
|
||||
auto filterLeft = (controller()->filtersWidth()
|
||||
? st::dialogsFilterSkip
|
||||
: (st::dialogsFilterPadding.x() + _mainMenuToggle->width()))
|
||||
: (st::dialogsFilterPadding.x() + _mainMenu.toggle->width()))
|
||||
+ st::dialogsFilterPadding.x();
|
||||
auto filterRight = (session().domain().local().hasLocalPasscode()
|
||||
? (st::dialogsFilterPadding.x() + _lockUnlock->width())
|
||||
@@ -2388,9 +2407,16 @@ void Widget::updateControlsGeometry() {
|
||||
_filter->setGeometryToLeft(filterLeft, filterTop, filterWidth, _filter->height());
|
||||
auto mainMenuLeft = anim::interpolate(
|
||||
st::dialogsFilterPadding.x(),
|
||||
(_narrowWidth - _mainMenuToggle->width()) / 2,
|
||||
(_narrowWidth - _mainMenu.toggle->width()) / 2,
|
||||
narrowRatio);
|
||||
_mainMenuToggle->moveToLeft(mainMenuLeft, st::dialogsFilterPadding.y());
|
||||
_mainMenu.toggle->moveToLeft(mainMenuLeft, st::dialogsFilterPadding.y());
|
||||
_mainMenu.under->setGeometry(
|
||||
0,
|
||||
0,
|
||||
filterLeft,
|
||||
_mainMenu.toggle->y()
|
||||
+ _mainMenu.toggle->height()
|
||||
+ st::dialogsFilterPadding.y());
|
||||
const auto searchLeft = anim::interpolate(
|
||||
-_searchForNarrowFilters->width(),
|
||||
(_narrowWidth - _searchForNarrowFilters->width()) / 2,
|
||||
|
||||
@@ -33,6 +33,7 @@ class ContactStatus;
|
||||
} // namespace HistoryView
|
||||
|
||||
namespace Ui {
|
||||
class AbstractButton;
|
||||
class IconButton;
|
||||
class PopupMenu;
|
||||
class DropdownMenu;
|
||||
@@ -222,7 +223,10 @@ private:
|
||||
int _narrowWidth = 0;
|
||||
object_ptr<Ui::RpWidget> _searchControls;
|
||||
object_ptr<HistoryView::TopBarWidget> _subsectionTopBar = { nullptr } ;
|
||||
object_ptr<Ui::IconButton> _mainMenuToggle;
|
||||
struct {
|
||||
object_ptr<Ui::IconButton> toggle;
|
||||
object_ptr<Ui::AbstractButton> under;
|
||||
} _mainMenu;
|
||||
object_ptr<Ui::IconButton> _searchForNarrowFilters;
|
||||
object_ptr<Ui::InputField> _filter;
|
||||
object_ptr<Ui::FadeWrapScaled<Ui::IconButton>> _chooseFromUser;
|
||||
|
||||
@@ -227,6 +227,12 @@ void MessageView::stopLastRipple() {
|
||||
}
|
||||
}
|
||||
|
||||
void MessageView::clearRipple() {
|
||||
if (_topics) {
|
||||
_topics->clearRipple();
|
||||
}
|
||||
}
|
||||
|
||||
int MessageView::countWidth() const {
|
||||
auto result = 0;
|
||||
if (!_senderCache.isEmpty()) {
|
||||
|
||||
@@ -73,6 +73,7 @@ public:
|
||||
not_null<TopicJumpCache*> topicJumpCache,
|
||||
Fn<void()> updateCallback);
|
||||
void stopLastRipple();
|
||||
void clearRipple();
|
||||
|
||||
private:
|
||||
struct LoadingContext;
|
||||
|
||||
@@ -187,6 +187,10 @@ void TopicsView::stopLastRipple() {
|
||||
}
|
||||
}
|
||||
|
||||
void TopicsView::clearRipple() {
|
||||
_ripple = nullptr;
|
||||
}
|
||||
|
||||
void TopicsView::paintRipple(
|
||||
QPainter &p,
|
||||
int x,
|
||||
|
||||
@@ -89,8 +89,8 @@ public:
|
||||
int y,
|
||||
int outerWidth,
|
||||
const QColor *colorOverride) const;
|
||||
void clearRipple();
|
||||
void stopLastRipple();
|
||||
void clearRipple();
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime() {
|
||||
return _lifetime;
|
||||
|
||||
@@ -54,8 +54,8 @@ photoEditorRotateButton: IconButton(defaultIconButton) {
|
||||
width: photoEditorButtonBarHeight;
|
||||
height: photoEditorButtonBarHeight;
|
||||
|
||||
icon: icon {{ "photo_editor/rotate", photoEditorButtonIconFg }};
|
||||
iconOver: icon {{ "photo_editor/rotate", photoEditorButtonIconFgOver }};
|
||||
icon: icon {{ "photo_editor/rotate-flip_horizontal", photoEditorButtonIconFg }};
|
||||
iconOver: icon {{ "photo_editor/rotate-flip_horizontal", photoEditorButtonIconFgOver }};
|
||||
|
||||
rippleAreaPosition: point(4px, 4px);
|
||||
rippleAreaSize: 40px;
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/boxes/confirm_box.h" // InformBox
|
||||
#include "editor/editor_layer_widget.h"
|
||||
#include "editor/photo_editor.h"
|
||||
#include "storage/localimageloader.h"
|
||||
#include "storage/storage_media_prepare.h"
|
||||
#include "ui/chat/attach/attach_prepare.h"
|
||||
#include "window/window_controller.h"
|
||||
@@ -42,10 +43,11 @@ void OpenWithPreparedFile(
|
||||
return;
|
||||
}
|
||||
|
||||
const auto sideLimit = PhotoSideLimit();
|
||||
auto callback = [=, done = std::move(doneCallback)](
|
||||
const PhotoModifications &mods) {
|
||||
image->modifications = mods;
|
||||
Storage::UpdateImageDetails(*file, previewWidth);
|
||||
Storage::UpdateImageDetails(*file, previewWidth, sideLimit);
|
||||
{
|
||||
using namespace Ui;
|
||||
const auto size = file->preview.size();
|
||||
|
||||
@@ -490,8 +490,6 @@ void ApiWrap::requestSplitRanges() {
|
||||
_splits.push_back(MTP_messageRange(
|
||||
MTP_int(1),
|
||||
MTP_int(std::numeric_limits<int>::max())));
|
||||
} else {
|
||||
ranges::reverse(_splits);
|
||||
}
|
||||
_startProcess->splitIndex = useOnlyLastSplit()
|
||||
? (_splits.size() - 1)
|
||||
|
||||
@@ -845,18 +845,17 @@ not_null<HistoryItem*> History::addNewToBack(
|
||||
addItemToBlock(item);
|
||||
|
||||
if (!unread && item->isRegular()) {
|
||||
if (const auto sharedMediaTypes = item->sharedMediaTypes()) {
|
||||
if (const auto types = item->sharedMediaTypes()) {
|
||||
auto from = loadedAtTop() ? 0 : minMsgId();
|
||||
auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId();
|
||||
auto &storage = session().storage();
|
||||
storage.add(Storage::SharedMediaAddExisting(
|
||||
peer->id,
|
||||
MsgId(0), // topicRootId
|
||||
sharedMediaTypes,
|
||||
types,
|
||||
item->id,
|
||||
{ from, till }));
|
||||
const auto pinned = sharedMediaTypes.test(
|
||||
Storage::SharedMediaType::Pinned);
|
||||
const auto pinned = types.test(Storage::SharedMediaType::Pinned);
|
||||
if (pinned) {
|
||||
setHasPinnedMessages(true);
|
||||
}
|
||||
@@ -864,7 +863,7 @@ not_null<HistoryItem*> History::addNewToBack(
|
||||
storage.add(Storage::SharedMediaAddExisting(
|
||||
peer->id,
|
||||
topic->rootId(),
|
||||
sharedMediaTypes,
|
||||
types,
|
||||
item->id,
|
||||
{ item->id, item->id}));
|
||||
if (pinned) {
|
||||
|
||||
@@ -1395,6 +1395,7 @@ void HistoryItem::applyEdition(HistoryMessageEdition &&edition) {
|
||||
setReplyMarkup(base::take(edition.replyMarkup));
|
||||
}
|
||||
if (!isLocalUpdateMedia()) {
|
||||
removeFromSharedMediaIndex();
|
||||
refreshMedia(edition.mtpMedia);
|
||||
}
|
||||
if (!edition.useSameReactions) {
|
||||
@@ -1405,6 +1406,9 @@ void HistoryItem::applyEdition(HistoryMessageEdition &&edition) {
|
||||
setText(_media
|
||||
? edition.textWithEntities
|
||||
: EnsureNonEmpty(edition.textWithEntities));
|
||||
if (!isLocalUpdateMedia()) {
|
||||
indexAsNewItem();
|
||||
}
|
||||
if (!edition.useSameReplies) {
|
||||
if (!edition.replies.isNull) {
|
||||
if (checkRepliesPts(edition.replies)) {
|
||||
@@ -1424,6 +1428,7 @@ void HistoryItem::applyEdition(const MTPDmessageService &message) {
|
||||
if (message.vaction().type() == mtpc_messageActionHistoryClear) {
|
||||
const auto wasGrouped = history()->owner().groups().isGrouped(this);
|
||||
setReplyMarkup({});
|
||||
removeFromSharedMediaIndex();
|
||||
refreshMedia(nullptr);
|
||||
setTextValue({});
|
||||
changeViewsCount(-1);
|
||||
@@ -1653,7 +1658,10 @@ void HistoryItem::destroyHistoryEntry() {
|
||||
|
||||
Storage::SharedMediaTypesMask HistoryItem::sharedMediaTypes() const {
|
||||
auto result = Storage::SharedMediaTypesMask {};
|
||||
if (const auto media = this->media()) {
|
||||
const auto media = _savedLocalEditMediaData
|
||||
? _savedLocalEditMediaData->media.get()
|
||||
: _media.get();
|
||||
if (media) {
|
||||
result.set(media->sharedMediaTypes());
|
||||
}
|
||||
if (hasTextLinks()) {
|
||||
@@ -1684,6 +1692,18 @@ void HistoryItem::indexAsNewItem() {
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryItem::removeFromSharedMediaIndex() {
|
||||
if (isRegular()) {
|
||||
if (const auto types = sharedMediaTypes()) {
|
||||
_history->session().storage().remove(
|
||||
Storage::SharedMediaRemoveOne(
|
||||
_history->peer->id,
|
||||
types,
|
||||
id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryItem::incrementReplyToTopCounter() {
|
||||
if (const auto reply = Get<HistoryMessageReply>()) {
|
||||
changeReplyToTopCounter(reply, 1);
|
||||
@@ -3676,18 +3696,22 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
|
||||
const auto duration = (period == 5)
|
||||
? u"5 seconds"_q
|
||||
: Ui::FormatTTL(period);
|
||||
if (const auto from = action.vauto_setting_from()) {
|
||||
if (const auto from = action.vauto_setting_from(); from && period) {
|
||||
if (const auto peer = _from->owner().peer(peerFromUser(*from))) {
|
||||
if (!peer->isSelf() && period) {
|
||||
result.text = tr::lng_action_ttl_global(
|
||||
result.text = (peer->id == peer->session().userPeerId())
|
||||
? tr::lng_action_ttl_global_me(
|
||||
tr::now,
|
||||
lt_duration,
|
||||
{ .text = duration },
|
||||
Ui::Text::WithEntities)
|
||||
: tr::lng_action_ttl_global(
|
||||
tr::now,
|
||||
lt_from,
|
||||
fromLinkText(), // Link 1.
|
||||
Ui::Text::Link(peer->name(), 1), // Link 1.
|
||||
lt_duration,
|
||||
{ .text = duration },
|
||||
Ui::Text::WithEntities);
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
if (isPost()) {
|
||||
|
||||
@@ -346,6 +346,7 @@ public:
|
||||
[[nodiscard]] Storage::SharedMediaTypesMask sharedMediaTypes() const;
|
||||
|
||||
void indexAsNewItem();
|
||||
void removeFromSharedMediaIndex();
|
||||
|
||||
[[nodiscard]] QString notificationHeader() const;
|
||||
[[nodiscard]] TextWithEntities notificationText() const;
|
||||
|
||||
@@ -47,7 +47,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace {
|
||||
|
||||
const auto kPsaForwardedPrefix = "cloud_lng_forwarded_psa_";
|
||||
constexpr auto kReplyBarAlpha = 230. / 255.;
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -283,9 +282,10 @@ bool HistoryMessageReply::updateData(
|
||||
}
|
||||
|
||||
if (replyToMsg) {
|
||||
const auto repaint = [=] { holder->customEmojiRepaint(); };
|
||||
const auto context = Core::MarkedTextContext{
|
||||
.session = &holder->history()->session(),
|
||||
.customEmojiRepaint = [=] { holder->customEmojiRepaint(); },
|
||||
.customEmojiRepaint = repaint,
|
||||
};
|
||||
replyToText.setMarkedText(
|
||||
st::messageTextStyle,
|
||||
@@ -312,9 +312,17 @@ bool HistoryMessageReply::updateData(
|
||||
? replyToMsg->from()->id
|
||||
: PeerId(0);
|
||||
}
|
||||
|
||||
const auto media = replyToMsg->media();
|
||||
if (!media || !media->hasReplyPreview() || !media->hasSpoiler()) {
|
||||
spoiler = nullptr;
|
||||
} else if (!spoiler) {
|
||||
spoiler = std::make_unique<Ui::SpoilerAnimation>(repaint);
|
||||
}
|
||||
} else if (force) {
|
||||
replyToMsgId = 0;
|
||||
replyToColorKey = PeerId(0);
|
||||
spoiler = nullptr;
|
||||
}
|
||||
if (force) {
|
||||
holder->history()->owner().requestItemResize(holder);
|
||||
@@ -456,21 +464,22 @@ void HistoryMessageReply::paint(
|
||||
st::msgReplyBarSize.height(),
|
||||
w + 2 * x);
|
||||
const auto opacity = p.opacity();
|
||||
p.setOpacity(opacity * kReplyBarAlpha);
|
||||
p.setOpacity(opacity * kBarAlpha);
|
||||
p.fillRect(rbar, bar);
|
||||
p.setOpacity(opacity);
|
||||
}
|
||||
|
||||
if (w > st::msgReplyBarSkip) {
|
||||
if (replyToMsg) {
|
||||
auto hasPreview = replyToMsg->media() ? replyToMsg->media()->hasReplyPreview() : false;
|
||||
const auto media = replyToMsg->media();
|
||||
auto hasPreview = media && media->hasReplyPreview();
|
||||
if (hasPreview && w < st::msgReplyBarSkip + st::msgReplyBarSize.height()) {
|
||||
hasPreview = false;
|
||||
}
|
||||
auto previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0;
|
||||
|
||||
if (hasPreview) {
|
||||
if (const auto image = replyToMsg->media()->replyPreview()) {
|
||||
if (const auto image = media->replyPreview()) {
|
||||
auto to = style::rtlrect(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height(), w + 2 * x);
|
||||
const auto preview = image->pixSingle(
|
||||
image->size() / style::DevicePixelRatio(),
|
||||
@@ -482,6 +491,16 @@ void HistoryMessageReply::paint(
|
||||
.outer = to.size(),
|
||||
});
|
||||
p.drawPixmap(to.x(), to.y(), preview);
|
||||
if (spoiler) {
|
||||
holder->clearCustomEmojiRepaint();
|
||||
Ui::FillSpoilerRect(
|
||||
p,
|
||||
to,
|
||||
Ui::DefaultImageSpoiler().frame(
|
||||
spoiler->index(
|
||||
context.now,
|
||||
context.paused)));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (w > st::msgReplyBarSkip + previewSkip) {
|
||||
|
||||
@@ -195,6 +195,8 @@ struct HistoryMessageReply
|
||||
Expects(replyToVia == nullptr);
|
||||
}
|
||||
|
||||
static constexpr auto kBarAlpha = 230. / 255.;
|
||||
|
||||
bool updateData(not_null<HistoryItem*> holder, bool force = false);
|
||||
|
||||
// Must be called before destructor.
|
||||
@@ -246,6 +248,7 @@ struct HistoryMessageReply
|
||||
WebPageId replyToWebPageId = 0;
|
||||
ReplyToMessagePointer replyToMsg;
|
||||
std::unique_ptr<HistoryMessageVia> replyToVia;
|
||||
std::unique_ptr<Ui::SpoilerAnimation> spoiler;
|
||||
ClickHandlerPtr replyToLnk;
|
||||
mutable Ui::Text::String replyToName, replyToText;
|
||||
mutable int replyToVersion = 0;
|
||||
|
||||
@@ -24,6 +24,10 @@ public:
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString url() const override {
|
||||
return _text;
|
||||
}
|
||||
|
||||
QString dragText() const override {
|
||||
return _text;
|
||||
}
|
||||
|
||||
@@ -126,7 +126,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "media/player/media_player_instance.h"
|
||||
#include "core/application.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/options.h"
|
||||
#include "base/qthelp_regex.h"
|
||||
#include "ui/boxes/report_box.h"
|
||||
#include "ui/chat/pinned_bar.h"
|
||||
@@ -171,13 +170,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <QtGui/QWindow>
|
||||
#include <QtCore/QMimeData>
|
||||
|
||||
const char kOptionAutoScrollInactiveChat[] =
|
||||
"auto-scroll-inactive-chat";
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kMessagesPerPageFirst = 10;
|
||||
constexpr auto kMessagesPerPage = 10;
|
||||
constexpr auto kMessagesPerPageFirst = 30;
|
||||
constexpr auto kMessagesPerPage = 50;
|
||||
constexpr auto kPreloadHeightsCount = 3; // when 3 screens to scroll left make a preload request
|
||||
constexpr auto kScrollToVoiceAfterScrolledMs = 1000;
|
||||
constexpr auto kSkipRepaintWhileScrollMs = 100;
|
||||
@@ -194,13 +190,6 @@ constexpr auto kCommonModifiers = 0
|
||||
| Qt::ControlModifier;
|
||||
const auto kPsaAboutPrefix = "cloud_lng_about_psa_";
|
||||
|
||||
base::options::toggle AutoScrollInactiveChat({
|
||||
.id = kOptionAutoScrollInactiveChat,
|
||||
.name = "Enable auto-scroll of inactive chat",
|
||||
.description = "Enable auto-scrolling chat for new messages, "
|
||||
"even when the window is not in focus.",
|
||||
});
|
||||
|
||||
[[nodiscard]] rpl::producer<PeerData*> ActivePeerValue(
|
||||
not_null<Window::SessionController*> controller) {
|
||||
return controller->activeChatValue(
|
||||
@@ -1971,7 +1960,9 @@ void HistoryWidget::applyCloudDraft(History *history) {
|
||||
}
|
||||
|
||||
bool HistoryWidget::insideJumpToEndInsteadOfToUnread() const {
|
||||
if (session().supportMode()) {
|
||||
Expects(_history != nullptr);
|
||||
|
||||
if (session().supportMode() || !_history->trackUnreadMessages()) {
|
||||
return true;
|
||||
} else if (!_historyInited) {
|
||||
return false;
|
||||
@@ -1979,6 +1970,14 @@ bool HistoryWidget::insideJumpToEndInsteadOfToUnread() const {
|
||||
_history->calculateFirstUnreadMessage();
|
||||
const auto unread = _history->firstUnreadMessage();
|
||||
const auto visibleBottom = _scroll->scrollTop() + _scroll->height();
|
||||
DEBUG_LOG(("JumpToEnd(%1, %2, %3): "
|
||||
"unread: %4, top: %5, visibleBottom: %6."
|
||||
).arg(_history->peer->name()
|
||||
).arg(_history->inboxReadTillId().bare
|
||||
).arg(Logs::b(_history->loadedAtBottom())
|
||||
).arg(unread ? unread->data()->id.bare : 0
|
||||
).arg(unread ? _list->itemTop(unread) : -1
|
||||
).arg(visibleBottom));
|
||||
return unread && _list->itemTop(unread) <= visibleBottom;
|
||||
}
|
||||
|
||||
@@ -2004,6 +2003,11 @@ void HistoryWidget::showHistory(
|
||||
|
||||
if (showAtMsgId == ShowAtUnreadMsgId
|
||||
&& insideJumpToEndInsteadOfToUnread()) {
|
||||
DEBUG_LOG(("JumpToEnd(%1, %2, %3): "
|
||||
"Jumping to end instead of unread."
|
||||
).arg(_history->peer->name()
|
||||
).arg(_history->inboxReadTillId().bare
|
||||
).arg(Logs::b(_history->loadedAtBottom())));
|
||||
showAtMsgId = ShowAtTheEndMsgId;
|
||||
} else if (showAtMsgId == ShowForChooseMessagesMsgId) {
|
||||
if (_chooseForReport) {
|
||||
@@ -2023,6 +2027,11 @@ void HistoryWidget::showHistory(
|
||||
}
|
||||
const auto canShowNow = _history->isReadyFor(showAtMsgId);
|
||||
if (!canShowNow) {
|
||||
DEBUG_LOG(("JumpToEnd(%1, %2, %3): Showing delayed at %4."
|
||||
).arg(_history->peer->name()
|
||||
).arg(_history->inboxReadTillId().bare
|
||||
).arg(Logs::b(_history->loadedAtBottom())
|
||||
).arg(showAtMsgId.bare));
|
||||
delayedShowAt(showAtMsgId);
|
||||
} else {
|
||||
_history->forgetScrollState();
|
||||
@@ -2042,6 +2051,13 @@ void HistoryWidget::showHistory(
|
||||
|
||||
setMsgId(showAtMsgId);
|
||||
if (_historyInited) {
|
||||
DEBUG_LOG(("JumpToEnd(%1, %2, %3): "
|
||||
"Showing instant at %4."
|
||||
).arg(_history->peer->name()
|
||||
).arg(_history->inboxReadTillId().bare
|
||||
).arg(Logs::b(_history->loadedAtBottom())
|
||||
).arg(showAtMsgId.bare));
|
||||
|
||||
const auto to = countInitialScrollTop();
|
||||
const auto item = getItemFromHistoryOrMigrated(
|
||||
_showAtMsgId);
|
||||
@@ -2179,6 +2195,10 @@ void HistoryWidget::showHistory(
|
||||
} else {
|
||||
_chooseForReport = nullptr;
|
||||
}
|
||||
if (_showAtMsgId == ShowAtUnreadMsgId
|
||||
&& !_history->trackUnreadMessages()) {
|
||||
_showAtMsgId = ShowAtTheEndMsgId;
|
||||
}
|
||||
refreshTopBarActiveChat();
|
||||
updateTopBarSelection();
|
||||
|
||||
@@ -2970,7 +2990,9 @@ void HistoryWidget::unreadCountUpdated() {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
_cornerButtons.updateJumpDownVisibility(_history->isForum()
|
||||
const auto hideCounter = _history->isForum()
|
||||
|| !_history->trackUnreadMessages();
|
||||
_cornerButtons.updateJumpDownVisibility(hideCounter
|
||||
? 0
|
||||
: _history->chatListBadgesState().unreadCounter);
|
||||
}
|
||||
@@ -3266,6 +3288,12 @@ void HistoryWidget::loadMessages() {
|
||||
const auto minId = 0;
|
||||
const auto historyHash = uint64(0);
|
||||
|
||||
DEBUG_LOG(("JumpToEnd(%1, %2, %3): Loading up before %4."
|
||||
).arg(_history->peer->name()
|
||||
).arg(_history->inboxReadTillId().bare
|
||||
).arg(Logs::b(_history->loadedAtBottom())
|
||||
).arg(offsetId.bare));
|
||||
|
||||
const auto history = from;
|
||||
const auto type = Data::Histories::RequestType::History;
|
||||
auto &histories = history->owner().histories();
|
||||
@@ -3318,6 +3346,12 @@ void HistoryWidget::loadMessagesDown() {
|
||||
const auto minId = 0;
|
||||
const auto historyHash = uint64(0);
|
||||
|
||||
DEBUG_LOG(("JumpToEnd(%1, %2, %3): Loading down after %4."
|
||||
).arg(_history->peer->name()
|
||||
).arg(_history->inboxReadTillId().bare
|
||||
).arg(Logs::b(_history->loadedAtBottom())
|
||||
).arg(offsetId.bare));
|
||||
|
||||
const auto history = from;
|
||||
const auto type = Data::Histories::RequestType::History;
|
||||
auto &histories = history->owner().histories();
|
||||
@@ -3350,6 +3384,12 @@ void HistoryWidget::delayedShowAt(MsgId showAtMsgId) {
|
||||
clearAllLoadRequests();
|
||||
_delayedShowAtMsgId = showAtMsgId;
|
||||
|
||||
DEBUG_LOG(("JumpToEnd(%1, %2, %3): Loading delayed around %4."
|
||||
).arg(_history->peer->name()
|
||||
).arg(_history->inboxReadTillId().bare
|
||||
).arg(Logs::b(_history->loadedAtBottom())
|
||||
).arg(showAtMsgId.bare));
|
||||
|
||||
auto from = _history;
|
||||
auto offsetId = MsgId();
|
||||
auto offset = 0;
|
||||
@@ -4028,6 +4068,10 @@ void HistoryWidget::doneShow() {
|
||||
void HistoryWidget::cornerButtonsShowAtPosition(
|
||||
Data::MessagePosition position) {
|
||||
if (position == Data::UnreadMessagePosition) {
|
||||
DEBUG_LOG(("JumpToEnd(%1, %2, %3): Show at unread requested."
|
||||
).arg(_history->peer->name()
|
||||
).arg(_history->inboxReadTillId().bare
|
||||
).arg(Logs::b(_history->loadedAtBottom())));
|
||||
showHistory(_peer->id, ShowAtUnreadMsgId);
|
||||
} else if (_peer && position.fullId.peer == _peer->id) {
|
||||
showHistory(_peer->id, position.fullId.msg);
|
||||
@@ -5413,9 +5457,6 @@ int HistoryWidget::countAutomaticScrollTop() {
|
||||
Expects(_list != nullptr);
|
||||
|
||||
if (const auto unread = _history->firstUnreadMessage()) {
|
||||
if (AutoScrollInactiveChat.value()) {
|
||||
return ScrollMax;
|
||||
}
|
||||
const auto firstUnreadTop = _list->itemTop(unread);
|
||||
const auto possibleUnreadBarTop = _scroll->scrollTopMax()
|
||||
+ HistoryView::UnreadBar::height()
|
||||
@@ -5866,7 +5907,10 @@ std::optional<bool> HistoryWidget::cornerButtonsDownShown() {
|
||||
}
|
||||
|
||||
const auto haveUnreadBelowBottom = [&](History *history) {
|
||||
if (!_list || !history || history->unreadCount() <= 0) {
|
||||
if (!_list
|
||||
|| !history
|
||||
|| history->unreadCount() <= 0
|
||||
|| !history->trackUnreadMessages()) {
|
||||
return false;
|
||||
}
|
||||
const auto unread = history->firstUnreadMessage();
|
||||
@@ -6224,7 +6268,7 @@ void HistoryWidget::checkPinnedBarState() {
|
||||
_pinnedBar = std::make_unique<Ui::PinnedBar>(this, [=] {
|
||||
return controller()->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::Any);
|
||||
});
|
||||
}, controller()->gifPauseLevelChanged());
|
||||
auto pinnedRefreshed = Info::Profile::SharedMediaCountValue(
|
||||
_peer,
|
||||
MsgId(0), // topicRootId
|
||||
@@ -7490,20 +7534,44 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) {
|
||||
p.setInactive(
|
||||
controller()->isGifPausedAtLeastFor(Window::GifPauseReason::Any));
|
||||
p.fillRect(myrtlrect(0, backy, width(), backh), st::historyReplyBg);
|
||||
|
||||
const auto media = (!drawWebPagePreview && drawMsgText)
|
||||
? drawMsgText->media()
|
||||
: nullptr;
|
||||
const auto hasPreview = media && media->hasReplyPreview();
|
||||
const auto preview = hasPreview ? media->replyPreview() : nullptr;
|
||||
const auto spoilered = preview && media->hasSpoiler();
|
||||
if (!spoilered) {
|
||||
_replySpoiler = nullptr;
|
||||
} else if (!_replySpoiler) {
|
||||
_replySpoiler = std::make_unique<Ui::SpoilerAnimation>([=] {
|
||||
updateField();
|
||||
});
|
||||
}
|
||||
|
||||
if (_editMsgId || _replyToId || (!hasForward && _kbReplyTo)) {
|
||||
const auto now = crl::now();
|
||||
const auto paused = p.inactive();
|
||||
auto replyLeft = st::historyReplySkip;
|
||||
(_editMsgId ? st::historyEditIcon : st::historyReplyIcon).paint(p, st::historyReplyIconPosition + QPoint(0, backy), width());
|
||||
if (!drawWebPagePreview) {
|
||||
if (drawMsgText) {
|
||||
if (drawMsgText->media() && drawMsgText->media()->hasReplyPreview()) {
|
||||
if (const auto image = drawMsgText->media()->replyPreview()) {
|
||||
if (hasPreview) {
|
||||
if (preview) {
|
||||
auto to = QRect(replyLeft, backy + st::msgReplyPadding.top(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height());
|
||||
p.drawPixmap(to.x(), to.y(), image->pixSingle(
|
||||
image->size() / style::DevicePixelRatio(),
|
||||
p.drawPixmap(to.x(), to.y(), preview->pixSingle(
|
||||
preview->size() / style::DevicePixelRatio(),
|
||||
{
|
||||
.options = Images::Option::RoundSmall,
|
||||
.outer = to.size(),
|
||||
}));
|
||||
if (_replySpoiler) {
|
||||
Ui::FillSpoilerRect(
|
||||
p,
|
||||
to,
|
||||
Ui::DefaultImageSpoiler().frame(
|
||||
_replySpoiler->index(now, paused)));
|
||||
}
|
||||
}
|
||||
replyLeft += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x();
|
||||
}
|
||||
@@ -7521,8 +7589,8 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) {
|
||||
.availableWidth = width() - replyLeft - _fieldBarCancel->width() - st::msgReplyPadding.right(),
|
||||
.palette = &st::historyComposeAreaPalette,
|
||||
.spoiler = Ui::Text::DefaultSpoilerCache(),
|
||||
.now = crl::now(),
|
||||
.paused = p.inactive(),
|
||||
.now = now,
|
||||
.paused = paused,
|
||||
.elisionLines = 1,
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -70,6 +70,7 @@ class RequestsBar;
|
||||
struct PreparedList;
|
||||
class SendFilesWay;
|
||||
class SendAsButton;
|
||||
class SpoilerAnimation;
|
||||
enum class ReportReason;
|
||||
class ChooseThemeController;
|
||||
class ContinuousScroll;
|
||||
@@ -102,8 +103,6 @@ class TTLButton;
|
||||
class BotKeyboard;
|
||||
class HistoryInner;
|
||||
|
||||
extern const char kOptionAutoScrollInactiveChat[];
|
||||
|
||||
class HistoryWidget final
|
||||
: public Window::AbstractSectionWidget
|
||||
, private HistoryView::CornerButtonsDelegate {
|
||||
@@ -626,6 +625,7 @@ private:
|
||||
|
||||
HistoryItem *_replyEditMsg = nullptr;
|
||||
Ui::Text::String _replyEditMsgText;
|
||||
std::unique_ptr<Ui::SpoilerAnimation> _replySpoiler;
|
||||
mutable base::Timer _updateEditTimeLeftDisplay;
|
||||
|
||||
object_ptr<Ui::IconButton> _fieldBarCancel;
|
||||
|
||||
@@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_forum_topic.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "data/data_web_page.h"
|
||||
@@ -987,12 +988,12 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) {
|
||||
updateControlsGeometry(_wrap->size());
|
||||
updateControlsVisibility();
|
||||
updateFieldPlaceholder();
|
||||
updateSendAsButton();
|
||||
updateAttachBotsMenu();
|
||||
//if (!_history) {
|
||||
// return;
|
||||
//}
|
||||
const auto peer = _history->peer;
|
||||
initSendAsButton(peer);
|
||||
if (peer->isChat() && peer->asChat()->noParticipantInfo()) {
|
||||
session().api().requestFullPeer(peer);
|
||||
} else if (const auto channel = peer->asMegagroup()) {
|
||||
@@ -1342,7 +1343,6 @@ void ComposeControls::init() {
|
||||
initField();
|
||||
initTabbedSelector();
|
||||
initSendButton();
|
||||
initSendAsButton();
|
||||
initWriteRestriction();
|
||||
initVoiceRecordBar();
|
||||
initKeyHandler();
|
||||
@@ -2058,17 +2058,24 @@ void ComposeControls::initSendButton() {
|
||||
SendMenu::DefaultScheduleCallback(_wrap.get(), sendMenuType(), send));
|
||||
}
|
||||
|
||||
void ComposeControls::initSendAsButton() {
|
||||
session().sendAsPeers().updated(
|
||||
) | rpl::filter([=](not_null<PeerData*> peer) {
|
||||
return _history && (peer == _history->peer);
|
||||
}) | rpl::start_with_next([=] {
|
||||
void ComposeControls::initSendAsButton(not_null<PeerData*> peer) {
|
||||
using namespace rpl::mappers;
|
||||
|
||||
// SendAsPeers::shouldChoose checks PeerData::canWrite(false).
|
||||
rpl::combine(
|
||||
rpl::single(peer) | rpl::then(
|
||||
session().sendAsPeers().updated() | rpl::filter(_1 == peer)
|
||||
),
|
||||
Data::CanWriteValue(peer, false)
|
||||
) | rpl::skip(1) | rpl::start_with_next([=] {
|
||||
if (updateSendAsButton()) {
|
||||
updateControlsVisibility();
|
||||
updateControlsGeometry(_wrap->size());
|
||||
orderControls();
|
||||
}
|
||||
}, _wrap->lifetime());
|
||||
|
||||
updateSendAsButton();
|
||||
}
|
||||
|
||||
void ComposeControls::cancelInlineBot() {
|
||||
|
||||
@@ -210,7 +210,7 @@ private:
|
||||
void initField();
|
||||
void initTabbedSelector();
|
||||
void initSendButton();
|
||||
void initSendAsButton();
|
||||
void initSendAsButton(not_null<PeerData*> peer);
|
||||
void initWebpageProcess();
|
||||
void initForwardProcess();
|
||||
void initWriteRestriction();
|
||||
|
||||
@@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_forum_topic.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/chat/forward_options_box.h"
|
||||
#include "ui/effects/spoiler_mess.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/painter.h"
|
||||
@@ -306,33 +307,35 @@ void ForwardPanel::paint(
|
||||
return;
|
||||
}
|
||||
const_cast<ForwardPanel*>(this)->checkTexts();
|
||||
const auto now = crl::now();
|
||||
const auto paused = p.inactive();
|
||||
const auto firstItem = _data.items.front();
|
||||
const auto firstMedia = firstItem->media();
|
||||
const auto hasPreview = (_data.items.size() < 2)
|
||||
&& firstMedia
|
||||
&& firstMedia->hasReplyPreview();
|
||||
const auto preview = hasPreview ? firstMedia->replyPreview() : nullptr;
|
||||
const auto spoiler = preview && firstMedia->hasSpoiler();
|
||||
if (!spoiler) {
|
||||
_spoiler = nullptr;
|
||||
} else if (!_spoiler) {
|
||||
_spoiler = std::make_unique<Ui::SpoilerAnimation>(_repaint);
|
||||
}
|
||||
if (preview) {
|
||||
auto to = QRect(
|
||||
x,
|
||||
y + st::msgReplyPadding.top(),
|
||||
st::msgReplyBarSize.height(),
|
||||
st::msgReplyBarSize.height());
|
||||
if (preview->width() == preview->height()) {
|
||||
p.drawPixmap(to.x(), to.y(), preview->pix());
|
||||
} else {
|
||||
auto from = (preview->width() > preview->height())
|
||||
? QRect(
|
||||
(preview->width() - preview->height()) / 2,
|
||||
0,
|
||||
preview->height(),
|
||||
preview->height())
|
||||
: QRect(
|
||||
0,
|
||||
(preview->height() - preview->width()) / 2,
|
||||
preview->width(),
|
||||
preview->width());
|
||||
p.drawPixmap(to, preview->pix(), from);
|
||||
p.drawPixmap(to.x(), to.y(), preview->pixSingle(
|
||||
preview->size() / style::DevicePixelRatio(),
|
||||
{
|
||||
.options = Images::Option::RoundSmall,
|
||||
.outer = to.size(),
|
||||
}));
|
||||
if (_spoiler) {
|
||||
Ui::FillSpoilerRect(p, to, Ui::DefaultImageSpoiler().frame(
|
||||
_spoiler->index(now, paused)));
|
||||
}
|
||||
const auto skip = st::msgReplyBarSize.height()
|
||||
+ st::msgReplyBarSkip
|
||||
@@ -355,8 +358,8 @@ void ForwardPanel::paint(
|
||||
.availableWidth = available,
|
||||
.palette = &st::historyComposeAreaPalette,
|
||||
.spoiler = Ui::Text::DefaultSpoilerCache(),
|
||||
.now = crl::now(),
|
||||
.paused = p.inactive(),
|
||||
.now = now,
|
||||
.paused = paused,
|
||||
.elisionLines = 1,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
class Painter;
|
||||
class HistoryItem;
|
||||
|
||||
namespace Ui {
|
||||
class SpoilerAnimation;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Data {
|
||||
class Thread;
|
||||
} // namespace Data
|
||||
@@ -57,6 +61,7 @@ private:
|
||||
|
||||
rpl::event_stream<> _itemsUpdated;
|
||||
Ui::Text::String _from, _text;
|
||||
mutable std::unique_ptr<Ui::SpoilerAnimation> _spoiler;
|
||||
int _nameVersion = 0;
|
||||
|
||||
};
|
||||
|
||||
@@ -3749,6 +3749,8 @@ void ListWidget::viewReplaced(not_null<const Element*> was, Element *now) {
|
||||
}
|
||||
|
||||
void ListWidget::itemRemoved(not_null<const HistoryItem*> item) {
|
||||
saveScrollState();
|
||||
|
||||
if (_reactionsItem.current() == item) {
|
||||
_reactionsItem = nullptr;
|
||||
}
|
||||
|
||||
@@ -38,8 +38,9 @@ namespace {
|
||||
[[nodiscard]] Ui::MessageBarContent ContentWithPreview(
|
||||
not_null<HistoryItem*> item,
|
||||
Image *preview,
|
||||
bool spoiler,
|
||||
Fn<void()> repaint) {
|
||||
auto result = ContentWithoutPreview(item, std::move(repaint));
|
||||
auto result = ContentWithoutPreview(item, repaint);
|
||||
if (!preview) {
|
||||
static const auto kEmpty = [&] {
|
||||
const auto size = st::historyReplyHeight * cIntRetinaFactor();
|
||||
@@ -51,8 +52,10 @@ namespace {
|
||||
return result;
|
||||
}();
|
||||
result.preview = kEmpty;
|
||||
result.spoilerRepaint = nullptr;
|
||||
} else {
|
||||
result.preview = preview->original();
|
||||
result.spoilerRepaint = spoiler ? repaint : nullptr;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -90,7 +93,11 @@ namespace {
|
||||
}) | rpl::then(
|
||||
rpl::single(kFullLoaded)
|
||||
) | rpl::map([=] {
|
||||
return ContentWithPreview(item, media->replyPreview(), repaint);
|
||||
return ContentWithPreview(
|
||||
item,
|
||||
media->replyPreview(),
|
||||
media->hasSpoiler(),
|
||||
repaint);
|
||||
});
|
||||
}) | rpl::flatten_latest();
|
||||
}
|
||||
|
||||
@@ -442,7 +442,7 @@ void RepliesWidget::setupRootView() {
|
||||
_rootView = std::make_unique<Ui::PinnedBar>(this, [=] {
|
||||
return controller()->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::Any);
|
||||
});
|
||||
}, controller()->gifPauseLevelChanged());
|
||||
_rootView->setContent(rpl::combine(
|
||||
RootViewContent(
|
||||
_history,
|
||||
@@ -1592,7 +1592,7 @@ void RepliesWidget::checkPinnedBarState() {
|
||||
_pinnedBar = std::make_unique<Ui::PinnedBar>(this, [=] {
|
||||
return controller()->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::Any);
|
||||
});
|
||||
}, controller()->gifPauseLevelChanged());
|
||||
auto pinnedRefreshed = Info::Profile::SharedMediaCountValue(
|
||||
_history->peer,
|
||||
_rootId,
|
||||
|
||||
@@ -71,8 +71,8 @@ bool DrawWebPageDataPreview(
|
||||
}
|
||||
|
||||
const auto preview = photo
|
||||
? photo->getReplyPreview(Data::FileOrigin(), context)
|
||||
: document->getReplyPreview(Data::FileOrigin(), context);
|
||||
? photo->getReplyPreview(Data::FileOrigin(), context, false)
|
||||
: document->getReplyPreview(Data::FileOrigin(), context, false);
|
||||
if (preview) {
|
||||
const auto w = preview->width();
|
||||
const auto h = preview->height();
|
||||
|
||||
@@ -128,7 +128,7 @@ void ShowSetToast(
|
||||
lt_link,
|
||||
Ui::Text::Link(
|
||||
tr::lng_profile_changed_photo_link(tr::now),
|
||||
u"tg://settings/information"_q),
|
||||
u"tg://settings/edit_profile"_q),
|
||||
Ui::Text::WithEntities)
|
||||
);
|
||||
auto st = std::make_shared<style::Toast>(st::historyPremiumToast);
|
||||
|
||||
@@ -166,6 +166,7 @@ QSize WebPage::countOptimalSize() {
|
||||
} else if (!_data->document
|
||||
&& _data->photo
|
||||
&& _data->type != WebPageType::Photo
|
||||
&& _data->type != WebPageType::Document
|
||||
&& _data->type != WebPageType::Video) {
|
||||
if (_data->type == WebPageType::Profile) {
|
||||
_asArticle = true;
|
||||
@@ -755,6 +756,7 @@ ClickHandlerPtr WebPage::replaceAttachLink(
|
||||
|| _data->type == WebPageType::Video) {
|
||||
return _openl;
|
||||
} else if (_data->type == WebPageType::Photo
|
||||
|| _data->type == WebPageType::Document
|
||||
|| _data->siteName == u"Twitter"_q
|
||||
|| _data->siteName == u"Facebook"_q) {
|
||||
// leave photo link
|
||||
|
||||
@@ -412,14 +412,14 @@ infoLabeledOneLine: FlatLabel(defaultFlatLabel) {
|
||||
margin: margins(5px, 5px, 5px, 5px);
|
||||
}
|
||||
infoLabelSkip: 2px;
|
||||
infoLabel: FlatLabel(infoLabeledOneLine) {
|
||||
textFg: windowSubTextFg;
|
||||
}
|
||||
infoLabeled: FlatLabel(infoLabeledOneLine) {
|
||||
minWidth: 180px;
|
||||
maxHeight: 0px;
|
||||
margin: margins(5px, 5px, 5px, 5px);
|
||||
}
|
||||
infoLabel: FlatLabel(infoLabeled) {
|
||||
textFg: windowSubTextFg;
|
||||
}
|
||||
|
||||
infoBlockHeaderLabel: FlatLabel(infoProfileStatus) {
|
||||
textFg: windowBoldFg;
|
||||
|
||||
@@ -497,7 +497,7 @@ void AttachWebView::request(const WebViewButton &button) {
|
||||
data.vquery_id().v,
|
||||
qs(data.vurl()),
|
||||
button.text,
|
||||
button.fromMenu);
|
||||
button.fromMenu || button.url.isEmpty());
|
||||
});
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_requestId = 0;
|
||||
@@ -742,7 +742,7 @@ void AttachWebView::requestMenu(
|
||||
_action->history->peer->input,
|
||||
_bot->inputUser,
|
||||
MTP_string(url),
|
||||
MTPstring(), // url
|
||||
MTPstring(), // start_param
|
||||
MTP_dataJSON(MTP_bytes(Window::Theme::WebViewParams().json)),
|
||||
MTP_string("tdesktop"),
|
||||
MTP_int(_action->replyTo.bare),
|
||||
@@ -800,7 +800,7 @@ void AttachWebView::show(
|
||||
uint64 queryId,
|
||||
const QString &url,
|
||||
const QString &buttonText,
|
||||
bool fromMenu) {
|
||||
bool allowClipboardRead) {
|
||||
Expects(_bot != nullptr && _action.has_value());
|
||||
|
||||
const auto close = crl::guard(this, [=] {
|
||||
@@ -924,7 +924,7 @@ void AttachWebView::show(
|
||||
.menuButtons = buttons,
|
||||
.handleMenuButton = handleMenuButton,
|
||||
.themeParams = [] { return Window::Theme::WebViewParams(); },
|
||||
.allowClipboardRead = fromMenu,
|
||||
.allowClipboardRead = allowClipboardRead,
|
||||
});
|
||||
*panel = _panel.get();
|
||||
started(queryId);
|
||||
|
||||
@@ -135,7 +135,7 @@ private:
|
||||
uint64 queryId,
|
||||
const QString &url,
|
||||
const QString &buttonText = QString(),
|
||||
bool fromMenu = false);
|
||||
bool allowClipboardRead = false);
|
||||
void confirmAddToMenu(
|
||||
AttachWebViewBot bot,
|
||||
Fn<void()> callback = nullptr);
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "inline_bots/inline_bot_layout_item.h"
|
||||
|
||||
#include "base/never_freed_pointer.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_peer.h"
|
||||
@@ -24,7 +25,7 @@ namespace InlineBots {
|
||||
namespace Layout {
|
||||
namespace {
|
||||
|
||||
NeverFreedPointer<DocumentItems> documentItemsMap;
|
||||
base::NeverFreedPointer<DocumentItems> documentItemsMap;
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -100,10 +100,13 @@ CodeWidget::CodeWidget(
|
||||
|
||||
_code->setDigitsCountMax(getData()->codeLength);
|
||||
|
||||
setTitleText(getData()->codeByFragmentUrl.isEmpty()
|
||||
? rpl::single(Ui::FormatPhone(getData()->phone))
|
||||
: tr::lng_intro_fragment_title());
|
||||
updateDescText();
|
||||
setTitleText(_isFragment.value(
|
||||
) | rpl::map([=](bool isFragment) {
|
||||
return !isFragment
|
||||
? rpl::single(Ui::FormatPhone(getData()->phone))
|
||||
: tr::lng_intro_fragment_title();
|
||||
}) | rpl::flatten_latest());
|
||||
|
||||
account->setHandleLoginCode([=](const QString &code) {
|
||||
_code->setText(code);
|
||||
@@ -126,6 +129,7 @@ int CodeWidget::errorTop() const {
|
||||
void CodeWidget::updateDescText() {
|
||||
const auto byTelegram = getData()->codeByTelegram;
|
||||
const auto isFragment = !getData()->codeByFragmentUrl.isEmpty();
|
||||
_isFragment = isFragment;
|
||||
setDescriptionText(
|
||||
isFragment
|
||||
? tr::lng_intro_fragment_about(
|
||||
@@ -136,8 +140,7 @@ void CodeWidget::updateDescText() {
|
||||
Ui::Text::RichLangValue)
|
||||
: (byTelegram ? tr::lng_code_from_telegram : tr::lng_code_desc)(
|
||||
Ui::Text::RichLangValue));
|
||||
if (isFragment) {
|
||||
} else if (getData()->codeByTelegram) {
|
||||
if (getData()->codeByTelegram) {
|
||||
_noTelegramCode->show();
|
||||
_callTimer.cancel();
|
||||
} else {
|
||||
@@ -420,15 +423,19 @@ void CodeWidget::submitCode() {
|
||||
}
|
||||
|
||||
rpl::producer<QString> CodeWidget::nextButtonText() const {
|
||||
return getData()->codeByFragmentUrl.isEmpty()
|
||||
? Step::nextButtonText()
|
||||
: tr::lng_intro_fragment_button();
|
||||
return _isFragment.value(
|
||||
) | rpl::map([=](bool isFragment) {
|
||||
return isFragment
|
||||
? tr::lng_intro_fragment_button()
|
||||
: Step::nextButtonText();
|
||||
}) | rpl::flatten_latest();
|
||||
}
|
||||
|
||||
const style::RoundButton *CodeWidget::nextButtonStyle() const {
|
||||
return !getData()->codeByFragmentUrl.isEmpty()
|
||||
? &st::introFragmentButton
|
||||
: nullptr;
|
||||
rpl::producer<const style::RoundButton*> CodeWidget::nextButtonStyle() const {
|
||||
return _isFragment.value(
|
||||
) | rpl::map([](bool isFragment) {
|
||||
return isFragment ? &st::introFragmentButton : nullptr;
|
||||
});
|
||||
}
|
||||
|
||||
void CodeWidget::noTelegramCode() {
|
||||
|
||||
@@ -56,7 +56,7 @@ public:
|
||||
void cancelled() override;
|
||||
void submit() override;
|
||||
rpl::producer<QString> nextButtonText() const override;
|
||||
const style::RoundButton *nextButtonStyle() const override;
|
||||
rpl::producer<const style::RoundButton*> nextButtonStyle() const override;
|
||||
|
||||
void updateDescText();
|
||||
|
||||
@@ -96,6 +96,8 @@ private:
|
||||
QString _sentCode;
|
||||
mtpRequestId _sentRequest = 0;
|
||||
|
||||
rpl::variable<bool> _isFragment = false;
|
||||
|
||||
base::Timer _callTimer;
|
||||
CallStatus _callStatus = CallStatus();
|
||||
int _callTimeout;
|
||||
|
||||
@@ -119,8 +119,8 @@ rpl::producer<QString> Step::nextButtonText() const {
|
||||
return tr::lng_intro_next();
|
||||
}
|
||||
|
||||
const style::RoundButton *Step::nextButtonStyle() const {
|
||||
return nullptr;
|
||||
rpl::producer<const style::RoundButton*> Step::nextButtonStyle() const {
|
||||
return rpl::single((const style::RoundButton*)(nullptr));
|
||||
}
|
||||
|
||||
void Step::goBack() {
|
||||
|
||||
@@ -81,7 +81,8 @@ public:
|
||||
|
||||
virtual void submit() = 0;
|
||||
[[nodiscard]] virtual rpl::producer<QString> nextButtonText() const;
|
||||
[[nodiscard]] virtual const style::RoundButton *nextButtonStyle() const;
|
||||
[[nodiscard]] virtual auto nextButtonStyle() const
|
||||
-> rpl::producer<const style::RoundButton*>;
|
||||
|
||||
[[nodiscard]] int contentLeft() const;
|
||||
[[nodiscard]] int contentTop() const;
|
||||
|
||||
@@ -342,17 +342,21 @@ void Widget::historyMove(StackAction action, Animate animate) {
|
||||
hideAndDestroy(std::exchange(_terms, { nullptr }));
|
||||
}
|
||||
{
|
||||
const auto st = getStep()->nextButtonStyle();
|
||||
const auto nextStyle = st ? st : &st::introNextButton;
|
||||
if (_nextStyle != nextStyle) {
|
||||
_nextStyle = nextStyle;
|
||||
_next = nullptr;
|
||||
_next.create(
|
||||
this,
|
||||
object_ptr<Ui::RoundButton>(this, nullptr, *nextStyle));
|
||||
showControls();
|
||||
updateControlsGeometry();
|
||||
}
|
||||
getStep()->nextButtonStyle(
|
||||
) | rpl::start_with_next([=](const style::RoundButton *st) {
|
||||
const auto nextStyle = st ? st : &st::introNextButton;
|
||||
if (_nextStyle != nextStyle) {
|
||||
_nextStyle = nextStyle;
|
||||
const auto wasShown = _next->toggled();
|
||||
_next.destroy();
|
||||
_next.create(
|
||||
this,
|
||||
object_ptr<Ui::RoundButton>(this, nullptr, *nextStyle));
|
||||
showControls();
|
||||
updateControlsGeometry();
|
||||
_next->toggle(wasShown, anim::type::instant);
|
||||
}
|
||||
}, _next->lifetime());
|
||||
}
|
||||
|
||||
getStep()->finishInit();
|
||||
|
||||
@@ -66,5 +66,9 @@ inline QString EmailConfirmationExpired() {
|
||||
return u"This email confirmation has expired. Please setup two-step verification once again."_q;
|
||||
}
|
||||
|
||||
inline QString AutostartEnableError() {
|
||||
return u"Could not register for autostart."_q;
|
||||
}
|
||||
|
||||
} // namespace Hard
|
||||
} // namespace Lang
|
||||
|
||||
@@ -41,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "settings/settings_intro.h"
|
||||
#include "platform/platform_notifications_manager.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/options.h"
|
||||
#include "base/variant.h"
|
||||
#include "window/notifications_manager.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
@@ -76,8 +77,18 @@ void FeedLangTestingKey(int key) {
|
||||
}
|
||||
}
|
||||
|
||||
base::options::toggle AutoScrollInactiveChat({
|
||||
.id = kOptionAutoScrollInactiveChat,
|
||||
.name = "Mark as read of inactive chat",
|
||||
.description = "Mark new messages as read and scroll the chat "
|
||||
"even when the window is not in focus.",
|
||||
});
|
||||
|
||||
} // namespace
|
||||
|
||||
const char kOptionAutoScrollInactiveChat[] =
|
||||
"auto-scroll-inactive-chat";
|
||||
|
||||
MainWindow::MainWindow(not_null<Window::Controller*> controller)
|
||||
: Platform::MainWindow(controller) {
|
||||
resize(st::windowDefaultWidth, st::windowDefaultHeight);
|
||||
@@ -538,8 +549,8 @@ bool MainWindow::markingAsRead() const {
|
||||
&& !_main->isHidden()
|
||||
&& !_main->animatingShow()
|
||||
&& !_layer
|
||||
&& isActive()
|
||||
&& !_main->session().updates().isIdle();
|
||||
&& (AutoScrollInactiveChat.value()
|
||||
|| (isActive() && !_main->session().updates().isIdle()));
|
||||
}
|
||||
|
||||
void MainWindow::checkActivation() {
|
||||
|
||||
@@ -42,6 +42,8 @@ class LayerStackWidget;
|
||||
|
||||
class MediaPreviewWidget;
|
||||
|
||||
extern const char kOptionAutoScrollInactiveChat[];
|
||||
|
||||
class MainWindow : public Platform::MainWindow {
|
||||
public:
|
||||
explicit MainWindow(not_null<Window::Controller*> controller);
|
||||
|
||||
@@ -285,10 +285,10 @@ QImage PrepareBlurredBackground(QSize outer, QImage frame) {
|
||||
const auto bsize = frame.size();
|
||||
const auto copyw = std::min(
|
||||
bsize.width(),
|
||||
outer.width() * bsize.height() / outer.height());
|
||||
std::max(outer.width() * bsize.height() / outer.height(), 1));
|
||||
const auto copyh = std::min(
|
||||
bsize.height(),
|
||||
outer.height() * bsize.width() / outer.width());
|
||||
std::max(outer.height() * bsize.width() / outer.width(), 1));
|
||||
auto copy = (bsize == QSize(copyw, copyh))
|
||||
? std::move(frame)
|
||||
: frame.copy(
|
||||
|
||||
@@ -254,6 +254,7 @@ struct OverlayWidget::PipWrap {
|
||||
|
||||
PipDelegate delegate;
|
||||
Pip wrapped;
|
||||
rpl::lifetime lifetime;
|
||||
};
|
||||
|
||||
OverlayWidget::Streamed::Streamed(
|
||||
@@ -1035,13 +1036,18 @@ void OverlayWidget::fillContextMenuActions(const MenuCallback &addAction) {
|
||||
}, &st::mediaMenuIconProfile);
|
||||
}();
|
||||
[&] { // Report userpic.
|
||||
if (!_peer || !_photo ) {
|
||||
if (!_peer || !_photo) {
|
||||
return;
|
||||
}
|
||||
using Type = SharedMediaType;
|
||||
if (userPhotosKey()) {
|
||||
if (_peer->isSelf() || _peer->isNotificationsUser()) {
|
||||
return;
|
||||
} else if (const auto user = _peer->asUser()) {
|
||||
if (user->hasPersonalPhoto()
|
||||
&& user->userpicPhotoId() == _photo->id) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if ((sharedMediaType().value_or(Type::File) == Type::ChatPhoto)
|
||||
|| (_peer->userpicPhotoId() == _photo->id)) {
|
||||
@@ -1523,10 +1529,7 @@ void OverlayWidget::hideControls(bool force) {
|
||||
if (!_dropdown->isHidden()
|
||||
|| (_streamed && _streamed->controls.hasMenu())
|
||||
|| _menu
|
||||
|| _mousePressed
|
||||
|| (_fullScreenVideo
|
||||
&& !videoIsGifOrUserpic()
|
||||
&& _streamed->controls.geometry().contains(_lastMouseMovePos))) {
|
||||
|| _mousePressed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -3144,18 +3147,22 @@ void OverlayWidget::refreshClipControllerGeometry() {
|
||||
|
||||
void OverlayWidget::playbackControlsPlay() {
|
||||
playbackPauseResume();
|
||||
activateControls();
|
||||
}
|
||||
|
||||
void OverlayWidget::playbackControlsPause() {
|
||||
playbackPauseResume();
|
||||
activateControls();
|
||||
}
|
||||
|
||||
void OverlayWidget::playbackControlsToFullScreen() {
|
||||
playbackToggleFullScreen();
|
||||
activateControls();
|
||||
}
|
||||
|
||||
void OverlayWidget::playbackControlsFromFullScreen() {
|
||||
playbackToggleFullScreen();
|
||||
activateControls();
|
||||
}
|
||||
|
||||
void OverlayWidget::playbackControlsToPictureInPicture() {
|
||||
@@ -3274,7 +3281,7 @@ void OverlayWidget::playbackControlsSeekProgress(crl::time position) {
|
||||
if (!_streamed->instance.player().paused()
|
||||
&& !_streamed->instance.player().finished()) {
|
||||
_streamed->pausedBySeek = true;
|
||||
playbackControlsPause();
|
||||
playbackPauseResume();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3284,6 +3291,7 @@ void OverlayWidget::playbackControlsSeekFinished(crl::time position) {
|
||||
_streamingStartPaused = !_streamed->pausedBySeek
|
||||
&& !_streamed->instance.player().finished();
|
||||
restartAtSeekPosition(position);
|
||||
activateControls();
|
||||
}
|
||||
|
||||
void OverlayWidget::playbackControlsVolumeChanged(float64 volume) {
|
||||
@@ -3301,6 +3309,7 @@ float64 OverlayWidget::playbackControlsCurrentVolume() {
|
||||
void OverlayWidget::playbackControlsVolumeToggled() {
|
||||
const auto volume = Core::App().settings().videoVolume();
|
||||
playbackControlsVolumeChanged(volume ? 0. : _lastPositiveVolume);
|
||||
activateControls();
|
||||
}
|
||||
|
||||
void OverlayWidget::playbackControlsVolumeChangeFinished() {
|
||||
@@ -3308,6 +3317,7 @@ void OverlayWidget::playbackControlsVolumeChangeFinished() {
|
||||
if (volume > 0.) {
|
||||
_lastPositiveVolume = volume;
|
||||
}
|
||||
activateControls();
|
||||
}
|
||||
|
||||
void OverlayWidget::playbackControlsSpeedChanged(float64 speed) {
|
||||
@@ -3334,14 +3344,14 @@ void OverlayWidget::switchToPip() {
|
||||
Expects(_document != nullptr);
|
||||
|
||||
const auto document = _document;
|
||||
const auto message = _message;
|
||||
const auto messageId = _message ? _message->fullId() : FullMsgId();
|
||||
const auto topicRootId = _topicRootId;
|
||||
const auto closeAndContinue = [=] {
|
||||
_showAsPip = false;
|
||||
show(OpenRequest(
|
||||
findWindow(false),
|
||||
document,
|
||||
message,
|
||||
document->owner().message(messageId),
|
||||
topicRootId,
|
||||
true));
|
||||
};
|
||||
@@ -3352,6 +3362,16 @@ void OverlayWidget::switchToPip() {
|
||||
_streamed->instance.shared(),
|
||||
closeAndContinue,
|
||||
[=] { _pip = nullptr; });
|
||||
|
||||
if (const auto raw = _message) {
|
||||
raw->history()->owner().itemRemoved(
|
||||
) | rpl::filter([=](not_null<const HistoryItem*> item) {
|
||||
return (raw == item);
|
||||
}) | rpl::start_with_next([=] {
|
||||
_pip = nullptr;
|
||||
}, _pip->lifetime);
|
||||
}
|
||||
|
||||
if (isHidden()) {
|
||||
clearBeforeHide();
|
||||
clearAfterHide();
|
||||
@@ -4529,7 +4549,7 @@ void OverlayWidget::handleMouseRelease(
|
||||
updateOver(position);
|
||||
|
||||
if (const auto activated = ClickHandler::unpressed()) {
|
||||
if (activated->dragText() == u"internal:show_saved_message"_q) {
|
||||
if (activated->url() == u"internal:show_saved_message"_q) {
|
||||
showSaveMsgFile();
|
||||
return;
|
||||
}
|
||||
@@ -4934,6 +4954,11 @@ void OverlayWidget::updateHeader() {
|
||||
&& (index == count - 1)
|
||||
&& SyncUserFallbackPhotoViewer(_user)) {
|
||||
_headerText = tr::lng_mediaview_profile_public_photo(tr::now);
|
||||
} else if (_user
|
||||
&& _user->hasPersonalPhoto()
|
||||
&& _photo
|
||||
&& (_photo->id == _user->userpicPhotoId())) {
|
||||
_headerText = tr::lng_mediaview_profile_photo_by_you(tr::now);
|
||||
} else {
|
||||
_headerText = tr::lng_mediaview_n_of_amount(
|
||||
tr::now,
|
||||
|
||||
@@ -21,11 +21,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/view/history_view_list_widget.h" // HistoryView::SelectedItem.
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "mainwindow.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
#include "styles/style_widgets.h"
|
||||
|
||||
@@ -76,7 +78,12 @@ void AddAction(
|
||||
: &st::menuIconDownload;
|
||||
const auto showToast = documents.empty();
|
||||
|
||||
const auto weak = base::make_weak(controller);
|
||||
const auto saveImages = [=](const QString &folderPath) {
|
||||
const auto controller = weak.get();
|
||||
if (!controller) {
|
||||
return;
|
||||
}
|
||||
const auto session = &controller->session();
|
||||
const auto downloadPath = folderPath.isEmpty()
|
||||
? Core::App().settings().downloadPath()
|
||||
@@ -141,6 +148,10 @@ void AddAction(
|
||||
saveDocuments(folderPath);
|
||||
callback();
|
||||
};
|
||||
const auto controller = weak.get();
|
||||
if (!controller) {
|
||||
return;
|
||||
}
|
||||
if (Core::App().settings().askDownloadPath()) {
|
||||
const auto initialPath = [] {
|
||||
const auto path = Core::App().settings().downloadPath();
|
||||
@@ -159,7 +170,7 @@ void AddAction(
|
||||
}
|
||||
};
|
||||
FileDialog::GetFolder(
|
||||
nullptr,
|
||||
controller->window().widget().get(),
|
||||
tr::lng_download_path_choose(tr::now),
|
||||
initialPath,
|
||||
handleFolder);
|
||||
|
||||
@@ -116,7 +116,7 @@ void ConfigLoader::enumerate() {
|
||||
}
|
||||
|
||||
void ConfigLoader::refreshSpecialLoader() {
|
||||
if (_proxyEnabled) {
|
||||
if (_proxyEnabled || _instance->isKeysDestroyer()) {
|
||||
_specialLoader.reset();
|
||||
return;
|
||||
}
|
||||
@@ -136,6 +136,7 @@ void ConfigLoader::setPhone(const QString &phone) {
|
||||
}
|
||||
|
||||
void ConfigLoader::createSpecialLoader() {
|
||||
const auto testMode = _instance->isTestMode();
|
||||
_triedSpecialEndpoints.clear();
|
||||
_specialLoader = std::make_unique<SpecialConfigRequest>([=](
|
||||
DcId dcId,
|
||||
@@ -147,7 +148,7 @@ void ConfigLoader::createSpecialLoader() {
|
||||
} else {
|
||||
addSpecialEndpoint(dcId, ip, port, secret);
|
||||
}
|
||||
}, _instance->configValues().txtDomainString, _phone);
|
||||
}, testMode, _instance->configValues().txtDomainString, _phone);
|
||||
}
|
||||
|
||||
void ConfigLoader::addSpecialEndpoint(
|
||||
|
||||
@@ -65,7 +65,7 @@ QByteArray DnsUserAgent() {
|
||||
static const auto kResult = QByteArray(
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||
"Chrome/107.0.5304.110 Safari/537.36");
|
||||
"Chrome/108.0.5359.98 Safari/537.36");
|
||||
return kResult;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "mtproto/details/mtproto_tcp_socket.h"
|
||||
|
||||
#include "base/invoke_queued.h"
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
|
||||
namespace MTP::details {
|
||||
|
||||
@@ -47,7 +46,7 @@ TcpSocket::TcpSocket(
|
||||
wrap([=] { _readyRead.fire({}); }));
|
||||
connect(
|
||||
&_socket,
|
||||
base::QTcpSocket_error,
|
||||
&QAbstractSocket::errorOccurred,
|
||||
wrap([=](Error e) { handleError(e); }));
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/bytes.h"
|
||||
#include "base/invoke_queued.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
|
||||
#include <QtCore/QtEndian>
|
||||
#include <range/v3/algorithm/reverse.hpp>
|
||||
@@ -482,7 +481,7 @@ TlsSocket::TlsSocket(
|
||||
wrap([=] { plainReadyRead(); }));
|
||||
connect(
|
||||
&_socket,
|
||||
base::QTcpSocket_error,
|
||||
&QAbstractSocket::errorOccurred,
|
||||
wrap([=](Error e) { handleError(e); }));
|
||||
}
|
||||
|
||||
|
||||
@@ -542,7 +542,7 @@ void Instance::Private::syncHttpUnixtime() {
|
||||
InvokeQueued(_instance, [=] {
|
||||
_httpUnixtimeLoader = nullptr;
|
||||
});
|
||||
}, configValues().txtDomainString);
|
||||
}, isTestMode(), configValues().txtDomainString);
|
||||
}
|
||||
|
||||
void Instance::Private::restartedByTimeout(ShiftedDcId shiftedDcId) {
|
||||
|
||||
@@ -188,6 +188,7 @@ SpecialConfigRequest::SpecialConfigRequest(
|
||||
int port,
|
||||
bytes::const_span secret)> callback,
|
||||
Fn<void()> timeDoneCallback,
|
||||
bool isTestMode,
|
||||
const QString &domainString,
|
||||
const QString &phone)
|
||||
: _callback(std::move(callback))
|
||||
@@ -198,16 +199,7 @@ SpecialConfigRequest::SpecialConfigRequest(
|
||||
|
||||
_manager.setProxy(QNetworkProxy::NoProxy);
|
||||
|
||||
auto domains = DnsDomains();
|
||||
const auto domainsCount = domains.size();
|
||||
|
||||
std::random_device rd;
|
||||
ranges::shuffle(domains, std::mt19937(rd()));
|
||||
const auto takeDomain = [&] {
|
||||
const auto result = domains.back();
|
||||
domains.pop_back();
|
||||
return result;
|
||||
};
|
||||
const auto shuffle = [&](int from, int till) {
|
||||
Expects(till > from);
|
||||
|
||||
@@ -219,14 +211,9 @@ SpecialConfigRequest::SpecialConfigRequest(
|
||||
|
||||
_attempts = {};
|
||||
_attempts.push_back({ Type::Google, "dns.google.com" });
|
||||
_attempts.push_back({ Type::Google, takeDomain(), "dns" });
|
||||
_attempts.push_back({ Type::Mozilla, "mozilla.cloudflare-dns.com" });
|
||||
_attempts.push_back({ Type::RemoteConfig, "firebaseremoteconfig" });
|
||||
while (!domains.empty()) {
|
||||
_attempts.push_back({ Type::Google, takeDomain(), "dns" });
|
||||
}
|
||||
if (!_timeDoneCallback) {
|
||||
_attempts.push_back({ Type::Realtime, "firebaseio.com" });
|
||||
_attempts.push_back({ Type::FireStore, "firestore" });
|
||||
for (const auto &domain : DnsDomains()) {
|
||||
_attempts.push_back({ Type::FireStore, domain, "firestore" });
|
||||
@@ -234,12 +221,15 @@ SpecialConfigRequest::SpecialConfigRequest(
|
||||
}
|
||||
|
||||
shuffle(0, 2);
|
||||
shuffle(2, 4);
|
||||
if (!_timeDoneCallback) {
|
||||
shuffle(
|
||||
_attempts.size() - (2 + domainsCount),
|
||||
_attempts.size() - domainsCount);
|
||||
shuffle(_attempts.size() - domainsCount, _attempts.size());
|
||||
shuffle(_attempts.size() - (int(DnsDomains().size()) + 1), _attempts.size());
|
||||
}
|
||||
if (isTestMode) {
|
||||
_attempts.erase(ranges::remove_if(_attempts, [](
|
||||
const Attempt &attempt) {
|
||||
return (attempt.type != Type::Google)
|
||||
&& (attempt.type != Type::Mozilla);
|
||||
}), _attempts.end());
|
||||
}
|
||||
ranges::reverse(_attempts); // We go from last to first.
|
||||
|
||||
@@ -252,17 +242,25 @@ SpecialConfigRequest::SpecialConfigRequest(
|
||||
const std::string &ip,
|
||||
int port,
|
||||
bytes::const_span secret)> callback,
|
||||
bool isTestMode,
|
||||
const QString &domainString,
|
||||
const QString &phone)
|
||||
: SpecialConfigRequest(std::move(callback), nullptr, domainString, phone) {
|
||||
: SpecialConfigRequest(
|
||||
std::move(callback),
|
||||
nullptr,
|
||||
isTestMode,
|
||||
domainString,
|
||||
phone) {
|
||||
}
|
||||
|
||||
SpecialConfigRequest::SpecialConfigRequest(
|
||||
Fn<void()> timeDoneCallback,
|
||||
bool isTestMode,
|
||||
const QString &domainString)
|
||||
: SpecialConfigRequest(
|
||||
nullptr,
|
||||
std::move(timeDoneCallback),
|
||||
isTestMode,
|
||||
domainString,
|
||||
QString()) {
|
||||
}
|
||||
|
||||
@@ -25,10 +25,12 @@ public:
|
||||
const std::string &ip,
|
||||
int port,
|
||||
bytes::const_span secret)> callback,
|
||||
bool isTestMode,
|
||||
const QString &domainString,
|
||||
const QString &phone);
|
||||
SpecialConfigRequest(
|
||||
Fn<void()> timeDoneCallback,
|
||||
bool isTestMode,
|
||||
const QString &domainString);
|
||||
|
||||
private:
|
||||
@@ -52,6 +54,7 @@ private:
|
||||
int port,
|
||||
bytes::const_span secret)> callback,
|
||||
Fn<void()> timeDoneCallback,
|
||||
bool isTestMode,
|
||||
const QString &domainString,
|
||||
const QString &phone);
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user