From 87ebd2720f0f0664b27f8ccebb874f71b8425432 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 17 Mar 2026 14:27:30 +0400 Subject: [PATCH] Send high quality as first-class option. --- Telegram/Resources/icons/menu/quality_hd.svg | 6 + Telegram/Resources/icons/menu/quality_sd.svg | 6 + Telegram/Resources/langs/lang.strings | 2 + Telegram/SourceFiles/apiwrap.cpp | 4 + .../SourceFiles/boxes/edit_caption_box.cpp | 111 ++++++++++++++---- Telegram/SourceFiles/boxes/edit_caption_box.h | 4 + Telegram/SourceFiles/boxes/send_files_box.cpp | 56 +++++++-- Telegram/SourceFiles/boxes/send_files_box.h | 4 +- .../chat_helpers/chat_helpers.style | 6 + .../editor/photo_editor_layer_widget.cpp | 3 +- .../editor/photo_editor_layer_widget.h | 1 + .../SourceFiles/media/view/media_view.style | 2 + Telegram/SourceFiles/menu/menu_send.cpp | 16 +++ Telegram/SourceFiles/menu/menu_send.h | 9 ++ .../settings/settings_experimental.cpp | 2 - .../SourceFiles/storage/localimageloader.cpp | 32 ++--- .../SourceFiles/storage/localimageloader.h | 5 +- .../attach_abstract_single_media_preview.cpp | 11 ++ .../attach_abstract_single_media_preview.h | 2 + .../ui/chat/attach/attach_album_preview.cpp | 25 +++- .../ui/chat/attach/attach_album_thumbnail.cpp | 19 ++- .../ui/chat/attach/attach_album_thumbnail.h | 11 +- .../ui/chat/attach/attach_prepare.cpp | 102 ++++++++++++++++ .../ui/chat/attach/attach_prepare.h | 15 +++ .../ui/chat/attach/attach_send_files_way.cpp | 21 +++- .../ui/chat/attach/attach_send_files_way.h | 5 + .../attach/attach_single_media_preview.cpp | 4 +- Telegram/SourceFiles/ui/menu_icons.style | 2 + 28 files changed, 413 insertions(+), 73 deletions(-) create mode 100644 Telegram/Resources/icons/menu/quality_hd.svg create mode 100644 Telegram/Resources/icons/menu/quality_sd.svg diff --git a/Telegram/Resources/icons/menu/quality_hd.svg b/Telegram/Resources/icons/menu/quality_hd.svg new file mode 100644 index 0000000000..1d4378221f --- /dev/null +++ b/Telegram/Resources/icons/menu/quality_hd.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/Telegram/Resources/icons/menu/quality_sd.svg b/Telegram/Resources/icons/menu/quality_sd.svg new file mode 100644 index 0000000000..e1ef219e0d --- /dev/null +++ b/Telegram/Resources/icons/menu/quality_sd.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index ebb454e6d0..ee7750d1d3 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -5171,8 +5171,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_send_grouped" = "Group items"; "lng_send_compressed_one" = "Compress the image"; "lng_send_compressed" = "Compress images"; +"lng_send_high_quality" = "Send in High Quality"; "lng_send_as_documents_one" = "Send as a document"; "lng_send_as_documents" = "Send as documents"; +"lng_send_standard_quality" = "Standard Quality"; "lng_send_media_invalid_files" = "Sorry, no valid files found."; "lng_send_image" = "Send an image"; "lng_send_file" = "Send as a file"; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 000120ca19..1a2afb5f87 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -3820,6 +3820,7 @@ void ApiWrap::editMedia( .spoiler = false, .album = nullptr, .forceFile = false, + .sendLargePhotos = false, .idOverride = 0, }) : nullptr), @@ -3829,6 +3830,7 @@ void ApiWrap::editMedia( .spoiler = file.spoiler, .album = nullptr, .forceFile = forceFile, + .sendLargePhotos = file.sendLargePhotos, .idOverride = 0, .displayName = file.displayName, })); @@ -3872,6 +3874,7 @@ void ApiWrap::sendFiles( .spoiler = false, .album = nullptr, .forceFile = false, + .sendLargePhotos = false, .idOverride = 0, }) : nullptr), @@ -3881,6 +3884,7 @@ void ApiWrap::sendFiles( .spoiler = file.spoiler, .album = album, .forceFile = forceFile, + .sendLargePhotos = file.sendLargePhotos, .idOverride = 0, .displayName = file.displayName, })); diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp index bb803d6aba..d929632061 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp @@ -184,6 +184,7 @@ void EditPhotoImage( not_null controller, std::shared_ptr media, bool spoilered, + int sideLimit, Fn done) { const auto large = media ? media->image(Data::PhotoSize::Large) @@ -210,7 +211,6 @@ void EditPhotoImage( const auto image = std::get_if(&file.information->media); image->modifications = mods; - const auto sideLimit = PhotoSideLimit(); Storage::UpdateImageDetails(file, previewWidth, sideLimit); done(std::move(list)); }; @@ -265,6 +265,7 @@ EditCaptionBox::EditCaptionBox( Expects(item->allowsEditMedia()); _asFile = !AlbumTypeCompressed(_albumType); + _sendLargePhotos = Core::App().settings().sendFilesWay().sendLargePhotos(); _mediaEditManager.start(item, spoilered, invertCaption); @@ -370,22 +371,26 @@ void EditCaptionBox::StartPhotoEdit( if (!item) { return; } - EditPhotoImage(controller, media, spoilered, [=]( - Ui::PreparedList &&list) mutable { - const auto item = session->data().message(itemId); - if (!item) { - return; - } - controller->show(Box( - controller, - item, - std::move(text), - suggest, - spoilered, - invertCaption, - std::move(list), - std::move(saved))); - }); + EditPhotoImage( + controller, + media, + spoilered, + PhotoSideLimit(true), + [=](Ui::PreparedList &&list) mutable { + const auto item = session->data().message(itemId); + if (!item) { + return; + } + controller->show(Box( + controller, + item, + std::move(text), + suggest, + spoilered, + invertCaption, + std::move(list), + std::move(saved))); + }); } void EditCaptionBox::showFinished() { @@ -419,10 +424,20 @@ void EditCaptionBox::prepare() { : _mediaEditManager.invertCaption() ? SendMenu::CaptionState::Above : SendMenu::CaptionState::Below; + result.photoQuality = !hasSendLargePhotosOption() + ? SendMenu::PhotoQualityState::None + : _sendLargePhotos + ? SendMenu::PhotoQualityState::High + : SendMenu::PhotoQualityState::Standard; return result; }); const auto callback = [=](SendMenu::Action action, const auto &) { - _mediaEditManager.apply(action); + using Type = SendMenu::ActionType; + switch (action.type) { + case Type::PhotoQualityOn: _sendLargePhotos = true; break; + case Type::PhotoQualityOff: _sendLargePhotos = false; break; + default: _mediaEditManager.apply(action); break; + } rebuildPreview(); }; SendMenu::SetupMenuAndShortcuts( @@ -496,6 +511,8 @@ void EditCaptionBox::rebuildPreview() { Ui::AttachControls::Type::EditOnly); _isPhoto = (media && media->isPhoto()); if (media && !_asFile) { + media->setSendWay(currentSendWay()); + media->setCanShowHighQualityBadge(file.canUseHighQualityPhoto()); media->spoileredChanges( ) | rpl::on_next([=](bool spoilered) { _mediaEditManager.apply({ .type = spoilered @@ -741,6 +758,20 @@ void EditCaptionBox::setupEditEventHandler() { }, &st::menuIconDraw); } if (!_asFile && (_isPhoto || _isVideo)) { + if (hasSendLargePhotosOption()) { + const auto enabled = _sendLargePhotos; + (*menu)->addAction( + (enabled + ? tr::lng_send_standard_quality(tr::now) + : tr::lng_send_high_quality(tr::now)), + [=] { + _sendLargePhotos = !enabled; + rebuildPreview(); + }, + enabled + ? &st::menuIconQualityStandard + : &st::menuIconQualityHigh); + } if (_preparedList.hasSpoilerMenu(!_asFile)) { const auto spoilered = hasSpoiler(); auto text = spoilered @@ -803,12 +834,17 @@ void EditCaptionBox::setupPhotoEditorEventHandler() { controller->uiShow(), &_preparedList.files.front(), st::sendMediaPreviewSize, - [=](bool ok) { if (ok) rebuildPreview(); }); + [=](bool ok) { if (ok) rebuildPreview(); }, + PhotoSideLimit(true)); } else { - EditPhotoImage(_controller, _photoMedia, hasSpoiler(), [=]( - Ui::PreparedList &&list) { - setPreparedList(std::move(list)); - }); + EditPhotoImage( + _controller, + _photoMedia, + hasSpoiler(), + PhotoSideLimit(true), + [=](Ui::PreparedList &&list) { + setPreparedList(std::move(list)); + }); } }, lifetime()); } @@ -852,6 +888,7 @@ void EditCaptionBox::setupEditCoverHandler() { } rebuildPreview(); }), + PhotoSideLimit(true), video->thumbnail.size()); }; const auto checkResult = [=](const Ui::PreparedList &list) { @@ -1033,6 +1070,32 @@ bool EditCaptionBox::hasSpoiler() const { return _mediaEditManager.spoilered(); } +bool EditCaptionBox::hasSendLargePhotosOption() const { + const auto compressed = CanToggleCompressed(_albumType) + ? (!_asFile) + : AlbumTypeCompressed(_albumType); + return compressed + && !_preparedList.files.empty() + && _preparedList.hasSendLargePhotosOption(compressed); +} + +Ui::SendFilesWay EditCaptionBox::currentSendWay() const { + auto way = Core::App().settings().sendFilesWay(); + way.setSendImagesAsPhotos(!_asFile); + way.setSendLargePhotos(_sendLargePhotos); + return way; +} + +void EditCaptionBox::saveSendWaySettings() { + auto way = Core::App().settings().sendFilesWay(); + if (way.sendLargePhotos() == _sendLargePhotos) { + return; + } + way.setSendLargePhotos(_sendLargePhotos); + Core::App().settings().setSendFilesWay(way); + Core::App().saveSettingsDelayed(); +} + void EditCaptionBox::captionResized() { updateBoxSize(); resizeEvent(0); @@ -1141,6 +1204,7 @@ bool EditCaptionBox::validateLength(const QString &text) const { void EditCaptionBox::applyChanges() { if (!_preparedList.files.empty()) { _preparedList.files.front().spoiler = _mediaEditManager.spoilered(); + _preparedList.files.front().sendLargePhotos = _sendLargePhotos; } } @@ -1192,6 +1256,7 @@ void EditCaptionBox::save() { const auto compressed = CanToggleCompressed(_albumType) ? (!_asFile) : AlbumTypeCompressed(_albumType); + saveSendWaySettings(); _controller->session().api().editMedia( std::move(_preparedList), (compressed ? SendMediaType::Photo : SendMediaType::File), diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.h b/Telegram/SourceFiles/boxes/edit_caption_box.h index 760df00183..887d4b99c5 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.h +++ b/Telegram/SourceFiles/boxes/edit_caption_box.h @@ -112,6 +112,9 @@ private: [[nodiscard]] int errorTopSkip() const; [[nodiscard]] bool hasSpoiler() const; + [[nodiscard]] bool hasSendLargePhotosOption() const; + [[nodiscard]] Ui::SendFilesWay currentSendWay() const; + void saveSendWaySettings(); bool setPreparedList(Ui::PreparedList &&list); @@ -147,6 +150,7 @@ private: bool _isPhoto = false; bool _isVideo = false; bool _asFile = false; + bool _sendLargePhotos = false; QString _error; diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index 55a84efcc2..145212de9b 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/send_files_box.h" #include "lang/lang_keys.h" +#include "storage/localimageloader.h" #include "storage/localstorage.h" #include "storage/storage_media_prepare.h" #include "iv/iv_instance.h" @@ -386,6 +387,8 @@ SendFilesBox::Block::Block( : nullptr; if (media) { _isSingleMedia = true; + media->setSendWay(way); + media->setCanShowHighQualityBadge(first.canUseHighQualityPhoto()); _preview.reset(media); } else { _preview.reset(Ui::CreateChild( @@ -674,6 +677,11 @@ Fn SendFilesBox::prepareSendMenuDetails( : _invertCaption ? SendMenu::CaptionState::Above : SendMenu::CaptionState::Below; + result.photoQuality = !hasSendLargePhotosOption() + ? SendMenu::PhotoQualityState::None + : way.sendLargePhotos() + ? SendMenu::PhotoQualityState::High + : SendMenu::PhotoQualityState::Standard; result.price = canChangePrice() ? _price.current() : std::optional(); @@ -688,6 +696,8 @@ auto SendFilesBox::prepareSendMenuCallback() switch (action.type) { case Type::CaptionDown: _invertCaption = false; break; case Type::CaptionUp: _invertCaption = true; break; + case Type::PhotoQualityOn: setSendLargePhotos(true); break; + case Type::PhotoQualityOff: setSendLargePhotos(false); break; case Type::SpoilerOn: toggleSpoilers(true); break; case Type::SpoilerOff: toggleSpoilers(false); break; case Type::ChangePrice: changePrice(); break; @@ -742,7 +752,7 @@ void SendFilesBox::enqueueNextPrepare() { _list.filesToProcess.pop_front(); const auto weak = base::make_weak(this); _preparing = true; - const auto sideLimit = PhotoSideLimit(); // Get on main thread. + const auto sideLimit = PhotoSideLimit(_sendWay.current().sendLargePhotos()); crl::async([weak, sideLimit, file = std::move(file)]() mutable { Storage::PrepareDetails(file, st::sendMediaPreviewSize, sideLimit); crl::on_main([weak, file = std::move(file)]() mutable { @@ -926,7 +936,9 @@ void SendFilesBox::refreshButtons() { bool SendFilesBox::hasSendMenu(const MenuDetails &details) const { return (details.type != SendMenu::Type::Disabled) || (details.spoiler != SendMenu::SpoilerState::None) - || (details.caption != SendMenu::CaptionState::None); + || (details.caption != SendMenu::CaptionState::None) + || (details.photoQuality != SendMenu::PhotoQualityState::None) + || details.price.has_value(); } bool SendFilesBox::hasSpoilerMenu() const { @@ -934,6 +946,11 @@ bool SendFilesBox::hasSpoilerMenu() const { && _list.hasSpoilerMenu(_sendWay.current().sendImagesAsPhotos()); } +bool SendFilesBox::hasSendLargePhotosOption() const { + return _list.hasSendLargePhotosOption( + _sendWay.current().sendImagesAsPhotos()); +} + bool SendFilesBox::canChangePrice() const { const auto way = _sendWay.current(); const auto broadcast = _toPeer->asBroadcast(); @@ -964,6 +981,15 @@ void SendFilesBox::toggleSpoilers(bool enabled) { } } +void SendFilesBox::setSendLargePhotos(bool enabled) { + auto way = _sendWay.current(); + if (way.sendLargePhotos() == enabled) { + return; + } + way.setSendLargePhotos(enabled); + _sendWay = way; +} + void SendFilesBox::changePrice() { const auto weak = base::make_weak(this); const auto session = &_show->session(); @@ -1249,7 +1275,8 @@ void SendFilesBox::pushBlock(int from, int till) { if (ok) { refreshAllAfterChanges(from); } - }); + }, + PhotoSideLimit(true)); }; const auto replaceAttachment = [=, show = _show](int index) { applyBlockChanges(); @@ -1358,6 +1385,7 @@ void SendFilesBox::pushBlock(int from, int till) { } refreshAllAfterChanges(from); }), + PhotoSideLimit(true), video->thumbnail.size()); }; const auto checkResult = [=](const Ui::PreparedList &list) { @@ -2171,14 +2199,18 @@ void SendFilesBox::setInnerFocus() { } } -void SendFilesBox::saveSendWaySettings() { +void SendFilesBox::saveSendWaySettings(bool rememberAll) { auto way = _sendWay.current(); auto oldWay = Core::App().settings().sendFilesWay(); - if (_groupFiles->isHidden()) { + if (!rememberAll) { + way.setGroupFiles(oldWay.groupFiles()); + way.setSendImagesAsPhotos(oldWay.sendImagesAsPhotos()); + } else if (_groupFiles->isHidden()) { way.setGroupFiles(oldWay.groupFiles()); } - if (_list.overrideSendImagesAsPhotos == way.sendImagesAsPhotos() - || _sendImagesAsPhotos->isHidden()) { + if (rememberAll + && (_list.overrideSendImagesAsPhotos == way.sendImagesAsPhotos() + || _sendImagesAsPhotos->isHidden())) { way.setSendImagesAsPhotos(oldWay.sendImagesAsPhotos()); } if (way != oldWay) { @@ -2208,6 +2240,7 @@ void SendFilesBox::send( auto child = _sendMenuDetails(); child.spoiler = SendMenu::SpoilerState::None; child.caption = SendMenu::CaptionState::None; + child.photoQuality = SendMenu::PhotoQualityState::None; child.price = std::nullopt; return SendMenu::DefaultCallback(_show, sendCallback())( { .type = SendMenu::ActionType::Schedule }, @@ -2220,20 +2253,20 @@ void SendFilesBox::send( return; } - if (_wayRemember && _wayRemember->checked()) { - saveSendWaySettings(); - } + const auto way = _sendWay.current(); for (auto &item : _list.files) { item.spoiler = false; } applyBlockChanges(); + for (auto &item : _list.files) { + item.sendLargePhotos = way.sendLargePhotos(); + } Storage::ApplyModifications(_list); _confirmed = true; if (_confirmedCallback) { - const auto way = _sendWay.current(); auto caption = fieldText(); if (!validateLength(caption.text)) { return; @@ -2244,6 +2277,7 @@ void SendFilesBox::send( return; } } + saveSendWaySettings(_wayRemember && _wayRemember->checked()); options.invertCaption = _invertCaption; options.price = hasPrice() ? _price.current() : 0; if (options.price > 0) { diff --git a/Telegram/SourceFiles/boxes/send_files_box.h b/Telegram/SourceFiles/boxes/send_files_box.h index 7531631546..5225cd7011 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.h +++ b/Telegram/SourceFiles/boxes/send_files_box.h @@ -211,10 +211,12 @@ private: void addMenuButton(); void applyBlockChanges(); void toggleSpoilers(bool enabled); + void setSendLargePhotos(bool enabled); void changePrice(); [[nodiscard]] bool canChangePrice() const; [[nodiscard]] bool hasPrice() const; + [[nodiscard]] bool hasSendLargePhotosOption() const; void refreshPriceTag(); [[nodiscard]] QImage preparePriceTagBg(QSize size) const; @@ -235,7 +237,7 @@ private: void send(Api::SendOptions options, bool ctrlShiftEnter = false); [[nodiscard]] Fn sendCallback(); void captionResized(); - void saveSendWaySettings(); + void saveSendWaySettings(bool rememberAll); void setupDragArea(); void refreshTitleText(); diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 597a82ed3d..a851b3ff84 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -72,6 +72,8 @@ ComposeIcons { menuSpoilerOff: icon; menuBelow: icon; menuAbove: icon; + menuQualityHigh: icon; + menuQualityStandard: icon; menuPrice: icon; menuEditStars: icon; @@ -242,6 +244,7 @@ ComposeControls { tabbedHeightMax: pixels; record: RecordBar; files: ComposeFiles; + photoQualityBadgeOuterSkip: pixels; premium: PremiumLimits; boxField: InputField; restrictionLabel: FlatLabel; @@ -657,6 +660,8 @@ defaultComposeIcons: ComposeIcons { menuSpoilerOff: menuIconSpoilerOff; menuBelow: menuIconBelow; menuAbove: menuIconAbove; + menuQualityHigh: menuIconQualityHigh; + menuQualityStandard: menuIconQualityStandard; menuPrice: menuIconEarn; stripBubble: icon{ @@ -1511,6 +1516,7 @@ defaultComposeControls: ComposeControls { tabbedHeightMax: emojiPanMaxHeight; record: defaultRecordBar; files: defaultComposeFiles; + photoQualityBadgeOuterSkip: 3px; premium: defaultPremiumLimits; boxField: defaultInputField; restrictionLabel: defaultRestrictionLabel; diff --git a/Telegram/SourceFiles/editor/photo_editor_layer_widget.cpp b/Telegram/SourceFiles/editor/photo_editor_layer_widget.cpp index 257e47ec4f..45190842e5 100644 --- a/Telegram/SourceFiles/editor/photo_editor_layer_widget.cpp +++ b/Telegram/SourceFiles/editor/photo_editor_layer_widget.cpp @@ -27,6 +27,7 @@ void OpenWithPreparedFile( not_null file, int previewWidth, Fn &&doneCallback, + int sideLimit, QSize exactSize) { using ImageInfo = Ui::PreparedFileInformation::Image; const auto image = std::get_if(&file->information->media); @@ -42,7 +43,7 @@ void OpenWithPreparedFile( return; } - const auto sideLimit = PhotoSideLimit(); + sideLimit = sideLimit ? sideLimit : PhotoSideLimit(true); const auto accepted = std::make_shared(); auto callback = [=](const PhotoModifications &mods) { *accepted = true; diff --git a/Telegram/SourceFiles/editor/photo_editor_layer_widget.h b/Telegram/SourceFiles/editor/photo_editor_layer_widget.h index 0259a0fb7e..b08aba8f2f 100644 --- a/Telegram/SourceFiles/editor/photo_editor_layer_widget.h +++ b/Telegram/SourceFiles/editor/photo_editor_layer_widget.h @@ -35,6 +35,7 @@ void OpenWithPreparedFile( not_null file, int previewWidth, Fn &&doneCallback, + int sideLimit = 0, QSize exactSize = {}); void PrepareProfilePhoto( diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style index e5a0c4af04..cae366abd0 100644 --- a/Telegram/SourceFiles/media/view/media_view.style +++ b/Telegram/SourceFiles/media/view/media_view.style @@ -659,6 +659,8 @@ storiesEmojiPan: EmojiPan(defaultEmojiPan) { menuSpoilerOff: icon {{ "menu/spoiler_off", storiesComposeWhiteText }}; menuBelow: icon {{ "menu/link_below", storiesComposeWhiteText }}; menuAbove: icon {{ "menu/link_above", storiesComposeWhiteText }}; + menuQualityHigh: icon {{ "menu/quality_hd", storiesComposeWhiteText }}; + menuQualityStandard: icon {{ "menu/quality_sd", storiesComposeWhiteText }}; menuPrice: icon {{ "menu/earn", storiesComposeWhiteText }}; menuEditStars: icon {{ "chat/input_paid", storiesComposeWhiteText, point(-8px, -8px) }}; diff --git a/Telegram/SourceFiles/menu/menu_send.cpp b/Telegram/SourceFiles/menu/menu_send.cpp index 9c4794d5d0..d2fc93e2fe 100644 --- a/Telegram/SourceFiles/menu/menu_send.cpp +++ b/Telegram/SourceFiles/menu/menu_send.cpp @@ -722,6 +722,7 @@ FillMenuResult FillSendMenu( const auto empty = !sending && (details.spoiler == SpoilerState::None) && (details.caption == CaptionState::None) + && (details.photoQuality == PhotoQualityState::None) && !details.price.has_value(); if (empty || !action) { return FillMenuResult::Skipped; @@ -764,9 +765,24 @@ FillMenuResult FillSendMenu( if ((type != Type::Disabled) && ((details.spoiler != SpoilerState::None) || (details.caption != CaptionState::None) + || (details.photoQuality != PhotoQualityState::None) || details.price.has_value())) { menu->addSeparator(&st::expandedMenuSeparator); } + if (details.photoQuality != PhotoQualityState::None) { + const auto high = (details.photoQuality == PhotoQualityState::High); + menu->addAction( + (high + ? tr::lng_send_standard_quality(tr::now) + : tr::lng_send_high_quality(tr::now)), + [=] { action({ .type = high + ? ActionType::PhotoQualityOff + : ActionType::PhotoQualityOn + }, details); }, + (high + ? &icons.menuQualityStandard + : &icons.menuQualityHigh)); + } if (details.spoiler != SpoilerState::None) { const auto spoilered = (details.spoiler == SpoilerState::Enabled); menu->addAction( diff --git a/Telegram/SourceFiles/menu/menu_send.h b/Telegram/SourceFiles/menu/menu_send.h index 1eb4f12854..993dbffd1a 100644 --- a/Telegram/SourceFiles/menu/menu_send.h +++ b/Telegram/SourceFiles/menu/menu_send.h @@ -51,10 +51,17 @@ enum class CaptionState : uchar { Above, }; +enum class PhotoQualityState : uchar { + None, + Standard, + High, +}; + struct Details { Type type = Type::Disabled; SpoilerState spoiler = SpoilerState::None; CaptionState caption = CaptionState::None; + PhotoQualityState photoQuality = PhotoQualityState::None; TextWithTags commentPreview; QString commentStreamerName; std::optional price; @@ -75,6 +82,8 @@ enum class ActionType : uchar { SpoilerOff, CaptionUp, CaptionDown, + PhotoQualityOn, + PhotoQualityOff, ChangePrice, }; struct Action { diff --git a/Telegram/SourceFiles/settings/settings_experimental.cpp b/Telegram/SourceFiles/settings/settings_experimental.cpp index 5dc8191c4a..3bbddecc62 100644 --- a/Telegram/SourceFiles/settings/settings_experimental.cpp +++ b/Telegram/SourceFiles/settings/settings_experimental.cpp @@ -39,7 +39,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_session_controller.h" #include "window/window_controller.h" #include "window/notifications_manager.h" -#include "storage/localimageloader.h" #include "info/info_flexible_scroll.h" #include "chat_helpers/stickers_list_widget.h" #include "styles/style_settings.h" @@ -212,7 +211,6 @@ void SetupExperimental( addToggle(Info::Profile::kOptionShowChannelJoinedBelowAbout); addToggle(Ui::kOptionUseSmallMsgBubbleRadius); addToggle(Media::Player::kOptionDisableAutoplayNext); - addToggle(kOptionSendLargePhotos); addToggle(Webview::kOptionWebviewDebugEnabled); addToggle(Webview::kOptionWebviewLegacyEdge); addToggle(kOptionAutoScrollInactiveChat); diff --git a/Telegram/SourceFiles/storage/localimageloader.cpp b/Telegram/SourceFiles/storage/localimageloader.cpp index 5ecabb50a9..970899da55 100644 --- a/Telegram/SourceFiles/storage/localimageloader.cpp +++ b/Telegram/SourceFiles/storage/localimageloader.cpp @@ -12,9 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_document.h" #include "data/data_session.h" #include "data/data_user.h" +#include "core/application.h" +#include "core/core_settings.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" @@ -51,13 +52,6 @@ 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 SendLargePhotosAtomic/* = false*/; - struct PreparedFileThumbnail { uint64 id = 0; QString name; @@ -206,20 +200,15 @@ struct PreparedFileThumbnail { return result; } -[[nodiscard]] int PhotoSideLimit(bool large) { +} // namespace + +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()); + return PhotoSideLimit( + Core::App().settings().sendFilesWay().sendLargePhotos()); } TaskQueue::TaskQueue(crl::time stopTimeoutMs) { @@ -480,13 +469,12 @@ FileLoadTask::FileLoadTask(Args &&args) , _type(args.type) , _caption(std::move(args.caption)) , _spoiler(args.spoiler) -, _forceFile(args.forceFile) { +, _forceFile(args.forceFile) +, _sendLargePhotos(args.sendLargePhotos) { Expects(_to.options.scheduled || _to.options.shortcutId || !_to.replaceMediaOf || IsServerMsgId(_to.replaceMediaOf)); - - SendLargePhotosAtomic = SendLargePhotos.value(); } FileLoadTask::FileLoadTask(VoiceArgs &&args) @@ -944,7 +932,7 @@ void FileLoadTask::process(ProcessArgs &&args) { } auto medium = (w > 320 || h > 320) ? fullimage.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation) : fullimage; - const auto limit = PhotoSideLimitAtomic(); + const auto limit = PhotoSideLimit(_sendLargePhotos); const auto downscaled = (w > limit || h > limit); auto full = downscaled ? fullimage.scaled(limit, limit, Qt::KeepAspectRatio, Qt::SmoothTransformation) : fullimage; if (downscaled) { diff --git a/Telegram/SourceFiles/storage/localimageloader.h b/Telegram/SourceFiles/storage/localimageloader.h index e2c3832b8b..af90953b44 100644 --- a/Telegram/SourceFiles/storage/localimageloader.h +++ b/Telegram/SourceFiles/storage/localimageloader.h @@ -24,8 +24,7 @@ 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(bool large); [[nodiscard]] int PhotoSideLimit(); enum class SendMediaType { @@ -232,6 +231,7 @@ public: bool spoiler = false; std::shared_ptr album; bool forceFile = false; + bool sendLargePhotos = false; uint64 idOverride = 0; QString displayName; }; @@ -303,6 +303,7 @@ private: TextWithTags _caption; bool _spoiler = false; bool _forceFile = false; + bool _sendLargePhotos = false; std::shared_ptr _result; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.cpp index 0d57e7413f..da2bc305fe 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.cpp @@ -81,6 +81,11 @@ void AbstractSingleMediaPreview::setSpoiler(bool spoiler) { update(); } +void AbstractSingleMediaPreview::setCanShowHighQualityBadge(bool value) { + _canShowHighQualityBadge = value; + update(); +} + bool AbstractSingleMediaPreview::hasSpoiler() const { return _spoiler != nullptr; } @@ -249,6 +254,12 @@ void AbstractSingleMediaPreview::paintEvent(QPaintEvent *e) { auto icon = &st::historyFileInPlay; icon->paintInCenter(p, inner); } + if (_canShowHighQualityBadge && _sendWay.sendLargePhotos()) { + PaintHighQualityBadge( + p, + _st, + QRect(_previewLeft, _previewTop, _previewWidth, _previewHeight)); + } } void AbstractSingleMediaPreview::mousePressEvent(QMouseEvent *e) { diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.h b/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.h index 04fa1ccc70..710397d093 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_abstract_single_media_preview.h @@ -42,6 +42,7 @@ public: [[nodiscard]] bool isPhoto() const; void setSpoiler(bool spoiler); + void setCanShowHighQualityBadge(bool value); [[nodiscard]] bool hasSpoiler() const; [[nodiscard]] bool canHaveSpoiler() const; [[nodiscard]] rpl::producer spoileredChanges() const; @@ -87,6 +88,7 @@ private: std::unique_ptr _spoiler; rpl::event_stream _spoileredChanges; + bool _canShowHighQualityBadge = false; const int _minThumbH; const base::unique_qptr _controls; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp index 176e30556a..cd2dc1d616 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_album_preview.cpp @@ -381,11 +381,23 @@ void AlbumPreview::paintAlbum(Painter &p) const { const auto top = contentTop(); for (const auto &thumb : _thumbs) { if (thumb.get() != _paintedAbove) { - thumb->paintInAlbum(p, left, top, shrink, moveProgress); + thumb->paintInAlbum( + p, + left, + top, + shrink, + moveProgress, + _sendWay.sendLargePhotos()); } } if (_paintedAbove) { - _paintedAbove->paintInAlbum(p, left, top, shrink, moveProgress); + _paintedAbove->paintInAlbum( + p, + left, + top, + shrink, + moveProgress, + _sendWay.sendLargePhotos()); } } @@ -403,7 +415,12 @@ void AlbumPreview::paintPhotos(Painter &p, QRect clip) const { } else if (bottom <= clip.y()) { continue; } - thumb->paintPhoto(p, left, top, outerWidth); + thumb->paintPhoto( + p, + left, + top, + outerWidth, + _sendWay.sendLargePhotos()); } } @@ -698,7 +715,7 @@ QImage AlbumPreview::generatePriceTagBackground() const { p.translate(geometry.center()); p.scale(wscale, hscale); p.translate(-geometry.center()); - thumb->paintInAlbum(p, 0, 0, 1., 1.); + thumb->paintInAlbum(p, 0, 0, 1., 1., _sendWay.sendLargePhotos()); p.restore(); } p.end(); diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.cpp index df101b8cc1..8b7186d884 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.cpp @@ -42,6 +42,7 @@ AlbumThumbnail::AlbumThumbnail( , _shrinkSize(int(std::ceil(st::roundRadiusLarge / 1.4))) , _isPhoto(file.type == PreparedFile::Type::Photo) , _isVideo(file.type == PreparedFile::Type::Video) +, _canShowHighQualityBadge(file.canUseHighQualityPhoto()) , _isCompressedSticker(Core::IsMimeSticker(file.information->filemime)) , _repaint(std::move(repaint)) , _repaintRect(std::move(repaintRect)) { @@ -250,7 +251,8 @@ void AlbumThumbnail::paintInAlbum( int left, int top, float64 shrinkProgress, - float64 moveProgress) { + float64 moveProgress, + bool showHighQualityBadge) { const auto shrink = anim::interpolate(0, _shrinkSize, shrinkProgress); _lastShrinkValue = shrink; const auto geometry = countCurrentGeometry( @@ -314,6 +316,9 @@ void AlbumThumbnail::paintInAlbum( geometry, shrinkProgress); _lastRectOfModify = geometry; + if (showHighQualityBadge && _canShowHighQualityBadge) { + PaintHighQualityBadge(p, _st, paintedTo); + } } void AlbumThumbnail::paintPlayVideo(QPainter &p, QRect geometry) { @@ -476,7 +481,12 @@ void AlbumThumbnail::drawSimpleFrame(QPainter &p, QRect to, QSize size) const { } } -void AlbumThumbnail::paintPhoto(Painter &p, int left, int top, int outerWidth) { +void AlbumThumbnail::paintPhoto( + Painter &p, + int left, + int top, + int outerWidth, + bool showHighQualityBadge) { const auto size = _photo.size() / style::DevicePixelRatio(); if (_spoiler && _photoBlurred.isNull()) { _photoBlurred = BlurredPreviewFromPixmap( @@ -515,6 +525,9 @@ void AlbumThumbnail::paintPhoto(Painter &p, int left, int top, int outerWidth) { 0); _lastRectOfModify = QRect(topLeft, size); + if (showHighQualityBadge && _canShowHighQualityBadge) { + PaintHighQualityBadge(p, _st, rect); + } } void AlbumThumbnail::paintFile( @@ -525,7 +538,7 @@ void AlbumThumbnail::paintFile( if (isCompressedSticker()) { auto spoiler = base::take(_spoiler); - paintPhoto(p, left, top, outerWidth); + paintPhoto(p, left, top, outerWidth, false); _spoiler = base::take(spoiler); return; } diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.h b/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.h index d10b77723c..655596b8f8 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_album_thumbnail.h @@ -53,8 +53,14 @@ public: int left, int top, float64 shrinkProgress, - float64 moveProgress); - void paintPhoto(Painter &p, int left, int top, int outerWidth); + float64 moveProgress, + bool showHighQualityBadge); + void paintPhoto( + Painter &p, + int left, + int top, + int outerWidth, + bool showHighQualityBadge); void paintFile(Painter &p, int left, int top, int outerWidth); [[nodiscard]] QRect geometry() const; @@ -94,6 +100,7 @@ private: const int _shrinkSize; const bool _isPhoto; const bool _isVideo; + const bool _canShowHighQualityBadge; QPixmap _albumImage; QPixmap _albumImageBlurred; QImage _albumCache; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp index 61b43fb979..8939f6d32e 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp @@ -12,8 +12,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/attach/attach_send_files_way.h" #include "ui/image/image_prepare.h" +#include "ui/painter.h" #include "ui/ui_utility.h" #include "core/mime_type.h" +#include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" +#include "styles/style_media_player.h" #include @@ -21,6 +25,7 @@ namespace Ui { namespace { constexpr auto kMaxAlbumCount = 10; +constexpr auto kStandardPhotoSideLimit = 1280; struct GroupRange { int from = 0; @@ -32,6 +37,69 @@ struct GroupRange { } }; +struct HighQualityBadgeCache { + QRgb bg = 0; + QRgb fg = 0; + qreal ratio = 0.; + int width = 0; + int height = 0; + QImage image; +}; + +[[nodiscard]] const QImage &HighQualityBadgeImage( + const style::ComposeControls &st) { + static auto cache = HighQualityBadgeCache(); + const auto text = u"HD"_q; + const auto &font = st::mediaPlayerSpeedButton.font; + const auto xpadding = style::ConvertScale(2.); + const auto ypadding = 0; + const auto stroke = style::ConvertScaleExact(1.); + const auto width = font->width(text); + const auto height = font->height; + const auto ratio = style::DevicePixelRatio(); + const auto bg = st::roundedBg->c.rgba(); + const auto fg = st::roundedFg->c.rgba(); + if (cache.image.isNull() + || (cache.bg != bg) + || (cache.fg != fg) + || (cache.ratio != ratio) + || (cache.width != width) + || (cache.height != height)) { + cache.bg = bg; + cache.fg = fg; + cache.ratio = ratio; + cache.width = width; + cache.height = height; + cache.image = QImage( + (width + 2 * xpadding + stroke) * ratio, + (height + 2 * ypadding + stroke) * ratio, + QImage::Format_ARGB32_Premultiplied); + cache.image.setDevicePixelRatio(ratio); + cache.image.fill(Qt::transparent); + auto painter = QPainter(&cache.image); + auto hq = PainterHighQualityEnabler(painter); + painter.setCompositionMode(QPainter::CompositionMode_Source); + painter.setPen(QPen(Qt::transparent, stroke)); + painter.setBrush(st::roundedBg); + painter.setFont(font); + painter.drawRoundedRect( + QRectF( + 0, + 0, + width + 2 * xpadding + stroke, + height + 2 * ypadding + stroke), + height / 3., + height / 3.); + painter.setPen(st::roundedFg); + painter.drawText( + QPointF( + xpadding + stroke / 2., + ypadding + font->ascent + stroke / 2.), + text); + } + return cache.image; +} + [[nodiscard]] AlbumType GroupTypeForFile( PreparedFile::Type type, bool groupFiles, @@ -132,6 +200,14 @@ bool PreparedFile::isGifv() const { && v::get