Support birthday suggestion view/process.

This commit is contained in:
John Preston
2025-09-25 19:34:50 +04:00
parent 16fe056c99
commit ccdff5baef
15 changed files with 343 additions and 16 deletions
+2
View File
@@ -783,6 +783,8 @@ PRIVATE
history/view/controls/history_view_voice_record_bar.h
history/view/controls/history_view_webpage_processor.cpp
history/view/controls/history_view_webpage_processor.h
history/view/media/history_view_birthday_suggestion.cpp
history/view/media/history_view_birthday_suggestion.h
history/view/media/history_view_call.cpp
history/view/media/history_view_call.h
history/view/media/history_view_contact.cpp
Binary file not shown.
@@ -40,6 +40,7 @@
<file alias="direct_messages.tgs">../../animations/edit_peers/direct_messages.tgs</file>
<file alias="no_chats.tgs">../../animations/no_chats.tgs</file>
<file alias="transcribe_loading.tgs">../../animations/transcribe_loading.tgs</file>
<file alias="cake.tgs">../../animations/cake.tgs</file>
<file alias="dice_idle.tgs">../../animations/dice/dice_idle.tgs</file>
<file alias="dart_idle.tgs">../../animations/dice/dart_idle.tgs</file>
@@ -971,7 +971,11 @@ bool ShowEditBirthday(
box->setTitle(tr::lng_suggest_birthday_box_title(
lt_user,
Info::Profile::NameValue(targetUser)));
Ui::EditBirthdayBox(box, Data::Birthday(), save, true);
Ui::EditBirthdayBox(
box,
Data::Birthday(),
save,
Ui::EditBirthdayType::Suggest);
}));
return true;
}
@@ -998,8 +1002,20 @@ bool ShowEditBirthday(
: (u"Error: "_q + error.type()));
})).handleFloodErrors().send();
};
if (captured.isEmpty()) {
controller->show(Box(Ui::EditBirthdayBox, user->birthday(), save, 0));
if (captured.startsWith(u":suggestion_"_q)) {
const auto suggested = Data::Birthday::FromSerialized(
captured.mid(u":suggestion_"_q.size()).toInt());
controller->show(Box(
Ui::EditBirthdayBox,
suggested,
save,
Ui::EditBirthdayType::ConfirmSuggestion));
} else if (captured.isEmpty()) {
controller->show(Box(
Ui::EditBirthdayBox,
user->birthday(),
save,
Ui::EditBirthdayType::Edit));
} else {
controller->show(Box([=](not_null<Ui::GenericBox*> box) {
Ui::EditBirthdayBox(box, user->birthday(), save);
+14 -1
View File
@@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_location_manager.h"
#include "history/view/history_view_element.h"
#include "history/view/history_view_item_preview.h"
#include "history/view/media/history_view_birthday_suggestion.h"
#include "history/view/media/history_view_photo.h"
#include "history/view/media/history_view_sticker.h"
#include "history/view/media/history_view_gif.h"
@@ -2593,7 +2594,19 @@ std::unique_ptr<HistoryView::Media> MediaGiftBox::createView(
not_null<HistoryView::Element*> message,
not_null<HistoryItem*> realParent,
HistoryView::Element *replacing) {
if (_data.type == GiftType::ChatTheme) {
if (_data.type == GiftType::BirthdaySuggest) {
return std::make_unique<HistoryView::MediaGeneric>(
message,
HistoryView::GenerateSuggetsBirthdayMedia(
message,
replacing,
Data::Birthday::FromSerialized(_data.count)),
HistoryView::MediaGenericDescriptor{
.maxWidth = st::birthdaySuggestStickerWidth,
.service = true,
.hideServiceText = true,
});
} else if (_data.type == GiftType::ChatTheme) {
return std::make_unique<HistoryView::ServiceBox>(
message,
std::make_unique<HistoryView::GiftThemeBox>(message, this));
@@ -139,6 +139,7 @@ enum class GiftType : uchar {
Ton, // count - nano tons
StarGift, // count - stars
ChatTheme,
BirthdaySuggest,
};
struct GiftCode {
+12 -5
View File
@@ -6312,11 +6312,6 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
auto prepareSuggestBirthday = [this](const MTPDmessageActionSuggestBirthday &action) {
auto result = PreparedServiceText{};
const auto &data = action.vbirthday().data();
const auto birthday = Data::Birthday(
data.vday().v,
data.vmonth().v,
data.vyear().value_or_empty());
const auto isSelf = (_from->id == _from->session().userPeerId());
const auto peer = isSelf ? history()->peer : _from;
const auto user = peer->asUser();
@@ -6631,6 +6626,18 @@ void HistoryItem::applyAction(const MTPMessageAction &action) {
this,
_from,
std::move(fields));
}, [&](const MTPDmessageActionSuggestBirthday &data) {
const auto &fields = data.vbirthday().data();
_media = std::make_unique<Data::MediaGiftBox>(
this,
_from,
Data::GiftCode{
.count = Data::Birthday(
fields.vday().v,
fields.vmonth().v,
fields.vyear().value_or_empty()).serialize(),
.type = Data::GiftType::BirthdaySuggest,
});
}, [](const auto &) {
});
}
@@ -0,0 +1,202 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/view/media/history_view_birthday_suggestion.h"
#include "boxes/star_gift_box.h"
#include "chat_helpers/stickers_lottie.h"
#include "core/application.h"
#include "core/click_handler_types.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/data_birthday.h"
#include "data/data_media_types.h"
#include "data/data_session.h"
#include "data/data_star_gift.h"
#include "history/view/media/history_view_media_generic.h"
#include "history/view/media/history_view_premium_gift.h"
#include "history/view/media/history_view_unique_gift.h"
#include "history/view/history_view_cursor_state.h"
#include "history/view/history_view_element.h"
#include "history/history.h"
#include "history/history_item.h"
#include "info/peer_gifts/info_peer_gifts_common.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "settings/settings_credits_graphics.h"
#include "ui/chat/chat_style.h"
#include "ui/effects/premium_stars_colored.h"
#include "ui/effects/ripple_animation.h"
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h"
#include "ui/painter.h"
#include "ui/power_saving.h"
#include "ui/rect.h"
#include "window/window_session_controller.h"
#include "styles/style_chat.h"
#include "styles/style_credits.h"
namespace HistoryView {
[[nodiscard]] auto GenerateSuggetsBirthdayMedia(
not_null<Element*> parent,
Element *replacing,
Data::Birthday birthday)
-> Fn<void(
not_null<MediaGeneric*>,
Fn<void(std::unique_ptr<MediaGenericPart>)>)> {
return [=](
not_null<MediaGeneric*> media,
Fn<void(std::unique_ptr<MediaGenericPart>)> push) {
const auto session = &media->parent()->history()->session();
const auto document = ChatHelpers::GenerateLocalTgsSticker(
session,
u"cake"_q);
const auto sticker = [=] {
using Tag = ChatHelpers::StickerLottieSize;
return StickerInBubblePart::Data{
.sticker = document,
.size = st::birthdaySuggestStickerSize,
.cacheTag = Tag::ChatIntroHelloSticker,
.stopOnLastFrame = true,
};
};
push(std::make_unique<StickerInBubblePart>(
parent,
replacing,
sticker,
st::birthdaySuggestStickerPadding));
const auto from = media->parent()->data()->from();
const auto isSelf = (from->id == from->session().userPeerId());
const auto peer = isSelf ? media->parent()->history()->peer : from;
push(std::make_unique<MediaGenericTextPart>(
(isSelf
? tr::lng_action_suggested_birthday_me
: tr::lng_action_suggested_birthday)(
tr::now,
lt_user,
TextWithEntities{ peer->shortName() },
Ui::Text::WithEntities),
st::birthdaySuggestTextPadding));
push(std::make_unique<BirthdayTable>(
birthday,
(isSelf
? st::birthdaySuggestTableLastPadding
: st::birthdaySuggestTablePadding)));
if (!isSelf) {
auto link = std::make_shared<LambdaClickHandler>([=](
ClickContext context) {
Core::App().openInternalUrl(
(u"internal:edit_birthday:suggestion_"_q
+ QString::number(birthday.serialize())),
context.other);
});
push(MakeGenericButtonPart(
tr::lng_sticker_premium_view(tr::now),
st::chatUniqueButtonPadding,
[=] { parent->repaint(); },
std::move(link)));
}
};
}
BirthdayTable::BirthdayTable(Data::Birthday birthday, QMargins margins)
: _margins(margins) {
const auto push = [&](QString label, QString value) {
_parts.push_back({
.label = Ui::Text::String(st::defaultTextStyle, label),
.value = Ui::Text::String(
st::defaultTextStyle,
Ui::Text::Bold(value)),
});
};
push(tr::lng_date_input_day(tr::now), QString::number(birthday.day()));
push(
tr::lng_date_input_month(tr::now),
Lang::Month(birthday.month())(tr::now));
if (const auto year = birthday.year()) {
push(tr::lng_date_input_year(tr::now), QString::number(year));
}
}
void BirthdayTable::draw(
Painter &p,
not_null<const MediaGeneric*> owner,
const PaintContext &context,
int outerWidth) const {
const auto top = _margins.top();
const auto palette = &context.st->serviceTextPalette();
const auto paint = [&](
const Ui::Text::String &text,
int left,
int yskip = 0) {
text.draw(p, {
.position = { left, top + yskip},
.outerWidth = outerWidth,
.availableWidth = text.maxWidth(),
.palette = palette,
.spoiler = Ui::Text::DefaultSpoilerCache(),
.now = context.now,
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
.elisionLines = 1,
});
};
p.setPen(context.st->msgServiceFg()->c);
for (const auto &part : _parts) {
p.setOpacity(0.7);
paint(part.label, part.labelLeft);
p.setOpacity(1.);
paint(
part.value,
part.valueLeft,
st::normalFont->height + st::birthdaySuggestTableSkip);
}
}
TextState BirthdayTable::textState(
QPoint point,
StateRequest request,
int outerWidth) const {
return {};
}
QSize BirthdayTable::countOptimalSize() {
auto width = 0;
for (const auto &part : _parts) {
width += std::max(part.label.maxWidth(), part.value.maxWidth());
}
width += st::normalFont->spacew * (_parts.size() - 1);
const auto height = st::normalFont->height * 2
+ st::birthdaySuggestTableSkip;
return {
_margins.left() + width + _margins.right(),
_margins.top() + height + _margins.bottom(),
};
}
QSize BirthdayTable::countCurrentSize(int newWidth) {
auto available = newWidth - _margins.left() - _margins.right();
for (const auto &part : _parts) {
available -= std::max(part.label.maxWidth(), part.value.maxWidth());
}
const auto skip = available / int(_parts.size() + 1);
auto left = _margins.left() + skip;
for (auto &part : _parts) {
auto full = std::max(part.label.maxWidth(), part.value.maxWidth());
part.labelLeft = left + (full - part.label.maxWidth()) / 2;
part.valueLeft = left + (full - part.value.maxWidth()) / 2;
left += full + skip;
}
return { newWidth, minHeight() };
}
} // namespace HistoryView
@@ -0,0 +1,70 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "history/view/media/history_view_media_generic.h"
class Painter;
namespace Data {
class MediaGiftBox;
struct UniqueGift;
class Birthday;
} // namespace Data
namespace Ui {
struct ChatPaintContext;
} // namespace Ui
namespace HistoryView {
class Element;
class MediaGeneric;
class MediaGenericPart;
[[nodiscard]] auto GenerateSuggetsBirthdayMedia(
not_null<Element*> parent,
Element *replacing,
Data::Birthday birthday)
-> Fn<void(
not_null<MediaGeneric*>,
Fn<void(std::unique_ptr<MediaGenericPart>)>)>;
class BirthdayTable final : public MediaGenericPart {
public:
BirthdayTable(Data::Birthday birthday, QMargins margins);
void draw(
Painter &p,
not_null<const MediaGeneric*> owner,
const PaintContext &context,
int outerWidth) const override;
TextState textState(
QPoint point,
StateRequest request,
int outerWidth) const override;
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
private:
struct Part {
Ui::Text::String label;
Ui::Text::String value;
int labelLeft = 0;
int valueLeft = 0;
};
std::vector<Part> _parts;
QMargins _margins;
Fn<QColor(const PaintContext &)> _labelColor;
Fn<QColor(const PaintContext &)> _valueColor;
};
} // namespace HistoryView
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/stickers_lottie.h"
#include "core/click_handler_types.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/data_birthday.h"
#include "data/data_media_types.h"
#include "data/data_session.h"
#include "data/data_star_gift.h"
@@ -14,6 +14,7 @@ class Painter;
namespace Data {
class MediaGiftBox;
struct UniqueGift;
class Birthday;
} // namespace Data
namespace Ui {
@@ -57,7 +58,6 @@ class MediaGenericPart;
ClickHandlerPtr link,
QColor bg = QColor(0, 0, 0, 0));
class TextPartColored : public MediaGenericTextPart {
public:
TextPartColored(
+2 -2
View File
@@ -186,7 +186,7 @@ messageActionPaymentRefunded#41b3e202 flags:# peer:Peer currency:string total_am
messageActionGiftStars#45d5b021 flags:# currency:string amount:long stars:long crypto_currency:flags.0?string crypto_amount:flags.0?long transaction_id:flags.1?string = MessageAction;
messageActionPrizeStars#b00c47a2 flags:# unclaimed:flags.0?true stars:long transaction_id:string boost_peer:Peer giveaway_msg_id:int = MessageAction;
messageActionStarGift#f24de7fa flags:# name_hidden:flags.0?true saved:flags.2?true converted:flags.3?true upgraded:flags.5?true refunded:flags.9?true can_upgrade:flags.10?true prepaid_upgrade:flags.13?true upgrade_separate:flags.16?true gift:StarGift message:flags.1?TextWithEntities convert_stars:flags.4?long upgrade_msg_id:flags.5?int upgrade_stars:flags.8?long from_id:flags.11?Peer peer:flags.12?Peer saved_id:flags.12?long prepaid_upgrade_hash:flags.14?string gift_msg_id:flags.15?int = MessageAction;
messageActionStarGiftUnique#95728543 flags:# upgrade:flags.0?true transferred:flags.1?true saved:flags.2?true refunded:flags.5?true prepaid_upgrade:flags.11?true gift:StarGift can_export_at:flags.3?int transfer_stars:flags.4?long from_id:flags.6?Peer peer:flags.7?Peer saved_id:flags.7?long resale_amount:flags.8?StarsAmount can_transfer_at:flags.9?int can_resell_at:flags.10?int drop_original_details_stars:flags.12?long = MessageAction;
messageActionStarGiftUnique#95728543 flags:# upgrade:flags.0?true transferred:flags.1?true saved:flags.2?true refunded:flags.5?true prepaid_upgrade:flags.11?true assigned:flags.13?true gift:StarGift can_export_at:flags.3?int transfer_stars:flags.4?long from_id:flags.6?Peer peer:flags.7?Peer saved_id:flags.7?long resale_amount:flags.8?StarsAmount can_transfer_at:flags.9?int can_resell_at:flags.10?int drop_original_details_stars:flags.12?long = MessageAction;
messageActionPaidMessagesRefunded#ac1f1fcd count:int stars:long = MessageAction;
messageActionPaidMessagesPrice#84b88578 flags:# broadcast_messages_allowed:flags.0?true stars:long = MessageAction;
messageActionConferenceCall#2ffe2f7a flags:# missed:flags.0?true active:flags.1?true video:flags.4?true call_id:long duration:flags.2?int other_participants:flags.3?Vector<Peer> = MessageAction;
@@ -1907,7 +1907,7 @@ starsGiveawayOption#94ce852a flags:# extended:flags.0?true default:flags.1?true
starsGiveawayWinnersOption#54236209 flags:# default:flags.0?true users:int per_user_stars:long = StarsGiveawayWinnersOption;
starGift#80ac53c3 flags:# limited:flags.0?true sold_out:flags.1?true birthday:flags.2?true require_premium:flags.7?true limited_per_user:flags.8?true peer_color_available:flags.10?true id:long sticker:Document stars:long availability_remains:flags.0?int availability_total:flags.0?int availability_resale:flags.4?long convert_stars:long first_sale_date:flags.1?int last_sale_date:flags.1?int upgrade_stars:flags.3?long resell_min_stars:flags.4?long title:flags.5?string released_by:flags.6?Peer per_user_total:flags.8?int per_user_remains:flags.8?int locked_until_date:flags.9?int = StarGift;
starGiftUnique#3a0893b8 flags:# require_premium:flags.6?true resale_ton_only:flags.7?true theme_available:flags.9?true id:long gift_id:long title:string slug:string num:int owner_id:flags.0?Peer owner_name:flags.1?string owner_address:flags.2?string attributes:Vector<StarGiftAttribute> availability_issued:int availability_total:int gift_address:flags.3?string resell_amount:flags.4?Vector<StarsAmount> released_by:flags.5?Peer value_amount:flags.8?long value_currency:flags.8?string theme_peer:flags.10?Peer peer_color:flags.11?PeerColor = StarGift;
starGiftUnique#b0bf741b flags:# require_premium:flags.6?true resale_ton_only:flags.7?true theme_available:flags.9?true id:long gift_id:long title:string slug:string num:int owner_id:flags.0?Peer owner_name:flags.1?string owner_address:flags.2?string attributes:Vector<StarGiftAttribute> availability_issued:int availability_total:int gift_address:flags.3?string resell_amount:flags.4?Vector<StarsAmount> released_by:flags.5?Peer value_amount:flags.8?long value_currency:flags.8?string theme_peer:flags.10?Peer peer_color:flags.11?PeerColor host_id:flags.12?Peer = StarGift;
payments.starGiftsNotModified#a388a368 = payments.StarGifts;
payments.starGifts#2ed82995 hash:int gifts:Vector<StarGift> chats:Vector<Chat> users:Vector<User> = payments.StarGifts;
@@ -26,7 +26,7 @@ void EditBirthdayBox(
not_null<Ui::GenericBox*> box,
Data::Birthday current,
Fn<void(Data::Birthday)> save,
bool useSuggestText) {
EditBirthdayType type) {
box->setWidth(st::boxWideWidth);
const auto content = box->addRow(object_ptr<Ui::FixedHeightWidget>(
box,
@@ -210,7 +210,7 @@ void EditBirthdayBox(
return base::EventFilterResult::Continue;
});
auto confirmText = useSuggestText
auto confirmText = (type == EditBirthdayType::Suggest)
? tr::lng_suggest_birthday_box_confirm()
: tr::lng_settings_save();
box->addButton(std::move(confirmText), [=] {
@@ -226,7 +226,7 @@ void EditBirthdayBox(
box->addButton(tr::lng_cancel(), [=] {
box->closeBox();
});
if (current) {
if (current && type == EditBirthdayType::Edit) {
box->addLeftButton(tr::lng_settings_birthday_reset(), [=] {
box->closeBox();
save(Data::Birthday());
@@ -15,10 +15,16 @@ namespace Ui {
class GenericBox;
enum class EditBirthdayType {
Edit,
Suggest,
ConfirmSuggestion,
};
void EditBirthdayBox(
not_null<Ui::GenericBox*> box,
Data::Birthday current,
Fn<void(Data::Birthday)> save,
bool useConfirmText = false);
EditBirthdayType type = EditBirthdayType::Edit);
} // namespace Ui
+8
View File
@@ -1464,3 +1464,11 @@ saveMusicInfoMenu: Menu(defaultMenu) {
newBotThreadDown: icon {{ "history_down_arrow", msgServiceFg }};
newBotThreadTopSkip: 24px;
birthdaySuggestStickerWidth: 236px;
birthdaySuggestStickerSize: 96px;
birthdaySuggestStickerPadding: margins(10px, 16px, 10px, 4px);
birthdaySuggestTextPadding: margins(10px, 4px, 10px, 8px);
birthdaySuggestTablePadding: margins(2px, 4px, 2px, 12px);
birthdaySuggestTableLastPadding: margins(2px, 4px, 2px, 24px);
birthdaySuggestTableSkip: 2px;