Add Stars/TON checks to suggestions.

This commit is contained in:
John Preston 2025-06-27 13:23:27 +04:00
parent 141fb875f9
commit c70f75b21a
13 changed files with 254 additions and 67 deletions

View file

@ -2269,6 +2269,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_todo_tasks_fallback#other" = "{count} tasks";
"lng_action_todo_tasks_and_one" = "{tasks}, {task}";
"lng_action_todo_tasks_and_last" = "{tasks} and {task}";
"lng_action_suggest_success" = "{from} has received {amount} for publishing post.";
"lng_action_suggest_refund_user" = "User refunded the Stars so that post was deleted.";
"lng_action_suggest_refund_admin" = "Admin deleted the post early so that the price was refunded to the user.";
"lng_you_paid_stars#one" = "You paid {count} Star.";
"lng_you_paid_stars#other" = "You paid {count} Stars.";
@ -4434,6 +4437,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_suggest_options_ton_price_about" = "Choose how many TON to pay to publish this message.";
"lng_suggest_options_date" = "Time";
"lng_suggest_options_date_any" = "Anytime";
"lng_suggest_options_date_publish" = "Publish";
"lng_suggest_options_date_now" = "Publish Now";
"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";
@ -4473,7 +4478,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"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_text_to" = "Do you want to decline publishing this post to {channel}?";
"lng_suggest_decline_reason" = "Add a reason (optional)";
"lng_suggest_accept_title" = "Accept";
"lng_suggest_accept_text" = "Do you want to publish this post from {from}?";
"lng_suggest_accept_text_to" = "Do you want to publish this post to {channel}?";
"lng_suggest_accept_receive" = "{channel} will receive {amount} for publishing {date}.";
"lng_suggest_accept_receive_now" = "{channel} will receive {amount} for publishing right now.";
"lng_suggest_accept_pay" = "You will pay {amount} for publishing {date}.";
"lng_suggest_accept_pay_now" = "You will pay {amount} for publishing right now.";
"lng_suggest_accept_send" = "Send";
"lng_suggest_ton_amount#one" = "{count} TON";
"lng_suggest_ton_amount#other" = "{count} TON";
"lng_suggest_warn_title_stars" = "Stars will be lost";
"lng_suggest_warn_title_ton" = "TON will be lost";
"lng_suggest_warn_text_stars" = "You won't receive **Stars** for the post if you delete it now. The post must remain visible for at least **24 hours** after it was published.";

View file

