diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index ed4342dbb5..2ce69477aa 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -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 diff --git a/Telegram/Resources/animations/cake.tgs b/Telegram/Resources/animations/cake.tgs new file mode 100644 index 0000000000..2d49c2066d Binary files /dev/null and b/Telegram/Resources/animations/cake.tgs differ diff --git a/Telegram/Resources/qrc/telegram/animations.qrc b/Telegram/Resources/qrc/telegram/animations.qrc index 4e206e01ca..7b120093d0 100644 --- a/Telegram/Resources/qrc/telegram/animations.qrc +++ b/Telegram/Resources/qrc/telegram/animations.qrc @@ -40,6 +40,7 @@ ../../animations/edit_peers/direct_messages.tgs ../../animations/no_chats.tgs ../../animations/transcribe_loading.tgs + ../../animations/cake.tgs ../../animations/dice/dice_idle.tgs ../../animations/dice/dart_idle.tgs diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index 62b07cca2c..651bf8efa3 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -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 box) { Ui::EditBirthdayBox(box, user->birthday(), save); diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index e7290c63cf..615d26d166 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -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 MediaGiftBox::createView( not_null message, not_null realParent, HistoryView::Element *replacing) { - if (_data.type == GiftType::ChatTheme) { + if (_data.type == GiftType::BirthdaySuggest) { + return std::make_unique( + 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( message, std::make_unique(message, this)); diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index 1e3911a3fa..bc27065da2 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -139,6 +139,7 @@ enum class GiftType : uchar { Ton, // count - nano tons StarGift, // count - stars ChatTheme, + BirthdaySuggest, }; struct GiftCode { diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 475e4d0ab0..eeb86629d4 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -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( + 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 &) { }); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_birthday_suggestion.cpp b/Telegram/SourceFiles/history/view/media/history_view_birthday_suggestion.cpp new file mode 100644 index 0000000000..614549c717 --- /dev/null +++ b/Telegram/SourceFiles/history/view/media/history_view_birthday_suggestion.cpp @@ -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 parent, + Element *replacing, + Data::Birthday birthday) +-> Fn, + Fn)>)> { + return [=]( + not_null media, + Fn)> 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( + 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( + (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( + birthday, + (isSelf + ? st::birthdaySuggestTableLastPadding + : st::birthdaySuggestTablePadding))); + if (!isSelf) { + auto link = std::make_shared([=]( + 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 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 diff --git a/Telegram/SourceFiles/history/view/media/history_view_birthday_suggestion.h b/Telegram/SourceFiles/history/view/media/history_view_birthday_suggestion.h new file mode 100644 index 0000000000..32a493f0e8 --- /dev/null +++ b/Telegram/SourceFiles/history/view/media/history_view_birthday_suggestion.h @@ -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 parent, + Element *replacing, + Data::Birthday birthday) +-> Fn, + Fn)>)>; + +class BirthdayTable final : public MediaGenericPart { +public: + BirthdayTable(Data::Birthday birthday, QMargins margins); + + void draw( + Painter &p, + not_null 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 _parts; + QMargins _margins; + Fn _labelColor; + Fn _valueColor; + +}; + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp b/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp index 3fc36c36ee..d186bb8e90 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp @@ -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" diff --git a/Telegram/SourceFiles/history/view/media/history_view_unique_gift.h b/Telegram/SourceFiles/history/view/media/history_view_unique_gift.h index 4816b1f681..2b5ec31fc2 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_unique_gift.h +++ b/Telegram/SourceFiles/history/view/media/history_view_unique_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( diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 84b6f0751c..dba3bef7f9 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -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 = 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 availability_issued:int availability_total:int gift_address:flags.3?string resell_amount:flags.4?Vector 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 availability_issued:int availability_total:int gift_address:flags.3?string resell_amount:flags.4?Vector 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 chats:Vector users:Vector = payments.StarGifts; diff --git a/Telegram/SourceFiles/ui/boxes/edit_birthday_box.cpp b/Telegram/SourceFiles/ui/boxes/edit_birthday_box.cpp index 40b0066117..cc346fa96e 100644 --- a/Telegram/SourceFiles/ui/boxes/edit_birthday_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/edit_birthday_box.cpp @@ -26,7 +26,7 @@ void EditBirthdayBox( not_null box, Data::Birthday current, Fn save, - bool useSuggestText) { + EditBirthdayType type) { box->setWidth(st::boxWideWidth); const auto content = box->addRow(object_ptr( 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()); diff --git a/Telegram/SourceFiles/ui/boxes/edit_birthday_box.h b/Telegram/SourceFiles/ui/boxes/edit_birthday_box.h index 7eb7da043c..4d33fc262b 100644 --- a/Telegram/SourceFiles/ui/boxes/edit_birthday_box.h +++ b/Telegram/SourceFiles/ui/boxes/edit_birthday_box.h @@ -15,10 +15,16 @@ namespace Ui { class GenericBox; +enum class EditBirthdayType { + Edit, + Suggest, + ConfirmSuggestion, +}; + void EditBirthdayBox( not_null box, Data::Birthday current, Fn save, - bool useConfirmText = false); + EditBirthdayType type = EditBirthdayType::Edit); } // namespace Ui diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 2e3a012ca0..2b6d3f8c51 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -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;