mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Allow sending paid reactions.
This commit is contained in:
parent
bb3fc17489
commit
9bb1fa8782
36 changed files with 1257 additions and 275 deletions
|
@ -475,6 +475,8 @@ PRIVATE
|
||||||
data/business/data_business_info.h
|
data/business/data_business_info.h
|
||||||
data/business/data_shortcut_messages.cpp
|
data/business/data_shortcut_messages.cpp
|
||||||
data/business/data_shortcut_messages.h
|
data/business/data_shortcut_messages.h
|
||||||
|
data/components/credits.cpp
|
||||||
|
data/components/credits.h
|
||||||
data/components/factchecks.cpp
|
data/components/factchecks.cpp
|
||||||
data/components/factchecks.h
|
data/components/factchecks.h
|
||||||
data/components/location_pickers.cpp
|
data/components/location_pickers.cpp
|
||||||
|
@ -1236,6 +1238,8 @@ PRIVATE
|
||||||
payments/payments_form.h
|
payments/payments_form.h
|
||||||
payments/payments_non_panel_process.cpp
|
payments/payments_non_panel_process.cpp
|
||||||
payments/payments_non_panel_process.h
|
payments/payments_non_panel_process.h
|
||||||
|
payments/payments_reaction_process.cpp
|
||||||
|
payments/payments_reaction_process.h
|
||||||
platform/linux/file_utilities_linux.cpp
|
platform/linux/file_utilities_linux.cpp
|
||||||
platform/linux/file_utilities_linux.h
|
platform/linux/file_utilities_linux.h
|
||||||
platform/linux/launcher_linux.cpp
|
platform/linux/launcher_linux.cpp
|
||||||
|
|
BIN
Telegram/Resources/animations/star_reaction/center.tgs
Normal file
BIN
Telegram/Resources/animations/star_reaction/center.tgs
Normal file
Binary file not shown.
Binary file not shown.
BIN
Telegram/Resources/animations/star_reaction/effect1.tgs
Normal file
BIN
Telegram/Resources/animations/star_reaction/effect1.tgs
Normal file
Binary file not shown.
BIN
Telegram/Resources/animations/star_reaction/effect2.tgs
Normal file
BIN
Telegram/Resources/animations/star_reaction/effect2.tgs
Normal file
Binary file not shown.
BIN
Telegram/Resources/animations/star_reaction/effect3.tgs
Normal file
BIN
Telegram/Resources/animations/star_reaction/effect3.tgs
Normal file
Binary file not shown.
|
@ -2399,6 +2399,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_credits_small_balance_title#one" = "{count} Star Needed";
|
"lng_credits_small_balance_title#one" = "{count} Star Needed";
|
||||||
"lng_credits_small_balance_title#other" = "{count} Stars Needed";
|
"lng_credits_small_balance_title#other" = "{count} Stars Needed";
|
||||||
"lng_credits_small_balance_about" = "Buy **Stars** and use them on **{bot}** and other miniapps.";
|
"lng_credits_small_balance_about" = "Buy **Stars** and use them on **{bot}** and other miniapps.";
|
||||||
|
"lng_credits_small_balance_reaction" = "Buy **Stars** and send them to {channel} to support their posts.";
|
||||||
"lng_credits_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars.";
|
"lng_credits_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars.";
|
||||||
|
|
||||||
"lng_credits_gift_title" = "Gift Telegram Stars";
|
"lng_credits_gift_title" = "Gift Telegram Stars";
|
||||||
|
@ -3416,6 +3417,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_paid_about_link_url" = "https://telegram.org/blog/telegram-stars";
|
"lng_paid_about_link_url" = "https://telegram.org/blog/telegram-stars";
|
||||||
"lng_paid_price" = "Unlock for {price}";
|
"lng_paid_price" = "Unlock for {price}";
|
||||||
|
|
||||||
|
"lng_paid_react_title" = "Star Reaction";
|
||||||
|
"lng_paid_react_about" = "Choose how many stars you want to send to {channel} to support this post.";
|
||||||
|
"lng_paid_react_top_title" = "Top Senders";
|
||||||
|
"lng_paid_react_send" = "Send {price}";
|
||||||
|
"lng_paid_react_agree" = "By sending stars, you agree to the {link}.";
|
||||||
|
"lng_paid_react_agree_link" = "Terms of Service";
|
||||||
|
|
||||||
"lng_translate_show_original" = "Show Original";
|
"lng_translate_show_original" = "Show Original";
|
||||||
"lng_translate_bar_to" = "Translate to {name}";
|
"lng_translate_bar_to" = "Translate to {name}";
|
||||||
"lng_translate_bar_to_other" = "Translate to {name}";
|
"lng_translate_bar_to_other" = "Translate to {name}";
|
||||||
|
|
|
@ -41,7 +41,10 @@
|
||||||
<file alias="winners.tgs">../../animations/dice/winners.tgs</file>
|
<file alias="winners.tgs">../../animations/dice/winners.tgs</file>
|
||||||
|
|
||||||
<file alias="star_reaction_appear.tgs">../../animations/star_reaction/appear.tgs</file>
|
<file alias="star_reaction_appear.tgs">../../animations/star_reaction/appear.tgs</file>
|
||||||
<file alias="star_reaction_effect.tgs">../../animations/star_reaction/effect.tgs</file>
|
<file alias="star_reaction_center.tgs">../../animations/star_reaction/center.tgs</file>
|
||||||
<file alias="star_reaction_select.tgs">../../animations/star_reaction/select.tgs</file>
|
<file alias="star_reaction_select.tgs">../../animations/star_reaction/select.tgs</file>
|
||||||
|
<file alias="star_reaction_effect1.tgs">../../animations/star_reaction/effect1.tgs</file>
|
||||||
|
<file alias="star_reaction_effect2.tgs">../../animations/star_reaction/effect2.tgs</file>
|
||||||
|
<file alias="star_reaction_effect3.tgs">../../animations/star_reaction/effect3.tgs</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|
|
@ -200,10 +200,14 @@ void CreditsStatus::request(
|
||||||
_peer->isSelf() ? MTP_inputPeerSelf() : _peer->input
|
_peer->isSelf() ? MTP_inputPeerSelf() : _peer->input
|
||||||
)).done([=](const TLResult &result) {
|
)).done([=](const TLResult &result) {
|
||||||
_requestId = 0;
|
_requestId = 0;
|
||||||
done(StatusFromTL(result, _peer));
|
if (const auto onstack = done) {
|
||||||
|
onstack(StatusFromTL(result, _peer));
|
||||||
|
}
|
||||||
}).fail([=] {
|
}).fail([=] {
|
||||||
_requestId = 0;
|
_requestId = 0;
|
||||||
done({});
|
if (const auto onstack = done) {
|
||||||
|
onstack({});
|
||||||
|
}
|
||||||
}).send();
|
}).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "mtproto/mtproto_config.h"
|
#include "mtproto/mtproto_config.h"
|
||||||
#include "mtproto/mtproto_dc_options.h"
|
#include "mtproto/mtproto_dc_options.h"
|
||||||
#include "data/business/data_shortcut_messages.h"
|
#include "data/business/data_shortcut_messages.h"
|
||||||
|
#include "data/components/credits.h"
|
||||||
#include "data/components/scheduled_messages.h"
|
#include "data/components/scheduled_messages.h"
|
||||||
#include "data/components/top_peers.h"
|
#include "data/components/top_peers.h"
|
||||||
#include "data/notify/data_notify_settings.h"
|
#include "data/notify/data_notify_settings.h"
|
||||||
|
@ -2618,7 +2619,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||||
|
|
||||||
case mtpc_updateStarsBalance: {
|
case mtpc_updateStarsBalance: {
|
||||||
const auto &data = update.c_updateStarsBalance();
|
const auto &data = update.c_updateStarsBalance();
|
||||||
_session->setCredits(data.vbalance().v);
|
_session->credits().apply(data);
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "api/api_credits.h"
|
#include "api/api_credits.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
#include "core/ui_integration.h" // Core::MarkedTextContext.
|
#include "core/ui_integration.h" // Core::MarkedTextContext.
|
||||||
|
#include "data/components/credits.h"
|
||||||
#include "data/data_credits.h"
|
#include "data/data_credits.h"
|
||||||
#include "data/data_photo.h"
|
#include "data/data_photo.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
|
@ -301,7 +302,7 @@ void SendCreditsBox(
|
||||||
st::giveawayGiftCodeStartButton.height / 2);
|
st::giveawayGiftCodeStartButton.height / 2);
|
||||||
AddChildToWidgetCenter(button.data(), loadingAnimation);
|
AddChildToWidgetCenter(button.data(), loadingAnimation);
|
||||||
loadingAnimation->showOn(state->confirmButtonBusy.value());
|
loadingAnimation->showOn(state->confirmButtonBusy.value());
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto buttonText = tr::lng_credits_box_out_confirm(
|
auto buttonText = tr::lng_credits_box_out_confirm(
|
||||||
lt_count,
|
lt_count,
|
||||||
|
@ -361,15 +362,11 @@ void SendCreditsBox(
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
session->credits().load(true);
|
||||||
const auto balance = Settings::AddBalanceWidget(
|
const auto balance = Settings::AddBalanceWidget(
|
||||||
content,
|
content,
|
||||||
session->creditsValue(),
|
session->credits().balanceValue(),
|
||||||
false);
|
false);
|
||||||
const auto api = balance->lifetime().make_state<Api::CreditsStatus>(
|
|
||||||
session->user());
|
|
||||||
api->request({}, [=](Data::CreditsStatusSlice slice) {
|
|
||||||
session->setCredits(slice.balance);
|
|
||||||
});
|
|
||||||
rpl::combine(
|
rpl::combine(
|
||||||
balance->sizeValue(),
|
balance->sizeValue(),
|
||||||
content->sizeValue()
|
content->sizeValue()
|
||||||
|
|
113
Telegram/SourceFiles/data/components/credits.cpp
Normal file
113
Telegram/SourceFiles/data/components/credits.cpp
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
/*
|
||||||
|
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 "data/components/credits.h"
|
||||||
|
|
||||||
|
#include "api/api_credits.h"
|
||||||
|
#include "data/data_user.h"
|
||||||
|
#include "main/main_session.h"
|
||||||
|
|
||||||
|
namespace Data {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kReloadThreshold = 60 * crl::time(1000);
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Credits::Credits(not_null<Main::Session*> session)
|
||||||
|
: _session(session)
|
||||||
|
, _reload([=] { load(true); }) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Credits::~Credits() = default;
|
||||||
|
|
||||||
|
void Credits::apply(const MTPDupdateStarsBalance &data) {
|
||||||
|
apply(data.vbalance().v);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Credits::load(bool force) {
|
||||||
|
if (_loader
|
||||||
|
|| (!force
|
||||||
|
&& _lastLoaded
|
||||||
|
&& _lastLoaded + kReloadThreshold > crl::now())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_loader = std::make_unique<Api::CreditsStatus>(_session->user());
|
||||||
|
_loader->request({}, [=](Data::CreditsStatusSlice slice) {
|
||||||
|
_loader = nullptr;
|
||||||
|
apply(slice.balance);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Credits::loaded() const {
|
||||||
|
return _lastLoaded != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<bool> Credits::loadedValue() const {
|
||||||
|
if (loaded()) {
|
||||||
|
return rpl::single(true);
|
||||||
|
}
|
||||||
|
return rpl::single(
|
||||||
|
false
|
||||||
|
) | rpl::then(_loadedChanges.events() | rpl::map_to(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64 Credits::balance() const {
|
||||||
|
const auto balance = _balance.current();
|
||||||
|
const auto locked = _locked.current();
|
||||||
|
return (balance >= locked) ? (balance - locked) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<uint64> Credits::balanceValue() const {
|
||||||
|
return rpl::combine(
|
||||||
|
_balance.value(),
|
||||||
|
_locked.value()
|
||||||
|
) | rpl::map([=](uint64 balance, uint64 locked) {
|
||||||
|
return (balance >= locked) ? (balance - locked) : 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Credits::lock(int count) {
|
||||||
|
Expects(loaded());
|
||||||
|
Expects(count >= 0);
|
||||||
|
|
||||||
|
_locked = _locked.current() + count;
|
||||||
|
|
||||||
|
Ensures(_locked.current() <= _balance.current());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Credits::unlock(int count) {
|
||||||
|
Expects(count >= 0);
|
||||||
|
Expects(_locked.current() >= count);
|
||||||
|
|
||||||
|
_locked = _locked.current() - count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Credits::withdrawLocked(int count) {
|
||||||
|
Expects(count >= 0);
|
||||||
|
Expects(_locked.current() >= count);
|
||||||
|
|
||||||
|
const auto balance = _balance.current();
|
||||||
|
_locked = _locked.current() - count;
|
||||||
|
apply(balance >= count ? (balance - count) : 0);
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Credits::invalidate() {
|
||||||
|
_reload.call();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Credits::apply(uint64 balance) {
|
||||||
|
_balance = balance;
|
||||||
|
|
||||||
|
const auto was = std::exchange(_lastLoaded, crl::now());
|
||||||
|
if (!was) {
|
||||||
|
_loadedChanges.fire({});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Data
|
55
Telegram/SourceFiles/data/components/credits.h
Normal file
55
Telegram/SourceFiles/data/components/credits.h
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
|
||||||
|
namespace Api {
|
||||||
|
class CreditsStatus;
|
||||||
|
} // namespace Api
|
||||||
|
|
||||||
|
namespace Main {
|
||||||
|
class Session;
|
||||||
|
} // namespace Main
|
||||||
|
|
||||||
|
namespace Data {
|
||||||
|
|
||||||
|
class Credits final {
|
||||||
|
public:
|
||||||
|
explicit Credits(not_null<Main::Session*> session);
|
||||||
|
~Credits();
|
||||||
|
|
||||||
|
void load(bool force = false);
|
||||||
|
void apply(uint64 balance);
|
||||||
|
|
||||||
|
[[nodiscard]] bool loaded() const;
|
||||||
|
[[nodiscard]] rpl::producer<bool> loadedValue() const;
|
||||||
|
|
||||||
|
[[nodiscard]] uint64 balance() const;
|
||||||
|
[[nodiscard]] rpl::producer<uint64> balanceValue() const;
|
||||||
|
|
||||||
|
void lock(int count);
|
||||||
|
void unlock(int count);
|
||||||
|
void withdrawLocked(int count);
|
||||||
|
void invalidate();
|
||||||
|
|
||||||
|
void apply(const MTPDupdateStarsBalance &data);
|
||||||
|
|
||||||
|
private:
|
||||||
|
const not_null<Main::Session*> _session;
|
||||||
|
|
||||||
|
std::unique_ptr<Api::CreditsStatus> _loader;
|
||||||
|
|
||||||
|
rpl::variable<uint64> _balance;
|
||||||
|
rpl::variable<uint64> _locked;
|
||||||
|
rpl::event_stream<> _loadedChanges;
|
||||||
|
crl::time _lastLoaded = 0;
|
||||||
|
|
||||||
|
SingleQueuedInvokation _reload;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Data
|
|
@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "main/main_app_config.h"
|
#include "main/main_app_config.h"
|
||||||
#include "main/session/send_as_peers.h"
|
#include "main/session/send_as_peers.h"
|
||||||
|
#include "data/components/credits.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_histories.h"
|
#include "data/data_histories.h"
|
||||||
|
@ -34,6 +35,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
|
||||||
|
#include "base/random.h"
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
@ -45,6 +48,7 @@ constexpr auto kRecentReactionsLimit = 40;
|
||||||
constexpr auto kMyTagsRequestTimeout = crl::time(1000);
|
constexpr auto kMyTagsRequestTimeout = crl::time(1000);
|
||||||
constexpr auto kTopRequestDelay = 60 * crl::time(1000);
|
constexpr auto kTopRequestDelay = 60 * crl::time(1000);
|
||||||
constexpr auto kTopReactionsLimit = 14;
|
constexpr auto kTopReactionsLimit = 14;
|
||||||
|
constexpr auto kPaidAccumulatePeriod = 5 * crl::time(1000);
|
||||||
|
|
||||||
[[nodiscard]] QString ReactionIdToLog(const ReactionId &id) {
|
[[nodiscard]] QString ReactionIdToLog(const ReactionId &id) {
|
||||||
if (const auto custom = id.custom()) {
|
if (const auto custom = id.custom()) {
|
||||||
|
@ -109,17 +113,17 @@ constexpr auto kTopReactionsLimit = 14;
|
||||||
: config->get<int>("reactions_user_max_default", 1);
|
: config->get<int>("reactions_user_max_default", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsMyRecent(
|
[[nodiscard]] bool IsMyRecent(
|
||||||
const MTPDmessagePeerReaction &data,
|
const MTPDmessagePeerReaction &data,
|
||||||
const ReactionId &id,
|
const ReactionId &id,
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
const base::flat_map<
|
const base::flat_map<
|
||||||
ReactionId,
|
ReactionId,
|
||||||
std::vector<RecentReaction>> &recent,
|
std::vector<RecentReaction>> &recent,
|
||||||
bool ignoreChosen) {
|
bool min) {
|
||||||
if (peer->id == peer->session().userPeerId()) {
|
if (peer->isSelf()) {
|
||||||
return true;
|
return true;
|
||||||
} else if (!ignoreChosen) {
|
} else if (!min) {
|
||||||
return data.is_my();
|
return data.is_my();
|
||||||
}
|
}
|
||||||
const auto j = recent.find(id);
|
const auto j = recent.find(id);
|
||||||
|
@ -133,6 +137,20 @@ bool IsMyRecent(
|
||||||
return (k != end(j->second)) && k->my;
|
return (k != end(j->second)) && k->my;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsMyTop(
|
||||||
|
const MTPDmessageReactor &data,
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
const std::vector<MessageReactionsTopPaid> &top,
|
||||||
|
bool min) {
|
||||||
|
if (peer->isSelf()) {
|
||||||
|
return true;
|
||||||
|
} else if (!min) {
|
||||||
|
return data.is_my();
|
||||||
|
}
|
||||||
|
const auto i = ranges::find(top, peer, &MessageReactionsTopPaid::peer);
|
||||||
|
return (i != end(top)) && i->my;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
PossibleItemReactionsRef LookupPossibleReactions(
|
PossibleItemReactionsRef LookupPossibleReactions(
|
||||||
|
@ -265,7 +283,8 @@ PossibleItemReactions::PossibleItemReactions(
|
||||||
Reactions::Reactions(not_null<Session*> owner)
|
Reactions::Reactions(not_null<Session*> owner)
|
||||||
: _owner(owner)
|
: _owner(owner)
|
||||||
, _topRefreshTimer([=] { refreshTop(); })
|
, _topRefreshTimer([=] { refreshTop(); })
|
||||||
, _repaintTimer([=] { repaintCollected(); }) {
|
, _repaintTimer([=] { repaintCollected(); })
|
||||||
|
, _sendPaidTimer([=] { sendPaid(); }) {
|
||||||
refreshDefault();
|
refreshDefault();
|
||||||
|
|
||||||
_myTags.emplace(nullptr);
|
_myTags.emplace(nullptr);
|
||||||
|
@ -284,6 +303,14 @@ Reactions::Reactions(not_null<Session*> owner)
|
||||||
_pollingItems.remove(item);
|
_pollingItems.remove(item);
|
||||||
_pollItems.remove(item);
|
_pollItems.remove(item);
|
||||||
_repaintItems.remove(item);
|
_repaintItems.remove(item);
|
||||||
|
_sendPaidItems.remove(item);
|
||||||
|
if (_sendingPaid == item) {
|
||||||
|
_sendingPaid = nullptr;
|
||||||
|
_owner->session().credits().invalidate();
|
||||||
|
crl::on_main(&_owner->session(), [=] {
|
||||||
|
sendPaid();
|
||||||
|
});
|
||||||
|
}
|
||||||
}, _lifetime);
|
}, _lifetime);
|
||||||
|
|
||||||
crl::on_main(&owner->session(), [=] {
|
crl::on_main(&owner->session(), [=] {
|
||||||
|
@ -510,21 +537,49 @@ DocumentData *Reactions::chooseGenericAnimation(
|
||||||
return i->aroundAnimation;
|
return i->aroundAnimation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_genericAnimations.empty()) {
|
return randomLoadedFrom(_genericAnimations);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Reactions::fillPaidReactionAnimations() const {
|
||||||
|
const auto generate = [&](int index) {
|
||||||
|
const auto session = &_owner->session();
|
||||||
|
const auto name = u"star_reaction_effect%1"_q.arg(index + 1);
|
||||||
|
return ChatHelpers::GenerateLocalTgsSticker(session, name);
|
||||||
|
};
|
||||||
|
const auto kCount = 3;
|
||||||
|
for (auto i = 0; i != kCount; ++i) {
|
||||||
|
const auto document = generate(i);
|
||||||
|
_paidReactionAnimations.push_back(document);
|
||||||
|
_paidReactionCache.emplace(
|
||||||
|
document,
|
||||||
|
document->createMediaView());
|
||||||
|
}
|
||||||
|
_paidReactionCache.front().second->checkStickerLarge();
|
||||||
|
}
|
||||||
|
|
||||||
|
DocumentData *Reactions::choosePaidReactionAnimation() const {
|
||||||
|
if (_paidReactionAnimations.empty()) {
|
||||||
|
fillPaidReactionAnimations();
|
||||||
|
}
|
||||||
|
return randomLoadedFrom(_paidReactionAnimations);
|
||||||
|
}
|
||||||
|
|
||||||
|
DocumentData *Reactions::randomLoadedFrom(
|
||||||
|
std::vector<not_null<DocumentData*>> list) const {
|
||||||
|
if (list.empty()) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
auto copy = _genericAnimations;
|
ranges::shuffle(list);
|
||||||
ranges::shuffle(copy);
|
const auto first = list.front();
|
||||||
const auto first = copy.front();
|
|
||||||
const auto view = first->createMediaView();
|
const auto view = first->createMediaView();
|
||||||
view->checkStickerLarge();
|
view->checkStickerLarge();
|
||||||
if (view->loaded()) {
|
if (view->loaded()) {
|
||||||
return first;
|
return first;
|
||||||
}
|
}
|
||||||
const auto k = ranges::find_if(copy, [&](not_null<DocumentData*> value) {
|
const auto k = ranges::find_if(list, [&](not_null<DocumentData*> value) {
|
||||||
return value->createMediaView()->loaded();
|
return value->createMediaView()->loaded();
|
||||||
});
|
});
|
||||||
return (k != end(copy)) ? (*k) : first;
|
return (k != end(list)) ? (*k) : first;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Reactions::applyFavorite(const ReactionId &id) {
|
void Reactions::applyFavorite(const ReactionId &id) {
|
||||||
|
@ -593,7 +648,7 @@ void Reactions::preloadImageFor(const ReactionId &id) {
|
||||||
auto &set = _images.emplace(id).first->second;
|
auto &set = _images.emplace(id).first->second;
|
||||||
set.effect = (id.custom() != 0);
|
set.effect = (id.custom() != 0);
|
||||||
if (id.paid()) {
|
if (id.paid()) {
|
||||||
loadImage(set, lookupPaid()->selectAnimation, true);
|
loadImage(set, lookupPaid()->centerIcon, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto &list = set.effect ? _effects : _available;
|
auto &list = set.effect ? _effects : _available;
|
||||||
|
@ -631,6 +686,20 @@ void Reactions::preloadEffect(const Reaction &effect) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Reactions::preloadAnimationsFor(const ReactionId &id) {
|
void Reactions::preloadAnimationsFor(const ReactionId &id) {
|
||||||
|
const auto preload = [&](DocumentData *document) {
|
||||||
|
const auto view = document
|
||||||
|
? document->activeMediaView()
|
||||||
|
: nullptr;
|
||||||
|
if (view) {
|
||||||
|
view->checkStickerLarge();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (id.paid()) {
|
||||||
|
const auto fake = lookupPaid();
|
||||||
|
preload(fake->centerIcon);
|
||||||
|
preload(fake->aroundAnimation);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const auto custom = id.custom();
|
const auto custom = id.custom();
|
||||||
const auto document = custom ? _owner->document(custom).get() : nullptr;
|
const auto document = custom ? _owner->document(custom).get() : nullptr;
|
||||||
const auto customSticker = document ? document->sticker() : nullptr;
|
const auto customSticker = document ? document->sticker() : nullptr;
|
||||||
|
@ -641,15 +710,6 @@ void Reactions::preloadAnimationsFor(const ReactionId &id) {
|
||||||
if (i == end(_available)) {
|
if (i == end(_available)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto preload = [&](DocumentData *document) {
|
|
||||||
const auto view = document
|
|
||||||
? document->activeMediaView()
|
|
||||||
: nullptr;
|
|
||||||
if (view) {
|
|
||||||
view->checkStickerLarge();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!custom) {
|
if (!custom) {
|
||||||
preload(i->centerIcon);
|
preload(i->centerIcon);
|
||||||
}
|
}
|
||||||
|
@ -1380,21 +1440,6 @@ void Reactions::send(not_null<HistoryItem*> item, bool addToRecent) {
|
||||||
}).send();
|
}).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Reactions::sendPaid(not_null<HistoryItem*> item, int count) {
|
|
||||||
const auto id = item->fullId();
|
|
||||||
const auto randomId = base::unixtime::mtproto_msg_id();
|
|
||||||
auto &api = _owner->session().api();
|
|
||||||
api.request(MTPmessages_SendPaidReaction(
|
|
||||||
item->history()->peer->input,
|
|
||||||
MTP_int(id.msg),
|
|
||||||
MTP_int(count),
|
|
||||||
MTP_long(randomId)
|
|
||||||
)).done([=](const MTPUpdates &result) {
|
|
||||||
_owner->session().api().applyUpdates(result);
|
|
||||||
}).fail([=](const MTP::Error &error) {
|
|
||||||
}).send();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Reactions::poll(not_null<HistoryItem*> item, crl::time now) {
|
void Reactions::poll(not_null<HistoryItem*> item, crl::time now) {
|
||||||
// Group them by one second.
|
// Group them by one second.
|
||||||
const auto last = item->lastReactionsRefreshTime();
|
const auto last = item->lastReactionsRefreshTime();
|
||||||
|
@ -1460,16 +1505,22 @@ not_null<Reaction*> Reactions::lookupPaid() {
|
||||||
const auto session = &_owner->session();
|
const auto session = &_owner->session();
|
||||||
return ChatHelpers::GenerateLocalTgsSticker(session, name);
|
return ChatHelpers::GenerateLocalTgsSticker(session, name);
|
||||||
};
|
};
|
||||||
|
const auto appear = generate(u"star_reaction_appear"_q);
|
||||||
|
const auto center = generate(u"star_reaction_center"_q);
|
||||||
const auto select = generate(u"star_reaction_select"_q);
|
const auto select = generate(u"star_reaction_select"_q);
|
||||||
_paid.emplace(Reaction{
|
_paid.emplace(Reaction{
|
||||||
.id = ReactionId::Paid(),
|
.id = ReactionId::Paid(),
|
||||||
.title = u"Telegram Star"_q,
|
.title = u"Telegram Star"_q,
|
||||||
.appearAnimation = generate(u"star_reaction_appear"_q),
|
.appearAnimation = appear,
|
||||||
.selectAnimation = select,
|
.selectAnimation = select,
|
||||||
.centerIcon = select,
|
.centerIcon = center,
|
||||||
//.aroundAnimation = generate(u"star_reaction_effect"_q),
|
|
||||||
.active = true,
|
.active = true,
|
||||||
});
|
});
|
||||||
|
_iconsCache.emplace(appear, appear->createMediaView());
|
||||||
|
_iconsCache.emplace(center, center->createMediaView());
|
||||||
|
_iconsCache.emplace(select, select->createMediaView());
|
||||||
|
|
||||||
|
fillPaidReactionAnimations();
|
||||||
}
|
}
|
||||||
return &*_paid;
|
return &*_paid;
|
||||||
}
|
}
|
||||||
|
@ -1488,6 +1539,13 @@ rpl::producer<std::vector<Reaction>> Reactions::myTagsValue(
|
||||||
) | rpl::map(list));
|
) | rpl::map(list));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Reactions::schedulePaid(not_null<HistoryItem*> item) {
|
||||||
|
_sendPaidItems[item] = crl::now() + kPaidAccumulatePeriod;
|
||||||
|
if (!_sendPaidTimer.isActive()) {
|
||||||
|
_sendPaidTimer.callOnce(kPaidAccumulatePeriod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Reactions::repaintCollected() {
|
void Reactions::repaintCollected() {
|
||||||
const auto now = crl::now();
|
const auto now = crl::now();
|
||||||
auto closest = crl::time();
|
auto closest = crl::time();
|
||||||
|
@ -1543,7 +1601,7 @@ void Reactions::pollCollected() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Reactions::sending(not_null<HistoryItem*> item) const {
|
bool Reactions::sending(not_null<HistoryItem*> item) const {
|
||||||
return _sentRequests.contains(item->fullId());
|
return _sentRequests.contains(item->fullId()) || (_sendingPaid == item);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Reactions::HasUnread(const MTPMessageReactions &data) {
|
bool Reactions::HasUnread(const MTPMessageReactions &data) {
|
||||||
|
@ -1575,31 +1633,91 @@ void Reactions::CheckUnknownForUnread(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Reactions::sendPaid() {
|
||||||
|
if (_sendingPaid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto next = crl::time();
|
||||||
|
const auto now = crl::now();
|
||||||
|
for (auto i = begin(_sendPaidItems); i != end(_sendPaidItems);) {
|
||||||
|
const auto item = i->first;
|
||||||
|
const auto when = i->second;
|
||||||
|
if (when > now) {
|
||||||
|
if (!next || next > when) {
|
||||||
|
next = when;
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
} else {
|
||||||
|
i = _sendPaidItems.erase(i);
|
||||||
|
if (sendPaid(item)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (next) {
|
||||||
|
_sendPaidTimer.callOnce(next - now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Reactions::sendPaid(not_null<HistoryItem*> item) {
|
||||||
|
Expects(!_sendingPaid);
|
||||||
|
|
||||||
|
const auto count = item->startPaidReactionSending();
|
||||||
|
if (!count) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_sendingPaid = item;
|
||||||
|
sendPaidRequest(count);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Reactions::sendPaidRequest(int count) {
|
||||||
|
const auto id = _sendingPaid->fullId();
|
||||||
|
const auto randomId = base::unixtime::mtproto_msg_id();
|
||||||
|
auto &api = _owner->session().api();
|
||||||
|
api.request(MTPmessages_SendPaidReaction(
|
||||||
|
_sendingPaid->history()->peer->input,
|
||||||
|
MTP_int(id.msg),
|
||||||
|
MTP_int(count),
|
||||||
|
MTP_long(randomId)
|
||||||
|
)).done([=](const MTPUpdates &result) {
|
||||||
|
sendPaidFinish(id, count, true);
|
||||||
|
_owner->session().api().applyUpdates(result);
|
||||||
|
}).fail([=](const MTP::Error &error) {
|
||||||
|
if (!_sendingPaid
|
||||||
|
|| (_sendingPaid->fullId() != id)
|
||||||
|
|| (error.type() != u"RANDOM_ID_EXPIRED"_q)) {
|
||||||
|
sendPaidFinish(id, count, false);
|
||||||
|
} else {
|
||||||
|
sendPaidRequest(count);
|
||||||
|
}
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Reactions::sendPaidFinish(FullMsgId id, int count, bool success) {
|
||||||
|
if (_sendingPaid && _sendingPaid->fullId() == id) {
|
||||||
|
base::take(_sendingPaid)->finishPaidReactionSending(count, success);
|
||||||
|
sendPaid();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MessageReactions::MessageReactions(not_null<HistoryItem*> item)
|
MessageReactions::MessageReactions(not_null<HistoryItem*> item)
|
||||||
: _item(item) {
|
: _item(item) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageReactions::addPaid(int count) {
|
MessageReactions::~MessageReactions() {
|
||||||
Expects(_item->history()->peer->isBroadcast());
|
cancelScheduledPaid();
|
||||||
|
if (const auto paid = _paid.get()) {
|
||||||
const auto id = Data::ReactionId::Paid();
|
if (paid->sending > 0) {
|
||||||
const auto history = _item->history();
|
finishPaidSending(paid->sending, false);
|
||||||
const auto peer = history->peer;
|
}
|
||||||
const auto i = ranges::find(_list, id, &MessageReaction::id);
|
|
||||||
if (i != end(_list)) {
|
|
||||||
i->my = true;
|
|
||||||
i->count += count;
|
|
||||||
std::rotate(i, i + 1, end(_list));
|
|
||||||
} else {
|
|
||||||
_list.push_back({ .id = id, .count = count, .my = true });
|
|
||||||
}
|
}
|
||||||
auto &owner = history->owner();
|
|
||||||
owner.reactions().sendPaid(_item, count);
|
|
||||||
owner.notifyItemDataChange(_item);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageReactions::add(const ReactionId &id, bool addToRecent) {
|
void MessageReactions::add(const ReactionId &id, bool addToRecent) {
|
||||||
Expects(!id.empty());
|
Expects(!id.empty());
|
||||||
|
Expects(!id.paid());
|
||||||
|
|
||||||
const auto history = _item->history();
|
const auto history = _item->history();
|
||||||
const auto myLimit = SentReactionsLimit(_item);
|
const auto myLimit = SentReactionsLimit(_item);
|
||||||
|
@ -1613,6 +1731,9 @@ void MessageReactions::add(const ReactionId &id, bool addToRecent) {
|
||||||
history->owner().reactions().incrementMyTag(id, sublist);
|
history->owner().reactions().incrementMyTag(id, sublist);
|
||||||
}
|
}
|
||||||
_list.erase(ranges::remove_if(_list, [&](MessageReaction &one) {
|
_list.erase(ranges::remove_if(_list, [&](MessageReaction &one) {
|
||||||
|
if (one.id.paid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const auto removing = one.my && (my == myLimit || ++my == myLimit);
|
const auto removing = one.my && (my == myLimit || ++my == myLimit);
|
||||||
if (!removing) {
|
if (!removing) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -1662,6 +1783,8 @@ void MessageReactions::add(const ReactionId &id, bool addToRecent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageReactions::remove(const ReactionId &id) {
|
void MessageReactions::remove(const ReactionId &id) {
|
||||||
|
Expects(!id.paid());
|
||||||
|
|
||||||
const auto history = _item->history();
|
const auto history = _item->history();
|
||||||
const auto self = history->session().user();
|
const auto self = history->session().user();
|
||||||
const auto i = ranges::find(_list, id, &MessageReaction::id);
|
const auto i = ranges::find(_list, id, &MessageReaction::id);
|
||||||
|
@ -1765,6 +1888,7 @@ bool MessageReactions::checkIfChanged(
|
||||||
bool MessageReactions::change(
|
bool MessageReactions::change(
|
||||||
const QVector<MTPReactionCount> &list,
|
const QVector<MTPReactionCount> &list,
|
||||||
const QVector<MTPMessagePeerReaction> &recent,
|
const QVector<MTPMessagePeerReaction> &recent,
|
||||||
|
const QVector<MTPMessageReactor> &top,
|
||||||
bool min) {
|
bool min) {
|
||||||
auto &owner = _item->history()->owner();
|
auto &owner = _item->history()->owner();
|
||||||
if (owner.reactions().sending(_item)) {
|
if (owner.reactions().sending(_item)) {
|
||||||
|
@ -1844,8 +1968,7 @@ bool MessageReactions::change(
|
||||||
if (list.size() >= i->count) {
|
if (list.size() >= i->count) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto peerId = peerFromMTP(data.vpeer_id());
|
const auto peer = owner.peer(peerFromMTP(data.vpeer_id()));
|
||||||
const auto peer = owner.peer(peerId);
|
|
||||||
const auto my = IsMyRecent(data, id, peer, _recent, min);
|
const auto my = IsMyRecent(data, id, peer, _recent, min);
|
||||||
list.push_back({
|
list.push_back({
|
||||||
.peer = peer,
|
.peer = peer,
|
||||||
|
@ -1859,6 +1982,54 @@ bool MessageReactions::change(
|
||||||
_recent = std::move(parsed);
|
_recent = std::move(parsed);
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto paidTop = std::vector<TopPaid>();
|
||||||
|
const auto &paindTopNow = _paid ? _paid->top : std::vector<TopPaid>();
|
||||||
|
for (const auto &reactor : top) {
|
||||||
|
const auto &data = reactor.data();
|
||||||
|
const auto peer = owner.peer(peerFromMTP(data.vpeer_id()));
|
||||||
|
paidTop.push_back({
|
||||||
|
.peer = peer,
|
||||||
|
.count = uint32(data.vcount().v),
|
||||||
|
.top = data.is_top(),
|
||||||
|
.my = IsMyTop(data, peer, paindTopNow, min),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (paidTop.empty()) {
|
||||||
|
if (_paid && !_paid->top.empty()) {
|
||||||
|
changed = true;
|
||||||
|
if (localPaidCount()) {
|
||||||
|
_paid->top.clear();
|
||||||
|
} else {
|
||||||
|
_paid = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (min && _paid) {
|
||||||
|
const auto mine = [](const TopPaid &entry) {
|
||||||
|
return entry.my != 0;
|
||||||
|
};
|
||||||
|
if (!ranges::contains(paidTop, true, mine)) {
|
||||||
|
const auto nonTopMine = [](const TopPaid &entry) {
|
||||||
|
return entry.my && !entry.top;
|
||||||
|
};
|
||||||
|
const auto i = ranges::find(_paid->top, true, nonTopMine);
|
||||||
|
if (i != end(_paid->top)) {
|
||||||
|
paidTop.push_back(*i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ranges::sort(paidTop, std::greater(), [](const TopPaid &entry) {
|
||||||
|
return entry.count;
|
||||||
|
});
|
||||||
|
if (!_paid) {
|
||||||
|
_paid = std::make_unique<Paid>();
|
||||||
|
}
|
||||||
|
if (_paid->top != paidTop) {
|
||||||
|
_paid->top = std::move(paidTop);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1892,6 +2063,74 @@ void MessageReactions::markRead() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MessageReactions::scheduleSendPaid(int count) {
|
||||||
|
Expects(count > 0);
|
||||||
|
|
||||||
|
if (!_paid) {
|
||||||
|
_paid = std::make_unique<Paid>();
|
||||||
|
}
|
||||||
|
_paid->scheduled += count;
|
||||||
|
_item->history()->session().credits().lock(count);
|
||||||
|
_item->history()->owner().reactions().schedulePaid(_item);
|
||||||
|
}
|
||||||
|
|
||||||
|
int MessageReactions::scheduledPaid() const {
|
||||||
|
return _paid ? _paid->scheduled : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageReactions::cancelScheduledPaid() {
|
||||||
|
if (_paid) {
|
||||||
|
if (_paid->scheduled > 0) {
|
||||||
|
_item->history()->session().credits().unlock(
|
||||||
|
base::take(_paid->scheduled));
|
||||||
|
}
|
||||||
|
if (!_paid->sending && _paid->top.empty()) {
|
||||||
|
_paid = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int MessageReactions::startPaidSending() {
|
||||||
|
if (!_paid || !_paid->scheduled || _paid->sending) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
_paid->sending = _paid->scheduled;
|
||||||
|
_paid->scheduled = 0;
|
||||||
|
return _paid->sending;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageReactions::finishPaidSending(int count, bool success) {
|
||||||
|
Expects(count > 0);
|
||||||
|
Expects(_paid != nullptr);
|
||||||
|
Expects(count == _paid->sending);
|
||||||
|
|
||||||
|
_paid->sending = 0;
|
||||||
|
if (!_paid->scheduled && _paid->top.empty()) {
|
||||||
|
_paid = nullptr;
|
||||||
|
}
|
||||||
|
if (success) {
|
||||||
|
_item->history()->session().credits().withdrawLocked(count);
|
||||||
|
} else {
|
||||||
|
_item->history()->session().credits().unlock(count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int MessageReactions::localPaidCount() const {
|
||||||
|
return _paid ? (_paid->scheduled + _paid->sending) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MessageReactions::clearCloudData() {
|
||||||
|
const auto result = !_list.empty();
|
||||||
|
_recent.clear();
|
||||||
|
_list.clear();
|
||||||
|
if (localPaidCount()) {
|
||||||
|
_paid->top.clear();
|
||||||
|
} else {
|
||||||
|
_paid = nullptr;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<ReactionId> MessageReactions::chosen() const {
|
std::vector<ReactionId> MessageReactions::chosen() const {
|
||||||
return _list
|
return _list
|
||||||
| ranges::views::filter(&MessageReaction::my)
|
| ranges::views::filter(&MessageReaction::my)
|
||||||
|
|
|
@ -106,6 +106,7 @@ public:
|
||||||
void renameTag(const ReactionId &id, const QString &name);
|
void renameTag(const ReactionId &id, const QString &name);
|
||||||
[[nodiscard]] DocumentData *chooseGenericAnimation(
|
[[nodiscard]] DocumentData *chooseGenericAnimation(
|
||||||
not_null<DocumentData*> custom) const;
|
not_null<DocumentData*> custom) const;
|
||||||
|
[[nodiscard]] DocumentData *choosePaidReactionAnimation() const;
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<> topUpdates() const;
|
[[nodiscard]] rpl::producer<> topUpdates() const;
|
||||||
[[nodiscard]] rpl::producer<> recentUpdates() const;
|
[[nodiscard]] rpl::producer<> recentUpdates() const;
|
||||||
|
@ -129,7 +130,6 @@ public:
|
||||||
void preloadAnimationsFor(const ReactionId &emoji);
|
void preloadAnimationsFor(const ReactionId &emoji);
|
||||||
|
|
||||||
void send(not_null<HistoryItem*> item, bool addToRecent);
|
void send(not_null<HistoryItem*> item, bool addToRecent);
|
||||||
void sendPaid(not_null<HistoryItem*> item, int count);
|
|
||||||
[[nodiscard]] bool sending(not_null<HistoryItem*> item) const;
|
[[nodiscard]] bool sending(not_null<HistoryItem*> item) const;
|
||||||
|
|
||||||
void poll(not_null<HistoryItem*> item, crl::time now);
|
void poll(not_null<HistoryItem*> item, crl::time now);
|
||||||
|
@ -143,6 +143,8 @@ public:
|
||||||
[[nodiscard]] rpl::producer<std::vector<Reaction>> myTagsValue(
|
[[nodiscard]] rpl::producer<std::vector<Reaction>> myTagsValue(
|
||||||
SavedSublist *sublist = nullptr);
|
SavedSublist *sublist = nullptr);
|
||||||
|
|
||||||
|
void schedulePaid(not_null<HistoryItem*> item);
|
||||||
|
|
||||||
[[nodiscard]] static bool HasUnread(const MTPMessageReactions &data);
|
[[nodiscard]] static bool HasUnread(const MTPMessageReactions &data);
|
||||||
static void CheckUnknownForUnread(
|
static void CheckUnknownForUnread(
|
||||||
not_null<Session*> owner,
|
not_null<Session*> owner,
|
||||||
|
@ -233,9 +235,18 @@ private:
|
||||||
void resolveEffectImages();
|
void resolveEffectImages();
|
||||||
void downloadTaskFinished();
|
void downloadTaskFinished();
|
||||||
|
|
||||||
|
void fillPaidReactionAnimations() const;
|
||||||
|
[[nodiscard]] DocumentData *randomLoadedFrom(
|
||||||
|
std::vector<not_null<DocumentData*>> list) const;
|
||||||
|
|
||||||
void repaintCollected();
|
void repaintCollected();
|
||||||
void pollCollected();
|
void pollCollected();
|
||||||
|
|
||||||
|
void sendPaid();
|
||||||
|
bool sendPaid(not_null<HistoryItem*> item);
|
||||||
|
void sendPaidRequest(int count);
|
||||||
|
void sendPaidFinish(FullMsgId id, int count, bool success);
|
||||||
|
|
||||||
const not_null<Session*> _owner;
|
const not_null<Session*> _owner;
|
||||||
|
|
||||||
std::vector<Reaction> _active;
|
std::vector<Reaction> _active;
|
||||||
|
@ -254,6 +265,7 @@ private:
|
||||||
std::vector<ReactionId> _topIds;
|
std::vector<ReactionId> _topIds;
|
||||||
base::flat_set<ReactionId> _unresolvedTop;
|
base::flat_set<ReactionId> _unresolvedTop;
|
||||||
std::vector<not_null<DocumentData*>> _genericAnimations;
|
std::vector<not_null<DocumentData*>> _genericAnimations;
|
||||||
|
mutable std::vector<not_null<DocumentData*>> _paidReactionAnimations;
|
||||||
std::vector<Reaction> _effects;
|
std::vector<Reaction> _effects;
|
||||||
ReactionId _favoriteId;
|
ReactionId _favoriteId;
|
||||||
ReactionId _unresolvedFavoriteId;
|
ReactionId _unresolvedFavoriteId;
|
||||||
|
@ -264,6 +276,9 @@ private:
|
||||||
base::flat_map<
|
base::flat_map<
|
||||||
not_null<DocumentData*>,
|
not_null<DocumentData*>,
|
||||||
std::shared_ptr<DocumentMedia>> _genericCache;
|
std::shared_ptr<DocumentMedia>> _genericCache;
|
||||||
|
mutable base::flat_map<
|
||||||
|
not_null<DocumentData*>,
|
||||||
|
std::shared_ptr<DocumentMedia>> _paidReactionCache;
|
||||||
rpl::event_stream<> _topUpdated;
|
rpl::event_stream<> _topUpdated;
|
||||||
rpl::event_stream<> _recentUpdated;
|
rpl::event_stream<> _recentUpdated;
|
||||||
rpl::event_stream<> _defaultUpdated;
|
rpl::event_stream<> _defaultUpdated;
|
||||||
|
@ -311,6 +326,10 @@ private:
|
||||||
base::flat_set<not_null<HistoryItem*>> _pollingItems;
|
base::flat_set<not_null<HistoryItem*>> _pollingItems;
|
||||||
mtpRequestId _pollRequestId = 0;
|
mtpRequestId _pollRequestId = 0;
|
||||||
|
|
||||||
|
base::flat_map<not_null<HistoryItem*>, crl::time> _sendPaidItems;
|
||||||
|
HistoryItem *_sendingPaid = nullptr;
|
||||||
|
base::Timer _sendPaidTimer;
|
||||||
|
|
||||||
mtpRequestId _saveFaveRequestId = 0;
|
mtpRequestId _saveFaveRequestId = 0;
|
||||||
|
|
||||||
rpl::lifetime _lifetime;
|
rpl::lifetime _lifetime;
|
||||||
|
@ -323,29 +342,40 @@ struct RecentReaction {
|
||||||
bool big = false;
|
bool big = false;
|
||||||
bool my = false;
|
bool my = false;
|
||||||
|
|
||||||
friend inline auto operator<=>(
|
|
||||||
const RecentReaction &a,
|
|
||||||
const RecentReaction &b) = default;
|
|
||||||
friend inline bool operator==(
|
friend inline bool operator==(
|
||||||
const RecentReaction &a,
|
const RecentReaction &a,
|
||||||
const RecentReaction &b) = default;
|
const RecentReaction &b) = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct MessageReactionsTopPaid {
|
||||||
|
not_null<PeerData*> peer;
|
||||||
|
uint32 count : 30 = 0;
|
||||||
|
uint32 top : 1 = 0;
|
||||||
|
uint32 my : 1 = 0;
|
||||||
|
|
||||||
|
friend inline bool operator==(
|
||||||
|
const MessageReactionsTopPaid &a,
|
||||||
|
const MessageReactionsTopPaid &b) = default;
|
||||||
|
};
|
||||||
|
|
||||||
class MessageReactions final {
|
class MessageReactions final {
|
||||||
public:
|
public:
|
||||||
explicit MessageReactions(not_null<HistoryItem*> item);
|
explicit MessageReactions(not_null<HistoryItem*> item);
|
||||||
|
~MessageReactions();
|
||||||
|
|
||||||
|
using TopPaid = MessageReactionsTopPaid;
|
||||||
|
|
||||||
void add(const ReactionId &id, bool addToRecent);
|
void add(const ReactionId &id, bool addToRecent);
|
||||||
void addPaid(int count);
|
|
||||||
void remove(const ReactionId &id);
|
void remove(const ReactionId &id);
|
||||||
bool change(
|
bool change(
|
||||||
const QVector<MTPReactionCount> &list,
|
const QVector<MTPReactionCount> &list,
|
||||||
const QVector<MTPMessagePeerReaction> &recent,
|
const QVector<MTPMessagePeerReaction> &recent,
|
||||||
bool ignoreChosen);
|
const QVector<MTPMessageReactor> &top,
|
||||||
|
bool min);
|
||||||
[[nodiscard]] bool checkIfChanged(
|
[[nodiscard]] bool checkIfChanged(
|
||||||
const QVector<MTPReactionCount> &list,
|
const QVector<MTPReactionCount> &list,
|
||||||
const QVector<MTPMessagePeerReaction> &recent,
|
const QVector<MTPMessagePeerReaction> &recent,
|
||||||
bool ignoreChosen) const;
|
bool min) const;
|
||||||
[[nodiscard]] const std::vector<MessageReaction> &list() const;
|
[[nodiscard]] const std::vector<MessageReaction> &list() const;
|
||||||
[[nodiscard]] auto recent() const
|
[[nodiscard]] auto recent() const
|
||||||
-> const base::flat_map<ReactionId, std::vector<RecentReaction>> &;
|
-> const base::flat_map<ReactionId, std::vector<RecentReaction>> &;
|
||||||
|
@ -355,11 +385,27 @@ public:
|
||||||
[[nodiscard]] bool hasUnread() const;
|
[[nodiscard]] bool hasUnread() const;
|
||||||
void markRead();
|
void markRead();
|
||||||
|
|
||||||
|
void scheduleSendPaid(int count);
|
||||||
|
[[nodiscard]] int scheduledPaid() const;
|
||||||
|
void cancelScheduledPaid();
|
||||||
|
|
||||||
|
int startPaidSending();
|
||||||
|
void finishPaidSending(int count, bool success);
|
||||||
|
|
||||||
|
[[nodiscard]] int localPaidCount() const;
|
||||||
|
bool clearCloudData();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
struct Paid {
|
||||||
|
std::vector<TopPaid> top;
|
||||||
|
int scheduled = 0;
|
||||||
|
int sending = 0;
|
||||||
|
};
|
||||||
const not_null<HistoryItem*> _item;
|
const not_null<HistoryItem*> _item;
|
||||||
|
|
||||||
std::vector<MessageReaction> _list;
|
std::vector<MessageReaction> _list;
|
||||||
base::flat_map<ReactionId, std::vector<RecentReaction>> _recent;
|
base::flat_map<ReactionId, std::vector<RecentReaction>> _recent;
|
||||||
|
std::unique_ptr<Paid> _paid;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/view/history_view_emoji_interactions.h"
|
#include "history/view/history_view_emoji_interactions.h"
|
||||||
#include "history/history_item_components.h"
|
#include "history/history_item_components.h"
|
||||||
#include "history/history_item_text.h"
|
#include "history/history_item_text.h"
|
||||||
|
#include "payments/payments_reaction_process.h"
|
||||||
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
|
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
|
||||||
#include "ui/widgets/menu/menu_multiline_action.h"
|
#include "ui/widgets/menu/menu_multiline_action.h"
|
||||||
#include "ui/widgets/popup_menu.h"
|
#include "ui/widgets/popup_menu.h"
|
||||||
|
@ -490,13 +491,11 @@ void HistoryInner::reactionChosen(const ChosenReaction &reaction) {
|
||||||
if (!item) {
|
if (!item) {
|
||||||
return;
|
return;
|
||||||
} else if (reaction.id.paid()) {
|
} else if (reaction.id.paid()) {
|
||||||
_controller->show(Ui::MakeConfirmBox({
|
Payments::ShowPaidReactionDetails(
|
||||||
.text = u"Send 10-stars reaction?"_q,
|
_controller->uiShow(),
|
||||||
.confirmed = [=](Fn<void()> close) {
|
item,
|
||||||
item->addPaidReaction(10, HistoryItem::ReactionSource::Selector);
|
viewByItem(item),
|
||||||
close();
|
HistoryReactionSource::Selector);
|
||||||
},
|
|
||||||
}));
|
|
||||||
return;
|
return;
|
||||||
} else if (Window::ShowReactPremiumError(
|
} else if (Window::ShowReactPremiumError(
|
||||||
_controller,
|
_controller,
|
||||||
|
@ -507,7 +506,7 @@ void HistoryInner::reactionChosen(const ChosenReaction &reaction) {
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
item->toggleReaction(reaction.id, HistoryItem::ReactionSource::Selector);
|
item->toggleReaction(reaction.id, HistoryReactionSource::Selector);
|
||||||
if (!ranges::contains(item->chosenReactions(), reaction.id)) {
|
if (!ranges::contains(item->chosenReactions(), reaction.id)) {
|
||||||
return;
|
return;
|
||||||
} else if (const auto view = viewByItem(item)) {
|
} else if (const auto view = viewByItem(item)) {
|
||||||
|
@ -2047,7 +2046,7 @@ void HistoryInner::toggleFavoriteReaction(not_null<Element*> view) const {
|
||||||
view->animateReaction({ .id = favorite });
|
view->animateReaction({ .id = favorite });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
item->toggleReaction(favorite, HistoryItem::ReactionSource::Quick);
|
item->toggleReaction(favorite, HistoryReactionSource::Quick);
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryView::SelectedQuote HistoryInner::selectedQuote(
|
HistoryView::SelectedQuote HistoryInner::selectedQuote(
|
||||||
|
|
|
@ -2514,19 +2514,34 @@ bool HistoryItem::canReact() const {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryItem::addPaidReaction(int count, ReactionSource source) {
|
void HistoryItem::addPaidReaction(int count) {
|
||||||
|
Expects(count > 0);
|
||||||
Expects(_history->peer->isBroadcast());
|
Expects(_history->peer->isBroadcast());
|
||||||
|
|
||||||
if (!_reactions) {
|
if (!_reactions) {
|
||||||
_reactions = std::make_unique<Data::MessageReactions>(this);
|
_reactions = std::make_unique<Data::MessageReactions>(this);
|
||||||
}
|
}
|
||||||
_reactions->addPaid(count);
|
_reactions->scheduleSendPaid(count);
|
||||||
|
_history->owner().notifyItemDataChange(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
int HistoryItem::startPaidReactionSending() {
|
||||||
|
return _reactions ? _reactions->startPaidSending() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HistoryItem::finishPaidReactionSending(int count, bool success) {
|
||||||
|
Expects(_reactions != nullptr);
|
||||||
|
|
||||||
|
_reactions->finishPaidSending(count, success);
|
||||||
_history->owner().notifyItemDataChange(this);
|
_history->owner().notifyItemDataChange(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryItem::toggleReaction(
|
void HistoryItem::toggleReaction(
|
||||||
const Data::ReactionId &reaction,
|
const Data::ReactionId &reaction,
|
||||||
ReactionSource source) {
|
HistoryReactionSource source) {
|
||||||
|
Expects(!reaction.paid());
|
||||||
|
|
||||||
|
const auto addToRecent = (source == HistoryReactionSource::Selector);
|
||||||
if (!_reactions) {
|
if (!_reactions) {
|
||||||
_reactions = std::make_unique<Data::MessageReactions>(this);
|
_reactions = std::make_unique<Data::MessageReactions>(this);
|
||||||
const auto canViewReactions = !isDiscussionPost()
|
const auto canViewReactions = !isDiscussionPost()
|
||||||
|
@ -2534,7 +2549,7 @@ void HistoryItem::toggleReaction(
|
||||||
if (canViewReactions) {
|
if (canViewReactions) {
|
||||||
_flags |= MessageFlag::CanViewReactions;
|
_flags |= MessageFlag::CanViewReactions;
|
||||||
}
|
}
|
||||||
_reactions->add(reaction, (source == ReactionSource::Selector));
|
_reactions->add(reaction, addToRecent);
|
||||||
} else if (ranges::contains(_reactions->chosen(), reaction)) {
|
} else if (ranges::contains(_reactions->chosen(), reaction)) {
|
||||||
_reactions->remove(reaction);
|
_reactions->remove(reaction);
|
||||||
if (_reactions->empty()) {
|
if (_reactions->empty()) {
|
||||||
|
@ -2542,7 +2557,7 @@ void HistoryItem::toggleReaction(
|
||||||
_flags &= ~MessageFlag::CanViewReactions;
|
_flags &= ~MessageFlag::CanViewReactions;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_reactions->add(reaction, (source == ReactionSource::Selector));
|
_reactions->add(reaction, addToRecent);
|
||||||
}
|
}
|
||||||
_history->owner().notifyItemDataChange(this);
|
_history->owner().notifyItemDataChange(this);
|
||||||
}
|
}
|
||||||
|
@ -2556,6 +2571,30 @@ const std::vector<Data::MessageReaction> &HistoryItem::reactions() const {
|
||||||
return _reactions ? _reactions->list() : kEmpty;
|
return _reactions ? _reactions->list() : kEmpty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<Data::MessageReaction> HistoryItem::reactionsWithLocal() const {
|
||||||
|
auto result = reactions();
|
||||||
|
if (const auto local = _reactions ? _reactions->localPaidCount() : 0) {
|
||||||
|
const auto i = ranges::find(
|
||||||
|
result,
|
||||||
|
Data::ReactionId::Paid(),
|
||||||
|
&Data::MessageReaction::id);
|
||||||
|
if (i != end(result)) {
|
||||||
|
i->my = true;
|
||||||
|
i->count += local;
|
||||||
|
if (i != begin(result)) {
|
||||||
|
std::rotate(begin(result), i, i + 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.insert(begin(result), Data::MessageReaction{
|
||||||
|
.id = Data::ReactionId::Paid(),
|
||||||
|
.count = local,
|
||||||
|
.my = true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
bool HistoryItem::reactionsAreTags() const {
|
bool HistoryItem::reactionsAreTags() const {
|
||||||
return _flags & MessageFlag::ReactionsAreTags;
|
return _flags & MessageFlag::ReactionsAreTags;
|
||||||
}
|
}
|
||||||
|
@ -3729,12 +3768,21 @@ bool HistoryItem::changeReactions(const MTPMessageReactions *reactions) {
|
||||||
if (reactions || _reactionsLastRefreshed) {
|
if (reactions || _reactionsLastRefreshed) {
|
||||||
_reactionsLastRefreshed = crl::now();
|
_reactionsLastRefreshed = crl::now();
|
||||||
}
|
}
|
||||||
|
const auto changeToEmpty = [&] {
|
||||||
|
if (!_reactions) {
|
||||||
|
return false;
|
||||||
|
} else if (!_reactions->localPaidCount()) {
|
||||||
|
_reactions = nullptr;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return _reactions->clearCloudData();
|
||||||
|
};
|
||||||
if (!reactions) {
|
if (!reactions) {
|
||||||
_flags &= ~MessageFlag::CanViewReactions;
|
_flags &= ~MessageFlag::CanViewReactions;
|
||||||
if (_history->peer->isSelf()) {
|
if (_history->peer->isSelf()) {
|
||||||
_flags |= MessageFlag::ReactionsAreTags;
|
_flags |= MessageFlag::ReactionsAreTags;
|
||||||
}
|
}
|
||||||
return (base::take(_reactions) != nullptr);
|
return changeToEmpty();
|
||||||
}
|
}
|
||||||
const auto &data = reactions->data();
|
const auto &data = reactions->data();
|
||||||
const auto empty = data.vresults().v.isEmpty();
|
const auto empty = data.vresults().v.isEmpty();
|
||||||
|
@ -3750,13 +3798,14 @@ bool HistoryItem::changeReactions(const MTPMessageReactions *reactions) {
|
||||||
_flags &= ~MessageFlag::CanViewReactions;
|
_flags &= ~MessageFlag::CanViewReactions;
|
||||||
}
|
}
|
||||||
if (empty) {
|
if (empty) {
|
||||||
return (base::take(_reactions) != nullptr);
|
return changeToEmpty();
|
||||||
} else if (!_reactions) {
|
} else if (!_reactions) {
|
||||||
_reactions = std::make_unique<Data::MessageReactions>(this);
|
_reactions = std::make_unique<Data::MessageReactions>(this);
|
||||||
}
|
}
|
||||||
const auto min = data.is_min();
|
const auto min = data.is_min();
|
||||||
const auto &list = data.vresults().v;
|
const auto &list = data.vresults().v;
|
||||||
const auto &recent = data.vrecent_reactions().value_or_empty();
|
const auto &recent = data.vrecent_reactions().value_or_empty();
|
||||||
|
const auto &top = data.vtop_reactors().value_or_empty();
|
||||||
if (min && hasUnreadReaction()) {
|
if (min && hasUnreadReaction()) {
|
||||||
// We can't update reactions from min if we have unread.
|
// We can't update reactions from min if we have unread.
|
||||||
if (_reactions->checkIfChanged(list, recent, min)) {
|
if (_reactions->checkIfChanged(list, recent, min)) {
|
||||||
|
@ -3764,7 +3813,7 @@ bool HistoryItem::changeReactions(const MTPMessageReactions *reactions) {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return _reactions->change(list, recent, min);
|
return _reactions->change(list, recent, top, min);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryItem::applyTTL(const MTPDmessage &data) {
|
void HistoryItem::applyTTL(const MTPDmessage &data) {
|
||||||
|
|
|
@ -107,6 +107,12 @@ struct HistoryItemCommonFields {
|
||||||
HistoryMessageMarkupData markup;
|
HistoryMessageMarkupData markup;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class HistoryReactionSource : char {
|
||||||
|
Selector,
|
||||||
|
Quick,
|
||||||
|
Existing,
|
||||||
|
};
|
||||||
|
|
||||||
class HistoryItem final : public RuntimeComposer<HistoryItem> {
|
class HistoryItem final : public RuntimeComposer<HistoryItem> {
|
||||||
public:
|
public:
|
||||||
[[nodiscard]] static std::unique_ptr<Data::Media> CreateMedia(
|
[[nodiscard]] static std::unique_ptr<Data::Media> CreateMedia(
|
||||||
|
@ -435,18 +441,17 @@ public:
|
||||||
void translationDone(LanguageId to, TextWithEntities result);
|
void translationDone(LanguageId to, TextWithEntities result);
|
||||||
|
|
||||||
[[nodiscard]] bool canReact() const;
|
[[nodiscard]] bool canReact() const;
|
||||||
enum class ReactionSource {
|
|
||||||
Selector,
|
|
||||||
Quick,
|
|
||||||
Existing,
|
|
||||||
};
|
|
||||||
void toggleReaction(
|
void toggleReaction(
|
||||||
const Data::ReactionId &reaction,
|
const Data::ReactionId &reaction,
|
||||||
ReactionSource source);
|
HistoryReactionSource source);
|
||||||
void addPaidReaction(int count, ReactionSource source);
|
void addPaidReaction(int count);
|
||||||
|
[[nodiscard]] int startPaidReactionSending();
|
||||||
|
void finishPaidReactionSending(int count, bool success);
|
||||||
void updateReactionsUnknown();
|
void updateReactionsUnknown();
|
||||||
[[nodiscard]] auto reactions() const
|
[[nodiscard]] auto reactions() const
|
||||||
-> const std::vector<Data::MessageReaction> &;
|
-> const std::vector<Data::MessageReaction> &;
|
||||||
|
[[nodiscard]] auto reactionsWithLocal() const
|
||||||
|
-> std::vector<Data::MessageReaction>;
|
||||||
[[nodiscard]] auto recentReactions() const
|
[[nodiscard]] auto recentReactions() const
|
||||||
-> const base::flat_map<
|
-> const base::flat_map<
|
||||||
Data::ReactionId,
|
Data::ReactionId,
|
||||||
|
|
|
@ -1549,9 +1549,7 @@ void ShowTagMenu(
|
||||||
if (const auto item = owner->message(itemId)) {
|
if (const auto item = owner->message(itemId)) {
|
||||||
const auto &list = item->reactions();
|
const auto &list = item->reactions();
|
||||||
if (ranges::contains(list, id, &MessageReaction::id)) {
|
if (ranges::contains(list, id, &MessageReaction::id)) {
|
||||||
item->toggleReaction(
|
item->toggleReaction(id, HistoryReactionSource::Quick);
|
||||||
id,
|
|
||||||
HistoryItem::ReactionSource::Quick);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -2650,7 +2650,7 @@ void ListWidget::toggleFavoriteReaction(not_null<Element*> view) const {
|
||||||
view->animateReaction({ .id = favorite });
|
view->animateReaction({ .id = favorite });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
item->toggleReaction(favorite, HistoryItem::ReactionSource::Quick);
|
item->toggleReaction(favorite, HistoryReactionSource::Quick);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ListWidget::trySwitchToWordSelection() {
|
void ListWidget::trySwitchToWordSelection() {
|
||||||
|
@ -2807,9 +2807,7 @@ void ListWidget::reactionChosen(ChosenReaction reaction) {
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
item->toggleReaction(
|
item->toggleReaction(reaction.id, HistoryReactionSource::Selector);
|
||||||
reaction.id,
|
|
||||||
HistoryItem::ReactionSource::Selector);
|
|
||||||
if (!ranges::contains(item->chosenReactions(), reaction.id)) {
|
if (!ranges::contains(item->chosenReactions(), reaction.id)) {
|
||||||
return;
|
return;
|
||||||
} else if (const auto view = viewForItem(item)) {
|
} else if (const auto view = viewForItem(item)) {
|
||||||
|
|
|
@ -37,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "mainwidget.h"
|
#include "mainwidget.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
|
#include "payments/payments_reaction_process.h" // TryAddingPaidReaction.
|
||||||
#include "ui/text/text_options.h"
|
#include "ui/text/text_options.h"
|
||||||
#include "ui/painter.h"
|
#include "ui/painter.h"
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
|
@ -808,11 +809,7 @@ QSize Message::performCountOptimalSize() {
|
||||||
|
|
||||||
const auto markup = item->inlineReplyMarkup();
|
const auto markup = item->inlineReplyMarkup();
|
||||||
const auto reactionsKey = [&] {
|
const auto reactionsKey = [&] {
|
||||||
return embedReactionsInBottomInfo()
|
return embedReactionsInBubble() ? 0 : 1;
|
||||||
? 0
|
|
||||||
: embedReactionsInBubble()
|
|
||||||
? 1
|
|
||||||
: 2;
|
|
||||||
};
|
};
|
||||||
const auto oldKey = reactionsKey();
|
const auto oldKey = reactionsKey();
|
||||||
validateText();
|
validateText();
|
||||||
|
@ -3248,97 +3245,62 @@ bool Message::isSignedAuthorElided() const {
|
||||||
return _bottomInfo.isSignedAuthorElided();
|
return _bottomInfo.isSignedAuthorElided();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Message::embedReactionsInBottomInfo() const {
|
|
||||||
return false;
|
|
||||||
#if 0 // legacy
|
|
||||||
const auto item = data();
|
|
||||||
const auto user = item->history()->peer->asUser();
|
|
||||||
if (!user
|
|
||||||
|| user->isPremium()
|
|
||||||
|| user->isSelf()
|
|
||||||
|| user->session().premium()) {
|
|
||||||
// Only in messages of a non premium user with a non premium user.
|
|
||||||
// In saved messages we use reactions for tags, we don't embed them.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
auto seenMy = false;
|
|
||||||
auto seenHis = false;
|
|
||||||
for (const auto &reaction : item->reactions()) {
|
|
||||||
if (reaction.id.custom()) {
|
|
||||||
// Only in messages without any custom emoji reactions.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Only in messages without two reactions from the same person.
|
|
||||||
if (reaction.my) {
|
|
||||||
if (seenMy) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
seenMy = true;
|
|
||||||
}
|
|
||||||
if (!reaction.my || (reaction.count > 1)) {
|
|
||||||
if (seenHis) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
seenHis = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Message::embedReactionsInBubble() const {
|
bool Message::embedReactionsInBubble() const {
|
||||||
return needInfoDisplay();
|
return needInfoDisplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Message::refreshReactions() {
|
void Message::refreshReactions() {
|
||||||
const auto item = data();
|
using namespace Reactions;
|
||||||
const auto &list = item->reactions();
|
auto reactionsData = InlineListDataFromMessage(this);
|
||||||
if (list.empty() || embedReactionsInBottomInfo()) {
|
if (reactionsData.reactions.empty()) {
|
||||||
setReactions(nullptr);
|
setReactions(nullptr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
using namespace Reactions;
|
|
||||||
auto reactionsData = InlineListDataFromMessage(this);
|
|
||||||
if (!_reactions) {
|
if (!_reactions) {
|
||||||
const auto handlerFactory = [=](ReactionId id) {
|
const auto handlerFactory = [=](ReactionId id) {
|
||||||
const auto weak = base::make_weak(this);
|
const auto weak = base::make_weak(this);
|
||||||
return std::make_shared<LambdaClickHandler>([=](
|
return std::make_shared<LambdaClickHandler>([=](
|
||||||
ClickContext context) {
|
ClickContext context) {
|
||||||
if (const auto strong = weak.get()) {
|
const auto strong = weak.get();
|
||||||
const auto item = strong->data();
|
if (!strong) {
|
||||||
if (id.paid()) {
|
return;
|
||||||
item->addPaidReaction(
|
}
|
||||||
1,
|
const auto item = strong->data();
|
||||||
HistoryItem::ReactionSource::Existing);
|
const auto controller = ExtractController(context);
|
||||||
return;
|
if (item->reactionsAreTags()) {
|
||||||
} else if (item->reactionsAreTags()) {
|
if (item->history()->session().premium()) {
|
||||||
if (item->history()->session().premium()) {
|
const auto tag = Data::SearchTagToQuery(id);
|
||||||
const auto tag = Data::SearchTagToQuery(id);
|
HashtagClickHandler(tag).onClick(context);
|
||||||
HashtagClickHandler(tag).onClick(context);
|
} else if (controller) {
|
||||||
} else if (const auto controller
|
ShowPremiumPreviewBox(
|
||||||
= ExtractController(context)) {
|
controller,
|
||||||
ShowPremiumPreviewBox(
|
PremiumFeature::TagsForMessages);
|
||||||
controller,
|
|
||||||
PremiumFeature::TagsForMessages);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
item->toggleReaction(
|
return;
|
||||||
id,
|
}
|
||||||
HistoryItem::ReactionSource::Existing);
|
if (id.paid()) {
|
||||||
if (const auto now = weak.get()) {
|
Payments::TryAddingPaidReaction(
|
||||||
const auto chosen = now->data()->chosenReactions();
|
item,
|
||||||
if (ranges::contains(chosen, id)) {
|
weak.get(),
|
||||||
now->animateReaction({
|
1,
|
||||||
.id = id,
|
controller->uiShow());
|
||||||
});
|
return;
|
||||||
}
|
} else {
|
||||||
|
const auto source = HistoryReactionSource::Existing;
|
||||||
|
item->toggleReaction(id, source);
|
||||||
|
}
|
||||||
|
if (const auto now = weak.get()) {
|
||||||
|
const auto chosen = now->data()->chosenReactions();
|
||||||
|
if (id.paid() || ranges::contains(chosen, id)) {
|
||||||
|
now->animateReaction({
|
||||||
|
.id = id,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
setReactions(std::make_unique<InlineList>(
|
setReactions(std::make_unique<InlineList>(
|
||||||
&item->history()->owner().reactions(),
|
&history()->owner().reactions(),
|
||||||
handlerFactory,
|
handlerFactory,
|
||||||
[=] { customEmojiRepaint(); },
|
[=] { customEmojiRepaint(); },
|
||||||
std::move(reactionsData)));
|
std::move(reactionsData)));
|
||||||
|
|
|
@ -77,7 +77,6 @@ public:
|
||||||
[[nodiscard]] const HistoryMessageEdited *displayedEditBadge() const;
|
[[nodiscard]] const HistoryMessageEdited *displayedEditBadge() const;
|
||||||
[[nodiscard]] HistoryMessageEdited *displayedEditBadge();
|
[[nodiscard]] HistoryMessageEdited *displayedEditBadge();
|
||||||
|
|
||||||
[[nodiscard]] bool embedReactionsInBottomInfo() const;
|
|
||||||
[[nodiscard]] bool embedReactionsInBubble() const;
|
[[nodiscard]] bool embedReactionsInBubble() const;
|
||||||
|
|
||||||
int marginTop() const override;
|
int marginTop() const override;
|
||||||
|
|
|
@ -791,7 +791,7 @@ InlineListData InlineListDataFromMessage(not_null<Message*> message) {
|
||||||
using Flag = InlineListData::Flag;
|
using Flag = InlineListData::Flag;
|
||||||
const auto item = message->data();
|
const auto item = message->data();
|
||||||
auto result = InlineListData();
|
auto result = InlineListData();
|
||||||
result.reactions = item->reactions();
|
result.reactions = item->reactionsWithLocal();
|
||||||
if (const auto user = item->history()->peer->asUser()) {
|
if (const auto user = item->history()->peer->asUser()) {
|
||||||
// Always show userpics, we have all information.
|
// Always show userpics, we have all information.
|
||||||
result.recent.reserve(result.reactions.size());
|
result.recent.reserve(result.reactions.size());
|
||||||
|
|
|
@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "storage/file_upload.h"
|
#include "storage/file_upload.h"
|
||||||
#include "storage/storage_account.h"
|
#include "storage/storage_account.h"
|
||||||
#include "storage/storage_facade.h"
|
#include "storage/storage_facade.h"
|
||||||
|
#include "data/components/credits.h"
|
||||||
#include "data/components/factchecks.h"
|
#include "data/components/factchecks.h"
|
||||||
#include "data/components/location_pickers.h"
|
#include "data/components/location_pickers.h"
|
||||||
#include "data/components/recent_peers.h"
|
#include "data/components/recent_peers.h"
|
||||||
|
@ -115,6 +116,7 @@ Session::Session(
|
||||||
std::make_unique<Data::TopPeers>(this, Data::TopPeerType::BotApp))
|
std::make_unique<Data::TopPeers>(this, Data::TopPeerType::BotApp))
|
||||||
, _factchecks(std::make_unique<Data::Factchecks>(this))
|
, _factchecks(std::make_unique<Data::Factchecks>(this))
|
||||||
, _locationPickers(std::make_unique<Data::LocationPickers>())
|
, _locationPickers(std::make_unique<Data::LocationPickers>())
|
||||||
|
, _credits(std::make_unique<Data::Credits>(this))
|
||||||
, _cachedReactionIconFactory(std::make_unique<ReactionIconFactory>())
|
, _cachedReactionIconFactory(std::make_unique<ReactionIconFactory>())
|
||||||
, _supportHelper(Support::Helper::Create(this))
|
, _supportHelper(Support::Helper::Create(this))
|
||||||
, _saveSettingsTimer([=] { saveSettings(); }) {
|
, _saveSettingsTimer([=] { saveSettings(); }) {
|
||||||
|
@ -290,14 +292,6 @@ bool Session::premiumCanBuy() const {
|
||||||
return _premiumPossible.current();
|
return _premiumPossible.current();
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<uint64> Session::creditsValue() const {
|
|
||||||
return _credits.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Session::setCredits(uint64 credits) {
|
|
||||||
_credits = credits;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Session::isTestMode() const {
|
bool Session::isTestMode() const {
|
||||||
return mtp().isTestMode();
|
return mtp().isTestMode();
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ class SponsoredMessages;
|
||||||
class TopPeers;
|
class TopPeers;
|
||||||
class Factchecks;
|
class Factchecks;
|
||||||
class LocationPickers;
|
class LocationPickers;
|
||||||
|
class Credits;
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
||||||
namespace HistoryView::Reactions {
|
namespace HistoryView::Reactions {
|
||||||
|
@ -102,9 +103,6 @@ public:
|
||||||
[[nodiscard]] bool premiumBadgesShown() const;
|
[[nodiscard]] bool premiumBadgesShown() const;
|
||||||
[[nodiscard]] bool premiumCanBuy() const;
|
[[nodiscard]] bool premiumCanBuy() const;
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<uint64> creditsValue() const;
|
|
||||||
void setCredits(uint64 credits);
|
|
||||||
|
|
||||||
[[nodiscard]] bool isTestMode() const;
|
[[nodiscard]] bool isTestMode() const;
|
||||||
[[nodiscard]] uint64 uniqueId() const; // userId() with TestDC shift.
|
[[nodiscard]] uint64 uniqueId() const; // userId() with TestDC shift.
|
||||||
[[nodiscard]] UserId userId() const;
|
[[nodiscard]] UserId userId() const;
|
||||||
|
@ -138,6 +136,9 @@ public:
|
||||||
[[nodiscard]] Data::LocationPickers &locationPickers() const {
|
[[nodiscard]] Data::LocationPickers &locationPickers() const {
|
||||||
return *_locationPickers;
|
return *_locationPickers;
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] Data::Credits &credits() const {
|
||||||
|
return *_credits;
|
||||||
|
}
|
||||||
[[nodiscard]] Api::Updates &updates() const {
|
[[nodiscard]] Api::Updates &updates() const {
|
||||||
return *_updates;
|
return *_updates;
|
||||||
}
|
}
|
||||||
|
@ -268,6 +269,7 @@ private:
|
||||||
const std::unique_ptr<Data::TopPeers> _topBotApps;
|
const std::unique_ptr<Data::TopPeers> _topBotApps;
|
||||||
const std::unique_ptr<Data::Factchecks> _factchecks;
|
const std::unique_ptr<Data::Factchecks> _factchecks;
|
||||||
const std::unique_ptr<Data::LocationPickers> _locationPickers;
|
const std::unique_ptr<Data::LocationPickers> _locationPickers;
|
||||||
|
const std::unique_ptr<Data::Credits> _credits;
|
||||||
|
|
||||||
using ReactionIconFactory = HistoryView::Reactions::CachedIconFactory;
|
using ReactionIconFactory = HistoryView::Reactions::CachedIconFactory;
|
||||||
const std::unique_ptr<ReactionIconFactory> _cachedReactionIconFactory;
|
const std::unique_ptr<ReactionIconFactory> _cachedReactionIconFactory;
|
||||||
|
@ -275,7 +277,6 @@ private:
|
||||||
const std::unique_ptr<Support::Helper> _supportHelper;
|
const std::unique_ptr<Support::Helper> _supportHelper;
|
||||||
|
|
||||||
std::shared_ptr<QImage> _selfUserpicView;
|
std::shared_ptr<QImage> _selfUserpicView;
|
||||||
rpl::variable<uint64> _credits = 0;
|
|
||||||
rpl::variable<bool> _premiumPossible = false;
|
rpl::variable<bool> _premiumPossible = false;
|
||||||
|
|
||||||
rpl::event_stream<bool> _termsLockChanges;
|
rpl::event_stream<bool> _termsLockChanges;
|
||||||
|
|
|
@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "api/api_credits.h"
|
#include "api/api_credits.h"
|
||||||
#include "base/unixtime.h"
|
#include "base/unixtime.h"
|
||||||
#include "boxes/send_credits_box.h"
|
#include "boxes/send_credits_box.h"
|
||||||
|
#include "data/components/credits.h"
|
||||||
#include "data/data_credits.h"
|
#include "data/data_credits.h"
|
||||||
#include "data/data_photo.h"
|
#include "data/data_photo.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
|
@ -39,27 +40,35 @@ bool IsCreditsInvoice(not_null<HistoryItem*> item) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProcessCreditsPayment(
|
void ProcessCreditsPayment(
|
||||||
std::shared_ptr<Main::SessionShow> show,
|
std::shared_ptr<Main::SessionShow> show,
|
||||||
QPointer<QWidget> fireworks,
|
QPointer<QWidget> fireworks,
|
||||||
std::shared_ptr<CreditsFormData> form,
|
std::shared_ptr<CreditsFormData> form,
|
||||||
Fn<void(CheckoutResult)> maybeReturnToBot) {
|
Fn<void(CheckoutResult)> maybeReturnToBot) {
|
||||||
const auto lifetime = std::make_shared<rpl::lifetime>();
|
const auto done = [=](Settings::SmallBalanceResult result) {
|
||||||
const auto api = lifetime->make_state<Api::CreditsStatus>(
|
if (result == Settings::SmallBalanceResult::Blocked) {
|
||||||
show->session().user());
|
if (const auto onstack = maybeReturnToBot) {
|
||||||
const auto sendBox = [=] {
|
onstack(CheckoutResult::Failed);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else if (result == Settings::SmallBalanceResult::Cancelled) {
|
||||||
|
if (const auto onstack = maybeReturnToBot) {
|
||||||
|
onstack(CheckoutResult::Cancelled);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
const auto unsuccessful = std::make_shared<bool>(true);
|
const auto unsuccessful = std::make_shared<bool>(true);
|
||||||
const auto box = show->show(Box(
|
const auto box = show->show(Box(
|
||||||
Ui::SendCreditsBox,
|
Ui::SendCreditsBox,
|
||||||
form,
|
form,
|
||||||
[=] {
|
[=] {
|
||||||
*unsuccessful = false;
|
*unsuccessful = false;
|
||||||
if (const auto widget = fireworks.data()) {
|
if (const auto widget = fireworks.data()) {
|
||||||
Ui::StartFireworks(widget);
|
Ui::StartFireworks(widget);
|
||||||
}
|
}
|
||||||
if (maybeReturnToBot) {
|
if (maybeReturnToBot) {
|
||||||
maybeReturnToBot(CheckoutResult::Paid);
|
maybeReturnToBot(CheckoutResult::Paid);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
box->boxClosing() | rpl::start_with_next([=] {
|
box->boxClosing() | rpl::start_with_next([=] {
|
||||||
crl::on_main([=] {
|
crl::on_main([=] {
|
||||||
if ((*unsuccessful) && maybeReturnToBot) {
|
if ((*unsuccessful) && maybeReturnToBot) {
|
||||||
|
@ -68,28 +77,11 @@ void ProcessCreditsPayment(
|
||||||
});
|
});
|
||||||
}, box->lifetime());
|
}, box->lifetime());
|
||||||
};
|
};
|
||||||
api->request({}, [=](Data::CreditsStatusSlice slice) {
|
Settings::MaybeRequestBalanceIncrease(
|
||||||
show->session().setCredits(slice.balance);
|
show,
|
||||||
const auto creditsNeeded = int64(form->invoice.credits)
|
form->invoice.credits,
|
||||||
- int64(slice.balance);
|
Settings::SmallBalanceBot{ .botId = form->botId },
|
||||||
if (creditsNeeded <= 0) {
|
done);
|
||||||
sendBox();
|
|
||||||
} else if (show->session().premiumPossible()) {
|
|
||||||
show->show(Box(
|
|
||||||
Settings::SmallBalanceBox,
|
|
||||||
show,
|
|
||||||
creditsNeeded,
|
|
||||||
form->botId,
|
|
||||||
sendBox));
|
|
||||||
} else {
|
|
||||||
show->showToast(
|
|
||||||
tr::lng_credits_purchase_blocked(tr::now));
|
|
||||||
if (maybeReturnToBot) {
|
|
||||||
maybeReturnToBot(CheckoutResult::Failed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lifetime->destroy();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProcessCreditsReceipt(
|
void ProcessCreditsReceipt(
|
||||||
|
|
190
Telegram/SourceFiles/payments/payments_reaction_process.cpp
Normal file
190
Telegram/SourceFiles/payments/payments_reaction_process.cpp
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
/*
|
||||||
|
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 "payments/payments_reaction_process.h"
|
||||||
|
|
||||||
|
#include "api/api_credits.h"
|
||||||
|
#include "boxes/send_credits_box.h" // CreditsEmojiSmall.
|
||||||
|
#include "core/ui_integration.h" // MarkedTextContext.
|
||||||
|
#include "data/components/credits.h"
|
||||||
|
#include "data/data_session.h"
|
||||||
|
#include "data/data_user.h"
|
||||||
|
#include "history/view/history_view_element.h"
|
||||||
|
#include "history/history.h"
|
||||||
|
#include "history/history_item.h"
|
||||||
|
#include "lang/lang_keys.h"
|
||||||
|
#include "main/session/session_show.h"
|
||||||
|
#include "main/main_app_config.h"
|
||||||
|
#include "main/main_session.h"
|
||||||
|
#include "payments/ui/payments_reaction_box.h"
|
||||||
|
#include "settings/settings_credits_graphics.h"
|
||||||
|
#include "ui/effects/reaction_fly_animation.h"
|
||||||
|
#include "ui/layers/box_content.h"
|
||||||
|
#include "ui/layers/generic_box.h"
|
||||||
|
#include "ui/layers/show.h"
|
||||||
|
#include "ui/text/text_utilities.h"
|
||||||
|
|
||||||
|
namespace Payments {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kMaxPerReactionFallback = 2'500;
|
||||||
|
constexpr auto kDefaultPerReaction = 20;
|
||||||
|
|
||||||
|
void TryAddingPaidReaction(
|
||||||
|
not_null<Main::Session*> session,
|
||||||
|
FullMsgId itemId,
|
||||||
|
base::weak_ptr<HistoryView::Element> weakView,
|
||||||
|
int count,
|
||||||
|
std::shared_ptr<Ui::Show> show,
|
||||||
|
Fn<void(bool)> finished) {
|
||||||
|
const auto checkItem = [=] {
|
||||||
|
const auto item = session->data().message(itemId);
|
||||||
|
if (!item) {
|
||||||
|
if (const auto onstack = finished) {
|
||||||
|
onstack(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto item = checkItem();
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto done = [=](Settings::SmallBalanceResult result) {
|
||||||
|
if (result == Settings::SmallBalanceResult::Success) {
|
||||||
|
if (const auto item = checkItem()) {
|
||||||
|
item->addPaidReaction(count);
|
||||||
|
if (const auto view = weakView.get()) {
|
||||||
|
view->animateReaction({
|
||||||
|
.id = Data::ReactionId::Paid(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (const auto onstack = finished) {
|
||||||
|
onstack(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (const auto onstack = finished) {
|
||||||
|
onstack(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const auto channelId = peerToChannel(itemId.peer);
|
||||||
|
Settings::MaybeRequestBalanceIncrease(
|
||||||
|
Main::MakeSessionShow(show, session),
|
||||||
|
count,
|
||||||
|
Settings::SmallBalanceReaction{ .channelId = channelId },
|
||||||
|
done);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void TryAddingPaidReaction(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
|
HistoryView::Element *view,
|
||||||
|
int count,
|
||||||
|
std::shared_ptr<Ui::Show> show,
|
||||||
|
Fn<void(bool)> finished) {
|
||||||
|
TryAddingPaidReaction(
|
||||||
|
&item->history()->session(),
|
||||||
|
item->fullId(),
|
||||||
|
view,
|
||||||
|
count,
|
||||||
|
std::move(show),
|
||||||
|
std::move(finished));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShowPaidReactionDetails(
|
||||||
|
std::shared_ptr<Ui::Show> show,
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
|
HistoryView::Element *view,
|
||||||
|
HistoryReactionSource source) {
|
||||||
|
Expects(item->history()->peer->isBroadcast());
|
||||||
|
|
||||||
|
const auto itemId = item->fullId();
|
||||||
|
const auto session = &item->history()->session();
|
||||||
|
const auto appConfig = &session->appConfig();
|
||||||
|
|
||||||
|
const auto min = 1;
|
||||||
|
const auto max = std::max(
|
||||||
|
appConfig->get<int>(
|
||||||
|
u"stars_paid_reaction_amount_max"_q,
|
||||||
|
kMaxPerReactionFallback),
|
||||||
|
min);
|
||||||
|
const auto chosen = std::clamp(kDefaultPerReaction, min, max);
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
QPointer<Ui::BoxContent> selectBox;
|
||||||
|
bool sending = false;
|
||||||
|
};
|
||||||
|
const auto state = std::make_shared<State>();
|
||||||
|
session->credits().load(true);
|
||||||
|
|
||||||
|
const auto weakView = base::make_weak(view);
|
||||||
|
const auto send = [=](int count, auto resend) -> void {
|
||||||
|
Expects(count > 0);
|
||||||
|
|
||||||
|
const auto finish = [=](bool success) {
|
||||||
|
state->sending = false;
|
||||||
|
if (success) {
|
||||||
|
if (const auto strong = state->selectBox.data()) {
|
||||||
|
strong->closeBox();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (state->sending) {
|
||||||
|
return;
|
||||||
|
} else if (const auto item = session->data().message(itemId)) {
|
||||||
|
state->sending = true;
|
||||||
|
TryAddingPaidReaction(
|
||||||
|
item,
|
||||||
|
weakView.get(),
|
||||||
|
count,
|
||||||
|
show,
|
||||||
|
finish);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto submitText = [=](rpl::producer<int> amount) {
|
||||||
|
auto nice = std::move(amount) | rpl::map([=](int count) {
|
||||||
|
return Ui::CreditsEmojiSmall(session).append(
|
||||||
|
Lang::FormatCountDecimal(count));
|
||||||
|
});
|
||||||
|
return tr::lng_paid_react_send(
|
||||||
|
lt_price,
|
||||||
|
std::move(nice),
|
||||||
|
Ui::Text::RichLangValue
|
||||||
|
) | rpl::map([=](TextWithEntities &&text) {
|
||||||
|
return Ui::TextWithContext{
|
||||||
|
.text = std::move(text),
|
||||||
|
.context = Core::MarkedTextContext{
|
||||||
|
.session = session,
|
||||||
|
.customEmojiRepaint = [] {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
state->selectBox = show->show(Ui::MakePaidReactionBox({
|
||||||
|
.min = min,
|
||||||
|
.max = max,
|
||||||
|
.chosen = chosen,
|
||||||
|
.channel = item->history()->peer->name(),
|
||||||
|
.submit = std::move(submitText),
|
||||||
|
.balanceValue = session->credits().balanceValue(),
|
||||||
|
.send = [=](int count) { send(count, send); },
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (const auto strong = state->selectBox.data()) {
|
||||||
|
session->data().itemRemoved(
|
||||||
|
) | rpl::start_with_next([=](not_null<const HistoryItem*> removed) {
|
||||||
|
if (removed == item) {
|
||||||
|
strong->closeBox();
|
||||||
|
}
|
||||||
|
}, strong->lifetime());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Payments
|
37
Telegram/SourceFiles/payments/payments_reaction_process.h
Normal file
37
Telegram/SourceFiles/payments/payments_reaction_process.h
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
|
||||||
|
enum class HistoryReactionSource : char;
|
||||||
|
|
||||||
|
class HistoryItem;
|
||||||
|
|
||||||
|
namespace HistoryView {
|
||||||
|
class Element;
|
||||||
|
} // namespace HistoryView
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class Show;
|
||||||
|
} // namespace Ui
|
||||||
|
|
||||||
|
namespace Payments {
|
||||||
|
|
||||||
|
void TryAddingPaidReaction(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
|
HistoryView::Element *view,
|
||||||
|
int count,
|
||||||
|
std::shared_ptr<Ui::Show> show,
|
||||||
|
Fn<void(bool)> finished = nullptr);
|
||||||
|
|
||||||
|
void ShowPaidReactionDetails(
|
||||||
|
std::shared_ptr<Ui::Show> show,
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
|
HistoryView::Element *view,
|
||||||
|
HistoryReactionSource source);
|
||||||
|
|
||||||
|
} // namespace Payments
|
148
Telegram/SourceFiles/payments/ui/payments_reaction_box.cpp
Normal file
148
Telegram/SourceFiles/payments/ui/payments_reaction_box.cpp
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
/*
|
||||||
|
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 "payments/ui/payments_reaction_box.h"
|
||||||
|
|
||||||
|
#include "lang/lang_keys.h"
|
||||||
|
#include "ui/layers/generic_box.h"
|
||||||
|
#include "ui/text/text_utilities.h"
|
||||||
|
#include "ui/widgets/buttons.h"
|
||||||
|
#include "ui/widgets/continuous_sliders.h"
|
||||||
|
#include "styles/style_credits.h"
|
||||||
|
#include "styles/style_layers.h"
|
||||||
|
#include "styles/style_premium.h"
|
||||||
|
#include "styles/style_settings.h"
|
||||||
|
|
||||||
|
namespace Settings {
|
||||||
|
[[nodiscard]] not_null<Ui::RpWidget*> AddBalanceWidget(
|
||||||
|
not_null<Ui::RpWidget*> parent,
|
||||||
|
rpl::producer<uint64> balanceValue,
|
||||||
|
bool rightAlign);
|
||||||
|
} // namespace Settings
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
void PaidReactionSlider(
|
||||||
|
not_null<VerticalLayout*> container,
|
||||||
|
int min,
|
||||||
|
int current,
|
||||||
|
int max,
|
||||||
|
Fn<void(int)> changed) {
|
||||||
|
const auto top = st::boxTitleClose.height + st::creditsHistoryRightSkip;
|
||||||
|
const auto slider = container->add(
|
||||||
|
object_ptr<MediaSlider>(container, st::settingsScale),
|
||||||
|
st::boxRowPadding + QMargins(0, top, 0, 0));
|
||||||
|
slider->resize(slider->width(), st::settingsScale.seekSize.height());
|
||||||
|
slider->setPseudoDiscrete(
|
||||||
|
max + 1 - min,
|
||||||
|
[=](int index) { return min + index; },
|
||||||
|
current - min,
|
||||||
|
changed,
|
||||||
|
changed);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void PaidReactionsBox(
|
||||||
|
not_null<GenericBox*> box,
|
||||||
|
PaidReactionBoxArgs &&args) {
|
||||||
|
box->setWidth(st::boxWideWidth);
|
||||||
|
box->setStyle(st::boostBox);
|
||||||
|
box->setNoContentMargin(true);
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
rpl::variable<int> chosen;
|
||||||
|
};
|
||||||
|
const auto state = box->lifetime().make_state<State>();
|
||||||
|
state->chosen = args.chosen;
|
||||||
|
const auto changed = [=](int count) {
|
||||||
|
state->chosen = count;
|
||||||
|
};
|
||||||
|
PaidReactionSlider(
|
||||||
|
box->verticalLayout(),
|
||||||
|
args.min,
|
||||||
|
args.chosen,
|
||||||
|
args.max,
|
||||||
|
changed);
|
||||||
|
|
||||||
|
box->addTopButton(st::boxTitleClose, [=] { box->closeBox(); });
|
||||||
|
|
||||||
|
box->addRow(
|
||||||
|
object_ptr<Ui::FlatLabel>(
|
||||||
|
box,
|
||||||
|
tr::lng_paid_react_title(),
|
||||||
|
st::boostCenteredTitle),
|
||||||
|
st::boxRowPadding + QMargins(0, st::boostTitleSkip, 0, 0));
|
||||||
|
box->addRow(
|
||||||
|
object_ptr<Ui::FlatLabel>(
|
||||||
|
box,
|
||||||
|
tr::lng_paid_react_about(
|
||||||
|
lt_channel,
|
||||||
|
rpl::single(Text::Bold(args.channel)),
|
||||||
|
Text::RichLangValue),
|
||||||
|
st::boostText),
|
||||||
|
(st::boxRowPadding
|
||||||
|
+ QMargins(0, st::boostTextSkip, 0, st::boostBottomSkip)));
|
||||||
|
|
||||||
|
const auto button = box->addButton(rpl::single(QString()), [=] {
|
||||||
|
args.send(state->chosen.current());
|
||||||
|
});
|
||||||
|
{
|
||||||
|
const auto buttonLabel = Ui::CreateChild<Ui::FlatLabel>(
|
||||||
|
button,
|
||||||
|
rpl::single(QString()),
|
||||||
|
st::creditsBoxButtonLabel);
|
||||||
|
args.submit(
|
||||||
|
state->chosen.value()
|
||||||
|
) | rpl::start_with_next([=](const TextWithContext &text) {
|
||||||
|
buttonLabel->setMarkedText(
|
||||||
|
text.text,
|
||||||
|
text.context);
|
||||||
|
}, buttonLabel->lifetime());
|
||||||
|
buttonLabel->setTextColorOverride(
|
||||||
|
box->getDelegate()->style().button.textFg->c);
|
||||||
|
button->sizeValue(
|
||||||
|
) | rpl::start_with_next([=](const QSize &size) {
|
||||||
|
buttonLabel->moveToLeft(
|
||||||
|
(size.width() - buttonLabel->width()) / 2,
|
||||||
|
(size.height() - buttonLabel->height()) / 2);
|
||||||
|
}, buttonLabel->lifetime());
|
||||||
|
buttonLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
|
}
|
||||||
|
|
||||||
|
box->widthValue(
|
||||||
|
) | rpl::start_with_next([=](int width) {
|
||||||
|
const auto &padding = st::boostBox.buttonPadding;
|
||||||
|
button->resizeToWidth(width
|
||||||
|
- padding.left()
|
||||||
|
- padding.right());
|
||||||
|
button->moveToLeft(padding.left(), button->y());
|
||||||
|
}, button->lifetime());
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto balance = Settings::AddBalanceWidget(
|
||||||
|
box->verticalLayout(),
|
||||||
|
std::move(args.balanceValue),
|
||||||
|
false);
|
||||||
|
rpl::combine(
|
||||||
|
balance->sizeValue(),
|
||||||
|
box->widthValue()
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
balance->moveToLeft(
|
||||||
|
st::creditsHistoryRightSkip * 2,
|
||||||
|
st::creditsHistoryRightSkip);
|
||||||
|
balance->update();
|
||||||
|
}, balance->lifetime());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object_ptr<BoxContent> MakePaidReactionBox(PaidReactionBoxArgs &&args) {
|
||||||
|
return Box(PaidReactionsBox, std::move(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Ui
|
40
Telegram/SourceFiles/payments/ui/payments_reaction_box.h
Normal file
40
Telegram/SourceFiles/payments/ui/payments_reaction_box.h
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
|
||||||
|
#include "base/object_ptr.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
|
||||||
|
class BoxContent;
|
||||||
|
class GenericBox;
|
||||||
|
|
||||||
|
struct TextWithContext {
|
||||||
|
TextWithEntities text;
|
||||||
|
std::any context;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PaidReactionBoxArgs {
|
||||||
|
int min = 0;
|
||||||
|
int max = 0;
|
||||||
|
int chosen = 0;
|
||||||
|
|
||||||
|
QString channel;
|
||||||
|
Fn<rpl::producer<TextWithContext>(rpl::producer<int> amount)> submit;
|
||||||
|
rpl::producer<uint64> balanceValue;
|
||||||
|
Fn<void(int)> send;
|
||||||
|
};
|
||||||
|
|
||||||
|
void PaidReactionsBox(
|
||||||
|
not_null<GenericBox*> box,
|
||||||
|
PaidReactionBoxArgs &&args);
|
||||||
|
|
||||||
|
[[nodiscard]] object_ptr<BoxContent> MakePaidReactionBox(
|
||||||
|
PaidReactionBoxArgs &&args);
|
||||||
|
|
||||||
|
} // namespace Ui
|
|
@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "boxes/gift_credits_box.h"
|
#include "boxes/gift_credits_box.h"
|
||||||
#include "boxes/gift_premium_box.h"
|
#include "boxes/gift_premium_box.h"
|
||||||
#include "core/click_handler_types.h"
|
#include "core/click_handler_types.h"
|
||||||
|
#include "data/components/credits.h"
|
||||||
#include "data/data_file_origin.h"
|
#include "data/data_file_origin.h"
|
||||||
#include "data/data_photo_media.h"
|
#include "data/data_photo_media.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
|
@ -360,13 +361,9 @@ QPointer<Ui::RpWidget> Credits::createPinnedToTop(
|
||||||
{
|
{
|
||||||
const auto balance = AddBalanceWidget(
|
const auto balance = AddBalanceWidget(
|
||||||
content,
|
content,
|
||||||
_controller->session().creditsValue(),
|
_controller->session().credits().balanceValue(),
|
||||||
true);
|
true);
|
||||||
const auto api = balance->lifetime().make_state<Api::CreditsStatus>(
|
_controller->session().credits().load(true);
|
||||||
_controller->session().user());
|
|
||||||
api->request({}, [=](Data::CreditsStatusSlice slice) {
|
|
||||||
_controller->session().setCredits(slice.balance);
|
|
||||||
});
|
|
||||||
rpl::combine(
|
rpl::combine(
|
||||||
balance->sizeValue(),
|
balance->sizeValue(),
|
||||||
content->sizeValue()
|
content->sizeValue()
|
||||||
|
|
|
@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "core/click_handler_types.h"
|
#include "core/click_handler_types.h"
|
||||||
#include "core/click_handler_types.h" // UrlClickHandler
|
#include "core/click_handler_types.h" // UrlClickHandler
|
||||||
#include "core/ui_integration.h"
|
#include "core/ui_integration.h"
|
||||||
|
#include "data/components/credits.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
#include "data/data_document_media.h"
|
#include "data/data_document_media.h"
|
||||||
#include "data/data_file_origin.h"
|
#include "data/data_file_origin.h"
|
||||||
|
@ -416,16 +417,20 @@ not_null<Ui::RpWidget*> AddBalanceWidget(
|
||||||
});
|
});
|
||||||
count->draw(p, {
|
count->draw(p, {
|
||||||
.position = QPoint(
|
.position = QPoint(
|
||||||
balance->width() - count->maxWidth(),
|
(rightAlign
|
||||||
|
? (balance->width() - count->maxWidth())
|
||||||
|
: (starSize.width() + diffBetweenStarAndCount)),
|
||||||
label->minHeight()
|
label->minHeight()
|
||||||
+ (starSize.height() - count->minHeight()) / 2),
|
+ (starSize.height() - count->minHeight()) / 2),
|
||||||
.availableWidth = balance->width(),
|
.availableWidth = balance->width(),
|
||||||
});
|
});
|
||||||
p.drawImage(
|
p.drawImage(
|
||||||
balance->width()
|
(rightAlign
|
||||||
- count->maxWidth()
|
? (balance->width()
|
||||||
- starSize.width()
|
- count->maxWidth()
|
||||||
- diffBetweenStarAndCount,
|
- starSize.width()
|
||||||
|
- diffBetweenStarAndCount)
|
||||||
|
: 0),
|
||||||
label->minHeight(),
|
label->minHeight(),
|
||||||
*balanceStar);
|
*balanceStar);
|
||||||
}, balance->lifetime());
|
}, balance->lifetime());
|
||||||
|
@ -859,9 +864,11 @@ object_ptr<Ui::RpWidget> PaidMediaThumbnail(
|
||||||
void SmallBalanceBox(
|
void SmallBalanceBox(
|
||||||
not_null<Ui::GenericBox*> box,
|
not_null<Ui::GenericBox*> box,
|
||||||
std::shared_ptr<Main::SessionShow> show,
|
std::shared_ptr<Main::SessionShow> show,
|
||||||
int creditsNeeded,
|
uint64 credits,
|
||||||
UserId botId,
|
SmallBalanceSource source,
|
||||||
Fn<void()> paid) {
|
Fn<void()> paid) {
|
||||||
|
Expects(show->session().credits().loaded());
|
||||||
|
|
||||||
box->setWidth(st::boxWideWidth);
|
box->setWidth(st::boxWideWidth);
|
||||||
box->addButton(tr::lng_close(), [=] { box->closeBox(); });
|
box->addButton(tr::lng_close(), [=] { box->closeBox(); });
|
||||||
const auto done = [=] {
|
const auto done = [=] {
|
||||||
|
@ -869,8 +876,17 @@ void SmallBalanceBox(
|
||||||
paid();
|
paid();
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto bot = show->session().data().user(botId).get();
|
const auto owner = &show->session().data();
|
||||||
|
const auto peer = v::match(source, [&](SmallBalanceBot value) {
|
||||||
|
return owner->peer(peerFromUser(value.botId));
|
||||||
|
}, [&](SmallBalanceReaction value) {
|
||||||
|
return owner->peer(peerFromChannel(value.channelId));
|
||||||
|
});
|
||||||
|
|
||||||
|
auto needed = show->session().credits().balanceValue(
|
||||||
|
) | rpl::map([=](uint64 balance) {
|
||||||
|
return (balance < credits) ? (credits - balance) : 0;
|
||||||
|
});
|
||||||
const auto content = [&]() -> Ui::Premium::TopBarAbstract* {
|
const auto content = [&]() -> Ui::Premium::TopBarAbstract* {
|
||||||
return box->setPinnedToTopContent(object_ptr<Ui::Premium::TopBar>(
|
return box->setPinnedToTopContent(object_ptr<Ui::Premium::TopBar>(
|
||||||
box,
|
box,
|
||||||
|
@ -878,11 +894,18 @@ void SmallBalanceBox(
|
||||||
Ui::Premium::TopBarDescriptor{
|
Ui::Premium::TopBarDescriptor{
|
||||||
.title = tr::lng_credits_small_balance_title(
|
.title = tr::lng_credits_small_balance_title(
|
||||||
lt_count,
|
lt_count,
|
||||||
rpl::single(creditsNeeded) | tr::to_count()),
|
rpl::duplicate(
|
||||||
.about = tr::lng_credits_small_balance_about(
|
needed
|
||||||
lt_bot,
|
) | rpl::filter(rpl::mappers::_1 > 0) | tr::to_count()),
|
||||||
rpl::single(TextWithEntities{ bot->name() }),
|
.about = (peer->isBroadcast()
|
||||||
Ui::Text::RichLangValue),
|
? tr::lng_credits_small_balance_reaction(
|
||||||
|
lt_channel,
|
||||||
|
rpl::single(Ui::Text::Bold(peer->name())),
|
||||||
|
Ui::Text::RichLangValue)
|
||||||
|
: tr::lng_credits_small_balance_about(
|
||||||
|
lt_bot,
|
||||||
|
rpl::single(TextWithEntities{ peer->name() }),
|
||||||
|
Ui::Text::RichLangValue)),
|
||||||
.light = true,
|
.light = true,
|
||||||
.gradientStops = Ui::Premium::CreditsIconGradientStops(),
|
.gradientStops = Ui::Premium::CreditsIconGradientStops(),
|
||||||
}));
|
}));
|
||||||
|
@ -892,8 +915,8 @@ void SmallBalanceBox(
|
||||||
show,
|
show,
|
||||||
box->verticalLayout(),
|
box->verticalLayout(),
|
||||||
show->session().user(),
|
show->session().user(),
|
||||||
creditsNeeded,
|
credits - show->session().credits().balance(),
|
||||||
done);
|
[=] { show->session().credits().load(true); });
|
||||||
|
|
||||||
content->setMaximumHeight(st::creditsLowBalancePremiumCoverHeight);
|
content->setMaximumHeight(st::creditsLowBalancePremiumCoverHeight);
|
||||||
content->setMinimumHeight(st::infoLayerTopBarHeight);
|
content->setMinimumHeight(st::infoLayerTopBarHeight);
|
||||||
|
@ -912,13 +935,10 @@ void SmallBalanceBox(
|
||||||
{
|
{
|
||||||
const auto balance = AddBalanceWidget(
|
const auto balance = AddBalanceWidget(
|
||||||
content,
|
content,
|
||||||
show->session().creditsValue(),
|
show->session().credits().balanceValue(),
|
||||||
true);
|
true);
|
||||||
const auto api = balance->lifetime().make_state<Api::CreditsStatus>(
|
show->session().credits().load(true);
|
||||||
show->session().user());
|
|
||||||
api->request({}, [=](Data::CreditsStatusSlice slice) {
|
|
||||||
show->session().setCredits(slice.balance);
|
|
||||||
});
|
|
||||||
rpl::combine(
|
rpl::combine(
|
||||||
balance->sizeValue(),
|
balance->sizeValue(),
|
||||||
content->sizeValue()
|
content->sizeValue()
|
||||||
|
@ -929,6 +949,12 @@ void SmallBalanceBox(
|
||||||
balance->update();
|
balance->update();
|
||||||
}, balance->lifetime());
|
}, balance->lifetime());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::move(
|
||||||
|
needed
|
||||||
|
) | rpl::filter(
|
||||||
|
!rpl::mappers::_1
|
||||||
|
) | rpl::start_with_next(done, content->lifetime());
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddWithdrawalWidget(
|
void AddWithdrawalWidget(
|
||||||
|
@ -1229,4 +1255,58 @@ void AddWithdrawalWidget(
|
||||||
Ui::AddSkip(container);
|
Ui::AddSkip(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MaybeRequestBalanceIncrease(
|
||||||
|
std::shared_ptr<Main::SessionShow> show,
|
||||||
|
uint64 credits,
|
||||||
|
SmallBalanceSource source,
|
||||||
|
Fn<void(SmallBalanceResult)> done) {
|
||||||
|
struct State {
|
||||||
|
rpl::lifetime lifetime;
|
||||||
|
bool success = false;
|
||||||
|
};
|
||||||
|
const auto state = std::make_shared<State>();
|
||||||
|
|
||||||
|
const auto session = &show->session();
|
||||||
|
session->credits().load();
|
||||||
|
session->credits().loadedValue(
|
||||||
|
) | rpl::filter(rpl::mappers::_1) | rpl::start_with_next([=] {
|
||||||
|
state->lifetime.destroy();
|
||||||
|
|
||||||
|
const auto balance = session->credits().balance();
|
||||||
|
if (credits <= balance) {
|
||||||
|
if (const auto onstack = done) {
|
||||||
|
onstack(SmallBalanceResult::Success);
|
||||||
|
}
|
||||||
|
} else if (show->session().premiumPossible()) {
|
||||||
|
const auto success = [=] {
|
||||||
|
state->success = true;
|
||||||
|
if (const auto onstack = done) {
|
||||||
|
onstack(SmallBalanceResult::Success);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const auto box = show->show(Box(
|
||||||
|
Settings::SmallBalanceBox,
|
||||||
|
show,
|
||||||
|
credits,
|
||||||
|
source,
|
||||||
|
success));
|
||||||
|
box->boxClosing() | rpl::start_with_next([=] {
|
||||||
|
crl::on_main([=] {
|
||||||
|
if (!state->success) {
|
||||||
|
if (const auto onstack = done) {
|
||||||
|
onstack(SmallBalanceResult::Cancelled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, box->lifetime());
|
||||||
|
} else {
|
||||||
|
show->showToast(
|
||||||
|
tr::lng_credits_purchase_blocked(tr::now));
|
||||||
|
if (const auto onstack = done) {
|
||||||
|
onstack(SmallBalanceResult::Blocked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, state->lifetime);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Settings
|
} // namespace Settings
|
||||||
|
|
|
@ -87,12 +87,36 @@ void ShowRefundInfoBox(
|
||||||
int totalCount,
|
int totalCount,
|
||||||
int photoSize);
|
int photoSize);
|
||||||
|
|
||||||
|
struct SmallBalanceBot {
|
||||||
|
UserId botId = 0;
|
||||||
|
};
|
||||||
|
struct SmallBalanceReaction {
|
||||||
|
ChannelId channelId = 0;
|
||||||
|
};
|
||||||
|
struct SmallBalanceSource : std::variant<
|
||||||
|
SmallBalanceBot,
|
||||||
|
SmallBalanceReaction> {
|
||||||
|
using variant::variant;
|
||||||
|
};
|
||||||
|
|
||||||
void SmallBalanceBox(
|
void SmallBalanceBox(
|
||||||
not_null<Ui::GenericBox*> box,
|
not_null<Ui::GenericBox*> box,
|
||||||
std::shared_ptr<Main::SessionShow> show,
|
std::shared_ptr<Main::SessionShow> show,
|
||||||
int creditsNeeded,
|
uint64 credits,
|
||||||
UserId botId,
|
SmallBalanceSource source,
|
||||||
Fn<void()> paid);
|
Fn<void()> paid);
|
||||||
|
|
||||||
|
enum class SmallBalanceResult {
|
||||||
|
Success,
|
||||||
|
Blocked,
|
||||||
|
Cancelled,
|
||||||
|
};
|
||||||
|
|
||||||
|
void MaybeRequestBalanceIncrease(
|
||||||
|
std::shared_ptr<Main::SessionShow> show,
|
||||||
|
uint64 credits,
|
||||||
|
SmallBalanceSource source,
|
||||||
|
Fn<void(SmallBalanceResult)> done);
|
||||||
|
|
||||||
} // namespace Settings
|
} // namespace Settings
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/vertical_list.h"
|
#include "ui/vertical_list.h"
|
||||||
#include "info/profile/info_profile_badge.h"
|
#include "info/profile/info_profile_badge.h"
|
||||||
#include "info/profile/info_profile_emoji_status_panel.h"
|
#include "info/profile/info_profile_emoji_status_panel.h"
|
||||||
|
#include "data/components/credits.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_cloud_themes.h"
|
#include "data/data_cloud_themes.h"
|
||||||
|
@ -492,19 +493,21 @@ void SetupPremium(
|
||||||
showOther(PremiumId());
|
showOther(PremiumId());
|
||||||
});
|
});
|
||||||
{
|
{
|
||||||
|
controller->session().credits().load();
|
||||||
|
|
||||||
const auto wrap = container->add(
|
const auto wrap = container->add(
|
||||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||||
container,
|
container,
|
||||||
object_ptr<Ui::VerticalLayout>(container)));
|
object_ptr<Ui::VerticalLayout>(container)));
|
||||||
wrap->toggleOn(
|
wrap->toggleOn(
|
||||||
controller->session().creditsValue(
|
controller->session().credits().balanceValue(
|
||||||
) | rpl::map(rpl::mappers::_1 > 0));
|
) | rpl::map(rpl::mappers::_1 > 0));
|
||||||
wrap->finishAnimating();
|
wrap->finishAnimating();
|
||||||
AddPremiumStar(
|
AddPremiumStar(
|
||||||
AddButtonWithLabel(
|
AddButtonWithLabel(
|
||||||
wrap->entity(),
|
wrap->entity(),
|
||||||
tr::lng_settings_credits(),
|
tr::lng_settings_credits(),
|
||||||
controller->session().creditsValue(
|
controller->session().credits().balanceValue(
|
||||||
) | rpl::map([=](uint64 c) {
|
) | rpl::map([=](uint64 c) {
|
||||||
return c ? Lang::FormatCountToShort(c).string : QString{};
|
return c ? Lang::FormatCountToShort(c).string : QString{};
|
||||||
}),
|
}),
|
||||||
|
@ -525,12 +528,6 @@ void SetupPremium(
|
||||||
});
|
});
|
||||||
Ui::NewBadge::AddToRight(button);
|
Ui::NewBadge::AddToRight(button);
|
||||||
|
|
||||||
const auto api = button->lifetime().make_state<Api::CreditsStatus>(
|
|
||||||
controller->session().user());
|
|
||||||
api->request({}, [=](Data::CreditsStatusSlice slice) {
|
|
||||||
controller->session().setCredits(slice.balance);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (controller->session().premiumCanBuy()) {
|
if (controller->session().premiumCanBuy()) {
|
||||||
const auto button = AddButtonWithIcon(
|
const auto button = AddButtonWithIcon(
|
||||||
container,
|
container,
|
||||||
|
|
|
@ -89,8 +89,8 @@ ReactionFlyAnimation::ReactionFlyAnimation(
|
||||||
} else if (args.id.paid()) {
|
} else if (args.id.paid()) {
|
||||||
const auto fake = owner->lookupPaid();
|
const auto fake = owner->lookupPaid();
|
||||||
centerIcon = fake->centerIcon;
|
centerIcon = fake->centerIcon;
|
||||||
aroundAnimation = fake->aroundAnimation;
|
aroundAnimation = owner->choosePaidReactionAnimation();
|
||||||
_centerSizeMultiplier = 1.;// fake->centerIcon ? 1. : 0.5;
|
_centerSizeMultiplier = 0.5;
|
||||||
} else {
|
} else {
|
||||||
const auto i = ranges::find(list, args.id, &::Data::Reaction::id);
|
const auto i = ranges::find(list, args.id, &::Data::Reaction::id);
|
||||||
if (i == end(list)/* || !i->centerIcon*/) {
|
if (i == end(list)/* || !i->centerIcon*/) {
|
||||||
|
|
|
@ -197,6 +197,8 @@ PRIVATE
|
||||||
payments/ui/payments_panel.h
|
payments/ui/payments_panel.h
|
||||||
payments/ui/payments_panel_data.h
|
payments/ui/payments_panel_data.h
|
||||||
payments/ui/payments_panel_delegate.h
|
payments/ui/payments_panel_delegate.h
|
||||||
|
payments/ui/payments_reaction_box.cpp
|
||||||
|
payments/ui/payments_reaction_box.h
|
||||||
|
|
||||||
platform/linux/current_geo_location_linux.cpp
|
platform/linux/current_geo_location_linux.cpp
|
||||||
platform/linux/current_geo_location_linux.h
|
platform/linux/current_geo_location_linux.h
|
||||||
|
|
Loading…
Add table
Reference in a new issue