payments_reaction_process.cpp 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "payments/payments_reaction_process.h"
  8. #include "api/api_credits.h"
  9. #include "api/api_global_privacy.h"
  10. #include "apiwrap.h"
  11. #include "boxes/send_credits_box.h" // CreditsEmojiSmall.
  12. #include "core/ui_integration.h" // TextContext.
  13. #include "data/components/credits.h"
  14. #include "data/data_channel.h"
  15. #include "data/data_message_reactions.h"
  16. #include "data/data_session.h"
  17. #include "data/data_user.h"
  18. #include "history/view/history_view_element.h"
  19. #include "history/history.h"
  20. #include "history/history_item.h"
  21. #include "lang/lang_keys.h"
  22. #include "main/session/session_show.h"
  23. #include "main/session/send_as_peers.h"
  24. #include "main/main_app_config.h"
  25. #include "main/main_session.h"
  26. #include "payments/ui/payments_reaction_box.h"
  27. #include "settings/settings_credits_graphics.h"
  28. #include "ui/effects/reaction_fly_animation.h"
  29. #include "ui/layers/box_content.h"
  30. #include "ui/layers/generic_box.h"
  31. #include "ui/layers/show.h"
  32. #include "ui/text/text_utilities.h"
  33. #include "ui/dynamic_thumbnails.h"
  34. #include "window/window_session_controller.h"
  35. namespace Payments {
  36. namespace {
  37. constexpr auto kMaxPerReactionFallback = 2'500;
  38. constexpr auto kDefaultPerReaction = 50;
  39. void TryAddingPaidReaction(
  40. not_null<Main::Session*> session,
  41. FullMsgId itemId,
  42. base::weak_ptr<HistoryView::Element> weakView,
  43. int count,
  44. std::optional<PeerId> shownPeer,
  45. std::shared_ptr<Ui::Show> show,
  46. Fn<void(bool)> finished) {
  47. const auto checkItem = [=] {
  48. const auto item = session->data().message(itemId);
  49. if (!item) {
  50. if (const auto onstack = finished) {
  51. onstack(false);
  52. }
  53. }
  54. return item;
  55. };
  56. const auto item = checkItem();
  57. if (!item) {
  58. return;
  59. }
  60. const auto done = [=](Settings::SmallBalanceResult result) {
  61. if (result == Settings::SmallBalanceResult::Success
  62. || result == Settings::SmallBalanceResult::Already) {
  63. if (const auto item = checkItem()) {
  64. item->addPaidReaction(count, shownPeer);
  65. if (const auto view = count ? weakView.get() : nullptr) {
  66. const auto history = view->history();
  67. history->owner().notifyViewPaidReactionSent(view);
  68. view->animateReaction({
  69. .id = Data::ReactionId::Paid(),
  70. });
  71. }
  72. if (const auto onstack = finished) {
  73. onstack(true);
  74. }
  75. }
  76. } else if (const auto onstack = finished) {
  77. onstack(false);
  78. }
  79. };
  80. const auto channelId = peerToChannel(itemId.peer);
  81. Settings::MaybeRequestBalanceIncrease(
  82. Main::MakeSessionShow(show, session),
  83. count,
  84. Settings::SmallBalanceReaction{ .channelId = channelId },
  85. done);
  86. }
  87. } // namespace
  88. bool LookupMyPaidAnonymous(not_null<HistoryItem*> item) {
  89. for (const auto &entry : item->topPaidReactionsWithLocal()) {
  90. if (entry.my) {
  91. return !entry.peer;
  92. }
  93. }
  94. return false;
  95. }
  96. void TryAddingPaidReaction(
  97. not_null<HistoryItem*> item,
  98. HistoryView::Element *view,
  99. int count,
  100. std::optional<PeerId> shownPeer,
  101. std::shared_ptr<Ui::Show> show,
  102. Fn<void(bool)> finished) {
  103. TryAddingPaidReaction(
  104. &item->history()->session(),
  105. item->fullId(),
  106. view,
  107. count,
  108. shownPeer,
  109. std::move(show),
  110. std::move(finished));
  111. }
  112. void ShowPaidReactionDetails(
  113. not_null<Window::SessionController*> controller,
  114. not_null<HistoryItem*> item,
  115. HistoryView::Element *view,
  116. HistoryReactionSource source) {
  117. Expects(item->history()->peer->isBroadcast()
  118. || item->isDiscussionPost());
  119. const auto show = controller->uiShow();
  120. const auto itemId = item->fullId();
  121. const auto session = &item->history()->session();
  122. const auto appConfig = &session->appConfig();
  123. const auto max = std::max(
  124. appConfig->get<int>(
  125. u"stars_paid_reaction_amount_max"_q,
  126. kMaxPerReactionFallback),
  127. 2);
  128. const auto chosen = std::clamp(kDefaultPerReaction, 1, max);
  129. struct State {
  130. QPointer<Ui::BoxContent> selectBox;
  131. bool ignoreShownPeerSwitch = false;
  132. bool sending = false;
  133. };
  134. const auto state = std::make_shared<State>();
  135. session->credits().load(true);
  136. const auto weakView = base::make_weak(view);
  137. const auto send = [=](int count, PeerId shownPeer, auto resend) -> void {
  138. Expects(count >= 0);
  139. const auto finish = [=](bool success) {
  140. state->sending = false;
  141. if (success && count > 0) {
  142. state->ignoreShownPeerSwitch = true;
  143. if (const auto strong = state->selectBox.data()) {
  144. strong->closeBox();
  145. }
  146. }
  147. };
  148. if (state->sending || (!count && state->ignoreShownPeerSwitch)) {
  149. return;
  150. } else if (const auto item = session->data().message(itemId)) {
  151. state->sending = true;
  152. TryAddingPaidReaction(
  153. item,
  154. weakView.get(),
  155. count,
  156. shownPeer,
  157. show,
  158. finish);
  159. }
  160. };
  161. auto submitText = [=](rpl::producer<int> amount) {
  162. auto nice = std::move(amount) | rpl::map([=](int count) {
  163. return Ui::CreditsEmojiSmall(session).append(
  164. Lang::FormatCountDecimal(count));
  165. });
  166. return tr::lng_paid_react_send(
  167. lt_price,
  168. std::move(nice),
  169. Ui::Text::RichLangValue
  170. ) | rpl::map([=](TextWithEntities &&text) {
  171. return Ui::TextWithContext{
  172. .text = std::move(text),
  173. .context = Core::TextContext({ .session = session }),
  174. };
  175. });
  176. };
  177. auto top = std::vector<Ui::PaidReactionTop>();
  178. const auto add = [&](const Data::MessageReactionsTopPaid &entry) {
  179. const auto peer = entry.peer;
  180. const auto name = peer
  181. ? peer->shortName()
  182. : tr::lng_paid_react_anonymous(tr::now);
  183. const auto open = [=] {
  184. controller->showPeerInfo(peer);
  185. };
  186. top.push_back({
  187. .name = name,
  188. .photo = (peer
  189. ? Ui::MakeUserpicThumbnail(peer)
  190. : Ui::MakeHiddenAuthorThumbnail()),
  191. .barePeerId = peer ? uint64(peer->id.value) : 0,
  192. .count = int(entry.count),
  193. .click = peer ? open : Fn<void()>(),
  194. .my = (entry.my == 1),
  195. });
  196. };
  197. const auto linked = item->discussionPostOriginalSender();
  198. const auto channel = (linked ? linked : item->history()->peer.get());
  199. const auto channels = session->sendAsPeers().paidReactionList(channel);
  200. const auto topPaid = item->topPaidReactionsWithLocal();
  201. top.reserve(topPaid.size() + 2 + channels.size());
  202. for (const auto &entry : topPaid) {
  203. add(entry);
  204. }
  205. auto myAdded = base::flat_set<uint64>();
  206. const auto i = ranges::find(top, true, &Ui::PaidReactionTop::my);
  207. if (i != end(top)) {
  208. myAdded.emplace(i->barePeerId);
  209. }
  210. const auto myCount = uint32((i != end(top)) ? i->count : 0);
  211. const auto myAdd = [&](PeerData *peer) {
  212. const auto barePeerId = peer ? uint64(peer->id.value) : 0;
  213. if (!myAdded.emplace(barePeerId).second) {
  214. return;
  215. }
  216. add(Data::MessageReactionsTopPaid{
  217. .peer = peer,
  218. .count = myCount,
  219. .my = true,
  220. });
  221. };
  222. const auto globalPrivacy = &session->api().globalPrivacy();
  223. const auto shown = globalPrivacy->paidReactionShownPeerCurrent();
  224. const auto owner = &session->data();
  225. const auto shownPeer = shown ? owner->peer(shown).get() : nullptr;
  226. myAdd(shownPeer);
  227. myAdd(session->user());
  228. myAdd(nullptr);
  229. for (const auto &channel : channels) {
  230. myAdd(channel);
  231. }
  232. ranges::stable_sort(top, ranges::greater(), &Ui::PaidReactionTop::count);
  233. state->selectBox = show->show(Ui::MakePaidReactionBox({
  234. .chosen = chosen,
  235. .max = max,
  236. .top = std::move(top),
  237. .channel = channel->name(),
  238. .submit = std::move(submitText),
  239. .balanceValue = session->credits().balanceValue(),
  240. .send = [=](int count, uint64 barePeerId) {
  241. send(count, PeerId(barePeerId), send);
  242. },
  243. }));
  244. if (const auto strong = state->selectBox.data()) {
  245. session->data().itemRemoved(
  246. ) | rpl::start_with_next([=](not_null<const HistoryItem*> removed) {
  247. if (removed == item) {
  248. strong->closeBox();
  249. }
  250. }, strong->lifetime());
  251. }
  252. }
  253. } // namespace Payments