Various improvements

This commit is contained in:
Ilya Laktyushin 2025-12-02 23:02:12 +04:00
parent 5d9d21e0dc
commit 0044bde566
6 changed files with 199 additions and 107 deletions

View file

@ -39,6 +39,8 @@ public final class GiftCompositionComponent: Component {
let animationOffset: CGPoint?
let animationScale: CGFloat?
let displayAnimationStars: Bool
let animateScaleOnTransition: Bool
let alwaysAnimateTransition: Bool
let revealedAttributes: Set<StarGift.UniqueGift.Attribute.AttributeType>
let externalState: ExternalState?
let requestUpdate: (ComponentTransition) -> Void
@ -50,6 +52,8 @@ public final class GiftCompositionComponent: Component {
animationOffset: CGPoint? = nil,
animationScale: CGFloat? = nil,
displayAnimationStars: Bool = false,
animateScaleOnTransition: Bool = true,
alwaysAnimateTransition: Bool = false,
revealedAttributes: Set<StarGift.UniqueGift.Attribute.AttributeType> = Set(),
externalState: ExternalState? = nil,
requestUpdate: @escaping (ComponentTransition) -> Void = { _ in }
@ -60,6 +64,8 @@ public final class GiftCompositionComponent: Component {
self.animationOffset = animationOffset
self.animationScale = animationScale
self.displayAnimationStars = displayAnimationStars
self.animateScaleOnTransition = animateScaleOnTransition
self.alwaysAnimateTransition = alwaysAnimateTransition
self.revealedAttributes = revealedAttributes
self.externalState = externalState
self.requestUpdate = requestUpdate
@ -84,6 +90,9 @@ public final class GiftCompositionComponent: Component {
if lhs.displayAnimationStars != rhs.displayAnimationStars {
return false
}
if lhs.animateScaleOnTransition != rhs.animateScaleOnTransition {
return false
}
if lhs.revealedAttributes != rhs.revealedAttributes {
return false
}
@ -767,7 +776,9 @@ public final class GiftCompositionComponent: Component {
}
var animateTransition = false
if self.animatePreviewTransition {
if component.alwaysAnimateTransition {
animateTransition = true
} else if self.animatePreviewTransition {
animateTransition = true
self.animatePreviewTransition = false
} else if let previousComponent, case .preview = previousComponent.subject, case .unique = component.subject {
@ -788,6 +799,9 @@ public final class GiftCompositionComponent: Component {
bounce = self.previewPatternIndex == -1
background = false
}
if !component.animateScaleOnTransition {
bounce = false
}
backgroundView.animateTransition(background: background, bounce: bounce)
}
if animateBackdropSwipe {

View file

@ -168,6 +168,7 @@ public final class GiftItemComponent: Component {
let isEditing: Bool
let isDateLocked: Bool
let isPlaceholder: Bool
let animateChanges: Bool
let mode: Mode
let action: (() -> Void)?
let contextAction: ((UIView, ContextGesture) -> Void)?
@ -193,6 +194,7 @@ public final class GiftItemComponent: Component {
isEditing: Bool = false,
isDateLocked: Bool = false,
isPlaceholder: Bool = false,
animateChanges: Bool = false,
mode: Mode = .generic,
action: (() -> Void)? = nil,
contextAction: ((UIView, ContextGesture) -> Void)? = nil
@ -217,6 +219,7 @@ public final class GiftItemComponent: Component {
self.isEditing = isEditing
self.isDateLocked = isDateLocked
self.isPlaceholder = isPlaceholder
self.animateChanges = animateChanges
self.mode = mode
self.action = action
self.contextAction = contextAction
@ -283,6 +286,9 @@ public final class GiftItemComponent: Component {
if lhs.isPlaceholder != rhs.isPlaceholder {
return false
}
if lhs.animateChanges != rhs.animateChanges {
return false
}
if lhs.mode != rhs.mode {
return false
}
@ -642,12 +648,22 @@ public final class GiftItemComponent: Component {
}
var animationTransition = transition
var animateBackgroundChange = false
if self.animationLayer == nil || self.animationFile?.fileId != animationFile?.fileId, let emoji {
animationTransition = .immediate
self.animationFile = animationFile
var animateAppearance = false
if let animationLayer = self.animationLayer {
self.animationLayer = nil
animationLayer.removeFromSuperlayer()
if component.animateChanges {
animateAppearance = true
animateBackgroundChange = true
animationLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
animationLayer.removeFromSuperlayer()
})
} else {
animationLayer.removeFromSuperlayer()
}
}
let animationLayer = InlineStickerItemLayer(
context: .account(component.context),
@ -670,6 +686,9 @@ public final class GiftItemComponent: Component {
} else {
self.layer.insertSublayer(animationLayer, above: self.backgroundLayer)
}
if animateAppearance {
animationLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
}
}
let animationFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - iconSize.width) / 2.0), y: component.mode == .generic ? animationOffset : (floorToScreenPixels((size.height - iconSize.height) / 2.0) + explicitAnimationOffset)), size: iconSize)
@ -678,6 +697,11 @@ public final class GiftItemComponent: Component {
}
if let backgroundColor {
if let backgroundView = self.patternView.view as? PeerInfoCoverComponent.View {
if animateBackgroundChange {
backgroundView.animateTransition(background: true, bounce: false)
}
}
let _ = self.patternView.update(
transition: .immediate,
component: AnyComponent(PeerInfoCoverComponent(
@ -694,7 +718,7 @@ public final class GiftItemComponent: Component {
environment: {},
containerSize: backgroundSize
)
if let backgroundView = self.patternView.view {
if let backgroundView = self.patternView.view as? PeerInfoCoverComponent.View {
if backgroundView.superview == nil {
backgroundView.layer.cornerRadius = cornerRadius
if #available(iOS 13.0, *) {
@ -1620,7 +1644,7 @@ public final class StarsButtonContentComponent: Component {
}
}
self.backgroundLayer.backgroundColor = backgroundColor.cgColor
transition.setBackgroundColor(layer: self.backgroundLayer, color: backgroundColor)
transition.setFrame(layer: self.backgroundLayer, frame: CGRect(origin: .zero, size: size))
self.backgroundLayer.cornerRadius = size.height / 2.0

View file

@ -692,7 +692,7 @@ final class GiftOptionsScreenComponent: Component {
action = environment.strings.Gift_Options_Gift_ViewAuction
}
subject = .starGift(gift: gift, price: action)
} else if let availability = gift.availability, availability.remains == 0, let minResaleStars = availability.minResaleStars {
} else if let availability = gift.availability, availability.remains == 0, availability.resale > 0, let minResaleStars = availability.minResaleStars {
let priceString = presentationStringsFormattedNumber(Int32(minResaleStars), environment.dateTimeFormat.groupingSeparator)
if let resaleConfiguration = self.resaleConfiguration, minResaleStars == resaleConfiguration.starGiftResaleMaxStarsAmount || availability.resale == 1 {
subject = .starGift(gift: gift, price: "# \(priceString)")

View file

@ -535,7 +535,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
}), case let .backdrop(_, _, innerColor, outerColor, _, _, _) = backdropAttribute {
let topColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultiplied(hue: 1.01, saturation: 1.22, brightness: 1.04)
let bottomColor = UIColor(rgb: UInt32(bitPattern: outerColor)).withMultiplied(hue: 0.97, saturation: 1.45, brightness: 0.89)
buttonColor = topColor.mixedWith(bottomColor, alpha: 0.8)
buttonColor = topColor.mixedWith(bottomColor, alpha: 0.8).withMultipliedBrightnessBy(1.25)
secondaryTextColor = topColor.withMultiplied(hue: 1.0, saturation: 1.02, brightness: 1.25).mixedWith(UIColor.white, alpha: 0.5)
}
@ -677,11 +677,8 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
)
))
} else {
// var auctionGiftsPerRound: Int32 = 50
// if let auctionGiftsPerRoundValue = gift.auctionGiftsPerRound {
// auctionGiftsPerRound = auctionGiftsPerRoundValue
// }
descriptionText = isUpcoming ? "Upcoming Auction" : "Gift Auction" //strings.Gift_Auction_Description("\(auctionGiftsPerRound)", gift.title ?? "").string
//TODO:localize
descriptionText = isUpcoming ? "Upcoming Auction" : "Gift Auction"
tableItems.append(.init(
id: "start",
@ -787,7 +784,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
height: 24.0
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 50.0, height: CGFloat.greatestFiniteMagnitude),
transition: .immediate
transition: context.transition
)
context.add(description
.position(CGPoint(x: context.availableSize.width / 2.0, y: 167.0 + description.size.height / 2.0))
@ -852,92 +849,6 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
var hasAdditionalButtons = false
if case let .generic(gift) = component.auctionContext.gift, let upgradeVariantsCount = gift.upgradeVariantsCount {
originY += 5.0
var variant1: GiftItemComponent.Subject = .starGift(gift: gift, price: "")
var variant2: GiftItemComponent.Subject = .starGift(gift: gift, price: "")
var variant3: GiftItemComponent.Subject = .starGift(gift: gift, price: "")
if !state.previewModels.isEmpty {
if state.previewModels.count > 0 {
variant1 = .preview(attributes: [state.previewModels[0]], rarity: 0)
}
if state.previewModels.count > 1 {
variant2 = .preview(attributes: [state.previewModels[1]], rarity: 0)
}
if state.previewModels.count > 2 {
variant3 = .preview(attributes: [state.previewModels[2]], rarity: 0)
}
}
let variantsButton = variantsButton.update(
component: PlainButtonComponent(content: AnyComponent(
HStack([
AnyComponentWithIdentity(id: "view", component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Gift_Auction_ViewVariants, font: Font.regular(17.0), textColor: theme.actionSheet.controlAccentColor)))
)),
AnyComponentWithIdentity(id: "spacing", component: AnyComponent(
Rectangle(color: .clear, width: 8.0, height: 1.0)
)),
AnyComponentWithIdentity(id: "icon1", component: AnyComponent(
GiftItemComponent(
context: component.context,
theme: theme,
strings: strings,
peer: nil,
subject: variant1,
isPlaceholder: state.previewModels.isEmpty,
mode: .buttonIcon
)
)),
AnyComponentWithIdentity(id: "icon2", component: AnyComponent(
GiftItemComponent(
context: component.context,
theme: theme,
strings: strings,
peer: nil,
subject: variant2,
isPlaceholder: state.previewModels.isEmpty,
mode: .buttonIcon
)
)),
AnyComponentWithIdentity(id: "icon3", component: AnyComponent(
GiftItemComponent(
context: component.context,
theme: theme,
strings: strings,
peer: nil,
subject: variant3,
isPlaceholder: state.previewModels.isEmpty,
mode: .buttonIcon
)
)),
AnyComponentWithIdentity(id: "text", component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: " \(strings.Gift_Auction_Variants(upgradeVariantsCount))", font: Font.regular(17.0), textColor: theme.actionSheet.controlAccentColor)))
)),
AnyComponentWithIdentity(id: "arrow", component: AnyComponent(
BundleIconComponent(name: "Chat/Context Menu/Arrow", tintColor: theme.actionSheet.controlAccentColor)
))
], spacing: 0.0)
), action: { [weak state] in
guard let state, let attributes = state.giftUpgradeAttributes else {
return
}
let variantsController = component.context.sharedContext.makeGiftUpgradeVariantsPreviewScreen(context: component.context, gift: .generic(gift), attributes: attributes)
environment.controller()?.push(variantsController)
}, animateScale: false),
availableSize: CGSize(width: context.availableSize.width - 64.0, height: context.availableSize.height),
transition: context.transition
)
context.add(variantsButton
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + variantsButton.size.height / 2.0)))
originY += variantsButton.size.height
originY += 12.0
hasAdditionalButtons = true
}
let acquiredGiftsCount = state.giftAuctionState?.myState.acquiredCount ?? 0
if acquiredGiftsCount > 0, case let .generic(gift) = component.auctionContext.gift {
originY += 5.0
@ -1078,6 +989,96 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
}
}
if case let .generic(gift) = component.auctionContext.gift, let upgradeVariantsCount = gift.upgradeVariantsCount {
originY += 5.0
if !hasAdditionalButtons {
originY -= 13.0
}
var variant1: GiftItemComponent.Subject = .starGift(gift: gift, price: "")
var variant2: GiftItemComponent.Subject = .starGift(gift: gift, price: "")
var variant3: GiftItemComponent.Subject = .starGift(gift: gift, price: "")
if !state.previewModels.isEmpty {
if state.previewModels.count > 0 {
variant1 = .preview(attributes: [state.previewModels[0]], rarity: 0)
}
if state.previewModels.count > 1 {
variant2 = .preview(attributes: [state.previewModels[1]], rarity: 0)
}
if state.previewModels.count > 2 {
variant3 = .preview(attributes: [state.previewModels[2]], rarity: 0)
}
}
let variantsButton = variantsButton.update(
component: PlainButtonComponent(content: AnyComponent(
HStack([
AnyComponentWithIdentity(id: "view", component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Gift_Auction_ViewVariants, font: Font.regular(13.0), textColor: theme.actionSheet.controlAccentColor)))
)),
AnyComponentWithIdentity(id: "spacing", component: AnyComponent(
Rectangle(color: .clear, width: 7.0, height: 1.0)
)),
AnyComponentWithIdentity(id: "icon1", component: AnyComponent(
GiftItemComponent(
context: component.context,
theme: theme,
strings: strings,
peer: nil,
subject: variant1,
isPlaceholder: state.previewModels.isEmpty,
mode: .tableIcon
)
)),
AnyComponentWithIdentity(id: "icon2", component: AnyComponent(
GiftItemComponent(
context: component.context,
theme: theme,
strings: strings,
peer: nil,
subject: variant2,
isPlaceholder: state.previewModels.isEmpty,
mode: .tableIcon
)
)),
AnyComponentWithIdentity(id: "icon3", component: AnyComponent(
GiftItemComponent(
context: component.context,
theme: theme,
strings: strings,
peer: nil,
subject: variant3,
isPlaceholder: state.previewModels.isEmpty,
mode: .tableIcon
)
)),
AnyComponentWithIdentity(id: "text", component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: " \(strings.Gift_Auction_Variants(upgradeVariantsCount))", font: Font.regular(13.0), textColor: theme.actionSheet.controlAccentColor)))
)),
AnyComponentWithIdentity(id: "arrow", component: AnyComponent(
BundleIconComponent(name: "Item List/InlineTextRightArrow", tintColor: theme.actionSheet.controlAccentColor)
))
], spacing: 0.0)
), action: { [weak state] in
guard let state, let attributes = state.giftUpgradeAttributes else {
return
}
let variantsController = component.context.sharedContext.makeGiftUpgradeVariantsPreviewScreen(context: component.context, gift: .generic(gift), attributes: attributes)
environment.controller()?.push(variantsController)
}, animateScale: false),
availableSize: CGSize(width: context.availableSize.width - 64.0, height: context.availableSize.height),
transition: context.transition
)
context.add(variantsButton
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + variantsButton.size.height / 2.0)))
originY += variantsButton.size.height
originY += 12.0
originY -= 15.0
hasAdditionalButtons = true
}
if hasAdditionalButtons {
originY += 21.0
}
@ -1200,7 +1201,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
let closeButton = closeButton.update(
component: GlassBarButtonComponent(
size: CGSize(width: 40.0, height: 40.0),
backgroundColor: buttonColor.withMultipliedBrightnessBy(1.2),
backgroundColor: buttonColor,
isDark: theme.overallDarkAppearance,
state: .tintedGlass,
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
@ -1217,7 +1218,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
}
),
availableSize: CGSize(width: 40.0, height: 40.0),
transition: .immediate
transition: context.transition
)
context.add(closeButton
.position(CGPoint(x: 16.0 + closeButton.size.width / 2.0, y: 16.0 + closeButton.size.height / 2.0))
@ -1226,7 +1227,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
let moreButton = moreButton.update(
component: GlassBarButtonComponent(
size: CGSize(width: 40.0, height: 40.0),
backgroundColor: buttonColor.withMultipliedBrightnessBy(1.2),
backgroundColor: buttonColor,
isDark: theme.overallDarkAppearance,
state: .tintedGlass,
component: AnyComponentWithIdentity(id: "more", component: AnyComponent(
@ -1248,7 +1249,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
}
),
availableSize: CGSize(width: 40.0, height: 40.0),
transition: .immediate
transition: context.transition
)
context.add(moreButton
.position(CGPoint(x: context.availableSize.width - 16.0 - moreButton.size.width / 2.0, y: 16.0 + moreButton.size.height / 2.0))

View file

@ -169,7 +169,7 @@ private final class GiftAuctionWearPreviewSheetContent: CombinedComponent {
let arrow = Child(BundleIconComponent.self)
let upgradeLabel = Child(MultilineTextComponent.self)
let remainingCount = Child(GiftRemainingCountComponent.self)
//let auctionFooter = Child(MultilineTextComponent.self)
let auctionFooter = Child(MultilineTextComponent.self)
let button = Child(ButtonComponent.self)
@ -231,6 +231,7 @@ private final class GiftAuctionWearPreviewSheetContent: CombinedComponent {
animationOffset: animationOffset,
animationScale: animationScale,
displayAnimationStars: true,
animateScaleOnTransition: false,
externalState: giftCompositionExternalState,
requestUpdate: { [weak state] transition in
state?.updated(transition: transition)
@ -370,6 +371,7 @@ private final class GiftAuctionWearPreviewSheetContent: CombinedComponent {
strings: strings,
subject: .preview(attributes: attributes, rarity: 0),
ribbon: GiftItemComponent.Ribbon(text: "upgraded", color: ribbonColor),
animateChanges: true,
mode: .thumbnail
),
availableSize: CGSize(width: 120.0, height: 120.0),
@ -440,8 +442,57 @@ private final class GiftAuctionWearPreviewSheetContent: CombinedComponent {
context.add(remainingCount
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight))
)
if let giftsPerRound = gift.auctionGiftsPerRound {
let footerAttributes = MarkdownAttributes(
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.list.freeTextColor),
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.list.freeTextColor),
link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.list.itemAccentColor),
linkAttribute: { contents in
return (TelegramTextAttributes.URL, contents)
}
)
let parsedString = parseMarkdownIntoAttributedString(strings.Gift_Setup_AuctionInfo(environment.strings.Gift_Setup_AuctionInfo_Gifts(giftsPerRound), strings.Gift_Setup_AuctionInfo_Bidders(giftsPerRound)).string, attributes: footerAttributes)
let auctionFooterText = NSMutableAttributedString(attributedString: parsedString)
if state.cachedSmallChevronImage == nil || state.cachedSmallChevronImage?.1 !== environment.theme {
state.cachedSmallChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: theme.list.itemAccentColor)!, environment.theme)
}
if let range = auctionFooterText.string.range(of: ">"), let chevronImage = state.cachedSmallChevronImage?.0 {
auctionFooterText.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: auctionFooterText.string))
}
let auctionFooter = auctionFooter.update(
component: MultilineTextComponent(
text: .plain(auctionFooterText),
maximumNumberOfLines: 0,
highlightColor: theme.list.itemAccentColor.withAlphaComponent(0.1),
highlightInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: -8.0),
highlightAction: { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
} else {
return nil
}
},
tapAction: { _, _ in
guard let controller = controller() else {
return
}
let infoController = component.context.sharedContext.makeGiftAuctionInfoScreen(context: component.context, auctionContext: component.auctionContext, completion: nil)
controller.push(infoController)
}
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 16.0 * 2.0, height: 10000.0),
transition: context.transition
)
context.add(auctionFooter
.position(CGPoint(x: sideInset + 16.0 + auctionFooter.size.width * 0.5, y: contentHeight + 52.0 + auctionFooter.size.height * 0.5))
)
contentHeight += auctionFooter.size.height
}
}
contentHeight += 110.0
contentHeight += 80.0
var buttonTitle = strings.Gift_Auction_Join
if component.auctionContext.isUpcoming {
@ -521,6 +572,7 @@ final class GiftAuctionWearPreviewSheetComponent: CombinedComponent {
return { context in
let environment = context.environment[EnvironmentType.self]
let controller = environment.controller
let theme = environment.theme.withModalBlocksBackground()
let sheet = sheet.update(
component: SheetComponent<EnvironmentType>(
@ -532,7 +584,7 @@ final class GiftAuctionWearPreviewSheetComponent: CombinedComponent {
getController: controller
)),
style: .glass,
backgroundColor: .color(environment.theme.list.blocksBackgroundColor),
backgroundColor: .color(theme.list.blocksBackgroundColor),
followContentSizeChanges: true,
clipsContent: true,
hasDimView: false,

View file

@ -665,7 +665,7 @@ private final class GiftUpgradePreviewScreenComponent: Component {
}), case let .backdrop(_, _, innerColor, outerColor, _, _, _) = backdropAttribute {
let topColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultiplied(hue: 1.01, saturation: 1.22, brightness: 1.04)
let bottomColor = UIColor(rgb: UInt32(bitPattern: outerColor)).withMultiplied(hue: 0.97, saturation: 1.45, brightness: 0.89)
buttonColor = topColor.mixedWith(bottomColor, alpha: 0.8).withMultipliedBrightnessBy(1.2)
buttonColor = topColor.mixedWith(bottomColor, alpha: 0.8).withMultipliedBrightnessBy(1.25)
secondaryTextColor = topColor.withMultiplied(hue: 1.0, saturation: 1.02, brightness: 1.25).mixedWith(UIColor.white, alpha: 0.3)
}
@ -680,6 +680,7 @@ private final class GiftUpgradePreviewScreenComponent: Component {
animationOffset: CGPoint(x: 0.0, y: 20.0),
animationScale: nil,
displayAnimationStars: false,
alwaysAnimateTransition: true,
revealedAttributes: Set(),
externalState: self.giftCompositionExternalState,
requestUpdate: { [weak state] transition in
@ -944,7 +945,7 @@ private final class GiftUpgradePreviewScreenComponent: Component {
self.glassContainerView.frame = CGRect(origin: CGPoint(x: rawSideInset, y: 0.0), size: CGSize(width: fillingSize, height: 64.0))
let closeButtonSize = self.closeButton.update(
transition: .immediate,
transition: transition,
component: AnyComponent(GlassBarButtonComponent(
size: CGSize(width: 40.0, height: 40.0),
backgroundColor: buttonColor,