diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 963205f75a..e6e6884e4c 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1280,6 +1280,8 @@ PRIVATE menu/menu_antispam_validator.h menu/menu_item_download_files.cpp menu/menu_item_download_files.h + menu/menu_item_rate_transcribe_session.cpp + menu/menu_item_rate_transcribe_session.h menu/menu_mute.cpp menu/menu_mute.h menu/menu_send.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index ac65cc4fde..77f3ead60d 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4310,6 +4310,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_context_remove_from_group" = "Remove from group"; "lng_context_add_to_group" = "Add to group"; +"lng_context_rate_transcription" = "Rate transcription"; +"lng_toast_sent_rate_transcription" = "Thank you for your feedback!"; + "lng_context_copy_link" = "Copy Link"; "lng_context_copy_message_link" = "Copy Message Link"; "lng_context_copy_post_link" = "Copy Post Link"; diff --git a/Telegram/SourceFiles/api/api_transcribes.cpp b/Telegram/SourceFiles/api/api_transcribes.cpp index dd983dc37b..17681cc849 100644 --- a/Telegram/SourceFiles/api/api_transcribes.cpp +++ b/Telegram/SourceFiles/api/api_transcribes.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item_helpers.h" #include "main/main_app_config.h" #include "main/main_session.h" +#include "main/main_session_settings.h" namespace Api { @@ -25,6 +26,32 @@ Transcribes::Transcribes(not_null api) , _api(&api->instance()) { } +bool Transcribes::isRated(not_null item) const { + const auto fullId = item->fullId(); + for (const auto &[transcribeId, id] : _ids) { + if (id == fullId) { + return _session->settings().isTranscriptionRated(transcribeId); + } + } + return false; +} + +void Transcribes::rate(not_null item, bool isGood) { + const auto fullId = item->fullId(); + for (const auto &[transcribeId, id] : _ids) { + if (id == fullId) { + _api.request(MTPmessages_RateTranscribedAudio( + item->history()->peer->input, + MTP_int(item->id), + MTP_long(transcribeId), + MTP_bool(isGood))).send(); + _session->settings().markTranscriptionAsRated(transcribeId); + _session->saveSettings(); + return; + } + } +} + bool Transcribes::freeFor(not_null item) const { if (const auto channel = item->history()->peer->asMegagroup()) { const auto owner = &channel->owner(); diff --git a/Telegram/SourceFiles/api/api_transcribes.h b/Telegram/SourceFiles/api/api_transcribes.h index b074e42f13..93b42401df 100644 --- a/Telegram/SourceFiles/api/api_transcribes.h +++ b/Telegram/SourceFiles/api/api_transcribes.h @@ -37,6 +37,8 @@ public: void apply(const MTPDupdateTranscribedAudio &update); [[nodiscard]] bool freeFor(not_null item) const; + [[nodiscard]] bool isRated(not_null item) const; + void rate(not_null item, bool isGood); [[nodiscard]] bool trialsSupport(); [[nodiscard]] TimeId trialsRefreshAt(); diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 252a7dbc94..9025996c95 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -1626,4 +1626,17 @@ frozenInfoBox: Box(defaultBox) { shadowIgnoreTopSkip: true; } +menuTranscribeItemPadding: margins(0px, 10px, 0px, 10px); +menuTranscribeDummyButton: IconButton(defaultIconButton) { + width: 40px; + height: 40px; + + icon: icon {{ "chat/input_attach", historyComposeIconFgOver }}; + iconOver: icon {{ "chat/input_attach", historyComposeIconFgOver }}; + + rippleAreaPosition: point(3px, 3px); + rippleAreaSize: 34px; + ripple: defaultRippleAnimationBgOver; +} + roundVideoFont: font(14px semibold); diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 6ffd488289..dcf365b534 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -73,6 +73,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session_settings.h" #include "mainwidget.h" #include "menu/menu_item_download_files.h" +#include "menu/menu_item_rate_transcribe.h" +#include "menu/menu_item_rate_transcribe_session.h" #include "menu/menu_sponsored.h" #include "core/application.h" #include "apiwrap.h" @@ -2500,6 +2502,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { }, &st::menuIconStickers); } }; + auto rateTranscriptionItem = (HistoryItem*)(nullptr); const auto addDocumentActions = [&](not_null document, HistoryItem *item) { if (document->loading()) { _menu->addAction(tr::lng_context_cancel_download(tr::now), [=] { @@ -2559,6 +2562,11 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { document); }, &st::menuIconStickers); } + if (item + && (lnkIsVoice || document->isVideoMessage()) + && Menu::HasRateTranscribeItem(item)) { + rateTranscriptionItem = item; + } }; #ifdef _DEBUG // Sometimes we need to save emoji to files. @@ -2972,6 +2980,12 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { &st::menuIconGiftPremium); } } + } else if (!rateTranscriptionItem && media->document()) { + if ((media->document()->isVoiceMessage() + || media->document()->isVideoMessage()) + && Menu::HasRateTranscribeItem(item)) { + rateTranscriptionItem = item; + } } } if (!item->isService() && view && actionText.isEmpty()) { @@ -3139,6 +3153,13 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { _controller); } + if (!_menu->empty() && rateTranscriptionItem) { + _menu->insertAction(0, base::make_unique_q( + _menu, + _menu->st().menu, + Menu::RateTranscribeCallbackFactory(rateTranscriptionItem))); + } + if (_menu->empty()) { _menu = nullptr; return; diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 84e258e2fb..53017e65de 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -44,6 +44,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/boxes/report_box_graphics.h" #include "ui/ui_utility.h" #include "menu/menu_item_download_files.h" +#include "menu/menu_item_rate_transcribe.h" +#include "menu/menu_item_rate_transcribe_session.h" #include "menu/menu_send.h" #include "ui/boxes/confirm_box.h" #include "ui/boxes/show_or_premium_box.h" @@ -338,6 +340,16 @@ void AddDocumentActions( const auto controller = list->controller(); AddSaveSoundForNotifications(menu, item, document, controller); } + if ((document->isVoiceMessage() + || document->isVideoMessage()) + && Menu::HasRateTranscribeItem(item)) { + if (!menu->empty()) { + menu->insertAction(0, base::make_unique_q( + menu, + menu->st().menu, + Menu::RateTranscribeCallbackFactory(item))); + } + } AddSaveDocumentAction(menu, item, document, list); AddCopyFilename( menu, diff --git a/Telegram/SourceFiles/main/main_session_settings.cpp b/Telegram/SourceFiles/main/main_session_settings.cpp index b4645e0f51..2f5d97a803 100644 --- a/Telegram/SourceFiles/main/main_session_settings.cpp +++ b/Telegram/SourceFiles/main/main_session_settings.cpp @@ -54,7 +54,9 @@ QByteArray SessionSettings::serialize() const { + (_ringtoneVolumes.size() * (0 + sizeof(quint64) * 3 // ThreadId - + sizeof(ushort))); // Volume + + sizeof(ushort))) // Volume + + sizeof(qint32) // _ratedTranscriptions size + + (_ratedTranscriptions.size() * sizeof(quint64)); auto result = QByteArray(); result.reserve(size); @@ -122,6 +124,10 @@ QByteArray SessionSettings::serialize() const { << SerializePeerId(key.monoforumPeerId) << ushort(value); } + stream << qint32(_ratedTranscriptions.size()); + for (const auto &transcriptionId : _ratedTranscriptions) { + stream << quint64(transcriptionId); + } } Ensures(result.size() == size); @@ -191,6 +197,7 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { qint32 lastNonPremiumLimitUpload = 0; base::flat_map ringtoneDefaultVolumes; base::flat_map ringtoneVolumes; + base::flat_set ratedTranscriptions; stream >> versionTag; if (versionTag == kVersionTag) { @@ -419,7 +426,7 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { stream >> count; if (stream.status() == QDataStream::Ok) { for (auto i = 0; i != count; ++i) { - quint64 period; + auto period = quint64(); stream >> period; mutePeriods.emplace_back(period); } @@ -561,6 +568,23 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { } } } + if (!stream.atEnd()) { + auto count = qint32(0); + stream >> count; + if (stream.status() == QDataStream::Ok) { + for (auto i = 0; i != count; ++i) { + auto transcriptionId = quint64(); + stream >> transcriptionId; + if (stream.status() != QDataStream::Ok) { + LOG(("App Error: " + "Bad data for SessionSettings::addFromSerialized()" + "with ratedTranscriptions")); + return; + } + ratedTranscriptions.emplace(transcriptionId); + } + } + } if (stream.status() != QDataStream::Ok) { LOG(("App Error: " "Bad data for SessionSettings::addFromSerialized()")); @@ -610,6 +634,7 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { _verticalSubsectionTabs = std::move(verticalSubsectionTabs); _ringtoneDefaultVolumes = std::move(ringtoneDefaultVolumes); _ringtoneVolumes = std::move(ringtoneVolumes); + _ratedTranscriptions = std::move(ratedTranscriptions); if (version < 2) { app.setLastSeenWarningSeen(appLastSeenWarningSeen == 1); @@ -822,4 +847,12 @@ void SessionSettings::setRingtoneVolume( } } +void SessionSettings::markTranscriptionAsRated(uint64 transcriptionId) { + _ratedTranscriptions.emplace(transcriptionId); +} + +bool SessionSettings::isTranscriptionRated(uint64 transcriptionId) const { + return _ratedTranscriptions.contains(transcriptionId); +} + } // namespace Main diff --git a/Telegram/SourceFiles/main/main_session_settings.h b/Telegram/SourceFiles/main/main_session_settings.h index 1069af4510..bab7c6496c 100644 --- a/Telegram/SourceFiles/main/main_session_settings.h +++ b/Telegram/SourceFiles/main/main_session_settings.h @@ -162,6 +162,9 @@ public: MsgId topicRootId, PeerId monoforumPeerId) const; + void markTranscriptionAsRated(uint64 transcriptionId); + [[nodiscard]] bool isTranscriptionRated(uint64 transcriptionId) const; + private: static constexpr auto kDefaultSupportChatsLimitSlice = 7 * 24 * 60 * 60; static constexpr auto kPhotoEditorHintMaxShowsCount = 5; @@ -202,6 +205,8 @@ private: = kDefaultSupportChatsLimitSlice; rpl::variable _supportAllSearchResults = false; + base::flat_set _ratedTranscriptions; + }; } // namespace Main diff --git a/Telegram/SourceFiles/menu/menu_item_rate_transcribe.cpp b/Telegram/SourceFiles/menu/menu_item_rate_transcribe.cpp new file mode 100644 index 0000000000..75567c7692 --- /dev/null +++ b/Telegram/SourceFiles/menu/menu_item_rate_transcribe.cpp @@ -0,0 +1,166 @@ +/* +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 "menu/menu_item_rate_transcribe.h" + +#include "base/call_delayed.h" +#include "lang/lang_keys.h" +#include "ui/rect.h" +#include "ui/toast/toast.h" +#include "ui/ui_utility.h" +#include "ui/vertical_list.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/labels.h" +#include "ui/widgets/popup_menu.h" +#include "ui/wrap/vertical_layout.h" +#include "styles/style_chat_helpers.h" +#include "styles/style_layers.h" + +namespace Menu { +namespace { + +constexpr auto kDuration = crl::time(5000); + +} // namespace + +RateTranscribe::RateTranscribe( + not_null parent, + const style::Menu &st, + Fn rate) +: Ui::Menu::ItemBase(parent, st) +, _dummyAction(Ui::CreateChild(this)) { + setAcceptBoth(true); + + initResizeHook(parent->sizeValue()); + + enableMouseSelecting(); + + const auto content = Ui::CreateChild(this); + + Ui::AddSkip(content); + + const auto label = content->add( + object_ptr( + content, + tr::lng_context_rate_transcription(), + st::boxDividerLabel), + style::margins(), + style::al_top); + setMinWidth( + label->st().style.font->width( + tr::lng_context_rate_transcription(tr::now))); + widthValue() | rpl::start_with_next([=](int w) { + content->resizeToWidth(parentWidget()->width()); + }, content->lifetime()); + Ui::AddSkip(content); + + // const auto leftButton = Ui::CreateChild( + // this, + // st::menuTranscribeDummyButton); + // const auto rightButton = Ui::CreateChild( + // this, + // st::menuTranscribeDummyButton); + const auto leftButton = Ui::CreateSimpleCircleButton( + this, + st::defaultRippleAnimation); + { + leftButton->resize(Size(st::menuTranscribeDummyButton.width)); + const auto label = Ui::CreateChild( + leftButton, + QString::fromUtf8("\U0001F44D")); + label->setAttribute(Qt::WA_TransparentForMouseEvents, true); + leftButton->sizeValue() | rpl::start_with_next([=](QSize s) { + label->moveToLeft( + (s.width() - label->width()) / 2, + (s.height() - label->height()) / 2); + }, label->lifetime()); + } + const auto rightButton = Ui::CreateSimpleCircleButton( + this, + st::defaultRippleAnimation); + { + rightButton->resize(Size(st::menuTranscribeDummyButton.width)); + const auto label = Ui::CreateChild( + rightButton, + QString::fromUtf8("\U0001F44E")); + label->setAttribute(Qt::WA_TransparentForMouseEvents, true); + rightButton->sizeValue() | rpl::start_with_next([=](QSize s) { + label->moveToLeft( + (s.width() - label->width()) / 2, + (s.height() - label->height()) / 2); + }, label->lifetime()); + } + { + const auto showToast = [=, + weak = base::make_weak(parent->parentWidget())]{ + if (const auto strong = weak.get()) { + base::call_delayed( + st::universalDuration * 1.1, + crl::guard(strong, [=] { + Ui::Toast::Show(strong->window(), { + .text = tr::lng_toast_sent_rate_transcription( + tr::now, + TextWithEntities::Simple), + .duration = kDuration, + }); + })); + } + }; + const auto hideMenu = [=, weak = base::make_weak(parent)] { + if (const auto strong = weak.get()) { + base::call_delayed( + st::universalDuration, + crl::guard(strong, [=] { strong->hideMenu(false); })); + } + }; + leftButton->setClickedCallback([=] { + rate(true); + showToast(); + hideMenu(); + }); + rightButton->setClickedCallback([=] { + rate(false); + showToast(); + hideMenu(); + }); + } + _desiredHeight = rect::m::sum::v(st::menuTranscribeItemPadding) + + st::menuTranscribeDummyButton.height + + label->st().style.font->height; + rpl::combine( + content->geometryValue(), + label->geometryValue() + ) | rpl::start_with_next([=]( + const QRect &contentRect, + const QRect &labelRect) { + leftButton->moveToLeft( + labelRect.x(), + rect::bottom(contentRect)); + rightButton->moveToLeft( + rect::right(labelRect) - rightButton->width(), + rect::bottom(contentRect)); + _desiredHeight = rect::m::sum::v(st::menuTranscribeItemPadding) + + leftButton->height() + + labelRect.height(); + }, leftButton->lifetime()); + leftButton->show(); + rightButton->show(); +} + +not_null RateTranscribe::action() const { + return _dummyAction; +} + +bool RateTranscribe::isEnabled() const { + return true; +} + +int RateTranscribe::contentHeight() const { + return _desiredHeight; +} + +} // namespace Menu diff --git a/Telegram/SourceFiles/menu/menu_item_rate_transcribe.h b/Telegram/SourceFiles/menu/menu_item_rate_transcribe.h new file mode 100644 index 0000000000..1a147c2f50 --- /dev/null +++ b/Telegram/SourceFiles/menu/menu_item_rate_transcribe.h @@ -0,0 +1,41 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/widgets/menu/menu_item_base.h" + +class HistoryItem; + +namespace Ui { +class PopupMenu; +} // namespace Ui + +namespace Menu { + +class RateTranscribe final : public Ui::Menu::ItemBase { +public: + RateTranscribe( + not_null parent, + const style::Menu &st, + Fn rate); + + not_null action() const override; + bool isEnabled() const override; + +protected: + int contentHeight() const override; + +private: + int _desiredHeight = 0; + not_null _dummyAction; + +}; + +bool HasRateTranscribeItem(not_null); + +} // namespace Menu diff --git a/Telegram/SourceFiles/menu/menu_item_rate_transcribe_session.cpp b/Telegram/SourceFiles/menu/menu_item_rate_transcribe_session.cpp new file mode 100644 index 0000000000..95ce62c335 --- /dev/null +++ b/Telegram/SourceFiles/menu/menu_item_rate_transcribe_session.cpp @@ -0,0 +1,46 @@ +/* +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 "menu/menu_item_rate_transcribe_session.h" + +#include "api/api_transcribes.h" +#include "apiwrap.h" +#include "data/data_peer.h" +#include "data/data_session.h" +#include "history/history.h" +#include "history/history_item.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "ui/rect.h" +#include "ui/vertical_list.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/labels.h" +#include "ui/wrap/vertical_layout.h" +#include "styles/style_chat_helpers.h" +#include "styles/style_layers.h" + +namespace Menu { + +Fn RateTranscribeCallbackFactory( + not_null item) { + return [item](bool good) { + item->history()->peer->owner().session().api().transcribes().rate( + item, + good); + }; +} + +bool HasRateTranscribeItem(not_null item) { + const auto &peer = item->history()->peer; + if (!peer->owner().session().api().transcribes().entry( + item).result.isEmpty()) { + return !peer->owner().session().api().transcribes().isRated(item); + } + return false; +} + +} // namespace Menu diff --git a/Telegram/SourceFiles/menu/menu_item_rate_transcribe_session.h b/Telegram/SourceFiles/menu/menu_item_rate_transcribe_session.h new file mode 100644 index 0000000000..92ce16aeb2 --- /dev/null +++ b/Telegram/SourceFiles/menu/menu_item_rate_transcribe_session.h @@ -0,0 +1,19 @@ +/* +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 HistoryItem; + +namespace Menu { + +[[nodiscard]] Fn RateTranscribeCallbackFactory( + not_null); + +[[nodiscard]] bool HasRateTranscribeItem(not_null); + +} // namespace Menu diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 25d13f8750..b9f54ad843 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -196,6 +196,8 @@ PRIVATE menu/gift_resale_filter.h menu/menu_check_item.cpp menu/menu_check_item.h + menu/menu_item_rate_transcribe.cpp + menu/menu_item_rate_transcribe.h menu/menu_ttl.cpp menu/menu_ttl.h