diff --git a/Telegram/Resources/icons/menu/cancel_fee.png b/Telegram/Resources/icons/menu/cancel_fee.png new file mode 100644 index 0000000000..467c369cec Binary files /dev/null and b/Telegram/Resources/icons/menu/cancel_fee.png differ diff --git a/Telegram/Resources/icons/menu/cancel_fee@2x.png b/Telegram/Resources/icons/menu/cancel_fee@2x.png new file mode 100644 index 0000000000..75c0a6c941 Binary files /dev/null and b/Telegram/Resources/icons/menu/cancel_fee@2x.png differ diff --git a/Telegram/Resources/icons/menu/cancel_fee@3x.png b/Telegram/Resources/icons/menu/cancel_fee@3x.png new file mode 100644 index 0000000000..73fee9f98a Binary files /dev/null and b/Telegram/Resources/icons/menu/cancel_fee@3x.png differ diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 9c50c0e773..b5c74ff866 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4315,6 +4315,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_context_edit_shortcut" = "Edit Shortcut"; "lng_context_delete_shortcut" = "Delete Quick Reply"; "lng_context_gift_send" = "Send Another Gift"; +"lng_context_charge_fee" = "Charge Fee"; +"lng_context_remove_fee" = "Remove Fee"; +"lng_context_fee_now" = "{name} pays {amount} per message."; +"lng_context_fee_free" = "{name} can send messages for free."; "lng_add_tag_about" = "Tag this message with an emoji for quick search."; "lng_subscribe_tag_about" = "Organize your Saved Messages with tags. {link}"; @@ -5317,6 +5321,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_payment_bar_button" = "Remove Fee"; "lng_payment_refund_title" = "Remove Fee"; "lng_payment_refund_text" = "Are you sure you want to allow {name} to message you for free?"; +"lng_payment_refund_channel" = "Do you want to allow {name} to message the channel for free?"; "lng_payment_refund_also#one" = "Refund already paid {count} Star"; "lng_payment_refund_also#other" = "Refund already paid {count} Stars"; "lng_payment_refund_confirm" = "Confirm"; diff --git a/Telegram/SourceFiles/data/data_saved_sublist.cpp b/Telegram/SourceFiles/data/data_saved_sublist.cpp index 488eab3a1a..64ef0886df 100644 --- a/Telegram/SourceFiles/data/data_saved_sublist.cpp +++ b/Telegram/SourceFiles/data/data_saved_sublist.cpp @@ -731,6 +731,24 @@ void SavedSublist::applyMonoforumDialog( unreadReactions().setCount(data.vunread_reactions_count().v); setUnreadMark(data.is_unread_mark()); applyMaybeLast(topItem); + + if (data.is_nopaid_messages_exception()) { + _flags |= Flag::FeeRemoved; + } else { + _flags &= ~Flag::FeeRemoved; + } +} + +bool SavedSublist::isFeeRemoved() const { + return (_flags & Flag::FeeRemoved); +} + +void SavedSublist::toggleFeeRemoved(bool feeRemoved) { + if (feeRemoved) { + _flags |= Flag::FeeRemoved; + } else { + _flags &= ~Flag::FeeRemoved; + } } TimeId SavedSublist::adjustedChatListTimeId() const { diff --git a/Telegram/SourceFiles/data/data_saved_sublist.h b/Telegram/SourceFiles/data/data_saved_sublist.h index 1361f207fb..cdcc2e28ee 100644 --- a/Telegram/SourceFiles/data/data_saved_sublist.h +++ b/Telegram/SourceFiles/data/data_saved_sublist.h @@ -32,6 +32,8 @@ public: ~SavedSublist(); [[nodiscard]] bool inMonoforum() const; + [[nodiscard]] bool isFeeRemoved() const; + void toggleFeeRemoved(bool feeRemoved); void apply(const SublistReadTillUpdate &update); void apply(const MessageUpdate &update); @@ -125,6 +127,7 @@ private: enum class Flag : uchar { ResolveChatListMessage = (1 << 0), InMonoforum = (1 << 1), + FeeRemoved = (1 << 2), }; friend inline constexpr bool is_flag_type(Flag) { return true; } using Flags = base::flags; diff --git a/Telegram/SourceFiles/history/view/history_view_contact_status.cpp b/Telegram/SourceFiles/history/view/history_view_contact_status.cpp index f5710124bb..99c135f6f3 100644 --- a/Telegram/SourceFiles/history/view/history_view_contact_status.cpp +++ b/Telegram/SourceFiles/history/view/history_view_contact_status.cpp @@ -1194,6 +1194,7 @@ PaysStatus::PaysStatus( not_null user) : _controller(window) , _user(user) +, _paidAlready(std::make_shared>()) , _inner(Ui::CreateChild(parent.get(), user)) , _bar(parent, object_ptr::fromRaw(_inner)) { setupState(); @@ -1220,65 +1221,12 @@ void PaysStatus::setupState() { void PaysStatus::setupHandlers() { _inner->removeClicks( ) | rpl::start_with_next([=] { - const auto user = _user; - const auto exception = [=](bool refund) { - using Flag = MTPaccount_ToggleNoPaidMessagesException::Flag; - const auto api = &user->session().api(); - const auto require = false; - api->request(MTPaccount_ToggleNoPaidMessagesException( - MTP_flags((refund ? Flag::f_refund_charged : Flag()) - | (require ? Flag::f_require_payment : Flag())), - MTPInputPeer(), // parent_peer // #TODO monoforum - user->inputUser - )).done([=] { - user->clearPaysPerMessage(); - }).send(); - }; - _controller->show(Box([=](not_null box) { - const auto refund = std::make_shared>(); - Ui::ConfirmBox(box, { - .text = tr::lng_payment_refund_text( - tr::now, - lt_name, - Ui::Text::Bold(user->shortName()), - Ui::Text::WithEntities), - .confirmed = [=](Fn close) { - exception(*refund && (*refund)->checked()); - close(); - }, - .confirmText = tr::lng_payment_refund_confirm(tr::now), - .title = tr::lng_payment_refund_title(tr::now), - }); - const auto paid = box->lifetime().make_state< - rpl::variable - >(); - *paid = _paidAlready.value(); - paid->value() | rpl::start_with_next([=](int already) { - if (!already) { - delete base::take(*refund); - } else if (!*refund) { - const auto skip = st::defaultCheckbox.margin.top(); - *refund = box->addRow( - object_ptr( - box, - tr::lng_payment_refund_also( - lt_count, - paid->value() | tr::to_count()), - false, - st::defaultCheckbox), - st::boxRowPadding + QMargins(0, skip, 0, skip)); - } - }, box->lifetime()); - - user->session().api().request(MTPaccount_GetPaidMessagesRevenue( - MTP_flags(0), - MTPInputPeer(), // parent_peer // #TODO monoforum - user->inputUser - )).done(crl::guard(_inner, [=]( - const MTPaccount_PaidMessagesRevenue &result) { - _paidAlready = result.data().vstars_amount().v; - })).send(); - })); + Window::PeerMenuConfirmToggleFee( + _controller, + _paidAlready, + _user->session().user(), + _user, + true); }, _bar.lifetime()); } diff --git a/Telegram/SourceFiles/history/view/history_view_contact_status.h b/Telegram/SourceFiles/history/view/history_view_contact_status.h index 0c540a36c4..47aa1232ce 100644 --- a/Telegram/SourceFiles/history/view/history_view_contact_status.h +++ b/Telegram/SourceFiles/history/view/history_view_contact_status.h @@ -208,7 +208,7 @@ private: const not_null _controller; const not_null _user; - rpl::variable _paidAlready; + std::shared_ptr> _paidAlready; State _state; QPointer _inner; SlidingBar _bar; diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp index 2ecbe5ce3a..97edfbdc52 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -1171,6 +1171,11 @@ void TopBarWidget::updateControlsVisibility() { ? (hasPollsMenu || hasTodoListsMenu || hasTopicMenu) : (section == Section::ChatsList) ? (_activeChat.key.peer() && _activeChat.key.peer()->isForum()) + : (section == Section::SavedSublist) + ? (_activeChat.key.peer() + && _activeChat.key.peer()->isChannel() + && _activeChat.key.peer()->owner().commonStarsPerMessage( + _activeChat.key.peer()->asChannel())) : false); const auto hasInfo = !_activeChat.key.folder() && (section == Section::History diff --git a/Telegram/SourceFiles/ui/menu_icons.style b/Telegram/SourceFiles/ui/menu_icons.style index 9c3233f0e2..fcd6d2bbb8 100644 --- a/Telegram/SourceFiles/ui/menu_icons.style +++ b/Telegram/SourceFiles/ui/menu_icons.style @@ -56,6 +56,7 @@ menuIconDiscussion: icon {{ "menu/discussion", menuIconColor }}; menuIconStats: icon {{ "menu/stats", menuIconColor }}; menuIconBoosts: icon {{ "menu/boosts", menuIconColor }}; menuIconEarn: icon {{ "menu/earn", menuIconColor }}; +menuIconCancelFee: icon {{ "menu/cancel_fee", menuIconColor }}; menuIconCreatePoll: icon {{ "menu/create_poll", menuIconColor }}; menuIconCreateTodoList: icon {{ "menu/select", menuIconColor }}; menuIconQrCode: icon {{ "menu/qr_code", menuIconColor }}; diff --git a/Telegram/SourceFiles/window/window.style b/Telegram/SourceFiles/window/window.style index acdda31299..26e77ab1f1 100644 --- a/Telegram/SourceFiles/window/window.style +++ b/Telegram/SourceFiles/window/window.style @@ -316,6 +316,14 @@ windowArchiveToast: Toast(defaultToast) { maxWidth: boxWideWidth; } +windowFeeItem: Menu(defaultMenu) { + itemPadding: margins(17px, 3px, 17px, 4px); + itemRightSkip: 0px; + itemStyle: whenReadStyle; + itemFgOver: windowFg; + itemFgDisabled: windowFg; +} + ivWidthMin: 380px; ivHeightMin: 480px; ivWidthDefault: 600px; diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 114f0ec207..8519ddf620 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/random.h" #include "base/options.h" #include "base/unixtime.h" +#include "base/unique_qptr.h" #include "base/qt/qt_key_modifiers.h" #include "boxes/delete_messages_box.h" #include "boxes/max_invite_box.h" @@ -83,6 +84,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/stories/info_stories_widget.h" #include "data/components/scheduled_messages.h" #include "data/notify/data_notify_settings.h" +#include "data/stickers/data_custom_emoji.h" #include "data/data_changes.h" #include "data/data_session.h" #include "data/data_folder.h" @@ -98,10 +100,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_chat_filters.h" #include "dialogs/dialogs_key.h" #include "core/application.h" +#include "core/ui_integration.h" #include "export/export_manager.h" #include "boxes/peers/edit_peer_info_box.h" #include "boxes/premium_preview_box.h" #include "styles/style_chat.h" +#include "styles/style_credits.h" #include "styles/style_layers.h" #include "styles/style_boxes.h" #include "styles/style_window.h" // st::windowMinWidth @@ -277,6 +281,7 @@ private: void fillArchiveActions(); void fillSavedSublistActions(); void fillContextMenuActions(); + void fillMonoforumPeerActions(); void addHidePromotion(); void addTogglePin(); @@ -321,6 +326,7 @@ private: void addVideoChat(); void addViewStatistics(); void addBoostChat(); + void addToggleFee(); [[nodiscard]] bool skipCreateActions() const; @@ -1360,6 +1366,7 @@ void Filler::fill() { case Section::Scheduled: fillScheduledActions(); break; case Section::ContextMenu: case Section::SubsectionTabsMenu: fillContextMenuActions(); break; + case Section::SavedSublist: fillMonoforumPeerActions(); break; default: Unexpected("_request.section in Filler::fill."); } } @@ -1647,6 +1654,68 @@ void Filler::fillSavedSublistActions() { addTogglePin(); } +void Filler::fillMonoforumPeerActions() { + Expects(_sublist != nullptr); + + addToggleFee(); +} + +void Filler::addToggleFee() { + const auto feeRemoved = _sublist->isFeeRemoved(); + const auto text = feeRemoved + ? tr::lng_context_charge_fee(tr::now) + : tr::lng_context_remove_fee(tr::now); + const auto navigation = _controller; + const auto parent = _sublist->parentChat(); + const auto user = _sublist->sublistPeer()->asUser(); + if (!parent || !user) { + return; + } + const auto paidAmount = std::make_shared>(); + _addAction(text, [=] { + const auto removeFee = !feeRemoved; + PeerMenuConfirmToggleFee( + navigation, + paidAmount, + parent, + user, + removeFee); + }, feeRemoved ? &st::menuIconEarn : &st::menuIconCancelFee); + _addAction({ .isSeparator = true }); + _addAction({ .make = [=](not_null actionParent) { + const auto text = feeRemoved + ? tr::lng_context_fee_free( + tr::now, + lt_name, + TextWithEntities{ user->shortName() }, + Ui::Text::WithEntities) + : tr::lng_context_fee_now( + tr::now, + lt_name, + TextWithEntities{ user->shortName() }, + lt_amount, + user->owner().customEmojiManager().ministarEmoji( + { 0, st::giftBoxByStarsStarTop, 0, 0 } + ).append(Lang::FormatCountDecimal( + user->owner().commonStarsPerMessage(parent) + )), + Ui::Text::WithEntities); + const auto action = new QAction(actionParent); + action->setDisabled(true); + auto result = base::make_unique_q( + actionParent, + st::windowFeeItem, + action, + nullptr, + nullptr); + result->setMarkedText( + text, + QString(), + Core::TextContext({ .session = &user->session() })); + return result; + } }); +} + } // namespace void PeerMenuExportChat( @@ -3726,4 +3795,81 @@ bool CanArchive(History *history, PeerData *peer) { return true; } +void PeerMenuConfirmToggleFee( + not_null navigation, + std::shared_ptr> paidAmount, + not_null peer, + not_null user, + bool removeFee) { + const auto parent = peer->isChannel() ? peer->asChannel() : nullptr; + const auto exception = [=](bool refund) { + using Flag = MTPaccount_ToggleNoPaidMessagesException::Flag; + const auto api = &user->session().api(); + api->request(MTPaccount_ToggleNoPaidMessagesException( + MTP_flags((refund ? Flag::f_refund_charged : Flag()) + | (removeFee ? Flag() : Flag::f_require_payment) + | (parent ? Flag::f_parent_peer : Flag())), + parent->input, + user->inputUser + )).done([=] { + if (!parent) { + user->clearPaysPerMessage(); + } else if (const auto monoforum = peer->monoforum()) { + if (const auto sublist = monoforum->sublistLoaded(user)) { + sublist->toggleFeeRemoved(removeFee); + } + } + }).send(); + }; + if (!removeFee) { + exception(false); + return; + } + navigation->uiShow()->show(Box([=](not_null box) { + const auto refund = std::make_shared>(); + Ui::ConfirmBox(box, { + .text = tr::lng_payment_refund_text( + tr::now, + lt_name, + Ui::Text::Bold(user->shortName()), + Ui::Text::WithEntities), + .confirmed = [=](Fn close) { + exception(*refund && (*refund)->checked()); + close(); + }, + .confirmText = tr::lng_payment_refund_confirm(tr::now), + .title = tr::lng_payment_refund_title(tr::now), + }); + const auto paid = box->lifetime().make_state< + rpl::variable + >(); + *paid = paidAmount->value(); + paid->value() | rpl::start_with_next([=](int already) { + if (!already) { + delete base::take(*refund); + } else if (!*refund) { + const auto skip = st::defaultCheckbox.margin.top(); + *refund = box->addRow( + object_ptr( + box, + tr::lng_payment_refund_also( + lt_count, + paid->value() | tr::to_count()), + false, + st::defaultCheckbox), + st::boxRowPadding + QMargins(0, skip, 0, skip)); + } + }, box->lifetime()); + + using Flag = MTPaccount_GetPaidMessagesRevenue::Flag; + user->session().api().request(MTPaccount_GetPaidMessagesRevenue( + MTP_flags(parent ? Flag::f_parent_peer : Flag()), + parent ? parent->input : MTPInputPeer(), + user->inputUser + )).done([=](const MTPaccount_PaidMessagesRevenue &result) { + *paidAmount = result.data().vstars_amount().v; + }).send(); + })); +} + } // namespace Window diff --git a/Telegram/SourceFiles/window/window_peer_menu.h b/Telegram/SourceFiles/window/window_peer_menu.h index f01801423f..56c4a6baec 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.h +++ b/Telegram/SourceFiles/window/window_peer_menu.h @@ -253,4 +253,11 @@ void AddSeparatorAndShiftUp(const PeerMenuCallback &addAction); [[nodiscard]] bool IsArchived(not_null history); [[nodiscard]] bool CanArchive(History *history, PeerData *peer); +void PeerMenuConfirmToggleFee( + not_null navigation, + std::shared_ptr> paidAmount, + not_null peer, + not_null user, + bool removeFee); + } // namespace Window diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 0b7933ad11..d905dad9c9 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 0b7933ad1189076233b69fac0a5a475d2a5deb8b +Subproject commit d905dad9c9b0e7e2b6755bb13957f37862888464