Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -60,7 +60,7 @@ if (NOT DESKTOP_APP_USE_PACKAGED)
|
||||
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
|
||||
|
||||
@@ -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.3.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 4,4,3,0
|
||||
PRODUCTVERSION 4,4,3,0
|
||||
FILEVERSION 4,5,3,0
|
||||
PRODUCTVERSION 4,5,3,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "4.4.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
|
||||
VALUE "FileVersion", "4.5.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "4.4.3.0"
|
||||
VALUE "ProductVersion", "4.5.3.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,3,0
|
||||
PRODUCTVERSION 4,5,3,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "4.4.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
|
||||
VALUE "FileVersion", "4.5.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "4.4.3.0"
|
||||
VALUE "ProductVersion", "4.5.3.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -869,7 +869,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
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 AppBetaVersion = true;
|
||||
constexpr auto AppVersion = 4005003;
|
||||
constexpr auto AppVersionStr = "4.5.3";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -1198,26 +1198,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 {
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -3739,6 +3739,9 @@ void InnerWidget::setupShortcuts() {
|
||||
Command::ChatPinned3,
|
||||
Command::ChatPinned4,
|
||||
Command::ChatPinned5,
|
||||
Command::ChatPinned6,
|
||||
Command::ChatPinned7,
|
||||
Command::ChatPinned8,
|
||||
};
|
||||
auto &&pinned = ranges::views::zip(
|
||||
kPinned,
|
||||
@@ -3791,14 +3794,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([=] {
|
||||
|
||||
@@ -252,6 +252,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 +491,11 @@ void Row::stopLastRipple() {
|
||||
}
|
||||
}
|
||||
|
||||
void Row::clearRipple() {
|
||||
BasicRow::clearRipple();
|
||||
clearTopicJumpRipple();
|
||||
}
|
||||
|
||||
void Row::addTopicJumpRipple(
|
||||
QPoint origin,
|
||||
not_null<Ui::TopicJumpCache*> topicJumpCache,
|
||||
@@ -503,10 +512,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,
|
||||
|
||||
@@ -2160,15 +2160,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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -283,9 +283,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 +313,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);
|
||||
@@ -463,14 +472,15 @@ void HistoryMessageReply::paint(
|
||||
|
||||
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 +492,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) {
|
||||
|
||||
@@ -246,6 +246,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;
|
||||
|
||||
@@ -176,8 +176,8 @@ const char kOptionAutoScrollInactiveChat[] =
|
||||
|
||||
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;
|
||||
@@ -6224,7 +6224,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 +7490,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 +7545,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;
|
||||
@@ -626,6 +627,7 @@ private:
|
||||
|
||||
HistoryItem *_replyEditMsg = nullptr;
|
||||
Ui::Text::String _replyEditMsgText;
|
||||
std::unique_ptr<Ui::SpoilerAnimation> _replySpoiler;
|
||||
mutable base::Timer _updateEditTimeLeftDisplay;
|
||||
|
||||
object_ptr<Ui::IconButton> _fieldBarCancel;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -254,6 +254,7 @@ struct OverlayWidget::PipWrap {
|
||||
|
||||
PipDelegate delegate;
|
||||
Pip wrapped;
|
||||
rpl::lifetime lifetime;
|
||||
};
|
||||
|
||||
OverlayWidget::Streamed::Streamed(
|
||||
@@ -3334,14 +3335,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 +3353,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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1610,9 +1610,11 @@ Link::Link(
|
||||
}),
|
||||
parent->fullId());
|
||||
} else if (_page->photo) {
|
||||
if (_page->type == WebPageType::Profile || _page->type == WebPageType::Video) {
|
||||
if (_page->type == WebPageType::Profile
|
||||
|| _page->type == WebPageType::Video) {
|
||||
_photol = createHandler(_page->url);
|
||||
} else if (_page->type == WebPageType::Photo
|
||||
|| _page->type == WebPageType::Document
|
||||
|| _page->siteName == u"Twitter"_q
|
||||
|| _page->siteName == u"Facebook"_q) {
|
||||
_photol = std::make_shared<PhotoOpenClickHandler>(
|
||||
|
||||
@@ -119,11 +119,13 @@ namespace {
|
||||
constexpr auto kDesktopFile = ":/misc/org.telegram.desktop.desktop"_cs;
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
void PortalAutostart(bool start, bool silent) {
|
||||
bool PortalAutostart(bool start, bool silent) {
|
||||
if (cExeName().isEmpty()) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto error = false;
|
||||
|
||||
try {
|
||||
const auto connection = Gio::DBus::Connection::get_sync(
|
||||
Gio::DBus::BusType::SESSION);
|
||||
@@ -182,14 +184,18 @@ void PortalAutostart(bool start, bool silent) {
|
||||
const auto response = base::Platform::GlibVariantCast<
|
||||
uint>(parameters.get_child(0));
|
||||
|
||||
if (response && !silent) {
|
||||
LOG(("Portal Autostart Error: Request denied"));
|
||||
if (response) {
|
||||
if (!silent) {
|
||||
LOG(("Portal Autostart Error: Request denied"));
|
||||
}
|
||||
error = true;
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
if (!silent) {
|
||||
LOG(("Portal Autostart Error: %1").arg(
|
||||
QString::fromStdString(e.what())));
|
||||
}
|
||||
error = true;
|
||||
}
|
||||
|
||||
loop->quit();
|
||||
@@ -227,7 +233,10 @@ void PortalAutostart(bool start, bool silent) {
|
||||
LOG(("Portal Autostart Error: %1").arg(
|
||||
QString::fromStdString(e.what())));
|
||||
}
|
||||
error = true;
|
||||
}
|
||||
|
||||
return !error;
|
||||
}
|
||||
|
||||
void LaunchGApplication() {
|
||||
@@ -620,25 +629,31 @@ bool AutostartSupported() {
|
||||
|
||||
void AutostartToggle(bool enabled, Fn<void(bool)> done) {
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
const auto guard = gsl::finally([&] {
|
||||
if (done) {
|
||||
done(enabled);
|
||||
}
|
||||
});
|
||||
const auto success = [&] {
|
||||
const auto silent = !done;
|
||||
|
||||
if (KSandbox::isFlatpak()) {
|
||||
return PortalAutostart(enabled, silent);
|
||||
}
|
||||
|
||||
const auto silent = !done;
|
||||
if (KSandbox::isFlatpak()) {
|
||||
PortalAutostart(enabled, silent);
|
||||
} else {
|
||||
const auto autostart = QStandardPaths::writableLocation(
|
||||
QStandardPaths::GenericConfigLocation)
|
||||
+ u"/autostart/"_q;
|
||||
|
||||
if (enabled) {
|
||||
GenerateDesktopFile(autostart, { u"-autostart"_q }, true, silent);
|
||||
} else {
|
||||
QFile::remove(autostart + QGuiApplication::desktopFileName());
|
||||
if (!enabled) {
|
||||
return QFile::remove(
|
||||
autostart + QGuiApplication::desktopFileName());
|
||||
}
|
||||
|
||||
return GenerateDesktopFile(
|
||||
autostart,
|
||||
{ u"-autostart"_q },
|
||||
true,
|
||||
silent);
|
||||
}();
|
||||
|
||||
if (done) {
|
||||
done(enabled && success);
|
||||
}
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
}
|
||||
|
||||
@@ -142,51 +142,78 @@ void DeleteMyModules() {
|
||||
RemoveDirectory(modules.c_str());
|
||||
}
|
||||
|
||||
void ManageAppLink(bool create, bool silent, int path_csidl, const wchar_t *args, const wchar_t *description) {
|
||||
bool ManageAppLink(
|
||||
bool create,
|
||||
bool silent,
|
||||
const GUID &folderId,
|
||||
const wchar_t *args,
|
||||
const wchar_t *description) {
|
||||
if (cExeName().isEmpty()) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
WCHAR startupFolder[MAX_PATH];
|
||||
HRESULT hr = SHGetFolderPath(0, path_csidl, 0, SHGFP_TYPE_CURRENT, startupFolder);
|
||||
if (SUCCEEDED(hr)) {
|
||||
QString lnk = QString::fromWCharArray(startupFolder) + '\\' + AppFile.utf16() + u".lnk"_q;
|
||||
if (create) {
|
||||
const auto shellLink = base::WinRT::TryCreateInstance<IShellLink>(
|
||||
CLSID_ShellLink,
|
||||
CLSCTX_INPROC_SERVER);
|
||||
if (shellLink) {
|
||||
QString exe = QDir::toNativeSeparators(cExeDir() + cExeName()), dir = QDir::toNativeSeparators(QDir(cWorkingDir()).absolutePath());
|
||||
shellLink->SetArguments(args);
|
||||
shellLink->SetPath(exe.toStdWString().c_str());
|
||||
shellLink->SetWorkingDirectory(dir.toStdWString().c_str());
|
||||
shellLink->SetDescription(description);
|
||||
|
||||
if (const auto propertyStore = shellLink.try_as<IPropertyStore>()) {
|
||||
PROPVARIANT appIdPropVar;
|
||||
hr = InitPropVariantFromString(AppUserModelId::getId(), &appIdPropVar);
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = propertyStore->SetValue(AppUserModelId::getKey(), appIdPropVar);
|
||||
PropVariantClear(&appIdPropVar);
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = propertyStore->Commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto persistFile = shellLink.try_as<IPersistFile>()) {
|
||||
hr = persistFile->Save(lnk.toStdWString().c_str(), TRUE);
|
||||
} else {
|
||||
if (!silent) LOG(("App Error: could not create interface IID_IPersistFile %1").arg(hr));
|
||||
}
|
||||
} else {
|
||||
if (!silent) LOG(("App Error: could not create instance of IID_IShellLink %1").arg(hr));
|
||||
}
|
||||
} else {
|
||||
QFile::remove(lnk);
|
||||
PWSTR startupFolder;
|
||||
HRESULT hr = SHGetKnownFolderPath(
|
||||
folderId,
|
||||
KF_FLAG_CREATE,
|
||||
nullptr,
|
||||
&startupFolder);
|
||||
const auto guard = gsl::finally([&] {
|
||||
CoTaskMemFree(startupFolder);
|
||||
});
|
||||
if (!SUCCEEDED(hr)) {
|
||||
WCHAR buffer[64];
|
||||
const auto size = base::array_size(buffer) - 1;
|
||||
const auto length = StringFromGUID2(folderId, buffer, size);
|
||||
if (length > 0 && length <= size) {
|
||||
buffer[length] = 0;
|
||||
if (!silent) LOG(("App Error: could not get %1 folder: %2").arg(buffer).arg(hr));
|
||||
}
|
||||
} else {
|
||||
if (!silent) LOG(("App Error: could not get CSIDL %1 folder %2").arg(path_csidl).arg(hr));
|
||||
return false;
|
||||
}
|
||||
const auto lnk = QString::fromWCharArray(startupFolder)
|
||||
+ '\\'
|
||||
+ AppFile.utf16()
|
||||
+ u".lnk"_q;
|
||||
if (!create) {
|
||||
QFile::remove(lnk);
|
||||
return true;
|
||||
}
|
||||
const auto shellLink = base::WinRT::TryCreateInstance<IShellLink>(
|
||||
CLSID_ShellLink,
|
||||
CLSCTX_INPROC_SERVER);
|
||||
if (!shellLink) {
|
||||
if (!silent) LOG(("App Error: could not create instance of IID_IShellLink %1").arg(hr));
|
||||
return false;
|
||||
}
|
||||
QString exe = QDir::toNativeSeparators(cExeDir() + cExeName()), dir = QDir::toNativeSeparators(QDir(cWorkingDir()).absolutePath());
|
||||
shellLink->SetArguments(args);
|
||||
shellLink->SetPath(exe.toStdWString().c_str());
|
||||
shellLink->SetWorkingDirectory(dir.toStdWString().c_str());
|
||||
shellLink->SetDescription(description);
|
||||
|
||||
if (const auto propertyStore = shellLink.try_as<IPropertyStore>()) {
|
||||
PROPVARIANT appIdPropVar;
|
||||
hr = InitPropVariantFromString(AppUserModelId::getId(), &appIdPropVar);
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = propertyStore->SetValue(AppUserModelId::getKey(), appIdPropVar);
|
||||
PropVariantClear(&appIdPropVar);
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = propertyStore->Commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto persistFile = shellLink.try_as<IPersistFile>();
|
||||
if (!persistFile) {
|
||||
if (!silent) LOG(("App Error: could not create interface IID_IPersistFile %1").arg(hr));
|
||||
return false;
|
||||
}
|
||||
hr = persistFile->Save(lnk.toStdWString().c_str(), TRUE);
|
||||
if (!SUCCEEDED(hr)) {
|
||||
if (!silent) LOG(("App Error: could not save IPersistFile to path %1").arg(lnk));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -415,9 +442,15 @@ void AutostartToggle(bool enabled, Fn<void(bool)> done) {
|
||||
done ? Fn<void(bool)>(callback) : nullptr);
|
||||
#else // OS_WIN_STORE
|
||||
const auto silent = !done;
|
||||
ManageAppLink(enabled, silent, CSIDL_STARTUP, L"-autostart", L"Telegram autorun link.\nYou can disable autorun in Telegram settings.");
|
||||
const auto success = ManageAppLink(
|
||||
enabled,
|
||||
silent,
|
||||
FOLDERID_Startup,
|
||||
L"-autostart",
|
||||
L"Telegram autorun link.\n"
|
||||
"You can disable autorun in Telegram settings.");
|
||||
if (done) {
|
||||
done(enabled);
|
||||
done(enabled && success);
|
||||
}
|
||||
#endif // OS_WIN_STORE
|
||||
}
|
||||
@@ -586,7 +619,13 @@ void NewVersionLaunched(int oldVersion) {
|
||||
} // namespace Platform
|
||||
|
||||
void psSendToMenu(bool send, bool silent) {
|
||||
ManageAppLink(send, silent, CSIDL_SENDTO, L"-sendpath", L"Telegram send to link.\nYou can disable send to menu item in Telegram settings.");
|
||||
ManageAppLink(
|
||||
send,
|
||||
silent,
|
||||
FOLDERID_SendTo,
|
||||
L"-sendpath",
|
||||
L"Telegram send to link.\n"
|
||||
"You can disable send to menu item in Telegram settings.");
|
||||
}
|
||||
|
||||
bool psLaunchMaps(const Data::LocationPoint &point) {
|
||||
|
||||
@@ -500,10 +500,17 @@ void SetupSystemIntegrationContent(
|
||||
) | rpl::filter([](bool checked) {
|
||||
return (checked != cAutoStart());
|
||||
}) | rpl::start_with_next([=](bool checked) {
|
||||
const auto weak = base::make_weak(controller);
|
||||
cSetAutoStart(checked);
|
||||
Platform::AutostartToggle(checked, crl::guard(autostart, [=](
|
||||
bool enabled) {
|
||||
autostart->setChecked(enabled);
|
||||
if (checked && !enabled && weak) {
|
||||
weak->window().showToast(
|
||||
Lang::Hard::AutostartEnableError());
|
||||
}
|
||||
Ui::PostponeCall(autostart, [=] {
|
||||
autostart->setChecked(enabled);
|
||||
});
|
||||
if (enabled || !minimized->entity()->checked()) {
|
||||
Local::writeSettings();
|
||||
} else {
|
||||
|
||||
@@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "window/window_session_controller.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "settings/settings_common.h"
|
||||
#include "storage/localimageloader.h"
|
||||
#include "styles/style_settings.h"
|
||||
#include "styles/style_layers.h"
|
||||
|
||||
@@ -142,6 +143,7 @@ void SetupExperimental(
|
||||
addToggle(Ui::GL::kOptionAllowLinuxNvidiaOpenGL);
|
||||
addToggle(Ui::kOptionUseSmallMsgBubbleRadius);
|
||||
addToggle(Media::Player::kOptionDisableAutoplayNext);
|
||||
addToggle(kOptionSendLargePhotos);
|
||||
addToggle(Settings::kOptionMonoSettingsIcons);
|
||||
addToggle(Webview::kOptionWebviewDebugEnabled);
|
||||
addToggle(kOptionAutoScrollInactiveChat);
|
||||
|
||||
@@ -1163,9 +1163,10 @@ object_ptr<Ui::RpWidget> ProfilePhotoPrivacyController::setupBelowWidget(
|
||||
|
||||
_saveAdditional = [=] {
|
||||
if (removeButton->isHidden()) {
|
||||
const auto photoId = SyncUserFallbackPhotoViewer(self);
|
||||
if (const auto photo = self->owner().photo(*photoId)) {
|
||||
controller->session().api().peerPhoto().clear(photo);
|
||||
if (const auto photoId = SyncUserFallbackPhotoViewer(self)) {
|
||||
if (const auto photo = self->owner().photo(*photoId)) {
|
||||
controller->session().api().peerPhoto().clear(photo);
|
||||
}
|
||||
}
|
||||
} else if (!state->localOriginal.isNull()) {
|
||||
controller->session().api().peerPhoto().uploadFallback(
|
||||
|
||||
@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_user.h"
|
||||
#include "core/file_utilities.h"
|
||||
#include "core/mime_type.h"
|
||||
#include "base/options.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "base/random.h"
|
||||
#include "editor/scene/scene_item_sticker.h"
|
||||
@@ -49,6 +50,13 @@ constexpr auto kRecompressAfterBpp = 4;
|
||||
|
||||
using Ui::ValidateThumbDimensions;
|
||||
|
||||
base::options::toggle SendLargePhotos({
|
||||
.id = kOptionSendLargePhotos,
|
||||
.name = "Send large photos",
|
||||
.description = "Increase the side limit on compressed images to 2560px.",
|
||||
});
|
||||
std::atomic<bool> SendLargePhotosAtomic/* = false*/;
|
||||
|
||||
struct PreparedFileThumbnail {
|
||||
uint64 id = 0;
|
||||
QString name;
|
||||
@@ -191,8 +199,22 @@ struct PreparedFileThumbnail {
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] int PhotoSideLimit(bool large) {
|
||||
return large ? 2560 : 1280;
|
||||
}
|
||||
|
||||
[[nodiscard]] int PhotoSideLimitAtomic() {
|
||||
return PhotoSideLimit(SendLargePhotosAtomic.load());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const char kOptionSendLargePhotos[] = "send-large-photos";
|
||||
|
||||
int PhotoSideLimit() {
|
||||
return PhotoSideLimit(SendLargePhotos.value());
|
||||
}
|
||||
|
||||
SendMediaPrepare::SendMediaPrepare(
|
||||
const QString &file,
|
||||
const PeerId &peer,
|
||||
@@ -518,7 +540,6 @@ void FileLoadResult::setThumbData(const QByteArray &thumbdata) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
FileLoadTask::FileLoadTask(
|
||||
not_null<Main::Session*> session,
|
||||
const QString &filepath,
|
||||
@@ -543,6 +564,8 @@ FileLoadTask::FileLoadTask(
|
||||
Expects(to.options.scheduled
|
||||
|| !to.replaceMediaOf
|
||||
|| IsServerMsgId(to.replaceMediaOf));
|
||||
|
||||
SendLargePhotosAtomic = SendLargePhotos.value();
|
||||
}
|
||||
|
||||
FileLoadTask::FileLoadTask(
|
||||
@@ -942,8 +965,9 @@ void FileLoadTask::process(Args &&args) {
|
||||
}
|
||||
auto medium = (w > 320 || h > 320) ? fullimage.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation) : fullimage;
|
||||
|
||||
const auto downscaled = (w > 1280 || h > 1280);
|
||||
auto full = downscaled ? fullimage.scaled(1280, 1280, Qt::KeepAspectRatio, Qt::SmoothTransformation) : fullimage;
|
||||
const auto limit = PhotoSideLimitAtomic();
|
||||
const auto downscaled = (w > limit || h > limit);
|
||||
auto full = downscaled ? fullimage.scaled(limit, limit, Qt::KeepAspectRatio, Qt::SmoothTransformation) : fullimage;
|
||||
if (downscaled) {
|
||||
fullimagebytes = fullimageformat = QByteArray();
|
||||
}
|
||||
|
||||
@@ -24,6 +24,10 @@ constexpr auto kFileSizeLimit = 2'000 * int64(1024 * 1024);
|
||||
// Load files up to 4'000 MB.
|
||||
constexpr auto kFileSizePremiumLimit = 4'000 * int64(1024 * 1024);
|
||||
|
||||
extern const char kOptionSendLargePhotos[];
|
||||
|
||||
[[nodiscard]] int PhotoSideLimit();
|
||||
|
||||
enum class SendMediaType {
|
||||
Photo,
|
||||
Audio,
|
||||
|
||||
@@ -47,13 +47,10 @@ bool ValidVideoForAlbum(const PreparedFileInformation::Video &video) {
|
||||
return Ui::ValidateThumbDimensions(width, height);
|
||||
}
|
||||
|
||||
QSize PrepareShownDimensions(const QImage &preview) {
|
||||
constexpr auto kMaxWidth = 1280;
|
||||
constexpr auto kMaxHeight = 1280;
|
||||
|
||||
QSize PrepareShownDimensions(const QImage &preview, int sideLimit) {
|
||||
const auto result = preview.size();
|
||||
return (result.width() > kMaxWidth || result.height() > kMaxHeight)
|
||||
? result.scaled(kMaxWidth, kMaxHeight, Qt::KeepAspectRatio)
|
||||
return (result.width() > sideLimit || result.height() > sideLimit)
|
||||
? result.scaled(sideLimit, sideLimit, Qt::KeepAspectRatio)
|
||||
: result;
|
||||
}
|
||||
|
||||
@@ -63,10 +60,11 @@ void PrepareDetailsInParallel(PreparedList &result, int previewWidth) {
|
||||
if (result.files.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto sideLimit = PhotoSideLimit(); // Get on main thread.
|
||||
QSemaphore semaphore;
|
||||
for (auto &file : result.files) {
|
||||
crl::async([=, &semaphore, &file] {
|
||||
PrepareDetails(file, previewWidth);
|
||||
PrepareDetails(file, previewWidth, sideLimit);
|
||||
semaphore.release();
|
||||
});
|
||||
}
|
||||
@@ -272,7 +270,7 @@ std::optional<PreparedList> PreparedFileFromFilesDialog(
|
||||
}
|
||||
}
|
||||
|
||||
void PrepareDetails(PreparedFile &file, int previewWidth) {
|
||||
void PrepareDetails(PreparedFile &file, int previewWidth, int sideLimit) {
|
||||
if (!file.path.isEmpty()) {
|
||||
file.information = FileLoadTask::ReadMediaInformation(
|
||||
file.path,
|
||||
@@ -293,7 +291,7 @@ void PrepareDetails(PreparedFile &file, int previewWidth) {
|
||||
&file.information->media)) {
|
||||
Assert(!image->data.isNull());
|
||||
if (ValidPhotoForAlbum(*image, file.information->filemime)) {
|
||||
UpdateImageDetails(file, previewWidth);
|
||||
UpdateImageDetails(file, previewWidth, sideLimit);
|
||||
file.type = PreparedFile::Type::Photo;
|
||||
} else if (image->animated) {
|
||||
file.type = PreparedFile::Type::None;
|
||||
@@ -303,7 +301,10 @@ void PrepareDetails(PreparedFile &file, int previewWidth) {
|
||||
if (ValidVideoForAlbum(*video)) {
|
||||
auto blurred = Images::Blur(
|
||||
Images::Opaque(base::duplicate(video->thumbnail)));
|
||||
file.shownDimensions = PrepareShownDimensions(video->thumbnail);
|
||||
file.originalDimensions = video->thumbnail.size();
|
||||
file.shownDimensions = PrepareShownDimensions(
|
||||
video->thumbnail,
|
||||
sideLimit);
|
||||
file.preview = std::move(blurred).scaledToWidth(
|
||||
previewWidth * cIntRetinaFactor(),
|
||||
Qt::SmoothTransformation);
|
||||
@@ -316,7 +317,10 @@ void PrepareDetails(PreparedFile &file, int previewWidth) {
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateImageDetails(PreparedFile &file, int previewWidth) {
|
||||
void UpdateImageDetails(
|
||||
PreparedFile &file,
|
||||
int previewWidth,
|
||||
int sideLimit) {
|
||||
const auto image = std::get_if<Image>(&file.information->media);
|
||||
if (!image) {
|
||||
return;
|
||||
@@ -326,7 +330,8 @@ void UpdateImageDetails(PreparedFile &file, int previewWidth) {
|
||||
? Editor::ImageModified(image->data, image->modifications)
|
||||
: image->data;
|
||||
Assert(!preview.isNull());
|
||||
file.shownDimensions = PrepareShownDimensions(preview);
|
||||
file.originalDimensions = preview.size();
|
||||
file.shownDimensions = PrepareShownDimensions(preview, sideLimit);
|
||||
const auto toWidth = std::min(
|
||||
previewWidth,
|
||||
style::ConvertScale(preview.width())
|
||||
|
||||
@@ -51,8 +51,11 @@ enum class MimeDataState {
|
||||
QImage &&image,
|
||||
QByteArray &&content,
|
||||
int previewWidth);
|
||||
void PrepareDetails(Ui::PreparedFile &file, int previewWidth);
|
||||
void UpdateImageDetails(Ui::PreparedFile &file, int previewWidth);
|
||||
void PrepareDetails(Ui::PreparedFile &file, int previewWidth, int sideLimit);
|
||||
void UpdateImageDetails(
|
||||
Ui::PreparedFile &file,
|
||||
int previewWidth,
|
||||
int sideLimit);
|
||||
|
||||
bool ApplyModifications(Ui::PreparedList &list);
|
||||
|
||||
|
||||
@@ -81,8 +81,7 @@ AlbumThumbnail::AlbumThumbnail(
|
||||
const auto filepath = file.path;
|
||||
if (filepath.isEmpty()) {
|
||||
_name = "image.png";
|
||||
_status = FormatImageSizeText(_fullPreview.size()
|
||||
/ _fullPreview.devicePixelRatio());
|
||||
_status = FormatImageSizeText(file.originalDimensions);
|
||||
} else {
|
||||
auto fileinfo = QFileInfo(filepath);
|
||||
_name = fileinfo.fileName();
|
||||
|
||||
@@ -103,7 +103,6 @@ void ItemSingleFilePreview::preparePreview(not_null<DocumentData*> document) {
|
||||
|
||||
data.name = Text::FormatSongName(filename, songTitle, songPerformer)
|
||||
.string();
|
||||
data.statusText = FormatSizeText(document->size);
|
||||
} else {
|
||||
data.name = document->filename();
|
||||
}
|
||||
|
||||
@@ -80,6 +80,7 @@ struct PreparedFile {
|
||||
std::unique_ptr<Ui::PreparedFileInformation> information;
|
||||
QImage preview;
|
||||
QSize shownDimensions;
|
||||
QSize originalDimensions;
|
||||
Type type = Type::File;
|
||||
bool spoiler = false;
|
||||
};
|
||||
|
||||
@@ -41,8 +41,7 @@ void SingleFilePreview::preparePreview(const PreparedFile &file) {
|
||||
if (filepath.isEmpty()) {
|
||||
auto filename = "image.png";
|
||||
data.name = filename;
|
||||
data.statusText = FormatImageSizeText(preview.size()
|
||||
/ preview.devicePixelRatio());
|
||||
data.statusText = FormatImageSizeText(file.originalDimensions);
|
||||
data.fileIsImage = true;
|
||||
} else {
|
||||
auto fileinfo = QFileInfo(filepath);
|
||||
|
||||
@@ -140,6 +140,7 @@ void MessageBar::tweenTo(MessageBarContent &&content) {
|
||||
? RectPart::Bottom
|
||||
: RectPart::None;
|
||||
animation.imageFrom = grabImagePart();
|
||||
animation.spoilerFrom = std::move(_spoiler);
|
||||
animation.bodyOrTextFrom = grabBodyOrTextPart(animation.bodyAnimation);
|
||||
const auto sameLength = SameFirstPartLength(
|
||||
_content.title,
|
||||
@@ -208,6 +209,12 @@ void MessageBar::updateFromContent(MessageBarContent &&content) {
|
||||
Ui::DialogTextOptions(),
|
||||
_content.context);
|
||||
_image = prepareImage(_content.preview);
|
||||
if (!_content.spoilerRepaint) {
|
||||
_spoiler = nullptr;
|
||||
} else if (!_spoiler) {
|
||||
_spoiler = std::make_unique<SpoilerAnimation>(
|
||||
_content.spoilerRepaint);
|
||||
}
|
||||
}
|
||||
|
||||
QRect MessageBar::imageRect() const {
|
||||
@@ -258,10 +265,21 @@ auto MessageBar::makeGrabGuard() {
|
||||
auto imageShown = _animation
|
||||
? std::move(_animation->imageShown)
|
||||
: Ui::Animations::Simple();
|
||||
return gsl::finally([&, shown = std::move(imageShown)]() mutable {
|
||||
auto spoiler = std::move(_spoiler);
|
||||
auto fromSpoiler = _animation
|
||||
? std::move(_animation->spoilerFrom)
|
||||
: nullptr;
|
||||
return gsl::finally([
|
||||
&,
|
||||
shown = std::move(imageShown),
|
||||
spoiler = std::move(spoiler),
|
||||
fromSpoiler = std::move(fromSpoiler)
|
||||
]() mutable {
|
||||
if (_animation) {
|
||||
_animation->imageShown = std::move(shown);
|
||||
_animation->spoilerFrom = std::move(fromSpoiler);
|
||||
}
|
||||
_spoiler = std::move(spoiler);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -358,12 +376,20 @@ void MessageBar::paint(Painter &p) {
|
||||
: (_animation->movingTo == RectPart::Top)
|
||||
? (shiftTo - shiftFull)
|
||||
: (shiftTo + shiftFull);
|
||||
const auto now = crl::now();
|
||||
const auto paused = p.inactive();
|
||||
|
||||
paintLeftBar(p);
|
||||
|
||||
if (!_animation) {
|
||||
if (!_image.isNull()) {
|
||||
p.drawPixmap(image, _image);
|
||||
paintImageWithSpoiler(
|
||||
p,
|
||||
image,
|
||||
_image,
|
||||
_spoiler.get(),
|
||||
now,
|
||||
paused);
|
||||
}
|
||||
} else if (!_animation->imageTo.isNull()
|
||||
|| (!_animation->imageFrom.isNull()
|
||||
@@ -381,14 +407,30 @@ void MessageBar::paint(Painter &p) {
|
||||
}();
|
||||
if (_animation->bodyMoved.animating()) {
|
||||
p.setOpacity(1. - progress);
|
||||
p.drawPixmap(
|
||||
paintImageWithSpoiler(
|
||||
p,
|
||||
rect.translated(0, shiftFrom),
|
||||
_animation->imageFrom);
|
||||
_animation->imageFrom,
|
||||
_animation->spoilerFrom.get(),
|
||||
now,
|
||||
paused);
|
||||
p.setOpacity(progress);
|
||||
p.drawPixmap(rect.translated(0, shiftTo), _animation->imageTo);
|
||||
paintImageWithSpoiler(
|
||||
p,
|
||||
rect.translated(0, shiftTo),
|
||||
_animation->imageTo,
|
||||
_spoiler.get(),
|
||||
now,
|
||||
paused);
|
||||
p.setOpacity(1.);
|
||||
} else {
|
||||
p.drawPixmap(rect, _image);
|
||||
paintImageWithSpoiler(
|
||||
p,
|
||||
rect,
|
||||
_image,
|
||||
_spoiler.get(),
|
||||
now,
|
||||
paused);
|
||||
}
|
||||
}
|
||||
if (!_animation || _animation->bodyAnimation == BodyAnimation::None) {
|
||||
@@ -409,8 +451,8 @@ void MessageBar::paint(Painter &p) {
|
||||
.availableWidth = body.width(),
|
||||
.palette = &_st.textPalette,
|
||||
.spoiler = Ui::Text::DefaultSpoilerCache(),
|
||||
.now = crl::now(),
|
||||
.paused = p.inactive(),
|
||||
.now = now,
|
||||
.paused = paused,
|
||||
.elisionLines = 1,
|
||||
});
|
||||
}
|
||||
@@ -510,6 +552,21 @@ void MessageBar::ensureGradientsCreated(int size) {
|
||||
_topBarGradient = Images::PixmapFast(std::move(top));
|
||||
}
|
||||
|
||||
void MessageBar::paintImageWithSpoiler(
|
||||
QPainter &p,
|
||||
QRect rect,
|
||||
const QPixmap &image,
|
||||
SpoilerAnimation *spoiler,
|
||||
crl::time now,
|
||||
bool paused) const {
|
||||
p.drawPixmap(rect, image);
|
||||
if (spoiler) {
|
||||
const auto frame = DefaultImageSpoiler().frame(
|
||||
spoiler->index(now, paused));
|
||||
FillSpoilerRect(p, rect, frame);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageBar::paintLeftBar(Painter &p) {
|
||||
const auto state = countBarState();
|
||||
const auto gradientSize = int(std::ceil(state.size * 2.5));
|
||||
|
||||
@@ -18,6 +18,8 @@ struct MessageBar;
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class SpoilerAnimation;
|
||||
|
||||
struct MessageBarContent {
|
||||
int index = 0;
|
||||
int count = 1;
|
||||
@@ -25,6 +27,7 @@ struct MessageBarContent {
|
||||
TextWithEntities text;
|
||||
std::any context;
|
||||
QImage preview;
|
||||
Fn<void()> spoilerRepaint;
|
||||
style::margins margins;
|
||||
};
|
||||
|
||||
@@ -38,7 +41,7 @@ public:
|
||||
void set(MessageBarContent &&content);
|
||||
void set(rpl::producer<MessageBarContent> content);
|
||||
|
||||
[[nodiscard]] not_null<Ui::RpWidget*> widget() {
|
||||
[[nodiscard]] not_null<RpWidget*> widget() {
|
||||
return &_widget;
|
||||
}
|
||||
|
||||
@@ -52,10 +55,10 @@ private:
|
||||
None,
|
||||
};
|
||||
struct Animation {
|
||||
Ui::Animations::Simple bodyMoved;
|
||||
Ui::Animations::Simple imageShown;
|
||||
Ui::Animations::Simple barScroll;
|
||||
Ui::Animations::Simple barTop;
|
||||
Animations::Simple bodyMoved;
|
||||
Animations::Simple imageShown;
|
||||
Animations::Simple barScroll;
|
||||
Animations::Simple barTop;
|
||||
QPixmap bodyOrTextFrom;
|
||||
QPixmap bodyOrTextTo;
|
||||
QPixmap titleSame;
|
||||
@@ -63,6 +66,7 @@ private:
|
||||
QPixmap titleTo;
|
||||
QPixmap imageFrom;
|
||||
QPixmap imageTo;
|
||||
std::unique_ptr<SpoilerAnimation> spoilerFrom;
|
||||
BodyAnimation bodyAnimation = BodyAnimation::None;
|
||||
RectPart movingTo = RectPart::None;
|
||||
};
|
||||
@@ -98,19 +102,28 @@ private:
|
||||
[[nodiscard]] BarState countBarState() const;
|
||||
void ensureGradientsCreated(int size);
|
||||
|
||||
void paintImageWithSpoiler(
|
||||
QPainter &p,
|
||||
QRect rect,
|
||||
const QPixmap &image,
|
||||
SpoilerAnimation *spoiler,
|
||||
crl::time now,
|
||||
bool paused) const;
|
||||
|
||||
[[nodiscard]] static BodyAnimation DetectBodyAnimationType(
|
||||
Animation *currentAnimation,
|
||||
const MessageBarContent ¤tContent,
|
||||
const MessageBarContent &nextContent);
|
||||
|
||||
const style::MessageBar &_st;
|
||||
Ui::RpWidget _widget;
|
||||
RpWidget _widget;
|
||||
Fn<bool()> _customEmojiPaused;
|
||||
MessageBarContent _content;
|
||||
rpl::lifetime _contentLifetime;
|
||||
Ui::Text::String _title, _text;
|
||||
Text::String _title, _text;
|
||||
QPixmap _image, _topBarGradient, _bottomBarGradient;
|
||||
std::unique_ptr<Animation> _animation;
|
||||
std::unique_ptr<SpoilerAnimation> _spoiler;
|
||||
bool _customEmojiRepaintScheduled = false;
|
||||
|
||||
};
|
||||
|
||||
@@ -18,7 +18,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace Ui {
|
||||
|
||||
PinnedBar::PinnedBar(not_null<QWidget*> parent, Fn<bool()> customEmojiPaused)
|
||||
PinnedBar::PinnedBar(
|
||||
not_null<QWidget*> parent,
|
||||
Fn<bool()> customEmojiPaused,
|
||||
rpl::producer<> customEmojiPausedChanges)
|
||||
: _wrap(parent, object_ptr<RpWidget>(parent))
|
||||
, _shadow(std::make_unique<PlainShadow>(_wrap.parentWidget()))
|
||||
, _customEmojiPaused(std::move(customEmojiPaused)) {
|
||||
@@ -30,6 +33,14 @@ PinnedBar::PinnedBar(not_null<QWidget*> parent, Fn<bool()> customEmojiPaused)
|
||||
QPainter(_wrap.entity()).fillRect(clip, st::historyPinnedBg);
|
||||
}, lifetime());
|
||||
_wrap.setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
|
||||
if (customEmojiPausedChanges) {
|
||||
std::move(
|
||||
customEmojiPausedChanges
|
||||
) | rpl::start_with_next([=] {
|
||||
_wrap.entity()->update();
|
||||
}, lifetime());
|
||||
}
|
||||
}
|
||||
|
||||
PinnedBar::~PinnedBar() {
|
||||
|
||||
@@ -22,7 +22,10 @@ class RpWidget;
|
||||
|
||||
class PinnedBar final {
|
||||
public:
|
||||
PinnedBar(not_null<QWidget*> parent, Fn<bool()> customEmojiPaused);
|
||||
PinnedBar(
|
||||
not_null<QWidget*> parent,
|
||||
Fn<bool()> customEmojiPaused,
|
||||
rpl::producer<> customEmojiPausedChanges);
|
||||
~PinnedBar();
|
||||
|
||||
void show();
|
||||
|
||||
@@ -51,8 +51,8 @@ constexpr auto kSaveWindowPositionTimeout = crl::time(1000);
|
||||
|
||||
base::options::toggle ShowChatNameInNewWindow({
|
||||
.id = kOptionShowChatNameInNewWindow,
|
||||
.name = "Show chat name in title of separated windows",
|
||||
.description = "",
|
||||
.name = "Chat name in window title",
|
||||
.description = "Show chat name in the additional windows titles.",
|
||||
});
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -923,11 +923,21 @@ QImage ChatBackground::createCurrentImage() const {
|
||||
}
|
||||
|
||||
bool ChatBackground::tile() const {
|
||||
if (!started()) {
|
||||
const auto &set = nightMode()
|
||||
? _localStoredTileNightValue
|
||||
: _localStoredTileDayValue;
|
||||
if (set.has_value()) {
|
||||
return *set;
|
||||
}
|
||||
}
|
||||
return nightMode() ? _tileNightValue : _tileDayValue;
|
||||
}
|
||||
|
||||
bool ChatBackground::tileDay() const {
|
||||
if (Data::details::IsTestingThemeWallPaper(_paper) ||
|
||||
if (!started() && _localStoredTileDayValue.has_value()) {
|
||||
return *_localStoredTileDayValue;
|
||||
} else if (Data::details::IsTestingThemeWallPaper(_paper) ||
|
||||
Data::details::IsTestingDefaultWallPaper(_paper)) {
|
||||
if (!nightMode()) {
|
||||
return _tileForRevert;
|
||||
@@ -937,7 +947,9 @@ bool ChatBackground::tileDay() const {
|
||||
}
|
||||
|
||||
bool ChatBackground::tileNight() const {
|
||||
if (Data::details::IsTestingThemeWallPaper(_paper) ||
|
||||
if (!started() && _localStoredTileNightValue.has_value()) {
|
||||
return *_localStoredTileNightValue;
|
||||
} else if (Data::details::IsTestingThemeWallPaper(_paper) ||
|
||||
Data::details::IsTestingDefaultWallPaper(_paper)) {
|
||||
if (nightMode()) {
|
||||
return _tileForRevert;
|
||||
|
||||
@@ -161,31 +161,6 @@ void SetActionText(not_null<QAction*> action, rpl::producer<QString> &&text) {
|
||||
}, *lifetime);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsUnreadThread(not_null<Data::Thread*> thread) {
|
||||
return thread->chatListBadgesState().unread;
|
||||
}
|
||||
|
||||
void MarkAsReadThread(not_null<Data::Thread*> thread) {
|
||||
const auto readHistory = [&](not_null<History*> history) {
|
||||
history->owner().histories().readInbox(history);
|
||||
};
|
||||
if (!IsUnreadThread(thread)) {
|
||||
return;
|
||||
} else if (const auto forum = thread->asForum()) {
|
||||
forum->enumerateTopics([](
|
||||
not_null<Data::ForumTopic*> topic) {
|
||||
MarkAsReadThread(topic);
|
||||
});
|
||||
} else if (const auto history = thread->asHistory()) {
|
||||
readHistory(history);
|
||||
if (const auto migrated = history->migrateSibling()) {
|
||||
readHistory(migrated);
|
||||
}
|
||||
} else if (const auto topic = thread->asTopic()) {
|
||||
topic->readTillEnd();
|
||||
}
|
||||
}
|
||||
|
||||
void MarkAsReadChatList(not_null<Dialogs::MainList*> list) {
|
||||
auto mark = std::vector<not_null<History*>>();
|
||||
for (const auto &row : list->indexed()->all()) {
|
||||
@@ -2391,4 +2366,29 @@ bool FillVideoChatMenu(
|
||||
return has || manager;
|
||||
}
|
||||
|
||||
bool IsUnreadThread(not_null<Data::Thread*> thread) {
|
||||
return thread->chatListBadgesState().unread;
|
||||
}
|
||||
|
||||
void MarkAsReadThread(not_null<Data::Thread*> thread) {
|
||||
const auto readHistory = [&](not_null<History*> history) {
|
||||
history->owner().histories().readInbox(history);
|
||||
};
|
||||
if (!IsUnreadThread(thread)) {
|
||||
return;
|
||||
} else if (const auto forum = thread->asForum()) {
|
||||
forum->enumerateTopics([](
|
||||
not_null<Data::ForumTopic*> topic) {
|
||||
MarkAsReadThread(topic);
|
||||
});
|
||||
} else if (const auto history = thread->asHistory()) {
|
||||
readHistory(history);
|
||||
if (const auto migrated = history->migrateSibling()) {
|
||||
readHistory(migrated);
|
||||
}
|
||||
} else if (const auto topic = thread->asTopic()) {
|
||||
topic->readTillEnd();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Window
|
||||
|
||||
@@ -161,4 +161,7 @@ void UnpinAllMessages(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<Data::Thread*> thread);
|
||||
|
||||
[[nodiscard]] bool IsUnreadThread(not_null<Data::Thread*> thread);
|
||||
void MarkAsReadThread(not_null<Data::Thread*> thread);
|
||||
|
||||
} // namespace Window
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.social-networking</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
|
||||
2
Telegram/ThirdParty/tgcalls
vendored
2
Telegram/ThirdParty/tgcalls
vendored
Submodule Telegram/ThirdParty/tgcalls updated: 1c27ba1c6b...92640c4db6
@@ -1,8 +1,8 @@
|
||||
{%- set GIT = "https://github.com" -%}
|
||||
{%- set GIT_FREEDESKTOP = GIT ~ "/gitlab-freedesktop-mirrors" -%}
|
||||
{%- set QT = "6_4_1" -%}
|
||||
{%- set QT_TAG = "v6.4.1" -%}
|
||||
{%- set QT_PREFIX = "/usr/local/desktop-app/Qt-6.4.1" -%}
|
||||
{%- set QT = "6_4_2" -%}
|
||||
{%- set QT_TAG = "v6.4.2" -%}
|
||||
{%- set QT_PREFIX = "/usr/local/desktop-app/Qt-6.4.2" -%}
|
||||
{%- set OPENSSL_VER = "1_1_1" -%}
|
||||
{%- set OPENSSL_PREFIX = "/usr/local/desktop-app/openssl-1.1.1" -%}
|
||||
{%- set CMAKE_VER = "3.24.3" -%}
|
||||
@@ -56,7 +56,7 @@ ENV CXXFLAGS $CFLAGS
|
||||
FROM builder AS patches
|
||||
RUN git clone {{ GIT }}/desktop-app/patches.git \
|
||||
&& cd patches \
|
||||
&& git checkout 4ecd75fad8 \
|
||||
&& git checkout 614ff3aebd \
|
||||
&& rm -rf .git
|
||||
|
||||
FROM builder AS nasm
|
||||
@@ -157,7 +157,7 @@ RUN git clone -b v1.0.9 --depth=1 {{ GIT }}/google/brotli.git \
|
||||
&& rm -rf brotli
|
||||
|
||||
FROM builder AS highway
|
||||
RUN git clone -b 1.0.1 --depth=1 {{ GIT }}/google/highway.git \
|
||||
RUN git clone -b 1.0.2 --depth=1 {{ GIT }}/google/highway.git \
|
||||
&& cd highway \
|
||||
&& cmake -GNinja -B build . \
|
||||
-DCMAKE_BUILD_TYPE=None \
|
||||
@@ -208,7 +208,7 @@ RUN git clone -b 1.0.0 --depth=1 {{ GIT }}/videolan/dav1d.git \
|
||||
&& rm -rf dav1d
|
||||
|
||||
FROM builder AS libde265
|
||||
RUN git clone -b v1.0.8 --depth=1 {{ GIT }}/strukturag/libde265.git \
|
||||
RUN git clone -b v1.0.9 --depth=1 {{ GIT }}/strukturag/libde265.git \
|
||||
&& cd libde265 \
|
||||
&& cmake -GNinja . \
|
||||
-DCMAKE_BUILD_TYPE=None \
|
||||
@@ -238,7 +238,7 @@ RUN git clone -b v1.11.0 --depth=1 {{ GIT }}/webmproject/libvpx.git \
|
||||
FROM builder AS libavif
|
||||
COPY --link --from=dav1d {{ LibrariesPath }}/dav1d-cache /
|
||||
|
||||
RUN git clone -b v0.10.1 --depth=1 {{ GIT }}/AOMediaCodec/libavif.git \
|
||||
RUN git clone -b v0.11.1 --depth=1 {{ GIT }}/AOMediaCodec/libavif.git \
|
||||
&& cd libavif \
|
||||
&& cmake -GNinja -B build . \
|
||||
-DCMAKE_BUILD_TYPE=None \
|
||||
@@ -252,7 +252,7 @@ RUN git clone -b v0.10.1 --depth=1 {{ GIT }}/AOMediaCodec/libavif.git \
|
||||
FROM builder AS libheif
|
||||
COPY --link --from=libde265 {{ LibrariesPath }}/libde265-cache /
|
||||
|
||||
RUN git clone -b v1.13.0 --depth=1 {{ GIT }}/strukturag/libheif.git \
|
||||
RUN git clone -b v1.14.0 --depth=1 {{ GIT }}/strukturag/libheif.git \
|
||||
&& cd libheif \
|
||||
&& cmake -GNinja -B build . \
|
||||
-DCMAKE_BUILD_TYPE=None \
|
||||
@@ -752,7 +752,7 @@ COPY --link --from=pipewire {{ LibrariesPath }}/pipewire-cache /
|
||||
RUN git init tg_owt \
|
||||
&& cd tg_owt \
|
||||
&& git remote add origin {{ GIT }}/desktop-app/tg_owt.git \
|
||||
&& git fetch --depth=1 origin 9b70d7679e86e6c216127d5a6a06ef5aa4f54793 \
|
||||
&& git fetch --depth=1 origin 5098730b9eb6173f0b52068fe2555b7c1015123a \
|
||||
&& git reset --hard FETCH_HEAD \
|
||||
&& git submodule update --init --recursive --depth=1 \
|
||||
&& rm -rf .git \
|
||||
|
||||
@@ -12,8 +12,15 @@ def error(text):
|
||||
print('[ERROR] ' + text)
|
||||
finish(1)
|
||||
|
||||
def nativeToolsError():
|
||||
error('Make sure to run from Native Tools Command Prompt.')
|
||||
|
||||
win = (sys.platform == 'win32')
|
||||
mac = (sys.platform == 'darwin')
|
||||
|
||||
if win and not 'Platform' in os.environ:
|
||||
nativeToolsError()
|
||||
|
||||
win32 = win and (os.environ['Platform'] == 'x86')
|
||||
win64 = win and (os.environ['Platform'] == 'x64')
|
||||
|
||||
@@ -21,7 +28,7 @@ if win and not 'COMSPEC' in os.environ:
|
||||
error('COMSPEC environment variable is not set.')
|
||||
|
||||
if win and not win32 and not win64:
|
||||
error('Make sure to run from Native Tools Command Prompt.')
|
||||
nativeToolsError()
|
||||
|
||||
os.chdir(scriptPath + '/../../../..')
|
||||
|
||||
@@ -397,7 +404,7 @@ if customRunCommand:
|
||||
stage('patches', """
|
||||
git clone https://github.com/desktop-app/patches.git
|
||||
cd patches
|
||||
git checkout 8edd80d889
|
||||
git checkout 9e04e2eb9c
|
||||
""")
|
||||
|
||||
stage('msys64', """
|
||||
@@ -413,13 +420,13 @@ win:
|
||||
del msys64.exe
|
||||
|
||||
bash -c "pacman-key --init; pacman-key --populate; pacman -Syu --noconfirm"
|
||||
pacman -S --noconfirm mingw-w64-x86_64-perl mingw-w64-x86_64-nasm mingw-w64-x86_64-yasm mingw-w64-x86_64-ninja
|
||||
pacman -Syu --noconfirm mingw-w64-x86_64-perl mingw-w64-x86_64-nasm mingw-w64-x86_64-yasm mingw-w64-x86_64-ninja
|
||||
|
||||
SET PATH=%PATH_BACKUP_%
|
||||
""", 'ThirdParty')
|
||||
|
||||
stage('python', """
|
||||
version: """ + (subprocess.run(['python', '-V'], capture_output=True, env=modifiedEnv).stdout.decode().strip().split()[-1] if win else '0') + """
|
||||
version: """ + (subprocess.run(['python', '-V'], capture_output=True, text=True, env=modifiedEnv).stdout.strip().split()[-1] if win else '0') + """
|
||||
win:
|
||||
python -m venv python
|
||||
python\\Scripts\\activate.bat
|
||||
@@ -1464,4 +1471,10 @@ win:
|
||||
# -Dprotobuf_WITH_ZLIB_DEFAULT=OFF
|
||||
# cmake --build . $MAKE_THREADS_CNT
|
||||
|
||||
runStages()
|
||||
if win:
|
||||
currentCodePage = subprocess.run('chcp', capture_output=True, shell=True, text=True, env=modifiedEnv).stdout.strip().split()[-1]
|
||||
subprocess.run('chcp 65001 > nul', shell=True, env=modifiedEnv)
|
||||
runStages()
|
||||
subprocess.run('chcp ' + currentCodePage + ' > nul', shell=True, env=modifiedEnv)
|
||||
else:
|
||||
runStages()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
AppVersion 4004003
|
||||
AppVersionStrMajor 4.4
|
||||
AppVersionStrSmall 4.4.3
|
||||
AppVersionStr 4.4.3
|
||||
BetaChannel 1
|
||||
AppVersion 4005003
|
||||
AppVersionStrMajor 4.5
|
||||
AppVersionStrSmall 4.5.3
|
||||
AppVersionStr 4.5.3
|
||||
BetaChannel 0
|
||||
AlphaVersion 0
|
||||
AppVersionOriginal 4.4.3.beta
|
||||
AppVersionOriginal 4.5.3
|
||||
|
||||
Submodule Telegram/lib_ui updated: 3fea35ff19...7882604a29
@@ -1,3 +1,27 @@
|
||||
4.5.3 (06.01.22)
|
||||
|
||||
- Attempt to fix incoming video in calls from mobile apps.
|
||||
|
||||
4.5.2 (03.01.22)
|
||||
|
||||
- Fix unread reactions button in private chats.
|
||||
- Fix tile background saving after an app update.
|
||||
- Allow Ctrl+6,7,8 to activate extra pinned chats.
|
||||
|
||||
4.5.1 (02.01.22)
|
||||
|
||||
- Fix crash in profile photo privacy edition.
|
||||
- Allow sending photos larger than 1280px (in Experimental Settings).
|
||||
|
||||
4.5 (30.12.22)
|
||||
|
||||
- Media with spoiler effects. You can wrap photos and videos you send in a fuzzy cover by selecting media in the attachment menu and tapping (...) > Hide With Spoiler.
|
||||
- Setting pictures for your contacts. You can choose your own picture for a contact – only you will see it on their profile.
|
||||
- Suggested profile pictures. When editing your contacts, you can suggest a photo for their profile. It will take them just two clicks to add the picture you suggested.
|
||||
- Public profile pictures. If you only allow certain users to see your profile photos, you can set a public picture for everyone else.
|
||||
- Ultimate profile picture privacy. You can set 'Who can see my profile photos' to 'Nobody' and add some users or groups as exceptions.
|
||||
- Member list privacy. Owners of large groups can hide the list of members.
|
||||
|
||||
4.4.3 beta (29.12.22)
|
||||
|
||||
- Support for anonymous numbers from the Fragment.com platform.
|
||||
|
||||
2
cmake
2
cmake
Submodule cmake updated: 4d8c8a0f84...de92292f89
@@ -84,6 +84,10 @@ layout:
|
||||
/usr/lib/$CRAFT_ARCH_TRIPLET/webkit2gtk-4.1:
|
||||
bind: $SNAP/usr/lib/$CRAFT_ARCH_TRIPLET/webkit2gtk-4.1
|
||||
|
||||
package-repositories:
|
||||
- type: apt
|
||||
ppa: kisak/kisak-mesa
|
||||
|
||||
parts:
|
||||
telegram:
|
||||
plugin: cmake
|
||||
@@ -445,9 +449,9 @@ parts:
|
||||
- libxkbcommon-x11-0
|
||||
- zlib1g
|
||||
override-pull: |
|
||||
QT=6_4_1
|
||||
QT=6_4_2
|
||||
|
||||
git clone -b v6.4.1 --depth=1 git://code.qt.io/qt/qt5.git .
|
||||
git clone -b v6.4.2 --depth=1 git://code.qt.io/qt/qt5.git .
|
||||
git submodule update --init --recursive --depth=1 qtbase qtdeclarative qtwayland qtimageformats qtsvg qt5compat qtshadertools
|
||||
|
||||
cd qtbase
|
||||
@@ -513,7 +517,7 @@ parts:
|
||||
webrtc:
|
||||
source: https://github.com/desktop-app/tg_owt.git
|
||||
source-depth: 1
|
||||
source-commit: cc8edd5719556e1711f50da9a3f7a4c59bba574d
|
||||
source-commit: 5098730b9eb6173f0b52068fe2555b7c1015123a
|
||||
plugin: cmake
|
||||
build-packages:
|
||||
- yasm
|
||||
|
||||
Reference in New Issue
Block a user