diff --git a/Telegram/Resources/animations/bomb.tgs b/Telegram/Resources/animations/bomb.tgs new file mode 100644 index 0000000000..62fc72a4e5 Binary files /dev/null and b/Telegram/Resources/animations/bomb.tgs differ diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 5b82702fa2..bf6ebaeb1f 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4122,6 +4122,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_craft_progress" = "Crafting"; "lng_gift_craft_progress_chance" = "{percent} success chance"; "lng_gift_craft_progress_fails" = "If crafting fails, all selected gifts will be consumed."; +"lng_gift_craft_failed" = "Crafting failed. Gifts consumed :("; "lng_auction_about_title" = "Auction"; "lng_auction_about_subtitle" = "Join the battle for exclusive gifts."; diff --git a/Telegram/Resources/qrc/telegram/animations.qrc b/Telegram/Resources/qrc/telegram/animations.qrc index 3902bfff8b..6b319f4bb5 100644 --- a/Telegram/Resources/qrc/telegram/animations.qrc +++ b/Telegram/Resources/qrc/telegram/animations.qrc @@ -55,6 +55,7 @@ ../../animations/passkeys.tgs ../../animations/ban.tgs ../../animations/cocoon.tgs + ../../animations/bomb.tgs ../../animations/profile/profile_muting.tgs ../../animations/profile/profile_unmuting.tgs diff --git a/Telegram/SourceFiles/boxes/star_gift_craft_animation.cpp b/Telegram/SourceFiles/boxes/star_gift_craft_animation.cpp index 32d28dbcd4..810f85c36e 100644 --- a/Telegram/SourceFiles/boxes/star_gift_craft_animation.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_craft_animation.cpp @@ -8,7 +8,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/star_gift_craft_animation.h" #include "base/call_delayed.h" +#include "chat_helpers/stickers_lottie.h" +#include "data/data_document.h" +#include "data/data_document_media.h" +#include "data/data_session.h" +#include "history/view/media/history_view_sticker_player.h" #include "info/peer_gifts/info_peer_gifts_common.h" +#include "lottie/lottie_common.h" +#include "main/main_session.h" #include "ui/image/image_prepare.h" #include "ui/top_background_gradient.h" #include "ui/painter.h" @@ -27,6 +34,10 @@ constexpr auto kFlightDuration = crl::time(400); constexpr auto kLoadingFadeInDuration = crl::time(150); constexpr auto kLoadingFadeOutDuration = crl::time(300); constexpr auto kLoadingMinDuration = crl::time(600); +constexpr auto kFailurePlayerStopFrame = 120; +constexpr auto kFailurePlayerFps = 60; +constexpr auto kFailurePlayerFadeOutFrames = 10; +constexpr auto kFailureScaleDuration = crl::time(200); struct Rotation { float64 x = 0.; @@ -42,18 +53,6 @@ struct Rotation { using RotationFn = Fn; -[[nodiscard]] Rotation RotateImpulse(Rotation impulse, Rotation orientation) { - const auto cosX = std::cos(orientation.x); - const auto sinX = std::sin(orientation.x); - const auto cosY = std::cos(orientation.y); - const auto sinY = std::sin(orientation.y); - - return { - impulse.x * cosY - impulse.y * sinX * sinY, - impulse.y * cosX, - }; -} - [[nodiscard]] RotationFn DecayingRotation(Rotation impulse, float64 decay) { const auto lambda = -std::log(decay) / kFrameDuration; return [=](Rotation initial, crl::time t) { @@ -62,7 +61,7 @@ using RotationFn = Fn; }; } -[[nodiscard]] RotationFn DecayingRotationFinal( +[[nodiscard]] RotationFn DecayingEndRotation( Rotation impulse, float64 decay, Rotation target, @@ -86,100 +85,53 @@ struct GiftAnimationConfig { RotationFn rotation; crl::time duration = 0; crl::time revealTime = 0; - int nextFaceIndex = 0; - int nextFaceRotation = 0; + int targetFaceIndex = 0; + int targetFaceRotation = 0; }; -const auto kGiftAnimations = std::array{{ - { DecayingRotationFinal({ 4.1, -8.2 }, 0.98, { 1, -2 }, 2200), 2200, 600, 1, 180 }, - { DecayingRotation({ 3.2, -5.9 }, 0.97), 1200, 0, 4, 180 }, - { DecayingRotationFinal({ 6.2, 7.8 }, 0.98, { 2, 1 }, 2200), 2200, 1000, 1, 0 }, - { DecayingRotation({ 7.0, 4.5 }, 0.97), 1200, 200, 5, 0 }, - { DecayingRotationFinal({ -6.5, 5.0 }, 0.98, { 0, 1 }, 2200), 2200, 800, 1, 0 }, - { DecayingRotation({ -2.5, 3.5 }, 0.98), 1200, 200, 2, 180 }, - { DecayingRotationFinal({ -4.4, -8.2 }, 0.98, { 0, -1.5 }, 2200), 2200, 600, 3, 0 }, -}}; - -[[nodiscard]] int GetFrontFaceAtRotation(float64 rotationX, float64 rotationY) { - struct Vec3 { - float64 x, y, z; - }; - - const auto rotateY = [](Vec3 v, float64 angle) { - const auto c = std::cos(angle); - const auto s = std::sin(angle); - return Vec3{ v.x * c + v.z * s, v.y, -v.x * s + v.z * c }; - }; - const auto rotateX = [](Vec3 v, float64 angle) { - const auto c = std::cos(angle); - const auto s = std::sin(angle); - return Vec3{ v.x, v.y * c - v.z * s, v.y * s + v.z * c }; - }; - - constexpr auto normals = std::array{{ - { 0., 0., -1. }, - { 0., 0., 1. }, - { -1., 0., 0. }, - { 1., 0., 0. }, - { 0., -1., 0. }, - { 0., 1., 0. }, - }}; - - auto bestFace = 0; - auto bestZ = 0.; - for (auto i = 0; i != 6; ++i) { - const auto transformed = rotateX(rotateY(normals[i], rotationY), rotationX); - if (i == 0 || transformed.z < bestZ) { - bestFace = i; - bestZ = transformed.z; - } - } - return bestFace; -} - -[[nodiscard]] int ComputeFaceRotation( - float64 rotationX, - float64 rotationY, - int faceIndex) { - struct Vec3 { - float64 x, y, z; - }; - - const auto rotateY = [](Vec3 v, float64 angle) { - const auto c = std::cos(angle); - const auto s = std::sin(angle); - return Vec3{ v.x * c + v.z * s, v.y, -v.x * s + v.z * c }; - }; - const auto rotateX = [](Vec3 v, float64 angle) { - const auto c = std::cos(angle); - const auto s = std::sin(angle); - return Vec3{ v.x, v.y * c - v.z * s, v.y * s + v.z * c }; - }; - - constexpr auto faceUpVectors = std::array{{ - { 0., -1., 0. }, - { 0., -1., 0. }, - { 0., -1., 0. }, - { 0., -1., 0. }, - { 0., 0., -1. }, - { 0., 0., 1. }, - }}; - - const auto faceUp = rotateX( - rotateY(faceUpVectors[faceIndex], rotationY), - rotationX); - - const auto screenUpY = faceUp.y; - const auto screenUpX = faceUp.x; - - auto rotation = 0; - if (std::abs(screenUpY) >= std::abs(screenUpX)) { - rotation = (screenUpY < 0.) ? 0 : 180; - } else { - rotation = (screenUpX < 0.) ? 90 : 270; - } - return rotation; -} +const auto kGiftAnimations = std::array{{ { + .rotation = DecayingEndRotation({ 4.1, -8.2 }, 0.98, { 1, -2 }, 2200), + .duration = 2200, + .revealTime = 600, + .targetFaceIndex = 1, + .targetFaceRotation = 180, +}, { + .rotation = DecayingRotation({ 3.2, -5.9 }, 0.97), + .duration = 1200, + .revealTime = 0, + .targetFaceIndex = 4, + .targetFaceRotation = 180, +}, { + .rotation = DecayingEndRotation({ 6.2, 7.8 }, 0.98, { 2, 1 }, 2200), + .duration = 2200, + .revealTime = 1000, + .targetFaceIndex = 1, + .targetFaceRotation = 0, +}, { + .rotation = DecayingRotation({ 7.0, 4.5 }, 0.97), + .duration = 1200, + .revealTime = 200, + .targetFaceIndex = 5, + .targetFaceRotation = 0, +}, { + .rotation = DecayingEndRotation({ -6.5, 5.0 }, 0.98, { 0, 1 }, 2200), + .duration = 2200, + .revealTime = 800, + .targetFaceIndex = 1, + .targetFaceRotation = 0, +}, { + .rotation = DecayingRotation({ -2.5, 3.5 }, 0.98), + .duration = 1200, + .revealTime = 200, + .targetFaceIndex = 2, + .targetFaceRotation = 180, +}, { + .rotation = DecayingEndRotation({ -4.4, -8.2 }, 0.98, { 0, -1.5 }, 2200), + .duration = 2200, + .revealTime = 600, + .targetFaceIndex = 3, + .targetFaceRotation = 0, +} }}; [[nodiscard]] QImage CreateBgGradient( QSize size, @@ -303,14 +255,18 @@ const auto kGiftAnimations = std::array{{ const auto &face = faces[faceIndex]; - auto transformedNormal = rotateX(rotateY(face.normal, rotationY), rotationX); + const auto transformedNormal = rotateX( + rotateY(face.normal, rotationY), + rotationX); if (transformedNormal.z >= 0.) { return std::nullopt; } auto result = std::array(); for (auto i = 0; i != 4; ++i) { - auto p = rotateX(rotateY(face.corners[i], rotationY), rotationX); + const auto p = rotateX( + rotateY(face.corners[i], rotationY), + rotationX); const auto viewZ = p.z + half + kFocalLength; if (viewZ <= 0.) { @@ -364,7 +320,7 @@ void PaintCubeFace( rotatedCorners[3] }); - QTransform transform; + auto transform = QTransform(); if (!QTransform::quadToQuad(srcRect, dstRect, transform)) { return; } @@ -415,7 +371,9 @@ void PaintCubeFace( visible.reserve(3); for (auto i = 0; i != 6; ++i) { - auto transformed = rotateX(rotateY(normals[i], rotationY), rotationX); + const auto transformed = rotateX( + rotateY(normals[i], rotationY), + rotationX); if (transformed.z < 0.) { visible.push_back({ i, transformed.z }); } @@ -433,10 +391,10 @@ void PaintCubeFace( void PaintCubeFirstFlight( QPainter &p, - const CraftAnimationState &animState, + not_null state, float64 progress, bool skipForgeIcon = false) { - const auto &shared = animState.shared; + const auto shared = state->shared.get(); const auto overlayBg = shared->forgeBgOverlay; auto sideBg = shared->forgeBg1; @@ -457,74 +415,227 @@ void PaintCubeFirstFlight( } } +std::unique_ptr SetupFailureAnimation( + not_null canvas, + not_null state) { + auto result = std::make_unique(); + const auto raw = result.get(); + + const auto document + = raw->document + = ChatHelpers::GenerateLocalTgsSticker( + state->shared->session, + u"bomb"_q); + const auto media = document->createMediaView(); + media->checkStickerLarge(); + + rpl::single() | rpl::then( + document->session().downloaderTaskFinished() + ) | rpl::filter([=] { + return media->loaded(); + }) | rpl::take(1) | rpl::on_next([=] { + const auto sticker = document->sticker(); + Assert(sticker != nullptr); + + const auto size = state->shared->forgeRect.size(); + raw->player = std::make_unique( + ChatHelpers::LottiePlayerFromDocument( + media.get(), + ChatHelpers::StickerLottieSize::InlineResults, + size, + Lottie::Quality::High)); + raw->player->setRepaintCallback([=] { canvas->update(); }); + }, raw->lifetime); + + return result; +} + +[[nodiscard]] float64 FailureAnimationScale( + not_null state) { + const auto animation = state->failureAnimation.get(); + return animation + ? animation->scaleAnimation.value(animation->scaleStarted ? 0. : 1.) + : 1.; +} + +void FailureAnimationCheck( + not_null state, + not_null canvas) { + const auto animation = state->failureAnimation.get(); + if (!animation) { + return; + } + const auto &config = kGiftAnimations[state->currentPhaseIndex]; + const auto elapsed = (crl::now() - state->animationStartTime); + const auto failureAnimationDuration = (1000 * kFailurePlayerStopFrame) + / kFailurePlayerFps; + if (!animation->playerStarted + && elapsed >= config.duration - failureAnimationDuration) { + animation->playerStarted = true; + } + if (!animation->scaleStarted + && elapsed >= config.duration - kFailureScaleDuration) { + animation->scaleStarted = true; + animation->scaleAnimation.start([=] { + canvas->update(); + }, 1., 0., kFailureScaleDuration, anim::easeInCubic); + } +} + +[[nodiscard]] std::optional FailureAnimationPrepareFrame( + not_null state, + not_null canvas, + int faceIndex) { + if (!state->failureAnimation) { + state->failureAnimation = SetupFailureAnimation( + canvas, + state); + } + const auto shared = state->shared.get(); + const auto animation = state->failureAnimation.get(); + if (!animation->player->ready()) { + return {}; + } else if (shared->finalSide.frame.isNull()) { + shared->finalSide = shared->forgeSides[faceIndex]; + shared->finalSide.frame.fill(shared->finalSide.bg); + } + if (animation->frame.isNull()) { + animation->frame = shared->finalSide.frame; + } + animation->frame.fill(Qt::transparent); + auto q = QPainter(&animation->frame); + const auto paused = !animation->playerStarted; + auto info = animation->player->frame( + st::giftBoxStickerTiny * 2, + QColor(0, 0, 0, 0), + false, + crl::now(), + paused); + const auto frame = info.image; + const auto finished + = animation->playerFinished + = (info.index + 1 == kFailurePlayerStopFrame); + if (finished) { + return 0.; + } + const auto result = (kFailurePlayerStopFrame - info.index) + / float64(kFailurePlayerFadeOutFrames); + if (!paused) { + animation->player->markFrameShown(); + } + const auto outer = animation->frame.size() + / animation->frame.devicePixelRatio(); + const auto size = frame.size() / style::DevicePixelRatio(); + q.drawImage( + QRect( + (outer.width() - size.width()) / 2, + (outer.height() - size.width()) / 2, + size.width(), + size.height()), + frame); + q.end(); + return result; +} + void PaintCube( QPainter &p, - CraftAnimationState *animState, + not_null state, + not_null canvas, QPointF center, float64 size) { - const auto &shared = animState->shared; + const auto shared = state->shared.get(); - const auto faces = GetVisibleCubeFaces(animState->rotationX, animState->rotationY); + FailureAnimationCheck(state, canvas); - if (!animState->nextFaceRevealed - && animState->currentConfigIndex >= 0) { - const auto &config = kGiftAnimations[animState->currentConfigIndex]; - const auto elapsed = (crl::now() - animState->animationStartTime); + const auto totalScale = FailureAnimationScale(state); + const auto showCube = (totalScale > 0.); + const auto scaledCube = showCube && (totalScale < 1.); + if (scaledCube) { + p.save(); + p.translate(center); + p.scale(totalScale, totalScale); + p.translate(-center); + } + + const auto faces = GetVisibleCubeFaces( + state->rotationX, + state->rotationY); + + if (!state->nextFaceRevealed + && state->currentPhaseIndex >= 0) { + const auto &config = kGiftAnimations[state->currentPhaseIndex]; + const auto elapsed = (crl::now() - state->animationStartTime); if (elapsed >= config.revealTime * anim::SlowMultiplier()) { - animState->nextFaceRevealed = true; + state->nextFaceRevealed = true; } } + const auto craftFailed = state->craftResult.has_value() + && !state->craftResult->get(); + + auto paintFailure = Fn(); for (const auto faceIndex : faces) { const auto corners = ComputeCubeFaceCorners( center, size, - animState->rotationX, - animState->rotationY, + state->rotationX, + state->rotationY, faceIndex); if (!corners) { continue; } - const auto thisFaceRevealed = animState->nextFaceRevealed - && (faceIndex == animState->nextFaceIndex); + const auto thisFaceRevealed = state->nextFaceRevealed + && (faceIndex == state->nextFaceIndex); auto faceImage = shared->forgeSides[faceIndex].frame; auto faceRotation = thisFaceRevealed - ? animState->nextFaceRotation + ? state->nextFaceRotation : 0; for (auto i = 0; i < 4; ++i) { - if (i != animState->currentlyFlying - && animState->giftToSide[i].face == faceIndex + if (i != state->currentlyFlying + && state->giftToSide[i].face == faceIndex && shared->corners[i].giftButton) { faceImage = shared->corners[i].gift(1.); - faceRotation = animState->giftToSide[i].rotation; + faceRotation = state->giftToSide[i].rotation; break; } } - if (thisFaceRevealed && animState->allGiftsLanded) { - if (shared->finalSide.frame.isNull()) { - shared->finalSide = shared->forgeSides[faceIndex]; - shared->finalSide.frame.fill(shared->finalSide.bg); - - auto p = QPainter(&shared->finalSide.frame); - const auto side = shared->finalSide.frame.width() / 6; - p.setPen(Qt::NoPen); - p.setBrush(Qt::red); - p.drawEllipse(side, side, side, side); + if (thisFaceRevealed && state->allGiftsLanded) { + if (craftFailed) { + const auto maybeOpacity = FailureAnimationPrepareFrame( + state, + canvas, + faceIndex); + if (maybeOpacity) { + if (const auto opacity = *maybeOpacity; opacity > 0.) { + paintFailure = [=, &p] { + if (opacity < 1.) { + p.setOpacity(opacity); + } + PaintCubeFace( + p, + state->failureAnimation->frame, + *corners, + faceRotation); + }; + } + faceImage = shared->finalSide.frame; + } } - faceImage = shared->finalSide.frame; } - - PaintCubeFace(p, faceImage, *corners, faceRotation); + if (showCube) { + PaintCubeFace(p, faceImage, *corners, faceRotation); + } } - const auto elapsed = (crl::now() - animState->animationStartTime) - / anim::SlowMultiplier(); - p.setPen(Qt::white); - p.setFont(st::semiboldFont); - p.drawText(0, st::semiboldFont->ascent, QString::number(elapsed, 'f', 2)); + if (scaledCube) { + p.restore(); + } + if (paintFailure) { + paintFailure(); + } } struct GiftFlightPosition { @@ -540,11 +651,16 @@ struct GiftFlightPosition { float64 startOffsetY) { const auto eased = progress; - const auto startOrigin = QPointF(originalRect.topLeft()) + QPointF(0, startOffsetY); - const auto targetOrigin = targetCenter - QPointF(targetSize, targetSize) / 2.; - const auto currentOrigin = startOrigin + (targetOrigin - startOrigin) * eased; + const auto startOrigin = QPointF(originalRect.topLeft()) + + QPointF(0, startOffsetY); + const auto targetOrigin = targetCenter + - QPointF(targetSize, targetSize) / 2.; + const auto currentOrigin = startOrigin + + (targetOrigin - startOrigin) * eased; - const auto originalSize = std::max(originalRect.width(), originalRect.height()); + const auto originalSize = std::max( + originalRect.width(), + originalRect.height()); const auto endScale = (originalSize > 0.) ? (targetSize / float64(originalSize)) : 1.; @@ -557,7 +673,7 @@ struct GiftFlightPosition { } [[nodiscard]] FacePlacement GetCameraFacingFreeFace( - const CraftAnimationState &state) { + not_null state) { struct Vec3 { float64 x, y, z; }; @@ -592,7 +708,7 @@ struct GiftFlightPosition { }}; const auto isOccupied = [&](int faceIndex) { - for (const auto &placement : state.giftToSide) { + for (const auto &placement : state->giftToSide) { if (placement.face == faceIndex) { return true; } @@ -608,8 +724,8 @@ struct GiftFlightPosition { continue; } const auto transformed = rotateX( - rotateY(normals[i], state.rotationY), - state.rotationX); + rotateY(normals[i], state->rotationY), + state->rotationX); if (bestFace < 0 || transformed.z < bestZ) { bestFace = i; bestZ = transformed.z; @@ -621,8 +737,8 @@ struct GiftFlightPosition { } const auto faceUp = rotateX( - rotateY(faceUpVectors[bestFace], state.rotationY), - state.rotationX); + rotateY(faceUpVectors[bestFace], state->rotationY), + state->rotationX); const auto screenUpY = faceUp.y; const auto screenUpX = faceUp.x; @@ -708,24 +824,34 @@ void PaintSlideOutCorner( const auto giftTopLeft = currentTopLeft; const auto &extend = st::defaultDropdownMenu.wrap.shadow.extend; - const auto innerTopLeft = giftTopLeft + QPointF(extend.left(), extend.top()); - const auto innerWidth = giftSize.width() - extend.left() - extend.right(); + const auto innerTopLeft = giftTopLeft + + QPointF(extend.left(), extend.top()); + const auto innerWidth = giftSize.width() + - extend.left() + - extend.right(); p.drawImage(giftTopLeft, frame); const auto badgeFade = std::clamp(1. - progress / 0.7, 0., 1.); if (!corner.percentBadge.isNull() && badgeFade > 0.) { - const auto badgeSize = QSizeF(corner.percentBadge.size()) / ratio; - const auto badgePos = innerTopLeft - QPointF(badgeSize.width() / 3., badgeSize.height() / 3.); + const auto badgeSize = QSizeF(corner.percentBadge.size()) + / ratio; + const auto badgePos = innerTopLeft + - QPointF(badgeSize.width() / 3., badgeSize.height() / 3.); p.setOpacity(badgeFade); p.drawImage(badgePos, corner.percentBadge); p.setOpacity(1.); } if (!corner.removeButton.isNull() && badgeFade > 0.) { - const auto removeSize = QSizeF(corner.removeButton.size()) / ratio; + const auto removeSize = QSizeF(corner.removeButton.size()) + / ratio; const auto removePos = innerTopLeft - + QPointF(innerWidth - removeSize.width() + removeSize.width() / 3., -removeSize.height() / 3.); + + QPointF( + (innerWidth + - removeSize.width() + + removeSize.width() / 3.), + -removeSize.height() / 3.); p.setOpacity(badgeFade); p.drawImage(removePos, corner.removeButton); p.setOpacity(1.); @@ -741,49 +867,54 @@ void PaintSlideOutCorner( void PaintSlideOutPhase( QPainter &p, - const std::shared_ptr &state, + not_null shared, QSize canvasSize, float64 progress) { const auto ratio = style::DevicePixelRatio(); - if (!state->topPart.isNull()) { - const auto topSize = QSizeF(state->topPart.size()) / ratio; + if (!shared->topPart.isNull()) { + const auto topSize = QSizeF(shared->topPart.size()) / ratio; const auto slideDistance = topSize.height(); const auto offsetY = slideDistance * progress; const auto opacity = 1. - progress; p.setOpacity(opacity); - p.drawImage(state->topPartRect.topLeft() - QPointF(0, offsetY), state->topPart); + p.drawImage( + shared->topPartRect.topLeft() - QPointF(0, offsetY), + shared->topPart); p.setOpacity(1.); } - if (!state->bottomPart.isNull()) { - const auto slideDistance = QSizeF(state->bottomPart.size()).height() / ratio; + if (!shared->bottomPart.isNull()) { + const auto slideDistance = QSizeF(shared->bottomPart.size()).height() + / ratio; const auto offsetY = slideDistance * progress; const auto opacity = 1. - progress; p.setOpacity(opacity); - p.drawImage(QPointF(0, state->bottomPartY + offsetY), state->bottomPart); + p.drawImage( + QPointF(0, shared->bottomPartY + offsetY), + shared->bottomPart); p.setOpacity(1.); } - const auto offset = state->craftingOffsetY; - for (const auto &corner : state->corners) { + const auto offset = shared->craftingOffsetY; + for (const auto &corner : shared->corners) { PaintSlideOutCorner(p, corner, offset, progress); } auto hq = PainterHighQualityEnabler(p); - const auto forge = state->forgeRect.translated(0, offset); + const auto forge = shared->forgeRect.translated(0, offset); const auto radius = st::boxRadius; p.setPen(Qt::NoPen); - p.setBrush(state->forgeBgOverlay); + p.setBrush(shared->forgeBgOverlay); p.drawRoundedRect(forge, radius, radius); st::craftForge.paintInCenter(p, forge, st::white->c); p.setOpacity(1. - progress); p.drawImage( forge.x() + st::craftForgePadding, forge.y() + st::craftForgePadding, - state->forgePercent); + shared->forgePercent); } [[nodiscard]] std::array RotateCornersForFace( @@ -802,10 +933,10 @@ void PaintSlideOutPhase( } void StartGiftFlight( - CraftAnimationState *animState, + not_null state, not_null canvas, int startIndex) { - const auto &corners = animState->shared->corners; + const auto &corners = state->shared->corners; auto nextGift = -1; for (auto i = startIndex; i < 4; ++i) { @@ -819,32 +950,32 @@ void StartGiftFlight( return; } - animState->currentlyFlying = nextGift; - animState->giftToSide[nextGift] = GetCameraFacingFreeFace(*animState); - animState->flightTargetCorners = std::nullopt; + state->currentlyFlying = nextGift; + state->giftToSide[nextGift] = GetCameraFacingFreeFace(state); + state->flightTargetCorners = std::nullopt; - animState->flightAnimation.start( + state->flightAnimation.start( [=] { canvas->update(); }, 0., 1., kFlightDuration, anim::easeInCubic); - if (!animState->continuousAnimation.animating()) { - animState->continuousAnimation.start(); + if (!state->continuousAnimation.animating()) { + state->continuousAnimation.start(); } } void StartGiftFlightToFace( - CraftAnimationState *animState, + not_null state, not_null canvas, int targetFace) { - const auto shared = animState->shared; + const auto shared = state->shared.get(); const auto &corners = shared->corners; auto nextCorner = -1; for (auto i = 0; i < 4; ++i) { - if (corners[i].giftButton && animState->giftToSide[i].face < 0) { + if (corners[i].giftButton && state->giftToSide[i].face < 0) { nextCorner = i; break; } @@ -854,19 +985,19 @@ void StartGiftFlightToFace( return; } - animState->currentlyFlying = nextCorner; + state->currentlyFlying = nextCorner; - const auto faceRotation = animState->nextFaceRotation; - animState->giftToSide[nextCorner] = FacePlacement{ + const auto faceRotation = state->nextFaceRotation; + state->giftToSide[nextCorner] = FacePlacement{ .face = targetFace, .rotation = faceRotation, }; - if (animState->currentConfigIndex >= 0) { - const auto &config = kGiftAnimations[animState->currentConfigIndex]; + if (state->currentPhaseIndex >= 0) { + const auto &config = kGiftAnimations[state->currentPhaseIndex]; const auto initial = Rotation{ - animState->initialRotationX, - animState->initialRotationY, + state->initialRotationX, + state->initialRotationY, }; const auto endRotation = config.rotation(initial, config.duration); @@ -882,68 +1013,69 @@ void StartGiftFlightToFace( endRotation.y, targetFace); if (targetCorners) { - animState->flightTargetCorners = RotateCornersForFace( + state->flightTargetCorners = RotateCornersForFace( *targetCorners, faceRotation); } else { - animState->flightTargetCorners = std::nullopt; + state->flightTargetCorners = std::nullopt; } } else { - animState->flightTargetCorners = std::nullopt; + state->flightTargetCorners = std::nullopt; } - animState->flightAnimation.start( - [=] { canvas->update(); }, - 0., - 1., - kFlightDuration, - anim::easeInCubic); + state->flightAnimation.start([=] { + canvas->update(); + }, 0., 1., kFlightDuration, anim::easeInCubic); - if (!animState->continuousAnimation.animating()) { - animState->continuousAnimation.start(); + if (!state->continuousAnimation.animating()) { + state->continuousAnimation.start(); } } -void LandCurrentGift(CraftAnimationState *animState, crl::time now) { - if (animState->currentlyFlying < 0) { +void LandCurrentGift(not_null state, crl::time now) { + if (state->currentlyFlying < 0) { return; } - const auto giftNumber = ++animState->giftsLanded; + const auto giftNumber = ++state->giftsLanded; const auto isLastGift - = animState->allGiftsLanded - = (giftNumber >= animState->totalGifts); + = state->allGiftsLanded + = (giftNumber >= state->totalGifts); const auto configIndex = (giftNumber - 1) * 2 + (isLastGift ? 0 : 1); Assert(configIndex < 7); const auto &config = kGiftAnimations[configIndex]; - animState->currentlyFlying = -1; + state->currentlyFlying = -1; - animState->initialRotationX = animState->rotationX; - animState->initialRotationY = animState->rotationY; - animState->currentConfigIndex = configIndex; - animState->animationStartTime = now; - animState->nextFaceIndex = config.nextFaceIndex; - animState->nextFaceRotation = config.nextFaceRotation; - animState->nextFaceRevealed = false; + state->initialRotationX = state->rotationX; + state->initialRotationY = state->rotationY; + state->currentPhaseIndex = configIndex; + state->currentPhaseFinished = false; + state->animationStartTime = now; + state->nextFaceIndex = config.targetFaceIndex; + state->nextFaceRotation = config.targetFaceRotation; + state->nextFaceRevealed = false; } -void PaintLoadingAnimation(QPainter &p, CraftAnimationState *animState) { - const auto &loading = animState->loadingAnimation; - if (!loading || !animState->loadingStartedTime) { +void PaintLoadingAnimation( + QPainter &p, + not_null state) { + const auto &loading = state->loadingAnimation; + if (!loading || !state->loadingStartedTime) { return; } - const auto loadingShown = animState->loadingShownAnimation.value( - animState->loadingFadingOut ? 0. : 1.); + const auto loadingShown = state->loadingShownAnimation.value( + state->loadingFadingOut ? 0. : 1.); if (loadingShown <= 0.) { return; } - const auto shared = animState->shared; - const auto forge = shared->forgeRect.translated(0, shared->craftingOffsetY); + const auto shared = state->shared.get(); + const auto forge = shared->forgeRect.translated( + { 0, shared->craftingOffsetY }); const auto inner = forge.marginsRemoved({ st::craftForgePadding, st::craftForgePadding, @@ -951,11 +1083,11 @@ void PaintLoadingAnimation(QPainter &p, CraftAnimationState *animState) { st::craftForgePadding, }); - const auto state = loading->computeState(); + const auto radial = loading->computeState(); const auto adjustedState = RadialState{ - .shown = state.shown * loadingShown, - .arcFrom = state.arcFrom, - .arcLength = state.arcLength, + .shown = radial.shown * loadingShown, + .arcFrom = radial.arcFrom, + .arcLength = radial.arcLength, }; auto pen = QPen(st::white->c); @@ -986,7 +1118,11 @@ QImage CraftState::CornerSnapshot::gift(float64 progress) const { return giftFrame; } -void CraftState::paint(QPainter &p, QSize size, int craftingHeight, float64 slideProgress) { +void CraftState::paint( + QPainter &p, + QSize size, + int craftingHeight, + float64 slideProgress) { const auto width = size.width(); const auto getBackdrop = [&](BackdropView &backdrop) { const auto ratio = style::DevicePixelRatio(); @@ -998,8 +1134,11 @@ void CraftState::paint(QPainter &p, QSize size, int craftingHeight, float64 slid return gradient; }; auto patternOffsetY = 0.; - if (slideProgress > 0. && containerHeight > 0 && craftingAreaCenterY > 0) { - patternOffsetY = (containerHeight / 2. - craftingAreaCenterY) * slideProgress; + if (slideProgress > 0. + && containerHeight > 0 + && craftingAreaCenterY > 0) { + patternOffsetY = (containerHeight / 2. - craftingAreaCenterY) + * slideProgress; } const auto paintPattern = [&]( QPainter &q, @@ -1118,34 +1257,40 @@ CraftState::EmptySide CraftState::prepareEmptySide(int index) const { void StartCraftAnimation( not_null container, - std::shared_ptr state, - Fn startRequest) { + std::shared_ptr shared, + Fn startRequest, + Fn closeOnFail) { while (container->count() > 0) { delete container->widgetAt(0); } - - const auto height = state->containerHeight; - const auto craftingHeight = state->craftingBottom - state->craftingTop; - auto canvas = object_ptr(container); const auto raw = canvas.data(); + const auto state = raw->lifetime().make_state(); + + const auto height = shared->containerHeight; + const auto craftingHeight = shared->craftingBottom - shared->craftingTop; + state->shared = std::move(shared); + raw->resize(container->width(), height); - const auto animState = raw->lifetime().make_state(); - animState->shared = std::move(state); - for (auto &corner : animState->shared->corners) { + for (auto &corner : state->shared->corners) { if (corner.giftButton) { - ++animState->totalGifts; + ++state->totalGifts; } } - animState->loadingAnimation = std::make_unique( + state->loadingAnimation = std::make_unique( [=] { raw->update(); }, st::craftForgeLoading); raw->paintOn([=](QPainter &p) { - const auto shared = animState->shared; - const auto slideProgress = animState->slideOutAnimation.value(1.); + if (state->failureAnimation + && state->failureAnimation->playerFinished) { + crl::on_main(closeOnFail); + } + + const auto shared = state->shared.get(); + const auto slideProgress = state->slideOutAnimation.value(1.); shared->paint(p, raw->size(), craftingHeight, slideProgress); shared->craftingOffsetY = (shared->containerHeight / 2.) @@ -1161,43 +1306,52 @@ void StartCraftAnimation( for (auto i = 0; i < 4; ++i) { const auto &corner = shared->corners[i]; - if (corner.giftButton && animState->giftToSide[i].face < 0) { - const auto giftTopLeft = QPointF(corner.originalRect.topLeft()) + if (corner.giftButton && state->giftToSide[i].face < 0) { + const auto giftTopLeft = QPointF() + + QPointF(corner.originalRect.topLeft()) + QPointF(0, shared->craftingOffsetY); p.drawImage(giftTopLeft, corner.gift(0)); } } - const auto flying = animState->currentlyFlying; + const auto flying = state->currentlyFlying; const auto firstFlyProgress = (flying == 0) - ? animState->flightAnimation.value(1.) - : animState->giftsLanded + ? state->flightAnimation.value(1.) + : state->giftsLanded ? 1. : 0.; - const auto loadingVisible = animState->loadingStartedTime - && !animState->loadingFadingOut; + const auto loadingShown = state->loadingStartedTime + && !state->loadingFadingOut; if (firstFlyProgress < 1.) { - const auto skipForgeIcon = loadingVisible && anim::Disabled(); - PaintCubeFirstFlight(p, *animState, firstFlyProgress, skipForgeIcon); + const auto skipForgeIcon = loadingShown && anim::Disabled(); + PaintCubeFirstFlight( + p, + state, + firstFlyProgress, + skipForgeIcon); } else { - PaintCube(p, animState, cubeCenter, cubeSize); + PaintCube(p, state, raw, cubeCenter, cubeSize); } if (flying >= 0) { const auto &corner = shared->corners[flying]; const auto progress = (flying > 0) - ? animState->flightAnimation.value(1.) + ? state->flightAnimation.value(1.) : firstFlyProgress; - if (animState->flightTargetCorners) { + if (state->flightTargetCorners) { const auto sourceRect = QRectF(corner.originalRect) .translated(0, shared->craftingOffsetY); const auto sourceCorners = RectToCorners(sourceRect); const auto interpolatedCorners = InterpolateQuadCorners( sourceCorners, - *animState->flightTargetCorners, + *state->flightTargetCorners, + progress); + PaintFlyingGiftWithQuad( + p, + corner, + interpolatedCorners, progress); - PaintFlyingGiftWithQuad(p, corner, interpolatedCorners, progress); } else { const auto position = ComputeGiftFlightPosition( corner.originalRect, @@ -1209,35 +1363,35 @@ void StartCraftAnimation( } } - PaintLoadingAnimation(p, animState); + PaintLoadingAnimation(p, state); } }); const auto startFlying = [=] { - if (animState->loadingStartedTime > 0) { - animState->loadingFadingOut = true; - animState->loadingShownAnimation.start( + if (state->loadingStartedTime > 0) { + state->loadingFadingOut = true; + state->loadingShownAnimation.start( [=] { raw->update(); }, 1., 0., kLoadingFadeOutDuration); } - StartGiftFlight(animState, raw, 0); + StartGiftFlight(state, raw, 0); }; const auto tryStartFlying = [=] { - const auto slideFinished = !animState->slideOutAnimation.animating(); - const auto resultArrived = animState->craftResult.has_value(); - const auto notYetFlying = (animState->currentlyFlying < 0) - && (animState->giftsLanded == 0); + const auto slideFinished = !state->slideOutAnimation.animating(); + const auto resultArrived = state->craftResult.has_value(); + const auto notYetFlying = (state->currentlyFlying < 0) + && (state->giftsLanded == 0); if (!slideFinished || !notYetFlying) { return; } if (!resultArrived) { - if (!animState->loadingStartedTime) { - animState->loadingStartedTime = crl::now(); - animState->loadingAnimation->start(); - animState->loadingShownAnimation.start( + if (!state->loadingStartedTime) { + state->loadingStartedTime = crl::now(); + state->loadingAnimation->start(); + state->loadingShownAnimation.start( [=] { raw->update(); }, 0., 1., @@ -1245,88 +1399,92 @@ void StartCraftAnimation( } return; } - if (!animState->loadingStartedTime) { + if (!state->loadingStartedTime) { startFlying(); return; } - const auto elapsed = crl::now() - animState->loadingStartedTime; + const auto elapsed = crl::now() - state->loadingStartedTime; if (elapsed >= kLoadingMinDuration) { startFlying(); } else { - base::call_delayed(kLoadingMinDuration - elapsed, raw, startFlying); + base::call_delayed( + kLoadingMinDuration - elapsed, + raw, + startFlying); } }; - animState->slideOutAnimation.start( - [=] { - raw->update(); + state->slideOutAnimation.start([=] { + raw->update(); - const auto progress = animState->slideOutAnimation.value(1.); - if (progress >= 1.) { - tryStartFlying(); - } - }, - 0., - 1., - crl::time(300), - anim::easeOutCubic); + const auto progress = state->slideOutAnimation.value(1.); + if (progress >= 1.) { + tryStartFlying(); + } + }, 0., 1., crl::time(300), anim::easeOutCubic); - animState->continuousAnimation.init([=](crl::time now) { - if (animState->currentConfigIndex >= 0) { - const auto &config = kGiftAnimations[animState->currentConfigIndex]; - const auto elapsed = (now - animState->animationStartTime) + state->continuousAnimation.init([=](crl::time now) { + if (state->currentPhaseIndex >= 0) { + const auto &config = kGiftAnimations[state->currentPhaseIndex]; + const auto elapsed = (now - state->animationStartTime) / anim::SlowMultiplier(); const auto initial = Rotation{ - animState->initialRotationX, - animState->initialRotationY, + state->initialRotationX, + state->initialRotationY, }; const auto r = config.rotation(initial, elapsed); - animState->rotationX = r.x; - animState->rotationY = r.y; + state->rotationX = r.x; + state->rotationY = r.y; } raw->update(); - if (animState->currentlyFlying >= 0 - && !animState->flightAnimation.animating()) { - LandCurrentGift(animState, now); + if (state->currentlyFlying >= 0 + && !state->flightAnimation.animating()) { + LandCurrentGift(state, now); } - if (!animState->flightAnimation.animating() - && animState->currentlyFlying < 0 - && animState->giftsLanded > 0 - && animState->giftsLanded < animState->totalGifts - && animState->currentConfigIndex >= 0) { + if (!state->flightAnimation.animating() + && state->currentlyFlying < 0 + && state->giftsLanded > 0 + && state->giftsLanded < state->totalGifts + && state->currentPhaseIndex >= 0) { - const auto &config = kGiftAnimations[animState->currentConfigIndex]; - const auto elapsed = (now - animState->animationStartTime) + const auto &config = kGiftAnimations[state->currentPhaseIndex]; + const auto elapsed = (now - state->animationStartTime) / anim::SlowMultiplier(); const auto flightStartTime = config.duration - kFlightDuration; if (elapsed >= flightStartTime) { - StartGiftFlightToFace(animState, raw, animState->nextFaceIndex); + StartGiftFlightToFace(state, raw, state->nextFaceIndex); } } - if (!animState->flightAnimation.animating() - && animState->currentlyFlying < 0 - && animState->giftsLanded >= animState->totalGifts - && animState->totalGifts > 0 - && animState->currentConfigIndex >= 0) { + if (!state->currentPhaseFinished + && !state->flightAnimation.animating() + && state->currentlyFlying < 0 + && state->giftsLanded >= state->totalGifts + && state->totalGifts > 0 + && state->currentPhaseIndex >= 0) { - const auto &config = kGiftAnimations[animState->currentConfigIndex]; - const auto elapsed = (now - animState->animationStartTime) + const auto &config = kGiftAnimations[state->currentPhaseIndex]; + const auto elapsed = (now - state->animationStartTime) / anim::SlowMultiplier(); if (elapsed >= config.duration) { - animState->currentConfigIndex = -1; + state->currentPhaseFinished = true; } } - const auto hasMoreFlights = (animState->giftsLanded < animState->totalGifts); - const auto isFlying = animState->flightAnimation.animating(); - const auto isAnimating = (animState->currentConfigIndex >= 0); - const auto isLoadingShown = animState->loadingShownAnimation.animating(); + const auto hasMoreFlights = (state->giftsLanded < state->totalGifts); + const auto isFlying = state->flightAnimation.animating(); + const auto isAnimating = !state->currentPhaseFinished; + const auto isLoadingShown = state->loadingShownAnimation.animating(); + const auto isFailureAnimation = state->failureAnimation != nullptr; - return hasMoreFlights || isFlying || isAnimating || isLoadingShown; + return hasMoreFlights + || isFlying + || isAnimating + || isLoadingShown + || isFailureAnimation; }); container->add(std::move(canvas)); @@ -1337,7 +1495,7 @@ void StartCraftAnimation( if (!weak) { return; } - animState->craftResult = std::move(result); + state->craftResult = std::move(result); tryStartFlying(); }); } diff --git a/Telegram/SourceFiles/boxes/star_gift_craft_animation.h b/Telegram/SourceFiles/boxes/star_gift_craft_animation.h index 4aab84a72b..7c74e1956c 100644 --- a/Telegram/SourceFiles/boxes/star_gift_craft_animation.h +++ b/Telegram/SourceFiles/boxes/star_gift_craft_animation.h @@ -13,6 +13,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/radial_animation.h" #include "ui/text/text_custom_emoji.h" +namespace Main { +class Session; +} // namespace Main + +namespace HistoryView { +class StickerPlayer; +} // namespace HistoryView + namespace Info::PeerGifts { class GiftButton; } // namespace Info::PeerGifts @@ -26,13 +34,11 @@ using CraftResultCallback = Fn)>; struct BackdropView { Data::UniqueGiftBackdrop colors; QImage gradient; - }; struct PatternView { std::unique_ptr emoji; base::flat_map> emojis; - }; struct CraftState { @@ -84,6 +90,8 @@ struct CraftState { std::array forgeSides; EmptySide finalSide; + Main::Session *session = nullptr; + int containerHeight = 0; int craftingTop = 0; int craftingBottom = 0; @@ -93,13 +101,22 @@ struct CraftState { void paint(QPainter &p, QSize size, int craftingHeight, float64 slideProgress = 0.); void updateForGiftCount(int count); [[nodiscard]] EmptySide prepareEmptySide(int index) const; - }; struct FacePlacement { int face = -1; int rotation = 0; +}; +struct FailureAnimation { + std::unique_ptr player; + QImage frame; + DocumentData *document = nullptr; + Animations::Simple scaleAnimation; + bool scaleStarted = false; + bool playerStarted = false; + bool playerFinished = false; + rpl::lifetime lifetime; }; struct CraftAnimationState { @@ -115,10 +132,11 @@ struct CraftAnimationState { int giftsLanded = 0; int totalGifts = 0; bool allGiftsLanded = false; + bool currentPhaseFinished = false; std::array giftToSide; Animations::Simple flightAnimation; - int currentConfigIndex = -1; + int currentPhaseIndex = -1; crl::time animationStartTime = 0; float64 initialRotationX = 0.; float64 initialRotationY = 0.; @@ -134,11 +152,13 @@ struct CraftAnimationState { crl::time loadingStartedTime = 0; bool loadingFadingOut = false; + std::unique_ptr failureAnimation; }; void StartCraftAnimation( not_null container, std::shared_ptr state, - Fn startRequest); + Fn startRequest, + Fn closeOnFail); } // namespace Ui diff --git a/Telegram/SourceFiles/boxes/star_gift_craft_box.cpp b/Telegram/SourceFiles/boxes/star_gift_craft_box.cpp index 5c0bc8d34e..cc11111699 100644 --- a/Telegram/SourceFiles/boxes/star_gift_craft_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_craft_box.cpp @@ -804,9 +804,10 @@ void ShowSelectGiftBox( } void Craft( - not_null container, + not_null box, std::shared_ptr state, - const std::vector &gifts) { + const std::vector &gifts, + Fn closeParent) { auto startRequest = [=](CraftResultCallback done) { constexpr auto kDelays = std::array{ 100, 200, 300, 400, 500, 1000, 2000 @@ -814,10 +815,11 @@ void Craft( const auto delay = kDelays[base::RandomIndex(kDelays.size())]; const auto giftsCopy = gifts; - base::call_delayed(delay, container, [=] { - const auto shouldSucceed = (base::RandomIndex(5) != 0); - if (shouldSucceed && !giftsCopy.empty()) { - const auto &chosen = giftsCopy[base::RandomIndex(giftsCopy.size())]; + base::call_delayed(delay, box, [=] { + const auto shouldSucceed = true;// (base::RandomIndex(5) != 0); + const auto count = int(giftsCopy.size()); + if (shouldSucceed && !count) { + const auto &chosen = giftsCopy[base::RandomIndex(count)]; auto info = Data::StarGift{ .id = chosen.unique->initialGiftId, .unique = chosen.unique, @@ -834,14 +836,31 @@ void Craft( } }); }; - StartCraftAnimation(container, std::move(state), std::move(startRequest)); + + const auto container = box->verticalLayout(); + const auto show = box->uiShow(); + const auto closeOnFail = crl::guard(box, [=] { + if (const auto onstack = closeParent) { + onstack(); + } + box->closeBox(); + show->showToast(tr::lng_gift_craft_failed(tr::now)); + }); + StartCraftAnimation( + container, + std::move(state), + std::move(startRequest), + closeOnFail); } void MakeCraftContent( not_null box, not_null controller, std::vector gifts, + Fn closeParent, bool autoStartCraft) { + Expects(!gifts.empty()); + struct State { std::shared_ptr craftState; GradientButton *button = nullptr; @@ -861,9 +880,11 @@ void MakeCraftContent( bool crafting = false; }; const auto session = &controller->session(); + const auto initialGiftId = gifts.front().unique->initialGiftId; const auto state = box->lifetime().make_state(); state->craftState = std::make_shared(); + state->craftState->session = session; state->craftState->coversAnimate = true; { @@ -935,7 +956,6 @@ void MakeCraftContent( } }, raw->lifetime()); - const auto initialGiftId = state->chosen.current().front().unique->initialGiftId; std::move( crafting.editRequests ) | rpl::on_next([=](int index) { @@ -1071,7 +1091,7 @@ void MakeCraftContent( const auto renderWidget = [&](QWidget *widget) { const auto pos = widget->pos() - bottomRect.topLeft(); - widget->render(&bottomPart, pos, QRegion(), QWidget::DrawChildren); + widget->render(&bottomPart, pos, {}, QWidget::DrawChildren); }; renderWidget(state->about); renderWidget(state->attributes); @@ -1081,7 +1101,7 @@ void MakeCraftContent( cs->bottomPartY = aboutPos.y(); cs->containerHeight = raw->height(); - Craft(raw, state->craftState, state->chosen.current()); + Craft(box, state->craftState, state->chosen.current(), closeParent); }; button->setClickedCallback(startCrafting); @@ -1113,12 +1133,18 @@ void MakeCraftContent( void ShowGiftCraftBoxInternal( not_null controller, std::vector gifts, + Fn closeParent, bool autoStartCraft) { controller->show(Box([=](not_null box) { box->setStyle(st::giftCraftBox); box->setWidth(st::boxWidth); box->setNoContentMargin(true); - MakeCraftContent(box, controller, gifts, autoStartCraft); + MakeCraftContent( + box, + controller, + gifts, + closeParent, + autoStartCraft); AddUniqueCloseButton(box); #if _DEBUG @@ -1132,7 +1158,7 @@ void ShowGiftCraftBoxInternal( } else { copy = full; } - ShowGiftCraftBoxInternal(controller, copy, true); + ShowGiftCraftBoxInternal(controller, copy, [] {}, true); }); }, box->lifetime()); } @@ -1145,7 +1171,8 @@ void ShowGiftCraftBoxInternal( void ShowGiftCraftInfoBox( not_null controller, std::shared_ptr gift, - Data::SavedStarGiftId savedId) { + Data::SavedStarGiftId savedId, + Fn closeParent) { controller->show(Box([=](not_null box) { const auto container = box->verticalLayout(); auto cover = tr::lng_gift_craft_info_title( @@ -1189,10 +1216,15 @@ void ShowGiftCraftInfoBox( box->setNoContentMargin(true); box->addButton(tr::lng_gift_craft_start_button(), [=] { + const auto weak = base::make_weak(box); ShowGiftCraftBoxInternal( controller, { GiftForCraft{ gift, savedId } }, + closeParent, false); + if (const auto strong = box.get()) { + strong->closeBox(); + } }); })); } @@ -1208,7 +1240,7 @@ void ShowTestGiftCraftBox( entry.manageId = std::move(gift.manageId); converted.push_back(std::move(entry)); } - ShowGiftCraftBoxInternal(controller, std::move(converted), true); + ShowGiftCraftBoxInternal(controller, std::move(converted), [] {}, true); } } // namespace Ui diff --git a/Telegram/SourceFiles/boxes/star_gift_craft_box.h b/Telegram/SourceFiles/boxes/star_gift_craft_box.h index 2ff7e17188..ab455df736 100644 --- a/Telegram/SourceFiles/boxes/star_gift_craft_box.h +++ b/Telegram/SourceFiles/boxes/star_gift_craft_box.h @@ -27,7 +27,8 @@ struct GiftForCraftEntry { void ShowGiftCraftInfoBox( not_null controller, std::shared_ptr gift, - Data::SavedStarGiftId savedId); + Data::SavedStarGiftId savedId, + Fn closeParent); void ShowTestGiftCraftBox( not_null controller, diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 2645ba31f5..9a19f6c579 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -1579,7 +1579,11 @@ void GenericCreditsEntryBody( const auto unique = e.uniqueGift; const auto savedId = EntryToSavedStarGiftId(&show->session(), e); if (const auto window = show->resolveWindow()) { - Ui::ShowGiftCraftInfoBox(window, unique, savedId); + const auto closeParent = crl::guard(box, [=] { + box->closeBox(); + }); + using namespace Ui; + ShowGiftCraftInfoBox(window, unique, savedId, closeParent); } } : Fn(); AddUniqueCloseMoreButton(box, st, [=](not_null menu) { diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index bdf47943ef..176806ce7c 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -1685,7 +1685,9 @@ SessionController::SessionController( #if _DEBUG // TEST: Auto-open craft box on startup constexpr auto kGiftsCount = 4; crl::on_main(this, [=] { - return; + if (rand() % 2 == 0) { + return; + } const auto user = session->user(); session->api().request(MTPpayments_GetSavedStarGifts( MTP_flags(MTPpayments_GetSavedStarGifts::Flag::f_exclude_unlimited),