Various improvements

This commit is contained in:
Ilya Laktyushin 2026-01-29 17:02:59 +04:00
parent b459de93b6
commit 4e43b0cbb2
7 changed files with 359 additions and 424 deletions

View file

@ -15714,3 +15714,5 @@ Error: %8$@";
"Gift.Variants.CollectionInfo.CraftableModel_any" = "**%@** craftable models";
"Gift.Variants.ViewCraftableModels" = "View Craftable Models";
"Gift.Variants.ViewPrimaryModels" = "View Primary Models";
"Gift.View.OnSale" = "On sale for %@";

View file

@ -191,9 +191,7 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
self.scrollView = ScrollView()
self.scrollView.delaysContentTouches = false
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
self.scrollView.contentInsetAdjustmentBehavior = .never
}
self.scrollView.contentInsetAdjustmentBehavior = .never
self.scrollView.showsVerticalScrollIndicator = false
self.scrollView.showsHorizontalScrollIndicator = false
self.scrollView.alwaysBounceVertical = true
@ -351,6 +349,9 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
})
} else {
var targetOffset: CGFloat = self.scrollView.contentSize.height + abs(contentView.frame.minY) + 6.0
if self.currentHasInputHeight {
targetOffset += 330.0
}
if self.isCentered {
targetOffset = self.frame.height + self.scrollView.frame.height * 0.5
}

View file

@ -23,6 +23,7 @@ import ThemeCarouselItem
import ThemeAccentColorScreen
import WallpaperGridScreen
import PeerNameColorItem
import DeviceModel
private final class ThemeSettingsControllerArguments {
let context: AccountContext
@ -487,7 +488,9 @@ private func themeSettingsControllerEntries(
}
entries.append(.otherHeader(presentationData.theme, strings.Appearance_Other.uppercased()))
entries.append(.sendWithCmdEnter(presentationData.theme, strings.Appearance_SendWithCmdEnter, chatSettings.sendWithCmdEnter))
if DeviceModel.current.isIpad {
entries.append(.sendWithCmdEnter(presentationData.theme, strings.Appearance_SendWithCmdEnter, chatSettings.sendWithCmdEnter))
}
entries.append(.showNextMediaOnTap(presentationData.theme, strings.Appearance_ShowNextMediaOnTap, mediaSettings.showNextMediaOnTap))
entries.append(.showNextMediaOnTapInfo(presentationData.theme, strings.Appearance_ShowNextMediaOnTapInfo))

View file

@ -1,267 +0,0 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import ComponentFlow
import TelegramCore
import BundleIconComponent
import MultilineTextComponent
import MoreButtonNode
import AccountContext
import TelegramPresentationData
import TelegramStringFormatting
final class PriceButtonComponent: Component {
let price: CurrencyAmount
let dateTimeFormat: PresentationDateTimeFormat
init(
price: CurrencyAmount,
dateTimeFormat: PresentationDateTimeFormat
) {
self.price = price
self.dateTimeFormat = dateTimeFormat
}
static func ==(lhs: PriceButtonComponent, rhs: PriceButtonComponent) -> Bool {
return lhs.price == rhs.price && lhs.dateTimeFormat == rhs.dateTimeFormat
}
final class View: UIView {
private let backgroundView = UIView()
private let icon = UIImageView()
private let text = ComponentView<Empty>()
private var component: PriceButtonComponent?
private weak var state: EmptyComponentState?
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundView.clipsToBounds = true
self.addSubview(self.backgroundView)
self.backgroundView.addSubview(self.icon)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(component: PriceButtonComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
let previousComponent = self.component
self.component = component
self.state = state
if previousComponent?.price.currency != component.price.currency {
switch component.price.currency {
case .stars:
self.icon.image = UIImage(bundleImageName: "Premium/Stars/ButtonStar")?.withRenderingMode(.alwaysTemplate)
case .ton:
self.icon.image = UIImage(bundleImageName: "Ads/TonAbout")?.withRenderingMode(.alwaysTemplate)
}
}
var backgroundSize = CGSize(width: 42.0, height: 30.0)
let textSize = self.text.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: formatCurrencyAmountText(component.price, dateTimeFormat: component.dateTimeFormat),
font: Font.semibold(11.0),
textColor: UIColor(rgb: 0xffffff)
))
)),
environment: {},
containerSize: availableSize
)
let textFrame = CGRect(origin: CGPoint(x: 32.0, y: floorToScreenPixels((backgroundSize.height - textSize.height) / 2.0)), size: textSize)
if let textView = self.text.view {
if textView.superview == nil {
self.backgroundView.addSubview(textView)
}
transition.setFrame(view: textView, frame: textFrame)
}
backgroundSize.width += textSize.width
self.backgroundView.layer.cornerRadius = backgroundSize.height / 2.0
let backgroundColor: UIColor = UIColor(rgb: 0xffffff, alpha: 0.1)
transition.setBackgroundColor(view: self.backgroundView, color: backgroundColor)
let backgroundFrame = CGRect(origin: .zero, size: backgroundSize)
transition.setFrame(view: self.backgroundView, frame: backgroundFrame)
if let iconSize = self.icon.image?.size {
let iconFrame = CGRect(origin: CGPoint(x: 12.0, y: floorToScreenPixels((backgroundSize.height - iconSize.height) / 2.0)), size: iconSize)
transition.setFrame(view: self.icon, frame: iconFrame)
}
self.icon.tintColor = UIColor(rgb: 0xffffff)
return backgroundSize
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
final class ButtonsComponent: Component {
let theme: PresentationTheme
let isOverlay: Bool
let showMoreButton: Bool
let closePressed: () -> Void
let morePressed: (ASDisplayNode, ContextGesture?) -> Void
init(
theme: PresentationTheme,
isOverlay: Bool,
showMoreButton: Bool,
closePressed: @escaping () -> Void,
morePressed: @escaping (ASDisplayNode, ContextGesture?) -> Void
) {
self.theme = theme
self.isOverlay = isOverlay
self.showMoreButton = showMoreButton
self.closePressed = closePressed
self.morePressed = morePressed
}
static func ==(lhs: ButtonsComponent, rhs: ButtonsComponent) -> Bool {
return lhs.theme === rhs.theme && lhs.isOverlay == rhs.isOverlay && lhs.showMoreButton == rhs.showMoreButton
}
final class View: UIView {
private let backgroundView = UIView()
private let closeButton = HighlightTrackingButton()
private let closeIcon = UIImageView()
private let moreNode = MoreButtonNode(theme: defaultPresentationTheme, size: CGSize(width: 36.0, height: 36.0), encircled: false)
private var component: ButtonsComponent?
private weak var state: EmptyComponentState?
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundView.clipsToBounds = true
self.addSubview(self.backgroundView)
self.closeIcon.image = generateCloseButtonImage()
self.moreNode.updateColor(.white, transition: .immediate)
self.backgroundView.addSubview(self.moreNode.view)
self.backgroundView.addSubview(self.closeButton)
self.backgroundView.addSubview(self.closeIcon)
self.closeButton.highligthedChanged = { [weak self] highlighted in
guard let self else {
return
}
if highlighted {
self.closeIcon.layer.removeAnimation(forKey: "opacity")
self.closeIcon.alpha = 0.6
} else {
self.closeIcon.alpha = 1.0
self.closeIcon.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2)
}
}
self.closeButton.addTarget(self, action: #selector(self.closePressed), for: .touchUpInside)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func closePressed() {
guard let component = self.component else {
return
}
component.closePressed()
}
func update(component: ButtonsComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.component = component
self.state = state
let backgroundSize = CGSize(width: component.showMoreButton ? 70.0 : 30.0, height: 30.0)
self.backgroundView.layer.cornerRadius = backgroundSize.height / 2.0
let backgroundColor: UIColor = component.isOverlay ? UIColor(rgb: 0xffffff, alpha: 0.1) : UIColor(rgb: 0x808084, alpha: 0.1)
let foregroundColor: UIColor = component.isOverlay ? .white : component.theme.actionSheet.inputClearButtonColor
transition.setBackgroundColor(view: self.backgroundView, color: backgroundColor)
transition.setTintColor(view: self.closeIcon, color: foregroundColor)
let backgroundFrame = CGRect(origin: .zero, size: backgroundSize)
transition.setFrame(view: self.backgroundView, frame: backgroundFrame)
transition.setFrame(view: self.moreNode.view, frame: CGRect(origin: CGPoint(x: -7.0, y: -4.0), size: CGSize(width: 36.0, height: 36.0)))
transition.setAlpha(view: self.moreNode.view, alpha: component.showMoreButton ? 1.0 : 0.0)
self.moreNode.action = { [weak self] node, gesture in
guard let self, let component = self.component else {
return
}
component.morePressed(node, gesture)
}
let closeFrame = CGRect(origin: CGPoint(x: backgroundSize.width - 30.0 - (component.showMoreButton ? 3.0 : 0.0), y: 0.0), size: CGSize(width: 30.0, height: 30.0))
transition.setFrame(view: self.closeIcon, frame: closeFrame)
transition.setFrame(view: self.closeButton, frame: closeFrame)
return backgroundSize
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
private func generateCloseButtonImage() -> UIImage? {
return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setLineWidth(2.0)
context.setLineCap(.round)
context.setStrokeColor(UIColor.white.cgColor)
context.move(to: CGPoint(x: 10.0, y: 10.0))
context.addLine(to: CGPoint(x: 20.0, y: 20.0))
context.strokePath()
context.move(to: CGPoint(x: 20.0, y: 10.0))
context.addLine(to: CGPoint(x: 10.0, y: 20.0))
context.strokePath()
})?.withRenderingMode(.alwaysTemplate)
}
func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor: UIColor) -> UIImage? {
return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(backgroundColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
context.setLineWidth(2.0)
context.setLineCap(.round)
context.setStrokeColor(foregroundColor.cgColor)
context.move(to: CGPoint(x: 10.0, y: 10.0))
context.addLine(to: CGPoint(x: 20.0, y: 20.0))
context.strokePath()
context.move(to: CGPoint(x: 20.0, y: 10.0))
context.addLine(to: CGPoint(x: 10.0, y: 20.0))
context.strokePath()
})
}

