diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index a20f3f652..d2333c7cf 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1682,7 +1682,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_giveaway_started" = "{from} just started a giveaway of Telegram Premium subscriptions to its followers."; "lng_action_giveaway_results#one" = "{count} winner of the giveaway was randomly selected by Telegram and received private messages with giftcodes."; "lng_action_giveaway_results#other" = "{count} winners of the giveaway were randomly selected by Telegram and received private messages with giftcodes."; -"lng_action_giveaway_results_some" = "Some winners of the giveaway was randomly selected by Telegram and received private messages with giftcodes."; +"lng_action_giveaway_results_some" = "Some winners of the giveaway were randomly selected by Telegram and received private messages with giftcodes."; "lng_action_giveaway_results_none" = "No winners of the giveaway could be selected."; "lng_similar_channels_title" = "Similar channels"; diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 48739c509..e14f0712e 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -2285,7 +2285,9 @@ std::unique_ptr MediaGiveaway::createView( not_null message, not_null realParent, HistoryView::Element *replacing) { - return std::make_unique(message, &_giveaway); + return std::make_unique( + message, + HistoryView::GenerateGiveawayStart(message, &_giveaway)); } } // namespace Data diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index e11670673..83e2c10c9 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -1196,6 +1196,8 @@ Media ParseMedia( // #TODO export stories }, [&](const MTPDmessageMediaGiveaway &data) { result.content = ParseGiveaway(data); + }, [&](const MTPDmessageMediaGiveawayResults &data) { + // #TODO export giveaway }, [](const MTPDmessageMediaEmpty &data) {}); return result; } diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 920415f2c..2861bf26a 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -314,6 +314,10 @@ std::unique_ptr HistoryItem::CreateMedia( return std::make_unique( item, Data::ComputeGiveawayData(item, media)); + }, [&](const MTPDmessageMediaGiveawayResults &media) -> Result { + return nullptr;/* std::make_unique( + item, + Data::ComputeGiveawayData(item, media));*/ }, [](const MTPDmessageMediaEmpty &) -> Result { return nullptr; }, [](const MTPDmessageMediaUnsupported &) -> Result { diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index d3bdddaca..1745148d1 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -498,6 +498,8 @@ MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) { : Result::Good; }, [](const MTPDmessageMediaGiveaway &) { return Result::Good; + }, [](const MTPDmessageMediaGiveawayResults &) { + return Result::Good; }, [](const MTPDmessageMediaUnsupported &) { return Result::Unsupported; }); diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 761e975e0..0fe88fb2d 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -1389,6 +1389,14 @@ bool Element::allowTextSelectionByHandler( return false; } +bool Element::usesBubblePattern(const PaintContext &context) const { + return (context.selection != FullSelection) + && hasOutLayout() + && context.bubblesPattern + && !context.viewport.isEmpty() + && !context.bubblesPattern->pixmap.size().isEmpty(); +} + bool Element::hasVisibleText() const { return false; } diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index a619f15de..b0d0a8fc6 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -465,6 +465,8 @@ public: [[nodiscard]] virtual bool allowTextSelectionByHandler( const ClickHandlerPtr &handler) const; + [[nodiscard]] bool usesBubblePattern(const PaintContext &context) const; + struct VerticalRepaintRange { int top = 0; int height = 0; diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp index 66f26b668..0045f962b 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp @@ -52,250 +52,248 @@ constexpr auto kAdditionalPrizesWithLineOpacity = 0.6; } // namespace -Giveaway::Giveaway( - not_null parent, - not_null giveaway) -: Media(parent) -, _prizesTitle(st::msgMinWidth) -, _prizes(st::msgMinWidth) -, _participantsTitle(st::msgMinWidth) -, _participants(st::msgMinWidth) -, _countries(st::msgMinWidth) -, _winnersTitle(st::msgMinWidth) -, _winners(st::msgMinWidth) { - fillFromData(giveaway); +TextState MediaInBubble::Part::textState( + QPoint point, + StateRequest request) const { + return {}; } -Giveaway::~Giveaway() { +void MediaInBubble::Part::clickHandlerPressedChanged( + const ClickHandlerPtr &p, + bool pressed) { +} + +bool MediaInBubble::Part::hasHeavyPart() { + return false; +} + +void MediaInBubble::Part::unloadHeavyPart() { +} + +MediaInBubble::MediaInBubble( + not_null parent, + Fn)>)> generate) +: Media(parent) { + generate([&](std::unique_ptr part) { + _entries.push_back({ + .object = std::move(part), + }); + }); +} + +MediaInBubble::~MediaInBubble() { if (hasHeavyPart()) { unloadHeavyPart(); _parent->checkHeavyPart(); } } -void Giveaway::fillFromData(not_null giveaway) { - _months = giveaway->months; - _quantity = giveaway->quantity; - - _prizesTitle.setText( - st::semiboldTextStyle, - tr::lng_prizes_title(tr::now, lt_count, _quantity), - kDefaultTextOptions); - - if (giveaway->additionalPrize.isEmpty()) { - _additional.clear(); - _with.clear(); - } else { - _additional.setMarkedText( - st::defaultTextStyle, - tr::lng_prizes_additional( - tr::now, - lt_count, - _quantity, - lt_prize, - TextWithEntities{ giveaway->additionalPrize }, - Ui::Text::RichLangValue)); - _with.setText( - st::defaultTextStyle, - tr::lng_prizes_additional_with(tr::now)); - } - - _prizes.setMarkedText( - st::defaultTextStyle, - tr::lng_prizes_about( - tr::now, - lt_count, - _quantity, - lt_duration, - Ui::Text::Bold(GiftDuration(_months)), - Ui::Text::RichLangValue)); - _participantsTitle.setText( - st::semiboldTextStyle, - tr::lng_prizes_participants(tr::now), - kDefaultTextOptions); - - for (const auto &channel : giveaway->channels) { - _channels.push_back({ - .name = Ui::Text::String( - st::semiboldTextStyle, - channel->name(), - kDefaultTextOptions, - st::msgMinWidth), - .thumbnail = Dialogs::Stories::MakeUserpicThumbnail(channel), - .link = channel->openLink(), - .colorIndex = channel->colorIndex(), - }); - } - const auto channels = int(_channels.size()); - - const auto &instance = Countries::Instance(); ; - auto countries = QStringList(); - for (const auto &country : giveaway->countries) { - const auto name = instance.countryNameByISO2(country); - const auto flag = instance.flagEmojiByISO2(country); - countries.push_back(flag + QChar(0xA0) + name); - } - if (const auto count = countries.size()) { - auto united = countries.front(); - for (auto i = 1; i != count; ++i) { - united = ((i + 1 == count) - ? tr::lng_prizes_countries_and_last - : tr::lng_prizes_countries_and_one)( - tr::now, - lt_countries, - united, - lt_country, - countries[i]); - } - _countries.setText( - st::defaultTextStyle, - tr::lng_prizes_countries(tr::now, lt_countries, united), - kDefaultTextOptions); - } else { - _countries.clear(); - } - - _participants.setText( - st::defaultTextStyle, - (giveaway->all - ? tr::lng_prizes_participants_all - : tr::lng_prizes_participants_new)(tr::now, lt_count, channels), - kDefaultTextOptions); - _winnersTitle.setText( - st::semiboldTextStyle, - tr::lng_prizes_date(tr::now), - kDefaultTextOptions); - _winners.setText( - st::defaultTextStyle, - langDateTime(base::unixtime::parse(giveaway->untilDate)), - kDefaultTextOptions); - - ensureStickerCreated(); -} - -QSize Giveaway::countOptimalSize() { +QSize MediaInBubble::countOptimalSize() { const auto maxWidth = st::chatGiveawayWidth; const auto padding = inBubblePadding(); const auto available = maxWidth - padding.left() - padding.right(); - _stickerTop = st::chatGiveawayStickerTop; - _prizesTitleTop = _stickerTop - + st::msgServiceGiftBoxStickerSize.height() - + st::chatGiveawayPrizesTop; - _additionalTop = _prizesTitleTop - + _prizesTitle.countHeight(available) - + st::chatGiveawayPrizesSkip; - if (!_additional.isEmpty()) { - const auto additionalSize = CountOptimalTextSize( - _additional, - st::msgMinWidth, - available); - _additionalWidth = additionalSize.width(); - _withTop = _additionalTop - + additionalSize.height() - + st::chatGiveawayPrizesWithPadding.top(); - _prizesTop = _withTop - + st::normalFont->height - + st::chatGiveawayPrizesWithPadding.bottom(); - } else { - _additionalWidth = 0; - _prizesTop = _withTop = _additionalTop; + auto top = 0; + for (auto &entry : _entries) { + const auto raw = entry.object.get(); + raw->initDimensions(); + top += raw->resizeGetHeight(maxWidth); } - const auto prizesSize = CountOptimalTextSize( - _prizes, - st::msgMinWidth, - available); - _prizesWidth = prizesSize.width(); - _participantsTitleTop = _prizesTop - + prizesSize.height() - + st::chatGiveawayParticipantsTop; - _participantsTop = _participantsTitleTop - + _participantsTitle.countHeight(available) - + st::chatGiveawayParticipantsSkip; - const auto participantsSize = CountOptimalTextSize( - _participants, - st::msgMinWidth, - available); - _participantsWidth = participantsSize.width(); - const auto channelsTop = _participantsTop - + participantsSize.height() - + st::chatGiveawayChannelTop; - const auto channelsBottom = layoutChannels( - padding.left(), - channelsTop, - available); - _countriesTop = channelsBottom; - if (_countries.isEmpty()) { - _winnersTitleTop = _countriesTop + st::chatGiveawayDateTop; - } else { - const auto countriesSize = CountOptimalTextSize( - _countries, - st::msgMinWidth, - available); - _countriesWidth = countriesSize.width(); - _winnersTitleTop = _countriesTop - + _countries.countHeight(available) - + st::chatGiveawayCountriesSkip; - } - _winnersTop = _winnersTitleTop - + _winnersTitle.countHeight(available) - + st::chatGiveawayDateSkip; - const auto height = _winnersTop - + _winners.countHeight(available) - + st::chatGiveawayBottomSkip; - return { maxWidth, height }; + return { maxWidth, top }; } -int Giveaway::layoutChannels(int x, int y, int available) { - const auto size = st::chatGiveawayChannelSize; - const auto skip = st::chatGiveawayChannelSkip; - const auto padding = st::chatGiveawayChannelPadding; - auto left = available; - const auto shiftRow = [&](int i, int top, int shift) { - for (auto j = i; j != 0; --j) { - auto &geometry = _channels[j - 1].geometry; - if (geometry.top() != top) { - break; - } - geometry.moveLeft(geometry.x() + shift); - } - }; - const auto count = int(_channels.size()); - for (auto i = 0; i != count; ++i) { - const auto desired = size - + padding.left() - + _channels[i].name.maxWidth() - + padding.right(); - const auto width = std::min(desired, available); - if (left < width) { - shiftRow(i, y, (left + skip) / 2); - left = available; - y += size + skip; - } - _channels[i].geometry = { x + available - left, y, width, size }; - left -= width + skip; - } - shiftRow(count, y, (left + skip) / 2); - return y + size + skip; -} - -QSize Giveaway::countCurrentSize(int newWidth) { +QSize MediaInBubble::countCurrentSize(int newWidth) { return { maxWidth(), minHeight()}; } -void Giveaway::draw(Painter &p, const PaintContext &context) const { - if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; - - const auto stm = context.messageStyle(); - - auto padding = inBubblePadding(); - +void MediaInBubble::draw(Painter &p, const PaintContext &context) const { const auto outer = width(); - const auto paintw = outer - padding.left() - padding.right(); + if (outer < st::msgPadding.left() + st::msgPadding.right() + 1) { + return; + } + auto translated = 0; + for (const auto &entry : _entries) { + const auto raw = entry.object.get(); + const auto height = raw->height(); + raw->draw(p, context, outer); + translated += height; + p.translate(0, height); + } + p.translate(0, -translated); +} + +TextState MediaInBubble::textState( + QPoint point, + StateRequest request) const { + auto result = TextState(_parent); + + if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { + return result; + } + + for (const auto &entry : _entries) { + const auto raw = entry.object.get(); + const auto height = raw->height(); + if (point.y() >= 0 && point.y() < height) { + const auto part = raw->textState(point, request); + result.link = part.link; + return result; + } + point.setY(point.y() - height); + } + return result; +} + +void MediaInBubble::clickHandlerActiveChanged( + const ClickHandlerPtr &p, + bool active) { +} + +void MediaInBubble::clickHandlerPressedChanged( + const ClickHandlerPtr &p, + bool pressed) { + for (const auto &entry : _entries) { + entry.object->clickHandlerPressedChanged(p, pressed); + } +} + +bool MediaInBubble::hideFromName() const { + return !parent()->data()->Has(); +} + +bool MediaInBubble::hasHeavyPart() const { + for (const auto &entry : _entries) { + if (entry.object->hasHeavyPart()) { + return true; + } + } + return false; +} + +void MediaInBubble::unloadHeavyPart() { + for (const auto &entry : _entries) { + entry.object->unloadHeavyPart(); + } +} + +QMargins MediaInBubble::inBubblePadding() const { + auto lshift = st::msgPadding.left(); + auto rshift = st::msgPadding.right(); + auto bshift = isBubbleBottom() ? st::msgPadding.top() : st::mediaInBubbleSkip; + auto tshift = isBubbleTop() ? st::msgPadding.bottom() : st::mediaInBubbleSkip; + return QMargins(lshift, tshift, rshift, bshift); +} + +TextMediaInBubblePart::TextMediaInBubblePart( + TextWithEntities text, + QMargins margins) +: _text(st::msgMinWidth) +, _margins(margins) { + _text.setMarkedText(st::defaultTextStyle, text); +} + +void TextMediaInBubblePart::draw( + Painter &p, + const PaintContext &context, + int outerWidth) const { + p.setPen(context.messageStyle()->historyTextFg); + _text.draw(p, { + .position = { (outerWidth - width()) / 2, _margins.top() }, + .outerWidth = outerWidth, + .availableWidth = width(), + .align = style::al_top, + .palette = &context.messageStyle()->textPalette, + .now = context.now, + }); +} + +QSize TextMediaInBubblePart::countOptimalSize() { + return { + _margins.left() + _text.maxWidth() + _margins.right(), + _margins.top() + _text.minHeight() + _margins.bottom(), + }; +} + +QSize TextMediaInBubblePart::countCurrentSize(int newWidth) { + auto skip = _margins.left() + _margins.right(); + const auto size = CountOptimalTextSize( + _text, + st::msgMinWidth, + newWidth - skip); + return { + size.width() + skip, + _margins.top() + size.height() + _margins.bottom(), + }; +} + +TextDelimeterPart::TextDelimeterPart( + const QString &text, + QMargins margins) +: _margins(margins) { + _text.setText(st::defaultTextStyle, text); +} + +void TextDelimeterPart::draw( + Painter &p, + const PaintContext &context, + int outerWidth) const { + const auto stm = context.messageStyle(); + const auto available = outerWidth - _margins.left() - _margins.right(); + p.setPen(stm->msgDateFg); + _text.draw(p, { + .position = { _margins.left(), _margins.top() }, + .outerWidth = outerWidth, + .availableWidth = available, + .align = style::al_top, + .palette = &stm->textPalette, + .now = context.now, + .elisionLines = 1, + }); + const auto skip = st::chatGiveawayPrizesWithSkip; + const auto inner = available - 2 * skip; + const auto sub = _text.maxWidth(); + if (inner > sub + 1) { + const auto fill = (inner - sub) / 2; + const auto stroke = st::lineWidth; + const auto top = _margins.top() + + st::chatGiveawayPrizesWithLineTop; + p.setOpacity(kAdditionalPrizesWithLineOpacity); + p.fillRect(_margins.left(), top, fill, stroke, stm->msgDateFg); + const auto start = outerWidth - _margins.right() - fill; + p.fillRect(start, top, fill, stroke, stm->msgDateFg); + p.setOpacity(1.); + } +} + +QSize TextDelimeterPart::countOptimalSize() { + return { + _margins.left() + _text.maxWidth() + _margins.right(), + _margins.top() + st::normalFont->height + _margins.bottom(), + }; +} + +QSize TextDelimeterPart::countCurrentSize(int newWidth) { + return { newWidth, minHeight() }; +} + +StickerWithBadgePart::StickerWithBadgePart( + not_null parent, + Fn lookup, + QString badge) +: _parent(parent) +, _lookup(std::move(lookup)) +, _badgeText(badge) { + ensureCreated(); +} + +void StickerWithBadgePart::draw( + Painter &p, + const PaintContext &context, + int outerWidth) const { const auto stickerSize = st::msgServiceGiftBoxStickerSize; const auto sticker = QRect( - (outer - stickerSize.width()) / 2, - _stickerTop, + (outerWidth - stickerSize.width()) / 2, + st::chatGiveawayStickerTop, stickerSize.width(), stickerSize.height()); @@ -303,62 +301,46 @@ void Giveaway::draw(Painter &p, const PaintContext &context) const { _sticker->draw(p, context, sticker); paintBadge(p, context); } else { - ensureStickerCreated(); + ensureCreated(); } - const auto paintText = [&]( - const Ui::Text::String &text, - int top, - int width) { - p.setPen(stm->historyTextFg); - text.draw(p, { - .position = { padding.left() + (paintw - width) / 2, top}, - .outerWidth = outer, - .availableWidth = width, - .align = style::al_top, - .palette = &stm->textPalette, - .now = context.now, - }); - }; - paintText(_prizesTitle, _prizesTitleTop, paintw); - if (!_additional.isEmpty()) { - paintText(_additional, _additionalTop, _additionalWidth); - p.setPen(stm->msgDateFg); - _with.draw(p, { - .position = { padding.left(), _withTop }, - .outerWidth = outer, - .availableWidth = paintw, - .align = style::al_top, - .palette = &stm->textPalette, - .now = context.now, - .elisionLines = 1, - }); - const auto skip = st::chatGiveawayPrizesWithPadding; - const auto inner = outer - 2 * (skip.left() + skip.right()); - const auto sub = _with.maxWidth(); - if (inner > sub + 1) { - const auto fill = (inner - sub) / 2; - const auto stroke = st::lineWidth; - const auto top = _withTop - + st::chatGiveawayPrizesWithTop; - p.setOpacity(kAdditionalPrizesWithLineOpacity); - p.fillRect(skip.left(), top, fill, stroke, stm->msgDateFg); - const auto start = outer - skip.left() - fill; - p.fillRect(start, top, fill, stroke, stm->msgDateFg); - p.setOpacity(1.); - } - } - paintText(_prizes, _prizesTop, _prizesWidth); - paintText(_participantsTitle, _participantsTitleTop, paintw); - paintText(_participants, _participantsTop, _participantsWidth); - if (!_countries.isEmpty()) { - paintText(_countries, _countriesTop, _countriesWidth); - } - paintText(_winnersTitle, _winnersTitleTop, paintw); - paintText(_winners, _winnersTop, paintw); - paintChannels(p, context); } -void Giveaway::paintBadge(Painter &p, const PaintContext &context) const { +bool StickerWithBadgePart::hasHeavyPart() { + return _sticker && _sticker->hasHeavyPart(); +} + +void StickerWithBadgePart::unloadHeavyPart() { + if (_sticker) { + _sticker->unloadHeavyPart(); + } +} + +QSize StickerWithBadgePart::countOptimalSize() { + const auto size = st::msgServiceGiftBoxStickerSize; + return { size.width(), st::chatGiveawayStickerTop + size.height() }; +} + +QSize StickerWithBadgePart::countCurrentSize(int newWidth) { + return { newWidth, minHeight() }; +} + +void StickerWithBadgePart::ensureCreated() const { + if (_sticker) { + return; + } else if (const auto document = _lookup()) { + if (const auto sticker = document->sticker()) { + const auto skipPremiumEffect = false; + _sticker.emplace(_parent, document, skipPremiumEffect, _parent); + _sticker->setDiceIndex(sticker->alt, 1); + _sticker->setGiftBoxSticker(true); + _sticker->initSize(); + } + } +} + +void StickerWithBadgePart::paintBadge( + Painter &p, + const PaintContext &context) const { validateBadge(context); const auto badge = _badge.size() / _badge.devicePixelRatio(); @@ -380,7 +362,7 @@ void Giveaway::paintBadge(Painter &p, const PaintContext &context) const { p.drawRoundedRect(inner, radius, radius); } - if (!usesBubblePattern(context)) { + if (!_parent->usesBubblePattern(context)) { paintContent(p); } else { Ui::PaintPatternBubblePart( @@ -393,86 +375,8 @@ void Giveaway::paintBadge(Painter &p, const PaintContext &context) const { } } -void Giveaway::paintChannels( - Painter &p, +void StickerWithBadgePart::validateBadge( const PaintContext &context) const { - if (_channels.empty()) { - return; - } - - const auto size = _channels[0].geometry.height(); - const auto st = context.st; - const auto stm = context.messageStyle(); - const auto selected = context.selected(); - const auto padding = st::chatGiveawayChannelPadding; - for (const auto &channel : _channels) { - const auto &thumbnail = channel.thumbnail; - const auto &geometry = channel.geometry; - if (!_subscribedToThumbnails) { - thumbnail->subscribeToUpdates([=] { repaint(); }); - } - - const auto colorIndex = channel.colorIndex; - const auto cache = context.outbg - ? stm->replyCache[st->colorPatternIndex(colorIndex)].get() - : st->coloredReplyCache(selected, colorIndex).get(); - if (channel.corners[0].isNull() || channel.bg != cache->bg) { - channel.bg = cache->bg; - channel.corners = Images::CornersMask(size / 2); - for (auto &image : channel.corners) { - style::colorizeImage(image, cache->bg, &image); - } - } - p.setPen(cache->icon); - Ui::DrawRoundedRect(p, geometry, channel.bg, channel.corners); - if (channel.ripple) { - channel.ripple->paint( - p, - geometry.x(), - geometry.y(), - width(), - &cache->bg); - if (channel.ripple->empty()) { - channel.ripple = nullptr; - } - } - - p.drawImage(geometry.topLeft(), thumbnail->image(size)); - const auto left = size + padding.left(); - const auto top = padding.top(); - const auto available = geometry.width() - left - padding.right(); - channel.name.draw(p, { - .position = { geometry.left() + left, geometry.top() + top }, - .outerWidth = width(), - .availableWidth = available, - .align = style::al_left, - .palette = &stm->textPalette, - .now = context.now, - .elisionLines = 1, - .elisionBreakEverywhere = true, - }); - } - _subscribedToThumbnails = 1; -} - -void Giveaway::ensureStickerCreated() const { - if (_sticker) { - return; - } - const auto &session = _parent->history()->session(); - auto &packs = session.giftBoxStickersPacks(); - if (const auto document = packs.lookup(_months)) { - if (const auto sticker = document->sticker()) { - const auto skipPremiumEffect = false; - _sticker.emplace(_parent, document, skipPremiumEffect, _parent); - _sticker->setDiceIndex(sticker->alt, 1); - _sticker->setGiftBoxSticker(true); - _sticker->initSize(); - } - } -} - -void Giveaway::validateBadge(const PaintContext &context) const { const auto stm = context.messageStyle(); const auto &badgeFg = stm->historyFileRadialFg->c; const auto &badgeBorder = stm->msgBg->c; @@ -484,11 +388,7 @@ void Giveaway::validateBadge(const PaintContext &context) const { const auto &font = st::chatGiveawayBadgeFont; _badgeFg = badgeFg; _badgeBorder = badgeBorder; - const auto text = tr::lng_prizes_badge( - tr::now, - lt_amount, - QString::number(_quantity)); - const auto width = font->width(text); + const auto width = font->width(_badgeText); const auto inner = QRect(0, 0, width, font->height); const auto rect = inner.marginsAdded(st::chatGiveawayBadgePadding); const auto size = rect.size(); @@ -512,78 +412,280 @@ void Giveaway::validateBadge(const PaintContext &context) const { p.drawText( st::chatGiveawayBadgePadding.left(), st::chatGiveawayBadgePadding.top() + font->ascent, - text); + _badgeText); } -TextState Giveaway::textState(QPoint point, StateRequest request) const { - auto result = TextState(_parent); +PeerBubbleListPart::PeerBubbleListPart( + not_null parent, + const std::vector> &list) +: _parent(parent) { + for (const auto &peer : list) { + _peers.push_back({ + .name = Ui::Text::String( + st::semiboldTextStyle, + peer->name(), + kDefaultTextOptions, + st::msgMinWidth), + .thumbnail = Dialogs::Stories::MakeUserpicThumbnail(peer), + .colorIndex = peer->colorIndex(), + }); + } +} - if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { - return result; +void PeerBubbleListPart::draw( + Painter &p, + const PaintContext &context, + int outerWidth) const { + if (_peers.empty()) { + return; } - for (const auto &channel : _channels) { - if (channel.geometry.contains(point)) { - result.link = channel.link; - _lastPoint = point; - return result; + const auto size = _peers[0].geometry.height(); + const auto st = context.st; + const auto stm = context.messageStyle(); + const auto selected = context.selected(); + const auto padding = st::chatGiveawayPeerPadding; + for (const auto &peer : _peers) { + const auto &thumbnail = peer.thumbnail; + const auto &geometry = peer.geometry; + if (!_subscribed) { + thumbnail->subscribeToUpdates([=] { _parent->repaint(); }); } - } - return result; -} -void Giveaway::clickHandlerActiveChanged( - const ClickHandlerPtr &p, - bool active) { -} - -void Giveaway::clickHandlerPressedChanged( - const ClickHandlerPtr &p, - bool pressed) { - for (auto &channel : _channels) { - if (channel.link != p) { - continue; - } - if (pressed) { - if (!channel.ripple) { - channel.ripple = std::make_unique( - st::defaultRippleAnimation, - Ui::RippleAnimation::RoundRectMask( - channel.geometry.size(), - channel.geometry.height() / 2), - [=] { repaint(); }); + const auto colorIndex = peer.colorIndex; + const auto cache = context.outbg + ? stm->replyCache[st->colorPatternIndex(colorIndex)].get() + : st->coloredReplyCache(selected, colorIndex).get(); + if (peer.corners[0].isNull() || peer.bg != cache->bg) { + peer.bg = cache->bg; + peer.corners = Images::CornersMask(size / 2); + for (auto &image : peer.corners) { + style::colorizeImage(image, cache->bg, &image); } - channel.ripple->add(_lastPoint - channel.geometry.topLeft()); - } else if (channel.ripple) { - channel.ripple->lastStop(); } - break; + p.setPen(cache->icon); + Ui::DrawRoundedRect(p, geometry, peer.bg, peer.corners); + if (peer.ripple) { + peer.ripple->paint( + p, + geometry.x(), + geometry.y(), + width(), + &cache->bg); + if (peer.ripple->empty()) { + peer.ripple = nullptr; + } + } + + p.drawImage(geometry.topLeft(), thumbnail->image(size)); + const auto left = size + padding.left(); + const auto top = padding.top(); + const auto available = geometry.width() - left - padding.right(); + peer.name.draw(p, { + .position = { geometry.left() + left, geometry.top() + top }, + .outerWidth = width(), + .availableWidth = available, + .align = style::al_left, + .palette = &stm->textPalette, + .now = context.now, + .elisionLines = 1, + .elisionBreakEverywhere = true, + }); } + _subscribed = true; } -bool Giveaway::hideFromName() const { - return !parent()->data()->Has(); +int PeerBubbleListPart::layout(int x, int y, int available) { + const auto size = st::chatGiveawayPeerSize; + const auto skip = st::chatGiveawayPeerSkip; + const auto padding = st::chatGiveawayPeerPadding; + auto left = available; + const auto shiftRow = [&](int i, int top, int shift) { + for (auto j = i; j != 0; --j) { + auto &geometry = _peers[j - 1].geometry; + if (geometry.top() != top) { + break; + } + geometry.moveLeft(geometry.x() + shift); + } + }; + const auto count = int(_peers.size()); + for (auto i = 0; i != count; ++i) { + const auto desired = size + + padding.left() + + _peers[i].name.maxWidth() + + padding.right(); + const auto width = std::min(desired, available); + if (left < width) { + shiftRow(i, y, (left + skip) / 2); + left = available; + y += size + skip; + } + _peers[i].geometry = { x + available - left, y, width, size }; + left -= width + skip; + } + shiftRow(count, y, (left + skip) / 2); + return y + size + skip; } -bool Giveaway::hasHeavyPart() const { - return _subscribedToThumbnails; +void PeerBubbleListPart::clickHandlerPressedChanged( + const ClickHandlerPtr &p, + bool pressed) { } -void Giveaway::unloadHeavyPart() { - if (_subscribedToThumbnails) { - _subscribedToThumbnails = 0; - for (const auto &channel : _channels) { - channel.thumbnail->subscribeToUpdates(nullptr); +bool PeerBubbleListPart::hasHeavyPart() { + return _subscribed; +} + +void PeerBubbleListPart::unloadHeavyPart() { + if (_subscribed) { + _subscribed = false; + for (const auto &peer : _peers) { + peer.thumbnail->subscribeToUpdates(nullptr); } } } -QMargins Giveaway::inBubblePadding() const { - auto lshift = st::msgPadding.left(); - auto rshift = st::msgPadding.right(); - auto bshift = isBubbleBottom() ? st::msgPadding.top() : st::mediaInBubbleSkip; - auto tshift = isBubbleTop() ? st::msgPadding.bottom() : st::mediaInBubbleSkip; - return QMargins(lshift, tshift, rshift, bshift); +QSize PeerBubbleListPart::countOptimalSize() { + if (_peers.empty()) { + return {}; + } + const auto size = st::chatGiveawayPeerSize; + const auto skip = st::chatGiveawayPeerSkip; + const auto padding = st::chatGiveawayPeerPadding; + auto left = st::msgPadding.left(); + for (const auto &peer : _peers) { + const auto desired = size + + padding.left() + + peer.name.maxWidth() + + padding.right(); + left += desired + skip; + } + return { left - skip + st::msgPadding.right(), size }; +} + +QSize PeerBubbleListPart::countCurrentSize(int newWidth) { + if (_peers.empty()) { + return {}; + } + const auto padding = st::msgPadding; + const auto available = newWidth - padding.left() - padding.right(); + const auto channelsBottom = layout( + padding.left(), + 0, + available); + return { newWidth, channelsBottom }; +} + +auto GenerateGiveawayStart( + not_null parent, + not_null giveaway) +-> Fn)>)> { + return [=](Fn)> push) { + const auto months = giveaway->months; + const auto quantity = giveaway->quantity; + + const auto sticker = [=] { + const auto &session = parent->history()->session(); + auto &packs = session.giftBoxStickersPacks(); + return packs.lookup(months); + }; + push(std::make_unique( + parent, + sticker, + tr::lng_prizes_badge( + tr::now, + lt_amount, + QString::number(quantity)))); + + auto pushText = [&](TextWithEntities text, QMargins margins = {}) { + push(std::make_unique( + std::move(text), + margins)); + }; + pushText( + Ui::Text::Bold( + tr::lng_prizes_title(tr::now, lt_count, quantity)), + st::chatGiveawayPrizesTitleMargin); + + if (!giveaway->additionalPrize.isEmpty()) { + pushText( + tr::lng_prizes_additional( + tr::now, + lt_count, + quantity, + lt_prize, + TextWithEntities{ giveaway->additionalPrize }, + Ui::Text::RichLangValue), + st::chatGiveawayPrizesMargin); + push(std::make_unique( + tr::lng_prizes_additional_with(tr::now), + st::chatGiveawayPrizesWithPadding)); + } + + pushText( + tr::lng_prizes_about( + tr::now, + lt_count, + quantity, + lt_duration, + Ui::Text::Bold(GiftDuration(months)), + Ui::Text::RichLangValue), + st::chatGiveawayPrizesMargin); + pushText( + Ui::Text::Bold(tr::lng_prizes_participants(tr::now)), + st::chatGiveawayPrizesTitleMargin); + + pushText({ (giveaway->all + ? tr::lng_prizes_participants_all + : tr::lng_prizes_participants_new)( + tr::now, + lt_count, + giveaway->channels.size()), + }, st::chatGiveawayParticipantsMargin); + + auto list = ranges::views::all( + giveaway->channels + ) | ranges::views::transform([](not_null channel) { + return not_null(channel); + }) | ranges::to_vector; + push(std::make_unique( + parent, + std::move(list))); + + const auto &instance = Countries::Instance(); + auto countries = QStringList(); + for (const auto &country : giveaway->countries) { + const auto name = instance.countryNameByISO2(country); + const auto flag = instance.flagEmojiByISO2(country); + countries.push_back(flag + QChar(0xA0) + name); + } + if (const auto count = countries.size()) { + auto united = countries.front(); + for (auto i = 1; i != count; ++i) { + united = ((i + 1 == count) + ? tr::lng_prizes_countries_and_last + : tr::lng_prizes_countries_and_one)( + tr::now, + lt_countries, + united, + lt_country, + countries[i]); + } + pushText({ + tr::lng_prizes_countries(tr::now, lt_countries, united), + }, st::chatGiveawayPrizesMargin); + } + + pushText( + Ui::Text::Bold(tr::lng_prizes_date(tr::now)), + (countries.empty() + ? st::chatGiveawayNoCountriesTitleMargin + : st::chatGiveawayPrizesMargin)); + pushText({ + langDateTime(base::unixtime::parse(giveaway->untilDate)), + }, st::chatGiveawayEndDateMargin); + }; } } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.h b/Telegram/SourceFiles/history/view/media/history_view_giveaway.h index 9f8aeed27..6f5afb8f7 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_giveaway.h +++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.h @@ -24,12 +24,30 @@ class RippleAnimation; namespace HistoryView { -class Giveaway final : public Media { +class MediaInBubble final : public Media { public: - Giveaway( + class Part : public Object { + public: + virtual ~Part() = default; + + virtual void draw( + Painter &p, + const PaintContext &context, + int outerWidth) const = 0; + [[nodiscard]] virtual TextState textState( + QPoint point, + StateRequest request) const; + virtual void clickHandlerPressedChanged( + const ClickHandlerPtr &p, + bool pressed); + [[nodiscard]] virtual bool hasHeavyPart(); + virtual void unloadHeavyPart(); + }; + + MediaInBubble( not_null parent, - not_null giveaway); - ~Giveaway(); + Fn)>)> generate); + ~MediaInBubble(); void draw(Painter &p, const PaintContext &context) const override; TextState textState(QPoint point, StateRequest request) const override; @@ -49,7 +67,7 @@ public: } bool toggleSelectionByHandlerClick( - const ClickHandlerPtr &p) const override { + const ClickHandlerPtr &p) const override { return true; } bool dragItemByHandler(const ClickHandlerPtr &p) const override { @@ -62,8 +80,113 @@ public: bool hasHeavyPart() const override; private: + struct Entry { + std::unique_ptr object; + int top = 0; + }; + + QSize countOptimalSize() override; + QSize countCurrentSize(int newWidth) override; + + [[nodiscard]] QMargins inBubblePadding() const; + + std::vector _entries; + +}; + +class TextMediaInBubblePart final : public MediaInBubble::Part { +public: + TextMediaInBubblePart(TextWithEntities text, QMargins margins); + + void draw( + Painter &p, + const PaintContext &context, + int outerWidth) const override; + + QSize countOptimalSize() override; + QSize countCurrentSize(int newWidth) override; + +private: + Ui::Text::String _text; + QMargins _margins; + +}; + +class TextDelimeterPart final : public MediaInBubble::Part { +public: + TextDelimeterPart(const QString &text, QMargins margins); + + void draw( + Painter &p, + const PaintContext &context, + int outerWidth) const override; + + QSize countOptimalSize() override; + QSize countCurrentSize(int newWidth) override; + +private: + Ui::Text::String _text; + QMargins _margins; + +}; + +class StickerWithBadgePart final : public MediaInBubble::Part { +public: + StickerWithBadgePart( + not_null parent, + Fn lookup, + QString badge); + + void draw( + Painter &p, + const PaintContext &context, + int outerWidth) const override; + bool hasHeavyPart() override; + void unloadHeavyPart() override; + + QSize countOptimalSize() override; + QSize countCurrentSize(int newWidth) override; + +private: + void ensureCreated() const; + void validateBadge(const PaintContext &context) const; + void paintBadge(Painter &p, const PaintContext &context) const; + + const not_null _parent; + Fn _lookup; + QString _badgeText; + mutable std::optional _sticker; + mutable QColor _badgeFg; + mutable QColor _badgeBorder; + mutable QImage _badge; + mutable QImage _badgeCache; + +}; + +class PeerBubbleListPart final : public MediaInBubble::Part { +public: + PeerBubbleListPart( + not_null parent, + const std::vector> &list); + + void draw( + Painter &p, + const PaintContext &context, + int outerWidth) const override; + void clickHandlerPressedChanged( + const ClickHandlerPtr &p, + bool pressed) override; + bool hasHeavyPart() override; + void unloadHeavyPart() override; + + QSize countOptimalSize() override; + QSize countCurrentSize(int newWidth) override; + +private: + int layout(int x, int y, int available); + using Thumbnail = Dialogs::Stories::Thumbnail; - struct Channel { + struct Peer { Ui::Text::String name; std::shared_ptr thumbnail; QRect geometry; @@ -74,55 +197,15 @@ private: uint8 colorIndex = 0; }; - void paintBadge(Painter &p, const PaintContext &context) const; - void paintChannels(Painter &p, const PaintContext &context) const; - int layoutChannels(int x, int y, int available); - QSize countOptimalSize() override; - QSize countCurrentSize(int newWidth) override; - - void fillFromData(not_null giveaway); - void ensureStickerCreated() const; - void validateBadge(const PaintContext &context) const; - - [[nodiscard]] QMargins inBubblePadding() const; - - mutable std::optional _sticker; - - Ui::Text::String _prizesTitle; - Ui::Text::String _additional; - Ui::Text::String _with; - Ui::Text::String _prizes; - Ui::Text::String _participantsTitle; - Ui::Text::String _participants; - std::vector _channels; - Ui::Text::String _countries; - Ui::Text::String _winnersTitle; - Ui::Text::String _winners; - - mutable QColor _badgeFg; - mutable QColor _badgeBorder; - mutable QImage _badge; - mutable QImage _badgeCache; - - mutable QPoint _lastPoint; - int _months = 0; - int _quantity = 0; - int _stickerTop = 0; - int _prizesTitleTop = 0; - int _additionalTop = 0; - int _additionalWidth = 0; - int _withTop = 0; - int _prizesTop = 0; - int _prizesWidth = 0; - int _participantsTitleTop = 0; - int _participantsTop = 0; - int _participantsWidth = 0; - int _countriesTop = 0; - int _countriesWidth = 0; - int _winnersTitleTop = 0; - int _winnersTop = 0; - mutable uint8 _subscribedToThumbnails : 1 = 0; + const not_null _parent; + std::vector _peers; + mutable bool _subscribed = false; }; +[[nodiscard]] auto GenerateGiveawayStart( + not_null parent, + not_null giveaway) +-> Fn)>)>; + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.cpp b/Telegram/SourceFiles/history/view/media/history_view_media.cpp index 048c43218..f64cb808b 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media.cpp @@ -341,11 +341,7 @@ auto Media::getBubbleSelectionIntervals( } bool Media::usesBubblePattern(const PaintContext &context) const { - return (context.selection != FullSelection) - && _parent->hasOutLayout() - && context.bubblesPattern - && !context.viewport.isEmpty() - && !context.bubblesPattern->pixmap.size().isEmpty(); + return _parent->usesBubblePattern(context); } PointState Media::pointState(QPoint point) const { diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index ae30cdcbd..7afd1cec3 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -132,6 +132,7 @@ messageMediaPoll#4bd6e798 poll:Poll results:PollResults = MessageMedia; messageMediaDice#3f7ee58b value:int emoticon:string = MessageMedia; messageMediaStory#68cb6283 flags:# via_mention:flags.1?true peer:Peer id:int story:flags.0?StoryItem = MessageMedia; messageMediaGiveaway#daad85b0 flags:# only_new_subscribers:flags.0?true winners_are_visible:flags.2?true channels:Vector countries_iso2:flags.1?Vector prize_description:flags.3?string quantity:int months:int until_date:int = MessageMedia; +messageMediaGiveawayResults#b11ff5a1 flags:# refunded:flags.0?true channel_id:long launch_msg_id:int winners_count:int unclaimed_count:int winners:Vector months:int prize_description:flags.1?string = MessageMedia; messageActionEmpty#b6aef7b0 = MessageAction; messageActionChatCreate#bd47cbad title:string users:Vector = MessageAction; @@ -1583,7 +1584,7 @@ premiumGiftCodeOption#257e962b flags:# users:int months:int store_product:flags. payments.checkedGiftCode#b722f158 flags:# via_giveaway:flags.2?true from_id:Peer giveaway_msg_id:flags.3?int to_id:flags.0?long date:int months:int used_date:flags.1?int chats:Vector users:Vector = payments.CheckedGiftCode; payments.giveawayInfo#4367daa0 flags:# participating:flags.0?true preparing_results:flags.3?true start_date:int joined_too_early_date:flags.1?int admin_disallowed_chat_id:flags.2?long disallowed_country:flags.4?string = payments.GiveawayInfo; -payments.giveawayInfoResults#8ac7e167 flags:# winner:flags.0?true refunded:flags.1?true start_date:int gift_code_slug:flags.0?string finish_date:int winners_count:int activated_count:int winners:flags.2?Vector = payments.GiveawayInfo; +payments.giveawayInfoResults#cd5570 flags:# winner:flags.0?true refunded:flags.1?true start_date:int gift_code_slug:flags.0?string finish_date:int winners_count:int activated_count:int = payments.GiveawayInfo; prepaidGiveaway#b2539d54 id:long months:int quantity:int date:int = PrepaidGiveaway; @@ -1620,6 +1621,7 @@ help.peerColorsNotModified#2ba1f5ce = help.PeerColors; help.peerColors#f8ed08 hash:int colors:Vector = help.PeerColors; storyPeerReaction#7decc433 peer_id:Peer date:int reaction:Reaction = StoryPeerReaction; +storyPeerPublicRepost#f7fbc17d peer_id:Peer story:StoryItem = StoryPeerReaction; stories.storyReactionsList#d86c162a flags:# count:int reactions:Vector chats:Vector users:Vector next_offset:flags.0?string = stories.StoryReactionsList; @@ -2198,7 +2200,7 @@ stories.getAllReadPeerStories#9b5ae7f9 = Updates; stories.getPeerMaxIDs#535983c3 id:Vector = Vector; stories.getChatsToSend#a56a8b60 = messages.Chats; stories.togglePeerStoriesHidden#bd0415c4 peer:InputPeer hidden:Bool = Bool; -stories.getStoryReactionsList#b9b2881f flags:# peer:InputPeer id:int reaction:flags.0?Reaction offset:flags.1?string limit:int = stories.StoryReactionsList; +stories.getStoryReactionsList#b9b2881f flags:# reposts_first:flags.2?true peer:InputPeer id:int reaction:flags.0?Reaction offset:flags.1?string limit:int = stories.StoryReactionsList; premium.getBoostsList#60f67660 flags:# gifts:flags.0?true peer:InputPeer offset:string limit:int = premium.BoostsList; premium.getMyBoosts#be77b4a = premium.MyBoosts; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index be34843b4..aaec33461 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -978,20 +978,17 @@ chatGiveawayBadgeFont: font(12px bold); chatGiveawayBadgeTop: 106px; chatGiveawayBadgePadding: margins(7px, 1px, 5px, 3px); chatGiveawayBadgeStroke: 2px; -chatGiveawayPrizesTop: 16px; -chatGiveawayPrizesSkip: 4px; -chatGiveawayPrizesWithPadding: margins(22px, 2px, 8px, 2px); -chatGiveawayPrizesWithTop: 9px; -chatGiveawayParticipantsTop: 16px; -chatGiveawayParticipantsSkip: 4px; -chatGiveawayChannelTop: 6px; -chatGiveawayChannelSize: 32px; -chatGiveawayChannelPadding: margins(5px, 7px, 12px, 0px); -chatGiveawayChannelSkip: 8px; -chatGiveawayCountriesSkip: 16px; -chatGiveawayDateTop: 6px; -chatGiveawayDateSkip: 4px; -chatGiveawayBottomSkip: 16px; +chatGiveawayPrizesTitleMargin: margins(11px, 16px, 11px, 4px); +chatGiveawayPrizesMargin: margins(11px, 0px, 11px, 0px); +chatGiveawayPrizesWithPadding: margins(22px, 2px, 22px, 2px); +chatGiveawayPrizesWithSkip: 8px; +chatGiveawayPrizesWithLineTop: 9px; +chatGiveawayParticipantsMargin: margins(11px, 0px, 11px, 6px); +chatGiveawayNoCountriesTitleMargin: margins(11px, 6px, 11px, 4px); +chatGiveawayEndDateMargin: margins(11px, 0px, 11px, 16px); +chatGiveawayPeerSize: 32px; +chatGiveawayPeerPadding: margins(5px, 7px, 12px, 0px); +chatGiveawayPeerSkip: 8px; chatSimilarRadius: 12px; chatSimilarArrowSize: 6px;