@ -11,7 +11,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unixtime.h"
#include "chat_helpers/message_field.h"
#include "core/click_handler_types.h"
#include "data/components/credits.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/data_session.h"
#include "data/data_saved_sublist.h"
#include "history/view/controls/history_view_suggest_options.h"
@ -22,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "mainwindow.h"
#include "settings/settings_credits_graphics.h"
#include "ui/boxes/choose_date_time.h"
#include "ui/layers/generic_box.h"
#include "ui/boxes/confirm_box.h"
@ -75,6 +78,138 @@ void SendApproval(
}).send();
}
void ConfirmApproval(
std::shared_ptr<Main::SessionShow> show,
not_null<HistoryItem*> item,
TimeId scheduleDate = 0,
Fn<void()> accepted = nullptr) {
using Flag = MTPmessages_ToggleSuggestedPostApproval::Flag;
const auto suggestion = item->Get<HistoryMessageSuggestedPost>();
if (!suggestion
|| suggestion->accepted
|| suggestion->rejected
|| suggestion->requestId) {
return;
}
const auto id = item->fullId();
const auto price = suggestion->price;
const auto admin = item->history()->amMonoforumAdmin();
if (!admin && !price.empty()) {
const auto credits = &item->history()->session().credits();
if (price.ton()) {
if (!credits->tonLoaded()) {
credits->tonLoad();
return;
} else if (price > credits->tonBalance()) {
const auto peer = item->history()->peer;
show->show(
Box(HistoryView::InsufficientTonBox, peer, price));
return;
}
} else {
if (!credits->loaded()) {
credits->load();
return;
} else if (price > credits->balance()) {
using namespace Settings;
const auto peer = item->history()->peer;
const auto broadcast = peer->monoforumBroadcast();
const auto broadcastId = (broadcast ? broadcast : peer)->id;
const auto done = [=](SmallBalanceResult result) {
if (result == SmallBalanceResult::Success
|| result == SmallBalanceResult::Already) {
const auto item = peer->owner().message(id);
if (item) {
ConfirmApproval(
show,
item,
scheduleDate,
accepted);
}
}
};
MaybeRequestBalanceIncrease(
show,
int(base::SafeRound(price.value())),
SmallBalanceForSuggest{ broadcastId },
done);
return;
}
}
}
const auto peer = item->history()->peer;
const auto broadcast = peer->monoforumBroadcast();
const auto channelName = (broadcast ? broadcast : peer)->name();
const auto amount = Lang::FormatCreditsAmountWithCurrency(price);
const auto date = langDateTime(base::unixtime::parse(scheduleDate));
show->show(Box([=](not_null<Ui::GenericBox*> box) {
const auto callback = std::make_shared<Fn<void()>>();
auto text = admin
? tr::lng_suggest_accept_text(
tr::now,
lt_from,
Ui::Text::Bold(item->from()->shortName()),
Ui::Text::WithEntities)
: tr::lng_suggest_accept_text_to(
tr::now,
lt_channel,
Ui::Text::Bold(channelName),
Ui::Text::WithEntities);
if (price) {
text.append("\n\n").append(admin
? (scheduleDate
? tr::lng_suggest_accept_receive(
tr::now,
lt_channel,
Ui::Text::Bold(channelName),
lt_amount,
Ui::Text::Bold(amount),
lt_date,
Ui::Text::Bold(date),
Ui::Text::WithEntities)
: tr::lng_suggest_accept_receive_now(
tr::now,
lt_channel,
Ui::Text::Bold(channelName),
lt_amount,
Ui::Text::Bold(amount),
Ui::Text::WithEntities))
: (scheduleDate
? tr::lng_suggest_accept_pay(
tr::now,
lt_amount,
Ui::Text::Bold(amount),
lt_date,
Ui::Text::Bold(date),
Ui::Text::WithEntities)
: tr::lng_suggest_accept_pay_now(
tr::now,
lt_amount,
Ui::Text::Bold(amount),
Ui::Text::WithEntities)));
}
Ui::ConfirmBox(box, {
.text = text,
.confirmed = [=](Fn<void()> close) { (*callback)(); close(); },
.confirmText = tr::lng_suggest_accept_send(),
.title = tr::lng_suggest_accept_title(),
});
*callback = [=, weak = Ui::MakeWeak(box)] {
if (const auto onstack = accepted) {
onstack();
}
const auto item = show->session().data().message(id);
if (!item) {
return;
}
SendApproval(show, item, scheduleDate);
if (const auto strong = weak.data()) {
strong->closeBox();
}
};
}));
}
void SendDecline(
std::shared_ptr<Main::SessionShow> show,
not_null<HistoryItem*> item,
@ -120,19 +255,23 @@ void RequestApprovalDate(
not_null<HistoryItem*> item) {
const auto id = item->fullId();
const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
const auto done = [=](TimeId result) {
if (const auto item = show->session().data().message(id)) {
SendApproval(show, item, result);
}
const auto close = [=] {
if (const auto strong = weak->data()) {
strong->closeBox();
}
};
const auto done = [=](TimeId result) {
if (const auto item = show->session().data().message(id)) {
ConfirmApproval(show, item, result, close);
} else {
close();
}
};
using namespace HistoryView;
auto dateBox = Box(ChooseSuggestTimeBox, SuggestTimeBoxArgs{
.session = &show->session(),
.done = done,
.mode = SuggestMode::New,
.mode = SuggestMode::Publish,
});
*weak = dateBox.data();
show->show(std::move(dateBox));
@ -142,13 +281,22 @@ void RequestDeclineComment(
std::shared_ptr<Main::SessionShow> show,
not_null<HistoryItem*> item) {
const auto id = item->fullId();
const auto admin = item->history()->amMonoforumAdmin();
const auto peer = item->history()->peer;
const auto broadcast = peer->monoforumBroadcast();
const auto channelName = (broadcast ? broadcast : peer)->name();
show->show(Box([=](not_null<Ui::GenericBox*> box) {
const auto callback = std::make_shared<Fn<void()>>();
Ui::ConfirmBox(box, {
.text = tr::lng_suggest_decline_text(
lt_from,
rpl::single(Ui::Text::Bold(item->from()->shortName())),
Ui::Text::WithEntities),
.text = (admin
? tr::lng_suggest_decline_text(
lt_from,
rpl::single(Ui::Text::Bold(item->from()->shortName())),
Ui::Text::WithEntities)
: tr::lng_suggest_decline_text_to(
lt_channel,
rpl::single(Ui::Text::Bold(channelName)),
Ui::Text::WithEntities)),
.confirmed = [=](Fn<void()> close) { (*callback)(); close(); },
.confirmText = tr::lng_suggest_action_decline(),
.confirmStyle = &st::attentionBoxButton,
@ -264,12 +412,11 @@ void SuggestApprovalDate(
close);
};
using namespace HistoryView;
const auto admin = item->history()->amMonoforumAdmin();
auto dateBox = Box(ChooseSuggestTimeBox, SuggestTimeBoxArgs{
.session = &show->session(),
.done = done,
.value = suggestion->date,
.mode = (admin ? SuggestMode::ChangeAdmin : SuggestMode::ChangeUser),
.mode = SuggestMode::Change,
});
*weak = dateBox.data();
show->show(std::move(dateBox));
@ -318,7 +465,6 @@ void SuggestApprovalPrice(
if (!suggestion) {
return;
}
const auto admin = item->history()->amMonoforumAdmin();
using namespace HistoryView;
SuggestOfferForMessage(show, item, {
.exists = uint32(1),
@ -326,7 +472,7 @@ void SuggestApprovalPrice(
.priceNano = uint32(suggestion->price.nano()),
.ton = uint32(suggestion->price.ton() ? 1 : 0),
.date = suggestion->date,
}, admin ? SuggestMode::ChangeAdmin : SuggestMode::ChangeUser);
}, SuggestMode::Change);
}
} // namespace
@ -352,7 +498,7 @@ std::shared_ptr<ClickHandler> AcceptClickHandler(
} else if (!suggestion->date) {
RequestApprovalDate(show, item);
} else {
SendApproval(show, item);
ConfirmApproval(show, item);
}
});
}

View file

@ -6093,11 +6093,25 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
};
auto prepareSuggestedPostSuccess = [&](const MTPDmessageActionSuggestedPostSuccess &data) {
return PreparedServiceText{ { u"hello"_q } }; AssertIsDebug();
const auto price = CreditsAmountFromTL(&data.vprice());
const auto amount = Lang::FormatCreditsAmountWithCurrency(price);
auto result = PreparedServiceText();
result.links.push_back(_from->createOpenLink());
result.text = tr::lng_action_suggest_success(
tr::now,
lt_from,
Ui::Text::Link(_from->shortName(), 1),
lt_amount,
TextWithEntities{ amount },
Ui::Text::WithEntities);
return result;
};
auto prepareSuggestedPostRefund = [&](const MTPDmessageActionSuggestedPostRefund &data) {
return PreparedServiceText{ { u"hello"_q } }; AssertIsDebug();
return PreparedServiceText{ { data.is_payer_initiated()
? tr::lng_action_suggest_refund_user(tr::now)
: tr::lng_action_suggest_refund_admin(tr::now)
} };
};
auto prepareConferenceCall = [&](const MTPDmessageActionConferenceCall &) -> PreparedServiceText {

View file

@ -224,7 +224,7 @@ std::optional<SendPaymentDetails> ComputePaymentDetails(
bool SuggestPaymentDataReady(
not_null<PeerData*> peer,
SuggestPostOptions suggest) {
if (!suggest.exists || !suggest.price()) {
if (!suggest.exists || !suggest.price() || peer->amMonoforumAdmin()) {
return true;
} else if (suggest.ton && !peer->session().credits().tonLoaded()) {
peer->session().credits().tonLoad();
@ -441,12 +441,13 @@ bool SendPaymentHelper::check(
PaidConfirmStyles styles) {
clear();
const auto admin = peer->amMonoforumAdmin();
const auto suggest = options.suggest;
const auto starsApproved = options.starsApproved;
const auto suggestPriceStars = suggest.ton
const auto checkSuggestPriceStars = (admin || suggest.ton)
? 0
: int(base::SafeRound(suggest.price().value()));
const auto suggestPriceTon = suggest.ton
const auto checkSuggestPriceTon = (!admin && suggest.ton)
? suggest.price()
: CreditsAmount();
const auto details = ComputePaymentDetails(peer, messagesCount);
@ -491,32 +492,33 @@ bool SendPaymentHelper::check(
} else if (const auto stars = details->stars; stars > starsApproved) {
ShowSendPaidConfirm(show, peer, *details, [=] {
resend(stars);
}, styles, suggestPriceStars);
}, styles, checkSuggestPriceStars);
return false;
} else if (suggestPriceStars
&& (CreditsAmount(details->stars + suggestPriceStars)
} else if (checkSuggestPriceStars
&& (CreditsAmount(details->stars + checkSuggestPriceStars)
> peer->session().credits().balance())) {
const auto peerId = peer->id;
using namespace Settings;
const auto broadcast = peer->monoforumBroadcast();
const auto broadcastId = (broadcast ? broadcast : peer)->id;
const auto forMessages = details->stars;
const auto required = forMessages + suggestPriceStars;
const auto done = [=](Settings::SmallBalanceResult result) {
if (result == Settings::SmallBalanceResult::Success
|| result == Settings::SmallBalanceResult::Already) {
const auto required = forMessages + checkSuggestPriceStars;
const auto done = [=](SmallBalanceResult result) {
if (result == SmallBalanceResult::Success
|| result == SmallBalanceResult::Already) {
resend(forMessages);
}
};
using namespace Settings;
MaybeRequestBalanceIncrease(
show,
required,
SmallBalanceForSuggest{ peerId },
SmallBalanceForSuggest{ broadcastId },
done);
return false;
}
if (suggestPriceTon
&& suggestPriceTon > peer->session().credits().tonBalance()) {
show->show(
Box(HistoryView::InsufficientTonBox, peer, suggestPriceTon));
if (checkSuggestPriceTon
&& checkSuggestPriceTon > peer->session().credits().tonBalance()) {
using namespace HistoryView;
show->show(Box(InsufficientTonBox, peer, checkSuggestPriceTon));
return false;
}
return true;

View file

@ -2275,11 +2275,7 @@ bool HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) {
}
if (editDraft && editDraft->suggest) {
using namespace HistoryView;
applySuggestOptions(
editDraft->suggest,
(_history->amMonoforumAdmin()
? SuggestMode::ChangeAdmin
: SuggestMode::ChangeUser));
applySuggestOptions(editDraft->suggest, SuggestMode::Change);
} else {
cancelSuggestPost();
}

View file

@ -793,7 +793,7 @@ void FieldHeader::editMessage(
_inPhotoEditOver.stop();
}
if (id && suggest) {
applySuggestOptions(suggest, SuggestMode::ChangeAdmin);
applySuggestOptions(suggest, SuggestMode::Change);
} else {
cancelSuggestPost();
}

View file

@ -58,10 +58,13 @@ void ChooseSuggestTimeBox(
: (now + 86400);
const auto done = args.done;
Ui::ChooseDateTimeBox(box, {
.title = ((args.mode == SuggestMode::New)
.title = ((args.mode == SuggestMode::New
|| args.mode == SuggestMode::Publish)
? tr::lng_suggest_options_date()
: tr::lng_suggest_menu_edit_time()),
.submit = ((args.mode == SuggestMode::New)
.submit = ((args.mode == SuggestMode::Publish)
? tr::lng_suggest_options_date_publish()
: (args.mode == SuggestMode::New)
? tr::lng_settings_save()
: tr::lng_suggest_options_update()),
.done = done,
@ -70,7 +73,9 @@ void ChooseSuggestTimeBox(
.max = [=] { return now + max; },
});
box->addLeftButton(tr::lng_suggest_options_date_any(), [=] {
box->addLeftButton((args.mode == SuggestMode::Publish)
? tr::lng_suggest_options_date_now()
: tr::lng_suggest_options_date_any(), [=] {
done(TimeId());
});
}
@ -96,9 +101,14 @@ void ChooseSuggestPriceBox(
state->ton = (args.value.ton != 0);
const auto peer = args.peer;
const auto admin = peer->amMonoforumAdmin();
const auto broadcast = peer->monoforumBroadcast();
const auto usePeer = broadcast ? broadcast : peer;
const auto session = &peer->session();
session->credits().load();
session->credits().tonLoad();
if (!admin) {
session->credits().load();
session->credits().tonLoad();
}
const auto limit = session->appConfig().suggestedPostStarsMax();
box->setTitle((args.mode == SuggestMode::New)
@ -109,7 +119,7 @@ void ChooseSuggestPriceBox(
state->buttons.push_back({
.text = Ui::Text::String(
st::semiboldTextStyle,
(args.mode == SuggestMode::ChangeAdmin
(admin
? tr::lng_suggest_options_stars_request(tr::now)
: tr::lng_suggest_options_stars_offer(tr::now))),
.active = !state->ton.current(),
@ -117,7 +127,7 @@ void ChooseSuggestPriceBox(
state->buttons.push_back({
.text = Ui::Text::String(
st::semiboldTextStyle,
(args.mode == SuggestMode::ChangeAdmin
(admin
? tr::lng_suggest_options_ton_request(tr::now)
: tr::lng_suggest_options_ton_offer(tr::now))),
.active = state->ton.current(),
@ -381,16 +391,15 @@ void ChooseSuggestPriceBox(
nanos % Ui::kNanosInOne,
ton ? CreditsType::Ton : CreditsType::Stars);
const auto credits = &session->credits();
if (ton) {
if (!admin && ton) {
if (!credits->tonLoaded()) {
state->savePending = true;
return;
} else if (credits->tonBalance() < value) {
box->uiShow()->show(
Box(InsufficientTonBox, peer, value));
box->uiShow()->show(Box(InsufficientTonBox, usePeer, value));
return;
}
} else {
} else if (!admin) {
if (!credits->loaded()) {
state->savePending = true;
return;
@ -408,7 +417,7 @@ void ChooseSuggestPriceBox(
MaybeRequestBalanceIncrease(
Main::MakeSessionShow(box->uiShow(), session),
required,
SmallBalanceForSuggest{ peer->id },
SmallBalanceForSuggest{ usePeer->id },
done);
return;
}

View file

@ -29,8 +29,8 @@ namespace HistoryView {
enum class SuggestMode {
New,
ChangeUser,
ChangeAdmin,
Change,
Publish,
};
struct SuggestTimeBoxArgs {

View file

@ -742,6 +742,8 @@ TextState Service::textState(QPoint point, StateRequest request) const {
result.link = done->lnk;
} else if (const auto append = item->Get<HistoryServiceTodoAppendTasks>()) {
result.link = append->lnk;
} else if (const auto finish = item->Get<HistoryServiceSuggestFinish>()) {
result.link = finish->lnk;
} else if (media && data()->showSimilarChannels()) {
result = media->textState(mediaPoint, request);
}

View file

@ -70,8 +70,8 @@ QSize PremiumGift::size() {
TextWithEntities PremiumGift::title() {
using namespace Ui::Text;
if (tonGift()) {
AssertIsDebug();
return { QString::number(_data.count / 1'000'000'000LL) + u" TON"_q };
return { Lang::FormatCreditsAmountWithCurrency(
CreditsAmount(0, _data.count, CreditsType::Ton)) };
} else if (starGift()) {
const auto peer = _parent->history()->peer;
return peer->isSelf()

View file

@ -214,12 +214,8 @@ auto GenerateSuggestDecisionMedia(
? st::chatSuggestInfoMiddleMargin
: st::chatSuggestInfoLastMargin));
if (price) {
const auto amount = Ui::Text::Bold(price.ton()
? (Lang::FormatCreditsAmountDecimal(price) + u" TON"_q)
: tr::lng_prize_credits_amount(
tr::now,
lt_count_decimal,
price.value()));
const auto amount = Ui::Text::Bold(
Lang::FormatCreditsAmountWithCurrency(price));
pushText(
TextWithEntities(
).append(Emoji(kMoney)).append(' ').append(
@ -334,12 +330,7 @@ auto GenerateSuggestRequestMedia(
: tr::lng_suggest_action_price_label)(tr::now),
Ui::Text::Bold(!suggest->price
? tr::lng_suggest_action_price_free(tr::now)
: suggest->price.ton() AssertIsDebug()
? (Lang::FormatCreditsAmountDecimal(suggest->price) + u" TON"_q)
: tr::lng_prize_credits_amount(
tr::now,
lt_count,
suggest->price.value())),
: Lang::FormatCreditsAmountWithCurrency(suggest->price)),
});
entries.push_back({
((changes && changes->date)

View file

@ -968,6 +968,15 @@ QString FormatCreditsAmountRounded(CreditsAmount amount) {
return FormatExactCountDecimal(base::SafeRound(value * 100.) / 100.);
}
QString FormatCreditsAmountWithCurrency(CreditsAmount amount) {
return (amount.ton()
? tr::lng_suggest_ton_amount
: tr::lng_prize_credits_amount)(
tr::now,
lt_count_decimal,
amount.value());
}
PluralResult Plural(
ushort keyBase,
float64 value,

View file

@ -34,6 +34,8 @@ struct ShortenedCount {
[[nodiscard]] QString FormatCreditsAmountDecimal(CreditsAmount amount);
[[nodiscard]] QString FormatCreditsAmountRounded(CreditsAmount amount);
[[nodiscard]] QString FormatCreditsAmountWithCurrency(CreditsAmount amount);
struct PluralResult {
int keyShift = 0;
QString replacement;