View file

@ -46,6 +46,7 @@ import PeerTableCellComponent
import AvatarComponent
import GlassControls
import GlassBarButtonComponent
import GlassBackgroundComponent
private final class GiftViewSheetContent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
@ -2398,8 +2399,6 @@ private final class GiftViewSheetContent: CombinedComponent {
}
static var body: Body {
let priceButton = Child(PlainButtonComponent.self)
let buttons = Child(GlassControlPanelComponent.self)
let animation = Child(GiftCompositionComponent.self)
let title = Child(MultilineTextComponent.self)
@ -2409,9 +2408,9 @@ private final class GiftViewSheetContent: CombinedComponent {
let description = Child(MultilineTextComponent.self)
let animatedDescription = Child(HStack<Empty>.self)
let transferButton = Child(PlainButtonComponent.self)
let wearButton = Child(PlainButtonComponent.self)
let resellButton = Child(PlainButtonComponent.self)
let transferButton = Child(HeaderButtonComponent.self)
let wearButton = Child(HeaderButtonComponent.self)
let resellButton = Child(HeaderButtonComponent.self)
let wearAvatar = Child(AvatarComponent.self)
let wearPeerName = Child(MultilineTextComponent.self)
@ -2431,7 +2430,10 @@ private final class GiftViewSheetContent: CombinedComponent {
let upgradePerks = Child(List<Empty>.self)
let upgradeKeepName = Child(PlainButtonComponent.self)
let upgradePriceButton = Child(PlainButtonComponent.self)
let variantsMeasureDescription = Child(MultilineTextComponent.self)
let upgradeDescriptionMeasure = Child(MultilineTextComponent.self)
let priceButtonMeasure = Child(MultilineTextWithEntitiesComponent.self)
let priceButton = Child(GlassBarButtonComponent.self)
let spaceRegex = try? NSRegularExpression(pattern: "\\[(.*?)\\]", options: [])
@ -2617,6 +2619,19 @@ private final class GiftViewSheetContent: CombinedComponent {
headerSubject = nil
}
var buttonsBackground: GlassControlGroupComponent.Background = .panel
if let uniqueGift, let backdropAttribute = uniqueGift.attributes.first(where: { attribute in
if case .backdrop = attribute {
return true
} else {
return false
}
}), case let .backdrop(_, _, _, outerColor, _, _, _) = backdropAttribute {
buttonsBackground = .color(UIColor(rgb: UInt32(bitPattern: outerColor)).mixedWith(.white, alpha: 0.2))
} else if showUpgradePreview, let backgroundColor = giftCompositionExternalState.backgroundColor {
buttonsBackground = .color(backgroundColor.mixedWith(.white, alpha: 0.2))
}
var ownerPeerId: EnginePeer.Id?
if let uniqueGift {
if case let .peerId(peerId) = uniqueGift.owner {
@ -2730,6 +2745,16 @@ private final class GiftViewSheetContent: CombinedComponent {
let tableTextColor = theme.list.itemPrimaryTextColor
let tableLinkColor = theme.list.itemAccentColor
var resellAmount: CurrencyAmount?
var selling = false
if let uniqueGift {
if uniqueGift.resellForTonOnly {
resellAmount = uniqueGift.resellAmounts?.first(where: { $0.currency == .ton })
} else {
resellAmount = uniqueGift.resellAmounts?.first(where: { $0.currency == .stars })
}
}
if let headerSubject {
let animation = animation.update(
component: GiftCompositionComponent(
@ -2925,16 +2950,16 @@ private final class GiftViewSheetContent: CombinedComponent {
buttonColor = backgroundColor.mixedWith(.white, alpha: 0.2)
}
let variantsMeasureDescription = variantsMeasureDescription.update(
let upgradeDescriptionMeasure = upgradeDescriptionMeasure.update(
component: MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Gift_Upgrade_ViewAllVariants, font: Font.semibold(13.0), textColor: .clear))),
availableSize: context.availableSize,
transition: .immediate
)
context.add(variantsMeasureDescription
context.add(upgradeDescriptionMeasure
.position(CGPoint(x: -10000.0, y: -10000.0))
)
let variantsButtonSize = CGSize(width: variantsMeasureDescription.size.width + 87.0, height: 24.0)
let variantsButtonSize = CGSize(width: upgradeDescriptionMeasure.size.width + 87.0, height: 24.0)
let upgradeTitle = upgradeTitle.update(
component: MultilineTextComponent(
@ -3153,7 +3178,7 @@ private final class GiftViewSheetContent: CombinedComponent {
}
}
if let releasedBy = uniqueGift.releasedBy, let peer = state.peerMap[releasedBy], let addressName = peer.addressName {
descriptionText = strings.Gift_Unique_CollectibleBy("#\(formatCollectibleNumber(uniqueGift.number, dateTimeFormat: environment.dateTimeFormat))", "[@\(addressName)]()").string
descriptionText = strings.Gift_View_ReleasedBy("[@\(addressName)]()").string
hasDescriptionButton = true
releasedByPeer = peer
}
@ -3300,6 +3325,88 @@ private final class GiftViewSheetContent: CombinedComponent {
})
descriptionOffset += subtitle.size.height
}
if let resellAmount {
if incoming || ownerPeerId == component.context.account.peerId {
var valueString = formatCurrencyAmountText(resellAmount, dateTimeFormat: environment.dateTimeFormat)
switch resellAmount.currency {
case .stars:
valueString = "⭐️\(valueString)"
case .ton:
valueString = "💎\(valueString)"
}
let priceButtonAttributedString = NSMutableAttributedString(string: strings.Gift_View_OnSale(valueString).string, font: Font.regular(13.0), textColor: .white)
let starRange = (priceButtonAttributedString.string as NSString).range(of: "⭐️")
if starRange.location != NSNotFound {
priceButtonAttributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: true)), range: starRange)
priceButtonAttributedString.addAttribute(.baselineOffset, value: 1.0, range: starRange)
}
let tonRange = (priceButtonAttributedString.string as NSString).range(of: "💎")
if tonRange.location != NSNotFound {
priceButtonAttributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 1, file: nil, custom: .ton(tinted: true)), range: tonRange)
priceButtonAttributedString.addAttribute(.baselineOffset, value: 1.0, range: tonRange)
}
let priceButtonMeasure = priceButtonMeasure.update(
component: MultilineTextWithEntitiesComponent(
context: component.context,
animationCache: component.context.animationCache,
animationRenderer: component.context.animationRenderer,
placeholderColor: .white,
text: .plain(priceButtonAttributedString)
),
availableSize: context.availableSize,
transition: context.transition
)
context.add(priceButtonMeasure
.position(CGPoint(x: -10000.0, y: -10000.0))
)
var buttonColor: UIColor = UIColor(rgb: 0xffffff, alpha: 0.1)
if case let .color(color) = buttonsBackground {
buttonColor = color
}
let priceButtonSize = CGSize(width: priceButtonMeasure.size.width + 18.0, height: 19.0)
let priceButton = priceButton.update(
component: GlassBarButtonComponent(
size: priceButtonSize,
backgroundColor: buttonColor,
isDark: true,
state: .tintedGlass,
component: AnyComponentWithIdentity(id: "label", component: AnyComponent(
MultilineTextWithEntitiesComponent(
context: component.context,
animationCache: component.context.animationCache,
animationRenderer: component.context.animationRenderer,
placeholderColor: .white,
text: .plain(priceButtonAttributedString)
)
)),
action: { [weak state] _ in
state?.resellGift(update: true)
}
),
availableSize: priceButtonSize,
transition: context.transition
)
headerComponents.append({
context.add(priceButton
.position(CGPoint(x: context.availableSize.width / 2.0, y: 207.0 + descriptionOffset + priceButton.size.height / 2.0))
.appear(.default(scale: true, alpha: true))
.disappear(.default(scale: true, alpha: true))
)
})
descriptionText = ""
originY += 7.0
}
if case let .uniqueGift(_, recipientPeerId) = component.subject, recipientPeerId != nil {
} else if ownerPeerId != component.context.account.peerId {
selling = true
}
}
var useDescriptionTint = false
if !descriptionText.isEmpty {
var linkColor = theme.actionSheet.controlAccentColor
@ -3787,18 +3894,19 @@ private final class GiftViewSheetContent: CombinedComponent {
let buttonWidth = floor(context.availableSize.width - sideInset * 2.0 - buttonSpacing * CGFloat(buttonsCount - 1)) / CGFloat(buttonsCount)
let buttonHeight: CGFloat = 58.0
var buttonColor: UIColor = UIColor(rgb: 0xffffff, alpha: 0.1)
if case let .color(color) = buttonsBackground {
buttonColor = color
}
var buttonOriginX = sideInset
if canTransfer {
let transferButton = transferButton.update(
component: PlainButtonComponent(
content: AnyComponent(
HeaderButtonComponent(
title: strings.Gift_View_Header_Transfer,
iconName: "Premium/Collectible/Transfer",
isLocked: isMyHostedUniqueGift
)
),
effectAlignment: .center,
component: HeaderButtonComponent(
title: strings.Gift_View_Header_Transfer,
buttonColor: buttonColor,
iconName: "Premium/Collectible/Transfer",
isLocked: isMyHostedUniqueGift,
action: { [weak state] in
state?.transferGift()
}
@ -3819,19 +3927,15 @@ private final class GiftViewSheetContent: CombinedComponent {
}
let wearButton = wearButton.update(
component: PlainButtonComponent(
content: AnyComponent(
HeaderButtonComponent(
title: isWearing ? strings.Gift_View_Header_TakeOff : strings.Gift_View_Header_Wear,
iconName: isWearing ? "Premium/Collectible/Unwear" : "Premium/Collectible/Wear"
)
),
effectAlignment: .center,
component: HeaderButtonComponent(
title: isWearing ? strings.Gift_View_Header_TakeOff : strings.Gift_View_Header_Wear,
buttonColor: buttonColor,
iconName: isWearing ? "Premium/Collectible/Unwear" : "Premium/Collectible/Wear",
action: { [weak state] in
if let state {
if isWearing {
state.commitTakeOff()
state.showAttributeInfo(tag: state.statusTag, text: strings.Gift_View_TookOff("\(uniqueGift.title) #\(formatCollectibleNumber(uniqueGift.number, dateTimeFormat: environment.dateTimeFormat))").string)
} else {
if let controller = controller() as? GiftViewScreen {
@ -3851,7 +3955,7 @@ private final class GiftViewSheetContent: CombinedComponent {
canWear = component.context.isPremium
}
let _ = (ApplicationSpecificNotice.getStarGiftWearTips(accountManager: component.context.sharedContext.accountManager)
|> deliverOnMainQueue).start(next: { [weak state] count in
|> deliverOnMainQueue).start(next: { [weak state] count in
guard let state else {
return
}
@ -3882,15 +3986,11 @@ private final class GiftViewSheetContent: CombinedComponent {
if canResell {
let resellButton = resellButton.update(
component: PlainButtonComponent(
content: AnyComponent(
HeaderButtonComponent(
title: (uniqueGift.resellAmounts ?? []).isEmpty ? strings.Gift_View_Sell : strings.Gift_View_Unlist,
iconName: (uniqueGift.resellAmounts ?? []).isEmpty ? "Premium/Collectible/Sell" : "Premium/Collectible/Unlist",
isLocked: isMyHostedUniqueGift
)
),
effectAlignment: .center,
component: HeaderButtonComponent(
title: (uniqueGift.resellAmounts ?? []).isEmpty ? strings.Gift_View_Sell : strings.Gift_View_Unlist,
buttonColor: buttonColor,
iconName: (uniqueGift.resellAmounts ?? []).isEmpty ? "Premium/Collectible/Sell" : "Premium/Collectible/Unlist",
isLocked: isMyHostedUniqueGift,
action: { [weak state] in
state?.resellGift()
}
@ -4440,44 +4540,6 @@ private final class GiftViewSheetContent: CombinedComponent {
component()
}
var resellAmount: CurrencyAmount?
var selling = false
if let uniqueGift {
if uniqueGift.resellForTonOnly {
resellAmount = uniqueGift.resellAmounts?.first(where: { $0.currency == .ton })
} else {
resellAmount = uniqueGift.resellAmounts?.first(where: { $0.currency == .stars })
}
if let resellAmount, wearPeerNameChild == nil {
if incoming || ownerPeerId == component.context.account.peerId {
let priceButton = priceButton.update(
component: PlainButtonComponent(
content: AnyComponent(
PriceButtonComponent(price: resellAmount, dateTimeFormat: environment.dateTimeFormat)
),
effectAlignment: .center,
action: { [weak state] in
state?.resellGift(update: true)
},
animateScale: false
),
availableSize: CGSize(width: 150.0, height: 30.0),
transition: context.transition
)
context.add(priceButton
.position(CGPoint(x: environment.safeInsets.left + 16.0 + priceButton.size.width / 2.0, y: 76.0 + priceButton.size.height / 2.0))
.appear(.default(scale: true, alpha: true))
.disappear(.default(scale: true, alpha: true))
)
}
if case let .uniqueGift(_, recipientPeerId) = component.subject, recipientPeerId != nil {
} else if ownerPeerId != component.context.account.peerId {
selling = true
}
}
}
var isChatTheme = false
if let controller = controller() as? GiftViewScreen, controller.openChatTheme != nil {
isChatTheme = true
@ -5155,20 +5217,7 @@ private final class GiftViewSheetContent: CombinedComponent {
)
originY += upgradePriceButton.size.height
}
var buttonsBackground: GlassControlGroupComponent.Background = .panel
if let uniqueGift, let backdropAttribute = uniqueGift.attributes.first(where: { attribute in
if case .backdrop = attribute {
return true
} else {
return false
}
}), case let .backdrop(_, _, _, outerColor, _, _, _) = backdropAttribute {
buttonsBackground = .color(UIColor(rgb: UInt32(bitPattern: outerColor)).mixedWith(.white, alpha: 0.2))
} else if showUpgradePreview, let backgroundColor = giftCompositionExternalState.backgroundColor {
buttonsBackground = .color(backgroundColor.mixedWith(.white, alpha: 0.2))
}
var isBackButton = false
if state.inWearPreview || state.inUpgradePreview {
isBackButton = true
@ -5233,6 +5282,11 @@ private final class GiftViewSheetContent: CombinedComponent {
))
}
var buttonsIsDark = theme.overallDarkAppearance
if case .color = buttonsBackground {
buttonsIsDark = true
}
let buttons = buttons.update(
component: GlassControlPanelComponent(
theme: theme,
@ -5246,6 +5300,7 @@ private final class GiftViewSheetContent: CombinedComponent {
background: buttonsBackground
),
centerAlignmentIfPossible: true,
isDark: buttonsIsDark,
tag: state.controlButtonsTag
),
availableSize: CGSize(width: context.availableSize.width - 16.0 * 2.0, height: 44.0),
@ -6095,25 +6150,34 @@ private final class GiftViewContextReferenceContentSource: ContextReferenceConte
}
}
private final class HeaderButtonComponent: CombinedComponent {
private final class HeaderButtonComponent: Component {
let title: String
let buttonColor: UIColor
let iconName: String
let isLocked: Bool
let action: () -> Void
public init(
title: String,
buttonColor: UIColor,
iconName: String,
isLocked: Bool = false
isLocked: Bool = false,
action: @escaping () -> Void
) {
self.title = title
self.buttonColor = buttonColor
self.iconName = iconName
self.isLocked = isLocked
self.action = action
}
static func ==(lhs: HeaderButtonComponent, rhs: HeaderButtonComponent) -> Bool {
if lhs.title != rhs.title {
return false
}
if lhs.buttonColor != rhs.buttonColor {
return false
}
if lhs.iconName != rhs.iconName {
return false
}
@ -6123,79 +6187,205 @@ private final class HeaderButtonComponent: CombinedComponent {
return true
}
static var body: Body {
let background = Child(RoundedRectangle.self)
let title = Child(MultilineTextComponent.self)
let icon = Child(BundleIconComponent.self)
let lockIcon = Child(BundleIconComponent.self)
final class View: UIView {
private var component: HeaderButtonComponent?
private weak var componentState: EmptyComponentState?
return { context in
let component = context.component
private let backgroundView = GlassBackgroundView()
private let title = ComponentView<Empty>()
private let icon = ComponentView<Empty>()
private let lockIcon = ComponentView<Empty>()
private let button = HighlightTrackingButton()
override init(frame: CGRect) {
super.init(frame: frame)
let background = background.update(
component: RoundedRectangle(
color: UIColor.white.withAlphaComponent(0.16),
cornerRadius: 16.0
self.addSubview(self.backgroundView)
self.backgroundView.contentView.addSubview(self.button)
self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func buttonPressed() {
if let component = self.component {
component.action()
}
}
func update(component: HeaderButtonComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.component = component
self.componentState = state
let bounds = CGRect(origin: .zero, size: availableSize)
self.backgroundView.update(size: bounds.size, cornerRadius: 16.0, isDark: true, tintColor: .init(kind: .custom(style: .default, color: component.buttonColor)), isInteractive: true, transition: transition)
transition.setFrame(view: self.backgroundView, frame: bounds)
let iconSize = self.icon.update(
transition: transition,
component: AnyComponent(
BundleIconComponent(
name: component.iconName,
tintColor: UIColor.white
)
),
availableSize: context.availableSize,
transition: .immediate
environment: {},
containerSize: availableSize
)
context.add(background
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
if let iconView = self.icon.view {
if iconView.superview == nil {
iconView.isUserInteractionEnabled = false
self.backgroundView.contentView.addSubview(iconView)
}
iconView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - iconSize.width) / 2.0), y: floorToScreenPixels(22.0 - iconSize.height * 0.5)), size: iconSize)
}
let titleSize = self.title.update(
transition: transition,
component: AnyComponent(
MultilineTextComponent(
text: .plain(NSAttributedString(
string: component.title,
font: Font.regular(11.0),
textColor: UIColor.white,
paragraphAlignment: .natural
)),
horizontalAlignment: .center,
maximumNumberOfLines: 1
)
),
environment: {},
containerSize: CGSize(width: availableSize.width - 16.0, height: availableSize.height)
)
let icon = icon.update(
component: BundleIconComponent(
name: component.iconName,
tintColor: UIColor.white
),
availableSize: context.availableSize,
transition: .immediate
)
context.add(icon
.position(CGPoint(x: context.availableSize.width / 2.0, y: 22.0))
)
let title = title.update(
component: MultilineTextComponent(
text: .plain(NSAttributedString(
string: component.title,
font: Font.regular(11.0),
textColor: UIColor.white,
paragraphAlignment: .natural
)),
horizontalAlignment: .center,
maximumNumberOfLines: 1
),
availableSize: CGSize(width: context.availableSize.width - 16.0, height: context.availableSize.height),
transition: .immediate
)
var totalTitleWidth = title.size.width
var titleOriginX = context.availableSize.width / 2.0 - totalTitleWidth / 2.0
var totalTitleWidth = titleSize.width
var titleOriginX = availableSize.width / 2.0 - totalTitleWidth / 2.0
if component.isLocked {
let titleSpacing: CGFloat = 3.0
let lockIcon = lockIcon.update(
component: BundleIconComponent(
name: "Chat List/StatusLockIcon",
tintColor: UIColor.white
let lockIconSize = self.lockIcon.update(
transition: transition,
component: AnyComponent(
BundleIconComponent(
name: "Chat List/StatusLockIcon",
tintColor: .white
)
),
availableSize: context.availableSize,
transition: .immediate
environment: {},
containerSize: availableSize
)
totalTitleWidth += lockIcon.size.width + titleSpacing
titleOriginX = context.availableSize.width / 2.0 - totalTitleWidth / 2.0
context.add(lockIcon
.position(CGPoint(x: titleOriginX + lockIcon.size.width / 2.0, y: 42.0))
)
titleOriginX += lockIcon.size.width + titleSpacing
totalTitleWidth += lockIconSize.width + titleSpacing
titleOriginX = availableSize.width / 2.0 - totalTitleWidth / 2.0
if let lockIconView = self.lockIcon.view {
if lockIconView.superview == nil {
lockIconView.isUserInteractionEnabled = false
self.backgroundView.contentView.addSubview(lockIconView)
}
lockIconView.frame = CGRect(origin: CGPoint(x: titleOriginX, y: floorToScreenPixels(42.0 - lockIconSize.height * 0.5)), size: lockIconSize)
}
titleOriginX += lockIconSize.width + titleSpacing
}
context.add(title
.position(CGPoint(x: titleOriginX + title.size.width / 2.0, y: 42.0))
)
return context.availableSize
if let titleView = self.title.view {
if titleView.superview == nil {
titleView.isUserInteractionEnabled = false
self.backgroundView.contentView.addSubview(titleView)
}
titleView.frame = CGRect(origin: CGPoint(x: titleOriginX, y: floorToScreenPixels(42.0 - titleSize.height * 0.5)), size: titleSize)
}
self.button.frame = bounds
return availableSize
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
// static var body: Body {
// let background = Child(RoundedRectangle.self)
// let title = Child(MultilineTextComponent.self)
// let icon = Child(BundleIconComponent.self)
// let lockIcon = Child(BundleIconComponent.self)
//
// return { context in
// let component = context.component
//
// let background = background.update(
// component: RoundedRectangle(
// color: UIColor.white.withAlphaComponent(0.16),
// cornerRadius: 16.0
// ),
// availableSize: context.availableSize,
// transition: .immediate
// )
// context.add(background
// .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
// )
//
// let icon = icon.update(
// component: BundleIconComponent(
// name: component.iconName,
// tintColor: UIColor.white
// ),
// availableSize: context.availableSize,
// transition: .immediate
// )
// context.add(icon
// .position(CGPoint(x: context.availableSize.width / 2.0, y: 22.0))
// )
//
// let title = title.update(
// component: MultilineTextComponent(
// text: .plain(NSAttributedString(
// string: component.title,
// font: Font.regular(11.0),
// textColor: UIColor.white,
// paragraphAlignment: .natural
// )),
// horizontalAlignment: .center,
// maximumNumberOfLines: 1
// ),
// availableSize: CGSize(width: context.availableSize.width - 16.0, height: context.availableSize.height),
// transition: .immediate
// )
// var totalTitleWidth = title.size.width
// var titleOriginX = context.availableSize.width / 2.0 - totalTitleWidth / 2.0
// if component.isLocked {
// let titleSpacing: CGFloat = 3.0
// let lockIcon = lockIcon.update(
// component: BundleIconComponent(
// name: "Chat List/StatusLockIcon",
// tintColor: UIColor.white
// ),
// availableSize: context.availableSize,
// transition: .immediate
// )
// totalTitleWidth += lockIcon.size.width + titleSpacing
// titleOriginX = context.availableSize.width / 2.0 - totalTitleWidth / 2.0
// context.add(lockIcon
// .position(CGPoint(x: titleOriginX + lockIcon.size.width / 2.0, y: 42.0))
// )
// titleOriginX += lockIcon.size.width + titleSpacing
// }
// context.add(title
// .position(CGPoint(x: titleOriginX + title.size.width / 2.0, y: 42.0))
// )
//
// return context.availableSize
// }
// }
}
private struct GiftViewConfiguration {

View file

@ -36,6 +36,7 @@ public final class GlassControlPanelComponent: Component {
public let rightItem: Item?
public let centralItem: Item?
public let centerAlignmentIfPossible: Bool
public let isDark: Bool?
public let tag: AnyObject?
public init(
@ -44,6 +45,7 @@ public final class GlassControlPanelComponent: Component {
centralItem: Item?,
rightItem: Item?,
centerAlignmentIfPossible: Bool = false,
isDark: Bool? = nil,
tag: AnyObject? = nil
) {
self.theme = theme
@ -51,6 +53,7 @@ public final class GlassControlPanelComponent: Component {
self.centralItem = centralItem
self.rightItem = rightItem
self.centerAlignmentIfPossible = centerAlignmentIfPossible
self.isDark = isDark
self.tag = tag
}
@ -70,6 +73,9 @@ public final class GlassControlPanelComponent: Component {
if lhs.centerAlignmentIfPossible != rhs.centerAlignmentIfPossible {
return false
}
if lhs.isDark != rhs.isDark {
return false
}
if lhs.tag !== rhs.tag {
return false
}
@ -298,7 +304,7 @@ public final class GlassControlPanelComponent: Component {
}
transition.setFrame(view: self.glassContainerView, frame: CGRect(origin: CGPoint(), size: availableSize))
self.glassContainerView.update(size: availableSize, isDark: component.theme.overallDarkAppearance, transition: transition)
self.glassContainerView.update(size: availableSize, isDark: component.isDark ?? component.theme.overallDarkAppearance, transition: transition)
return availableSize
}

View file

@ -89,7 +89,7 @@ private func generateNumberOffsets() -> [CGPoint] {
CGPoint(x: 0.9900000000000001, y: 0.0),
CGPoint(x: 0.66, y: 0.0),
CGPoint(x: 0.5775, y: 0.0),
CGPoint(x: 1.2375, y: 0.0),
CGPoint(x: 0.7425, y: 0.0),
CGPoint(x: 0.9900000000000001, y: 0.0),
CGPoint(x: 1.32, y: 0.0),
CGPoint(x: 1.155, y: 0.0),