mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-07-25 06:54:43 +00:00
Show Reject/Accept buttons for offers.
This commit is contained in:
@@ -2351,6 +2351,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
"lng_action_gift_offer" = "{user} offered you {cost} for {name}.";
|
"lng_action_gift_offer" = "{user} offered you {cost} for {name}.";
|
||||||
"lng_action_gift_offer_you" = "You offered {cost} for {name}.";
|
"lng_action_gift_offer_you" = "You offered {cost} for {name}.";
|
||||||
"lng_action_gift_offer_state_expires" = "This offer expires in {time}.";
|
"lng_action_gift_offer_state_expires" = "This offer expires in {time}.";
|
||||||
|
"lng_action_gift_offer_time_medium" = "{hours} h {minutes} m";
|
||||||
|
"lng_action_gift_offer_time_small" = "{minutes} m";
|
||||||
"lng_action_gift_offer_state_accepted" = "This offer was accepted.";
|
"lng_action_gift_offer_state_accepted" = "This offer was accepted.";
|
||||||
"lng_action_gift_offer_state_rejected" = "This offer was rejected.";
|
"lng_action_gift_offer_state_rejected" = "This offer was rejected.";
|
||||||
"lng_action_gift_offer_state_expired" = "This offer has expired.";
|
"lng_action_gift_offer_state_expired" = "This offer has expired.";
|
||||||
@@ -2359,7 +2361,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
"lng_action_gift_offer_decline" = "Reject";
|
"lng_action_gift_offer_decline" = "Reject";
|
||||||
"lng_action_gift_offer_accept" = "Accept";
|
"lng_action_gift_offer_accept" = "Accept";
|
||||||
"lng_action_gift_offer_expired" = "The offer from {user} to buy your {name} for {cost} has expired.";
|
"lng_action_gift_offer_expired" = "The offer from {user} to buy your {name} for {cost} has expired.";
|
||||||
"lng_action_gift_offer_expired_you" = "Your offer to buy {name} for {cost} has expired.";
|
"lng_action_gift_offer_expired_your" = "Your offer to buy {name} for {cost} has expired.";
|
||||||
"lng_action_gift_offer_declined" = "{user} rejected your offer to buy {name} for {cost}.";
|
"lng_action_gift_offer_declined" = "{user} rejected your offer to buy {name} for {cost}.";
|
||||||
"lng_action_gift_offer_declined_you" = "You rejected {user}'s offer to buy your {name} for {cost}.";
|
"lng_action_gift_offer_declined_you" = "You rejected {user}'s offer to buy your {name} for {cost}.";
|
||||||
"lng_action_suggested_photo_me" = "You suggested this photo for {user}'s Telegram profile.";
|
"lng_action_suggested_photo_me" = "You suggested this photo for {user}'s Telegram profile.";
|
||||||
|
|||||||
@@ -546,6 +546,7 @@ std::shared_ptr<ClickHandler> DeclineClickHandler(
|
|||||||
if (!item) {
|
if (!item) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
RequestDeclineComment(controller->uiShow(), item);
|
RequestDeclineComment(controller->uiShow(), item);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -181,3 +181,5 @@ private:
|
|||||||
[[nodiscard]] CreditsAmount CreditsAmountFromTL(
|
[[nodiscard]] CreditsAmount CreditsAmountFromTL(
|
||||||
const MTPStarsAmount *amount);
|
const MTPStarsAmount *amount);
|
||||||
[[nodiscard]] MTPStarsAmount StarsAmountToTL(CreditsAmount amount);
|
[[nodiscard]] MTPStarsAmount StarsAmountToTL(CreditsAmount amount);
|
||||||
|
|
||||||
|
[[nodiscard]] QString PrepareCreditsAmountText(CreditsAmount amount);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
#include "api/api_credits.h"
|
#include "api/api_credits.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
|
#include "lang/lang_keys.h"
|
||||||
#include "main/main_app_config.h"
|
#include "main/main_app_config.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
|
|
||||||
@@ -256,3 +257,15 @@ MTPStarsAmount StarsAmountToTL(CreditsAmount amount) {
|
|||||||
MTP_long(amount.whole() * uint64(1'000'000'000) + amount.nano())
|
MTP_long(amount.whole() * uint64(1'000'000'000) + amount.nano())
|
||||||
) : MTP_starsAmount(MTP_long(amount.whole()), MTP_int(amount.nano()));
|
) : MTP_starsAmount(MTP_long(amount.whole()), MTP_int(amount.nano()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString PrepareCreditsAmountText(CreditsAmount amount) {
|
||||||
|
return amount.stars()
|
||||||
|
? tr::lng_action_gift_for_stars(
|
||||||
|
tr::now,
|
||||||
|
lt_count_decimal,
|
||||||
|
amount.value())
|
||||||
|
: tr::lng_action_gift_for_ton(
|
||||||
|
tr::now,
|
||||||
|
lt_count_decimal,
|
||||||
|
amount.value());
|
||||||
|
}
|
||||||
|
|||||||
@@ -2606,10 +2606,11 @@ std::unique_ptr<HistoryView::Media> MediaGiftBox::createView(
|
|||||||
.service = true,
|
.service = true,
|
||||||
.hideServiceText = true,
|
.hideServiceText = true,
|
||||||
});
|
});
|
||||||
} else if (_data.type == GiftType::ChatTheme) {
|
} else if (_data.type == GiftType::ChatTheme
|
||||||
|
|| _data.type == GiftType::GiftOffer) {
|
||||||
return std::make_unique<HistoryView::ServiceBox>(
|
return std::make_unique<HistoryView::ServiceBox>(
|
||||||
message,
|
message,
|
||||||
std::make_unique<HistoryView::GiftThemeBox>(message, this));
|
std::make_unique<HistoryView::GiftServiceBox>(message, this));
|
||||||
} else if (const auto &unique = _data.unique) {
|
} else if (const auto &unique = _data.unique) {
|
||||||
return std::make_unique<HistoryView::MediaGeneric>(
|
return std::make_unique<HistoryView::MediaGeneric>(
|
||||||
message,
|
message,
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ enum class GiftType : uchar {
|
|||||||
StarGift, // count - stars
|
StarGift, // count - stars
|
||||||
ChatTheme,
|
ChatTheme,
|
||||||
BirthdaySuggest,
|
BirthdaySuggest,
|
||||||
|
GiftOffer,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GiftCode {
|
struct GiftCode {
|
||||||
|
|||||||
@@ -1098,13 +1098,26 @@ SuggestionActions HistoryItem::computeSuggestionActions() const {
|
|||||||
SuggestionActions HistoryItem::computeSuggestionActions(
|
SuggestionActions HistoryItem::computeSuggestionActions(
|
||||||
const HistoryMessageSuggestion *suggest) const {
|
const HistoryMessageSuggestion *suggest) const {
|
||||||
return suggest
|
return suggest
|
||||||
? computeSuggestionActions(suggest->accepted, suggest->rejected)
|
? computeSuggestionActions(
|
||||||
|
suggest->accepted,
|
||||||
|
suggest->rejected,
|
||||||
|
suggest->gift ? suggest->date: 0)
|
||||||
: SuggestionActions::None;
|
: SuggestionActions::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
SuggestionActions HistoryItem::computeSuggestionActions(
|
SuggestionActions HistoryItem::computeSuggestionActions(
|
||||||
bool accepted,
|
bool accepted,
|
||||||
bool rejected) const {
|
bool rejected,
|
||||||
|
TimeId giftOfferExpiresAt) const {
|
||||||
|
if (giftOfferExpiresAt) {
|
||||||
|
const auto can = isRegular()
|
||||||
|
&& !(accepted || rejected)
|
||||||
|
&& !out()
|
||||||
|
&& (giftOfferExpiresAt > base::unixtime::now());
|
||||||
|
return can
|
||||||
|
? SuggestionActions::GiftOfferActions
|
||||||
|
: SuggestionActions::None;
|
||||||
|
}
|
||||||
const auto channelIsAuthor = from()->isChannel();
|
const auto channelIsAuthor = from()->isChannel();
|
||||||
const auto amMonoforumAdmin = history()->peer->amMonoforumAdmin();
|
const auto amMonoforumAdmin = history()->peer->amMonoforumAdmin();
|
||||||
const auto broadcast = history()->peer->monoforumBroadcast();
|
const auto broadcast = history()->peer->monoforumBroadcast();
|
||||||
@@ -4077,7 +4090,8 @@ void HistoryItem::createComponents(CreateConfig &&config) {
|
|||||||
mask |= HistoryMessageSuggestion::Bit();
|
mask |= HistoryMessageSuggestion::Bit();
|
||||||
if (computeSuggestionActions(
|
if (computeSuggestionActions(
|
||||||
config.suggest.accepted,
|
config.suggest.accepted,
|
||||||
config.suggest.rejected
|
config.suggest.rejected,
|
||||||
|
TimeId()
|
||||||
) != SuggestionActions::None) {
|
) != SuggestionActions::None) {
|
||||||
mask |= HistoryMessageReplyMarkup::Bit();
|
mask |= HistoryMessageReplyMarkup::Bit();
|
||||||
}
|
}
|
||||||
@@ -4827,7 +4841,7 @@ void HistoryItem::createServiceFromMtp(const MTPDmessageService &message) {
|
|||||||
const auto &data = action.c_messageActionSuggestedPostSuccess();
|
const auto &data = action.c_messageActionSuggestedPostSuccess();
|
||||||
UpdateComponents(HistoryServiceSuggestFinish::Bit());
|
UpdateComponents(HistoryServiceSuggestFinish::Bit());
|
||||||
const auto finish = Get<HistoryServiceSuggestFinish>();
|
const auto finish = Get<HistoryServiceSuggestFinish>();
|
||||||
finish->successPrice = CreditsAmountFromTL(data.vprice());
|
finish->price = CreditsAmountFromTL(data.vprice());
|
||||||
} else if (type == mtpc_messageActionSuggestedPostRefund) {
|
} else if (type == mtpc_messageActionSuggestedPostRefund) {
|
||||||
const auto &data = action.c_messageActionSuggestedPostRefund();
|
const auto &data = action.c_messageActionSuggestedPostRefund();
|
||||||
UpdateComponents(HistoryServiceSuggestFinish::Bit());
|
UpdateComponents(HistoryServiceSuggestFinish::Bit());
|
||||||
@@ -4835,6 +4849,43 @@ void HistoryItem::createServiceFromMtp(const MTPDmessageService &message) {
|
|||||||
finish->refundType = data.is_payer_initiated()
|
finish->refundType = data.is_payer_initiated()
|
||||||
? SuggestRefundType::User
|
? SuggestRefundType::User
|
||||||
: SuggestRefundType::Admin;
|
: SuggestRefundType::Admin;
|
||||||
|
} else if (type == mtpc_messageActionStarGiftPurchaseOffer) {
|
||||||
|
const auto &data = action.c_messageActionStarGiftPurchaseOffer();
|
||||||
|
const auto accepted = data.is_accepted();
|
||||||
|
const auto rejected = data.is_declined();
|
||||||
|
const auto expiresAt = data.vexpires_at().v;
|
||||||
|
const auto gift = Api::FromTL(&history()->session(), data.vgift());
|
||||||
|
if (const auto unique = gift ? gift->unique : nullptr) {
|
||||||
|
auto mask = HistoryMessageSuggestion::Bit();
|
||||||
|
const auto actions = computeSuggestionActions(
|
||||||
|
accepted,
|
||||||
|
rejected,
|
||||||
|
expiresAt);
|
||||||
|
if (actions != SuggestionActions::None) {
|
||||||
|
mask |= HistoryMessageReplyMarkup::Bit();
|
||||||
|
}
|
||||||
|
UpdateComponents(mask);
|
||||||
|
|
||||||
|
const auto suggestion = Get<HistoryMessageSuggestion>();
|
||||||
|
suggestion->price = CreditsAmountFromTL(data.vprice());
|
||||||
|
suggestion->date = expiresAt;
|
||||||
|
suggestion->accepted = accepted;
|
||||||
|
suggestion->rejected = rejected;
|
||||||
|
suggestion->gift = unique;
|
||||||
|
|
||||||
|
if (actions != SuggestionActions::None) {
|
||||||
|
const auto markup = Get<HistoryMessageReplyMarkup>();
|
||||||
|
markup->updateSuggestControls(actions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (type == mtpc_messageActionStarGiftPurchaseOfferDeclined) {
|
||||||
|
const auto &data = action.c_messageActionStarGiftPurchaseOfferDeclined();
|
||||||
|
UpdateComponents(HistoryServiceSuggestFinish::Bit());
|
||||||
|
const auto finish = Get<HistoryServiceSuggestFinish>();
|
||||||
|
finish->refundType = data.is_expired()
|
||||||
|
? SuggestRefundType::Expired
|
||||||
|
: SuggestRefundType::User;
|
||||||
|
finish->price = CreditsAmountFromTL(data.vprice());
|
||||||
}
|
}
|
||||||
if (const auto replyTo = message.vreply_to()) {
|
if (const auto replyTo = message.vreply_to()) {
|
||||||
replyTo->match([&](const MTPDmessageReplyHeader &data) {
|
replyTo->match([&](const MTPDmessageReplyHeader &data) {
|
||||||
@@ -6152,16 +6203,8 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
|
|||||||
const auto isSelf = _from->isSelf();
|
const auto isSelf = _from->isSelf();
|
||||||
const auto resale = CreditsAmountFromTL(action.vresale_amount());
|
const auto resale = CreditsAmountFromTL(action.vresale_amount());
|
||||||
const auto resaleCost = !resale
|
const auto resaleCost = !resale
|
||||||
? TextWithEntities()
|
? tr::marked()
|
||||||
: resale.stars()
|
: tr::marked(PrepareCreditsAmountText(resale));
|
||||||
? TextWithEntities{ tr::lng_action_gift_for_stars(
|
|
||||||
tr::now,
|
|
||||||
lt_count,
|
|
||||||
resale.value()) }
|
|
||||||
: TextWithEntities{ tr::lng_action_gift_for_ton(
|
|
||||||
tr::now,
|
|
||||||
lt_count,
|
|
||||||
resale.value()) };
|
|
||||||
const auto giftPeer = action.vpeer()
|
const auto giftPeer = action.vpeer()
|
||||||
? peerFromMTP(*action.vpeer())
|
? peerFromMTP(*action.vpeer())
|
||||||
: PeerId();
|
: PeerId();
|
||||||
@@ -6414,11 +6457,91 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
|
|||||||
|
|
||||||
auto prepareStarGiftPurchaseOffer = [&](const MTPDmessageActionStarGiftPurchaseOffer &action) {
|
auto prepareStarGiftPurchaseOffer = [&](const MTPDmessageActionStarGiftPurchaseOffer &action) {
|
||||||
auto result = PreparedServiceText{};
|
auto result = PreparedServiceText{};
|
||||||
|
action.vgift().match([&](const MTPDstarGiftUnique &data) {
|
||||||
|
const auto amount = CreditsAmountFromTL(action.vprice());
|
||||||
|
const auto cost = tr::marked(PrepareCreditsAmountText(amount));
|
||||||
|
const auto giftName = tr::bold(qs(data.vtitle())
|
||||||
|
+ u" #"_q
|
||||||
|
+ QString::number(data.vnum().v));
|
||||||
|
if (_from->isSelf()) {
|
||||||
|
result.text = tr::lng_action_gift_offer_you(
|
||||||
|
tr::now,
|
||||||
|
lt_cost,
|
||||||
|
cost,
|
||||||
|
lt_name,
|
||||||
|
giftName,
|
||||||
|
tr::marked);
|
||||||
|
} else {
|
||||||
|
result.links.push_back(fromLink());
|
||||||
|
result.text = tr::lng_action_gift_offer(
|
||||||
|
tr::now,
|
||||||
|
lt_user,
|
||||||
|
fromLinkText(),
|
||||||
|
lt_cost,
|
||||||
|
cost,
|
||||||
|
lt_name,
|
||||||
|
giftName,
|
||||||
|
tr::marked);
|
||||||
|
}
|
||||||
|
}, [](const MTPDstarGift &) {
|
||||||
|
});
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
auto prepareStarGiftPurchaseOfferDeclined = [&](const MTPDmessageActionStarGiftPurchaseOfferDeclined &action) {
|
auto prepareStarGiftPurchaseOfferDeclined = [&](const MTPDmessageActionStarGiftPurchaseOfferDeclined &action) {
|
||||||
auto result = PreparedServiceText{};
|
auto result = PreparedServiceText{};
|
||||||
|
action.vgift().match([&](const MTPDstarGiftUnique &data) {
|
||||||
|
const auto amount = CreditsAmountFromTL(action.vprice());
|
||||||
|
const auto cost = tr::marked(PrepareCreditsAmountText(amount));
|
||||||
|
const auto giftName = Ui::Text::Bold(qs(data.vtitle())
|
||||||
|
+ u" #"_q
|
||||||
|
+ QString::number(data.vnum().v));
|
||||||
|
const auto expired = action.is_expired();
|
||||||
|
if (_from->isSelf()) {
|
||||||
|
result.links.push_back(_history->peer->createOpenLink());
|
||||||
|
result.text = expired
|
||||||
|
? tr::lng_action_gift_offer_expired(
|
||||||
|
tr::now,
|
||||||
|
lt_user,
|
||||||
|
tr::link(st::wrap_rtl(_history->peer->name()), 1),
|
||||||
|
lt_name,
|
||||||
|
giftName,
|
||||||
|
lt_cost,
|
||||||
|
cost,
|
||||||
|
tr::marked)
|
||||||
|
: tr::lng_action_gift_offer_declined_you(
|
||||||
|
tr::now,
|
||||||
|
lt_user,
|
||||||
|
tr::link(st::wrap_rtl(_history->peer->name()), 1),
|
||||||
|
lt_name,
|
||||||
|
giftName,
|
||||||
|
lt_cost,
|
||||||
|
cost,
|
||||||
|
tr::marked);
|
||||||
|
} else {
|
||||||
|
if (!expired) {
|
||||||
|
result.links.push_back(fromLink());
|
||||||
|
}
|
||||||
|
result.text = expired
|
||||||
|
? tr::lng_action_gift_offer_expired_your(
|
||||||
|
tr::now,
|
||||||
|
lt_name,
|
||||||
|
giftName,
|
||||||
|
lt_cost,
|
||||||
|
cost,
|
||||||
|
tr::marked)
|
||||||
|
: tr::lng_action_gift_offer_declined(
|
||||||
|
tr::now,
|
||||||
|
lt_user,
|
||||||
|
fromLinkText(),
|
||||||
|
lt_name,
|
||||||
|
giftName,
|
||||||
|
lt_cost,
|
||||||
|
cost,
|
||||||
|
tr::marked);
|
||||||
|
}
|
||||||
|
}, [](const MTPDstarGift &) {
|
||||||
|
});
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -6749,6 +6872,18 @@ void HistoryItem::applyAction(const MTPMessageAction &action) {
|
|||||||
fields.vyear().value_or_empty()).serialize(),
|
fields.vyear().value_or_empty()).serialize(),
|
||||||
.type = Data::GiftType::BirthdaySuggest,
|
.type = Data::GiftType::BirthdaySuggest,
|
||||||
});
|
});
|
||||||
|
}, [&](const MTPDmessageActionStarGiftPurchaseOffer &data) {
|
||||||
|
if (const auto suggestion = Get<HistoryMessageSuggestion>()) {
|
||||||
|
Assert(suggestion->gift != nullptr);
|
||||||
|
|
||||||
|
_media = std::make_unique<Data::MediaGiftBox>(
|
||||||
|
this,
|
||||||
|
_from,
|
||||||
|
Data::GiftCode{
|
||||||
|
.unique = suggestion->gift,
|
||||||
|
.type = Data::GiftType::GiftOffer,
|
||||||
|
});
|
||||||
|
}
|
||||||
}, [](const auto &) {
|
}, [](const auto &) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -580,7 +580,8 @@ public:
|
|||||||
const HistoryMessageSuggestion *suggest) const;
|
const HistoryMessageSuggestion *suggest) const;
|
||||||
[[nodiscard]] SuggestionActions computeSuggestionActions(
|
[[nodiscard]] SuggestionActions computeSuggestionActions(
|
||||||
bool accepted,
|
bool accepted,
|
||||||
bool rejected) const;
|
bool rejected,
|
||||||
|
TimeId giftOfferExpiresAt) const;
|
||||||
|
|
||||||
[[nodiscard]] bool needsUpdateForVideoQualities(const MTPMessage &data);
|
[[nodiscard]] bool needsUpdateForVideoQualities(const MTPMessage &data);
|
||||||
|
|
||||||
|
|||||||
@@ -1219,7 +1219,8 @@ bool HistoryMessageReplyMarkup::hiddenBy(Data::Media *media) const {
|
|||||||
|
|
||||||
void HistoryMessageReplyMarkup::updateSuggestControls(
|
void HistoryMessageReplyMarkup::updateSuggestControls(
|
||||||
SuggestionActions actions) {
|
SuggestionActions actions) {
|
||||||
if (actions == SuggestionActions::AcceptAndDecline) {
|
if (actions == SuggestionActions::AcceptAndDecline
|
||||||
|
|| actions == SuggestionActions::GiftOfferActions) {
|
||||||
data.flags |= ReplyMarkupFlag::SuggestionAccept;
|
data.flags |= ReplyMarkupFlag::SuggestionAccept;
|
||||||
} else {
|
} else {
|
||||||
data.flags &= ~ReplyMarkupFlag::SuggestionAccept;
|
data.flags &= ~ReplyMarkupFlag::SuggestionAccept;
|
||||||
@@ -1238,7 +1239,21 @@ void HistoryMessageReplyMarkup::updateSuggestControls(
|
|||||||
type,
|
type,
|
||||||
&HistoryMessageMarkupButton::type);
|
&HistoryMessageMarkupButton::type);
|
||||||
};
|
};
|
||||||
if (actions == SuggestionActions::AcceptAndDecline) {
|
if (actions == SuggestionActions::GiftOfferActions) {
|
||||||
|
if (has(Type::SuggestAccept)) {
|
||||||
|
// Nothing changed.
|
||||||
|
}
|
||||||
|
data.rows.push_back({
|
||||||
|
{
|
||||||
|
Type::SuggestDecline,
|
||||||
|
tr::lng_action_gift_offer_decline(tr::now),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type::SuggestAccept,
|
||||||
|
tr::lng_action_gift_offer_accept(tr::now),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else if (actions == SuggestionActions::AcceptAndDecline) {
|
||||||
// ... rows ...
|
// ... rows ...
|
||||||
// [decline] | [accept]
|
// [decline] | [accept]
|
||||||
// [suggestchanges]
|
// [suggestchanges]
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ enum class SuggestionActions : uchar {
|
|||||||
None,
|
None,
|
||||||
Decline,
|
Decline,
|
||||||
AcceptAndDecline,
|
AcceptAndDecline,
|
||||||
|
GiftOfferActions,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct HistoryMessageVia : RuntimeComponent<HistoryMessageVia, HistoryItem> {
|
struct HistoryMessageVia : RuntimeComponent<HistoryMessageVia, HistoryItem> {
|
||||||
@@ -624,12 +625,12 @@ struct HistoryMessageFactcheck
|
|||||||
|
|
||||||
struct HistoryMessageSuggestion
|
struct HistoryMessageSuggestion
|
||||||
: RuntimeComponent<HistoryMessageSuggestion, HistoryItem> {
|
: RuntimeComponent<HistoryMessageSuggestion, HistoryItem> {
|
||||||
|
std::shared_ptr<Data::UniqueGift> gift;
|
||||||
CreditsAmount price;
|
CreditsAmount price;
|
||||||
TimeId date = 0;
|
TimeId date = 0;
|
||||||
mtpRequestId requestId = 0;
|
mtpRequestId requestId = 0;
|
||||||
bool accepted = false;
|
bool accepted = false;
|
||||||
bool rejected = false;
|
bool rejected = false;
|
||||||
bool gift = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct HistoryMessageRestrictions
|
struct HistoryMessageRestrictions
|
||||||
@@ -714,12 +715,13 @@ enum class SuggestRefundType {
|
|||||||
None,
|
None,
|
||||||
User,
|
User,
|
||||||
Admin,
|
Admin,
|
||||||
|
Expired,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct HistoryServiceSuggestFinish
|
struct HistoryServiceSuggestFinish
|
||||||
: RuntimeComponent<HistoryServiceSuggestFinish, HistoryItem>
|
: RuntimeComponent<HistoryServiceSuggestFinish, HistoryItem>
|
||||||
, HistoryServiceDependentData {
|
, HistoryServiceDependentData {
|
||||||
CreditsAmount successPrice;
|
CreditsAmount price;
|
||||||
SuggestRefundType refundType = SuggestRefundType::None;
|
SuggestRefundType refundType = SuggestRefundType::None;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -647,7 +647,7 @@ void ChooseSuggestPriceBox(
|
|||||||
durations,
|
durations,
|
||||||
state->offerDuration.current());
|
state->offerDuration.current());
|
||||||
SingleChoiceBox(box, {
|
SingleChoiceBox(box, {
|
||||||
.title = tr::lng_settings_angle_backend(),
|
.title = tr::lng_gift_offer_duration(),
|
||||||
.options = options,
|
.options = options,
|
||||||
.initialSelection = int(selected - begin(durations)),
|
.initialSelection = int(selected - begin(durations)),
|
||||||
.callback = save,
|
.callback = save,
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "chat_helpers/stickers_emoji_pack.h"
|
#include "chat_helpers/stickers_emoji_pack.h"
|
||||||
#include "payments/payments_reaction_process.h" // TryAddingPaidReaction.
|
#include "payments/payments_reaction_process.h" // TryAddingPaidReaction.
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
|
#include "ui/effects/glare.h"
|
||||||
#include "ui/effects/path_shift_gradient.h"
|
#include "ui/effects/path_shift_gradient.h"
|
||||||
#include "ui/effects/reaction_fly_animation.h"
|
#include "ui/effects/reaction_fly_animation.h"
|
||||||
#include "ui/toast/toast.h"
|
#include "ui/toast/toast.h"
|
||||||
@@ -42,6 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "ui/item_text_options.h"
|
#include "ui/item_text_options.h"
|
||||||
#include "ui/painter.h"
|
#include "ui/painter.h"
|
||||||
#include "ui/rect.h"
|
#include "ui/rect.h"
|
||||||
|
#include "ui/round_rect.h"
|
||||||
#include "data/components/sponsored_messages.h"
|
#include "data/components/sponsored_messages.h"
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
#include "data/data_saved_sublist.h"
|
#include "data/data_saved_sublist.h"
|
||||||
@@ -53,6 +55,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
#include "styles/style_dialogs.h"
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
namespace {
|
namespace {
|
||||||
@@ -66,6 +69,287 @@ Element *HoveredLinkElement/* = nullptr*/;
|
|||||||
Element *PressedLinkElement/* = nullptr*/;
|
Element *PressedLinkElement/* = nullptr*/;
|
||||||
Element *MousedElement/* = nullptr*/;
|
Element *MousedElement/* = nullptr*/;
|
||||||
|
|
||||||
|
class KeyboardStyle : public ReplyKeyboard::Style {
|
||||||
|
public:
|
||||||
|
KeyboardStyle(const style::BotKeyboardButton &st, Fn<void()> repaint);
|
||||||
|
|
||||||
|
Images::CornersMaskRef buttonRounding(
|
||||||
|
Ui::BubbleRounding outer,
|
||||||
|
RectParts sides) const override;
|
||||||
|
|
||||||
|
void startPaint(
|
||||||
|
QPainter &p,
|
||||||
|
const Ui::ChatStyle *st) const override;
|
||||||
|
const style::TextStyle &textStyle() const override;
|
||||||
|
void repaint(not_null<const HistoryItem*> item) const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void paintButtonBg(
|
||||||
|
QPainter &p,
|
||||||
|
const Ui::ChatStyle *st,
|
||||||
|
const QRect &rect,
|
||||||
|
Ui::BubbleRounding rounding,
|
||||||
|
float64 howMuchOver) const override;
|
||||||
|
void paintButtonIcon(
|
||||||
|
QPainter &p,
|
||||||
|
const Ui::ChatStyle *st,
|
||||||
|
const QRect &rect,
|
||||||
|
int outerWidth,
|
||||||
|
HistoryMessageMarkupButton::Type type) const override;
|
||||||
|
void paintButtonLoading(
|
||||||
|
QPainter &p,
|
||||||
|
const Ui::ChatStyle *st,
|
||||||
|
const QRect &rect,
|
||||||
|
int outerWidth,
|
||||||
|
Ui::BubbleRounding rounding) const override;
|
||||||
|
int minButtonWidth(HistoryMessageMarkupButton::Type type) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct CachedBg {
|
||||||
|
QImage image;
|
||||||
|
QColor color;
|
||||||
|
};
|
||||||
|
using BubbleRoundingKey = uchar;
|
||||||
|
mutable base::flat_map<BubbleRoundingKey, CachedBg> _cachedBg;
|
||||||
|
mutable base::flat_map<BubbleRoundingKey, QPainterPath> _cachedOutline;
|
||||||
|
mutable std::unique_ptr<Ui::GlareEffect> _glare;
|
||||||
|
Fn<void()> _repaint;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
KeyboardStyle::KeyboardStyle(
|
||||||
|
const style::BotKeyboardButton &st,
|
||||||
|
Fn<void()> repaint)
|
||||||
|
: ReplyKeyboard::Style(st)
|
||||||
|
, _repaint(std::move(repaint)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void KeyboardStyle::startPaint(
|
||||||
|
QPainter &p,
|
||||||
|
const Ui::ChatStyle *st) const {
|
||||||
|
Expects(st != nullptr);
|
||||||
|
|
||||||
|
p.setPen(st->msgServiceFg());
|
||||||
|
}
|
||||||
|
|
||||||
|
const style::TextStyle &KeyboardStyle::textStyle() const {
|
||||||
|
return st::serviceTextStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void KeyboardStyle::repaint(not_null<const HistoryItem*> item) const {
|
||||||
|
item->history()->owner().requestItemRepaint(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
Images::CornersMaskRef KeyboardStyle::buttonRounding(
|
||||||
|
Ui::BubbleRounding outer,
|
||||||
|
RectParts sides) const {
|
||||||
|
using namespace Images;
|
||||||
|
using namespace Ui;
|
||||||
|
using Radius = CachedCornerRadius;
|
||||||
|
using Corner = BubbleCornerRounding;
|
||||||
|
auto result = CornersMaskRef(CachedCornersMasks(Radius::BubbleSmall));
|
||||||
|
if (sides & RectPart::Bottom) {
|
||||||
|
const auto &large = CachedCornersMasks(Radius::BubbleLarge);
|
||||||
|
auto round = [&](RectPart side, int index) {
|
||||||
|
if ((sides & side) && (outer[index] == Corner::Large)) {
|
||||||
|
result.p[index] = &large[index];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
round(RectPart::Left, kBottomLeft);
|
||||||
|
round(RectPart::Right, kBottomRight);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void KeyboardStyle::paintButtonBg(
|
||||||
|
QPainter &p,
|
||||||
|
const Ui::ChatStyle *st,
|
||||||
|
const QRect &rect,
|
||||||
|
Ui::BubbleRounding rounding,
|
||||||
|
float64 howMuchOver) const {
|
||||||
|
Expects(st != nullptr);
|
||||||
|
|
||||||
|
using Corner = Ui::BubbleCornerRounding;
|
||||||
|
auto &cachedBg = _cachedBg[rounding.key()];
|
||||||
|
|
||||||
|
const auto sti = &st->imageStyle(false);
|
||||||
|
const auto ratio = style::DevicePixelRatio();
|
||||||
|
if (cachedBg.image.isNull()
|
||||||
|
|| cachedBg.image.width() != (rect.width() * ratio)
|
||||||
|
|| cachedBg.color != sti->msgServiceBg->c) {
|
||||||
|
cachedBg.color = sti->msgServiceBg->c;
|
||||||
|
cachedBg.image = QImage(
|
||||||
|
rect.size() * ratio,
|
||||||
|
QImage::Format_ARGB32_Premultiplied);
|
||||||
|
cachedBg.image.setDevicePixelRatio(ratio);
|
||||||
|
cachedBg.image.fill(Qt::transparent);
|
||||||
|
{
|
||||||
|
auto painter = QPainter(&cachedBg.image);
|
||||||
|
|
||||||
|
const auto &small = sti->msgServiceBgCornersSmall;
|
||||||
|
const auto &large = sti->msgServiceBgCornersLarge;
|
||||||
|
auto corners = Ui::CornersPixmaps();
|
||||||
|
int radiuses[4];
|
||||||
|
for (auto i = 0; i != 4; ++i) {
|
||||||
|
const auto isLarge = (rounding[i] == Corner::Large);
|
||||||
|
corners.p[i] = (isLarge ? large : small).p[i];
|
||||||
|
radiuses[i] = Ui::CachedCornerRadiusValue(isLarge
|
||||||
|
? Ui::CachedCornerRadius::BubbleLarge
|
||||||
|
: Ui::CachedCornerRadius::BubbleSmall);
|
||||||
|
}
|
||||||
|
const auto r = Rect(rect.size());
|
||||||
|
_cachedOutline[rounding.key()] = Ui::ComplexRoundedRectPath(
|
||||||
|
r - Margins(st::lineWidth),
|
||||||
|
radiuses[0],
|
||||||
|
radiuses[1],
|
||||||
|
radiuses[2],
|
||||||
|
radiuses[3]);
|
||||||
|
Ui::FillRoundRect(painter, r, sti->msgServiceBg, corners);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.drawImage(rect.topLeft(), cachedBg.image);
|
||||||
|
if (howMuchOver > 0) {
|
||||||
|
auto o = p.opacity();
|
||||||
|
p.setOpacity(o * howMuchOver);
|
||||||
|
const auto &small = st->msgBotKbOverBgAddCornersSmall();
|
||||||
|
const auto &large = st->msgBotKbOverBgAddCornersLarge();
|
||||||
|
auto over = Ui::CornersPixmaps();
|
||||||
|
for (auto i = 0; i != 4; ++i) {
|
||||||
|
over.p[i] = (rounding[i] == Corner::Large ? large : small).p[i];
|
||||||
|
}
|
||||||
|
Ui::FillRoundRect(p, rect, st->msgBotKbOverBgAdd(), over);
|
||||||
|
p.setOpacity(o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void KeyboardStyle::paintButtonIcon(
|
||||||
|
QPainter &p,
|
||||||
|
const Ui::ChatStyle *st,
|
||||||
|
const QRect &rect,
|
||||||
|
int outerWidth,
|
||||||
|
HistoryMessageMarkupButton::Type type) const {
|
||||||
|
Expects(st != nullptr);
|
||||||
|
|
||||||
|
using Type = HistoryMessageMarkupButton::Type;
|
||||||
|
const auto icon = [&]() -> const style::icon* {
|
||||||
|
switch (type) {
|
||||||
|
case Type::Url:
|
||||||
|
case Type::Auth: return &st->msgBotKbUrlIcon();
|
||||||
|
case Type::Buy: return &st->msgBotKbPaymentIcon();
|
||||||
|
case Type::SwitchInlineSame:
|
||||||
|
case Type::SwitchInline: return &st->msgBotKbSwitchPmIcon();
|
||||||
|
case Type::WebView:
|
||||||
|
case Type::SimpleWebView: return &st->msgBotKbWebviewIcon();
|
||||||
|
case Type::CopyText: return &st->msgBotKbCopyIcon();
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}();
|
||||||
|
if (icon) {
|
||||||
|
icon->paint(p, rect.x() + rect.width() - icon->width() - st::msgBotKbIconPadding, rect.y() + st::msgBotKbIconPadding, outerWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void KeyboardStyle::paintButtonLoading(
|
||||||
|
QPainter &p,
|
||||||
|
const Ui::ChatStyle *st,
|
||||||
|
const QRect &rect,
|
||||||
|
int outerWidth,
|
||||||
|
Ui::BubbleRounding rounding) const {
|
||||||
|
Expects(st != nullptr);
|
||||||
|
|
||||||
|
if (anim::Disabled()) {
|
||||||
|
const auto &icon = st->historySendingInvertedIcon();
|
||||||
|
icon.paint(
|
||||||
|
p,
|
||||||
|
rect::right(rect) - icon.width() - st::msgBotKbIconPadding,
|
||||||
|
rect::bottom(rect) - icon.height() - st::msgBotKbIconPadding,
|
||||||
|
rect.x() * 2 + rect.width());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto cacheKey = rounding.key();
|
||||||
|
auto &cachedBg = _cachedBg[cacheKey];
|
||||||
|
if (!cachedBg.image.isNull()) {
|
||||||
|
if (_glare && _glare->glare.birthTime) {
|
||||||
|
const auto progress = _glare->progress(crl::now());
|
||||||
|
const auto w = _glare->width;
|
||||||
|
const auto h = rect.height();
|
||||||
|
const auto x = (-w) + (w * 2) * progress;
|
||||||
|
|
||||||
|
auto frame = cachedBg.image;
|
||||||
|
frame.fill(Qt::transparent);
|
||||||
|
{
|
||||||
|
auto painter = QPainter(&frame);
|
||||||
|
auto hq = PainterHighQualityEnabler(painter);
|
||||||
|
painter.setPen(Qt::NoPen);
|
||||||
|
painter.drawTiledPixmap(x, 0, w, h, _glare->pixmap, 0, 0);
|
||||||
|
|
||||||
|
auto path = QPainterPath();
|
||||||
|
path.addRect(Rect(rect.size()));
|
||||||
|
path -= _cachedOutline[cacheKey];
|
||||||
|
|
||||||
|
constexpr auto kBgOutlineAlpha = 0.5;
|
||||||
|
constexpr auto kFgOutlineAlpha = 0.8;
|
||||||
|
const auto &c = st::premiumButtonFg->c;
|
||||||
|
painter.setPen(Qt::NoPen);
|
||||||
|
painter.setBrush(c);
|
||||||
|
painter.setOpacity(kBgOutlineAlpha);
|
||||||
|
painter.drawPath(path);
|
||||||
|
auto gradient = QLinearGradient(-w, 0, w * 2, 0);
|
||||||
|
{
|
||||||
|
constexpr auto kShiftLeft = 0.01;
|
||||||
|
constexpr auto kShiftRight = 0.99;
|
||||||
|
auto stops = _glare->computeGradient(c).stops();
|
||||||
|
stops[1] = {
|
||||||
|
std::clamp(progress, kShiftLeft, kShiftRight),
|
||||||
|
QColor(c.red(), c.green(), c.blue(), kFgOutlineAlpha),
|
||||||
|
};
|
||||||
|
gradient.setStops(std::move(stops));
|
||||||
|
}
|
||||||
|
painter.setBrush(QBrush(gradient));
|
||||||
|
painter.setOpacity(1);
|
||||||
|
painter.drawPath(path);
|
||||||
|
|
||||||
|
painter.setCompositionMode(
|
||||||
|
QPainter::CompositionMode_DestinationIn);
|
||||||
|
painter.drawImage(0, 0, cachedBg.image);
|
||||||
|
}
|
||||||
|
p.drawImage(rect.x(), rect.y(), frame);
|
||||||
|
} else {
|
||||||
|
_glare = std::make_unique<Ui::GlareEffect>();
|
||||||
|
_glare->width = outerWidth;
|
||||||
|
|
||||||
|
constexpr auto kTimeout = crl::time(0);
|
||||||
|
constexpr auto kDuration = crl::time(1100);
|
||||||
|
const auto color = st::premiumButtonFg->c;
|
||||||
|
_glare->validate(color, _repaint, kTimeout, kDuration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int KeyboardStyle::minButtonWidth(
|
||||||
|
HistoryMessageMarkupButton::Type type) const {
|
||||||
|
using Type = HistoryMessageMarkupButton::Type;
|
||||||
|
int result = 2 * buttonPadding(), iconWidth = 0;
|
||||||
|
switch (type) {
|
||||||
|
case Type::Url:
|
||||||
|
case Type::Auth: iconWidth = st::msgBotKbUrlIcon.width(); break;
|
||||||
|
case Type::Buy: iconWidth = st::msgBotKbPaymentIcon.width(); break;
|
||||||
|
case Type::SwitchInlineSame:
|
||||||
|
case Type::SwitchInline: iconWidth = st::msgBotKbSwitchPmIcon.width(); break;
|
||||||
|
case Type::Callback:
|
||||||
|
case Type::CallbackWithPassword:
|
||||||
|
case Type::Game: iconWidth = st::historySendingInvertedIcon.width(); break;
|
||||||
|
case Type::WebView:
|
||||||
|
case Type::SimpleWebView: iconWidth = st::msgBotKbWebviewIcon.width(); break;
|
||||||
|
case Type::CopyText: return st::msgBotKbCopyIcon.width(); break;
|
||||||
|
}
|
||||||
|
if (iconWidth > 0) {
|
||||||
|
result = std::max(result, 2 * iconWidth + 4 * int(st::msgBotKbIconPadding));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] bool IsAttachedToPreviousInSavedMessages(
|
[[nodiscard]] bool IsAttachedToPreviousInSavedMessages(
|
||||||
not_null<HistoryItem*> previous,
|
not_null<HistoryItem*> previous,
|
||||||
HistoryMessageForwarded *prevForwarded,
|
HistoryMessageForwarded *prevForwarded,
|
||||||
@@ -1463,6 +1747,24 @@ void Element::validateTextSkipBlock(bool has, int width, int height) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Element::validateInlineKeyboard(HistoryMessageReplyMarkup *markup) {
|
||||||
|
if (!markup
|
||||||
|
|| markup->inlineKeyboard
|
||||||
|
|| markup->hiddenBy(data()->media())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto item = data();
|
||||||
|
//if (item->hideLinks()) {
|
||||||
|
// item->setHasHiddenLinks(true);
|
||||||
|
// return;
|
||||||
|
//}
|
||||||
|
markup->inlineKeyboard = std::make_unique<ReplyKeyboard>(
|
||||||
|
item,
|
||||||
|
std::make_unique<KeyboardStyle>(
|
||||||
|
st::msgBotKbButton,
|
||||||
|
[=] { item->history()->owner().requestItemRepaint(item); }));
|
||||||
|
}
|
||||||
|
|
||||||
void Element::previousInBlocksChanged() {
|
void Element::previousInBlocksChanged() {
|
||||||
recountThreadBarInBlocks();
|
recountThreadBarInBlocks();
|
||||||
recountDisplayDateInBlocks();
|
recountDisplayDateInBlocks();
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ class HistoryBlock;
|
|||||||
class HistoryItem;
|
class HistoryItem;
|
||||||
struct HistoryMessageReply;
|
struct HistoryMessageReply;
|
||||||
struct PreparedServiceText;
|
struct PreparedServiceText;
|
||||||
|
struct HistoryMessageReplyMarkup;
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
class Thread;
|
class Thread;
|
||||||
@@ -693,6 +694,7 @@ protected:
|
|||||||
[[nodiscard]] int textHeightFor(int textWidth);
|
[[nodiscard]] int textHeightFor(int textWidth);
|
||||||
void validateText();
|
void validateText();
|
||||||
void validateTextSkipBlock(bool has, int width, int height);
|
void validateTextSkipBlock(bool has, int width, int height);
|
||||||
|
void validateInlineKeyboard(HistoryMessageReplyMarkup *markup);
|
||||||
|
|
||||||
void clearSpecialOnlyEmoji();
|
void clearSpecialOnlyEmoji();
|
||||||
void checkSpecialOnlyEmoji();
|
void checkSpecialOnlyEmoji();
|
||||||
|
|||||||
@@ -26,13 +26,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "boxes/premium_preview_box.h"
|
#include "boxes/premium_preview_box.h"
|
||||||
#include "boxes/share_box.h"
|
#include "boxes/share_box.h"
|
||||||
#include "ui/effects/glare.h"
|
|
||||||
#include "ui/effects/reaction_fly_animation.h"
|
#include "ui/effects/reaction_fly_animation.h"
|
||||||
#include "ui/text/text_utilities.h"
|
#include "ui/text/text_utilities.h"
|
||||||
#include "ui/text/text_extended_data.h"
|
#include "ui/text/text_extended_data.h"
|
||||||
#include "ui/power_saving.h"
|
#include "ui/power_saving.h"
|
||||||
#include "ui/rect.h"
|
#include "ui/rect.h"
|
||||||
#include "ui/round_rect.h"
|
//#include "ui/round_rect.h"
|
||||||
#include "data/components/factchecks.h"
|
#include "data/components/factchecks.h"
|
||||||
#include "data/components/sponsored_messages.h"
|
#include "data/components/sponsored_messages.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
@@ -59,286 +58,6 @@ namespace {
|
|||||||
constexpr auto kPlayStatusLimit = 2;
|
constexpr auto kPlayStatusLimit = 2;
|
||||||
const auto kPsaTooltipPrefix = "cloud_lng_tooltip_psa_";
|
const auto kPsaTooltipPrefix = "cloud_lng_tooltip_psa_";
|
||||||
|
|
||||||
class KeyboardStyle : public ReplyKeyboard::Style {
|
|
||||||
public:
|
|
||||||
KeyboardStyle(const style::BotKeyboardButton &st, Fn<void()> repaint);
|
|
||||||
|
|
||||||
Images::CornersMaskRef buttonRounding(
|
|
||||||
Ui::BubbleRounding outer,
|
|
||||||
RectParts sides) const override;
|
|
||||||
|
|
||||||
void startPaint(
|
|
||||||
QPainter &p,
|
|
||||||
const Ui::ChatStyle *st) const override;
|
|
||||||
const style::TextStyle &textStyle() const override;
|
|
||||||
void repaint(not_null<const HistoryItem*> item) const override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void paintButtonBg(
|
|
||||||
QPainter &p,
|
|
||||||
const Ui::ChatStyle *st,
|
|
||||||
const QRect &rect,
|
|
||||||
Ui::BubbleRounding rounding,
|
|
||||||
float64 howMuchOver) const override;
|
|
||||||
void paintButtonIcon(
|
|
||||||
QPainter &p,
|
|
||||||
const Ui::ChatStyle *st,
|
|
||||||
const QRect &rect,
|
|
||||||
int outerWidth,
|
|
||||||
HistoryMessageMarkupButton::Type type) const override;
|
|
||||||
void paintButtonLoading(
|
|
||||||
QPainter &p,
|
|
||||||
const Ui::ChatStyle *st,
|
|
||||||
const QRect &rect,
|
|
||||||
int outerWidth,
|
|
||||||
Ui::BubbleRounding rounding) const override;
|
|
||||||
int minButtonWidth(HistoryMessageMarkupButton::Type type) const override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
using BubbleRoundingKey = uchar;
|
|
||||||
mutable base::flat_map<BubbleRoundingKey, QImage> _cachedBg;
|
|
||||||
mutable base::flat_map<BubbleRoundingKey, QPainterPath> _cachedOutline;
|
|
||||||
mutable std::unique_ptr<Ui::GlareEffect> _glare;
|
|
||||||
Fn<void()> _repaint;
|
|
||||||
rpl::lifetime _lifetime;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
KeyboardStyle::KeyboardStyle(
|
|
||||||
const style::BotKeyboardButton &st,
|
|
||||||
Fn<void()> repaint)
|
|
||||||
: ReplyKeyboard::Style(st)
|
|
||||||
, _repaint(std::move(repaint)) {
|
|
||||||
style::PaletteChanged(
|
|
||||||
) | rpl::start_with_next([=] {
|
|
||||||
_cachedBg = {};
|
|
||||||
_cachedOutline = {};
|
|
||||||
}, _lifetime);
|
|
||||||
}
|
|
||||||
|
|
||||||
void KeyboardStyle::startPaint(
|
|
||||||
QPainter &p,
|
|
||||||
const Ui::ChatStyle *st) const {
|
|
||||||
Expects(st != nullptr);
|
|
||||||
|
|
||||||
p.setPen(st->msgServiceFg());
|
|
||||||
}
|
|
||||||
|
|
||||||
const style::TextStyle &KeyboardStyle::textStyle() const {
|
|
||||||
return st::serviceTextStyle;
|
|
||||||
}
|
|
||||||
|
|
||||||
void KeyboardStyle::repaint(not_null<const HistoryItem*> item) const {
|
|
||||||
item->history()->owner().requestItemRepaint(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
Images::CornersMaskRef KeyboardStyle::buttonRounding(
|
|
||||||
Ui::BubbleRounding outer,
|
|
||||||
RectParts sides) const {
|
|
||||||
using namespace Images;
|
|
||||||
using namespace Ui;
|
|
||||||
using Radius = CachedCornerRadius;
|
|
||||||
using Corner = BubbleCornerRounding;
|
|
||||||
auto result = CornersMaskRef(CachedCornersMasks(Radius::BubbleSmall));
|
|
||||||
if (sides & RectPart::Bottom) {
|
|
||||||
const auto &large = CachedCornersMasks(Radius::BubbleLarge);
|
|
||||||
auto round = [&](RectPart side, int index) {
|
|
||||||
if ((sides & side) && (outer[index] == Corner::Large)) {
|
|
||||||
result.p[index] = &large[index];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
round(RectPart::Left, kBottomLeft);
|
|
||||||
round(RectPart::Right, kBottomRight);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void KeyboardStyle::paintButtonBg(
|
|
||||||
QPainter &p,
|
|
||||||
const Ui::ChatStyle *st,
|
|
||||||
const QRect &rect,
|
|
||||||
Ui::BubbleRounding rounding,
|
|
||||||
float64 howMuchOver) const {
|
|
||||||
Expects(st != nullptr);
|
|
||||||
|
|
||||||
using Corner = Ui::BubbleCornerRounding;
|
|
||||||
auto &cachedBg = _cachedBg[rounding.key()];
|
|
||||||
|
|
||||||
if (cachedBg.isNull()
|
|
||||||
|| cachedBg.width() != (rect.width() * style::DevicePixelRatio())) {
|
|
||||||
cachedBg = QImage(
|
|
||||||
rect.size() * style::DevicePixelRatio(),
|
|
||||||
QImage::Format_ARGB32_Premultiplied);
|
|
||||||
cachedBg.setDevicePixelRatio(style::DevicePixelRatio());
|
|
||||||
cachedBg.fill(Qt::transparent);
|
|
||||||
{
|
|
||||||
auto painter = QPainter(&cachedBg);
|
|
||||||
|
|
||||||
const auto sti = &st->imageStyle(false);
|
|
||||||
const auto &small = sti->msgServiceBgCornersSmall;
|
|
||||||
const auto &large = sti->msgServiceBgCornersLarge;
|
|
||||||
auto corners = Ui::CornersPixmaps();
|
|
||||||
int radiuses[4];
|
|
||||||
for (auto i = 0; i != 4; ++i) {
|
|
||||||
const auto isLarge = (rounding[i] == Corner::Large);
|
|
||||||
corners.p[i] = (isLarge ? large : small).p[i];
|
|
||||||
radiuses[i] = Ui::CachedCornerRadiusValue(isLarge
|
|
||||||
? Ui::CachedCornerRadius::BubbleLarge
|
|
||||||
: Ui::CachedCornerRadius::BubbleSmall);
|
|
||||||
}
|
|
||||||
const auto r = Rect(rect.size());
|
|
||||||
_cachedOutline[rounding.key()] = Ui::ComplexRoundedRectPath(
|
|
||||||
r - Margins(st::lineWidth),
|
|
||||||
radiuses[0],
|
|
||||||
radiuses[1],
|
|
||||||
radiuses[2],
|
|
||||||
radiuses[3]);
|
|
||||||
Ui::FillRoundRect(painter, r, sti->msgServiceBg, corners);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.drawImage(rect.topLeft(), cachedBg);
|
|
||||||
if (howMuchOver > 0) {
|
|
||||||
auto o = p.opacity();
|
|
||||||
p.setOpacity(o * howMuchOver);
|
|
||||||
const auto &small = st->msgBotKbOverBgAddCornersSmall();
|
|
||||||
const auto &large = st->msgBotKbOverBgAddCornersLarge();
|
|
||||||
auto over = Ui::CornersPixmaps();
|
|
||||||
for (auto i = 0; i != 4; ++i) {
|
|
||||||
over.p[i] = (rounding[i] == Corner::Large ? large : small).p[i];
|
|
||||||
}
|
|
||||||
Ui::FillRoundRect(p, rect, st->msgBotKbOverBgAdd(), over);
|
|
||||||
p.setOpacity(o);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void KeyboardStyle::paintButtonIcon(
|
|
||||||
QPainter &p,
|
|
||||||
const Ui::ChatStyle *st,
|
|
||||||
const QRect &rect,
|
|
||||||
int outerWidth,
|
|
||||||
HistoryMessageMarkupButton::Type type) const {
|
|
||||||
Expects(st != nullptr);
|
|
||||||
|
|
||||||
using Type = HistoryMessageMarkupButton::Type;
|
|
||||||
const auto icon = [&]() -> const style::icon* {
|
|
||||||
switch (type) {
|
|
||||||
case Type::Url:
|
|
||||||
case Type::Auth: return &st->msgBotKbUrlIcon();
|
|
||||||
case Type::Buy: return &st->msgBotKbPaymentIcon();
|
|
||||||
case Type::SwitchInlineSame:
|
|
||||||
case Type::SwitchInline: return &st->msgBotKbSwitchPmIcon();
|
|
||||||
case Type::WebView:
|
|
||||||
case Type::SimpleWebView: return &st->msgBotKbWebviewIcon();
|
|
||||||
case Type::CopyText: return &st->msgBotKbCopyIcon();
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}();
|
|
||||||
if (icon) {
|
|
||||||
icon->paint(p, rect.x() + rect.width() - icon->width() - st::msgBotKbIconPadding, rect.y() + st::msgBotKbIconPadding, outerWidth);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void KeyboardStyle::paintButtonLoading(
|
|
||||||
QPainter &p,
|
|
||||||
const Ui::ChatStyle *st,
|
|
||||||
const QRect &rect,
|
|
||||||
int outerWidth,
|
|
||||||
Ui::BubbleRounding rounding) const {
|
|
||||||
Expects(st != nullptr);
|
|
||||||
|
|
||||||
if (anim::Disabled()) {
|
|
||||||
const auto &icon = st->historySendingInvertedIcon();
|
|
||||||
icon.paint(
|
|
||||||
p,
|
|
||||||
rect::right(rect) - icon.width() - st::msgBotKbIconPadding,
|
|
||||||
rect::bottom(rect) - icon.height() - st::msgBotKbIconPadding,
|
|
||||||
rect.x() * 2 + rect.width());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto cacheKey = rounding.key();
|
|
||||||
auto &cachedBg = _cachedBg[cacheKey];
|
|
||||||
if (!cachedBg.isNull()) {
|
|
||||||
if (_glare && _glare->glare.birthTime) {
|
|
||||||
const auto progress = _glare->progress(crl::now());
|
|
||||||
const auto w = _glare->width;
|
|
||||||
const auto h = rect.height();
|
|
||||||
const auto x = (-w) + (w * 2) * progress;
|
|
||||||
|
|
||||||
auto frame = cachedBg;
|
|
||||||
frame.fill(Qt::transparent);
|
|
||||||
{
|
|
||||||
auto painter = QPainter(&frame);
|
|
||||||
auto hq = PainterHighQualityEnabler(painter);
|
|
||||||
painter.setPen(Qt::NoPen);
|
|
||||||
painter.drawTiledPixmap(x, 0, w, h, _glare->pixmap, 0, 0);
|
|
||||||
|
|
||||||
auto path = QPainterPath();
|
|
||||||
path.addRect(Rect(rect.size()));
|
|
||||||
path -= _cachedOutline[cacheKey];
|
|
||||||
|
|
||||||
constexpr auto kBgOutlineAlpha = 0.5;
|
|
||||||
constexpr auto kFgOutlineAlpha = 0.8;
|
|
||||||
const auto &c = st::premiumButtonFg->c;
|
|
||||||
painter.setPen(Qt::NoPen);
|
|
||||||
painter.setBrush(c);
|
|
||||||
painter.setOpacity(kBgOutlineAlpha);
|
|
||||||
painter.drawPath(path);
|
|
||||||
auto gradient = QLinearGradient(-w, 0, w * 2, 0);
|
|
||||||
{
|
|
||||||
constexpr auto kShiftLeft = 0.01;
|
|
||||||
constexpr auto kShiftRight = 0.99;
|
|
||||||
auto stops = _glare->computeGradient(c).stops();
|
|
||||||
stops[1] = {
|
|
||||||
std::clamp(progress, kShiftLeft, kShiftRight),
|
|
||||||
QColor(c.red(), c.green(), c.blue(), kFgOutlineAlpha),
|
|
||||||
};
|
|
||||||
gradient.setStops(std::move(stops));
|
|
||||||
}
|
|
||||||
painter.setBrush(QBrush(gradient));
|
|
||||||
painter.setOpacity(1);
|
|
||||||
painter.drawPath(path);
|
|
||||||
|
|
||||||
painter.setCompositionMode(
|
|
||||||
QPainter::CompositionMode_DestinationIn);
|
|
||||||
painter.drawImage(0, 0, cachedBg);
|
|
||||||
}
|
|
||||||
p.drawImage(rect.x(), rect.y(), frame);
|
|
||||||
} else {
|
|
||||||
_glare = std::make_unique<Ui::GlareEffect>();
|
|
||||||
_glare->width = outerWidth;
|
|
||||||
|
|
||||||
constexpr auto kTimeout = crl::time(0);
|
|
||||||
constexpr auto kDuration = crl::time(1100);
|
|
||||||
const auto color = st::premiumButtonFg->c;
|
|
||||||
_glare->validate(color, _repaint, kTimeout, kDuration);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int KeyboardStyle::minButtonWidth(
|
|
||||||
HistoryMessageMarkupButton::Type type) const {
|
|
||||||
using Type = HistoryMessageMarkupButton::Type;
|
|
||||||
int result = 2 * buttonPadding(), iconWidth = 0;
|
|
||||||
switch (type) {
|
|
||||||
case Type::Url:
|
|
||||||
case Type::Auth: iconWidth = st::msgBotKbUrlIcon.width(); break;
|
|
||||||
case Type::Buy: iconWidth = st::msgBotKbPaymentIcon.width(); break;
|
|
||||||
case Type::SwitchInlineSame:
|
|
||||||
case Type::SwitchInline: iconWidth = st::msgBotKbSwitchPmIcon.width(); break;
|
|
||||||
case Type::Callback:
|
|
||||||
case Type::CallbackWithPassword:
|
|
||||||
case Type::Game: iconWidth = st::historySendingInvertedIcon.width(); break;
|
|
||||||
case Type::WebView:
|
|
||||||
case Type::SimpleWebView: iconWidth = st::msgBotKbWebviewIcon.width(); break;
|
|
||||||
case Type::CopyText: return st::msgBotKbCopyIcon.width(); break;
|
|
||||||
}
|
|
||||||
if (iconWidth > 0) {
|
|
||||||
result = std::max(result, 2 * iconWidth + 4 * int(st::msgBotKbIconPadding));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString FastForwardText() {
|
QString FastForwardText() {
|
||||||
return u"Forward"_q;
|
return u"Forward"_q;
|
||||||
}
|
}
|
||||||
@@ -3507,24 +3226,6 @@ bool Message::embedReactionsInBubble() const {
|
|||||||
return needInfoDisplay();
|
return needInfoDisplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Message::validateInlineKeyboard(HistoryMessageReplyMarkup *markup) {
|
|
||||||
if (!markup
|
|
||||||
|| markup->inlineKeyboard
|
|
||||||
|| markup->hiddenBy(data()->media())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto item = data();
|
|
||||||
//if (item->hideLinks()) {
|
|
||||||
// item->setHasHiddenLinks(true);
|
|
||||||
// return;
|
|
||||||
//}
|
|
||||||
markup->inlineKeyboard = std::make_unique<ReplyKeyboard>(
|
|
||||||
item,
|
|
||||||
std::make_unique<KeyboardStyle>(
|
|
||||||
st::msgBotKbButton,
|
|
||||||
[=] { item->history()->owner().requestItemRepaint(item); }));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Message::validateFromNameText(PeerData *from) const {
|
void Message::validateFromNameText(PeerData *from) const {
|
||||||
if (!from) {
|
if (!from) {
|
||||||
if (_fromNameStatus) {
|
if (_fromNameStatus) {
|
||||||
|
|||||||
@@ -292,7 +292,6 @@ private:
|
|||||||
void refreshInfoSkipBlock(HistoryItem *textItem);
|
void refreshInfoSkipBlock(HistoryItem *textItem);
|
||||||
[[nodiscard]] int monospaceMaxWidth() const;
|
[[nodiscard]] int monospaceMaxWidth() const;
|
||||||
|
|
||||||
void validateInlineKeyboard(HistoryMessageReplyMarkup *markup);
|
|
||||||
void updateViewButtonExistence();
|
void updateViewButtonExistence();
|
||||||
[[nodiscard]] int viewButtonHeight() const;
|
[[nodiscard]] int viewButtonHeight() const;
|
||||||
|
|
||||||
|
|||||||
@@ -200,6 +200,15 @@ void SetText(Ui::Text::String &text, const QString &content) {
|
|||||||
text.setText(st::serviceTextStyle, content, EmptyLineOptions);
|
text.setText(st::serviceTextStyle, content, EmptyLineOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Ui::BubbleRounding KeyboardRounding() {
|
||||||
|
return Ui::BubbleRounding{
|
||||||
|
.topLeft = Ui::BubbleCornerRounding::Large,
|
||||||
|
.topRight = Ui::BubbleCornerRounding::Large,
|
||||||
|
.bottomLeft = Ui::BubbleCornerRounding::Large,
|
||||||
|
.bottomRight = Ui::BubbleCornerRounding::Large,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
int WideChatWidth() {
|
int WideChatWidth() {
|
||||||
@@ -411,6 +420,20 @@ Service::Service(
|
|||||||
setupReactions(replacing);
|
setupReactions(replacing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Service::clickHandlerPressedChanged(
|
||||||
|
const ClickHandlerPtr &handler,
|
||||||
|
bool pressed) {
|
||||||
|
if (const auto markup = data()->Get<HistoryMessageReplyMarkup>()) {
|
||||||
|
if (const auto keyboard = markup->inlineKeyboard.get()) {
|
||||||
|
keyboard->clickHandlerPressedChanged(
|
||||||
|
handler,
|
||||||
|
pressed,
|
||||||
|
KeyboardRounding());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Element::clickHandlerPressedChanged(handler, pressed);
|
||||||
|
}
|
||||||
|
|
||||||
QRect Service::innerGeometry() const {
|
QRect Service::innerGeometry() const {
|
||||||
return countGeometry();
|
return countGeometry();
|
||||||
}
|
}
|
||||||
@@ -439,6 +462,14 @@ void Service::animateReaction(Ui::ReactionFlyAnimationArgs &&args) {
|
|||||||
}
|
}
|
||||||
const auto repainter = [=] { repaint(); };
|
const auto repainter = [=] { repaint(); };
|
||||||
|
|
||||||
|
const auto item = data();
|
||||||
|
const auto keyboard = item->inlineReplyKeyboard();
|
||||||
|
auto keyboardHeight = 0;
|
||||||
|
if (keyboard) {
|
||||||
|
keyboardHeight = keyboard->naturalHeight();
|
||||||
|
g.setHeight(g.height() - st::msgBotKbButton.margin - keyboardHeight);
|
||||||
|
}
|
||||||
|
|
||||||
if (_reactions) {
|
if (_reactions) {
|
||||||
const auto reactionsHeight = st::mediaInBubbleSkip + _reactions->height();
|
const auto reactionsHeight = st::mediaInBubbleSkip + _reactions->height();
|
||||||
const auto reactionsLeft = 0;
|
const auto reactionsLeft = 0;
|
||||||
@@ -496,11 +527,21 @@ QSize Service::performCountCurrentSize(int newWidth) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto item = data();
|
||||||
|
if (const auto keyboard = item->inlineReplyKeyboard()) {
|
||||||
|
const auto keyboardWidth = mediaDisplayed ? media->width() : contentWidth;
|
||||||
|
const auto keyboardHeight = st::msgBotKbButton.margin + keyboard->naturalHeight();
|
||||||
|
newHeight += keyboardHeight;
|
||||||
|
keyboard->resize(keyboardWidth, keyboardHeight - st::msgBotKbButton.margin);
|
||||||
|
}
|
||||||
|
|
||||||
return { newWidth, newHeight };
|
return { newWidth, newHeight };
|
||||||
}
|
}
|
||||||
|
|
||||||
QSize Service::performCountOptimalSize() {
|
QSize Service::performCountOptimalSize() {
|
||||||
|
const auto markup = data()->inlineReplyMarkup();
|
||||||
validateText();
|
validateText();
|
||||||
|
validateInlineKeyboard(markup);
|
||||||
|
|
||||||
if (_reactions) {
|
if (_reactions) {
|
||||||
_reactions->initDimensions();
|
_reactions->initDimensions();
|
||||||
@@ -591,6 +632,31 @@ void Service::draw(Painter &p, const PaintContext &context) const {
|
|||||||
const auto mediaDisplayed = media && media->isDisplayed();
|
const auto mediaDisplayed = media && media->isDisplayed();
|
||||||
const auto onlyMedia = (mediaDisplayed && media->hideServiceText());
|
const auto onlyMedia = (mediaDisplayed && media->hideServiceText());
|
||||||
|
|
||||||
|
const auto item = data();
|
||||||
|
const auto keyboard = item->inlineReplyKeyboard();
|
||||||
|
const auto fullGeometry = g;
|
||||||
|
if (keyboard) {
|
||||||
|
// We need to count geometry without keyboard for bubble selection
|
||||||
|
// intervals counting below.
|
||||||
|
const auto keyboardHeight = st::msgBotKbButton.margin + keyboard->naturalHeight();
|
||||||
|
g.setHeight(g.height() - keyboardHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyboard) {
|
||||||
|
const auto keyboardWidth = mediaDisplayed ? media->width() : g.width();
|
||||||
|
const auto keyboardPosition = QPoint(
|
||||||
|
g.left() + (g.width() - keyboardWidth) / 2,
|
||||||
|
g.top() + g.height() + st::msgBotKbButton.margin);
|
||||||
|
p.translate(keyboardPosition);
|
||||||
|
keyboard->paint(
|
||||||
|
p,
|
||||||
|
context.st,
|
||||||
|
KeyboardRounding(),
|
||||||
|
keyboardWidth,
|
||||||
|
context.clip.translated(-keyboardPosition));
|
||||||
|
p.translate(-keyboardPosition);
|
||||||
|
}
|
||||||
|
|
||||||
if (_reactions) {
|
if (_reactions) {
|
||||||
const auto reactionsHeight = st::mediaInBubbleSkip + _reactions->height();
|
const auto reactionsHeight = st::mediaInBubbleSkip + _reactions->height();
|
||||||
const auto reactionsLeft = 0;
|
const auto reactionsLeft = 0;
|
||||||
@@ -680,6 +746,26 @@ TextState Service::textState(QPoint point, StateRequest request) const {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto keyboard = item->inlineReplyKeyboard();
|
||||||
|
auto keyboardHeight = 0;
|
||||||
|
if (keyboard) {
|
||||||
|
keyboardHeight = keyboard->naturalHeight();
|
||||||
|
g.setHeight(g.height() - st::msgBotKbButton.margin - keyboardHeight);
|
||||||
|
|
||||||
|
if (item->isHistoryEntry()) {
|
||||||
|
const auto keyboardWidth = mediaDisplayed ? media->width() : g.width();
|
||||||
|
const auto keyboardPosition = QPoint(
|
||||||
|
g.left() + (g.width() - keyboardWidth) / 2,
|
||||||
|
g.top() + g.height() + st::msgBotKbButton.margin);
|
||||||
|
if (QRect(keyboardPosition, QSize(keyboardWidth, keyboardHeight)).contains(point)) {
|
||||||
|
result.link = keyboard->getLink(point - keyboardPosition);
|
||||||
|
if (result.link) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (const auto service = Get<ServicePreMessage>()) {
|
if (const auto service = Get<ServicePreMessage>()) {
|
||||||
result.link = service->textState(point, request, g);
|
result.link = service->textState(point, request, g);
|
||||||
if (result.link) {
|
if (result.link) {
|
||||||
@@ -698,7 +784,6 @@ TextState Service::textState(QPoint point, StateRequest request) const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (onlyMedia) {
|
if (onlyMedia) {
|
||||||
return media->textState(point - QPoint(st::msgServiceMargin.left() + (g.width() - media->width()) / 2, g.top()), request);
|
return media->textState(point - QPoint(st::msgServiceMargin.left() + (g.width() - media->width()) / 2, g.top()), request);
|
||||||
} else if (mediaDisplayed) {
|
} else if (mediaDisplayed) {
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ public:
|
|||||||
not_null<HistoryItem*> data,
|
not_null<HistoryItem*> data,
|
||||||
Element *replacing);
|
Element *replacing);
|
||||||
|
|
||||||
|
void clickHandlerPressedChanged(
|
||||||
|
const ClickHandlerPtr &handler,
|
||||||
|
bool pressed) override;
|
||||||
|
|
||||||
int marginTop() const override;
|
int marginTop() const override;
|
||||||
int marginBottom() const override;
|
int marginBottom() const override;
|
||||||
bool isHidden() const override;
|
bool isHidden() const override;
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "history/view/media/history_view_service_box.h"
|
#include "history/view/media/history_view_service_box.h"
|
||||||
|
|
||||||
#include "core/ui_integration.h"
|
#include "core/ui_integration.h"
|
||||||
|
#include "data/data_session.h"
|
||||||
#include "history/view/media/history_view_sticker_player_abstract.h"
|
#include "history/view/media/history_view_sticker_player_abstract.h"
|
||||||
#include "history/view/history_view_cursor_state.h"
|
#include "history/view/history_view_cursor_state.h"
|
||||||
#include "history/view/history_view_element.h"
|
#include "history/view/history_view_element.h"
|
||||||
#include "history/view/history_view_text_helper.h"
|
#include "history/view/history_view_text_helper.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
|
#include "history/history_item.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "ui/chat/chat_style.h"
|
#include "ui/chat/chat_style.h"
|
||||||
#include "ui/effects/animation_value.h"
|
#include "ui/effects/animation_value.h"
|
||||||
@@ -122,10 +124,51 @@ ServiceBox::ServiceBox(
|
|||||||
*type);
|
*type);
|
||||||
_button.lastFg = std::make_unique<QColor>();
|
_button.lastFg = std::make_unique<QColor>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (auto changes = _content->changes()) {
|
||||||
|
std::move(changes) | rpl::start_with_next([=] {
|
||||||
|
applyContentChanges();
|
||||||
|
}, _lifetime);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ServiceBox::~ServiceBox() = default;
|
ServiceBox::~ServiceBox() = default;
|
||||||
|
|
||||||
|
void ServiceBox::applyContentChanges() {
|
||||||
|
const auto subtitleWas = _subtitle.countHeight(_maxWidth);
|
||||||
|
|
||||||
|
const auto parent = _parent;
|
||||||
|
_subtitle = Ui::Text::String(
|
||||||
|
st::premiumPreviewAbout.style,
|
||||||
|
Ui::Text::Filtered(
|
||||||
|
_content->subtitle(),
|
||||||
|
{
|
||||||
|
EntityType::Bold,
|
||||||
|
EntityType::StrikeOut,
|
||||||
|
EntityType::Underline,
|
||||||
|
EntityType::Italic,
|
||||||
|
EntityType::Spoiler,
|
||||||
|
EntityType::CustomEmoji,
|
||||||
|
}),
|
||||||
|
kMarkupTextOptions,
|
||||||
|
_maxWidth,
|
||||||
|
Core::TextContext({
|
||||||
|
.session = &parent->history()->session(),
|
||||||
|
.repaint = [parent] { parent->customEmojiRepaint(); },
|
||||||
|
}));
|
||||||
|
InitElementTextPart(parent, _subtitle);
|
||||||
|
const auto subtitleNow = _subtitle.countHeight(_maxWidth);
|
||||||
|
if (subtitleNow != subtitleWas) {
|
||||||
|
_size.setHeight(_size.height() - subtitleWas + subtitleNow);
|
||||||
|
_innerSize = _size - QSize(0, st::msgServiceGiftBoxTopSkip);
|
||||||
|
|
||||||
|
const auto item = parent->data();
|
||||||
|
item->history()->owner().requestItemResize(item);
|
||||||
|
} else {
|
||||||
|
parent->repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QSize ServiceBox::countOptimalSize() {
|
QSize ServiceBox::countOptimalSize() {
|
||||||
return _size;
|
return _size;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,12 @@ public:
|
|||||||
return top();
|
return top();
|
||||||
}
|
}
|
||||||
[[nodiscard]] virtual rpl::producer<QString> button() = 0;
|
[[nodiscard]] virtual rpl::producer<QString> button() = 0;
|
||||||
|
|
||||||
|
// For now only subtitle() changes are observed.
|
||||||
|
[[nodiscard]] virtual rpl::producer<> changes() {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] virtual auto buttonMinistars()
|
[[nodiscard]] virtual auto buttonMinistars()
|
||||||
-> std::optional<Ui::Premium::MiniStarsType> {
|
-> std::optional<Ui::Premium::MiniStarsType> {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
@@ -105,6 +111,8 @@ private:
|
|||||||
[[nodiscard]] QRect buttonRect() const;
|
[[nodiscard]] QRect buttonRect() const;
|
||||||
[[nodiscard]] QRect contentRect() const;
|
[[nodiscard]] QRect contentRect() const;
|
||||||
|
|
||||||
|
void applyContentChanges();
|
||||||
|
|
||||||
const not_null<Element*> _parent;
|
const not_null<Element*> _parent;
|
||||||
const std::unique_ptr<ServiceBoxContent> _content;
|
const std::unique_ptr<ServiceBoxContent> _content;
|
||||||
mutable ClickHandlerPtr _contentLink;
|
mutable ClickHandlerPtr _contentLink;
|
||||||
@@ -131,8 +139,8 @@ private:
|
|||||||
Ui::Text::String _title;
|
Ui::Text::String _title;
|
||||||
Ui::Text::String _author;
|
Ui::Text::String _author;
|
||||||
Ui::Text::String _subtitle;
|
Ui::Text::String _subtitle;
|
||||||
const QSize _size;
|
QSize _size;
|
||||||
const QSize _innerSize;
|
QSize _innerSize;
|
||||||
rpl::lifetime _lifetime;
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "history/view/media/history_view_theme_document.h"
|
#include "history/view/media/history_view_theme_document.h"
|
||||||
|
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
|
#include "base/unixtime.h"
|
||||||
#include "boxes/background_preview_box.h"
|
#include "boxes/background_preview_box.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
|
#include "history/history_item_components.h"
|
||||||
#include "history/view/history_view_element.h"
|
#include "history/view/history_view_element.h"
|
||||||
#include "history/view/history_view_cursor_state.h"
|
#include "history/view/history_view_cursor_state.h"
|
||||||
#include "history/view/media/history_view_sticker_player_abstract.h"
|
#include "history/view/media/history_view_sticker_player_abstract.h"
|
||||||
@@ -609,69 +611,181 @@ void ThemeDocumentBox::unloadHeavyPart() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GiftThemeBox::GiftThemeBox(
|
GiftServiceBox::GiftServiceBox(
|
||||||
not_null<Element*> parent,
|
not_null<Element*> parent,
|
||||||
not_null<Data::MediaGiftBox*> gift)
|
not_null<Data::MediaGiftBox*> gift)
|
||||||
: _parent(parent)
|
: _parent(parent)
|
||||||
, _data(*gift->gift()) {
|
, _data(*gift->gift()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
GiftThemeBox::~GiftThemeBox() = default;
|
GiftServiceBox::~GiftServiceBox() = default;
|
||||||
|
|
||||||
int GiftThemeBox::top() {
|
int GiftServiceBox::top() {
|
||||||
return st::msgServiceStarGiftStickerTop;
|
return st::msgServiceStarGiftStickerTop;
|
||||||
}
|
}
|
||||||
|
|
||||||
int GiftThemeBox::width() {
|
int GiftServiceBox::width() {
|
||||||
return st::msgServiceStarGiftBoxWidth;
|
return st::msgServiceStarGiftBoxWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSize GiftThemeBox::size() {
|
QSize GiftServiceBox::size() {
|
||||||
return QSize(
|
return QSize(
|
||||||
st::msgServiceGiftThemeStickerSize,
|
st::msgServiceGiftThemeStickerSize,
|
||||||
st::msgServiceGiftThemeStickerSize).grownBy(
|
st::msgServiceGiftThemeStickerSize).grownBy(
|
||||||
st::msgServiceGiftThemeStickerPadding);
|
st::msgServiceGiftThemeStickerPadding);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextWithEntities GiftThemeBox::title() {
|
TextWithEntities GiftServiceBox::title() {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
TextWithEntities GiftThemeBox::subtitle() {
|
TextWithEntities GiftServiceBox::subtitle() {
|
||||||
const auto giftName = Ui::Text::Bold(
|
const auto giftName = tr::bold(Data::UniqueGiftName(*_data.unique));
|
||||||
Data::UniqueGiftName(*_data.unique));
|
if (_data.type == Data::GiftType::GiftOffer) {
|
||||||
if (_parent->data()->out()) {
|
const auto item = _parent->data();
|
||||||
|
if (const auto suggestion = item->Get<HistoryMessageSuggestion>()) {
|
||||||
|
const auto amount = suggestion->price;
|
||||||
|
const auto cost = tr::bold(PrepareCreditsAmountText(amount));
|
||||||
|
auto text = tr::marked();
|
||||||
|
if (_parent->data()->out()) {
|
||||||
|
text.append(tr::lng_action_gift_offer_you(
|
||||||
|
tr::now,
|
||||||
|
lt_cost,
|
||||||
|
cost,
|
||||||
|
lt_name,
|
||||||
|
giftName,
|
||||||
|
tr::marked));
|
||||||
|
} else {
|
||||||
|
text.append(tr::lng_action_gift_offer(
|
||||||
|
tr::now,
|
||||||
|
lt_user,
|
||||||
|
tr::bold(_parent->data()->from()->shortName()),
|
||||||
|
lt_cost,
|
||||||
|
cost,
|
||||||
|
lt_name,
|
||||||
|
giftName,
|
||||||
|
tr::marked));
|
||||||
|
}
|
||||||
|
text.append(u"\n\n"_q);
|
||||||
|
|
||||||
|
const auto ends = suggestion->date;
|
||||||
|
const auto now = base::unixtime::now();
|
||||||
|
const auto expired = (now >= ends);
|
||||||
|
|
||||||
|
checkKeyboardRemoval(suggestion, expired);
|
||||||
|
|
||||||
|
if (suggestion->accepted) {
|
||||||
|
text.append(
|
||||||
|
tr::lng_action_gift_offer_state_accepted(tr::now));
|
||||||
|
} else if (suggestion->rejected) {
|
||||||
|
text.append(
|
||||||
|
tr::lng_action_gift_offer_state_rejected(tr::now));
|
||||||
|
} else {
|
||||||
|
if (expired) {
|
||||||
|
text.append(
|
||||||
|
tr::lng_action_gift_offer_state_expired(tr::now));
|
||||||
|
} else {
|
||||||
|
auto time = QString();
|
||||||
|
const auto left = (ends - now) + 59;
|
||||||
|
if (left >= 3600) {
|
||||||
|
const auto hours = left / 3600;
|
||||||
|
const auto minutes = (left % 3600) / 60;
|
||||||
|
time = tr::lng_action_gift_offer_time_medium(
|
||||||
|
tr::now,
|
||||||
|
lt_hours,
|
||||||
|
QString::number(hours),
|
||||||
|
lt_minutes,
|
||||||
|
QString::number(minutes));
|
||||||
|
} else {
|
||||||
|
const auto minutes = left / 60;
|
||||||
|
time = tr::lng_action_gift_offer_time_small(
|
||||||
|
tr::now,
|
||||||
|
lt_minutes,
|
||||||
|
QString::number(minutes));
|
||||||
|
}
|
||||||
|
text.append(tr::lng_action_gift_offer_state_expires(
|
||||||
|
tr::now,
|
||||||
|
lt_time,
|
||||||
|
tr::bold(time),
|
||||||
|
tr::marked));
|
||||||
|
|
||||||
|
const auto tillNext = left % 60;
|
||||||
|
_changeTimer.setCallback([=] { _changes.fire({}); });
|
||||||
|
_changeTimer.callOnce((tillNext ? tillNext : 60)
|
||||||
|
* crl::time(1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
} else if (_parent->data()->out()) {
|
||||||
return tr::lng_action_you_gift_theme_changed(
|
return tr::lng_action_you_gift_theme_changed(
|
||||||
tr::now,
|
tr::now,
|
||||||
lt_name,
|
lt_name,
|
||||||
giftName,
|
giftName,
|
||||||
Ui::Text::WithEntities);
|
tr::marked);
|
||||||
} else {
|
} else {
|
||||||
return tr::lng_action_gift_theme_changed(
|
return tr::lng_action_gift_theme_changed(
|
||||||
tr::now,
|
tr::now,
|
||||||
lt_from,
|
lt_from,
|
||||||
Ui::Text::Bold(_parent->data()->from()->shortName()),
|
tr::bold(_parent->data()->from()->shortName()),
|
||||||
lt_name,
|
lt_name,
|
||||||
giftName,
|
giftName,
|
||||||
Ui::Text::WithEntities);
|
tr::marked);
|
||||||
}
|
}
|
||||||
return _parent->data()->originalText();
|
return _parent->data()->originalText();
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<QString> GiftThemeBox::button() {
|
void GiftServiceBox::checkKeyboardRemoval(
|
||||||
|
not_null<const HistoryMessageSuggestion*> suggestion,
|
||||||
|
bool expired) {
|
||||||
|
Expects(_data.type == Data::GiftType::GiftOffer);
|
||||||
|
|
||||||
|
const auto item = _parent->data();
|
||||||
|
if (const auto markup = item->Get<HistoryMessageReplyMarkup>()) {
|
||||||
|
if (!markup->data.isTrivial()) {
|
||||||
|
if (suggestion->accepted || suggestion->rejected || expired) {
|
||||||
|
crl::on_main(this, [=] {
|
||||||
|
clearKeyboard();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GiftServiceBox::clearKeyboard() {
|
||||||
|
Expects(_data.type == Data::GiftType::GiftOffer);
|
||||||
|
|
||||||
|
const auto item = _parent->data();
|
||||||
|
if (const auto markup = item->Get<HistoryMessageReplyMarkup>()) {
|
||||||
|
markup->updateSuggestControls(SuggestionActions::None);
|
||||||
|
item->history()->owner().requestItemResize(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<> GiftServiceBox::changes() {
|
||||||
|
if (_data.type != Data::GiftType::GiftOffer) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return _changes.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<QString> GiftServiceBox::button() {
|
||||||
|
if (_data.type == Data::GiftType::GiftOffer) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
return tr::lng_sticker_premium_view();
|
return tr::lng_sticker_premium_view();
|
||||||
}
|
}
|
||||||
|
|
||||||
ClickHandlerPtr GiftThemeBox::createViewLink() {
|
ClickHandlerPtr GiftServiceBox::createViewLink() {
|
||||||
return std::make_shared<UrlClickHandler>(
|
return std::make_shared<UrlClickHandler>(
|
||||||
u"tg://nft?slug="_q + _data.unique->slug);
|
u"tg://nft?slug="_q + _data.unique->slug);
|
||||||
}
|
}
|
||||||
|
|
||||||
int GiftThemeBox::buttonSkip() {
|
int GiftServiceBox::buttonSkip() {
|
||||||
return st::msgServiceGiftBoxButtonMargins.top();
|
return st::msgServiceGiftBoxButtonMargins.top();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GiftThemeBox::cacheUniqueBackground(int width, int height) {
|
void GiftServiceBox::cacheUniqueBackground(int width, int height) {
|
||||||
if (!_patternEmoji) {
|
if (!_patternEmoji) {
|
||||||
const auto session = &_parent->data()->history()->session();
|
const auto session = &_parent->data()->history()->session();
|
||||||
_patternEmoji = session->data().customEmojiManager().create(
|
_patternEmoji = session->data().customEmojiManager().create(
|
||||||
@@ -717,7 +831,7 @@ void GiftThemeBox::cacheUniqueBackground(int width, int height) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GiftThemeBox::draw(
|
void GiftServiceBox::draw(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
const PaintContext &context,
|
const PaintContext &context,
|
||||||
const QRect &geometry) {
|
const QRect &geometry) {
|
||||||
@@ -734,17 +848,17 @@ void GiftThemeBox::draw(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GiftThemeBox::hideServiceText() {
|
bool GiftServiceBox::hideServiceText() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GiftThemeBox::stickerClearLoopPlayed() {
|
void GiftServiceBox::stickerClearLoopPlayed() {
|
||||||
if (_sticker) {
|
if (_sticker) {
|
||||||
_sticker->stickerClearLoopPlayed();
|
_sticker->stickerClearLoopPlayed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<StickerPlayer> GiftThemeBox::stickerTakePlayer(
|
std::unique_ptr<StickerPlayer> GiftServiceBox::stickerTakePlayer(
|
||||||
not_null<DocumentData*> data,
|
not_null<DocumentData*> data,
|
||||||
const Lottie::ColorReplacements *replacements) {
|
const Lottie::ColorReplacements *replacements) {
|
||||||
return _sticker
|
return _sticker
|
||||||
@@ -752,17 +866,17 @@ std::unique_ptr<StickerPlayer> GiftThemeBox::stickerTakePlayer(
|
|||||||
: nullptr;
|
: nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GiftThemeBox::hasHeavyPart() {
|
bool GiftServiceBox::hasHeavyPart() {
|
||||||
return (_sticker ? _sticker->hasHeavyPart() : false);
|
return (_sticker ? _sticker->hasHeavyPart() : false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GiftThemeBox::unloadHeavyPart() {
|
void GiftServiceBox::unloadHeavyPart() {
|
||||||
if (_sticker) {
|
if (_sticker) {
|
||||||
_sticker->unloadHeavyPart();
|
_sticker->unloadHeavyPart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GiftThemeBox::ensureStickerCreated() const {
|
void GiftServiceBox::ensureStickerCreated() const {
|
||||||
if (_sticker) {
|
if (_sticker) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
#include "history/view/media/history_view_sticker.h"
|
#include "history/view/media/history_view_sticker.h"
|
||||||
|
|
||||||
class Image;
|
class Image;
|
||||||
|
struct HistoryMessageSuggestion;
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
class DocumentMedia;
|
class DocumentMedia;
|
||||||
@@ -131,12 +132,14 @@ private:
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class GiftThemeBox final : public ServiceBoxContent {
|
class GiftServiceBox final
|
||||||
|
: public ServiceBoxContent
|
||||||
|
, public base::has_weak_ptr {
|
||||||
public:
|
public:
|
||||||
GiftThemeBox(
|
GiftServiceBox(
|
||||||
not_null<Element*> parent,
|
not_null<Element*> parent,
|
||||||
not_null<Data::MediaGiftBox*> gift);
|
not_null<Data::MediaGiftBox*> gift);
|
||||||
~GiftThemeBox();
|
~GiftServiceBox();
|
||||||
|
|
||||||
int top() override;
|
int top() override;
|
||||||
int width() override;
|
int width() override;
|
||||||
@@ -151,6 +154,8 @@ public:
|
|||||||
const QRect &geometry) override;
|
const QRect &geometry) override;
|
||||||
ClickHandlerPtr createViewLink() override;
|
ClickHandlerPtr createViewLink() override;
|
||||||
|
|
||||||
|
rpl::producer<> changes() override;
|
||||||
|
|
||||||
bool hideServiceText() override;
|
bool hideServiceText() override;
|
||||||
void stickerClearLoopPlayed() override;
|
void stickerClearLoopPlayed() override;
|
||||||
std::unique_ptr<StickerPlayer> stickerTakePlayer(
|
std::unique_ptr<StickerPlayer> stickerTakePlayer(
|
||||||
@@ -163,6 +168,10 @@ public:
|
|||||||
private:
|
private:
|
||||||
void ensureStickerCreated() const;
|
void ensureStickerCreated() const;
|
||||||
void cacheUniqueBackground(int width, int height);
|
void cacheUniqueBackground(int width, int height);
|
||||||
|
void checkKeyboardRemoval(
|
||||||
|
not_null<const HistoryMessageSuggestion*> suggestion,
|
||||||
|
bool expired);
|
||||||
|
void clearKeyboard();
|
||||||
|
|
||||||
const not_null<Element*> _parent;
|
const not_null<Element*> _parent;
|
||||||
const Data::GiftCode &_data;
|
const Data::GiftCode &_data;
|
||||||
@@ -171,6 +180,8 @@ private:
|
|||||||
base::flat_map<float64, QImage> _patternCache;
|
base::flat_map<float64, QImage> _patternCache;
|
||||||
bool _backroundPatterned = false;
|
bool _backroundPatterned = false;
|
||||||
mutable std::optional<Sticker> _sticker;
|
mutable std::optional<Sticker> _sticker;
|
||||||
|
rpl::event_stream<> _changes;
|
||||||
|
base::Timer _changeTimer;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user