From 27b284ef5b7f32db556d95efe0d87b19fa851ece Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 29 Nov 2023 07:29:27 +0300 Subject: [PATCH] Added initial support of trial voice transcribes. --- Telegram/Resources/langs/lang.strings | 3 + Telegram/SourceFiles/api/api_transcribes.cpp | 70 +++++++++++-- Telegram/SourceFiles/api/api_transcribes.h | 9 ++ .../history/history_item_helpers.cpp | 41 +++++++- .../history/history_item_helpers.h | 2 + .../view/history_view_transcribe_button.cpp | 97 +++++++++++++++++-- .../view/history_view_transcribe_button.h | 2 + .../view/media/history_view_document.cpp | 2 +- .../history/view/media/history_view_gif.cpp | 4 +- .../SourceFiles/intro/intro_code_input.cpp | 7 +- Telegram/SourceFiles/ui/chat/chat.style | 11 +++ Telegram/SourceFiles/ui/chat/chat_style.cpp | 7 ++ Telegram/SourceFiles/ui/chat/chat_style.h | 5 + 13 files changed, 240 insertions(+), 20 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index b27a6aa1a..cd78ed4d1 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3335,6 +3335,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_audio_player_reverse" = "Reverse order"; "lng_audio_player_shuffle" = "Shuffle"; "lng_audio_transcribe_long" = "This voice message is too long."; +"lng_audio_transcribe_trials_left#one" = "You have {count} free transcription left until {date}."; +"lng_audio_transcribe_trials_left#other" = "You have {count} free transcriptions left until {date}."; +"lng_audio_transcribe_trials_over" = "You have used all your free transcriptions this week. Wait until {date} to use it again or subscribe to {link} now."; "lng_rights_edit_admin" = "Manage permissions"; "lng_rights_edit_admin_header" = "What can this admin do?"; diff --git a/Telegram/SourceFiles/api/api_transcribes.cpp b/Telegram/SourceFiles/api/api_transcribes.cpp index 6a69b53d6..ac792883b 100644 --- a/Telegram/SourceFiles/api/api_transcribes.cpp +++ b/Telegram/SourceFiles/api/api_transcribes.cpp @@ -7,13 +7,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "api/api_transcribes.h" -#include "history/history_item.h" -#include "history/history.h" -#include "main/main_session.h" -#include "data/data_document.h" -#include "data/data_session.h" -#include "data/data_peer.h" #include "apiwrap.h" +#include "data/data_document.h" +#include "data/data_peer.h" +#include "data/data_session.h" +#include "history/history.h" +#include "history/history_item.h" +#include "history/history_item_helpers.h" +#include "main/main_account.h" +#include "main/main_app_config.h" +#include "main/main_session.h" namespace Api { @@ -22,6 +25,44 @@ Transcribes::Transcribes(not_null api) , _api(&api->instance()) { } +bool Transcribes::trialsSupport() { + if (!_trialsSupport) { + const auto count = _session->account().appConfig().get( + u"transcribe_audio_trial_weekly_number"_q, + 0); + const auto until = _session->account().appConfig().get( + u"transcribe_audio_trial_cooldown_until"_q, + 0); + _trialsSupport = (count > 0) || (until > 0); + } + return *_trialsSupport; +} + +TimeId Transcribes::trialsRefreshAt() { + if (_trialsRefreshAt < 0) { + _trialsRefreshAt = _session->account().appConfig().get( + u"transcribe_audio_trial_cooldown_until"_q, + 0); + } + return _trialsRefreshAt; +} + +int Transcribes::trialsCount() { + if (_trialsCount < 0) { + _trialsCount = _session->account().appConfig().get( + u"transcribe_audio_trial_weekly_number"_q, + -1); + return std::max(_trialsCount, 0); + } + return _trialsCount; +} + +crl::time Transcribes::trialsMaxLengthMs() const { + return 1000 * _session->account().appConfig().get( + u"transcribe_audio_trial_duration_max"_q, + 300); +} + void Transcribes::toggle(not_null item) { const auto id = item->fullId(); auto i = _map.find(id); @@ -86,6 +127,23 @@ void Transcribes::load(not_null item) { MTP_int(item->id) )).done([=](const MTPmessages_TranscribedAudio &result) { const auto &data = result.data(); + + { + const auto trialsCountChanged = data.vtrial_remains_num() + && (_trialsCount != data.vtrial_remains_num()->v); + if (trialsCountChanged) { + _trialsCount = data.vtrial_remains_num()->v; + } + const auto refreshAtChanged = data.vtrial_remains_until_date() + && (_trialsRefreshAt != data.vtrial_remains_until_date()->v); + if (refreshAtChanged) { + _trialsRefreshAt = data.vtrial_remains_until_date()->v; + } + if (trialsCountChanged) { + ShowTrialTranscribesToast(_trialsCount, _trialsRefreshAt); + } + } + auto &entry = _map[id]; entry.requestId = 0; entry.pending = data.is_pending(); diff --git a/Telegram/SourceFiles/api/api_transcribes.h b/Telegram/SourceFiles/api/api_transcribes.h index 2f8bb2234..a5c5923ec 100644 --- a/Telegram/SourceFiles/api/api_transcribes.h +++ b/Telegram/SourceFiles/api/api_transcribes.h @@ -36,12 +36,21 @@ public: void apply(const MTPDupdateTranscribedAudio &update); + [[nodiscard]] bool trialsSupport(); + [[nodiscard]] TimeId trialsRefreshAt(); + [[nodiscard]] int trialsCount(); + [[nodiscard]] crl::time trialsMaxLengthMs() const; + private: void load(not_null item); const not_null _session; MTP::Sender _api; + int _trialsCount = -1; + std::optional _trialsSupport; + TimeId _trialsRefreshAt = -1; + base::flat_map _map; base::flat_map _ids; diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index 07a552bce..d3bdddaca 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -11,20 +11,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/premium_preview_box.h" #include "calls/calls_instance.h" #include "data/notify/data_notify_settings.h" -#include "data/data_chat_participant_status.h" #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_changes.h" #include "data/data_group_call.h" #include "data/data_forum.h" #include "data/data_forum_topic.h" -#include "data/data_media_types.h" #include "data/data_message_reactions.h" #include "data/data_session.h" #include "data/data_stories.h" #include "data/data_user.h" #include "history/history.h" -#include "history/history_item.h" #include "history/history_item_components.h" #include "main/main_account.h" #include "main/main_domain.h" @@ -39,7 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/click_handler_types.h" // ClickHandlerContext. #include "ui/text/format_values.h" #include "ui/text/text_utilities.h" -#include "ui/text/text_entity.h" +#include "ui/toast/toast.h" #include "ui/item_text_options.h" #include "lang/lang_keys.h" @@ -744,3 +741,39 @@ void CheckReactionNotificationSchedule( EntityInText(EntityType::Italic, 0, result.text.size())); return result; } + +void ShowTrialTranscribesToast(int left, TimeId until) { + const auto window = Core::App().activeWindow(); + if (!window) { + return; + } + const auto filter = [=](const auto &...) { + if (const auto controller = window->sessionController()) { + ShowPremiumPreviewBox(controller, PremiumPreview::VoiceToText); + window->activate(); + } + return false; + }; + const auto date = langDateTime(base::unixtime::parse(until)); + constexpr auto kToastDuration = crl::time(4000); + const auto text = left + ? tr::lng_audio_transcribe_trials_left( + tr::now, + lt_count, + left, + lt_date, + { date }, + Ui::Text::WithEntities) + : tr::lng_audio_transcribe_trials_over( + tr::now, + lt_date, + Ui::Text::Bold(date), + lt_link, + Ui::Text::Link(tr::lng_settings_privacy_premium_link(tr::now)), + Ui::Text::WithEntities); + window->uiShow()->showToast(Ui::Toast::Config{ + .text = text, + .duration = kToastDuration, + .filter = filter, + }); +} diff --git a/Telegram/SourceFiles/history/history_item_helpers.h b/Telegram/SourceFiles/history/history_item_helpers.h index 855f1b653..661efe652 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.h +++ b/Telegram/SourceFiles/history/history_item_helpers.h @@ -153,3 +153,5 @@ ClickHandlerPtr JumpToStoryClickHandler( [[nodiscard]] ClickHandlerPtr GroupCallClickHandler( not_null peer, CallId callId); + +void ShowTrialTranscribesToast(int left, TimeId until); diff --git a/Telegram/SourceFiles/history/view/history_view_transcribe_button.cpp b/Telegram/SourceFiles/history/view/history_view_transcribe_button.cpp index 1e89e5e46..523cd5ce1 100644 --- a/Telegram/SourceFiles/history/view/history_view_transcribe_button.cpp +++ b/Telegram/SourceFiles/history/view/history_view_transcribe_button.cpp @@ -7,18 +7,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/history_view_transcribe_button.h" +#include "base/unixtime.h" +#include "boxes/premium_preview_box.h" +#include "core/click_handler_types.h" // ClickHandlerContext #include "history/history.h" #include "history/history_item.h" +#include "data/data_document.h" #include "data/data_session.h" #include "main/main_session.h" +#include "lang/lang_keys.h" #include "ui/chat/chat_style.h" -#include "ui/click_handler.h" #include "ui/effects/radial_animation.h" #include "ui/effects/ripple_animation.h" #include "ui/painter.h" +#include "ui/rect.h" #include "api/api_transcribes.h" #include "apiwrap.h" #include "styles/style_chat.h" +#include "window/window_session_controller.h" namespace HistoryView { namespace { @@ -26,6 +32,21 @@ namespace { constexpr auto kInNonChosenOpacity = 0.12; constexpr auto kOutNonChosenOpacity = 0.18; +void ClipPainterForLock(QPainter &p, bool roundview, const QRect &r) { + const auto &pos = roundview + ? st::historyFastTranscribeLockOverlayPos + : st::historyTranscribeLockOverlayPos; + const auto &size = roundview + ? st::historyFastTranscribeLockOverlaySize + : st::historyTranscribeLockOverlaySize; + + auto clipPath = QPainterPath(); + clipPath.addRect(r); + const auto clear = QRect(pos + r.topLeft(), size); + clipPath.addRoundedRect(clear, clear.width() * 0.5, clear.height() * 0.5); + p.setClipPath(clipPath); +} + } // namespace TranscribeButton::TranscribeButton( @@ -84,12 +105,22 @@ void TranscribeButton::paint( } } - PainterHighQualityEnabler hq(p); + auto hq = PainterHighQualityEnabler(p); p.setPen(Qt::NoPen); p.setBrush(context.st->msgServiceBg()); p.drawEllipse(r); - context.st->historyFastTranscribeIcon().paintInCenter(p, r); + if (!_loading && hasLock()) { + ClipPainterForLock(p, true, r); + context.st->historyFastTranscribeIcon().paintInCenter(p, r); + p.setClipping(false); + context.st->historyFastTranscribeLock().paint( + p, + r.topLeft() + st::historyFastTranscribeLockPos, + r.width()); + } else { + context.st->historyFastTranscribeIcon().paintInCenter(p, r); + } const auto state = _animation ? _animation->computeState() @@ -169,7 +200,19 @@ void TranscribeButton::paint( p.scale(1. - opened, 1. - opened); p.translate(-r.center()); } - stm->historyTranscribeIcon.paintInCenter(p, r); + + if (!_loading && hasLock()) { + ClipPainterForLock(p, false, r); + stm->historyTranscribeIcon.paintInCenter(p, r); + p.setClipping(false); + stm->historyTranscribeLock.paint( + p, + r.topLeft() + st::historyTranscribeLockPos, + r.width()); + } else { + stm->historyTranscribeIcon.paintInCenter(p, r); + } + if (opened != 0.) { p.restore(); } @@ -177,6 +220,21 @@ void TranscribeButton::paint( p.setOpacity(1.); } +bool TranscribeButton::hasLock() const { + if (_item->history()->session().premium()) { + return false; + } + if (_item->history()->session().api().transcribes().trialsCount()) { + return false; + } + const auto until = _item->history()->session().api().transcribes() + .trialsRefreshAt(); + if (!until || base::unixtime::now() >= until) { + return false; + } + return true; +} + void TranscribeButton::setOpened(bool opened, Fn update) { if (_opened == opened) { return; @@ -201,8 +259,35 @@ ClickHandlerPtr TranscribeButton::link() { } const auto session = &_item->history()->session(); const auto id = _item->fullId(); - _link = std::make_shared([=] { - if (const auto item = session->data().message(id)) { + _link = std::make_shared([=](ClickContext context) { + const auto item = session->data().message(id); + if (!item) { + return; + } + if (session->premium()) { + return session->api().transcribes().toggle(item); + } + const auto my = context.other.value(); + if (hasLock()) { + if (const auto controller = my.sessionWindow.get()) { + ShowPremiumPreviewBox( + controller, + PremiumPreview::VoiceToText); + } + } else { + const auto max = session->api().transcribes().trialsMaxLengthMs(); + const auto doc = _item->media() + ? _item->media()->document() + : nullptr; + if (doc && (doc->isVoiceMessage() || doc->isVideoMessage())) { + if (doc->duration() > max) { + if (const auto controller = my.sessionWindow.get()) { + controller->uiShow()->showToast( + tr::lng_audio_transcribe_long(tr::now)); + return; + } + } + } session->api().transcribes().toggle(item); } }); diff --git a/Telegram/SourceFiles/history/view/history_view_transcribe_button.h b/Telegram/SourceFiles/history/view/history_view_transcribe_button.h index 6fd26c52a..db318eb5a 100644 --- a/Telegram/SourceFiles/history/view/history_view_transcribe_button.h +++ b/Telegram/SourceFiles/history/view/history_view_transcribe_button.h @@ -36,6 +36,8 @@ public: [[nodiscard]] bool contains(const QPoint &p); private: + [[nodiscard]] bool hasLock() const; + const not_null _item; const bool _roundview = false; const QSize _size; diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp index 4a533b88d..04df9f2aa 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp @@ -306,7 +306,7 @@ QSize Document::countOptimalSize() { const auto voice = Get(); if (voice) { const auto session = &_realParent->history()->session(); - if (!session->premium()) { + if (!session->premium() && !session->api().transcribes().trialsSupport()) { voice->transcribe = nullptr; voice->transcribeText = {}; } else { diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 1739c6390..134d09fb5 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -1973,7 +1973,9 @@ bool Gif::needCornerStatusDisplay() const { } void Gif::ensureTranscribeButton() const { - if (_data->isVideoMessage() && _data->session().premium()) { + if (_data->isVideoMessage() + && (_data->session().premium() + || _data->session().api().transcribes().trialsSupport())) { if (!_transcribe) { _transcribe = std::make_unique( _realParent, diff --git a/Telegram/SourceFiles/intro/intro_code_input.cpp b/Telegram/SourceFiles/intro/intro_code_input.cpp index b68da1c65..5c0005b20 100644 --- a/Telegram/SourceFiles/intro/intro_code_input.cpp +++ b/Telegram/SourceFiles/intro/intro_code_input.cpp @@ -9,8 +9,8 @@ #include "lang/lang_keys.h" #include "ui/abstract_button.h" #include "ui/effects/shake_animation.h" +#include "ui/painter.h" #include "ui/rect.h" -#include "ui/text/text_entity.h" #include "ui/widgets/popup_menu.h" #include "styles/style_intro.h" #include "styles/style_layers.h" // boxRadius @@ -127,7 +127,10 @@ void CodeDigit::paintEvent(QPaintEvent *e) { p.setClipPath(clipPath); p.fillRect(rect(), st::windowBgOver); - p.strokePath(clipPath, _borderPen); + { + auto hq = PainterHighQualityEnabler(p); + p.strokePath(clipPath, _borderPen); + } if (_viewDigit == kDigitNone) { return; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 875ba5f2c..122daf9e3 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -522,6 +522,13 @@ historyTranscribeInHide: icon {{ "chat/voice_to_text_collapse", msgFileInBg }}; historyTranscribeInHideSelected: icon {{ "chat/voice_to_text_collapse", msgFileInBgSelected }}; historyTranscribeOutHide: icon {{ "chat/voice_to_text_collapse", msgFileOutBg }}; historyTranscribeOutHideSelected: icon {{ "chat/voice_to_text_collapse", msgFileOutBgSelected }}; +historyTranscribeInLock: icon {{ "chat/mini_lock", msgFileInBg }}; +historyTranscribeInLockSelected: icon {{ "chat/mini_lock", msgFileInBgSelected }}; +historyTranscribeOutLock: icon {{ "chat/mini_lock", msgFileOutBg }}; +historyTranscribeOutLockSelected: icon {{ "chat/mini_lock", msgFileOutBgSelected }}; +historyTranscribeLockPos: point(17px, 9px); +historyTranscribeLockOverlayPos: point(19px, 11px); +historyTranscribeLockOverlaySize: size(5px, 10px); historyVideoMessageMute: icon {{ "volume_mute", historyFileThumbIconFg }}; historyVideoMessageMuteSelected: icon {{ "volume_mute", historyFileThumbIconFgSelected }}; @@ -562,6 +569,10 @@ historyFastCommentsIcon: icon {{ "fast_comments", msgServiceFg }}; historyFastCloseSize: 30px; historyFastCloseIcon: icon {{ "box_button_close", msgServiceFg }}; historyFastTranscribeIcon: icon {{ "chat/voice_to_text", msgServiceFg }}; +historyFastTranscribeLock: icon {{ "chat/mini_lock", msgServiceFg }}; +historyFastTranscribeLockPos: point(18px, 13px); +historyFastTranscribeLockOverlayPos: point(21px, 13px); +historyFastTranscribeLockOverlaySize: size(6px, 10px); historySavedFont: font(semibold 14px); diff --git a/Telegram/SourceFiles/ui/chat/chat_style.cpp b/Telegram/SourceFiles/ui/chat/chat_style.cpp index a5187ecbc..0fc4840f1 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.cpp +++ b/Telegram/SourceFiles/ui/chat/chat_style.cpp @@ -189,6 +189,7 @@ ChatStyle::ChatStyle(rpl::producer colorIndices) { make(_historyFastCommentsIcon, st::historyFastCommentsIcon); make(_historyFastShareIcon, st::historyFastShareIcon); make(_historyFastTranscribeIcon, st::historyFastTranscribeIcon); + make(_historyFastTranscribeLock, st::historyFastTranscribeLock); make(_historyGoToOriginalIcon, st::historyGoToOriginalIcon); make(_historyFastCloseIcon, st::historyFastCloseIcon); make(_historyMapPoint, st::historyMapPoint); @@ -467,6 +468,12 @@ ChatStyle::ChatStyle(rpl::producer colorIndices) { st::historyTranscribeInIconSelected, st::historyTranscribeOutIcon, st::historyTranscribeOutIconSelected); + make( + &MessageStyle::historyTranscribeLock, + st::historyTranscribeInLock, + st::historyTranscribeInLockSelected, + st::historyTranscribeOutLock, + st::historyTranscribeOutLockSelected); make( &MessageStyle::historyTranscribeHide, st::historyTranscribeInHide, diff --git a/Telegram/SourceFiles/ui/chat/chat_style.h b/Telegram/SourceFiles/ui/chat/chat_style.h index 12c049582..45a4f3caf 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.h +++ b/Telegram/SourceFiles/ui/chat/chat_style.h @@ -89,6 +89,7 @@ struct MessageStyle { style::icon historyPollChosen = { Qt::Uninitialized }; style::icon historyPollChoiceRight = { Qt::Uninitialized }; style::icon historyTranscribeIcon = { Qt::Uninitialized }; + style::icon historyTranscribeLock = { Qt::Uninitialized }; style::icon historyTranscribeHide = { Qt::Uninitialized }; std::array< std::unique_ptr, @@ -385,6 +386,9 @@ public: [[nodiscard]] const style::icon &historyFastTranscribeIcon() const { return _historyFastTranscribeIcon; } + [[nodiscard]] const style::icon &historyFastTranscribeLock() const { + return _historyFastTranscribeLock; + } [[nodiscard]] const style::icon &historyGoToOriginalIcon() const { return _historyGoToOriginalIcon; } @@ -514,6 +518,7 @@ private: style::icon _historyFastCommentsIcon = { Qt::Uninitialized }; style::icon _historyFastShareIcon = { Qt::Uninitialized }; style::icon _historyFastTranscribeIcon = { Qt::Uninitialized }; + style::icon _historyFastTranscribeLock = { Qt::Uninitialized }; style::icon _historyGoToOriginalIcon = { Qt::Uninitialized }; style::icon _historyFastCloseIcon = { Qt::Uninitialized }; style::icon _historyMapPoint = { Qt::Uninitialized };