From 4f1c2788b8203a29bd4e341b896e33b1e7a4ad01 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 18 Aug 2025 13:43:33 +0400 Subject: [PATCH] Improve phrasing for saved music. --- Telegram/CMakeLists.txt | 2 + Telegram/Resources/langs/lang.strings | 9 ++ .../SourceFiles/boxes/delete_messages_box.cpp | 3 + Telegram/SourceFiles/data/data_document.cpp | 4 + Telegram/SourceFiles/data/data_document.h | 1 + .../SourceFiles/data/data_saved_music.cpp | 31 +++++- Telegram/SourceFiles/data/data_saved_music.h | 5 + Telegram/SourceFiles/data/data_session.cpp | 3 + .../history/history_inner_widget.cpp | 37 ++++--- .../media/history_view_save_audio_action.cpp | 101 ++++++++++++++++++ .../media/history_view_save_audio_action.h | 29 +++++ .../info/saved/info_saved_music_widget.cpp | 9 +- Telegram/SourceFiles/ui/chat/chat.style | 6 ++ .../SourceFiles/window/window_peer_menu.cpp | 52 ++++----- .../SourceFiles/window/window_peer_menu.h | 8 ++ 15 files changed, 251 insertions(+), 49 deletions(-) create mode 100644 Telegram/SourceFiles/history/view/media/history_view_save_audio_action.cpp create mode 100644 Telegram/SourceFiles/history/view/media/history_view_save_audio_action.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index ed133b857f..2d75c61ad5 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -817,6 +817,8 @@ PRIVATE history/view/media/history_view_poll.h history/view/media/history_view_premium_gift.cpp history/view/media/history_view_premium_gift.h + history/view/media/history_view_save_audio_action.cpp + history/view/media/history_view_save_audio_action.h history/view/media/history_view_service_box.cpp history/view/media/history_view_service_box.h history/view/media/history_view_similar_channels.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index e46c793933..2c84d7b8ba 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1659,6 +1659,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_media_type_audios" = "Voice messages"; "lng_media_type_links" = "Shared links"; "lng_media_type_rounds" = "Video messages"; +"lng_media_saved_music_your" = "Your playlist"; +"lng_media_saved_music_title" = "Playlist"; "lng_profile_common_groups_section" = "Groups in common"; "lng_info_edit_contact" = "Edit contact"; "lng_info_delete_contact" = "Delete contact"; @@ -4400,6 +4402,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_context_pack_info" = "View Sticker Set"; "lng_context_pack_add" = "Add Stickers"; "lng_context_save_file" = "Save As..."; +"lng_context_save_music_to" = "Save to..."; +"lng_context_save_music_profile" = "... Profile"; +"lng_context_save_music_saved" = "... Saved Messages"; +"lng_context_save_music_folder" = "... Downloads"; +"lng_context_save_music_about" = "Choose where you want this audio to be saved."; "lng_context_copy_text" = "Copy Text"; "lng_context_open_gif" = "Open GIF"; "lng_context_save_gif" = "Add to GIFs"; @@ -4815,6 +4822,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_selected_delete_sure#one" = "Do you want to delete {count} message?"; "lng_selected_delete_sure#other" = "Do you want to delete {count} messages?"; "lng_selected_remove_saved_music" = "Do you want to remove this file from your profile?"; +"lng_saved_music_added" = "Audio added to your Profile."; +"lng_saved_music_removed" = "Audio removed from your Profile."; "lng_delete_photo_sure" = "Do you want to delete this photo?"; "lng_delete_for_everyone_hint#one" = "This will delete it for everyone in this chat."; "lng_delete_for_everyone_hint#other" = "This will delete them for everyone in this chat."; diff --git a/Telegram/SourceFiles/boxes/delete_messages_box.cpp b/Telegram/SourceFiles/boxes/delete_messages_box.cpp index 20f5136512..9a04827223 100644 --- a/Telegram/SourceFiles/boxes/delete_messages_box.cpp +++ b/Telegram/SourceFiles/boxes/delete_messages_box.cpp @@ -582,6 +582,9 @@ void DeleteMessagesBox::deleteAndClear() { // deleteMessages can initiate closing of the current section, // which will cause this box to be destroyed. const auto weak = base::make_weak(this); + if (hasSavedMusicMessages()) { + uiShow()->showToast(tr::lng_saved_music_removed(tr::now)); + } if (const auto callback = _deleteConfirmedCallback) { callback(); } diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index bbb5ae1058..0d8a7799d4 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -1712,6 +1712,10 @@ void DocumentData::forceIsStreamedAnimation() { setMaybeSupportsStreaming(true); } +bool DocumentData::isMusicForProfile() const { + return isSong(); +} + bool DocumentData::isVoiceMessage() const { return (type == VoiceDocument); } diff --git a/Telegram/SourceFiles/data/data_document.h b/Telegram/SourceFiles/data/data_document.h index f91f828a1b..b0918a37c8 100644 --- a/Telegram/SourceFiles/data/data_document.h +++ b/Telegram/SourceFiles/data/data_document.h @@ -182,6 +182,7 @@ public: [[nodiscard]] const VideoData *video() const; void forceIsStreamedAnimation(); + [[nodiscard]] bool isMusicForProfile() const; [[nodiscard]] bool isVoiceMessage() const; [[nodiscard]] bool isVideoMessage() const; [[nodiscard]] bool isSong() const; diff --git a/Telegram/SourceFiles/data/data_saved_music.cpp b/Telegram/SourceFiles/data/data_saved_music.cpp index 273e876101..4ae5963ab9 100644 --- a/Telegram/SourceFiles/data/data_saved_music.cpp +++ b/Telegram/SourceFiles/data/data_saved_music.cpp @@ -22,6 +22,7 @@ namespace Data { namespace { constexpr auto kPerPage = 50; +constexpr auto kReloadIdsEvery = 30 * crl::time(1000); [[nodiscard]] not_null ItemDocument( not_null item) { @@ -66,9 +67,31 @@ not_null SavedMusic::musicIdToMsg( }, id, TextWithEntities())).first->second.get(); } +void SavedMusic::loadIds() { + if (_loadIdsRequest + || (_lastReceived + && (crl::now() - _lastReceived < kReloadIdsEvery))) { + return; + } + _loadIdsRequest = _owner->session().api().request( + MTPaccount_GetSavedMusicIds(MTP_long(Api::CountHash(_myIds))) + ).done([=](const MTPaccount_SavedMusicIds &result) { + _loadIdsRequest = 0; + _lastReceived = crl::now(); + result.match([&](const MTPDaccount_savedMusicIds &data) { + _myIds = data.vids().v + | ranges::views::transform(&MTPlong::v) + | ranges::to_vector; + }, [](const MTPDaccount_savedMusicIdsNotModified &) { + }); + }).fail([=] { + _loadIdsRequest = 0; + _lastReceived = crl::now(); + }).send(); +} + bool SavedMusic::has(not_null document) const { - const auto entry = lookupEntry(_owner->session().userPeerId()); - return entry && ranges::contains(entry->list, document, ItemDocument); + return ranges::contains(_myIds, document->id); } void SavedMusic::save(not_null document) { @@ -77,7 +100,7 @@ void SavedMusic::save(not_null document) { if (entry.list.empty() && !entry.loaded) { loadMore(peerId); } - if (ranges::contains(entry.list, document, ItemDocument)) { + if (has(document)) { return; } const auto item = musicIdToMsg(peerId, entry, document); @@ -85,6 +108,7 @@ void SavedMusic::save(not_null document) { if (entry.total >= 0) { ++entry.total; } + _myIds.insert(begin(_myIds), document->id); _owner->session().api().request(MTPaccount_SaveMusic( MTP_flags(0), document->mtpInput(), @@ -105,6 +129,7 @@ void SavedMusic::remove(not_null document) { } } entry.musicIdToMsg.remove(document); + _myIds.erase(ranges::remove(_myIds, document->id), end(_myIds)); _owner->session().api().request(MTPaccount_SaveMusic( MTP_flags(MTPaccount_SaveMusic::Flag::f_unsave), document->mtpInput(), diff --git a/Telegram/SourceFiles/data/data_saved_music.h b/Telegram/SourceFiles/data/data_saved_music.h index fe31a5d788..28c77d11f2 100644 --- a/Telegram/SourceFiles/data/data_saved_music.h +++ b/Telegram/SourceFiles/data/data_saved_music.h @@ -32,6 +32,7 @@ public: [[nodiscard]] rpl::producer changed() const; + void loadIds(); [[nodiscard]] bool has(not_null document) const; void save(not_null document); void remove(not_null document); @@ -65,6 +66,10 @@ private: const not_null _owner; + std::vector _myIds; + crl::time _lastReceived = 0; + mtpRequestId _loadIdsRequest = 0; + std::unordered_map _entries; rpl::event_stream _changed; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 367e0c54c1..c2dce9a9df 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -4391,6 +4391,9 @@ void Session::unregisterPhotoItem( void Session::registerDocumentItem( not_null document, not_null item) { + if (document->isMusicForProfile()) { + document->owner().savedMusic().loadIds(); + } _documentItems[document].insert(item); } diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index be9932b6d5..cc87f6f5b3 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/controls/history_view_forward_panel.h" #include "history/view/controls/history_view_draft_options.h" #include "history/view/controls/history_view_suggest_options.h" +#include "history/view/media/history_view_save_audio_action.h" #include "history/view/media/history_view_sticker.h" #include "history/view/media/history_view_web_page.h" #include "history/view/reactions/history_view_reactions.h" @@ -2547,24 +2548,26 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { item, document, controller); - _menu->addAction(lnkIsVideo ? tr::lng_context_save_video(tr::now) : (lnkIsVoice ? tr::lng_context_save_audio(tr::now) : (lnkIsAudio ? tr::lng_context_save_audio_file(tr::now) : tr::lng_context_save_file(tr::now))), base::fn_delayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [=] { - saveDocumentToFile(itemId, document); - }), &st::menuIconDownload); - - if (document->isSong()) { - if (document->owner().savedMusic().has(document)) { - _menu->addAction(u"remove muzlo :("_q, [=] { - document->owner().savedMusic().remove(document); - _controller->showToast(u"removed from muzlo!"_q); - }, &st::menuIconUnfave); - } else { - _menu->addAction(u"to muzlo!"_q, [=] { - document->owner().savedMusic().save(document); - _controller->showToast(u"added to muzlo! :)"_q); - }, &st::menuIconFave); - } + if (lnkIsAudio) { + HistoryView::AddSaveAudioAction( + Ui::Menu::CreateAddActionCallback(_menu), + item, + document, + controller); + } else { + const auto text = lnkIsVideo + ? tr::lng_context_save_video(tr::now) + : lnkIsVoice + ? tr::lng_context_save_audio(tr::now) + : lnkIsAudio + ? tr::lng_context_save_audio_file(tr::now) + : tr::lng_context_save_file(tr::now); + const auto &ripple = st::defaultDropdownMenu.menu.ripple; + const auto duration = ripple.hideDuration; + _menu->addAction(text, base::fn_delayed(duration, this, [=] { + saveDocumentToFile(itemId, document); + }), &st::menuIconDownload); } - HistoryView::AddCopyFilename( _menu, document, diff --git a/Telegram/SourceFiles/history/view/media/history_view_save_audio_action.cpp b/Telegram/SourceFiles/history/view/media/history_view_save_audio_action.cpp new file mode 100644 index 0000000000..7fd4de444a --- /dev/null +++ b/Telegram/SourceFiles/history/view/media/history_view_save_audio_action.cpp @@ -0,0 +1,101 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "history/view/media/history_view_save_audio_action.h" + +#include "base/call_delayed.h" +#include "data/data_document.h" +#include "data/data_file_click_handler.h" +#include "data/data_peer.h" +#include "data/data_saved_music.h" +#include "data/data_session.h" +#include "history/history.h" +#include "history/history_item.h" +#include "lang/lang_keys.h" +#include "ui/widgets/menu/menu_add_action_callback.h" +#include "ui/widgets/menu/menu_multiline_action.h" +#include "ui/widgets/popup_menu.h" +#include "window/window_peer_menu.h" +#include "window/window_session_controller.h" +#include "styles/style_chat.h" +#include "styles/style_menu_icons.h" +#include "styles/style_widgets.h" + +namespace HistoryView { + +void AddSaveAudioAction( + const Ui::Menu::MenuCallback &addAction, + not_null item, + not_null document, + not_null controller) { + const auto contextId = item->fullId(); + const auto fromSaved = item->history()->peer->isSelf(); + const auto savedMusic = &document->owner().savedMusic(); + const auto show = controller->uiShow(); + const auto inProfile = savedMusic->has(document); + const auto &ripple = st::defaultDropdownMenu.menu.ripple; + const auto duration = ripple.hideDuration; + const auto saveAs = base::fn_delayed(duration, controller, [=] { + DocumentSaveClickHandler::SaveAndTrack( + contextId, + document, + DocumentSaveClickHandler::Mode::ToNewFile); + }); + if (!document->isMusicForProfile() || (fromSaved && inProfile)) { + addAction( + tr::lng_context_save_audio_file(tr::now), + saveAs, + &st::menuIconDownload); + return; + } + const auto fill = [&](not_null menu) { + if (!inProfile) { + const auto saved = [=] { + savedMusic->save(document); + show->showToast(tr::lng_saved_music_added(tr::now)); + }; + menu->addAction( + tr::lng_context_save_music_profile(tr::now), + saved, + &st::menuIconProfile); + } + if (!fromSaved) { + menu->addAction( + tr::lng_context_save_music_saved(tr::now), + [=] { Window::ForwardToSelf(show, { { contextId } }); }, + &st::menuIconSavedMessages); + } + menu->addAction( + tr::lng_context_save_music_folder(tr::now), + saveAs, + &st::menuIconDownload); + + menu->addSeparator(&st::expandedMenuSeparator); + + auto item = base::make_unique_q( + menu, + st::saveMusicInfoMenu, + st::historyHasCustomEmoji, + QPoint( + st::saveMusicInfoMenu.itemPadding.left(), + st::saveMusicInfoMenu.itemPadding.top()), + TextWithEntities{ tr::lng_context_save_music_about(tr::now) }); + item->setAttribute(Qt::WA_TransparentForMouseEvents); + + item->setPointerCursor(false); + menu->addAction(std::move(item)); + }; + addAction(Ui::Menu::MenuCallback::Args{ + .text = tr::lng_context_save_music_to(tr::now), + .handler = nullptr, + .icon = &st::menuIconSoundAdd, + .fillSubmenu = fill, + .submenuSt = &st::popupMenuWithIcons, + }); +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_save_audio_action.h b/Telegram/SourceFiles/history/view/media/history_view_save_audio_action.h new file mode 100644 index 0000000000..103ac6ae95 --- /dev/null +++ b/Telegram/SourceFiles/history/view/media/history_view_save_audio_action.h @@ -0,0 +1,29 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +class DocumentData; +class HistoryItem; + +namespace Ui::Menu { +struct MenuCallback; +} // namespace Ui::Menu + +namespace Window { +class SessionController; +} // namespace Window + +namespace HistoryView { + +void AddSaveAudioAction( + const Ui::Menu::MenuCallback &addAction, + not_null item, + not_null document, + not_null controller); + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/info/saved/info_saved_music_widget.cpp b/Telegram/SourceFiles/info/saved/info_saved_music_widget.cpp index 458d71b2e3..208b6ec680 100644 --- a/Telegram/SourceFiles/info/saved/info_saved_music_widget.cpp +++ b/Telegram/SourceFiles/info/saved/info_saved_music_widget.cpp @@ -161,9 +161,10 @@ void MusicInner::refreshEmpty() { const auto knownEmpty = savedMusic->countKnown(_peer->id); _empty = object_ptr( this, - (!knownEmpty AssertIsDebug() + (!knownEmpty ? tr::lng_contacts_loading(Ui::Text::WithEntities) - : rpl::single(TextWithEntities{ u"no muzlo found :("_q })), + : rpl::single( + tr::lng_media_song_empty(tr::now, Ui::Text::WithEntities))), st::giftListAbout); _empty->show(); _emptyLoading = !knownEmpty; @@ -323,7 +324,9 @@ void MusicWidget::selectionAction(SelectionAction action) { } rpl::producer MusicWidget::title() { - return tr::lng_media_type_songs(); + return controller()->key().musicPeer()->isSelf() + ? tr::lng_media_saved_music_your() + : tr::lng_media_saved_music_title(); } std::shared_ptr MakeMusic(not_null peer) { diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index dd4c670b74..8f1373b45b 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1446,3 +1446,9 @@ groupMembersWidgetList: PeerList(defaultPeerList) { nameFgChecked: contactsNameFg; } } + +saveMusicInfoMenu: Menu(defaultMenu) { + itemBgOver: windowBg; + itemFgOver: windowFg; + itemPadding: margins(17px, 4px, 17px, 3px); +} diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index e15f6a33cb..9a38fa0ef9 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -240,32 +240,6 @@ void PeerMenuAddMuteSubmenuAction( } } -void ForwardToSelf( - std::shared_ptr show, - const Data::ForwardDraft &draft) { - const auto session = &show->session(); - const auto history = session->data().history(session->user()); - auto resolved = history->resolveForwardDraft(draft); - if (!resolved.items.empty()) { - const auto count = resolved.items.size(); - auto action = Api::SendAction(history); - action.clearDraft = false; - action.generateLocal = false; - session->api().forwardMessages( - std::move(resolved), - action, - [=] { - auto phrase = rpl::variable( - ChatHelpers::ForwardedMessagePhrase({ - .toCount = 1, - .singleMessage = (count == 1), - .to1 = session->user(), - })).current(); - show->showToast(std::move(phrase)); - }); - } -} - class Filler { public: Filler( @@ -3894,4 +3868,30 @@ void PeerMenuConfirmToggleFee( })); } +void ForwardToSelf( + std::shared_ptr show, + const Data::ForwardDraft &draft) { + const auto session = &show->session(); + const auto history = session->data().history(session->user()); + auto resolved = history->resolveForwardDraft(draft); + if (!resolved.items.empty()) { + const auto count = resolved.items.size(); + auto action = Api::SendAction(history); + action.clearDraft = false; + action.generateLocal = false; + session->api().forwardMessages( + std::move(resolved), + action, + [=] { + auto phrase = rpl::variable( + ChatHelpers::ForwardedMessagePhrase({ + .toCount = 1, + .singleMessage = (count == 1), + .to1 = session->user(), + })).current(); + show->showToast(std::move(phrase)); + }); + } +} + } // namespace Window diff --git a/Telegram/SourceFiles/window/window_peer_menu.h b/Telegram/SourceFiles/window/window_peer_menu.h index a39b2ba3ac..31d1a9b9fe 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.h +++ b/Telegram/SourceFiles/window/window_peer_menu.h @@ -54,6 +54,10 @@ enum class PeerType : uint8; using PeerTypes = base::flags; } // namespace InlineBots +namespace Main { +class SessionShow; +} // namespace Main + namespace Window { class Controller; @@ -260,4 +264,8 @@ void PeerMenuConfirmToggleFee( not_null user, bool removeFee); +void ForwardToSelf( + std::shared_ptr show, + const Data::ForwardDraft &draft); + } // namespace Window