diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 6cf845c5fd..3800886afa 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -818,6 +818,8 @@ PRIVATE history/view/media/history_view_sticker_player_abstract.h history/view/media/history_view_story_mention.cpp history/view/media/history_view_story_mention.h + history/view/media/history_view_suggest_decision.cpp + history/view/media/history_view_suggest_decision.h history/view/media/history_view_theme_document.cpp history/view/media/history_view_theme_document.h history/view/media/history_view_todo_list.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 207aecbdae..d36e7f77ed 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4421,12 +4421,45 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_suggest_bar_priced_dated" = "{amount} {date}"; "lng_suggest_bar_dated" = "Publish on {date}"; "lng_suggest_options_title" = "Suggest a Message"; +"lng_suggest_options_change" = "Suggest Changes"; "lng_suggest_options_price" = "Enter Price in Stars"; -"lng_suggest_options_price_about" = "Choose how many Stars you want to offer {channel} to publish this message."; +"lng_suggest_options_price_about" = "Choose how many Stars to pay to publish this message."; "lng_suggest_options_date" = "Time"; "lng_suggest_options_date_any" = "Anytime"; "lng_suggest_options_date_about" = "Select the date and time you want the message to be published."; "lng_suggest_options_offer" = "Offer {amount}"; +"lng_suggest_options_update" = "Update Terms"; + +"lng_suggest_action_decline" = "Decline"; +"lng_suggest_action_accept" = "Accept"; +"lng_suggest_action_change" = "Suggest Changes"; +"lng_suggest_action_your" = "You suggest to post this message."; +"lng_suggest_action_his" = "{from} suggests to post this message."; +"lng_suggest_action_price_label" = "Price"; +"lng_suggest_action_time_label" = "Time"; +"lng_suggest_action_agreement" = "Agreement reached!"; +"lng_suggest_action_agree_date" = "The post will be automatically published on {channel} {date}."; +"lng_suggest_action_your_charged" = "You have been charged {amount}."; +"lng_suggest_action_his_charged" = "{from} have been charged {amount}."; +"lng_suggest_action_agree_receive" = "{channel} will receive the Stars once the post has been live for 24 hours."; +"lng_suggest_action_agree_removed" = "If {channel} removes the post before it has been live for 24 hours, the Stars will be refunded."; +"lng_suggest_action_your_not_enough" = "**Transaction failed** because you didn't have enough Stars."; +"lng_suggest_action_his_not_enough" = "**Transaction failed** because the user didn't have enough Stars."; +"lng_suggest_action_declined" = "{from} rejected the message."; +"lng_suggest_action_declined_reason" = "{from} rejected the message with the comment."; +"lng_suggest_change_price" = "{from} suggests a new price for the message."; +"lng_suggest_change_time" = "{from} suggests a new time for the message."; +"lng_suggest_change_price_time" = "{from} suggests a new price and time for the message."; +"lng_suggest_change_content" = "{from} suggests changes for the message."; +"lng_suggest_change_price_label" = "New Price"; +"lng_suggest_change_time_label" = "New Time"; +"lng_suggest_change_text_label" = "Check the suggested message below"; +"lng_suggest_menu_edit_message" = "Edit Message"; +"lng_suggest_menu_edit_price" = "Edit Price"; +"lng_suggest_menu_edit_time" = "Edit Time"; +"lng_suggest_decline_title" = "Decline"; +"lng_suggest_decline_text" = "Do you want to decline publishing this post from {from}?"; +"lng_suggest_decline_reason" = "Add a reason (optional)"; "lng_reply_in_another_title" = "Reply in..."; "lng_reply_in_another_chat" = "Reply in Another Chat"; diff --git a/Telegram/SourceFiles/api/api_bot.cpp b/Telegram/SourceFiles/api/api_bot.cpp index 87111914ef..b86ce1a70c 100644 --- a/Telegram/SourceFiles/api/api_bot.cpp +++ b/Telegram/SourceFiles/api/api_bot.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "api/api_cloud_password.h" #include "api/api_send_progress.h" +#include "api/api_suggest_post.h" #include "boxes/share_box.h" #include "boxes/passcode_box.h" #include "boxes/url_auth_box.h" @@ -521,6 +522,27 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { controller->showToast(tr::lng_text_copied(tr::now)); } } break; + + case ButtonType::SuggestAccept: { + Api::AcceptClickHandler(item)->onClick(ClickContext{ + Qt::LeftButton, + QVariant::fromValue(context), + }); + } break; + + case ButtonType::SuggestDecline: { + Api::DeclineClickHandler(item)->onClick(ClickContext{ + Qt::LeftButton, + QVariant::fromValue(context), + }); + } break; + + case ButtonType::SuggestChange: { + Api::SuggestChangesClickHandler(item)->onClick(ClickContext{ + Qt::LeftButton, + QVariant::fromValue(context), + }); + } break; } } diff --git a/Telegram/SourceFiles/api/api_suggest_post.cpp b/Telegram/SourceFiles/api/api_suggest_post.cpp index 6ed775bbee..0a8f4ffa36 100644 --- a/Telegram/SourceFiles/api/api_suggest_post.cpp +++ b/Telegram/SourceFiles/api/api_suggest_post.cpp @@ -17,7 +17,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "main/main_session.h" #include "ui/boxes/choose_date_time.h" +#include "ui/layers/generic_box.h" +#include "ui/boxes/confirm_box.h" +#include "ui/text/text_utilities.h" +#include "ui/widgets/fields/input_field.h" #include "window/window_session_controller.h" +#include "styles/style_chat.h" +#include "styles/style_layers.h" namespace Api { namespace { @@ -128,6 +134,49 @@ void RequestApprovalDate( controller->uiShow()->show(std::move(dateBox)); } +void RequestDeclineComment( + not_null controller, + not_null item) { + const auto id = item->fullId(); + controller->uiShow()->show(Box([=](not_null box) { + const auto callback = std::make_shared>(); + Ui::ConfirmBox(box, { + .text = tr::lng_suggest_decline_text( + lt_from, + rpl::single(Ui::Text::Bold(item->from()->shortName())), + Ui::Text::WithEntities), + .confirmed = [=](Fn close) { (*callback)(); close(); }, + .confirmText = tr::lng_suggest_action_decline(), + .confirmStyle = &st::attentionBoxButton, + .title = tr::lng_suggest_decline_title(), + }); + const auto reason = box->addRow(object_ptr( + box, + st::factcheckField, + Ui::InputField::Mode::NoNewlines, + tr::lng_suggest_decline_reason())); + box->setFocusCallback([=] { + reason->setFocusFast(); + }); + *callback = [=, weak = Ui::MakeWeak(box)] { + const auto item = controller->session().data().message(id); + if (!item) { + return; + } + SendDecline(controller, item, reason->getLastText().trimmed()); + if (const auto strong = weak.data()) { + strong->closeBox(); + } + }; + reason->submits( + ) | rpl::start_with_next([=](Qt::KeyboardModifiers modifiers) { + if (!(modifiers & Qt::ShiftModifier)) { + (*callback)(); + } + }, box->lifetime()); + })); +} + } // namespace std::shared_ptr AcceptClickHandler( @@ -157,24 +206,14 @@ std::shared_ptr AcceptClickHandler( std::shared_ptr DeclineClickHandler( not_null item) { - const auto session = &item->history()->session(); const auto id = item->fullId(); return std::make_shared([=](ClickContext context) { const auto my = context.other.value(); const auto controller = my.sessionWindow.get(); - if (!controller || &controller->session() != session) { + if (!controller) { return; } - const auto item = session->data().message(id); - if (!item) { - return; - } - const auto suggestion = item->Get(); - if (!suggestion) { - return; - } else { - SendDecline(controller, item, "sorry, bro.."); - } + RequestDeclineComment(controller, item); }); } diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index bcef78a0e6..eb4e1fa1d3 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -828,6 +828,8 @@ HistoryServiceDependentData *HistoryItem::GetServiceDependentData() { return done; } else if (const auto append = Get()) { return append; + } else if (const auto decision = Get()) { + return decision; } return nullptr; } @@ -1068,8 +1070,58 @@ bool HistoryItem::checkDiscussionLink(ChannelId id) const { return false; } -void HistoryItem::setReplyMarkup(HistoryMessageMarkupData &&markup) { +SuggestionActions HistoryItem::computeSuggestionActions() const { + return computeSuggestionActions(Get()); +} + +SuggestionActions HistoryItem::computeSuggestionActions( + const HistoryMessageSuggestedPost *suggest) const { + return suggest + ? computeSuggestionActions(suggest->accepted, suggest->rejected) + : SuggestionActions::None; +} + +SuggestionActions HistoryItem::computeSuggestionActions( + bool accepted, + bool rejected) const { + const auto channelIsAuthor = from()->isChannel(); + const auto amMonoforumAdmin = history()->peer->amMonoforumAdmin(); + const auto broadcast = history()->peer->monoforumBroadcast(); + const auto canDecline = isRegular() + && !(accepted || rejected) + && (channelIsAuthor ? !amMonoforumAdmin : amMonoforumAdmin); + const auto canAccept = canDecline + && (channelIsAuthor + ? !amMonoforumAdmin + : (amMonoforumAdmin + && broadcast + && broadcast->canPostMessages())); + return canAccept + ? SuggestionActions::AcceptAndDecline + : canDecline + ? SuggestionActions::Decline + : SuggestionActions::None; +} + +void HistoryItem::updateSuggestControls( + const HistoryMessageSuggestedPost *suggest) { + if (const auto markup = Get()) { + markup->updateSuggestControls(computeSuggestionActions(suggest)); + } +} + +void HistoryItem::setReplyMarkup( + HistoryMessageMarkupData &&markup, + bool ignoreSuggestButtons) { const auto requestUpdate = [&] { + const auto actions = computeSuggestionActions(); + if (actions != SuggestionActions::None + && !Has()) { + AddComponents(HistoryMessageReplyMarkup::Bit()); + } + if (const auto markup = Get()) { + markup->updateSuggestControls(actions); + } history()->owner().requestItemResize(this); history()->session().changes().messageUpdated( this, @@ -1901,8 +1953,10 @@ void HistoryItem::applyEdition(HistoryMessageEdition &&edition) { suggest->date = edition.suggest.date; suggest->accepted = edition.suggest.accepted; suggest->rejected = edition.suggest.rejected; + updateSuggestControls(suggest); } else { RemoveComponents(HistoryMessageSuggestedPost::Bit()); + updateSuggestControls(nullptr); } } @@ -1942,7 +1996,7 @@ void HistoryItem::applyEdition(const MTPDmessageService &message) { const auto wasSublist = savedSublist(); if (message.vaction().type() == mtpc_messageActionHistoryClear) { const auto wasGrouped = history()->owner().groups().isGrouped(this); - setReplyMarkup({}); + setReplyMarkup({}, true); removeFromSharedMediaIndex(); refreshMedia(nullptr); setTextValue({}); @@ -2144,8 +2198,10 @@ void HistoryItem::applyEditionToHistoryCleared() { ).c_messageService()); } -void HistoryItem::updateReplyMarkup(HistoryMessageMarkupData &&markup) { - setReplyMarkup(std::move(markup)); +void HistoryItem::updateReplyMarkup( + HistoryMessageMarkupData &&markup, + bool ignoreSuggestButtons) { + setReplyMarkup(std::move(markup), ignoreSuggestButtons); } void HistoryItem::contributeToSlowmode(TimeId realDate) { @@ -3865,6 +3921,12 @@ void HistoryItem::createComponents(CreateConfig &&config) { } if (config.suggest.exists) { mask |= HistoryMessageSuggestedPost::Bit(); + if (computeSuggestionActions( + config.suggest.accepted, + config.suggest.rejected + ) != SuggestionActions::None) { + mask |= HistoryMessageReplyMarkup::Bit(); + } } UpdateComponents(mask); @@ -3965,6 +4027,7 @@ void HistoryItem::createComponents(CreateConfig &&config) { suggest->date = config.suggest.date; suggest->accepted = config.suggest.accepted; suggest->rejected = config.suggest.rejected; + updateSuggestControls(suggest); } if (out() && isSending()) { @@ -4601,6 +4664,15 @@ void HistoryItem::createServiceFromMtp(const MTPDmessageService &message) { ) | ranges::views::transform([&](const MTPTodoItem &item) { return TodoListItemFromMTP(session, item); }) | ranges::to_vector; + } else if (type == mtpc_messageActionSuggestedPostApproval) { + const auto &data = action.c_messageActionSuggestedPostApproval(); + UpdateComponents(HistoryServiceSuggestDecision::Bit()); + const auto decision = Get(); + decision->stars = data.vstars_amount().value_or_empty(); + decision->balanceTooLow = data.is_balance_too_low(); + decision->rejected = data.is_rejected(); + decision->rejectComment = qs(data.vreject_comment().value_or_empty()); + decision->date = data.vschedule_date().value_or_empty(); } if (const auto replyTo = message.vreply_to()) { replyTo->match([&](const MTPDmessageReplyHeader &data) { @@ -4629,7 +4701,7 @@ void HistoryItem::createServiceFromMtp(const MTPDmessageService &message) { : PeerId(); const auto requiresMonoforumPeer = _history->peer->amMonoforumAdmin(); if (savedSublistPeer || requiresMonoforumPeer) { - UpdateComponents(HistoryMessageSaved::Bit()); + AddComponents(HistoryMessageSaved::Bit()); const auto saved = Get(); saved->sublistPeerId = savedSublistPeer ? savedSublistPeer @@ -5950,14 +6022,7 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { }; auto prepareSuggestedPostApproval = [&](const MTPDmessageActionSuggestedPostApproval &data) { - if (data.is_balance_too_low()) { - return PreparedServiceText{ { u"balance too low :( need %1 stars"_q.arg(data.vstars_amount().value_or_empty()) } }; - } else if (data.is_rejected()) { - return PreparedServiceText{ { u"rejected :( comment: %1"_q.arg(qs(data.vreject_comment().value_or_empty())) } }; - } else if (const auto date = data.vschedule_date().value_or_empty()) { - return PreparedServiceText{ { u"approved!! for date: %1"_q.arg(langDateTime(base::unixtime::parse(date))) } }; - } - return PreparedServiceText{ { "approved!!" } }; AssertIsDebug(); + return PreparedServiceText{ { u"hello"_q } }; }; auto prepareConferenceCall = [&](const MTPDmessageActionConferenceCall &) -> PreparedServiceText { diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 71901f8e0c..a7551d89d8 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -22,6 +22,7 @@ struct HistoryMessageMarkupData; struct HistoryMessageReplyMarkup; struct HistoryMessageTranslation; struct HistoryMessageForwarded; +struct HistoryMessageSuggestedPost; struct HistoryServiceDependentData; struct HistoryServiceTodoCompletions; enum class HistorySelfDestructType; @@ -29,6 +30,7 @@ struct PreparedServiceText; struct MessageFactcheck; class ReplyKeyboard; struct LanguageId; +enum class SuggestionActions : uchar; namespace base { template @@ -353,7 +355,9 @@ public: void overrideMedia(std::unique_ptr media); void applyEditionToHistoryCleared(); - void updateReplyMarkup(HistoryMessageMarkupData &&markup); + void updateReplyMarkup( + HistoryMessageMarkupData &&markup, + bool ignoreSuggestButtons = false); void contributeToSlowmode(TimeId realDate = 0); void clearMediaAsExpired(); @@ -575,7 +579,16 @@ private: [[nodiscard]] bool checkDiscussionLink(ChannelId id) const; - void setReplyMarkup(HistoryMessageMarkupData &&markup); + void setReplyMarkup( + HistoryMessageMarkupData &&markup, + bool ignoreSuggestButtons = false); + [[nodiscard]] SuggestionActions computeSuggestionActions() const; + [[nodiscard]] SuggestionActions computeSuggestionActions( + const HistoryMessageSuggestedPost *suggest) const; + [[nodiscard]] SuggestionActions computeSuggestionActions( + bool accepted, + bool rejected) const; + void updateSuggestControls(const HistoryMessageSuggestedPost *suggest); void changeReplyToTopCounter( not_null reply, diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index f4381d5756..25503abc47 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -1214,6 +1214,95 @@ bool HistoryMessageReplyMarkup::hiddenBy(Data::Media *media) const { return false; } +void HistoryMessageReplyMarkup::updateSuggestControls( + SuggestionActions actions) { + if (actions == SuggestionActions::AcceptAndDecline) { + data.flags |= ReplyMarkupFlag::SuggestionAccept; + } else { + data.flags &= ~ReplyMarkupFlag::SuggestionAccept; + } + if (actions == SuggestionActions::None) { + data.flags &= ~ReplyMarkupFlag::SuggestionDecline; + } else { + data.flags |= ReplyMarkupFlag::Inline + | ReplyMarkupFlag::SuggestionDecline; + } + using Type = HistoryMessageMarkupButton::Type; + const auto has = [&](Type type) { + return !data.rows.empty() + && ranges::contains( + data.rows.back(), + type, + &HistoryMessageMarkupButton::type); + }; + if (actions == SuggestionActions::AcceptAndDecline) { + // ... rows ... + // [decline] | [accept] + // [suggestchanges] + if (has(Type::SuggestChange)) { + // Nothing changed. + } else { + if (has(Type::SuggestDecline)) { + data.rows.pop_back(); + } + data.rows.push_back({ + { + Type::SuggestDecline, + tr::lng_suggest_action_decline(tr::now), + }, + { + Type::SuggestAccept, + tr::lng_suggest_action_accept(tr::now), + }, + }); + data.rows.push_back({ { + Type::SuggestChange, + tr::lng_suggest_action_change(tr::now), + } }); + data.flags |= ReplyMarkupFlag::SuggestionAccept + | ReplyMarkupFlag::SuggestionDecline; + } + if (data.rows.size() > 2) { + data.flags |= ReplyMarkupFlag::SuggestionSeparator; + } else { + data.flags &= ~ReplyMarkupFlag::SuggestionSeparator; + } + } else { + while (!data.rows.empty()) { + if (has(Type::SuggestChange) || has(Type::SuggestAccept)) { + data.rows.pop_back(); + } else if (has(Type::SuggestDecline) + && actions == SuggestionActions::None) { + data.rows.pop_back(); + } else { + break; + } + } + data.flags &= ~ReplyMarkupFlag::SuggestionAccept; + if (actions == SuggestionActions::None) { + data.flags &= ReplyMarkupFlag::SuggestionDecline; + data.flags &= ~ReplyMarkupFlag::SuggestionSeparator; + } else { + if (!has(Type::SuggestDecline)) { + // ... rows ... + // [decline] + data.rows.push_back({ { + Type::SuggestDecline, + tr::lng_suggest_action_decline(tr::now), + } }); + data.flags |= ReplyMarkupFlag::SuggestionDecline; + } + if (data.rows.size() > 1) { + data.flags |= ReplyMarkupFlag::SuggestionSeparator; + } else { + data.flags &= ~ReplyMarkupFlag::SuggestionSeparator; + } + } + } + + inlineKeyboard = nullptr; +} + HistoryMessageLogEntryOriginal::HistoryMessageLogEntryOriginal() = default; HistoryMessageLogEntryOriginal::HistoryMessageLogEntryOriginal( diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index f701aecfb0..92714a2e2d 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -58,6 +58,12 @@ struct BotKeyboardButton; extern const char kOptionFastButtonsMode[]; [[nodiscard]] bool FastButtonsMode(); +enum class SuggestionActions : uchar { + None, + Decline, + AcceptAndDecline, +}; + struct HistoryMessageVia : RuntimeComponent { void create(not_null owner, UserId userId); void resize(int32 availw) const; @@ -383,6 +389,7 @@ struct HistoryMessageReplyMarkup void createForwarded(const HistoryMessageReplyMarkup &original); void updateData(HistoryMessageMarkupData &&markup); + void updateSuggestControls(SuggestionActions actions); [[nodiscard]] bool hiddenBy(Data::Media *media) const; @@ -691,6 +698,16 @@ struct HistoryServiceTodoAppendTasks [[nodiscard]] TextWithEntities ComposeTodoTasksList( not_null append); +struct HistoryServiceSuggestDecision +: RuntimeComponent +, HistoryServiceDependentData { + int stars = 0; + TimeId date = 0; + QString rejectComment; + bool rejected = false; + bool balanceTooLow = false; +}; + struct HistoryServiceGameScore : RuntimeComponent , HistoryServiceDependentData { diff --git a/Telegram/SourceFiles/history/history_item_reply_markup.h b/Telegram/SourceFiles/history/history_item_reply_markup.h index 8a735903fb..be9084211e 100644 --- a/Telegram/SourceFiles/history/history_item_reply_markup.h +++ b/Telegram/SourceFiles/history/history_item_reply_markup.h @@ -37,6 +37,9 @@ enum class ReplyMarkupFlag : uint32 { IsNull = (1U << 7), OnlyBuyButton = (1U << 8), Persistent = (1U << 9), + SuggestionDecline = (1U << 10), + SuggestionAccept = (1U << 11), + SuggestionSeparator = (1U << 12), }; inline constexpr bool is_flag_type(ReplyMarkupFlag) { return true; } using ReplyMarkupFlags = base::flags; @@ -85,6 +88,10 @@ struct HistoryMessageMarkupButton { WebView, SimpleWebView, CopyText, + + SuggestDecline, + SuggestAccept, + SuggestChange, }; HistoryMessageMarkupButton( diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 08a2373df5..251eb7d823 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -9,11 +9,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_service_message.h" #include "history/view/history_view_message.h" +#include "history/view/media/history_view_media_generic.h" #include "history/view/media/history_view_media_grouped.h" #include "history/view/media/history_view_similar_channels.h" #include "history/view/media/history_view_sticker.h" #include "history/view/media/history_view_large_emoji.h" #include "history/view/media/history_view_custom_emoji.h" +#include "history/view/media/history_view_suggest_decision.h" #include "history/view/reactions/history_view_reactions_button.h" #include "history/view/reactions/history_view_reactions.h" #include "history/view/history_view_cursor_state.h" @@ -1072,6 +1074,16 @@ void Element::refreshMedia(Element *replacing) { this, std::make_unique(this, emoji)); } + } else if (const auto decision = item->Get()) { + _media = std::make_unique( + this, + GenerateSuggestDecisionMedia(this, decision), + MediaGenericDescriptor{ + .maxWidth = st::chatSuggestInfoWidth, + .serviceLink = decision->lnk, + .service = true, + .hideServiceText = true, + }); } else { _media = nullptr; } diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 8ced796f8f..8c097f9472 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -468,33 +468,6 @@ void Message::initPaidInformation() { } else { text = { { u"suggestion to publish for %1 stars %2"_q.arg(suggest->stars).arg(langDateTime(base::unixtime::parse(suggest->date))) } }; } - const auto channelIsAuthor = item->from()->isChannel(); - const auto amMonoforumAdmin = item->history()->peer->amMonoforumAdmin(); - const auto broadcast = item->history()->peer->monoforumBroadcast(); - const auto canDecline = item->isRegular() - && !(suggest->accepted || suggest->rejected) - && (channelIsAuthor ? !amMonoforumAdmin : amMonoforumAdmin); - const auto canAccept = canDecline - && (channelIsAuthor - ? !amMonoforumAdmin - : (amMonoforumAdmin - && broadcast - && broadcast->canPostMessages())); - if (canDecline) { - text.links.push_back(Api::DeclineClickHandler(item)); - text.text.append(", ").append(Ui::Text::Link("[Decline]", text.links.size())); - if (canAccept) { - text.links.push_back(Api::AcceptClickHandler(item)); - text.text.append(", ").append(Ui::Text::Link("[Accept]", text.links.size())); - - text.links.push_back(Api::SuggestChangesClickHandler(item)); - text.text.append(", ").append(Ui::Text::Link("[SuggestChanges]", text.links.size())); - } - } else if (suggest->accepted) { - text.text.append(", accepted!"); - } else if (suggest->rejected) { - text.text.append(", rejected :("); - } setServicePreMessage(std::move(text)); } diff --git a/Telegram/SourceFiles/history/view/history_view_suggest_options.cpp b/Telegram/SourceFiles/history/view/history_view_suggest_options.cpp index 8ba648a063..1996ba2d42 100644 --- a/Telegram/SourceFiles/history/view/history_view_suggest_options.cpp +++ b/Telegram/SourceFiles/history/view/history_view_suggest_options.cpp @@ -78,9 +78,7 @@ void EditOptionsBox( Ui::AddSkip(container); Ui::AddDividerText( container, - tr::lng_suggest_options_price_about( - lt_channel, - rpl::single(args.channelName))); + tr::lng_suggest_options_price_about()); Ui::AddSkip(container); const auto time = Settings::AddButtonWithLabel( diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_generic.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_generic.cpp index bfac91b07d..7961f5c696 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_generic.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_generic.cpp @@ -236,9 +236,11 @@ MediaGenericTextPart::MediaGenericTextPart( QMargins margins, const style::TextStyle &st, const base::flat_map &links, - const Ui::Text::MarkedContext &context) + const Ui::Text::MarkedContext &context, + style::align align) : _text(st::msgMinWidth) -, _margins(margins) { +, _margins(margins) +, _align(align) { _text.setMarkedText( st, text, @@ -254,12 +256,18 @@ void MediaGenericTextPart::draw( not_null owner, const PaintContext &context, int outerWidth) const { + const auto use = (width() - _margins.left() - _margins.right()); setupPen(p, owner, context); _text.draw(p, { - .position = { (outerWidth - width()) / 2, _margins.top() }, + .position = { + ((_align == style::al_top) + ? ((outerWidth - use) / 2) + : _margins.left()), + _margins.top(), + }, .outerWidth = outerWidth, - .availableWidth = width(), - .align = style::al_top, + .availableWidth = use, + .align = _align, .palette = &(owner->service() ? context.st->serviceTextPalette() : context.messageStyle()->textPalette), @@ -284,11 +292,17 @@ TextState MediaGenericTextPart::textState( QPoint point, StateRequest request, int outerWidth) const { - point -= QPoint{ (outerWidth - width()) / 2, _margins.top() }; + const auto use = (width() - _margins.left() - _margins.right()); + point -= QPoint{ + ((_align == style::al_top) + ? ((outerWidth - use) / 2) + : _margins.left()), + _margins.top(), + }; auto result = TextState(); auto forText = request.forText(); - forText.align = style::al_top; - result.link = _text.getState(point, width(), forText).link; + forText.align = _align; + result.link = _text.getState(point, use, forText).link; return result; } diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_generic.h b/Telegram/SourceFiles/history/view/media/history_view_media_generic.h index 0fd011914f..b5133743b3 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_generic.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_generic.h @@ -141,7 +141,8 @@ public: QMargins margins, const style::TextStyle &st = st::defaultTextStyle, const base::flat_map &links = {}, - const Ui::Text::MarkedContext &context = {}); + const Ui::Text::MarkedContext &context = {}, + style::align align = style::al_top); void draw( Painter &p, @@ -165,6 +166,7 @@ protected: private: Ui::Text::String _text; QMargins _margins; + style::align _align = {}; }; diff --git a/Telegram/SourceFiles/history/view/media/history_view_suggest_decision.cpp b/Telegram/SourceFiles/history/view/media/history_view_suggest_decision.cpp new file mode 100644 index 0000000000..d242eb0a89 --- /dev/null +++ b/Telegram/SourceFiles/history/view/media/history_view_suggest_decision.cpp @@ -0,0 +1,192 @@ +/* +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_suggest_decision.h" + +#include "base/unixtime.h" +#include "data/data_channel.h" +#include "data/data_session.h" +#include "history/view/media/history_view_media_generic.h" +#include "history/view/history_view_element.h" +#include "history/history.h" +#include "history/history_item.h" +#include "history/history_item_components.h" +#include "lang/lang_keys.h" +#include "ui/text/text_utilities.h" +#include "ui/text/format_values.h" +#include "styles/style_chat.h" +#include "styles/style_credits.h" + +namespace HistoryView { +namespace { + +enum EmojiType { + kAgreement, + kCalendar, + kMoney, + kHourglass, + kReload, + kDecline, + kDiscard, + kWarning, +}; + +[[nodiscard]] const char *Raw(EmojiType type) { + switch (type) { + case EmojiType::kAgreement: return "\xf0\x9f\xa4\x9d"; + case EmojiType::kCalendar: return "\xf0\x9f\x93\x86"; + case EmojiType::kMoney: return "\xf0\x9f\x92\xb0"; + case EmojiType::kHourglass: return "\xe2\x8c\x9b\xef\xb8\x8f"; + case EmojiType::kReload: return "\xf0\x9f\x94\x84"; + case EmojiType::kDecline: return "\xe2\x9d\x8c"; + case EmojiType::kDiscard: return "\xf0\x9f\x9a\xab"; + case EmojiType::kWarning: return "\xe2\x9a\xa0\xef\xb8\x8f"; + } + Unexpected("EmojiType in Raw."); +} + +[[nodiscard]] QString Emoji(EmojiType type) { + return QString::fromUtf8(Raw(type)); +} + +} // namespace + +auto GenerateSuggestDecisionMedia( + not_null parent, + not_null decision) + -> Fn, + Fn)>)> { + return [=]( + not_null media, + Fn)> push) { + const auto peer = parent->history()->peer; + const auto broadcast = peer->monoforumBroadcast(); + if (!broadcast) { + return; + } + + const auto sublistPeerId = parent->data()->sublistPeerId(); + const auto sublistPeer = peer->owner().peer(sublistPeerId); + + auto pushText = [&]( + TextWithEntities text, + QMargins margins = {}, + style::align align = style::al_left, + const base::flat_map &links = {}) { + push(std::make_unique( + std::move(text), + margins, + st::defaultTextStyle, + links, + Ui::Text::MarkedContext(), + align)); + }; + + if (decision->balanceTooLow) { + pushText( + TextWithEntities( + ).append(Emoji(kWarning)).append(' ').append( + (sublistPeer->isSelf() + ? tr::lng_suggest_action_your_not_enough + : tr::lng_suggest_action_his_not_enough)( + tr::now, + Ui::Text::RichLangValue)), + st::chatSuggestInfoFullMargin, + style::al_top); + } else if (decision->rejected) { + const auto withComment = !decision->rejectComment.isEmpty(); + pushText( + TextWithEntities( + ).append(Emoji(kDecline)).append(' ').append( + (withComment + ? tr::lng_suggest_action_declined_reason + : tr::lng_suggest_action_declined)( + tr::now, + lt_from, + Ui::Text::Bold(broadcast->name()), + Ui::Text::WithEntities)), + (withComment + ? st::chatSuggestInfoTitleMargin + : st::chatSuggestInfoFullMargin)); + if (withComment) { + pushText( + TextWithEntities().append('"').append( + decision->rejectComment + ).append('"'), + st::chatSuggestInfoLastMargin, + style::al_top); + } + } else { + const auto stars = decision->stars; + pushText( + TextWithEntities( + ).append(Emoji(kAgreement)).append(' ').append( + Ui::Text::Bold(tr::lng_suggest_action_agreement(tr::now)) + ), + st::chatSuggestInfoTitleMargin, + style::al_top); + pushText( + TextWithEntities( + ).append(Emoji(kCalendar)).append(' ').append( + tr::lng_suggest_action_agree_date( + tr::now, + lt_channel, + Ui::Text::Bold(broadcast->name()), + lt_date, + Ui::Text::Bold(Ui::FormatDateTime( + base::unixtime::parse(decision->date))), + Ui::Text::WithEntities)), + (stars + ? st::chatSuggestInfoMiddleMargin + : st::chatSuggestInfoLastMargin)); + if (stars) { + const auto amount = Ui::Text::Bold( + tr::lng_prize_credits_amount(tr::now, lt_count, stars)); + pushText( + TextWithEntities( + ).append(Emoji(kMoney)).append(' ').append( + (sublistPeer->isSelf() + ? tr::lng_suggest_action_your_charged( + tr::now, + lt_amount, + amount, + Ui::Text::WithEntities) + : tr::lng_suggest_action_his_charged( + tr::now, + lt_from, + Ui::Text::Bold(sublistPeer->shortName()), + lt_amount, + amount, + Ui::Text::WithEntities))), + st::chatSuggestInfoMiddleMargin); + + pushText( + TextWithEntities( + ).append(Emoji(kHourglass)).append(' ').append( + tr::lng_suggest_action_agree_receive( + tr::now, + lt_channel, + Ui::Text::Bold(broadcast->name()), + Ui::Text::WithEntities)), + st::chatSuggestInfoMiddleMargin); + + pushText( + TextWithEntities( + ).append(Emoji(kReload)).append(' ').append( + tr::lng_suggest_action_agree_removed( + tr::now, + lt_channel, + Ui::Text::Bold(broadcast->name()), + Ui::Text::WithEntities)), + st::chatSuggestInfoLastMargin); + } + } + }; +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_suggest_decision.h b/Telegram/SourceFiles/history/view/media/history_view_suggest_decision.h new file mode 100644 index 0000000000..6315d50b9f --- /dev/null +++ b/Telegram/SourceFiles/history/view/media/history_view_suggest_decision.h @@ -0,0 +1,25 @@ +/* +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 + +struct HistoryServiceSuggestDecision; + +namespace HistoryView { + +class Element; +class MediaGeneric; +class MediaGenericPart; + +auto GenerateSuggestDecisionMedia( + not_null parent, + not_null decision +) -> Fn, + Fn)>)>; + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 3e3c2bbaf8..53791b79b2 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1052,6 +1052,12 @@ chatSimilarName: TextStyle(defaultTextStyle) { chatSimilarWidthMax: 424px; chatSimilarSkip: 12px; +chatSuggestInfoWidth: 272px; +chatSuggestInfoTitleMargin: margins(16px, 16px, 16px, 6px); +chatSuggestInfoMiddleMargin: margins(16px, 4px, 16px, 4px); +chatSuggestInfoLastMargin: margins(16px, 4px, 16px, 16px); +chatSuggestInfoFullMargin: margins(16px, 16px, 16px, 16px); + premiumRequiredWidth: 186px; premiumRequiredIcon: icon{{ "chat/large_lockedchat", msgServiceFg }}; premiumRequiredCircle: 60px;