mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-14 13:17:08 +02:00
Implement emoji status set from miniapps.
This commit is contained in:
parent
21487641c1
commit
4198203a7f
15 changed files with 281 additions and 11 deletions
|
@ -3418,6 +3418,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_bot_add_to_side_menu_done" = "Bot added to the main menu.";
|
||||
"lng_bot_no_scan_qr" = "QR Codes for bots are not supported on Desktop. Please use one of Telegram's mobile apps.";
|
||||
"lng_bot_no_share_story" = "Sharing to Stories is not supported on Desktop. Please use one of Telegram's mobile apps.";
|
||||
"lng_bot_emoji_status_confirm" = "Confirm";
|
||||
"lng_bot_emoji_status_title" = "Set Emoji Status";
|
||||
"lng_bot_emoji_status_text" = "Do you want to set this emoji status suggested by {bot}?";
|
||||
|
||||
"lng_bot_status_users#one" = "{count} monthly user";
|
||||
"lng_bot_status_users#other" = "{count} monthly users";
|
||||
|
|
|
@ -1225,6 +1225,9 @@ not_null<CustomEmojiManager::Listener*> Reactions::resolveListener() {
|
|||
}
|
||||
|
||||
void Reactions::customEmojiResolveDone(not_null<DocumentData*> document) {
|
||||
if (!document->sticker()) {
|
||||
return;
|
||||
}
|
||||
const auto id = ReactionId{ { document->id } };
|
||||
const auto favorite = (_unresolvedFavoriteId == id);
|
||||
const auto i = _unresolvedTop.find(id);
|
||||
|
|
|
@ -47,6 +47,7 @@ struct BotInfo {
|
|||
bool cantJoinGroups : 1 = false;
|
||||
bool supportsAttachMenu : 1 = false;
|
||||
bool canEditInformation : 1 = false;
|
||||
bool canManageEmojiStatus : 1 = false;
|
||||
bool supportsBusiness : 1 = false;
|
||||
bool hasMainApp : 1 = false;
|
||||
};
|
||||
|
|
|
@ -652,22 +652,27 @@ void CustomEmojiManager::unregisterListener(not_null<Listener*> listener) {
|
|||
}
|
||||
}
|
||||
|
||||
rpl::producer<not_null<DocumentData*>> CustomEmojiManager::resolve(
|
||||
DocumentId documentId) {
|
||||
auto CustomEmojiManager::resolve(DocumentId documentId)
|
||||
-> rpl::producer<not_null<DocumentData*>, rpl::empty_error> {
|
||||
return [=](auto consumer) {
|
||||
auto result = rpl::lifetime();
|
||||
const auto put = [=](not_null<DocumentData*> document) {
|
||||
const auto put = [=](
|
||||
not_null<DocumentData*> document,
|
||||
bool resolved = true) {
|
||||
if (!document->sticker()) {
|
||||
if (resolved) {
|
||||
consumer.put_error({});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
consumer.put_next_copy(document);
|
||||
return true;
|
||||
};
|
||||
if (!put(owner().document(documentId))) {
|
||||
const auto listener = new CallbackListener(put);
|
||||
if (!put(owner().document(documentId), false)) {
|
||||
const auto listener = result.make_state<CallbackListener>(put);
|
||||
resolve(documentId, listener);
|
||||
result.add([=] {
|
||||
unregisterListener(listener);
|
||||
delete listener;
|
||||
});
|
||||
}
|
||||
return result;
|
||||
|
@ -763,6 +768,9 @@ void CustomEmojiManager::request() {
|
|||
requestFinished();
|
||||
}).fail([=] {
|
||||
LOG(("API Error: Failed to get documents for emoji."));
|
||||
for (const auto &id : ids) {
|
||||
processListeners(_owner->document(id.v));
|
||||
}
|
||||
requestFinished();
|
||||
}).send();
|
||||
}
|
||||
|
@ -792,7 +800,8 @@ void CustomEmojiManager::processLoaders(not_null<DocumentData*> document) {
|
|||
}
|
||||
}
|
||||
|
||||
void CustomEmojiManager::processListeners(not_null<DocumentData*> document) {
|
||||
void CustomEmojiManager::processListeners(
|
||||
not_null<DocumentData*> document) {
|
||||
const auto id = document->id;
|
||||
if (const auto listeners = _resolvers.take(id)) {
|
||||
for (const auto &listener : *listeners) {
|
||||
|
|
|
@ -66,8 +66,8 @@ public:
|
|||
void resolve(DocumentId documentId, not_null<Listener*> listener);
|
||||
void unregisterListener(not_null<Listener*> listener);
|
||||
|
||||
[[nodiscard]] rpl::producer<not_null<DocumentData*>> resolve(
|
||||
DocumentId documentId);
|
||||
[[nodiscard]] auto resolve(DocumentId documentId)
|
||||
-> rpl::producer<not_null<DocumentData*>, rpl::empty_error>;
|
||||
|
||||
[[nodiscard]] std::unique_ptr<Ui::CustomEmoji::Loader> createLoader(
|
||||
not_null<DocumentData*> document,
|
||||
|
|
|
@ -98,7 +98,7 @@ namespace {
|
|||
text.size(),
|
||||
Data::SerializeCustomEmojiId(document)) },
|
||||
};
|
||||
});
|
||||
}) | rpl::map_error_to_done();
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<TextWithEntities> PeerCustomStatus(
|
||||
|
|
|
@ -108,6 +108,9 @@ CustomEmoji::CustomEmoji(
|
|||
}
|
||||
|
||||
void CustomEmoji::customEmojiResolveDone(not_null<DocumentData*> document) {
|
||||
if (!document->sticker()) {
|
||||
return;
|
||||
}
|
||||
_resolving = false;
|
||||
const auto id = document->id;
|
||||
for (auto &line : _lines) {
|
||||
|
|
|
@ -162,7 +162,7 @@ void TopicIconView::setupPlayer(not_null<Data::ForumTopic*> topic) {
|
|||
id
|
||||
) | rpl::map([=](not_null<DocumentData*> document) {
|
||||
return document.get();
|
||||
});
|
||||
}) | rpl::map_error_to_done();
|
||||
}) | rpl::flatten_latest(
|
||||
) | rpl::map([=](DocumentData *document)
|
||||
-> rpl::producer<std::shared_ptr<StickerPlayer>> {
|
||||
|
|
|
@ -26,12 +26,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_changes.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_emoji_statuses.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_peer_bot_command.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_web_page.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "info/profile/info_profile_values.h"
|
||||
|
@ -43,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "mainwidget.h"
|
||||
#include "payments/payments_checkout_process.h"
|
||||
#include "payments/payments_non_panel_process.h"
|
||||
#include "settings/settings_premium.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "storage/storage_domain.h"
|
||||
#include "ui/basic_click_handlers.h"
|
||||
|
@ -428,6 +431,124 @@ void FillBotUsepic(
|
|||
Ui::IconWithTitle(box->verticalLayout(), userpic, title, aboutLabel);
|
||||
}
|
||||
|
||||
std::unique_ptr<Ui::RpWidget> MakeEmojiSetStatusPreview(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<PeerData*> peer,
|
||||
not_null<DocumentData*> document) {
|
||||
auto result = std::make_unique<Ui::RpWidget>(parent);
|
||||
|
||||
const auto size = st::chatGiveawayPeerSize;
|
||||
const auto padding = st::chatGiveawayPeerPadding;
|
||||
|
||||
const auto raw = result.get();
|
||||
|
||||
const auto width = raw->lifetime().make_state<int>();
|
||||
const auto name = raw->lifetime().make_state<Ui::FlatLabel>(
|
||||
raw,
|
||||
rpl::single(peer->name()),
|
||||
st::botEmojiStatusName);
|
||||
auto emojiText = TextWithEntities();
|
||||
const auto emoji = raw->lifetime().make_state<Ui::FlatLabel>(
|
||||
raw,
|
||||
rpl::single(emojiText),
|
||||
st::botEmojiStatusName);
|
||||
const auto userpic = raw->lifetime().make_state<Ui::UserpicButton>(
|
||||
raw,
|
||||
peer,
|
||||
st::botEmojiStatusUserpic);
|
||||
|
||||
raw->resize(size, size);
|
||||
raw->sizeValue() | rpl::start_with_next([=](QSize outer) {
|
||||
const auto full = outer.width();
|
||||
const auto decorations = size
|
||||
+ padding.left()
|
||||
+ padding.right()
|
||||
+ emoji->width()
|
||||
+ st::normalFont->spacew;
|
||||
const auto inner = full - decorations;
|
||||
const auto use = std::min(inner, name->textMaxWidth());
|
||||
*width = use + decorations;
|
||||
const auto left = (full - *width) / 2;
|
||||
if (inner > 0) {
|
||||
userpic->moveToLeft(left, 0, outer.width());
|
||||
emoji->moveToLeft(
|
||||
left + *width - padding.right() - emoji->width(),
|
||||
padding.top(),
|
||||
outer.width());
|
||||
name->resizeToWidth(use);
|
||||
name->moveToLeft(
|
||||
left + size + padding.left(),
|
||||
padding.top(),
|
||||
outer.width());
|
||||
}
|
||||
}, raw->lifetime());
|
||||
raw->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = QPainter(raw);
|
||||
const auto left = (raw->width() - *width) / 2;
|
||||
const auto skip = size / 2;
|
||||
p.setClipRect(left + skip, 0, *width - skip, size);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::windowBgOver);
|
||||
p.drawRoundedRect(left, 0, *width, size, skip, skip);
|
||||
}, raw->lifetime());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void ConfirmEmojiStatusBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<UserData*> bot,
|
||||
not_null<DocumentData*> document,
|
||||
TimeId until,
|
||||
Fn<void(bool)> done) {
|
||||
box->setNoContentMargin(true);
|
||||
|
||||
auto owned = Settings::MakeEmojiStatusPreview(box, document);
|
||||
const auto preview = box->addRow(
|
||||
object_ptr<Ui::RpWidget>::fromRaw(owned.release()));
|
||||
preview->resize(preview->width(), st::botEmojiStatusPreviewHeight);
|
||||
|
||||
const auto set = box->lifetime().make_state<bool>();
|
||||
|
||||
box->addRow(object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_bot_emoji_status_title(),
|
||||
st::botEmojiStatusTitle));
|
||||
AddSkip(box->verticalLayout());
|
||||
|
||||
box->addRow(object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_bot_emoji_status_text(
|
||||
lt_bot,
|
||||
rpl::single(Ui::Text::Bold(bot->name())),
|
||||
Ui::Text::RichLangValue),
|
||||
st::botEmojiStatusText));
|
||||
|
||||
AddSkip(box->verticalLayout());
|
||||
|
||||
auto ownedSet = MakeEmojiSetStatusPreview(
|
||||
box,
|
||||
document->session().user(),
|
||||
document);
|
||||
box->addRow(
|
||||
object_ptr<Ui::RpWidget>::fromRaw(ownedSet.release()));
|
||||
|
||||
box->addButton(tr::lng_bot_emoji_status_confirm(), [=] {
|
||||
document->owner().emojiStatuses().set(document->id, until);
|
||||
*set = true;
|
||||
box->closeBox();
|
||||
done(true);
|
||||
});
|
||||
box->addButton(tr::lng_cancel(), [=] {
|
||||
const auto was = *set;
|
||||
box->closeBox();
|
||||
if (!was) {
|
||||
done(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class BotAction final : public Ui::Menu::ItemBase {
|
||||
public:
|
||||
BotAction(
|
||||
|
@ -1514,6 +1635,32 @@ void WebViewInstance::botInvokeCustomMethod(
|
|||
}).send();
|
||||
}
|
||||
|
||||
void WebViewInstance::botSetEmojiStatus(
|
||||
Ui::BotWebView::SetEmojiStatusRequest request) {
|
||||
const auto bot = _bot;
|
||||
const auto panel = _panel.get();
|
||||
const auto callback = request.callback;
|
||||
const auto until = request.expirationDate;
|
||||
if (!panel) {
|
||||
callback(u"UNKNOWN_ERROR"_q);
|
||||
return;
|
||||
}
|
||||
_session->data().customEmojiManager().resolve(
|
||||
request.customEmojiId
|
||||
) | rpl::start_with_next_error([=](not_null<DocumentData*> document) {
|
||||
const auto sticker = document->sticker();
|
||||
if (!sticker || sticker->setType != Data::StickersType::Emoji) {
|
||||
callback(u"SUGGESTED_EMOJI_INVALID"_q);
|
||||
return;
|
||||
}
|
||||
const auto done = [=](bool success) {
|
||||
callback(success ? QString() : u"USER_DECLINED"_q);
|
||||
};
|
||||
panel->showBox(
|
||||
Box(ConfirmEmojiStatusBox, bot, document, until, done));
|
||||
}, [=] { callback(u"SUGGESTED_EMOJI_INVALID"_q); }, panel->lifetime());
|
||||
}
|
||||
|
||||
void WebViewInstance::botOpenPrivacyPolicy() {
|
||||
const auto bot = _bot;
|
||||
const auto weak = _context.controller;
|
||||
|
|
|
@ -263,6 +263,8 @@ private:
|
|||
void botSharePhone(Fn<void(bool shared)> callback) override;
|
||||
void botInvokeCustomMethod(
|
||||
Ui::BotWebView::CustomMethodRequest request) override;
|
||||
void botSetEmojiStatus(
|
||||
Ui::BotWebView::SetEmojiStatusRequest request) override;
|
||||
void botOpenPrivacyPolicy() override;
|
||||
void botClose() override;
|
||||
|
||||
|
@ -283,6 +285,8 @@ private:
|
|||
QString _panelUrl;
|
||||
std::unique_ptr<Ui::BotWebView::Panel> _panel;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
static base::weak_ptr<WebViewInstance> PendingActivation;
|
||||
|
||||
};
|
||||
|
|
|
@ -1773,7 +1773,29 @@ void AddSummaryPremium(
|
|||
}
|
||||
|
||||
Ui::AddSkip(content, descriptionPadding.bottom());
|
||||
}
|
||||
|
||||
std::unique_ptr<Ui::RpWidget> MakeEmojiStatusPreview(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<DocumentData*> document) {
|
||||
auto result = std::make_unique<Ui::RpWidget>(parent);
|
||||
|
||||
const auto raw = result.get();
|
||||
const auto size = HistoryView::Sticker::EmojiSize();
|
||||
const auto emoji = raw->lifetime().make_state<EmojiStatusTopBar>(
|
||||
document,
|
||||
[=](QRect r) { raw->update(std::move(r)); },
|
||||
size);
|
||||
raw->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = QPainter(raw);
|
||||
emoji->paint(p);
|
||||
}, raw->lifetime());
|
||||
|
||||
raw->sizeValue() | rpl::start_with_next([=](QSize size) {
|
||||
emoji->setCenter(QPointF(size.width() / 2., size.height() / 2.));
|
||||
}, raw->lifetime());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Settings
|
||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "settings/settings_type.h"
|
||||
|
||||
class DocumentData;
|
||||
enum class PremiumFeature;
|
||||
|
||||
namespace style {
|
||||
|
@ -108,5 +109,13 @@ void AddSummaryPremium(
|
|||
const QString &ref,
|
||||
Fn<void(PremiumFeature)> buttonCallback);
|
||||
|
||||
[[nodiscard]] std::unique_ptr<Ui::RpWidget> MakeEmojiStatusPreview(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<DocumentData*> document);
|
||||
[[nodiscard]] std::unique_ptr<Ui::RpWidget> MakeEmojiSetStatusPreview(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<PeerData*> peer,
|
||||
not_null<DocumentData*> document);
|
||||
|
||||
} // namespace Settings
|
||||
|
||||
|
|
|
@ -824,6 +824,8 @@ bool Panel::createWebview(const Webview::ThemeParams ¶ms) {
|
|||
processHeaderColor(arguments);
|
||||
} else if (command == "web_app_set_bottom_bar_color") {
|
||||
processBottomBarColor(arguments);
|
||||
} else if (command == "web_app_set_emoji_status") {
|
||||
processEmojiStatusRequest(arguments);
|
||||
} else if (command == "share_score") {
|
||||
_delegate->botHandleMenuButton(MenuButton::ShareGame);
|
||||
}
|
||||
|
@ -963,6 +965,41 @@ void Panel::switchInlineQueryMessage(const QJsonObject &args) {
|
|||
_delegate->botSwitchInlineQuery(types, query);
|
||||
}
|
||||
|
||||
void Panel::processEmojiStatusRequest(const QJsonObject &args) {
|
||||
if (args.isEmpty()) {
|
||||
_delegate->botClose();
|
||||
return;
|
||||
}
|
||||
const auto emojiId = args["custom_emoji_id"].toString().toULongLong();
|
||||
const auto expirationDate = TimeId(base::SafeRound(
|
||||
args["expiration_date"].toDouble()));
|
||||
if (!emojiId) {
|
||||
postEvent(
|
||||
"emoji_status_failed",
|
||||
"{ error: \"SUGGESTED_EMOJI_INVALID\" }");
|
||||
return;
|
||||
} else if (expirationDate < 0) {
|
||||
postEvent(
|
||||
"emoji_status_failed",
|
||||
"{ error: \"EXPIRATION_DATE_INVALID\" }");
|
||||
return;
|
||||
}
|
||||
auto callback = crl::guard(this, [=](QString error) {
|
||||
if (error.isEmpty()) {
|
||||
postEvent("emoji_status_set");
|
||||
} else {
|
||||
postEvent(
|
||||
"emoji_status_failed",
|
||||
u"{ error: \"%1\" }"_q.arg(error));
|
||||
}
|
||||
});
|
||||
_delegate->botSetEmojiStatus({
|
||||
.customEmojiId = emojiId,
|
||||
.expirationDate = expirationDate,
|
||||
.callback = std::move(callback),
|
||||
});
|
||||
}
|
||||
|
||||
void Panel::openTgLink(const QJsonObject &args) {
|
||||
if (args.isEmpty()) {
|
||||
LOG(("BotWebView Error: Bad arguments in 'web_app_open_tg_link'."));
|
||||
|
|
|
@ -51,6 +51,12 @@ struct CustomMethodRequest {
|
|||
Fn<void(CustomMethodResult)> callback;
|
||||
};
|
||||
|
||||
struct SetEmojiStatusRequest {
|
||||
uint64 customEmojiId = 0;
|
||||
TimeId expirationDate = 0;
|
||||
Fn<void(QString)> callback;
|
||||
};
|
||||
|
||||
class Delegate {
|
||||
public:
|
||||
virtual Webview::ThemeParams botThemeParams() = 0;
|
||||
|
@ -67,6 +73,7 @@ public:
|
|||
virtual void botAllowWriteAccess(Fn<void(bool allowed)> callback) = 0;
|
||||
virtual void botSharePhone(Fn<void(bool shared)> callback) = 0;
|
||||
virtual void botInvokeCustomMethod(CustomMethodRequest request) = 0;
|
||||
virtual void botSetEmojiStatus(SetEmojiStatusRequest request) = 0;
|
||||
virtual void botOpenPrivacyPolicy() = 0;
|
||||
virtual void botClose() = 0;
|
||||
};
|
||||
|
@ -123,6 +130,8 @@ private:
|
|||
void setTitle(rpl::producer<QString> title);
|
||||
void sendDataMessage(const QJsonObject &args);
|
||||
void switchInlineQueryMessage(const QJsonObject &args);
|
||||
void processEmojiStatusRequest(const QJsonObject &args);
|
||||
void processEmojiStatusAccessRequest();
|
||||
void processButtonMessage(
|
||||
std::unique_ptr<Button> &button,
|
||||
const QJsonObject &args);
|
||||
|
|
|
@ -1155,3 +1155,26 @@ msgSelectionCheck: RoundCheckbox(defaultPeerListCheck) {
|
|||
}
|
||||
|
||||
sponsoredMessageBarMaxHeight: 156px;
|
||||
|
||||
botEmojiStatusPreviewHeight: 148px;
|
||||
botEmojiStatusTitle: FlatLabel(boxTitle) {
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(16px semibold);
|
||||
lineHeight: 20px;
|
||||
}
|
||||
minWidth: 256px;
|
||||
maxHeight: 0px;
|
||||
align: align(top);
|
||||
}
|
||||
botEmojiStatusText: FlatLabel(defaultFlatLabel) {
|
||||
style: boxTextStyle;
|
||||
minWidth: 256px;
|
||||
maxHeight: 0px;
|
||||
align: align(top);
|
||||
}
|
||||
botEmojiStatusUserpic: UserpicButton(defaultUserpicButton) {
|
||||
size: size(chatGiveawayPeerSize, chatGiveawayPeerSize);
|
||||
photoSize: chatGiveawayPeerSize;
|
||||
}
|
||||
botEmojiStatusName: FlatLabel(defaultFlatLabel) {
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue